feat: ajouter support pour les images de fond et les filtres CSS sur les notes avec des classes spécifiques, incluant des styles pour les éléments .link-outer et .note-card, ainsi que des ajustements pour la gestion des couleurs de texte et de fond dans le studio de personnalisation

This commit is contained in:
Bruno Charest 2026-04-21 13:47:30 -04:00
parent 1313f78b93
commit c1b48ad584
2 changed files with 234 additions and 205 deletions

View File

@ -1592,6 +1592,13 @@ body.view-notes .notes-masonry:empty::after {
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.link-outer.note-has-bg {
background-image: var(--note-bg-image, none) !important;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.note-modal.note-has-bg { .note-modal.note-has-bg {
background-image: var(--note-bg-image, none) !important; background-image: var(--note-bg-image, none) !important;
background-size: cover; background-size: cover;
@ -1612,7 +1619,14 @@ body.view-notes .notes-masonry:empty::after {
.note-card.note-filter-grid::after, .note-card.note-filter-grid::after,
.note-card.note-filter-noise::after, .note-card.note-filter-noise::after,
.note-card.note-filter-dots::after, .note-card.note-filter-dots::after,
.note-card.note-filter-stripes::after { .note-card.note-filter-stripes::after,
.link-outer.note-filter-glass::after,
.link-outer.note-filter-vignette::after,
.link-outer.note-filter-lined::after,
.link-outer.note-filter-grid::after,
.link-outer.note-filter-noise::after,
.link-outer.note-filter-dots::after,
.link-outer.note-filter-stripes::after {
content: ""; content: "";
position: absolute; position: absolute;
inset: 0; inset: 0;
@ -1628,11 +1642,21 @@ body.view-notes .notes-masonry:empty::after {
-webkit-backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
} }
.link-outer.note-filter-glass::after {
background: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}
/* Filter: Vignette — dark edges */ /* Filter: Vignette — dark edges */
.note-card.note-filter-vignette::after { .note-card.note-filter-vignette::after {
background: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, 0.42) 100%); background: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, 0.42) 100%);
} }
.link-outer.note-filter-vignette::after {
background: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, 0.42) 100%);
}
/* Filter: Lined — horizontal ruled lines */ /* Filter: Lined — horizontal ruled lines */
.note-card.note-filter-lined::after { .note-card.note-filter-lined::after {
background-image: repeating-linear-gradient( background-image: repeating-linear-gradient(
@ -1644,6 +1668,16 @@ body.view-notes .notes-masonry:empty::after {
); );
} }
.link-outer.note-filter-lined::after {
background-image: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 27px,
rgba(128, 128, 128, 0.18) 27px,
rgba(128, 128, 128, 0.18) 28px
);
}
/* Filter: Grid — squared grid */ /* Filter: Grid — squared grid */
.note-card.note-filter-grid::after { .note-card.note-filter-grid::after {
background-image: background-image:
@ -1651,18 +1685,34 @@ body.view-notes .notes-masonry:empty::after {
repeating-linear-gradient(to bottom, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px); repeating-linear-gradient(to bottom, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px);
} }
.link-outer.note-filter-grid::after {
background-image:
repeating-linear-gradient(to right, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px),
repeating-linear-gradient(to bottom, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px);
}
/* Filter: Noise — subtle stipple effect */ /* Filter: Noise — subtle stipple effect */
.note-card.note-filter-noise::after { .note-card.note-filter-noise::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E");
background-repeat: repeat; background-repeat: repeat;
} }
.link-outer.note-filter-noise::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E");
background-repeat: repeat;
}
/* Filter: Dots — dot pattern */ /* Filter: Dots — dot pattern */
.note-card.note-filter-dots::after { .note-card.note-filter-dots::after {
background-image: radial-gradient(circle, rgba(128, 128, 128, 0.3) 1px, transparent 1px); background-image: radial-gradient(circle, rgba(128, 128, 128, 0.3) 1px, transparent 1px);
background-size: 14px 14px; background-size: 14px 14px;
} }
.link-outer.note-filter-dots::after {
background-image: radial-gradient(circle, rgba(128, 128, 128, 0.3) 1px, transparent 1px);
background-size: 14px 14px;
}
/* Filter: Stripes — diagonal lines */ /* Filter: Stripes — diagonal lines */
.note-card.note-filter-stripes::after { .note-card.note-filter-stripes::after {
background-image: repeating-linear-gradient( background-image: repeating-linear-gradient(
@ -1674,9 +1724,55 @@ body.view-notes .notes-masonry:empty::after {
); );
} }
.link-outer.note-filter-stripes::after {
background-image: repeating-linear-gradient(
-45deg,
transparent 0px,
transparent 8px,
rgba(128, 128, 128, 0.12) 8px,
rgba(128, 128, 128, 0.12) 9px
);
}
/* Keep card inner content above the filter overlay */ /* Keep card inner content above the filter overlay */
.note-card .note-inner, .note-card .note-inner,
.note-card .note-hover-actions { .note-card .note-hover-actions {
position: relative; position: relative;
z-index: 1; z-index: 1;
}
.link-outer .link-content,
.link-outer .link-actions,
.link-outer .link-hover-actions,
.link-outer .link-meta,
.link-outer .link-select-checkbox,
.link-outer .link-visibility-badge {
position: relative;
z-index: 1;
}
/* Let Background Studio font color affect bookmark tiles too */
.link-outer[class*="note-color-"],
.link-outer[class*="note-color-"] .link-title,
.link-outer[class*="note-color-"] .link-title a,
.link-outer[class*="note-color-"] .link-url,
.link-outer[class*="note-color-"] .link-url a,
.link-outer[class*="note-color-"] .link-description,
.link-outer[class*="note-color-"] .link-meta,
.link-outer[class*="note-color-"] .link-meta a,
.link-outer[class*="note-color-"] .link-actions,
.link-outer[class*="note-color-"] .link-actions a,
.link-outer[class*="note-color-"] .link-actions button,
.view-list .link-outer[class*="note-color-"],
.view-list .link-outer[class*="note-color-"] a,
.view-list .link-outer[class*="note-color-"] button {
color: var(--note-card-fg, var(--tile-text));
}
/* Let Background Studio font color affect note/todo body text */
.note-card[class*="note-color-"] .note-title,
.note-card[class*="note-color-"] .note-body,
.note-card[class*="note-color-"] .todo-checklist-preview-item,
.note-card[class*="note-color-"] .todo-checklist-preview-text {
color: var(--note-card-fg, var(--special-view-strong));
} }

View File

@ -9,14 +9,17 @@ document.addEventListener("DOMContentLoaded", function () {
const pathTagRaw = pathMatch ? decodeURIComponent(pathMatch[1]) : ""; const pathTagRaw = pathMatch ? decodeURIComponent(pathMatch[1]) : "";
// Parse all active tags to safely detect the view // Parse all active tags to safely detect the view
const activeTags = (searchTagsRaw + " " + pathTagRaw).toLowerCase().split(/[\s,]+/).filter(t => t); const activeTags = (searchTagsRaw + " " + pathTagRaw)
.toLowerCase()
.split(/[\s,]+/)
.filter((t) => t);
// Foolproof detection using sidebar active state and DOM rendered tags // Foolproof detection using sidebar active state and DOM rendered tags
const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active, .sidebar-link[href*="searchtags=shaarli-note"].active'); const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active, .sidebar-link[href*="searchtags=shaarli-note"].active');
const hasTodoActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Mes tâches"].active, .header-nav-link[aria-label="Mes tâches"].active, .sidebar-link[href*="searchtags=shaarli-todo"].active, .sidebar-link[href*="searchtags=todo"].active'); const hasTodoActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Mes tâches"].active, .header-nav-link[aria-label="Mes tâches"].active, .sidebar-link[href*="searchtags=shaarli-todo"].active, .sidebar-link[href*="searchtags=todo"].active');
const hasArchiveActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Archive"].active, .header-nav-link[aria-label="Archive"].active, .sidebar-link[href*="searchtags=shaarli-archive"].active, .sidebar-link[href*="searchtags=shaarli-archiver"].active'); const hasArchiveActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Archive"].active, .header-nav-link[aria-label="Archive"].active, .sidebar-link[href*="searchtags=shaarli-archive"].active, .sidebar-link[href*="searchtags=shaarli-archiver"].active');
const domChipTags = Array.from(document.querySelectorAll('.search-tag-chip')).map(el => (el.textContent || "").trim().toLowerCase()); const domChipTags = Array.from(document.querySelectorAll(".search-tag-chip")).map((el) => (el.textContent || "").trim().toLowerCase());
const isArchiveTag = (tag) => tag === "shaarli-archive" || tag === "shaarli-archiver"; const isArchiveTag = (tag) => tag === "shaarli-archive" || tag === "shaarli-archiver";
const isNoteTag = (tag) => tag === "note" || tag === "shaarli-note" || tag === "#note"; const isNoteTag = (tag) => tag === "note" || tag === "shaarli-note" || tag === "#note";
@ -32,7 +35,7 @@ document.addEventListener("DOMContentLoaded", function () {
const showErrorBanner = (msg, err) => { const showErrorBanner = (msg, err) => {
const banner = document.createElement("div"); const banner = document.createElement("div");
banner.style.cssText = "background: #ffebee; color: #c62828; padding: 16px; margin: 16px; border-radius: 8px; border: 1px solid #ef9a9a; z-index: 9999; position: relative;"; banner.style.cssText = "background: #ffebee; color: #c62828; padding: 16px; margin: 16px; border-radius: 8px; border: 1px solid #ef9a9a; z-index: 9999; position: relative;";
banner.innerHTML = `<strong>Erreur Custom Views:</strong> ${msg}<br><pre style="margin-top:8px; font-size: 12px; white-space: pre-wrap; word-break: break-all;">${err ? err.stack || err : ''}</pre>`; banner.innerHTML = `<strong>Erreur Custom Views:</strong> ${msg}<br><pre style="margin-top:8px; font-size: 12px; white-space: pre-wrap; word-break: break-all;">${err ? err.stack || err : ""}</pre>`;
if (container) container.prepend(banner); if (container) container.prepend(banner);
else document.body.prepend(banner); else document.body.prepend(banner);
}; };
@ -58,18 +61,9 @@ document.addEventListener("DOMContentLoaded", function () {
if (!markdown) return ""; if (!markdown) return "";
const normalizeNewlines = String(markdown).replace(/\r\n?/g, "\n"); const normalizeNewlines = String(markdown).replace(/\r\n?/g, "\n");
const escape = (value) => const escape = (value) => String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const escapeAttr = (value) => const escapeAttr = (value) => String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
String(value)
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const sanitizeUrl = (rawUrl) => { const sanitizeUrl = (rawUrl) => {
const url = String(rawUrl || "").trim(); const url = String(rawUrl || "").trim();
@ -196,12 +190,7 @@ document.addEventListener("DOMContentLoaded", function () {
inTodo = true; inTodo = true;
} }
const checked = String(todoMatch[1]).toLowerCase() === "x"; const checked = String(todoMatch[1]).toLowerCase() === "x";
html.push( html.push(`<li class="md-todo-item${checked ? " is-checked" : ""}">` + `<span class="md-todo-box" aria-hidden="true"><i class="mdi mdi-check"></i></span>` + `<span class="md-todo-text">${renderInline(todoMatch[2])}</span>` + "</li>");
`<li class="md-todo-item${checked ? " is-checked" : ""}">`
+ `<span class="md-todo-box" aria-hidden="true"><i class="mdi mdi-check"></i></span>`
+ `<span class="md-todo-text">${renderInline(todoMatch[2])}</span>`
+ "</li>",
);
return; return;
} }
@ -490,8 +479,7 @@ function renderMarkdown(markdown) {
if (typeof window !== "undefined" && typeof window.renderMarkdown === "function") { if (typeof window !== "undefined" && typeof window.renderMarkdown === "function") {
return window.renderMarkdown(markdown); return window.renderMarkdown(markdown);
} }
return String(markdown || "") return String(markdown || "").replace(/\n/g, "<br>");
.replace(/\n/g, "<br>");
} }
function applyKeepNoteFormatting(textarea, format) { function applyKeepNoteFormatting(textarea, format) {
@ -505,80 +493,80 @@ const NOTE_COLOR_OPTIONS = [
key: "default", key: "default",
label: "Par défaut", label: "Par défaut",
light: "#f8fafc", light: "#f8fafc",
dark: "#20293A" dark: "#20293A",
}, },
{ {
key: "red", key: "red",
label: "Rouge", label: "Rouge",
light: "#f28b82", light: "#f28b82",
dark: "#9c2116" dark: "#9c2116",
}, },
{ {
key: "orange", key: "orange",
label: "Orange", label: "Orange",
light: "#fbbc04", light: "#fbbc04",
dark: "#9c7a16" dark: "#9c7a16",
}, },
{ {
key: "yellow", key: "yellow",
label: "Jaune", label: "Jaune",
light: "#fff475", light: "#fff475",
dark: "#9c9116" dark: "#9c9116",
}, },
{ {
key: "green", key: "green",
label: "Vert", label: "Vert",
light: "#ccff90", light: "#ccff90",
dark: "#5e9c16" dark: "#5e9c16",
}, },
{ {
key: "teal", key: "teal",
label: "Menthe", label: "Menthe",
light: "#a7ffeb", light: "#a7ffeb",
dark: "#169c7d" dark: "#169c7d",
}, },
{ {
key: "blue", key: "blue",
label: "Bleu clair", label: "Bleu clair",
light: "#cbf0f8", light: "#cbf0f8",
dark: "#16849c" dark: "#16849c",
}, },
{ {
key: "darkblue", key: "darkblue",
label: "Bleu", label: "Bleu",
light: "#aecbfa", light: "#aecbfa",
dark: "#16499c" dark: "#16499c",
}, },
{ {
key: "purple", key: "purple",
label: "Violet", label: "Violet",
light: "#d7aefb", light: "#d7aefb",
dark: "#5d169c" dark: "#5d169c",
}, },
{ {
key: "pink", key: "pink",
label: "Rose", label: "Rose",
light: "#fdcfe8", light: "#fdcfe8",
dark: "#9c165f" dark: "#9c165f",
}, },
{ {
key: "brown", key: "brown",
label: "Beige", label: "Beige",
light: "#e6c9a8", light: "#e6c9a8",
dark: "#9c5d16" dark: "#9c5d16",
}, },
{ {
key: "grey", key: "grey",
label: "Gris", label: "Gris",
light: "#e8eaed", light: "#e8eaed",
dark: "#4e5764" dark: "#4e5764",
}, },
{ {
key: "custom", key: "custom",
label: "Personnalisé", label: "Personnalisé",
light: "#custom", light: "#custom",
dark: "#custom" dark: "#custom",
} },
]; ];
const NOTE_FILTER_OPTIONS = [ const NOTE_FILTER_OPTIONS = [
@ -598,7 +586,7 @@ const NOTE_FONT_COLOR_OPTIONS = [
{ key: "dark", label: "Sombre", value: "#202124" }, { key: "dark", label: "Sombre", value: "#202124" },
{ key: "white", label: "Blanc", value: "#ffffff" }, { key: "white", label: "Blanc", value: "#ffffff" },
{ key: "black", label: "Noir", value: "#000000" }, { key: "black", label: "Noir", value: "#000000" },
{ key: "custom", label: "Personnalisé", value: "custom" } { key: "custom", label: "Personnalisé", value: "custom" },
]; ];
const NOTE_FONT_COLOR_TAG_PREFIX = "font-"; const NOTE_FONT_COLOR_TAG_PREFIX = "font-";
@ -703,11 +691,10 @@ function removeTagFromEntity(editUrl, tag) {
function deleteEntitySilently(deleteUrl) { function deleteEntitySilently(deleteUrl) {
if (!deleteUrl || deleteUrl === "#") return Promise.reject("Invalid delete URL"); if (!deleteUrl || deleteUrl === "#") return Promise.reject("Invalid delete URL");
return fetch(deleteUrl) return fetch(deleteUrl).then((response) => {
.then((response) => { if (!response.ok) throw new Error("Delete request failed");
if (!response.ok) throw new Error("Delete request failed"); return response;
return response; });
});
} }
let tagDisplayRemovalInitialized = false; let tagDisplayRemovalInitialized = false;
@ -818,18 +805,14 @@ function getModalCurrentEntity(modal) {
} }
function resolveThemeAssetBasePath() { function resolveThemeAssetBasePath() {
const cssLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find( const cssLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((link) => link.href && link.href.includes("/custom_views.css"));
(link) => link.href && link.href.includes("/custom_views.css"),
);
if (cssLink && cssLink.href) { if (cssLink && cssLink.href) {
const cssUrl = new URL(cssLink.href, window.location.origin); const cssUrl = new URL(cssLink.href, window.location.origin);
const cssPath = cssUrl.pathname.replace(/\/css\/custom_views\.css$/, ""); const cssPath = cssUrl.pathname.replace(/\/css\/custom_views\.css$/, "");
if (cssPath) return cssPath; if (cssPath) return cssPath;
} }
const jsScript = Array.from(document.querySelectorAll("script[src]")).find( const jsScript = Array.from(document.querySelectorAll("script[src]")).find((script) => script.src && script.src.includes("/custom_views.js"));
(script) => script.src && script.src.includes("/custom_views.js"),
);
if (jsScript && jsScript.src) { if (jsScript && jsScript.src) {
const jsUrl = new URL(jsScript.src, window.location.origin); const jsUrl = new URL(jsScript.src, window.location.origin);
const jsPath = jsUrl.pathname.replace(/\/js\/custom_views\.js$/, ""); const jsPath = jsUrl.pathname.replace(/\/js\/custom_views\.js$/, "");
@ -1052,11 +1035,7 @@ function getReadableForegroundForBackground(colorValue, mode = getCurrentThemeMo
} }
function normalizeDynamicBackgroundOptions(rawOptions) { function normalizeDynamicBackgroundOptions(rawOptions) {
const rawList = Array.isArray(rawOptions) const rawList = Array.isArray(rawOptions) ? rawOptions : rawOptions && Array.isArray(rawOptions.backgrounds) ? rawOptions.backgrounds : [];
? rawOptions
: rawOptions && Array.isArray(rawOptions.backgrounds)
? rawOptions.backgrounds
: [];
const mergedByKey = {}; const mergedByKey = {};
@ -1170,9 +1149,7 @@ function refreshBackgroundPalettes() {
const noteId = card.dataset.id || ""; const noteId = card.dataset.id || "";
if (!noteId) return; if (!noteId) return;
const editLink = card.querySelector( const editLink = card.querySelector('.note-hover-actions a[href*="/admin/shaare/"], .note-hover-actions a[href*="do=editlink"], .note-hover-actions a[title="Edit"]');
'.note-hover-actions a[href*="/admin/shaare/"], .note-hover-actions a[href*="do=editlink"], .note-hover-actions a[title="Edit"]',
);
palettePopup.innerHTML = generatePaletteButtons({ palettePopup.innerHTML = generatePaletteButtons({
id: noteId, id: noteId,
@ -1190,16 +1167,9 @@ function refreshBackgroundPalettes() {
if (!noteId) return; if (!noteId) return;
const actions = card.querySelector(".link-actions"); const actions = card.querySelector(".link-actions");
const editLink = actions const editLink = actions ? actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]') : null;
? actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]')
: null;
palettePopup.innerHTML = generateBookmarkPaletteButtons( palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editLink && editLink.href ? editLink.href : "#", getElementVisualColor(card), getElementVisualBackground(card));
noteId,
editLink && editLink.href ? editLink.href : "#",
getElementVisualColor(card),
getElementVisualBackground(card),
);
}); });
const modal = getOpenModalOverlay(); const modal = getOpenModalOverlay();
@ -1236,7 +1206,6 @@ function parseBackgroundManifestPayload(rawPayload) {
} }
} }
let isThemeModeBackgroundSyncInitialized = false; let isThemeModeBackgroundSyncInitialized = false;
function initThemeModeBackgroundSync() { function initThemeModeBackgroundSync() {
@ -1311,22 +1280,6 @@ function ensureBackgroundStudioPanel() {
panel.style.display = "none"; panel.style.display = "none";
document.body.appendChild(panel); document.body.appendChild(panel);
document.addEventListener("click", (e) => {
const p = document.getElementById("shaarli-bg-studio");
if (!p || !p.classList.contains("open")) return;
if (e.target.closest("#shaarli-bg-studio")) return;
const anchor = p.__anchorEl;
if (anchor && (e.target === anchor || e.target.closest(`#${anchor.id}`))) return;
closeBackgroundStudioPanel();
});
document.addEventListener("keydown", (e) => {
if (e.key !== "Escape") return;
const p = document.getElementById("shaarli-bg-studio");
if (!p || !p.classList.contains("open")) return;
closeBackgroundStudioPanel();
});
panel.addEventListener("click", (e) => { panel.addEventListener("click", (e) => {
const actionBtn = e.target.closest("button[data-bg-studio-action]"); const actionBtn = e.target.closest("button[data-bg-studio-action]");
if (!actionBtn) return; if (!actionBtn) return;
@ -1562,17 +1515,7 @@ function closeBackgroundStudioPanel() {
panel.__anchorEl = null; panel.__anchorEl = null;
} }
function openBackgroundStudioPanel({ function openBackgroundStudioPanel({ anchorEl, mode, entityId, editUrl, currentColor, currentFilter, currentBackground, currentFontColor, title }) {
anchorEl,
mode,
entityId,
editUrl,
currentColor,
currentFilter,
currentBackground,
currentFontColor,
title,
}) {
ensureBackgroundStudioPanel(); ensureBackgroundStudioPanel();
const panel = document.getElementById("shaarli-bg-studio"); const panel = document.getElementById("shaarli-bg-studio");
if (!panel) return; if (!panel) return;
@ -1693,12 +1636,7 @@ function renderBackgroundStudioPanel(panel) {
if (!panel) return; if (!panel) return;
const activeEl = document.activeElement; const activeEl = document.activeElement;
const wasSearchFocused = !!( const wasSearchFocused = !!(activeEl && panel.contains(activeEl) && activeEl.classList && activeEl.classList.contains("bg-studio-search-input"));
activeEl &&
panel.contains(activeEl) &&
activeEl.classList &&
activeEl.classList.contains("bg-studio-search-input")
);
const caretStart = wasSearchFocused && typeof activeEl.selectionStart === "number" ? activeEl.selectionStart : null; const caretStart = wasSearchFocused && typeof activeEl.selectionStart === "number" ? activeEl.selectionStart : null;
const caretEnd = wasSearchFocused && typeof activeEl.selectionEnd === "number" ? activeEl.selectionEnd : null; const caretEnd = wasSearchFocused && typeof activeEl.selectionEnd === "number" ? activeEl.selectionEnd : null;
@ -1733,14 +1671,15 @@ function renderBackgroundStudioPanel(panel) {
.join(""); .join("");
// Colors row with custom color picker button // Colors row with custom color picker button
const colorsRowHtml = colors const colorsRowHtml =
.filter((c) => c.key !== "default" && c.key !== "custom") colors
.map((c) => { .filter((c) => c.key !== "default" && c.key !== "custom")
const isActive = color === c.key; .map((c) => {
const hex = c.color || ""; const isActive = color === c.key;
return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-color" data-color-key="${c.key}" title="${hex}" aria-label="${c.label}" aria-pressed="${isActive}" style="background-color:${hex}"></button>`; const hex = c.color || "";
}) return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-color" data-color-key="${c.key}" title="${hex}" aria-label="${c.label}" aria-pressed="${isActive}" style="background-color:${hex}"></button>`;
.join("") + })
.join("") +
// Add custom color button (opens color picker) // Add custom color button (opens color picker)
`<button class="bg-studio-swatch bg-studio-swatch-custom ${isCustomColor ? "is-active" : ""}" type="button" data-bg-studio-action="open-color-picker" title="Couleur personnalisée" aria-label="Couleur personnalisée" aria-pressed="${isCustomColor}"> `<button class="bg-studio-swatch bg-studio-swatch-custom ${isCustomColor ? "is-active" : ""}" type="button" data-bg-studio-action="open-color-picker" title="Couleur personnalisée" aria-label="Couleur personnalisée" aria-pressed="${isCustomColor}">
<i class="mdi mdi-plus" aria-hidden="true"></i> <i class="mdi mdi-plus" aria-hidden="true"></i>
@ -1748,18 +1687,15 @@ function renderBackgroundStudioPanel(panel) {
// Font colors row // Font colors row
const isCustomFontColor = typeof currentFontColor === "string" && currentFontColor.startsWith("custom:"); const isCustomFontColor = typeof currentFontColor === "string" && currentFontColor.startsWith("custom:");
const fontColorsRowHtml = NOTE_FONT_COLOR_OPTIONS const fontColorsRowHtml =
.filter((c) => c.key !== "custom") NOTE_FONT_COLOR_OPTIONS.filter((c) => c.key !== "custom")
.map((c) => { .map((c) => {
const isActive = currentFontColor === c.key; const isActive = currentFontColor === c.key;
const value = c.value || "auto"; const value = c.value || "auto";
const style = const style = value === "auto" ? "background: linear-gradient(135deg, #f5f7fb 50%, #202124 50%);" : `background-color: ${value};`;
value === "auto" return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-font-color" data-font-color-key="${c.key}" title="${c.label}" aria-label="${c.label}" aria-pressed="${isActive}" style="${style}"></button>`;
? "background: linear-gradient(135deg, #f5f7fb 50%, #202124 50%);" })
: `background-color: ${value};`; .join("") +
return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-font-color" data-font-color-key="${c.key}" title="${c.label}" aria-label="${c.label}" aria-pressed="${isActive}" style="${style}"></button>`;
})
.join("") +
// Add custom font color button // Add custom font color button
`<button class="bg-studio-swatch bg-studio-swatch-custom ${isCustomFontColor ? "is-active" : ""}" type="button" data-bg-studio-action="open-font-color-picker" title="Couleur de texte personnalisée" aria-label="Couleur de texte personnalisée" aria-pressed="${isCustomFontColor}"> `<button class="bg-studio-swatch bg-studio-swatch-custom ${isCustomFontColor ? "is-active" : ""}" type="button" data-bg-studio-action="open-font-color-picker" title="Couleur de texte personnalisée" aria-label="Couleur de texte personnalisée" aria-pressed="${isCustomFontColor}">
<i class="mdi mdi-plus" aria-hidden="true"></i> <i class="mdi mdi-plus" aria-hidden="true"></i>
@ -1833,7 +1769,7 @@ function applyNoteVisualState(element, note) {
const customHex = isCustomColor ? rawColor.substring(7) : ""; const customHex = isCustomColor ? rawColor.substring(7) : "";
const resolvedColorOption = isCustomColor ? null : getColorOption(rawColor); const resolvedColorOption = isCustomColor ? null : getColorOption(rawColor);
const color = isCustomColor ? "custom" : (resolvedColorOption ? resolvedColorOption.key : "default"); const color = isCustomColor ? "custom" : resolvedColorOption ? resolvedColorOption.key : "default";
const colorValue = isCustomColor ? customHex : getThemeColorValue(resolvedColorOption); const colorValue = isCustomColor ? customHex : getThemeColorValue(resolvedColorOption);
const foregroundColor = getReadableForegroundForBackground(colorValue); const foregroundColor = getReadableForegroundForBackground(colorValue);
const filter = normalizeFilterKey(note.filter || "none"); const filter = normalizeFilterKey(note.filter || "none");
@ -2049,15 +1985,9 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentFi
const color = currentColor || "default"; const color = currentColor || "default";
const filter = normalizeFilterKey(currentFilter || "") || "none"; const filter = normalizeFilterKey(currentFilter || "") || "none";
const onColorClick = const onColorClick = mode === "modal" ? (c) => `setModalNoteColor('${c}')` : (c) => `setNoteColor('${entityId}', '${c}', '${editUrl}')`;
mode === "modal"
? (c) => `setModalNoteColor('${c}')`
: (c) => `setNoteColor('${entityId}', '${c}', '${editUrl}')`;
const onFilterClick = const onFilterClick = mode === "modal" ? (k) => `setModalNoteFilter('${k}')` : (k) => `setNoteFilter('${entityId}', '${k}', '${editUrl}')`;
mode === "modal"
? (k) => `setModalNoteFilter('${k}')`
: (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>`,
@ -2687,9 +2617,7 @@ async function createNewTodoViaForm({ title, items = [], visual = null }) {
formData.set("lf_url", todoUrl); formData.set("lf_url", todoUrl);
formData.set("lf_title", title || ""); formData.set("lf_title", title || "");
const normalizedItems = (Array.isArray(items) ? items : []) const normalizedItems = (Array.isArray(items) ? items : []).map((it) => ({ checked: !!(it && it.checked), text: String((it && it.text) || "").trim() })).filter((it) => it.text);
.map((it) => ({ checked: !!(it && it.checked), text: String((it && it.text) || "").trim() }))
.filter((it) => it.text);
const markdown = normalizedItems.map((it) => `- [${it.checked ? "x" : " "}] ${it.text}`).join("\n"); const markdown = normalizedItems.map((it) => `- [${it.checked ? "x" : " "}] ${it.text}`).join("\n");
formData.set("lf_description", markdown); formData.set("lf_description", markdown);
@ -3016,20 +2944,13 @@ function buildTodoPreviewHtml(items) {
} }
function escapeHtml(str) { function escapeHtml(str) {
return String(str) return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#039;");
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#039;");
} }
function parseTodoMarkdown(markdown) { function parseTodoMarkdown(markdown) {
const raw = String(markdown || ""); const raw = String(markdown || "");
// Minimal normalization for parsing lines // Minimal normalization for parsing lines
const normalized = raw const normalized = raw.replace(/<br\s*\/?\s*>/gi, "\n").replace(/<\/(p|div|li|h\d)\s*>/gi, "\n");
.replace(/<br\s*\/?\s*>/gi, "\n")
.replace(/<\/(p|div|li|h\d)\s*>/gi, "\n");
let text = normalized; let text = normalized;
try { try {
@ -3053,9 +2974,7 @@ function parseTodoMarkdown(markdown) {
const listItemEls = Array.from(tmp.querySelectorAll("li")); const listItemEls = Array.from(tmp.querySelectorAll("li"));
if (listItemEls.length > 0) { if (listItemEls.length > 0) {
const items = listItemEls const items = listItemEls.map((li) => ({ checked: false, text: (li.textContent || "").trim() })).filter((it) => it.text);
.map((li) => ({ checked: false, text: (li.textContent || "").trim() }))
.filter((it) => it.text);
if (items.length > 0) { if (items.length > 0) {
return { headerLines: [], footerLines: [], items }; return { headerLines: [], footerLines: [], items };
} }
@ -3066,12 +2985,7 @@ function parseTodoMarkdown(markdown) {
} }
const lines = text.split(/\r?\n/); const lines = text.split(/\r?\n/);
const taskRegexes = [ const taskRegexes = [/^\s*[-*+]\s*\[([ xX])\]\s*(.*)$/, /^\s*\[([ xX])\]\s*(.*)$/, /^\s*☐\s*(.*)$/, /^\s*☑\s*(.*)$/];
/^\s*[-*+]\s*\[([ xX])\]\s*(.*)$/,
/^\s*\[([ xX])\]\s*(.*)$/,
/^\s*☐\s*(.*)$/,
/^\s*☑\s*(.*)$/,
];
let firstTaskIndex = -1; let firstTaskIndex = -1;
let lastTaskIndex = -1; let lastTaskIndex = -1;
@ -3857,9 +3771,9 @@ function initNoteView(linkList, container) {
// Check for hash parameter to auto-open a specific note // Check for hash parameter to auto-open a specific note
const hash = window.location.hash; const hash = window.location.hash;
if (hash && hash.startsWith('#open-note-')) { if (hash && hash.startsWith("#open-note-")) {
const noteId = hash.replace('#open-note-', ''); const noteId = hash.replace("#open-note-", "");
const targetNote = notes.find(n => String(n.id) === String(noteId)); const targetNote = notes.find((n) => String(n.id) === String(noteId));
if (targetNote) { if (targetNote) {
// Clear the hash to avoid reopening on refresh // Clear the hash to avoid reopening on refresh
history.replaceState(null, null, window.location.pathname + window.location.search); history.replaceState(null, null, window.location.pathname + window.location.search);
@ -4003,9 +3917,7 @@ function initNoteView(linkList, container) {
togglePinTag(noteId, editUrl, modalPinBtn); togglePinTag(noteId, editUrl, modalPinBtn);
const isPinned = modalPinBtn.classList.contains("active"); const isPinned = modalPinBtn.classList.contains("active");
let tags = (modalCard.dataset.tags || "") let tags = (modalCard.dataset.tags || "").split("||").filter((t) => t);
.split("||")
.filter((t) => t);
if (isPinned) { if (isPinned) {
if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin"); if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin");
@ -4238,9 +4150,7 @@ function initArchiveView(linkList, container) {
togglePinTag(noteId, editUrl, modalPinBtn); togglePinTag(noteId, editUrl, modalPinBtn);
const isPinned = modalPinBtn.classList.contains("active"); const isPinned = modalPinBtn.classList.contains("active");
let tags = (modalCard.dataset.tags || "") let tags = (modalCard.dataset.tags || "").split("||").filter((t) => t);
.split("||")
.filter((t) => t);
if (isPinned) { if (isPinned) {
if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin"); if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin");
@ -4449,7 +4359,7 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
body.innerHTML = renderMarkdown(note._noteMarkdown); body.innerHTML = renderMarkdown(note._noteMarkdown);
} else { } else {
const textToRender = note.descText || ""; const textToRender = note.descText || "";
if (textToRender.trim().startsWith('<div class="markdown">') || textToRender.trim().startsWith('<')) { if (textToRender.trim().startsWith('<div class="markdown">') || textToRender.trim().startsWith("<")) {
body.innerHTML = textToRender; body.innerHTML = textToRender;
} else { } else {
body.innerHTML = renderMarkdown(textToRender); body.innerHTML = renderMarkdown(textToRender);
@ -4775,10 +4685,7 @@ async function saveOpenNoteEditorModal(modal) {
return; return;
} }
const changed = const changed = state.hasChanges || title !== (state.lastSavedTitle || "") || markdown !== (state.lastSavedMarkdown || "");
state.hasChanges ||
title !== (state.lastSavedTitle || "") ||
markdown !== (state.lastSavedMarkdown || "");
if (!changed || state.isSaving) return; if (!changed || state.isSaving) return;
@ -5004,14 +4911,18 @@ window.setNoteFilter = function (noteId, filterKey, 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, filter: normalizedFilterKey }); const background = getElementVisualBackground(card);
const fontColor = getElementVisualFontColor(card);
applyNoteVisualState(card, { color, filter: normalizedFilterKey, background, fontColor });
} }
const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`); 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, filter: normalizedFilterKey }); const background = getElementVisualBackground(bookmarkCard);
const fontColor = getElementVisualFontColor(bookmarkCard);
applyNoteVisualState(bookmarkCard, { color, filter: normalizedFilterKey, background, fontColor });
const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup"); const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup");
if (palettePopup) { if (palettePopup) {
palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedFilterKey); palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedFilterKey);
@ -5090,7 +5001,8 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
const colorClass = Array.from(card.classList).find((cls) => cls.startsWith("note-color-")); const 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";
const filter = card.dataset.filter || "none"; const filter = card.dataset.filter || "none";
applyNoteVisualState(card, { color, filter, background: normalizedBackgroundKey }); const fontColor = getElementVisualFontColor(card);
applyNoteVisualState(card, { color, filter, background: normalizedBackgroundKey, fontColor });
} }
const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`); const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`);
@ -5098,7 +5010,8 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
const colorClass = Array.from(bookmarkCard.classList).find((cls) => cls.startsWith("note-color-")); const 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";
const filter = bookmarkCard.dataset.filter || "none"; const filter = bookmarkCard.dataset.filter || "none";
applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey }); const fontColor = getElementVisualFontColor(bookmarkCard);
applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey, fontColor });
} }
const modal = getOpenModalOverlay(); const modal = getOpenModalOverlay();
@ -5611,17 +5524,16 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
let prevFontColorKey = "auto"; let prevFontColorKey = "auto";
if ((mode || "entity") === "draft") { if ((mode || "entity") === "draft") {
const bgPanel = document.getElementById("shaarli-bg-studio"); const bgPanel = document.getElementById("shaarli-bg-studio");
prevFontColorKey = bgPanel ? (bgPanel.dataset.fontColor || "auto") : "auto"; prevFontColorKey = bgPanel ? bgPanel.dataset.fontColor || "auto" : "auto";
} else } else if ((mode || "entity") === "modal") {
if ((mode || "entity") === "modal") { const modal = getOpenModalOverlay();
const modal = getOpenModalOverlay(); const modalCard = modal ? modal.querySelector(".note-modal") : null;
const modalCard = modal ? modal.querySelector(".note-modal") : null; prevFontColorKey = modalCard && modalCard.dataset.fontColor ? modalCard.dataset.fontColor : "auto";
prevFontColorKey = (modalCard && modalCard.dataset.fontColor) ? modalCard.dataset.fontColor : "auto"; } else {
} else { const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`);
const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`); const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`);
const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`); prevFontColorKey = noteCard && noteCard.dataset.fontColor ? noteCard.dataset.fontColor : bookmarkCard && bookmarkCard.dataset.fontColor ? bookmarkCard.dataset.fontColor : "auto";
prevFontColorKey = (noteCard && noteCard.dataset.fontColor) ? noteCard.dataset.fontColor : ((bookmarkCard && bookmarkCard.dataset.fontColor) ? bookmarkCard.dataset.fontColor : "auto"); }
}
panel.dataset.prevFontColorKey = prevFontColorKey; panel.dataset.prevFontColorKey = prevFontColorKey;
} else { } else {
panel.dataset.prevFontColorKey = ""; panel.dataset.prevFontColorKey = "";
@ -5634,25 +5546,24 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
if (bgPanel) { if (bgPanel) {
prevColorKey = bgPanel.dataset.color || "default"; prevColorKey = bgPanel.dataset.color || "default";
} }
} else } else if ((mode || "entity") === "modal") {
if ((mode || "entity") === "modal") { const modal = getOpenModalOverlay();
const modal = getOpenModalOverlay(); const modalCard = modal ? modal.querySelector(".note-modal") : null;
const modalCard = modal ? modal.querySelector(".note-modal") : null; if (modalCard) {
if (modalCard) { const isCustom = modalCard.dataset.color === "custom";
const isCustom = modalCard.dataset.color === "custom"; const cc = modalCard.dataset.customColor || "";
const cc = modalCard.dataset.customColor || ""; prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(modalCard);
prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(modalCard);
}
} else {
const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`);
const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`);
const el = noteCard || bookmarkCard;
if (el) {
const isCustom = el.dataset.color === "custom";
const cc = el.dataset.customColor || "";
prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(el);
}
} }
} else {
const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`);
const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`);
const el = noteCard || bookmarkCard;
if (el) {
const isCustom = el.dataset.color === "custom";
const cc = el.dataset.customColor || "";
prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(el);
}
}
panel.dataset.prevColorKey = prevColorKey; panel.dataset.prevColorKey = prevColorKey;
} else { } else {
panel.dataset.prevColorKey = ""; panel.dataset.prevColorKey = "";
@ -5706,14 +5617,36 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
const m = v - c; const m = v - c;
let r, g, b; let r, g, b;
if (h < 60) { r = c; g = x; b = 0; } if (h < 60) {
else if (h < 120) { r = x; g = c; b = 0; } r = c;
else if (h < 180) { r = 0; g = c; b = x; } g = x;
else if (h < 240) { r = 0; g = x; b = c; } b = 0;
else if (h < 300) { r = x; g = 0; b = c; } } else if (h < 120) {
else { r = c; g = 0; b = x; } r = x;
g = c;
b = 0;
} else if (h < 180) {
r = 0;
g = c;
b = x;
} else if (h < 240) {
r = 0;
g = x;
b = c;
} else if (h < 300) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
const toHex = (n) => Math.round((n + m) * 255).toString(16).padStart(2, "0"); const toHex = (n) =>
Math.round((n + m) * 255)
.toString(16)
.padStart(2, "0");
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase(); return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
} }
@ -5731,7 +5664,7 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
let h = 0; let h = 0;
if (d !== 0) { if (d !== 0) {
if (max === r) h = ((g - b) / d + 6) % 6 * 60; if (max === r) h = (((g - b) / d + 6) % 6) * 60;
else if (max === g) h = ((b - r) / d + 2) * 60; else if (max === g) h = ((b - r) / d + 2) * 60;
else h = ((r - g) / d + 4) * 60; else h = ((r - g) / d + 4) * 60;
} }