From c1b48ad584c441290ee41e67e07928d37bf503d6 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 21 Apr 2026 13:47:30 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20ajouter=20support=20pour=20les=20images?= =?UTF-8?q?=20de=20fond=20et=20les=20filtres=20CSS=20sur=20les=20notes=20a?= =?UTF-8?q?vec=20des=20classes=20sp=C3=A9cifiques,=20incluant=20des=20styl?= =?UTF-8?q?es=20pour=20les=20=C3=A9l=C3=A9ments=20.link-outer=20et=20.note?= =?UTF-8?q?-card,=20ainsi=20que=20des=20ajustements=20pour=20la=20gestion?= =?UTF-8?q?=20des=20couleurs=20de=20texte=20et=20de=20fond=20dans=20le=20s?= =?UTF-8?q?tudio=20de=20personnalisation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shaarli-pro/css/custom_views.css | 98 ++++++++- shaarli-pro/js/custom_views.js | 341 +++++++++++++------------------ 2 files changed, 234 insertions(+), 205 deletions(-) diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css index 9cb648f..86c3be9 100644 --- a/shaarli-pro/css/custom_views.css +++ b/shaarli-pro/css/custom_views.css @@ -1592,6 +1592,13 @@ body.view-notes .notes-masonry:empty::after { background-repeat: no-repeat; } +.link-outer.note-has-bg { + background-image: var(--note-bg-image, none) !important; + background-size: cover; + background-position: center; + background-repeat: no-repeat; +} + .note-modal.note-has-bg { background-image: var(--note-bg-image, none) !important; background-size: cover; @@ -1612,7 +1619,14 @@ body.view-notes .notes-masonry:empty::after { .note-card.note-filter-grid::after, .note-card.note-filter-noise::after, .note-card.note-filter-dots::after, -.note-card.note-filter-stripes::after { +.note-card.note-filter-stripes::after, +.link-outer.note-filter-glass::after, +.link-outer.note-filter-vignette::after, +.link-outer.note-filter-lined::after, +.link-outer.note-filter-grid::after, +.link-outer.note-filter-noise::after, +.link-outer.note-filter-dots::after, +.link-outer.note-filter-stripes::after { content: ""; position: absolute; inset: 0; @@ -1628,11 +1642,21 @@ body.view-notes .notes-masonry:empty::after { -webkit-backdrop-filter: blur(2px); } +.link-outer.note-filter-glass::after { + background: rgba(255, 255, 255, 0.06); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); +} + /* Filter: Vignette — dark edges */ .note-card.note-filter-vignette::after { background: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, 0.42) 100%); } +.link-outer.note-filter-vignette::after { + background: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, 0.42) 100%); +} + /* Filter: Lined — horizontal ruled lines */ .note-card.note-filter-lined::after { background-image: repeating-linear-gradient( @@ -1644,6 +1668,16 @@ body.view-notes .notes-masonry:empty::after { ); } +.link-outer.note-filter-lined::after { + background-image: repeating-linear-gradient( + to bottom, + transparent 0px, + transparent 27px, + rgba(128, 128, 128, 0.18) 27px, + rgba(128, 128, 128, 0.18) 28px + ); +} + /* Filter: Grid — squared grid */ .note-card.note-filter-grid::after { background-image: @@ -1651,18 +1685,34 @@ body.view-notes .notes-masonry:empty::after { repeating-linear-gradient(to bottom, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px); } +.link-outer.note-filter-grid::after { + background-image: + repeating-linear-gradient(to right, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px), + repeating-linear-gradient(to bottom, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px); +} + /* Filter: Noise — subtle stipple effect */ .note-card.note-filter-noise::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E"); background-repeat: repeat; } +.link-outer.note-filter-noise::after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E"); + background-repeat: repeat; +} + /* Filter: Dots — dot pattern */ .note-card.note-filter-dots::after { background-image: radial-gradient(circle, rgba(128, 128, 128, 0.3) 1px, transparent 1px); background-size: 14px 14px; } +.link-outer.note-filter-dots::after { + background-image: radial-gradient(circle, rgba(128, 128, 128, 0.3) 1px, transparent 1px); + background-size: 14px 14px; +} + /* Filter: Stripes — diagonal lines */ .note-card.note-filter-stripes::after { background-image: repeating-linear-gradient( @@ -1674,9 +1724,55 @@ body.view-notes .notes-masonry:empty::after { ); } +.link-outer.note-filter-stripes::after { + background-image: repeating-linear-gradient( + -45deg, + transparent 0px, + transparent 8px, + rgba(128, 128, 128, 0.12) 8px, + rgba(128, 128, 128, 0.12) 9px + ); +} + /* Keep card inner content above the filter overlay */ .note-card .note-inner, .note-card .note-hover-actions { position: relative; z-index: 1; +} + +.link-outer .link-content, +.link-outer .link-actions, +.link-outer .link-hover-actions, +.link-outer .link-meta, +.link-outer .link-select-checkbox, +.link-outer .link-visibility-badge { + position: relative; + z-index: 1; +} + +/* Let Background Studio font color affect bookmark tiles too */ +.link-outer[class*="note-color-"], +.link-outer[class*="note-color-"] .link-title, +.link-outer[class*="note-color-"] .link-title a, +.link-outer[class*="note-color-"] .link-url, +.link-outer[class*="note-color-"] .link-url a, +.link-outer[class*="note-color-"] .link-description, +.link-outer[class*="note-color-"] .link-meta, +.link-outer[class*="note-color-"] .link-meta a, +.link-outer[class*="note-color-"] .link-actions, +.link-outer[class*="note-color-"] .link-actions a, +.link-outer[class*="note-color-"] .link-actions button, +.view-list .link-outer[class*="note-color-"], +.view-list .link-outer[class*="note-color-"] a, +.view-list .link-outer[class*="note-color-"] button { +color: var(--note-card-fg, var(--tile-text)); +} + +/* Let Background Studio font color affect note/todo body text */ +.note-card[class*="note-color-"] .note-title, +.note-card[class*="note-color-"] .note-body, +.note-card[class*="note-color-"] .todo-checklist-preview-item, +.note-card[class*="note-color-"] .todo-checklist-preview-text { +color: var(--note-card-fg, var(--special-view-strong)); } \ No newline at end of file diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js index 462dfe4..92dfbc0 100644 --- a/shaarli-pro/js/custom_views.js +++ b/shaarli-pro/js/custom_views.js @@ -9,14 +9,17 @@ document.addEventListener("DOMContentLoaded", function () { const pathTagRaw = pathMatch ? decodeURIComponent(pathMatch[1]) : ""; // Parse all active tags to safely detect the view - const activeTags = (searchTagsRaw + " " + pathTagRaw).toLowerCase().split(/[\s,]+/).filter(t => t); + const activeTags = (searchTagsRaw + " " + pathTagRaw) + .toLowerCase() + .split(/[\s,]+/) + .filter((t) => t); // Foolproof detection using sidebar active state and DOM rendered tags const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active, .sidebar-link[href*="searchtags=shaarli-note"].active'); const hasTodoActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Mes tâches"].active, .header-nav-link[aria-label="Mes tâches"].active, .sidebar-link[href*="searchtags=shaarli-todo"].active, .sidebar-link[href*="searchtags=todo"].active'); const hasArchiveActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Archive"].active, .header-nav-link[aria-label="Archive"].active, .sidebar-link[href*="searchtags=shaarli-archive"].active, .sidebar-link[href*="searchtags=shaarli-archiver"].active'); - const domChipTags = Array.from(document.querySelectorAll('.search-tag-chip')).map(el => (el.textContent || "").trim().toLowerCase()); + const domChipTags = Array.from(document.querySelectorAll(".search-tag-chip")).map((el) => (el.textContent || "").trim().toLowerCase()); const isArchiveTag = (tag) => tag === "shaarli-archive" || tag === "shaarli-archiver"; const isNoteTag = (tag) => tag === "note" || tag === "shaarli-note" || tag === "#note"; @@ -32,7 +35,7 @@ document.addEventListener("DOMContentLoaded", function () { const showErrorBanner = (msg, err) => { const banner = document.createElement("div"); banner.style.cssText = "background: #ffebee; color: #c62828; padding: 16px; margin: 16px; border-radius: 8px; border: 1px solid #ef9a9a; z-index: 9999; position: relative;"; - banner.innerHTML = `Erreur Custom Views: ${msg}
${err ? err.stack || err : ''}
`; + banner.innerHTML = `Erreur Custom Views: ${msg}
${err ? err.stack || err : ""}
`; if (container) container.prepend(banner); else document.body.prepend(banner); }; @@ -58,18 +61,9 @@ document.addEventListener("DOMContentLoaded", function () { if (!markdown) return ""; const normalizeNewlines = String(markdown).replace(/\r\n?/g, "\n"); - const escape = (value) => - String(value) - .replace(/&/g, "&") - .replace(//g, ">"); + const escape = (value) => String(value).replace(/&/g, "&").replace(//g, ">"); - const escapeAttr = (value) => - String(value) - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(//g, ">"); + const escapeAttr = (value) => String(value).replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); const sanitizeUrl = (rawUrl) => { const url = String(rawUrl || "").trim(); @@ -196,12 +190,7 @@ document.addEventListener("DOMContentLoaded", function () { inTodo = true; } const checked = String(todoMatch[1]).toLowerCase() === "x"; - html.push( - `
  • ` - + `` - + `${renderInline(todoMatch[2])}` - + "
  • ", - ); + html.push(`
  • ` + `` + `${renderInline(todoMatch[2])}` + "
  • "); return; } @@ -490,8 +479,7 @@ function renderMarkdown(markdown) { if (typeof window !== "undefined" && typeof window.renderMarkdown === "function") { return window.renderMarkdown(markdown); } - return String(markdown || "") - .replace(/\n/g, "
    "); + return String(markdown || "").replace(/\n/g, "
    "); } function applyKeepNoteFormatting(textarea, format) { @@ -505,80 +493,80 @@ const NOTE_COLOR_OPTIONS = [ key: "default", label: "Par défaut", light: "#f8fafc", - dark: "#20293A" + dark: "#20293A", }, { key: "red", label: "Rouge", light: "#f28b82", - dark: "#9c2116" + dark: "#9c2116", }, { key: "orange", label: "Orange", light: "#fbbc04", - dark: "#9c7a16" + dark: "#9c7a16", }, { key: "yellow", label: "Jaune", light: "#fff475", - dark: "#9c9116" + dark: "#9c9116", }, { key: "green", label: "Vert", light: "#ccff90", - dark: "#5e9c16" + dark: "#5e9c16", }, { key: "teal", label: "Menthe", light: "#a7ffeb", - dark: "#169c7d" + dark: "#169c7d", }, { key: "blue", label: "Bleu clair", light: "#cbf0f8", - dark: "#16849c" + dark: "#16849c", }, { key: "darkblue", label: "Bleu", light: "#aecbfa", - dark: "#16499c" + dark: "#16499c", }, { key: "purple", label: "Violet", light: "#d7aefb", - dark: "#5d169c" + dark: "#5d169c", }, { key: "pink", label: "Rose", light: "#fdcfe8", - dark: "#9c165f" + dark: "#9c165f", }, { key: "brown", label: "Beige", light: "#e6c9a8", - dark: "#9c5d16" + dark: "#9c5d16", }, { key: "grey", label: "Gris", light: "#e8eaed", - dark: "#4e5764" + dark: "#4e5764", }, { key: "custom", label: "Personnalisé", light: "#custom", - dark: "#custom" - } + dark: "#custom", + }, ]; const NOTE_FILTER_OPTIONS = [ @@ -598,7 +586,7 @@ const NOTE_FONT_COLOR_OPTIONS = [ { key: "dark", label: "Sombre", value: "#202124" }, { key: "white", label: "Blanc", value: "#ffffff" }, { key: "black", label: "Noir", value: "#000000" }, - { key: "custom", label: "Personnalisé", value: "custom" } + { key: "custom", label: "Personnalisé", value: "custom" }, ]; const NOTE_FONT_COLOR_TAG_PREFIX = "font-"; @@ -703,11 +691,10 @@ function removeTagFromEntity(editUrl, tag) { function deleteEntitySilently(deleteUrl) { if (!deleteUrl || deleteUrl === "#") return Promise.reject("Invalid delete URL"); - return fetch(deleteUrl) - .then((response) => { - if (!response.ok) throw new Error("Delete request failed"); - return response; - }); + return fetch(deleteUrl).then((response) => { + if (!response.ok) throw new Error("Delete request failed"); + return response; + }); } let tagDisplayRemovalInitialized = false; @@ -818,18 +805,14 @@ function getModalCurrentEntity(modal) { } function resolveThemeAssetBasePath() { - const cssLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find( - (link) => link.href && link.href.includes("/custom_views.css"), - ); + const cssLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((link) => link.href && link.href.includes("/custom_views.css")); if (cssLink && cssLink.href) { const cssUrl = new URL(cssLink.href, window.location.origin); const cssPath = cssUrl.pathname.replace(/\/css\/custom_views\.css$/, ""); if (cssPath) return cssPath; } - const jsScript = Array.from(document.querySelectorAll("script[src]")).find( - (script) => script.src && script.src.includes("/custom_views.js"), - ); + const jsScript = Array.from(document.querySelectorAll("script[src]")).find((script) => script.src && script.src.includes("/custom_views.js")); if (jsScript && jsScript.src) { const jsUrl = new URL(jsScript.src, window.location.origin); const jsPath = jsUrl.pathname.replace(/\/js\/custom_views\.js$/, ""); @@ -1052,11 +1035,7 @@ function getReadableForegroundForBackground(colorValue, mode = getCurrentThemeMo } function normalizeDynamicBackgroundOptions(rawOptions) { - const rawList = Array.isArray(rawOptions) - ? rawOptions - : rawOptions && Array.isArray(rawOptions.backgrounds) - ? rawOptions.backgrounds - : []; + const rawList = Array.isArray(rawOptions) ? rawOptions : rawOptions && Array.isArray(rawOptions.backgrounds) ? rawOptions.backgrounds : []; const mergedByKey = {}; @@ -1170,9 +1149,7 @@ function refreshBackgroundPalettes() { const noteId = card.dataset.id || ""; if (!noteId) return; - const editLink = card.querySelector( - '.note-hover-actions a[href*="/admin/shaare/"], .note-hover-actions a[href*="do=editlink"], .note-hover-actions a[title="Edit"]', - ); + const editLink = card.querySelector('.note-hover-actions a[href*="/admin/shaare/"], .note-hover-actions a[href*="do=editlink"], .note-hover-actions a[title="Edit"]'); palettePopup.innerHTML = generatePaletteButtons({ id: noteId, @@ -1190,16 +1167,9 @@ function refreshBackgroundPalettes() { if (!noteId) return; const actions = card.querySelector(".link-actions"); - const editLink = actions - ? actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]') - : null; + const editLink = actions ? actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]') : null; - palettePopup.innerHTML = generateBookmarkPaletteButtons( - noteId, - editLink && editLink.href ? editLink.href : "#", - getElementVisualColor(card), - getElementVisualBackground(card), - ); + palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editLink && editLink.href ? editLink.href : "#", getElementVisualColor(card), getElementVisualBackground(card)); }); const modal = getOpenModalOverlay(); @@ -1236,7 +1206,6 @@ function parseBackgroundManifestPayload(rawPayload) { } } - let isThemeModeBackgroundSyncInitialized = false; function initThemeModeBackgroundSync() { @@ -1311,22 +1280,6 @@ function ensureBackgroundStudioPanel() { panel.style.display = "none"; document.body.appendChild(panel); - document.addEventListener("click", (e) => { - const p = document.getElementById("shaarli-bg-studio"); - if (!p || !p.classList.contains("open")) return; - if (e.target.closest("#shaarli-bg-studio")) return; - const anchor = p.__anchorEl; - if (anchor && (e.target === anchor || e.target.closest(`#${anchor.id}`))) return; - closeBackgroundStudioPanel(); - }); - - document.addEventListener("keydown", (e) => { - if (e.key !== "Escape") return; - const p = document.getElementById("shaarli-bg-studio"); - if (!p || !p.classList.contains("open")) return; - closeBackgroundStudioPanel(); - }); - panel.addEventListener("click", (e) => { const actionBtn = e.target.closest("button[data-bg-studio-action]"); if (!actionBtn) return; @@ -1562,17 +1515,7 @@ function closeBackgroundStudioPanel() { panel.__anchorEl = null; } -function openBackgroundStudioPanel({ - anchorEl, - mode, - entityId, - editUrl, - currentColor, - currentFilter, - currentBackground, - currentFontColor, - title, -}) { +function openBackgroundStudioPanel({ anchorEl, mode, entityId, editUrl, currentColor, currentFilter, currentBackground, currentFontColor, title }) { ensureBackgroundStudioPanel(); const panel = document.getElementById("shaarli-bg-studio"); if (!panel) return; @@ -1693,12 +1636,7 @@ function renderBackgroundStudioPanel(panel) { if (!panel) return; const activeEl = document.activeElement; - const wasSearchFocused = !!( - activeEl && - panel.contains(activeEl) && - activeEl.classList && - activeEl.classList.contains("bg-studio-search-input") - ); + const wasSearchFocused = !!(activeEl && panel.contains(activeEl) && activeEl.classList && activeEl.classList.contains("bg-studio-search-input")); const caretStart = wasSearchFocused && typeof activeEl.selectionStart === "number" ? activeEl.selectionStart : null; const caretEnd = wasSearchFocused && typeof activeEl.selectionEnd === "number" ? activeEl.selectionEnd : null; @@ -1733,14 +1671,15 @@ function renderBackgroundStudioPanel(panel) { .join(""); // Colors row with custom color picker button - const colorsRowHtml = colors - .filter((c) => c.key !== "default" && c.key !== "custom") - .map((c) => { - const isActive = color === c.key; - const hex = c.color || ""; - return ``; - }) - .join("") + + const colorsRowHtml = + colors + .filter((c) => c.key !== "default" && c.key !== "custom") + .map((c) => { + const isActive = color === c.key; + const hex = c.color || ""; + return ``; + }) + .join("") + // Add custom color button (opens color picker) ``; - }) - .join("") + + const fontColorsRowHtml = + NOTE_FONT_COLOR_OPTIONS.filter((c) => c.key !== "custom") + .map((c) => { + const isActive = currentFontColor === c.key; + const value = c.value || "auto"; + const style = value === "auto" ? "background: linear-gradient(135deg, #f5f7fb 50%, #202124 50%);" : `background-color: ${value};`; + return ``; + }) + .join("") + // Add custom font color button ``, @@ -2687,9 +2617,7 @@ async function createNewTodoViaForm({ title, items = [], visual = null }) { formData.set("lf_url", todoUrl); formData.set("lf_title", title || ""); - const normalizedItems = (Array.isArray(items) ? items : []) - .map((it) => ({ checked: !!(it && it.checked), text: String((it && it.text) || "").trim() })) - .filter((it) => it.text); + const normalizedItems = (Array.isArray(items) ? items : []).map((it) => ({ checked: !!(it && it.checked), text: String((it && it.text) || "").trim() })).filter((it) => it.text); const markdown = normalizedItems.map((it) => `- [${it.checked ? "x" : " "}] ${it.text}`).join("\n"); formData.set("lf_description", markdown); @@ -3016,20 +2944,13 @@ function buildTodoPreviewHtml(items) { } function escapeHtml(str) { - return String(str) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/\"/g, """) - .replace(/'/g, "'"); + return String(str).replace(/&/g, "&").replace(//g, ">").replace(/\"/g, """).replace(/'/g, "'"); } function parseTodoMarkdown(markdown) { const raw = String(markdown || ""); // Minimal normalization for parsing lines - const normalized = raw - .replace(//gi, "\n") - .replace(/<\/(p|div|li|h\d)\s*>/gi, "\n"); + const normalized = raw.replace(//gi, "\n").replace(/<\/(p|div|li|h\d)\s*>/gi, "\n"); let text = normalized; try { @@ -3053,9 +2974,7 @@ function parseTodoMarkdown(markdown) { const listItemEls = Array.from(tmp.querySelectorAll("li")); if (listItemEls.length > 0) { - const items = listItemEls - .map((li) => ({ checked: false, text: (li.textContent || "").trim() })) - .filter((it) => it.text); + const items = listItemEls.map((li) => ({ checked: false, text: (li.textContent || "").trim() })).filter((it) => it.text); if (items.length > 0) { return { headerLines: [], footerLines: [], items }; } @@ -3066,12 +2985,7 @@ function parseTodoMarkdown(markdown) { } const lines = text.split(/\r?\n/); - const taskRegexes = [ - /^\s*[-*+]\s*\[([ xX])\]\s*(.*)$/, - /^\s*\[([ xX])\]\s*(.*)$/, - /^\s*☐\s*(.*)$/, - /^\s*☑\s*(.*)$/, - ]; + const taskRegexes = [/^\s*[-*+]\s*\[([ xX])\]\s*(.*)$/, /^\s*\[([ xX])\]\s*(.*)$/, /^\s*☐\s*(.*)$/, /^\s*☑\s*(.*)$/]; let firstTaskIndex = -1; let lastTaskIndex = -1; @@ -3857,9 +3771,9 @@ function initNoteView(linkList, container) { // Check for hash parameter to auto-open a specific note const hash = window.location.hash; - if (hash && hash.startsWith('#open-note-')) { - const noteId = hash.replace('#open-note-', ''); - const targetNote = notes.find(n => String(n.id) === String(noteId)); + if (hash && hash.startsWith("#open-note-")) { + const noteId = hash.replace("#open-note-", ""); + const targetNote = notes.find((n) => String(n.id) === String(noteId)); if (targetNote) { // Clear the hash to avoid reopening on refresh history.replaceState(null, null, window.location.pathname + window.location.search); @@ -4003,9 +3917,7 @@ function initNoteView(linkList, container) { togglePinTag(noteId, editUrl, modalPinBtn); const isPinned = modalPinBtn.classList.contains("active"); - let tags = (modalCard.dataset.tags || "") - .split("||") - .filter((t) => t); + let tags = (modalCard.dataset.tags || "").split("||").filter((t) => t); if (isPinned) { if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin"); @@ -4238,9 +4150,7 @@ function initArchiveView(linkList, container) { togglePinTag(noteId, editUrl, modalPinBtn); const isPinned = modalPinBtn.classList.contains("active"); - let tags = (modalCard.dataset.tags || "") - .split("||") - .filter((t) => t); + let tags = (modalCard.dataset.tags || "").split("||").filter((t) => t); if (isPinned) { if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin"); @@ -4449,7 +4359,7 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) { body.innerHTML = renderMarkdown(note._noteMarkdown); } else { const textToRender = note.descText || ""; - if (textToRender.trim().startsWith('
    ') || textToRender.trim().startsWith('<')) { + if (textToRender.trim().startsWith('
    ') || textToRender.trim().startsWith("<")) { body.innerHTML = textToRender; } else { body.innerHTML = renderMarkdown(textToRender); @@ -4775,10 +4685,7 @@ async function saveOpenNoteEditorModal(modal) { return; } - const changed = - state.hasChanges || - title !== (state.lastSavedTitle || "") || - markdown !== (state.lastSavedMarkdown || ""); + const changed = state.hasChanges || title !== (state.lastSavedTitle || "") || markdown !== (state.lastSavedMarkdown || ""); if (!changed || state.isSaving) return; @@ -5004,14 +4911,18 @@ window.setNoteFilter = function (noteId, filterKey, editUrl) { if (card) { const colorClass = Array.from(card.classList).find((cls) => cls.startsWith("note-color-")); const color = colorClass ? colorClass.replace("note-color-", "") : card.dataset.color || "default"; - applyNoteVisualState(card, { color, filter: normalizedFilterKey }); + const background = getElementVisualBackground(card); + const fontColor = getElementVisualFontColor(card); + applyNoteVisualState(card, { color, filter: normalizedFilterKey, background, fontColor }); } const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`); if (bookmarkCard) { const colorClass = Array.from(bookmarkCard.classList).find((cls) => cls.startsWith("note-color-")); const color = colorClass ? colorClass.replace("note-color-", "") : bookmarkCard.dataset.color || "default"; - applyNoteVisualState(bookmarkCard, { color, filter: normalizedFilterKey }); + const background = getElementVisualBackground(bookmarkCard); + const fontColor = getElementVisualFontColor(bookmarkCard); + applyNoteVisualState(bookmarkCard, { color, filter: normalizedFilterKey, background, fontColor }); const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup"); if (palettePopup) { palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedFilterKey); @@ -5090,7 +5001,8 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) { const colorClass = Array.from(card.classList).find((cls) => cls.startsWith("note-color-")); const color = colorClass ? colorClass.replace("note-color-", "") : card.dataset.color || "default"; const filter = card.dataset.filter || "none"; - applyNoteVisualState(card, { color, filter, background: normalizedBackgroundKey }); + const fontColor = getElementVisualFontColor(card); + applyNoteVisualState(card, { color, filter, background: normalizedBackgroundKey, fontColor }); } const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`); @@ -5098,7 +5010,8 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) { const colorClass = Array.from(bookmarkCard.classList).find((cls) => cls.startsWith("note-color-")); const color = colorClass ? colorClass.replace("note-color-", "") : bookmarkCard.dataset.color || "default"; const filter = bookmarkCard.dataset.filter || "none"; - applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey }); + const fontColor = getElementVisualFontColor(bookmarkCard); + applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey, fontColor }); } const modal = getOpenModalOverlay(); @@ -5611,17 +5524,16 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) { let prevFontColorKey = "auto"; if ((mode || "entity") === "draft") { const bgPanel = document.getElementById("shaarli-bg-studio"); - prevFontColorKey = bgPanel ? (bgPanel.dataset.fontColor || "auto") : "auto"; - } else - if ((mode || "entity") === "modal") { - const modal = getOpenModalOverlay(); - const modalCard = modal ? modal.querySelector(".note-modal") : null; - prevFontColorKey = (modalCard && modalCard.dataset.fontColor) ? modalCard.dataset.fontColor : "auto"; - } else { - const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`); - const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`); - prevFontColorKey = (noteCard && noteCard.dataset.fontColor) ? noteCard.dataset.fontColor : ((bookmarkCard && bookmarkCard.dataset.fontColor) ? bookmarkCard.dataset.fontColor : "auto"); - } + prevFontColorKey = bgPanel ? bgPanel.dataset.fontColor || "auto" : "auto"; + } else if ((mode || "entity") === "modal") { + const modal = getOpenModalOverlay(); + const modalCard = modal ? modal.querySelector(".note-modal") : null; + prevFontColorKey = modalCard && modalCard.dataset.fontColor ? modalCard.dataset.fontColor : "auto"; + } else { + const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`); + const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`); + prevFontColorKey = noteCard && noteCard.dataset.fontColor ? noteCard.dataset.fontColor : bookmarkCard && bookmarkCard.dataset.fontColor ? bookmarkCard.dataset.fontColor : "auto"; + } panel.dataset.prevFontColorKey = prevFontColorKey; } else { panel.dataset.prevFontColorKey = ""; @@ -5634,25 +5546,24 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) { if (bgPanel) { prevColorKey = bgPanel.dataset.color || "default"; } - } else - if ((mode || "entity") === "modal") { - const modal = getOpenModalOverlay(); - const modalCard = modal ? modal.querySelector(".note-modal") : null; - if (modalCard) { - const isCustom = modalCard.dataset.color === "custom"; - const cc = modalCard.dataset.customColor || ""; - prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(modalCard); - } - } else { - const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`); - const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`); - const el = noteCard || bookmarkCard; - if (el) { - const isCustom = el.dataset.color === "custom"; - const cc = el.dataset.customColor || ""; - prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(el); - } + } else if ((mode || "entity") === "modal") { + const modal = getOpenModalOverlay(); + const modalCard = modal ? modal.querySelector(".note-modal") : null; + if (modalCard) { + const isCustom = modalCard.dataset.color === "custom"; + const cc = modalCard.dataset.customColor || ""; + prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(modalCard); } + } else { + const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`); + const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`); + const el = noteCard || bookmarkCard; + if (el) { + const isCustom = el.dataset.color === "custom"; + const cc = el.dataset.customColor || ""; + prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(el); + } + } panel.dataset.prevColorKey = prevColorKey; } else { panel.dataset.prevColorKey = ""; @@ -5706,14 +5617,36 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) { const m = v - c; let r, g, b; - if (h < 60) { r = c; g = x; b = 0; } - else if (h < 120) { r = x; g = c; b = 0; } - else if (h < 180) { r = 0; g = c; b = x; } - else if (h < 240) { r = 0; g = x; b = c; } - else if (h < 300) { r = x; g = 0; b = c; } - else { r = c; g = 0; b = x; } + if (h < 60) { + r = c; + g = x; + b = 0; + } else if (h < 120) { + r = x; + g = c; + b = 0; + } else if (h < 180) { + r = 0; + g = c; + b = x; + } else if (h < 240) { + r = 0; + g = x; + b = c; + } else if (h < 300) { + r = x; + g = 0; + b = c; + } else { + r = c; + g = 0; + b = x; + } - const toHex = (n) => Math.round((n + m) * 255).toString(16).padStart(2, "0"); + const toHex = (n) => + Math.round((n + m) * 255) + .toString(16) + .padStart(2, "0"); return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase(); } @@ -5731,7 +5664,7 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) { let h = 0; if (d !== 0) { - if (max === r) h = ((g - b) / d + 6) % 6 * 60; + if (max === r) h = (((g - b) / d + 6) % 6) * 60; else if (max === g) h = ((b - r) / d + 2) * 60; else h = ((r - g) / d + 4) * 60; }