diff --git a/shaarli-pro/css/style.css b/shaarli-pro/css/style.css index 418b0b9..03afb13 100644 --- a/shaarli-pro/css/style.css +++ b/shaarli-pro/css/style.css @@ -898,8 +898,9 @@ input:checked+.theme-slider:before { /* Results List */ .search-results { - max-height: 320px; + max-height: 60vh; overflow-y: auto; + overflow-x: hidden; padding: 0.5rem 0; } @@ -4505,4 +4506,19 @@ table { justify-content: center; padding: 0.55rem 0.45rem; } + + /* Mobile search panel - almost full screen */ + .search-overlay { + padding-top: 5vh; + padding-bottom: 5vh; + } + + .search-modal { + width: 95%; + max-height: 90vh; + } + + .search-results { + max-height: calc(90vh - 200px); + } } diff --git a/shaarli-pro/hidden-tags.html b/shaarli-pro/hidden-tags.html new file mode 100644 index 0000000..8f5a1ea --- /dev/null +++ b/shaarli-pro/hidden-tags.html @@ -0,0 +1,412 @@ + + + + + {$pageName="hidden-tags"} + {include="includes"} + + + + + {include="page.header"} + +
+
+
+
+
+ {'Hidden Tags'|t} + + + {'Saved'|t} + +
+
+
+

+ {'What are hidden tags?'|t}
+ {'Hidden tags are system tags that are used by the application but not displayed to users. They are still functional - bookmarks with these tags work normally, but the tags themselves are hidden from view in lists, clouds, and bookmark cards.'|t} +

+
+ +
+

{'Preset System Tags'|t}

+
+ +
+
+ +
+

{'Custom Hidden Tags'|t}

+

+ {'Add any additional tags you want to hide from display:'|t} +

