From 7bd618fe382b89530733cd78d4b25d93b52a7c7c Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 17 Feb 2026 11:49:47 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20ajouter=20syst=C3=A8me=20de=20filtres?= =?UTF-8?q?=20visuels=20pour=20notes=20avec=208=20effets=20CSS=20(glassmor?= =?UTF-8?q?phism,=20vignette,=20lign=C3=A9,=20quadrill=C3=A9,=20noise,=20p?= =?UTF-8?q?oints,=20rayures),=20int=C3=A9gration=20dans=20Background=20Stu?= =?UTF-8?q?dio=20avec=20palette=20d=C3=A9di=C3=A9e,=20support=20th=C3=A8me?= =?UTF-8?q?=20clair/sombre,=20et=20synchronisation=20compl=C3=A8te=20avec?= =?UTF-8?q?=20tags=20notefilter-=20pour=20persistance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shaarli-pro/css/custom_views.css | 173 +++++++++++++++++++ shaarli-pro/js/custom_views.js | 284 +++++++++++++++++++++++-------- 2 files changed, 382 insertions(+), 75 deletions(-) diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css index 6f7efdc..0c7b847 100644 --- a/shaarli-pro/css/custom_views.css +++ b/shaarli-pro/css/custom_views.css @@ -1557,4 +1557,177 @@ body.view-notes .content-container { background-size: cover; background-position: center bottom; } +} + +/* ========================================= + Note Filter Effects (8 CSS filters) + ========================================= */ + +/* 1. Glassmorphism - Effet verre givré */ +.note-card.note-filter-glass, +.note-modal.note-filter-glass, +.link-outer.note-filter-glass { + background: rgba(255, 255, 255, 0.25) !important; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.3); +} + +[data-theme="dark"] .note-card.note-filter-glass, +[data-theme="dark"] .note-modal.note-filter-glass, +[data-theme="dark"] .link-outer.note-filter-glass { + background: rgba(0, 0, 0, 0.35) !important; + border: 1px solid rgba(255, 255, 255, 0.15); +} + +/* 2. Vignette - Coins sombres */ +.note-card.note-filter-vignette::after, +.note-modal.note-filter-vignette::after, +.link-outer.note-filter-vignette::after { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + background: radial-gradient(circle, transparent 50%, rgba(0, 0, 0, 0.4) 100%); + z-index: 1; +} + +.note-card.note-filter-vignette, +.note-modal.note-filter-vignette, +.link-outer.note-filter-vignette { + position: relative; +} + +/* 3. Ligné - Papier ligné */ +.note-card.note-filter-lined, +.note-modal.note-filter-lined, +.link-outer.note-filter-lined { + background-image: repeating-linear-gradient( + transparent, + transparent 29px, + rgba(0, 0, 0, 0.1) 30px + ) !important; +} + +[data-theme="dark"] .note-card.note-filter-lined, +[data-theme="dark"] .note-modal.note-filter-lined, +[data-theme="dark"] .link-outer.note-filter-lined { + background-image: repeating-linear-gradient( + transparent, + transparent 29px, + rgba(255, 255, 255, 0.1) 30px + ) !important; +} + +/* 4. Quadrillé - Grid */ +.note-card.note-filter-grid, +.note-modal.note-filter-grid, +.link-outer.note-filter-grid { + background-image: + linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px) !important; + background-size: 20px 20px !important; +} + +[data-theme="dark"] .note-card.note-filter-grid, +[data-theme="dark"] .note-modal.note-filter-grid, +[data-theme="dark"] .link-outer.note-filter-grid { + background-image: + linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px) !important; +} + +/* 5. Noise - Texture granuleuse */ +.note-card.note-filter-noise, +.note-modal.note-filter-noise, +.link-outer.note-filter-noise { + position: relative; +} + +.note-card.note-filter-noise::before, +.note-modal.note-filter-noise::before, +.link-outer.note-filter-noise::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + opacity: 0.5; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E"); + mix-blend-mode: overlay; + z-index: 1; +} + +/* 6. Points - Bullet journal dotted */ +.note-card.note-filter-dots, +.note-modal.note-filter-dots, +.link-outer.note-filter-dots { + background-image: radial-gradient(rgba(0, 0, 0, 0.2) 1px, transparent 1px) !important; + background-size: 20px 20px !important; +} + +[data-theme="dark"] .note-card.note-filter-dots, +[data-theme="dark"] .note-modal.note-filter-dots, +[data-theme="dark"] .link-outer.note-filter-dots { + background-image: radial-gradient(rgba(255, 255, 255, 0.2) 1px, transparent 1px) !important; +} + +/* 7. Rayures - Stripes diagonales */ +.note-card.note-filter-stripes, +.note-modal.note-filter-stripes, +.link-outer.note-filter-stripes { + background-image: repeating-linear-gradient( + 45deg, + transparent, + transparent 10px, + rgba(0, 0, 0, 0.05) 10px, + rgba(0, 0, 0, 0.05) 20px + ) !important; +} + +[data-theme="dark"] .note-card.note-filter-stripes, +[data-theme="dark"] .note-modal.note-filter-stripes, +[data-theme="dark"] .link-outer.note-filter-stripes { + background-image: repeating-linear-gradient( + 45deg, + transparent, + transparent 10px, + rgba(255, 255, 255, 0.05) 10px, + rgba(255, 255, 255, 0.05) 20px + ) !important; +} + +/* ========================================= + Filter-specific row layout for palette + ========================================= */ +.palette-row-filters { + display: grid; + grid-template-columns: repeat(4, 28px); + gap: 10px; + justify-content: start; +} + +.palette-btn-filter, +.palette-btn-filter-none { + display: flex; + align-items: center; + justify-content: center; + background: rgba(15, 23, 42, 0.05); +} + +.palette-btn-filter i, +.palette-btn-filter-none i { + font-size: 1rem; + color: var(--text-color, #334155); +} + +[data-theme="dark"] .palette-btn-filter, +[data-theme="dark"] .palette-btn-filter-none { + background: rgba(255, 255, 255, 0.1); +} + +[data-theme="dark"] .palette-btn-filter i, +[data-theme="dark"] .palette-btn-filter-none i { + color: #e8eaed; } \ No newline at end of file diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js index 5df2183..1676a31 100644 --- a/shaarli-pro/js/custom_views.js +++ b/shaarli-pro/js/custom_views.js @@ -119,6 +119,18 @@ const NOTE_COLOR_OPTIONS = [ } ]; +const NOTE_FILTER_OPTIONS = [ + { key: "none", label: "Aucun", icon: "mdi-close-circle-outline" }, + { key: "glass", label: "Glass", icon: "mdi-blur" }, + { key: "vignette", label: "Vignette", icon: "mdi-vignette" }, + { key: "lined", label: "Ligné", icon: "mdi-format-align-justify" }, + { key: "grid", label: "Quadrillé", icon: "mdi-grid" }, + { key: "noise", label: "Noise", icon: "mdi-grain" }, + { key: "dots", label: "Points", icon: "mdi-dots-grid" }, + { key: "stripes", label: "Rayures", icon: "mdi-slash-forward" }, +]; + +const NOTE_FILTER_TAG_PREFIX = "notefilter-"; const NOTE_BACKGROUND_TAG_PREFIX = "notebg-"; function resolveThemeAssetBasePath() { @@ -361,11 +373,25 @@ function getElementVisualBackground(element) { return normalizedBackground || "none"; } -function refreshNoteBackgroundVisuals() { +function normalizeFilterKey(key) { + if (typeof key !== "string") return "none"; + const normalized = key.trim().toLowerCase(); + if (NOTE_FILTER_OPTIONS.some((opt) => opt.key === normalized)) { + return normalized; + } + return "none"; +} + +function getElementVisualFilter(element) { + if (!element) return "none"; + return normalizeFilterKey(element.dataset.filter || ""); +} + +function refreshNoteFilterVisuals() { document.querySelectorAll(".note-card, .note-modal, .link-outer").forEach((element) => { applyNoteVisualState(element, { color: getElementVisualColor(element), - background: getElementVisualBackground(element), + filter: getElementVisualFilter(element), }); }); } @@ -566,8 +592,9 @@ function ensureBackgroundStudioPanel() { } if (action === "set-filter") { - panelEl.dataset.filter = actionBtn.dataset.filter || "all"; - renderBackgroundStudioPanel(panelEl); + const filterKey = actionBtn.dataset.filter || "none"; + if (mode === "modal") setModalNoteFilter(filterKey); + else setNoteFilter(entityId, filterKey, editUrl); return; } @@ -582,10 +609,10 @@ function ensureBackgroundStudioPanel() { if (action === "set-defaults") { if (mode === "modal") { setModalNoteColor("default"); - setModalNoteBackground("none"); + setModalNoteFilter("none"); } else { setNoteColor(entityId, "default", editUrl); - setNoteBackground(entityId, "none", editUrl); + setNoteFilter(entityId, "none", editUrl); } return; } @@ -680,6 +707,7 @@ function openBackgroundStudioPanel({ entityId, editUrl, currentColor, + currentFilter, currentBackground, title, }) { @@ -691,8 +719,8 @@ function openBackgroundStudioPanel({ panel.dataset.entityId = entityId || ""; panel.dataset.editUrl = editUrl || ""; panel.dataset.color = currentColor || "default"; + panel.dataset.filter = normalizeFilterKey(currentFilter || "") || "none"; panel.dataset.background = normalizeBackgroundKey(currentBackground || "") || "none"; - panel.dataset.filter = panel.dataset.filter || "all"; panel.dataset.query = panel.dataset.query || ""; panel.dataset.title = title || "Mes images & couleurs"; panel.__anchorEl = anchorEl || null; @@ -813,37 +841,22 @@ function renderBackgroundStudioPanel(panel) { const title = panel.dataset.title || "Mes images & couleurs"; const color = panel.dataset.color || "default"; - const background = normalizeBackgroundKey(panel.dataset.background || "") || "none"; - const filter = panel.dataset.filter || "all"; + const currentFilter = panel.dataset.filter || "none"; const query = normalizeSearchText(panel.dataset.query || ""); const { colors, backgrounds } = getBackgroundStudioItems(); - const filterMatch = (item) => { - if (filter === "all") return true; - if (filter === "color") return item.type === "color"; - if (item.type !== "background") return false; - if (filter === "solid") return item.category === "solid"; - if (filter === "gradient") return item.category === "gradient"; - if (filter === "grid") return item.category === "grid"; - if (filter === "image") return item.category === "image"; - if (filter === "texture") return item.category === "texture"; - return true; - }; - - const queryMatch = (item) => { + // Filter backgrounds based on search query + const filteredBackgrounds = backgrounds.filter((b) => { if (!query) return true; - const hay = normalizeSearchText([item.label, ...(item.keywords || [])].join(" ")); + const hay = normalizeSearchText([b.label, b.key, "image", "background"].join(" ")); return hay.includes(query); - }; + }); - const galleryItems = backgrounds - .filter((b) => filterMatch(b) && queryMatch(b)) - .map((b) => ({ ...b, kind: "background" })); - - const galleryHtml = galleryItems + // Generate gallery HTML for backgrounds + const galleryHtml = filteredBackgrounds .map((item) => { - const isActive = background === item.key; + const isActive = panel.dataset.background === item.key; const common = `class="bg-studio-thumb ${isActive ? "is-active" : ""}" type="button"`; const thumb = item.key === "none" @@ -864,17 +877,11 @@ function renderBackgroundStudioPanel(panel) { .join(""); const filterBtn = (key, icon, label) => { - const active = filter === key; + const active = currentFilter === key; return ``; }; - const filtersHtml = [ - filterBtn("solid", "mdi-checkbox-blank-outline", "Couleur unie"), - filterBtn("gradient", "mdi-gradient-horizontal", "Dégradé"), - filterBtn("grid", "mdi-grid", "Motif grille"), - filterBtn("image", "mdi-image-outline", "Image personnalisée"), - filterBtn("texture", "mdi-checkerboard", "Texture"), - ].join(""); + const filtersHtml = NOTE_FILTER_OPTIONS.map((f) => filterBtn(f.key, f.icon, f.label)).join(""); const showClear = !!(panel.dataset.query || "").trim(); @@ -893,13 +900,13 @@ function renderBackgroundStudioPanel(panel) {
${colorsRowHtml}
-
Backgrounds:
+
Filtres:
${filtersHtml}
`; @@ -929,13 +936,18 @@ function applyNoteVisualState(element, note) { const color = resolvedColorOption ? resolvedColorOption.key : "default"; const colorValue = getThemeColorValue(resolvedColorOption); const foregroundColor = getReadableForegroundForBackground(colorValue); + const filter = normalizeFilterKey(note.filter || "none"); const normalizedBackground = normalizeBackgroundKey(note.background || ""); const background = normalizedBackground || "none"; element.classList.forEach((cls) => { if (cls.startsWith("note-color-")) element.classList.remove(cls); + if (cls.startsWith("note-filter-")) element.classList.remove(cls); }); element.classList.add(`note-color-${color}`); + if (filter !== "none") { + element.classList.add(`note-filter-${filter}`); + } if (colorValue) { element.style.backgroundColor = colorValue; @@ -966,6 +978,7 @@ function applyNoteVisualState(element, note) { } element.dataset.color = color; + element.dataset.filter = filter; } function extractNoteVisualStateFromTags(tags) { @@ -980,6 +993,15 @@ function extractNoteVisualStateFromTags(tags) { } } + let filter = "none"; + const foundFilterTag = safeTags.find((t) => typeof t === "string" && t.startsWith(NOTE_FILTER_TAG_PREFIX)); + if (foundFilterTag) { + const candidate = normalizeFilterKey(foundFilterTag.substring(NOTE_FILTER_TAG_PREFIX.length)); + if (candidate && candidate !== "none") { + filter = candidate; + } + } + let background = "none"; const foundBgTag = safeTags.find((t) => typeof t === "string" && t.startsWith(NOTE_BACKGROUND_TAG_PREFIX)); if (foundBgTag) { @@ -989,7 +1011,7 @@ function extractNoteVisualStateFromTags(tags) { } } - return { color, background }; + return { color, filter, background }; } function initBookmarkPaletteButtons() { @@ -1001,8 +1023,8 @@ function initBookmarkPaletteButtons() { if (!cardId) return; const tags = Array.from(card.querySelectorAll(".link-tag a")).map((a) => (a.textContent || "").trim()); - const { color, background } = extractNoteVisualStateFromTags(tags); - applyNoteVisualState(card, { color, background }); + const { color, filter, background } = extractNoteVisualStateFromTags(tags); + applyNoteVisualState(card, { color, filter, background }); const actions = card.querySelector(".link-actions"); if (!actions) return; @@ -1042,6 +1064,7 @@ function initBookmarkPaletteButtons() { entityId: cardId, editUrl, currentColor: getElementVisualColor(card), + currentFilter: getElementVisualFilter(card), currentBackground: getElementVisualBackground(card), title: "Mes images & couleurs", }); @@ -1055,29 +1078,29 @@ function initBookmarkPaletteButtons() { }); } -function generateBookmarkPaletteButtons(bookmarkId, editUrl, currentColor, currentBackground) { +function generateBookmarkPaletteButtons(bookmarkId, editUrl, currentColor, currentFilter) { return generateUnifiedPaletteMenu({ entityId: bookmarkId, editUrl, currentColor, - currentBackground, + currentFilter, mode: "entity", }); } -function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentBackground, mode }) { +function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentFilter, mode }) { const color = currentColor || "default"; - const background = normalizeBackgroundKey(currentBackground || "") || "none"; + const filter = normalizeFilterKey(currentFilter || "") || "none"; const onColorClick = mode === "modal" ? (c) => `setModalNoteColor('${c}')` : (c) => `setNoteColor('${entityId}', '${c}', '${editUrl}')`; - const onBackgroundClick = + const onFilterClick = mode === "modal" - ? (k) => `setModalNoteBackground('${k}')` - : (k) => `setNoteBackground('${entityId}', '${k}', '${editUrl}')`; + ? (k) => `setModalNoteFilter('${k}')` + : (k) => `setNoteFilter('${entityId}', '${k}', '${editUrl}')`; const colorButtons = [ ``, @@ -1087,11 +1110,10 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentBa }), ].join(""); - const backgroundButtons = [ - ``, - ...getAvailableBackgroundOptionsForMode().map((bg) => { - const bgUrl = getNoteBackgroundUrl(bg.key); - return ``; + const filterButtons = [ + ``, + ...NOTE_FILTER_OPTIONS.filter((opt) => opt.key !== "none").map((opt) => { + return ``; }), ].join(""); @@ -1102,8 +1124,8 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentBa
-
Images
-
${backgroundButtons}
+
Filtres
+
${filterButtons}
`; } @@ -1116,6 +1138,9 @@ function syncNoteFromCardElement(note, card) { note.color = colorClass.replace("note-color-", "") || "default"; } + const filter = card.dataset.filter; + note.filter = filter && filter !== "none" ? filter : "none"; + const background = card.dataset.background; note.background = background && background !== "none" ? background : "none"; } @@ -1470,6 +1495,7 @@ function initNoteView(linkList, container) { entityId: modalCard ? modalCard.dataset.noteId || "" : "", editUrl: modalCard ? modalCard.dataset.editUrl || "" : "", currentColor: modalCard ? getElementVisualColor(modalCard) : "default", + currentFilter: modalCard ? getElementVisualFilter(modalCard) : "none", currentBackground: modalCard ? getElementVisualBackground(modalCard) : "none", title: "Mes images & couleurs", }); @@ -1587,6 +1613,7 @@ function parseNoteFromLink(linkEl) { const tags = []; let color = "default"; + let filter = "none"; let background = "none"; linkEl.querySelectorAll(".link-tag-list a").forEach((tag) => { const t = tag.textContent.trim(); @@ -1599,6 +1626,8 @@ function parseNoteFromLink(linkEl) { } else { tags.push(t); } + } else if (t.startsWith(NOTE_FILTER_TAG_PREFIX)) { + filter = normalizeFilterKey(t.substring(NOTE_FILTER_TAG_PREFIX.length)) || "none"; } else if (t.startsWith(NOTE_BACKGROUND_TAG_PREFIX)) { background = normalizeBackgroundKey(t.substring(NOTE_BACKGROUND_TAG_PREFIX.length)) || "none"; } else { @@ -1616,7 +1645,7 @@ function parseNoteFromLink(linkEl) { // User requested "availability of the tag 'shaarli-pin' as the main source" const isPinned = tags.includes("shaarli-pin"); - return { id, title, descHtml, descText, coverImage, url, tags, color, background, editUrl, deleteUrl, pinUrl, isPinned }; + return { id, title, descHtml, descText, coverImage, url, tags, color, filter, background, editUrl, deleteUrl, pinUrl, isPinned }; } function renderNotes(container, notes, viewMode) { @@ -1726,6 +1755,7 @@ function renderNotes(container, notes, viewMode) { entityId: note.id, editUrl: note.editUrl, currentColor: getElementVisualColor(card), + currentFilter: getElementVisualFilter(card), currentBackground: getElementVisualBackground(card), title: "Mes images & couleurs", }); @@ -1817,7 +1847,7 @@ function generateModalPaletteButtons(note) { entityId: note && note.id ? note.id : "", editUrl: note && note.editUrl ? note.editUrl : "", currentColor: note && note.color ? note.color : "default", - currentBackground: note && note.background ? note.background : "none", + currentFilter: note && note.filter ? note.filter : "none", mode: "modal", }); } @@ -1843,15 +1873,15 @@ window.setModalNoteColor = function (color) { } }; -window.setModalNoteBackground = function (backgroundKey) { +window.setModalNoteFilter = function (filterKey) { const modal = document.querySelector(".note-modal-overlay"); if (!modal || !modal.currentNote) return; const currentNote = modal.currentNote; - const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none"; - setNoteBackground(currentNote.id, normalizedBackgroundKey, currentNote.editUrl); + const normalizedFilterKey = normalizeFilterKey(filterKey) || "none"; + setNoteFilter(currentNote.id, normalizedFilterKey, currentNote.editUrl); - currentNote.background = normalizedBackgroundKey; + currentNote.filter = normalizedFilterKey; const modalCard = modal.querySelector(".note-modal"); if (modalCard) { applyNoteVisualState(modalCard, currentNote); @@ -1870,7 +1900,7 @@ function generatePaletteButtons(note) { entityId: note && note.id ? note.id : "", editUrl: note && note.editUrl ? note.editUrl : "", currentColor: note && note.color ? note.color : "default", - currentBackground: note && note.background ? note.background : "none", + currentFilter: note && note.filter ? note.filter : "none", mode: "entity", }); } @@ -1879,17 +1909,17 @@ window.setNoteColor = function (noteId, color, editUrl) { // 1. Visual Update (Immediate feedback) const card = document.querySelector(`.note-card[data-id="${noteId}"]`); if (card) { - const background = card.dataset.background || "none"; - applyNoteVisualState(card, { color, background }); + const filter = card.dataset.filter || "none"; + applyNoteVisualState(card, { color, filter }); } const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`); if (bookmarkCard) { - const background = bookmarkCard.dataset.background || "none"; - applyNoteVisualState(bookmarkCard, { color, background }); + const filter = bookmarkCard.dataset.filter || "none"; + applyNoteVisualState(bookmarkCard, { color, filter }); const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup"); if (palettePopup) { - palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, background); + palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, filter); } } @@ -1969,6 +1999,90 @@ window.setNoteColor = function (noteId, color, editUrl) { }); }; +window.setNoteFilter = function (noteId, filterKey, editUrl) { + const normalizedFilterKey = normalizeFilterKey(filterKey) || "none"; + + const card = document.querySelector(`.note-card[data-id="${noteId}"]`); + 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 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 palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup"); + if (palettePopup) { + palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedFilterKey); + } + } + + const modal = document.querySelector(".note-modal-overlay"); + if (modal && modal.currentNote && String(modal.currentNote.id) === String(noteId)) { + modal.currentNote.filter = normalizedFilterKey; + const modalCard = modal.querySelector(".note-modal"); + if (modalCard) { + applyNoteVisualState(modalCard, modal.currentNote); + } + const modalColorPopup = modal.querySelector("#note-modal-color-popup"); + if (modalColorPopup) { + modalColorPopup.innerHTML = generateModalPaletteButtons(modal.currentNote); + } + } + + fetch(editUrl) + .then((response) => response.text()) + .then((html) => { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + const form = doc.querySelector('form[name="linkform"]'); + + if (!form) throw new Error("Could not find edit form"); + + const formData = new URLSearchParams(); + const inputs = form.querySelectorAll("input, textarea"); + + inputs.forEach((input) => { + if (input.type === "checkbox") { + if (input.checked) formData.append(input.name, input.value || "on"); + } else if (input.name) { + formData.append(input.name, input.value); + } + }); + + let currentTags = formData.get("lf_tags") || ""; + let tagsArray = currentTags.split(/[\s,]+/).filter((t) => t.trim() !== ""); + + tagsArray = tagsArray.filter((t) => !t.startsWith(NOTE_FILTER_TAG_PREFIX)); + if (normalizedFilterKey && normalizedFilterKey !== "none") { + tagsArray.push(`${NOTE_FILTER_TAG_PREFIX}${normalizedFilterKey}`); + } + + formData.set("lf_tags", tagsArray.join(" ")); + formData.append("save_edit", "1"); + + return fetch(form.action, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: formData.toString(), + }); + }) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to save filter"); + } + }) + .catch((err) => { + console.error("Error saving note filter:", err); + alert("Erreur lors de la sauvegarde du filtre. Veuillez rafraîchir la page."); + }); +}; + window.setNoteBackground = function (noteId, backgroundKey, editUrl) { const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none"; @@ -1976,18 +2090,16 @@ window.setNoteBackground = function (noteId, backgroundKey, 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, background: normalizedBackgroundKey }); + const filter = card.dataset.filter || "none"; + applyNoteVisualState(card, { color, filter, background: normalizedBackgroundKey }); } 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, background: normalizedBackgroundKey }); - const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup"); - if (palettePopup) { - palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedBackgroundKey); - } + const filter = bookmarkCard.dataset.filter || "none"; + applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey }); } const modal = document.querySelector(".note-modal-overlay"); @@ -2053,6 +2165,28 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) { }); }; +window.setModalNoteBackground = function (backgroundKey) { + const modal = document.querySelector(".note-modal-overlay"); + if (!modal || !modal.currentNote) return; + + const currentNote = modal.currentNote; + const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none"; + setNoteBackground(currentNote.id, normalizedBackgroundKey, currentNote.editUrl); + + currentNote.background = normalizedBackgroundKey; + const modalCard = modal.querySelector(".note-modal"); + if (modalCard) { + applyNoteVisualState(modalCard, currentNote); + } + + const modalColorPopup = modal.querySelector("#note-modal-color-popup"); + if (modalColorPopup) { + modalColorPopup.innerHTML = generateModalPaletteButtons(currentNote); + modalColorPopup.classList.add("open"); + positionPalettePopup(modalColorPopup); + } +}; + function addTagToNote(editUrl, tag) { return fetch(editUrl) .then((response) => response.text())