feat: implémenter système de gestion des tags cachés avec panel de configuration, présets système (note/shaarli-pin/readitlater/archiver), support wildcards (note-color-*/notebg-*/notefilter-*), ajout/suppression tags personnalisés, filtrage automatique DOM avec MutationObserver, exclusion tags cachés dans recherche/bookmarks/tag cloud, amélioration responsive modale recherche mobile (95% width, 90vh height, padding 5vh), et styles modernes avec gradients bleus, chips interactifs, toggle
This commit is contained in:
parent
f6862609c0
commit
3f9e481cf9
@ -898,8 +898,9 @@ input:checked+.theme-slider:before {
|
|||||||
|
|
||||||
/* Results List */
|
/* Results List */
|
||||||
.search-results {
|
.search-results {
|
||||||
max-height: 320px;
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4505,4 +4506,19 @@ table {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0.55rem 0.45rem;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
412
shaarli-pro/hidden-tags.html
Normal file
412
shaarli-pro/hidden-tags.html
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html{if="$language !=='auto'"} lang=" {$language}"{/if}>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
{$pageName="hidden-tags"}
|
||||||
|
{include="includes"}
|
||||||
|
<style>
|
||||||
|
.hidden-tags-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-section h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tags-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-item:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: var(--bg-card-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-item input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-item label {
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-item .tag-description {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tags-section {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-input-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-input-row input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tags-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
background: var(--primary);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-chip button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-chip button:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-info {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-left: 3px solid var(--primary);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0 0.5rem 0.5rem 0;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-info p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-info strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--success, #22c55e);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-status.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{include="page.header"}
|
||||||
|
|
||||||
|
<div class="container page-hidden-tags">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 col-md-offset-2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
{'Hidden Tags'|t}
|
||||||
|
<span class="save-status" id="saveStatus">
|
||||||
|
<i class="mdi mdi-check-circle"></i>
|
||||||
|
{'Saved'|t}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="hidden-tags-info">
|
||||||
|
<p>
|
||||||
|
<strong>{'What are hidden tags?'|t}</strong><br>
|
||||||
|
{'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}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hidden-tags-section">
|
||||||
|
<h3>{'Preset System Tags'|t}</h3>
|
||||||
|
<div class="preset-tags-grid" id="presetTagsGrid">
|
||||||
|
<!-- Preset tags will be populated by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="custom-tags-section">
|
||||||
|
<h3>{'Custom Hidden Tags'|t}</h3>
|
||||||
|
<p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem;">
|
||||||
|
{'Add any additional tags you want to hide from display:'|t}
|
||||||
|
</p>
|
||||||
|
<div class="custom-tag-input-row">
|
||||||
|
<input type="text" id="customTagInput" class="form-control" placeholder="{'Enter tag name'|t}...">
|
||||||
|
<button type="button" id="addCustomTagBtn" class="btn btn-primary">
|
||||||
|
<i class="mdi mdi-plus"></i>
|
||||||
|
{'Add'|t}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="custom-tags-list" id="customTagsList">
|
||||||
|
<!-- Custom tags will be populated by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button type="button" id="resetDefaultsBtn" class="btn btn-secondary">{'Reset to Defaults'|t}</button>
|
||||||
|
<a href="{$base_path}/admin/tools" class="btn btn-primary">{'Back to Tools'|t}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
// Default system tags with descriptions
|
||||||
|
const PRESET_TAGS = [
|
||||||
|
{ tag: 'note', description: 'Note bookmarks' },
|
||||||
|
{ tag: 'shaarli-pin', description: 'Pinned items' },
|
||||||
|
{ tag: 'note-color-*', description: 'Note color variants' },
|
||||||
|
{ tag: 'notebg-*', description: 'Note background images' },
|
||||||
|
{ tag: 'notefilter-*', description: 'Note filter categories' },
|
||||||
|
{ tag: 'readitlater', description: 'Read It Later items' },
|
||||||
|
{ tag: 'shaarli-archiver', description: 'Archived notes' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'shaarli_hidden_tags';
|
||||||
|
|
||||||
|
// Default hidden tags (all preset tags are hidden by default)
|
||||||
|
const DEFAULT_HIDDEN_TAGS = PRESET_TAGS.map(t => t.tag);
|
||||||
|
|
||||||
|
function getHiddenTags() {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (stored) {
|
||||||
|
return JSON.parse(stored);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading hidden tags:', e);
|
||||||
|
}
|
||||||
|
return [...DEFAULT_HIDDEN_TAGS];
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveHiddenTags(tags) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(tags));
|
||||||
|
showSaveStatus();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving hidden tags:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSaveStatus() {
|
||||||
|
const status = document.getElementById('saveStatus');
|
||||||
|
if (status) {
|
||||||
|
status.classList.add('visible');
|
||||||
|
setTimeout(() => {
|
||||||
|
status.classList.remove('visible');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWildcardTag(tag) {
|
||||||
|
return tag.includes('*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesWildcard(pattern, tag) {
|
||||||
|
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i');
|
||||||
|
return regex.test(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTagHidden(tag, hiddenTags) {
|
||||||
|
const normalizedTag = tag.toLowerCase().trim();
|
||||||
|
return hiddenTags.some(hiddenTag => {
|
||||||
|
const normalizedHidden = hiddenTag.toLowerCase().trim();
|
||||||
|
if (isWildcardTag(normalizedHidden)) {
|
||||||
|
return matchesWildcard(normalizedHidden, normalizedTag);
|
||||||
|
}
|
||||||
|
return normalizedHidden === normalizedTag;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make isTagHidden globally available
|
||||||
|
window.isTagHidden = isTagHidden;
|
||||||
|
window.getHiddenTags = getHiddenTags;
|
||||||
|
|
||||||
|
function renderPresetTags(hiddenTags) {
|
||||||
|
const grid = document.getElementById('presetTagsGrid');
|
||||||
|
if (!grid) return;
|
||||||
|
|
||||||
|
grid.innerHTML = PRESET_TAGS.map(({ tag, description }) => {
|
||||||
|
const isChecked = hiddenTags.includes(tag);
|
||||||
|
const tagId = 'preset-' + tag.replace(/[^a-zA-Z0-9]/g, '_');
|
||||||
|
return `
|
||||||
|
<div class="preset-tag-item">
|
||||||
|
<input type="checkbox" id="${tagId}" data-tag="${tag}" ${isChecked ? 'checked' : ''}>
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<label for="${tagId}">${tag}</label>
|
||||||
|
<div class="tag-description">${description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
grid.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', function() {
|
||||||
|
const tag = this.dataset.tag;
|
||||||
|
let hiddenTags = getHiddenTags();
|
||||||
|
|
||||||
|
if (this.checked) {
|
||||||
|
if (!hiddenTags.includes(tag)) {
|
||||||
|
hiddenTags.push(tag);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hiddenTags = hiddenTags.filter(t => t !== tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveHiddenTags(hiddenTags);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCustomTags(hiddenTags) {
|
||||||
|
const list = document.getElementById('customTagsList');
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
// Filter out preset tags to show only custom ones
|
||||||
|
const customTags = hiddenTags.filter(tag => {
|
||||||
|
return !PRESET_TAGS.some(preset => preset.tag === tag);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (customTags.length === 0) {
|
||||||
|
list.innerHTML = '<span style="color: var(--text-muted); font-size: 0.875rem;">{literal}No custom hidden tags{/literal}</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = customTags.map(tag => `
|
||||||
|
<span class="custom-tag-chip">
|
||||||
|
${tag}
|
||||||
|
<button type="button" data-tag="${tag}" title="{literal}Remove{/literal}">×</button>
|
||||||
|
</span>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
// Add event listeners for remove buttons
|
||||||
|
list.querySelectorAll('button').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const tag = this.dataset.tag;
|
||||||
|
let hiddenTags = getHiddenTags();
|
||||||
|
hiddenTags = hiddenTags.filter(t => t !== tag);
|
||||||
|
saveHiddenTags(hiddenTags);
|
||||||
|
renderCustomTags(hiddenTags);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCustomTag() {
|
||||||
|
const input = document.getElementById('customTagInput');
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
const tag = input.value.trim();
|
||||||
|
if (!tag) return;
|
||||||
|
|
||||||
|
// Don't add if it's a preset tag
|
||||||
|
if (PRESET_TAGS.some(preset => preset.tag === tag)) {
|
||||||
|
alert('This tag is already managed in the preset list.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hiddenTags = getHiddenTags();
|
||||||
|
if (!hiddenTags.includes(tag)) {
|
||||||
|
hiddenTags.push(tag);
|
||||||
|
saveHiddenTags(hiddenTags);
|
||||||
|
renderCustomTags(hiddenTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.value = '';
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetToDefaults() {
|
||||||
|
if (confirm('{literal}Reset to default hidden tags? This will restore all preset system tags.{/literal}')) {
|
||||||
|
saveHiddenTags([...DEFAULT_HIDDEN_TAGS]);
|
||||||
|
renderPresetTags(DEFAULT_HIDDEN_TAGS);
|
||||||
|
renderCustomTags(DEFAULT_HIDDEN_TAGS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const hiddenTags = getHiddenTags();
|
||||||
|
renderPresetTags(hiddenTags);
|
||||||
|
renderCustomTags(hiddenTags);
|
||||||
|
|
||||||
|
// Add custom tag button
|
||||||
|
const addBtn = document.getElementById('addCustomTagBtn');
|
||||||
|
if (addBtn) {
|
||||||
|
addBtn.addEventListener('click', addCustomTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter key in input
|
||||||
|
const input = document.getElementById('customTagInput');
|
||||||
|
if (input) {
|
||||||
|
input.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
addCustomTag();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset button
|
||||||
|
const resetBtn = document.getElementById('resetDefaultsBtn');
|
||||||
|
if (resetBtn) {
|
||||||
|
resetBtn.addEventListener('click', resetToDefaults);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{include="page.footer"}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -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) =====
|
// ===== Search Overlay (Spotlight Style) =====
|
||||||
try {
|
try {
|
||||||
const searchOverlay = document.getElementById('search-overlay');
|
const searchOverlay = document.getElementById('search-overlay');
|
||||||
@ -127,7 +245,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return text.toLowerCase().includes(query.toLowerCase());
|
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() {
|
function fetchTags() {
|
||||||
if (cachedTags) return cachedTags;
|
if (cachedTags) return cachedTags;
|
||||||
try {
|
try {
|
||||||
@ -137,6 +255,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (typeof value !== 'string') return;
|
if (typeof value !== 'string') return;
|
||||||
const cleaned = value.trim();
|
const cleaned = value.trim();
|
||||||
if (!cleaned || cleaned.includes('•')) return;
|
if (!cleaned || cleaned.includes('•')) return;
|
||||||
|
// Skip hidden tags
|
||||||
|
if (isTagHidden(cleaned)) return;
|
||||||
tagsSet.add(cleaned);
|
tagsSet.add(cleaned);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -172,7 +292,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
isNote: (el.querySelector('.link-title')?.getAttribute('title') || '').toLowerCase().includes('note'),
|
isNote: (el.querySelector('.link-title')?.getAttribute('title') || '').toLowerCase().includes('note'),
|
||||||
title: el.querySelector('.link-title a')?.textContent || el.querySelector('.link-title')?.textContent || '',
|
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 || '',
|
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 || ''
|
description: el.querySelector('.link-description')?.textContent || ''
|
||||||
}));
|
}));
|
||||||
return cachedBookmarks;
|
return cachedBookmarks;
|
||||||
|
|||||||
@ -66,6 +66,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<i class="mdi mdi-chevron-right"></i>
|
<i class="mdi mdi-chevron-right"></i>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="list-group-item list-group-item-action ripple" href="#" id="hidden-tags-toggle">
|
||||||
|
<div class="list-sortable-handle">
|
||||||
|
<i class="mdi mdi-tag-off-outline"></i>
|
||||||
|
</div>
|
||||||
|
<div class="list-group-item-content">
|
||||||
|
<div class="list-group-item-label">{'Hidden Tags'|t}</div>
|
||||||
|
<div class="list-group-item-sublabel">{'Manage system tags hidden from display'|t}</div>
|
||||||
|
</div>
|
||||||
|
<i class="mdi mdi-chevron-right"></i>
|
||||||
|
</a>
|
||||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/import">
|
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/import">
|
||||||
<div class="list-sortable-handle">
|
<div class="list-sortable-handle">
|
||||||
<i class="mdi mdi-file-import"></i>
|
<i class="mdi mdi-file-import"></i>
|
||||||
@ -91,6 +101,59 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hidden Tags Management Panel -->
|
||||||
|
<div class="row" id="hidden-tags-panel" style="display: none;">
|
||||||
|
<div class="col-md-8 col-md-offset-2">
|
||||||
|
<div class="card hidden-tags-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span><i class="mdi mdi-tag-off-outline"></i> {'Hidden Tags'|t}</span>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" id="close-hidden-tags">
|
||||||
|
<i class="mdi mdi-close"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Info Banner -->
|
||||||
|
<div class="hidden-tags-info">
|
||||||
|
<i class="mdi mdi-information-outline"></i>
|
||||||
|
<span>{'Hidden tags are functional tags used by the application that remain invisible to users. They are still fully operational for internal features.'|t}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Preset System Tags -->
|
||||||
|
<div class="hidden-tags-section">
|
||||||
|
<h4><i class="mdi mdi-tag-check-outline"></i> {'Preset System Tags'|t}</h4>
|
||||||
|
<p class="text-muted">{'Toggle visibility for built-in system tags'|t}</p>
|
||||||
|
<div id="preset-tags-grid" class="preset-tags-grid">
|
||||||
|
<!-- Filled by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Hidden Tags -->
|
||||||
|
<div class="hidden-tags-section">
|
||||||
|
<h4><i class="mdi mdi-tag-plus-outline"></i> {'Custom Hidden Tags'|t}</h4>
|
||||||
|
<p class="text-muted">{'Add your own tags to hide from display (supports wildcards like note-*)'|t}</p>
|
||||||
|
<div class="custom-tags-input-group">
|
||||||
|
<input type="text" id="custom-tag-input" class="form-control" placeholder="{'Enter tag name...'|t}">
|
||||||
|
<button type="button" class="btn btn-primary" id="add-custom-tag">
|
||||||
|
<i class="mdi mdi-plus"></i> {'Add'|t}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="custom-tags-list" class="custom-tags-list">
|
||||||
|
<!-- Filled by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="hidden-tags-actions">
|
||||||
|
<button type="button" class="btn btn-secondary" id="reset-hidden-tags">
|
||||||
|
<i class="mdi mdi-refresh"></i> {'Reset to Defaults'|t}
|
||||||
|
</button>
|
||||||
|
<span id="save-status" class="save-status"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{if="!empty($linkcount)"}
|
{if="!empty($linkcount)"}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8 col-md-offset-2">
|
<div class="col-md-8 col-md-offset-2">
|
||||||
@ -184,6 +247,380 @@
|
|||||||
{/loop}
|
{/loop}
|
||||||
</div>
|
</div>
|
||||||
{include="page.footer"}
|
{include="page.footer"}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Hidden Tags Panel Styles */
|
||||||
|
.hidden-tags-card .card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(99, 102, 241, 0.1) 100%);
|
||||||
|
border: 1px solid rgba(99, 102, 241, 0.2);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-info i {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #5b8def;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-info span {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-section h4 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-section h4 i {
|
||||||
|
color: #5b8def;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tags-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-item:hover {
|
||||||
|
border-color: rgba(99, 102, 241, 0.3);
|
||||||
|
background: rgba(99, 102, 241, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-checkbox {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
accent-color: #5b8def;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-name {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: 'Fira Code', monospace;
|
||||||
|
background: rgba(99, 102, 241, 0.1);
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-tag-desc {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tags-input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tags-input-group input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tags-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(91, 141, 239, 0.15) 100%);
|
||||||
|
border: 1px solid rgba(99, 102, 241, 0.3);
|
||||||
|
border-radius: 2rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-family: 'Fira Code', monospace;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-chip .remove-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
color: #ef4444;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tag-chip .remove-tag:hover {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-custom-tags {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-tags-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-status {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #10b981;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-status.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .hidden-tags-info {
|
||||||
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(99, 102, 241, 0.08) 100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Hidden Tags Management
|
||||||
|
(function() {
|
||||||
|
const STORAGE_KEY = 'shaarli_hidden_tags';
|
||||||
|
|
||||||
|
const PRESET_TAGS = [
|
||||||
|
{ name: 'note', desc: 'Notes - Internal note identifier' },
|
||||||
|
{ name: 'shaarli-pin', desc: 'Pinned - Keeps bookmarks at top' },
|
||||||
|
{ name: 'note-color-*', desc: 'Note Colors - Wildcard for color tags' },
|
||||||
|
{ name: 'notebg-*', desc: 'Note Backgrounds - Wildcard for background tags' },
|
||||||
|
{ name: 'notefilter-*', desc: 'Note Filters - Wildcard for filter tags' },
|
||||||
|
{ name: 'readitlater', desc: 'Read Later - Temporary reading list' },
|
||||||
|
{ name: 'shaarli-archiver', desc: 'Archived - Archived notes' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_HIDDEN_TAGS = PRESET_TAGS.map(t => t.name);
|
||||||
|
|
||||||
|
function getHiddenTags() {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (stored) return JSON.parse(stored);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading hidden tags:', e);
|
||||||
|
}
|
||||||
|
return [...DEFAULT_HIDDEN_TAGS];
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveHiddenTags(tags) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(tags));
|
||||||
|
showSaveStatus();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving hidden tags:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSaveStatus() {
|
||||||
|
const status = document.getElementById('save-status');
|
||||||
|
if (status) {
|
||||||
|
status.textContent = 'Saved!';
|
||||||
|
status.classList.add('show');
|
||||||
|
setTimeout(() => status.classList.remove('show'), 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWildcardTag(tag) {
|
||||||
|
return tag.includes('*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesWildcard(pattern, tag) {
|
||||||
|
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i');
|
||||||
|
return regex.test(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPresetTags() {
|
||||||
|
const container = document.getElementById('preset-tags-grid');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const hiddenTags = getHiddenTags();
|
||||||
|
container.innerHTML = PRESET_TAGS.map(tag => {
|
||||||
|
const isChecked = hiddenTags.some(ht => ht.toLowerCase() === tag.name.toLowerCase());
|
||||||
|
return `
|
||||||
|
<div class="preset-tag-item">
|
||||||
|
<input type="checkbox" class="preset-tag-checkbox"
|
||||||
|
data-tag="${tag.name}" ${isChecked ? 'checked' : ''}>
|
||||||
|
<div class="preset-tag-info">
|
||||||
|
<div class="preset-tag-name">${tag.name}</div>
|
||||||
|
<div class="preset-tag-desc">${tag.desc}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
container.querySelectorAll('.preset-tag-checkbox').forEach(cb => {
|
||||||
|
cb.addEventListener('change', function() {
|
||||||
|
const tagName = this.dataset.tag;
|
||||||
|
let hiddenTags = getHiddenTags();
|
||||||
|
|
||||||
|
if (this.checked) {
|
||||||
|
if (!hiddenTags.includes(tagName)) hiddenTags.push(tagName);
|
||||||
|
} else {
|
||||||
|
hiddenTags = hiddenTags.filter(t => t.toLowerCase() !== tagName.toLowerCase());
|
||||||
|
}
|
||||||
|
saveHiddenTags(hiddenTags);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCustomTags() {
|
||||||
|
const container = document.getElementById('custom-tags-list');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const hiddenTags = getHiddenTags();
|
||||||
|
const customTags = hiddenTags.filter(tag =>
|
||||||
|
!PRESET_TAGS.some(pt => pt.name.toLowerCase() === tag.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (customTags.length === 0) {
|
||||||
|
container.innerHTML = '<div class="no-custom-tags">No custom hidden tags</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = customTags.map(tag => `
|
||||||
|
<div class="custom-tag-chip">
|
||||||
|
<span>${tag}</span>
|
||||||
|
<span class="remove-tag" data-tag="${tag}">×</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
// Add remove listeners
|
||||||
|
container.querySelectorAll('.remove-tag').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const tagName = this.dataset.tag;
|
||||||
|
let hiddenTags = getHiddenTags();
|
||||||
|
hiddenTags = hiddenTags.filter(t => t.toLowerCase() !== tagName.toLowerCase());
|
||||||
|
saveHiddenTags(hiddenTags);
|
||||||
|
renderCustomTags();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCustomTag() {
|
||||||
|
const input = document.getElementById('custom-tag-input');
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
const tagName = input.value.trim().toLowerCase();
|
||||||
|
if (!tagName) return;
|
||||||
|
|
||||||
|
// Check if already exists
|
||||||
|
const hiddenTags = getHiddenTags();
|
||||||
|
if (hiddenTags.some(t => t.toLowerCase() === tagName)) {
|
||||||
|
input.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hiddenTags.push(tagName);
|
||||||
|
saveHiddenTags(hiddenTags);
|
||||||
|
input.value = '';
|
||||||
|
renderCustomTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetToDefaults() {
|
||||||
|
if (confirm('Reset hidden tags to default settings?')) {
|
||||||
|
saveHiddenTags([...DEFAULT_HIDDEN_TAGS]);
|
||||||
|
renderPresetTags();
|
||||||
|
renderCustomTags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const toggleBtn = document.getElementById('hidden-tags-toggle');
|
||||||
|
const panel = document.getElementById('hidden-tags-panel');
|
||||||
|
const closeBtn = document.getElementById('close-hidden-tags');
|
||||||
|
const addBtn = document.getElementById('add-custom-tag');
|
||||||
|
const resetBtn = document.getElementById('reset-hidden-tags');
|
||||||
|
const customInput = document.getElementById('custom-tag-input');
|
||||||
|
|
||||||
|
if (toggleBtn && panel) {
|
||||||
|
toggleBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
panel.style.display = panel.style.display === 'none' ? '' : 'none';
|
||||||
|
if (panel.style.display !== 'none') {
|
||||||
|
renderPresetTags();
|
||||||
|
renderCustomTags();
|
||||||
|
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeBtn && panel) {
|
||||||
|
closeBtn.addEventListener('click', function() {
|
||||||
|
panel.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addBtn) {
|
||||||
|
addBtn.addEventListener('click', addCustomTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customInput) {
|
||||||
|
customInput.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
addCustomTag();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resetBtn) {
|
||||||
|
resetBtn.addEventListener('click', resetToDefaults);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user