+
+ + +
+
+ +
+
+
+ +
+
+
+
+ + + + {include="page.footer"} + + + diff --git a/shaarli-pro/js/script.js b/shaarli-pro/js/script.js index f87bdb4..7ee15d6 100644 --- a/shaarli-pro/js/script.js +++ b/shaarli-pro/js/script.js @@ -84,6 +84,124 @@ document.addEventListener('DOMContentLoaded', () => { } }); + // ===== Hidden Tags Management ===== + const HIDDEN_TAGS_STORAGE_KEY = 'shaarli_hidden_tags'; + + // Default system tags that are hidden by default + const DEFAULT_HIDDEN_TAGS = [ + 'note', + 'shaarli-pin', + 'note-color-*', + 'notebg-*', + 'notefilter-*', + 'readitlater', + 'shaarli-archiver' + ]; + + // Get hidden tags from localStorage + function getHiddenTags() { + try { + const stored = localStorage.getItem(HIDDEN_TAGS_STORAGE_KEY); + if (stored) { + return JSON.parse(stored); + } + } catch (e) { + console.error('Error loading hidden tags:', e); + } + return [...DEFAULT_HIDDEN_TAGS]; + } + + // Check if a tag matches a wildcard pattern + function matchesWildcard(pattern, tag) { + const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i'); + return regex.test(tag); + } + + // Check if a tag should be hidden + function isTagHidden(tag) { + if (!tag) return false; + const normalizedTag = tag.toLowerCase().trim(); + const hiddenTags = getHiddenTags(); + + return hiddenTags.some(hiddenTag => { + const normalizedHidden = hiddenTag.toLowerCase().trim(); + if (normalizedHidden.includes('*')) { + return matchesWildcard(normalizedHidden, normalizedTag); + } + return normalizedHidden === normalizedTag; + }); + } + + // Filter out hidden tags from a list + function filterHiddenTags(tags) { + if (!Array.isArray(tags)) return []; + return tags.filter(tag => !isTagHidden(tag)); + } + + // Hide tag elements in the DOM + function hideHiddenTagElements() { + // Hide tag chips in bookmark cards + document.querySelectorAll('.link-tag[data-tag]').forEach(el => { + const tag = el.getAttribute('data-tag'); + if (isTagHidden(tag)) { + el.style.display = 'none'; + } + }); + + // Hide tags in tag list + document.querySelectorAll('#tagListContainer .list-group-item[data-tag-name]').forEach(el => { + const tagName = el.getAttribute('data-tag-name'); + if (isTagHidden(tagName)) { + el.style.display = 'none'; + } + }); + + // Hide tags in tag cloud + document.querySelectorAll('.tag-item[data-tag]').forEach(el => { + const tag = el.getAttribute('data-tag'); + if (isTagHidden(tag)) { + el.style.display = 'none'; + } + }); + + // Update visible tag count in tag list + const visibleTagCountEl = document.getElementById('visibleTagCount'); + if (visibleTagCountEl) { + const visibleTags = document.querySelectorAll('#tagListContainer .list-group-item:not([style*="display: none"])'); + visibleTagCountEl.textContent = visibleTags.length; + } + } + + // Run on page load + hideHiddenTagElements(); + + // Set up MutationObserver to handle dynamically added tags + const tagObserver = new MutationObserver((mutations) => { + let shouldFilter = false; + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + // Check if added node contains tags + if (node.matches?.('.link-tag[data-tag], .tag-item[data-tag], [data-tag-name], .list-group-item[data-tag-name]') || + node.querySelector?.('.link-tag[data-tag], .tag-item[data-tag], [data-tag-name], .list-group-item[data-tag-name]')) { + shouldFilter = true; + } + } + }); + } + }); + if (shouldFilter) { + hideHiddenTagElements(); + } + }); + + // Start observing once DOM is ready + tagObserver.observe(document.body, { + childList: true, + subtree: true + }); + // ===== Search Overlay (Spotlight Style) ===== try { const searchOverlay = document.getElementById('search-overlay'); @@ -127,7 +245,7 @@ document.addEventListener('DOMContentLoaded', () => { return text.toLowerCase().includes(query.toLowerCase()); } - // Fetch all unique tags from the page + // Fetch all unique tags from the page (excluding hidden tags) function fetchTags() { if (cachedTags) return cachedTags; try { @@ -137,6 +255,8 @@ document.addEventListener('DOMContentLoaded', () => { if (typeof value !== 'string') return; const cleaned = value.trim(); if (!cleaned || cleaned.includes('•')) return; + // Skip hidden tags + if (isTagHidden(cleaned)) return; tagsSet.add(cleaned); }; @@ -172,7 +292,10 @@ document.addEventListener('DOMContentLoaded', () => { isNote: (el.querySelector('.link-title')?.getAttribute('title') || '').toLowerCase().includes('note'), title: el.querySelector('.link-title a')?.textContent || el.querySelector('.link-title')?.textContent || '', url: el.querySelector('.link-url a')?.href || el.querySelector('.link-title a')?.href || '', - tags: Array.from(el.querySelectorAll('.link-tag[data-tag]')).map(t => (t.getAttribute('data-tag') || '').trim()).filter(Boolean), + // Filter out hidden tags + tags: filterHiddenTags( + Array.from(el.querySelectorAll('.link-tag[data-tag]')).map(t => (t.getAttribute('data-tag') || '').trim()).filter(Boolean) + ), description: el.querySelector('.link-description')?.textContent || '' })); return cachedBookmarks; diff --git a/shaarli-pro/tools.html b/shaarli-pro/tools.html index 0a36313..79e099b 100644 --- a/shaarli-pro/tools.html +++ b/shaarli-pro/tools.html @@ -66,6 +66,16 @@ + +
+ +
+
+
{'Hidden Tags'|t}
+
{'Manage system tags hidden from display'|t}
+
+ +
@@ -91,6 +101,59 @@
+ + + {if="!empty($linkcount)"}
@@ -184,6 +247,380 @@ {/loop}
{include="page.footer"} + + + + \ No newline at end of file