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:
parent
97f3bbca45
commit
7bd618fe38
@ -1558,3 +1558,176 @@ body.view-notes .content-container {
|
||||
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;
|
||||
}
|
||||
@ -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 `<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 = [
|
||||
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) {
|
||||
<div class="bg-studio-swatches" role="list">${colorsRowHtml}</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="bg-studio-search">
|
||||
<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>
|
||||
</div>
|
||||
`;
|
||||
@ -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 = [
|
||||
`<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("");
|
||||
|
||||
const backgroundButtons = [
|
||||
`<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>`,
|
||||
...getAvailableBackgroundOptionsForMode().map((bg) => {
|
||||
const bgUrl = getNoteBackgroundUrl(bg.key);
|
||||
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>`;
|
||||
const filterButtons = [
|
||||
`<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>`,
|
||||
...NOTE_FILTER_OPTIONS.filter((opt) => opt.key !== "none").map((opt) => {
|
||||
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>`;
|
||||
}),
|
||||
].join("");
|
||||
|
||||
@ -1102,8 +1124,8 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentBa
|
||||
</div>
|
||||
<div class="palette-divider"></div>
|
||||
<div class="palette-section">
|
||||
<div class="palette-section-title">Images</div>
|
||||
<div class="palette-row palette-row-backgrounds">${backgroundButtons}</div>
|
||||
<div class="palette-section-title">Filtres</div>
|
||||
<div class="palette-row palette-row-filters">${filterButtons}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -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())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user