From 3375523bae344cb6e81bdc9891c43149090932fd Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Wed, 11 Feb 2026 10:10:04 -0500 Subject: [PATCH] feat: Introduce the new Shaarli-pro theme with its templates, styles, and scripts. --- .gemini/analysis.md | 43 ++++++ shaarli-pro/css/style.css | 292 ++++++++------------------------------ shaarli-pro/daily.html | 2 +- shaarli-pro/includes.html | 16 +++ shaarli-pro/js/script.js | 45 +++--- shaarli-pro/linklist.html | 3 + shaarli-pro/picwall.html | 8 +- 7 files changed, 147 insertions(+), 262 deletions(-) create mode 100644 .gemini/analysis.md diff --git a/.gemini/analysis.md b/.gemini/analysis.md new file mode 100644 index 0000000..0aff2dd --- /dev/null +++ b/.gemini/analysis.md @@ -0,0 +1,43 @@ +# Theme Analysis & Fix Plan + +## Issues Found + +### 1. CSS: Massive duplication of styles (CRITICAL) +- `.filter-info-banner` defined 3 times (lines 2900, 3137, 3196) +- `.filter-badge` defined 3 times (lines 2873, 3117, 3255) +- `.filter-clear-btn` defined 3 times +- `.filter-info-content` defined 3 times +- `.header-action-btn.has-active-filter` defined 3 times +- `.button-primary` and `.button-secondary` duplicated (lines 1771+ and 1834+) +- `.paging` styles duplicated (lines 1056 and 3007) + +### 2. "Mark as Read" Issue +- The `action_plugin` loop in `linklist.paging.html` renders plugin buttons inside pagination +- No CSS styling for plugin-injected content in paging area +- Need to style `.paging-plugin` properly + +### 3. Missing Plugin Zones in linklist.html +- `{loop="$value.link_plugin"}` is missing from the link card template +- This means plugin hooks for individual links won't render + +### 4. linklist_new.html is incomplete/unused duplicate +- Appears to be an abandoned copy of linklist.html + +### 5. Daily page bugs +- Line 39: `{$type}day=` should be `{$type}=` (nextday URL is broken) + +### 6. Footer HTML structure issues +- `` and `` are inside footer +- This is fragile but necessary for structure + +### 7. Picwall uses remixicon classes but only MDI is loaded +- `ri-zoom-in-line`, `ri-subtract-line`, `ri-add-line`, `ri-external-link-line` won't render + +### 8. Console.log debug statements in production JS +- Many `console.log('[Filter Debug]` statements in script.js + +### 9. Missing Google Font import +- `Inter` font is referenced but never imported + +### 10. page.header.html structure +- `col-md-offset-2` and `col-md-6 col-md-offset-3` don't exist in the minimal grid diff --git a/shaarli-pro/css/style.css b/shaarli-pro/css/style.css index 9bf39e8..2698e50 100644 --- a/shaarli-pro/css/style.css +++ b/shaarli-pro/css/style.css @@ -1,3 +1,6 @@ +/* Google Font */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + /** * Shaarli Professional Theme - Bookmarkify Style * Modern Sidebar Layout + Light/Dark Mode @@ -120,6 +123,17 @@ a:hover { color: var(--primary-hover); } +/* Accessible Focus */ +:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +button:focus:not(:focus-visible), +a:focus:not(:focus-visible) { + outline: none; +} + /* ===== App Layout (Sidebar + Main) ===== */ .app-layout { display: flex; @@ -1052,54 +1066,6 @@ input:checked+.theme-slider:before { color: white; } -/* ===== Paging ===== */ -.paging { - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem 0; - margin-bottom: 1rem; -} - -.paging-links { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.paging-current { - font-size: 0.9rem; - color: var(--text-secondary); -} - -.paging-current strong { - color: var(--text-main); -} - -.paging-total { - color: var(--text-muted); - font-size: 0.85rem; - margin-left: 0.25rem; -} - -.paging a { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: 0.5rem; - color: var(--text-main); - transition: all 0.15s ease; -} - -.paging a:hover { - background: var(--primary); - border-color: var(--primary); - color: white; -} /* ===== Links Grid/List ===== */ .links-list { @@ -1830,47 +1796,8 @@ select:focus { box-shadow: 0 0 0 3px var(--primary-light); } -/* ===== Buttons ===== */ -.button-primary { - display: inline-flex; - align-items: center; - gap: 0.375rem; - padding: 0.625rem 1.25rem; - background: var(--primary); - color: white; - border: none; - border-radius: 0.5rem; - font-weight: 500; - font-size: 0.9rem; - cursor: pointer; - transition: all 0.15s ease; - text-decoration: none; -} -.button-primary:hover { - background: var(--primary-hover); - color: white; -} -.button-secondary { - display: inline-flex; - align-items: center; - gap: 0.375rem; - padding: 0.625rem 1.25rem; - background: var(--bg-card); - color: var(--text-main); - border: 1px solid var(--border); - border-radius: 0.5rem; - font-weight: 500; - font-size: 0.9rem; - cursor: pointer; - transition: all 0.15s ease; - text-decoration: none; -} - -.button-secondary:hover { - background: var(--border-light); -} .nav-link { display: flex; @@ -2556,8 +2483,8 @@ select:focus { } .picwall-pictureframe::after { - content: '\eb82'; - font-family: 'remixicon'; + content: '\F03CC'; + font-family: 'Material Design Icons'; position: absolute; top: 0.75rem; right: 0.75rem; @@ -3062,6 +2989,7 @@ select:focus { color: var(--primary); font-weight: 600; } + /* Search Results Header (Tags) */ .search-results-header { display: flex; @@ -3105,7 +3033,7 @@ select:focus { } .search-tag-close:hover { - background: rgba(0,0,0,0.1); + background: rgba(0, 0, 0, 0.1); opacity: 1; } @@ -3114,158 +3042,57 @@ select:focus { } -/* Filter Badge Indicator */ -.filter-badge { - position: absolute; - top: 2px; - right: 2px; - width: 8px; - height: 8px; - background: #ef4444; - border-radius: 50%; - border: 2px solid var(--header-bg); -} - -.header-action-btn { - position: relative; -} - -.header-action-btn.has-active-filter { - background: rgba(239, 68, 68, 0.2); -} - -/* Filter Info Banner */ -.filter-info-banner { - display: flex; +/* ===== Plugin Zone Styling ===== */ +/* Plugin buttons injected into paging (e.g., "Mark as Read") */ +.paging-plugin { + display: inline-flex; align-items: center; - justify-content: space-between; - background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%); - color: white; - padding: 0.75rem 1.25rem; - border-radius: 8px; - margin: 1rem 0; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } -.filter-info-banner.empty-results { - background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); -} - -.filter-info-content { - display: flex; +.paging-plugin a { + display: inline-flex; align-items: center; - gap: 0.75rem; - font-size: 0.95rem; -} - -.filter-info-content i { - font-size: 1.25rem; - opacity: 0.9; -} - -.filter-info-content strong { - background: rgba(255, 255, 255, 0.25); - padding: 0.15rem 0.5rem; - border-radius: 4px; - font-weight: 600; -} - -.filter-clear-btn { - display: flex; - align-items: center; - gap: 0.35rem; - background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - color: white; - padding: 0.4rem 0.75rem; - border-radius: 6px; - cursor: pointer; - font-size: 0.85rem; + gap: 0.375rem; + padding: 0.375rem 0.75rem; + border-radius: 0.375rem; + font-size: 0.8rem; font-weight: 500; - transition: background 0.2s ease; + color: var(--text-secondary); + background: var(--bg-card); + border: 1px solid var(--border); + transition: all 0.2s ease; + text-decoration: none; + white-space: nowrap; } -.filter-clear-btn:hover { - background: rgba(255, 255, 255, 0.35); +.paging-plugin a:hover { + background: var(--primary-light); + color: var(--primary); + border-color: var(--primary); } -.filter-clear-btn i { - font-size: 1rem; -} - -/* ===== Filter Info Banner ===== */ -.filter-info-banner { +/* Plugin zone inside link cards */ +.link-plugin { display: flex; align-items: center; - justify-content: space-between; - background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%); - color: white; - padding: 0.75rem 1.25rem; - border-radius: 8px; - margin: 1rem 0; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + gap: 0.25rem; + margin-top: 0.25rem; } -.filter-info-banner.empty-results { - background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); -} - -.filter-info-content { - display: flex; +.link-plugin a { + display: inline-flex; align-items: center; - gap: 0.75rem; - font-size: 0.95rem; + gap: 0.25rem; + padding: 0.25rem 0.625rem; + border-radius: 0.375rem; + font-size: 0.75rem; + color: var(--text-muted); + transition: all 0.2s ease; } -.filter-info-content i { - font-size: 1.25rem; - opacity: 0.9; -} - -.filter-info-content strong { - background: rgba(255, 255, 255, 0.25); - padding: 0.15rem 0.5rem; - border-radius: 4px; - font-weight: 600; -} - -.filter-clear-btn { - display: flex; - align-items: center; - gap: 0.35rem; - background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - color: white; - padding: 0.4rem 0.75rem; - border-radius: 6px; - cursor: pointer; - font-size: 0.85rem; - font-weight: 500; - transition: background 0.2s ease; -} - -.filter-clear-btn:hover { - background: rgba(255, 255, 255, 0.35); -} - -.filter-clear-btn i { - font-size: 1rem; -} - -/* Filter Badge on button */ -.filter-badge { - position: absolute; - top: -2px; - right: -2px; - width: 10px; - height: 10px; - background: #ef4444; - border-radius: 50%; - border: 2px solid var(--header-bg); -} - -.header-action-btn.has-active-filter { - position: relative; +.link-plugin a:hover { + background: var(--primary-light); + color: var(--primary); } /* Single page pagination - centered stats only */ @@ -3353,8 +3180,12 @@ select:focus { color: var(--text-main); } -.modal-body h1, .modal-body h2, .modal-body h3, -.modal-body h4, .modal-body h5, .modal-body h6 { +.modal-body h1, +.modal-body h2, +.modal-body h3, +.modal-body h4, +.modal-body h5, +.modal-body h6 { margin-top: 1.5em; margin-bottom: 0.75em; color: var(--text-main); @@ -3370,7 +3201,8 @@ select:focus { margin: 1rem 0; } -.modal-body pre, .modal-body code { +.modal-body pre, +.modal-body code { background: var(--bg-body); font-family: monospace; padding: 0.2rem 0.4rem; @@ -3385,4 +3217,4 @@ select:focus { /* Button style adjustment if needed */ .view-desc-btn { cursor: pointer; -} +} \ No newline at end of file diff --git a/shaarli-pro/daily.html b/shaarli-pro/daily.html index 0bb235e..b4f74b1 100644 --- a/shaarli-pro/daily.html +++ b/shaarli-pro/daily.html @@ -36,7 +36,7 @@
- + {function="t('Next :type', '', 1, 'shaarli', [':type' => t($type)], true)"}
diff --git a/shaarli-pro/includes.html b/shaarli-pro/includes.html index 42f3dcc..e805381 100644 --- a/shaarli-pro/includes.html +++ b/shaarli-pro/includes.html @@ -6,9 +6,25 @@ + + + + diff --git a/shaarli-pro/js/script.js b/shaarli-pro/js/script.js index 84fb932..df65355 100644 --- a/shaarli-pro/js/script.js +++ b/shaarli-pro/js/script.js @@ -416,8 +416,7 @@ document.addEventListener('DOMContentLoaded', () => { const currentVisibility = (typeof shaarli !== 'undefined' && shaarli.visibility) ? shaarli.visibility : ''; const currentUntagged = (typeof shaarli !== 'undefined' && shaarli.untaggedonly) || false; - console.log('[Filter] Applying filters - private:', isPrivate, 'public:', isPublic, 'untagged:', isUntagged); - console.log('[Filter] Current state - visibility:', currentVisibility, 'untagged:', currentUntagged); + let url = basePath + '/'; @@ -446,7 +445,7 @@ document.addEventListener('DOMContentLoaded', () => { } } - console.log('[Filter] Navigating to:', url); + window.location.href = url; } @@ -488,7 +487,7 @@ document.addEventListener('DOMContentLoaded', () => { // Initialize filter states from server-side variables (set in includes.html) (function initFilterStates() { - console.log('[Filter Debug] ========================================'); + // Get filter state from server-side rendered variables const visibility = (typeof shaarli !== 'undefined' && shaarli.visibility) ? shaarli.visibility : ''; @@ -499,42 +498,36 @@ document.addEventListener('DOMContentLoaded', () => { let isPublicActive = visibility === 'public'; let isUntaggedActive = untaggedonly === true; - console.log('[Filter Debug] shaarli.visibility:', visibility); - console.log('[Filter Debug] shaarli.untaggedonly:', untaggedonly); - console.log('[Filter Debug] isPrivateActive:', isPrivateActive); - console.log('[Filter Debug] isPublicActive:', isPublicActive); - console.log('[Filter Debug] isUntaggedActive:', isUntaggedActive); // Set checkbox states if (filterPrivate && isPrivateActive) { filterPrivate.checked = true; - console.log('[Filter Debug] ✓ Set filterPrivate to CHECKED'); + } if (filterPublic && isPublicActive) { filterPublic.checked = true; - console.log('[Filter Debug] ✓ Set filterPublic to CHECKED'); + } if (filterUntagged && isUntaggedActive) { filterUntagged.checked = true; - console.log('[Filter Debug] ✓ Set filterUntagged to CHECKED'); + } const hasActiveFilter = isPrivateActive || isPublicActive || isUntaggedActive; - console.log('[Filter Debug] hasActiveFilter:', hasActiveFilter); - console.log('[Filter Debug] ========================================'); + // Add/update filter indicator badge on the filter button if (filterToggleBtn && hasActiveFilter) { filterToggleBtn.classList.add('has-active-filter'); - console.log('[Filter Debug] Added has-active-filter class to button'); + // Add badge indicator if not exists if (!filterToggleBtn.querySelector('.filter-badge')) { const badge = document.createElement('span'); badge.className = 'filter-badge'; filterToggleBtn.appendChild(badge); - console.log('[Filter Debug] Added filter badge'); + } } @@ -587,9 +580,7 @@ document.addEventListener('DOMContentLoaded', () => { } } - console.log('[Filter Debug] resultCount:', resultCount); - console.log('[Filter Debug] isEmptyResults:', isEmptyResults); - console.log('[Filter Debug] message:', message); + if (message) { // Get base path safely @@ -613,26 +604,25 @@ document.addEventListener('DOMContentLoaded', () => { const linklist = document.getElementById('linklist'); const emptyStateDiv = document.querySelector('.empty-state'); - console.log('[Filter Debug] contentToolbar:', contentToolbar); - console.log('[Filter Debug] linklist:', linklist); + // Insert after the content-toolbar (pagination) if (contentToolbar && contentToolbar.parentNode) { contentToolbar.parentNode.insertBefore(banner, contentToolbar.nextSibling); - console.log('[Filter Debug] Banner inserted after content-toolbar'); + } else if (emptyStateDiv && emptyStateDiv.parentNode) { emptyStateDiv.parentNode.insertBefore(banner, emptyStateDiv); - console.log('[Filter Debug] Banner inserted before empty-state'); + } else if (linklist) { linklist.insertBefore(banner, linklist.firstChild); - console.log('[Filter Debug] Banner inserted at beginning of linklist'); + } else { - console.log('[Filter Debug] Could not find insertion point for banner'); + } // Add click handler to clear button document.getElementById('filter-clear-btn')?.addEventListener('click', () => { - console.log('[Filter] Clear button clicked, clearing all filters'); + // Clear based on what was active if (isPrivateActive || isPublicActive) { window.location.href = basePath + '/admin/visibility/all'; @@ -894,7 +884,8 @@ document.addEventListener('DOMContentLoaded', () => { let i = 0; const updateThumbnail = function (id) { - console.log('Updating thumbnail #' + i + ' with id ' + id); + + fetch(shaarli.basePath + '/admin/shaare/' + id + '/update-thumbnail', { method: 'PATCH', diff --git a/shaarli-pro/linklist.html b/shaarli-pro/linklist.html index 3a5b6e6..e912194 100644 --- a/shaarli-pro/linklist.html +++ b/shaarli-pro/linklist.html @@ -77,6 +77,9 @@ +{loop="$value.link_plugin"} + +{/loop} diff --git a/shaarli-pro/picwall.html b/shaarli-pro/picwall.html index 0b33ca2..46dd083 100644 --- a/shaarli-pro/picwall.html +++ b/shaarli-pro/picwall.html @@ -27,17 +27,17 @@
- + Image Size
200px @@ -58,7 +58,7 @@ {$value.title} - + {$value.real_url}