feat: ajouter système de filtres visuels pour notes avec 8 effets CSS (glassmorphism, vignette, ligné, quadrillé, noise, points, rayures), intégration dans Background Studio avec palette dédiée, support thème clair/sombre, et synchronisation complète avec tags notefilter- pour persistance

This commit is contained in:
Bruno Charest 2026-02-17 11:49:47 -05:00
parent 97f3bbca45
commit 7bd618fe38
2 changed files with 382 additions and 75 deletions

View File

@ -1558,3 +1558,176 @@ body.view-notes .content-container {
background-position: center bottom; 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;
}

View File

@ -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-"; const NOTE_BACKGROUND_TAG_PREFIX = "notebg-";
function resolveThemeAssetBasePath() { function resolveThemeAssetBasePath() {
@ -361,11 +373,25 @@ function getElementVisualBackground(element) {
return normalizedBackground || "none"; 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) => { document.querySelectorAll(".note-card, .note-modal, .link-outer").forEach((element) => {
applyNoteVisualState(element, { applyNoteVisualState(element, {
color: getElementVisualColor(element), color: getElementVisualColor(element),
background: getElementVisualBackground(element), filter: getElementVisualFilter(element),
}); });
}); });
} }
@ -566,8 +592,9 @@ function ensureBackgroundStudioPanel() {
} }
if (action === "set-filter") { if (action === "set-filter") {
panelEl.dataset.filter = actionBtn.dataset.filter || "all"; const filterKey = actionBtn.dataset.filter || "none";
renderBackgroundStudioPanel(panelEl); if (mode === "modal") setModalNoteFilter(filterKey);
else setNoteFilter(entityId, filterKey, editUrl);
return; return;
} }
@ -582,10 +609,10 @@ function ensureBackgroundStudioPanel() {
if (action === "set-defaults") { if (action === "set-defaults") {
if (mode === "modal") { if (mode === "modal") {
setModalNoteColor("default"); setModalNoteColor("default");
setModalNoteBackground("none"); setModalNoteFilter("none");
} else { } else {
setNoteColor(entityId, "default", editUrl); setNoteColor(entityId, "default", editUrl);
setNoteBackground(entityId, "none", editUrl); setNoteFilter(entityId, "none", editUrl);
} }
return; return;
} }
@ -680,6 +707,7 @@ function openBackgroundStudioPanel({
entityId, entityId,
editUrl, editUrl,
currentColor, currentColor,
currentFilter,
currentBackground, currentBackground,
title, title,
}) { }) {
@ -691,8 +719,8 @@ function openBackgroundStudioPanel({
panel.dataset.entityId = entityId || ""; panel.dataset.entityId = entityId || "";
panel.dataset.editUrl = editUrl || ""; panel.dataset.editUrl = editUrl || "";
panel.dataset.color = currentColor || "default"; panel.dataset.color = currentColor || "default";
panel.dataset.filter = normalizeFilterKey(currentFilter || "") || "none";
panel.dataset.background = normalizeBackgroundKey(currentBackground || "") || "none"; panel.dataset.background = normalizeBackgroundKey(currentBackground || "") || "none";
panel.dataset.filter = panel.dataset.filter || "all";
panel.dataset.query = panel.dataset.query || ""; panel.dataset.query = panel.dataset.query || "";
panel.dataset.title = title || "Mes images & couleurs"; panel.dataset.title = title || "Mes images & couleurs";
panel.__anchorEl = anchorEl || null; panel.__anchorEl = anchorEl || null;
@ -813,37 +841,22 @@ function renderBackgroundStudioPanel(panel) {
const title = panel.dataset.title || "Mes images & couleurs"; const title = panel.dataset.title || "Mes images & couleurs";
const color = panel.dataset.color || "default"; const color = panel.dataset.color || "default";
const background = normalizeBackgroundKey(panel.dataset.background || "") || "none"; const currentFilter = panel.dataset.filter || "none";
const filter = panel.dataset.filter || "all";
const query = normalizeSearchText(panel.dataset.query || ""); const query = normalizeSearchText(panel.dataset.query || "");
const { colors, backgrounds } = getBackgroundStudioItems(); const { colors, backgrounds } = getBackgroundStudioItems();
const filterMatch = (item) => { // Filter backgrounds based on search query
if (filter === "all") return true; const filteredBackgrounds = backgrounds.filter((b) => {
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) => {
if (!query) return true; 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); return hay.includes(query);
}; });
const galleryItems = backgrounds // Generate gallery HTML for backgrounds
.filter((b) => filterMatch(b) && queryMatch(b)) const galleryHtml = filteredBackgrounds
.map((b) => ({ ...b, kind: "background" }));
const galleryHtml = galleryItems
.map((item) => { .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 common = `class="bg-studio-thumb ${isActive ? "is-active" : ""}" type="button"`;
const thumb = const thumb =
item.key === "none" item.key === "none"
@ -864,17 +877,11 @@ function renderBackgroundStudioPanel(panel) {
.join(""); .join("");
const filterBtn = (key, icon, label) => { const filterBtn = (key, icon, label) => {
const active = filter === key; const active = currentFilter === key;
return `<button class="bg-studio-filter-btn ${active ? "is-active" : ""}" type="button" data-bg-studio-action="set-filter" data-filter="${key}" title="${label}" aria-label="${label}" aria-pressed="${active}"><i class="mdi ${icon}" aria-hidden="true"></i></button>`; return `<button class="bg-studio-filter-btn ${active ? "is-active" : ""}" type="button" data-bg-studio-action="set-filter" data-filter="${key}" title="${label}" aria-label="${label}" aria-pressed="${active}"><i class="mdi ${icon}" aria-hidden="true"></i></button>`;
}; };
const filtersHtml = [ const filtersHtml = NOTE_FILTER_OPTIONS.map((f) => filterBtn(f.key, f.icon, f.label)).join("");
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 showClear = !!(panel.dataset.query || "").trim(); const showClear = !!(panel.dataset.query || "").trim();
@ -893,13 +900,13 @@ function renderBackgroundStudioPanel(panel) {
<div class="bg-studio-swatches" role="list">${colorsRowHtml}</div> <div class="bg-studio-swatches" role="list">${colorsRowHtml}</div>
</div> </div>
<div class="bg-studio-row"> <div class="bg-studio-row">
<div class="bg-studio-row-label">Backgrounds:</div> <div class="bg-studio-row-label">Filtres:</div>
<div class="bg-studio-filters">${filtersHtml}</div> <div class="bg-studio-filters">${filtersHtml}</div>
</div> </div>
</div> </div>
<div class="bg-studio-search"> <div class="bg-studio-search">
<i class="mdi mdi-magnify" aria-hidden="true"></i> <i class="mdi mdi-magnify" aria-hidden="true"></i>
<input class="bg-studio-search-input" type="search" placeholder="Rechercher mes images… (filtrer par couleur ou background)" value="${panel.dataset.query || ""}" /> <input class="bg-studio-search-input" type="search" placeholder="Rechercher mes images… (filtrer par couleur ou filtre)" value="${panel.dataset.query || ""}" />
<button class="bg-studio-clear" type="button" data-bg-studio-action="set-query" title="Effacer" aria-label="Effacer" style="${showClear ? "" : "display:none"}"><i class="mdi mdi-close-circle-outline" aria-hidden="true"></i></button> <button class="bg-studio-clear" type="button" data-bg-studio-action="set-query" title="Effacer" aria-label="Effacer" style="${showClear ? "" : "display:none"}"><i class="mdi mdi-close-circle-outline" aria-hidden="true"></i></button>
</div> </div>
`; `;
@ -929,13 +936,18 @@ function applyNoteVisualState(element, note) {
const color = resolvedColorOption ? resolvedColorOption.key : "default"; const color = resolvedColorOption ? resolvedColorOption.key : "default";
const colorValue = getThemeColorValue(resolvedColorOption); const colorValue = getThemeColorValue(resolvedColorOption);
const foregroundColor = getReadableForegroundForBackground(colorValue); const foregroundColor = getReadableForegroundForBackground(colorValue);
const filter = normalizeFilterKey(note.filter || "none");
const normalizedBackground = normalizeBackgroundKey(note.background || ""); const normalizedBackground = normalizeBackgroundKey(note.background || "");
const background = normalizedBackground || "none"; const background = normalizedBackground || "none";
element.classList.forEach((cls) => { element.classList.forEach((cls) => {
if (cls.startsWith("note-color-")) element.classList.remove(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}`); element.classList.add(`note-color-${color}`);
if (filter !== "none") {
element.classList.add(`note-filter-${filter}`);
}
if (colorValue) { if (colorValue) {
element.style.backgroundColor = colorValue; element.style.backgroundColor = colorValue;
@ -966,6 +978,7 @@ function applyNoteVisualState(element, note) {
} }
element.dataset.color = color; element.dataset.color = color;
element.dataset.filter = filter;
} }
function extractNoteVisualStateFromTags(tags) { 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"; let background = "none";
const foundBgTag = safeTags.find((t) => typeof t === "string" && t.startsWith(NOTE_BACKGROUND_TAG_PREFIX)); const foundBgTag = safeTags.find((t) => typeof t === "string" && t.startsWith(NOTE_BACKGROUND_TAG_PREFIX));
if (foundBgTag) { if (foundBgTag) {
@ -989,7 +1011,7 @@ function extractNoteVisualStateFromTags(tags) {
} }
} }
return { color, background }; return { color, filter, background };
} }
function initBookmarkPaletteButtons() { function initBookmarkPaletteButtons() {
@ -1001,8 +1023,8 @@ function initBookmarkPaletteButtons() {
if (!cardId) return; if (!cardId) return;
const tags = Array.from(card.querySelectorAll(".link-tag a")).map((a) => (a.textContent || "").trim()); const tags = Array.from(card.querySelectorAll(".link-tag a")).map((a) => (a.textContent || "").trim());
const { color, background } = extractNoteVisualStateFromTags(tags); const { color, filter, background } = extractNoteVisualStateFromTags(tags);
applyNoteVisualState(card, { color, background }); applyNoteVisualState(card, { color, filter, background });
const actions = card.querySelector(".link-actions"); const actions = card.querySelector(".link-actions");
if (!actions) return; if (!actions) return;
@ -1042,6 +1064,7 @@ function initBookmarkPaletteButtons() {
entityId: cardId, entityId: cardId,
editUrl, editUrl,
currentColor: getElementVisualColor(card), currentColor: getElementVisualColor(card),
currentFilter: getElementVisualFilter(card),
currentBackground: getElementVisualBackground(card), currentBackground: getElementVisualBackground(card),
title: "Mes images & couleurs", title: "Mes images & couleurs",
}); });
@ -1055,29 +1078,29 @@ function initBookmarkPaletteButtons() {
}); });
} }
function generateBookmarkPaletteButtons(bookmarkId, editUrl, currentColor, currentBackground) { function generateBookmarkPaletteButtons(bookmarkId, editUrl, currentColor, currentFilter) {
return generateUnifiedPaletteMenu({ return generateUnifiedPaletteMenu({
entityId: bookmarkId, entityId: bookmarkId,
editUrl, editUrl,
currentColor, currentColor,
currentBackground, currentFilter,
mode: "entity", mode: "entity",
}); });
} }
function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentBackground, mode }) { function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentFilter, mode }) {
const color = currentColor || "default"; const color = currentColor || "default";
const background = normalizeBackgroundKey(currentBackground || "") || "none"; const filter = normalizeFilterKey(currentFilter || "") || "none";
const onColorClick = const onColorClick =
mode === "modal" mode === "modal"
? (c) => `setModalNoteColor('${c}')` ? (c) => `setModalNoteColor('${c}')`
: (c) => `setNoteColor('${entityId}', '${c}', '${editUrl}')`; : (c) => `setNoteColor('${entityId}', '${c}', '${editUrl}')`;
const onBackgroundClick = const onFilterClick =
mode === "modal" mode === "modal"
? (k) => `setModalNoteBackground('${k}')` ? (k) => `setModalNoteFilter('${k}')`
: (k) => `setNoteBackground('${entityId}', '${k}', '${editUrl}')`; : (k) => `setNoteFilter('${entityId}', '${k}', '${editUrl}')`;
const colorButtons = [ const colorButtons = [
`<button class="palette-btn palette-btn-default ${color === "default" ? "is-active" : ""}" type="button" title="Par défaut" aria-label="Par défaut" aria-pressed="${color === "default"}" onclick="${onColorClick("default")}"><i class="mdi mdi-format-color-reset" aria-hidden="true"></i></button>`, `<button class="palette-btn palette-btn-default ${color === "default" ? "is-active" : ""}" type="button" title="Par défaut" aria-label="Par défaut" aria-pressed="${color === "default"}" onclick="${onColorClick("default")}"><i class="mdi mdi-format-color-reset" aria-hidden="true"></i></button>`,
@ -1087,11 +1110,10 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentBa
}), }),
].join(""); ].join("");
const backgroundButtons = [ const filterButtons = [
`<button class="palette-btn palette-btn-bg-none ${background === "none" ? "is-active" : ""}" type="button" title="Sans image" aria-label="Sans image" aria-pressed="${background === "none"}" onclick="${onBackgroundClick("none")}"><i class="mdi mdi-image-off-outline" aria-hidden="true"></i></button>`, `<button class="palette-btn palette-btn-filter-none ${filter === "none" ? "is-active" : ""}" type="button" title="Aucun" aria-label="Aucun" aria-pressed="${filter === "none"}" onclick="${onFilterClick("none")}"><i class="mdi mdi-close-circle-outline" aria-hidden="true"></i></button>`,
...getAvailableBackgroundOptionsForMode().map((bg) => { ...NOTE_FILTER_OPTIONS.filter((opt) => opt.key !== "none").map((opt) => {
const bgUrl = getNoteBackgroundUrl(bg.key); return `<button class="palette-btn palette-btn-filter ${filter === opt.key ? "is-active" : ""}" type="button" title="${opt.label}" aria-label="${opt.label}" aria-pressed="${filter === opt.key}" onclick="${onFilterClick(opt.key)}"><i class="mdi ${opt.icon}" aria-hidden="true"></i></button>`;
return `<button class="palette-btn palette-btn-bg ${background === bg.key ? "is-active" : ""}" type="button" title="${bg.label}" aria-label="${bg.label}" aria-pressed="${background === bg.key}" onclick="${onBackgroundClick(bg.key)}" style="background-image:url('${bgUrl}')"></button>`;
}), }),
].join(""); ].join("");
@ -1102,8 +1124,8 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentBa
</div> </div>
<div class="palette-divider"></div> <div class="palette-divider"></div>
<div class="palette-section"> <div class="palette-section">
<div class="palette-section-title">Images</div> <div class="palette-section-title">Filtres</div>
<div class="palette-row palette-row-backgrounds">${backgroundButtons}</div> <div class="palette-row palette-row-filters">${filterButtons}</div>
</div> </div>
`; `;
} }
@ -1116,6 +1138,9 @@ function syncNoteFromCardElement(note, card) {
note.color = colorClass.replace("note-color-", "") || "default"; note.color = colorClass.replace("note-color-", "") || "default";
} }
const filter = card.dataset.filter;
note.filter = filter && filter !== "none" ? filter : "none";
const background = card.dataset.background; const background = card.dataset.background;
note.background = background && background !== "none" ? background : "none"; note.background = background && background !== "none" ? background : "none";
} }
@ -1470,6 +1495,7 @@ function initNoteView(linkList, container) {
entityId: modalCard ? modalCard.dataset.noteId || "" : "", entityId: modalCard ? modalCard.dataset.noteId || "" : "",
editUrl: modalCard ? modalCard.dataset.editUrl || "" : "", editUrl: modalCard ? modalCard.dataset.editUrl || "" : "",
currentColor: modalCard ? getElementVisualColor(modalCard) : "default", currentColor: modalCard ? getElementVisualColor(modalCard) : "default",
currentFilter: modalCard ? getElementVisualFilter(modalCard) : "none",
currentBackground: modalCard ? getElementVisualBackground(modalCard) : "none", currentBackground: modalCard ? getElementVisualBackground(modalCard) : "none",
title: "Mes images & couleurs", title: "Mes images & couleurs",
}); });
@ -1587,6 +1613,7 @@ function parseNoteFromLink(linkEl) {
const tags = []; const tags = [];
let color = "default"; let color = "default";
let filter = "none";
let background = "none"; let background = "none";
linkEl.querySelectorAll(".link-tag-list a").forEach((tag) => { linkEl.querySelectorAll(".link-tag-list a").forEach((tag) => {
const t = tag.textContent.trim(); const t = tag.textContent.trim();
@ -1599,6 +1626,8 @@ function parseNoteFromLink(linkEl) {
} else { } else {
tags.push(t); 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)) { } else if (t.startsWith(NOTE_BACKGROUND_TAG_PREFIX)) {
background = normalizeBackgroundKey(t.substring(NOTE_BACKGROUND_TAG_PREFIX.length)) || "none"; background = normalizeBackgroundKey(t.substring(NOTE_BACKGROUND_TAG_PREFIX.length)) || "none";
} else { } else {
@ -1616,7 +1645,7 @@ function parseNoteFromLink(linkEl) {
// User requested "availability of the tag 'shaarli-pin' as the main source" // User requested "availability of the tag 'shaarli-pin' as the main source"
const isPinned = tags.includes("shaarli-pin"); 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) { function renderNotes(container, notes, viewMode) {
@ -1726,6 +1755,7 @@ function renderNotes(container, notes, viewMode) {
entityId: note.id, entityId: note.id,
editUrl: note.editUrl, editUrl: note.editUrl,
currentColor: getElementVisualColor(card), currentColor: getElementVisualColor(card),
currentFilter: getElementVisualFilter(card),
currentBackground: getElementVisualBackground(card), currentBackground: getElementVisualBackground(card),
title: "Mes images & couleurs", title: "Mes images & couleurs",
}); });
@ -1817,7 +1847,7 @@ function generateModalPaletteButtons(note) {
entityId: note && note.id ? note.id : "", entityId: note && note.id ? note.id : "",
editUrl: note && note.editUrl ? note.editUrl : "", editUrl: note && note.editUrl ? note.editUrl : "",
currentColor: note && note.color ? note.color : "default", currentColor: note && note.color ? note.color : "default",
currentBackground: note && note.background ? note.background : "none", currentFilter: note && note.filter ? note.filter : "none",
mode: "modal", 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"); const modal = document.querySelector(".note-modal-overlay");
if (!modal || !modal.currentNote) return; if (!modal || !modal.currentNote) return;
const currentNote = modal.currentNote; const currentNote = modal.currentNote;
const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none"; const normalizedFilterKey = normalizeFilterKey(filterKey) || "none";
setNoteBackground(currentNote.id, normalizedBackgroundKey, currentNote.editUrl); setNoteFilter(currentNote.id, normalizedFilterKey, currentNote.editUrl);
currentNote.background = normalizedBackgroundKey; currentNote.filter = normalizedFilterKey;
const modalCard = modal.querySelector(".note-modal"); const modalCard = modal.querySelector(".note-modal");
if (modalCard) { if (modalCard) {
applyNoteVisualState(modalCard, currentNote); applyNoteVisualState(modalCard, currentNote);
@ -1870,7 +1900,7 @@ function generatePaletteButtons(note) {
entityId: note && note.id ? note.id : "", entityId: note && note.id ? note.id : "",
editUrl: note && note.editUrl ? note.editUrl : "", editUrl: note && note.editUrl ? note.editUrl : "",
currentColor: note && note.color ? note.color : "default", currentColor: note && note.color ? note.color : "default",
currentBackground: note && note.background ? note.background : "none", currentFilter: note && note.filter ? note.filter : "none",
mode: "entity", mode: "entity",
}); });
} }
@ -1879,17 +1909,17 @@ window.setNoteColor = function (noteId, color, editUrl) {
// 1. Visual Update (Immediate feedback) // 1. Visual Update (Immediate feedback)
const card = document.querySelector(`.note-card[data-id="${noteId}"]`); const card = document.querySelector(`.note-card[data-id="${noteId}"]`);
if (card) { if (card) {
const background = card.dataset.background || "none"; const filter = card.dataset.filter || "none";
applyNoteVisualState(card, { color, background }); applyNoteVisualState(card, { color, filter });
} }
const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`); const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`);
if (bookmarkCard) { if (bookmarkCard) {
const background = bookmarkCard.dataset.background || "none"; const filter = bookmarkCard.dataset.filter || "none";
applyNoteVisualState(bookmarkCard, { color, background }); applyNoteVisualState(bookmarkCard, { color, filter });
const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup"); const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup");
if (palettePopup) { 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) { window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none"; const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none";
@ -1976,18 +2090,16 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
if (card) { if (card) {
const colorClass = Array.from(card.classList).find((cls) => cls.startsWith("note-color-")); const colorClass = Array.from(card.classList).find((cls) => cls.startsWith("note-color-"));
const color = colorClass ? colorClass.replace("note-color-", "") : card.dataset.color || "default"; 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}"]`); const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`);
if (bookmarkCard) { if (bookmarkCard) {
const colorClass = Array.from(bookmarkCard.classList).find((cls) => cls.startsWith("note-color-")); const colorClass = Array.from(bookmarkCard.classList).find((cls) => cls.startsWith("note-color-"));
const color = colorClass ? colorClass.replace("note-color-", "") : bookmarkCard.dataset.color || "default"; const color = colorClass ? colorClass.replace("note-color-", "") : bookmarkCard.dataset.color || "default";
applyNoteVisualState(bookmarkCard, { color, background: normalizedBackgroundKey }); const filter = bookmarkCard.dataset.filter || "none";
const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup"); applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey });
if (palettePopup) {
palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedBackgroundKey);
}
} }
const modal = document.querySelector(".note-modal-overlay"); 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) { function addTagToNote(editUrl, tag) {
return fetch(editUrl) return fetch(editUrl)
.then((response) => response.text()) .then((response) => response.text())