946 lines
45 KiB
HTML

<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="tools"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="toolsdiv" class="page-tools container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">{'Settings'|t}</div>
<div class="list-group">
<a class="list-group-item list-group-item-action ripple" href="#" id="themes-panel-toggle">
<div class="list-sortable-handle">
<i class="mdi mdi-palette"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Themes'|t}</div>
<div class="list-group-item-sublabel">{'Choose application visual theme'|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/configure">
<div class="list-sortable-handle">
<i class="mdi mdi-cog"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Configure your Shaarli'|t}</div>
<div class="list-group-item-sublabel">{'Change Title, timezone...'|t}</div>
</div>
<i class="mdi mdi-chevron-right"></i>
</a>
{if="!$openshaarli"}
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/password">
<div class="list-sortable-handle">
<i class="mdi mdi-lock"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Change password'|t}</div>
<div class="list-group-item-sublabel">{'Change your password'|t}</div>
</div>
<i class="mdi mdi-chevron-right"></i>
</a>
{/if}
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/plugins">
<div class="list-sortable-handle">
<i class="mdi mdi-puzzle"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Plugin administration'|t}</div>
<div class="list-group-item-sublabel">{'Enable, disable and configure plugins'|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/server">
<div class="list-sortable-handle">
<i class="mdi mdi-server"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Server administration'|t}</div>
<div class="list-group-item-sublabel">{'Check instance\'s server configuration'|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/tags">
<div class="list-sortable-handle">
<i class="mdi mdi-tag-multiple"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Manage Tags'|t}</div>
<div class="list-group-item-sublabel">{'Rename or delete a tag in all links'|t}</div>
</div>
<i class="mdi mdi-chevron-right"></i>
</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">
<div class="list-sortable-handle">
<i class="mdi mdi-file-import"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Import'|t}</div>
<div class="list-group-item-sublabel">{'Import Netscape html bookmarks'|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/export">
<div class="list-sortable-handle">
<i class="mdi mdi-file-export"></i>
</div>
<div class="list-group-item-content">
<div class="list-group-item-label">{'Export'|t}</div>
<div class="list-group-item-sublabel">{'Export Netscape html bookmarks'|t}</div>
</div>
<i class="mdi mdi-chevron-right"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Themes Management Panel -->
<div class="row" id="themes-panel" style="display: none;">
<div class="col-md-8 col-md-offset-2">
<div class="card themes-card">
<div class="card-header">
<span><i class="mdi mdi-palette"></i> {'Application Themes'|t}</span>
<button type="button" class="btn btn-sm btn-secondary" id="close-themes-panel">
<i class="mdi mdi-close"></i>
</button>
</div>
<div class="card-body">
<div class="themes-info">
<i class="mdi mdi-information-outline"></i>
<span>{'Choose your preferred visual theme. The theme configuration will be synchronized across your devices via the shaarit_config bookmark.'|t}</span>
</div>
<div class="themes-grid" id="themes-grid">
<!-- Filled by JavaScript -->
</div>
<div class="themes-actions">
<span id="theme-save-status" class="save-status"></span>
</div>
</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)"}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">{'Statistics'|t}</div>
<div class="card-body">
<div class="key-value-list">
<div class="key-value-item">
<div class="key-value-label">Total Links</div>
<div class="key-value-data">{$linkcount}</div>
</div>
<div class="key-value-item">
<div class="key-value-label">Private Links</div>
<div class="key-value-data">{$privateLinkcount}</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/if}
{if="isset($tools_plugin)"}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">Plugin settings</div>
<div class="card-body">
{loop="$tools_plugin"}
{$value}
{/loop}
</div>
</div>
</div>
</div>
{/if}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">Bookmarklet</div>
<div class="card-body">
<p class="text-muted" style="margin-bottom: 1.5rem;">
Drag these buttons to your bookmarks bar to quickly add links from any page.
</p>
<div class="bookmarklet-actions">
<a class="btn btn-primary" href="javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;var%20desc=document.getSelection().toString();if(desc.length>4000){desc=desc.substr(0,4000)+'...';alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');}window.open('{$pageabsaddr}admin/shaare?post='%20+%20encodeURIComponent(url)+'&amp;title='%20+%20encodeURIComponent(title)+'&amp;description='%20+%20encodeURIComponent(desc)+'&amp;source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();">
<i class="mdi mdi-bookmark-plus"></i> {'Shaare link'|t}
</a>
<a class="btn btn-secondary" href="javascript:(function(){var desc=document.getSelection().toString();if(desc.length>4000){desc=desc.substr(0,4000)+'...';alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');}window.open('{$pageabsaddr}?private=1&amp;post='+'&amp;description='%20+%20encodeURIComponent(desc)+'&amp;source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();">
<i class="mdi mdi-note-plus"></i> {'Add Note'|t}
</a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">{'3rd party'|t}</div>
<div class="card-body">
<div class="third-party-links">
<a href="https://addons.mozilla.org/fr/firefox/addon/shaarli/" target="_blank" class="btn btn-secondary">
<i class="mdi mdi-firefox"></i> Firefox
</a>
<a href="https://chromewebstore.google.com/detail/add-to-shaarli/jhfblapoehcfajokolimghdfmeeakbee" target="_blank" class="btn btn-secondary">
<i class="mdi mdi-google-chrome"></i> Chrome
</a>
<a href="https://f-droid.org/fr/packages/com.dimtion.shaarlier/" target="_blank" class="btn btn-secondary">
<i class="mdi mdi-android"></i> Shaarlier
</a>
<a href="https://stakali.toneiv.eu/" target="_blank" class="btn btn-secondary">
<i class="mdi mdi-android"></i> Stakali
</a>
<a href="https://github.com/lockcp/ShaarliOS" target="_blank" class="btn btn-secondary">
<i class="mdi mdi-apple-ios"></i> iOS
</a>
</div>
<p class="text-center text-muted" style="margin-top: 1.5rem;">
Other <a href="https://shaarli.readthedocs.io/en/master/Community-and-related-software.html" target="_blank">{'Community and related software'|t}</a>.
</p>
</div>
</div>
</div>
</div>
{loop="$tools_plugin"}
{$value}
{/loop}
</div>
{include="page.footer"}
<style>
/* Themes Panel Styles */
.themes-card .card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.themes-info {
display: flex;
gap: 0.75rem;
padding: 1rem;
background: linear-gradient(135deg, rgba(0, 212, 170, 0.1) 0%, rgba(14, 165, 233, 0.1) 100%);
border: 1px solid rgba(0, 212, 170, 0.2);
border-radius: 0.75rem;
margin-bottom: 1.5rem;
}
.themes-info i {
font-size: 1.25rem;
color: var(--primary);
flex-shrink: 0;
}
.themes-info span {
font-size: 0.9rem;
color: var(--text-muted);
line-height: 1.5;
}
.themes-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.theme-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
padding: 1rem;
background: var(--bg-card);
border: 2px solid var(--border-light);
border-radius: 0.75rem;
cursor: pointer;
transition: all 0.2s ease;
}
.theme-item:hover {
border-color: rgba(0, 212, 170, 0.4);
transform: translateY(-2px);
}
.theme-item.active {
border-color: var(--primary);
background: rgba(var(--primary-rgb), 0.05);
}
.theme-preview {
width: 100%;
height: 80px;
border-radius: 0.5rem;
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid var(--border-light);
}
.theme-preview-top {
height: 30%;
background: var(--preview-primary);
}
.theme-preview-bottom {
height: 70%;
background: var(--preview-bg);
display: flex;
align-items: center;
justify-content: center;
}
.theme-preview-card {
width: 60%;
height: 50%;
background: var(--preview-surface);
border-radius: 4px;
}
.theme-name {
font-weight: 500;
font-size: 1rem;
color: var(--text-primary);
margin-top: 0.5rem;
}
.theme-desc {
font-size: 0.8rem;
color: var(--text-muted);
text-align: center;
}
[data-theme="light"] .themes-info {
background: linear-gradient(135deg, rgba(0, 107, 90, 0.05) 0%, rgba(0, 119, 182, 0.05) 100%);
}
/* 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>
// Themes Management
(function() {
const THEMES = [
{ id: 'DEFAULT', name: 'ShaarIt', desc: 'Original theme', color: '#00D4AA', bg: '#0A1628', surface: '#0D1B2A', hasLight: true },
{ id: 'GITHUB', name: 'GitHub', desc: 'Soft contrast', color: '#58A6FF', bg: '#0D1117', surface: '#161B22', hasLight: true },
{ id: 'LINEAR', name: 'Linear', desc: 'Subtle gradients', color: '#5E6AD2', bg: '#12131A', surface: '#1B1C24', hasLight: false },
{ id: 'SPOTIFY', name: 'Spotify', desc: 'Pure black & green', color: '#1DB954', bg: '#000000', surface: '#121212', hasLight: false },
{ id: 'NOTION', name: 'Notion', desc: 'Clean reading', color: '#529CCA', bg: '#191919', surface: '#202020', hasLight: false },
{ id: 'DISCORD', name: 'Discord', desc: 'Community standard', color: '#5865F2', bg: '#313338', surface: '#2B2D31', hasLight: false },
{ id: 'DRACULA', name: 'Dracula', desc: 'Pink & purple accents', color: '#BD93F9', bg: '#282A36', surface: '#21222C', hasLight: false },
{ id: 'ONE_DARK_PRO', name: 'One Dark', desc: 'Atom inspired', color: '#61AFEF', bg: '#282C34', surface: '#21252B', hasLight: false },
{ id: 'TOKYO_NIGHT', name: 'Tokyo Night', desc: 'Neon blue', color: '#7AA2F7', bg: '#1A1B26', surface: '#16171F', hasLight: false },
{ id: 'NORD', name: 'Nord', desc: 'Arctic style', color: '#88C0D0', bg: '#2E3440', surface: '#3B4252', hasLight: false },
{ id: 'NIGHT_OWL', name: 'Night Owl', desc: 'Optimized for night', color: '#7FDBCA', bg: '#011627', surface: '#0B2942', hasLight: false },
{ id: 'ANTHRACITE', name: 'Anthracite', desc: 'Material dark', color: '#BB86FC', bg: '#121212', surface: '#1E1E1E', hasLight: false },
{ id: 'CYBERPUNK', name: 'Cyberpunk', desc: 'Vibrant neon', color: '#00FFFF', bg: '#0A0A14', surface: '#12121E', hasLight: false },
{ id: 'NAVY_ELEGANCE', name: 'Navy Élégance', desc: 'Deep blue & gold', color: '#D4AF37', bg: '#0B1929', surface: '#0F2035', hasLight: false },
{ id: 'EARTHY', name: 'Tons Terreux', desc: 'Organic feel', color: '#81C784', bg: '#1A1510', surface: '#231E18', hasLight: false }
];
function getActiveTheme() {
return localStorage.getItem('shaarit_theme_id') || 'DEFAULT';
}
function showSaveStatus() {
const status = document.getElementById('theme-save-status');
if (status) {
status.textContent = 'Saved! Refreshing...';
status.classList.add('show');
}
}
function renderThemes() {
const container = document.getElementById('themes-grid');
if (!container) return;
const activeTheme = getActiveTheme();
container.innerHTML = THEMES.map(theme => `
<div class="theme-item ${activeTheme === theme.id ? 'active' : ''}" data-theme-id="${theme.id}" data-has-light="${theme.hasLight}">
<div class="theme-preview" style="--preview-primary: ${theme.color}; --preview-bg: ${theme.bg}; --preview-surface: ${theme.surface};">
<div class="theme-preview-top"></div>
<div class="theme-preview-bottom">
<div class="theme-preview-card"></div>
</div>
</div>
<div class="theme-name">${theme.name}</div>
<div class="theme-desc">${theme.desc}</div>
</div>
`).join('');
container.querySelectorAll('.theme-item').forEach(item => {
item.addEventListener('click', function() {
const themeId = this.dataset.themeId;
const hasLight = this.dataset.hasLight === 'true';
// Update UI immediately
container.querySelectorAll('.theme-item').forEach(el => el.classList.remove('active'));
this.classList.add('active');
// Apply theme locally
localStorage.setItem('shaarit_theme_id', themeId);
document.documentElement.setAttribute('data-theme-id', themeId);
// Handle light mode restriction
if (!hasLight) {
localStorage.setItem('theme', 'dark');
document.documentElement.setAttribute('data-theme', 'dark');
// Trigger event for sidebar to update its switch
window.dispatchEvent(new Event('themeChanged'));
}
showSaveStatus();
// Sync to bookmark in background
syncThemeToBookmark(themeId).then(() => {
// Refresh to ensure everything applies cleanly (plugins etc)
setTimeout(() => window.location.reload(), 500);
});
});
});
}
async function syncThemeToBookmark(themeId) {
try {
// We fetch the current config bookmark if any
const res = await fetch(shaarli.basePath + '/?searchtags=shaarit_config');
const text = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
let config = { version: 1, theme: themeId, collections: [] };
let editUrl = null;
// Find existing config
const linkCard = doc.querySelector('.linklist-item');
if (linkCard) {
const descEl = linkCard.querySelector('.link-description p');
if (descEl) {
try {
const parsed = JSON.parse(descEl.textContent.trim());
config = Object.assign({}, parsed, { theme: themeId });
} catch(e) {}
}
const editBtn = linkCard.querySelector('.link-action-edit');
if (editBtn) editUrl = editBtn.getAttribute('href');
}
// Prepare form data
const formData = new FormData();
formData.append('lf_title', 'collections');
formData.append('lf_url', 'https://shaarit.app/config/collections');
formData.append('lf_tags', 'shaarit_config');
formData.append('lf_description', JSON.stringify(config));
formData.append('save_edit', 'Save');
let token = '';
if (editUrl) {
// Update existing
const editRes = await fetch(editUrl);
const editDoc = parser.parseFromString(await editRes.text(), 'text/html');
const tokenEl = editDoc.querySelector('input[name="token"]');
const idEl = editDoc.querySelector('input[name="lf_id"]');
if (tokenEl) token = tokenEl.value;
if (idEl) formData.append('lf_id', idEl.value);
} else {
// Create new
const addRes = await fetch(shaarli.basePath + '/admin/add-shaare');
const addDoc = parser.parseFromString(await addRes.text(), 'text/html');
const tokenEl = addDoc.querySelector('input[name="token"]');
if (tokenEl) token = tokenEl.value;
}
if (token) {
formData.append('token', token);
await fetch(shaarli.basePath + '/admin/shaare', {
method: 'POST',
body: formData
});
}
} catch (e) {
console.error('Failed to sync theme to bookmark', e);
}
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
const toggleBtn = document.getElementById('themes-panel-toggle');
const panel = document.getElementById('themes-panel');
const closeBtn = document.getElementById('close-themes-panel');
if (toggleBtn && panel) {
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
panel.style.display = panel.style.display === 'none' ? '' : 'none';
if (panel.style.display !== 'none') {
renderThemes();
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
}
if (closeBtn && panel) {
closeBtn.addEventListener('click', function() {
panel.style.display = 'none';
});
}
});
})();
// Hidden Tags Management
(function() {
const STORAGE_KEY = 'shaarli_hidden_tags';
// Harmonisé avec ShaarIt Android (PRESET_SYSTEM_TAGS).
const PRESET_TAGS = [
{ name: 'note', desc: 'Notes - Internal note identifier' },
{ name: 'shaarli-note', desc: 'Notes - Legacy alias' },
{ name: 'todo', desc: 'Tasks - Internal todo identifier' },
{ name: 'shaarli-todo', desc: 'Tasks - Legacy alias' },
{ 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: 'font-*', desc: 'Note Font Colors - Wildcard' },
{ name: 'readitlater', desc: 'Read Later - Temporary reading list' },
{ name: 'brain-dump', desc: 'Brain dump - Quick idea capture' },
{ name: 'shaarli-archive', 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}">&times;</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>
</html>