Introduce a Node build system for minified CSS/JS with content hashes, add PWA support, i18n helpers, accessibility improvements, and more translatable strings. Move theme configuration from bookmarks to localStorage, add Playwright tests, and set up CI workflows.
This commit is contained in:
+185
-265
@@ -15,9 +15,12 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
.filter((t) => t);
|
||||
|
||||
// Foolproof detection using sidebar active state and DOM rendered tags
|
||||
const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active, .sidebar-link[href*="searchtags=shaarli-note"].active');
|
||||
const hasTodoActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Mes tâches"].active, .header-nav-link[aria-label="Mes tâches"].active, .sidebar-link[href*="searchtags=shaarli-todo"].active, .sidebar-link[href*="searchtags=todo"].active');
|
||||
const hasArchiveActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Archive"].active, .header-nav-link[aria-label="Archive"].active, .sidebar-link[href*="searchtags=shaarli-archive"].active, .sidebar-link[href*="searchtags=shaarli-archiver"].active');
|
||||
const 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') ||
|
||||
!!document.querySelector('.sidebar-link[aria-label="' + window.ShaarliProI18n.t('Notes') + '"].active, .header-nav-link[aria-label="' + window.ShaarliProI18n.t('Notes') + '"].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') ||
|
||||
!!document.querySelector('.sidebar-link[aria-label="' + window.ShaarliProI18n.t('My tasks') + '"].active, .header-nav-link[aria-label="' + window.ShaarliProI18n.t('My tasks') + '"].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') ||
|
||||
!!document.querySelector('.sidebar-link[aria-label="' + window.ShaarliProI18n.t('Archive') + '"].active, .header-nav-link[aria-label="' + window.ShaarliProI18n.t('Archive') + '"].active');
|
||||
|
||||
const domChipTags = Array.from(document.querySelectorAll(".search-tag-chip")).map((el) => (el.textContent || "").trim().toLowerCase());
|
||||
|
||||
@@ -416,7 +419,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
if (isTodoView) {
|
||||
try {
|
||||
console.log("[custom_views] Initializing Todo view");
|
||||
initTodoView(linkList, container);
|
||||
} catch (err) {
|
||||
console.error("[custom_views] Erreur lors de l'initialisation de la vue Todo:", err);
|
||||
@@ -424,7 +426,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
}
|
||||
} else if (isNoteView) {
|
||||
try {
|
||||
console.log("[custom_views] Initializing Note view");
|
||||
// Pour la vue notes, parser les notes AVANT de supprimer les tags techniques
|
||||
// afin que les propriétés visuelles (couleur, fond, etc.) soient correctement extraites
|
||||
initNoteView(linkList, container);
|
||||
@@ -438,7 +439,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
}
|
||||
} else if (isArchiveView) {
|
||||
try {
|
||||
console.log("[custom_views] Initializing Archive view");
|
||||
// Vue Archive - similaire à Notes mais pour les notes archivées
|
||||
initArchiveView(linkList, container);
|
||||
// Puis supprimer les tags techniques de l'affichage
|
||||
@@ -489,104 +489,39 @@ function applyKeepNoteFormatting(textarea, format) {
|
||||
}
|
||||
|
||||
const NOTE_COLOR_OPTIONS = [
|
||||
{
|
||||
key: "default",
|
||||
label: "Par défaut",
|
||||
light: "#f8fafc",
|
||||
dark: "#20293A",
|
||||
},
|
||||
{
|
||||
key: "red",
|
||||
label: "Rouge",
|
||||
light: "#f28b82",
|
||||
dark: "#9c2116",
|
||||
},
|
||||
{
|
||||
key: "orange",
|
||||
label: "Orange",
|
||||
light: "#fbbc04",
|
||||
dark: "#9c7a16",
|
||||
},
|
||||
{
|
||||
key: "yellow",
|
||||
label: "Jaune",
|
||||
light: "#fff475",
|
||||
dark: "#9c9116",
|
||||
},
|
||||
{
|
||||
key: "green",
|
||||
label: "Vert",
|
||||
light: "#ccff90",
|
||||
dark: "#5e9c16",
|
||||
},
|
||||
{
|
||||
key: "teal",
|
||||
label: "Menthe",
|
||||
light: "#a7ffeb",
|
||||
dark: "#169c7d",
|
||||
},
|
||||
{
|
||||
key: "blue",
|
||||
label: "Bleu clair",
|
||||
light: "#cbf0f8",
|
||||
dark: "#16849c",
|
||||
},
|
||||
{
|
||||
key: "darkblue",
|
||||
label: "Bleu",
|
||||
light: "#aecbfa",
|
||||
dark: "#16499c",
|
||||
},
|
||||
{
|
||||
key: "purple",
|
||||
label: "Violet",
|
||||
light: "#d7aefb",
|
||||
dark: "#5d169c",
|
||||
},
|
||||
{
|
||||
key: "pink",
|
||||
label: "Rose",
|
||||
light: "#fdcfe8",
|
||||
dark: "#9c165f",
|
||||
},
|
||||
{
|
||||
key: "brown",
|
||||
label: "Beige",
|
||||
light: "#e6c9a8",
|
||||
dark: "#9c5d16",
|
||||
},
|
||||
{
|
||||
key: "grey",
|
||||
label: "Gris",
|
||||
light: "#e8eaed",
|
||||
dark: "#4e5764",
|
||||
},
|
||||
{
|
||||
key: "custom",
|
||||
label: "Personnalisé",
|
||||
light: "#custom",
|
||||
dark: "#custom",
|
||||
},
|
||||
{ key: "default", label: window.ShaarliProI18n.t("Default"), light: "#f8fafc", dark: "#20293A" },
|
||||
{ key: "red", label: window.ShaarliProI18n.t("Red"), light: "#f28b82", dark: "#9c2116" },
|
||||
{ key: "orange", label: window.ShaarliProI18n.t("Orange"), light: "#fbbc04", dark: "#9c7a16" },
|
||||
{ key: "yellow", label: window.ShaarliProI18n.t("Yellow"), light: "#fff475", dark: "#9c9116" },
|
||||
{ key: "green", label: window.ShaarliProI18n.t("Green"), light: "#ccff90", dark: "#5e9c16" },
|
||||
{ key: "teal", label: window.ShaarliProI18n.t("Mint"), light: "#a7ffeb", dark: "#169c7d" },
|
||||
{ key: "blue", label: window.ShaarliProI18n.t("Light blue"), light: "#cbf0f8", dark: "#16849c" },
|
||||
{ key: "darkblue", label: window.ShaarliProI18n.t("Blue"), light: "#aecbfa", dark: "#16499c" },
|
||||
{ key: "purple", label: window.ShaarliProI18n.t("Purple"), light: "#d7aefb", dark: "#5d169c" },
|
||||
{ key: "pink", label: window.ShaarliProI18n.t("Pink"), light: "#fdcfe8", dark: "#9c165f" },
|
||||
{ key: "brown", label: window.ShaarliProI18n.t("Beige"), light: "#e6c9a8", dark: "#9c5d16" },
|
||||
{ key: "grey", label: window.ShaarliProI18n.t("Gray"), light: "#e8eaed", dark: "#4e5764" },
|
||||
{ key: "custom", label: window.ShaarliProI18n.t("Custom"), light: "#custom", dark: "#custom" },
|
||||
];
|
||||
|
||||
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" },
|
||||
{ key: "none", label: window.ShaarliProI18n.t("None"), icon: "mdi-close-circle-outline" },
|
||||
{ key: "glass", label: window.ShaarliProI18n.t("Glass"), icon: "mdi-blur" },
|
||||
{ key: "vignette", label: window.ShaarliProI18n.t("Vignette"), icon: "mdi-vignette" },
|
||||
{ key: "lined", label: window.ShaarliProI18n.t("Lined"), icon: "mdi-format-align-justify" },
|
||||
{ key: "grid", label: window.ShaarliProI18n.t("Grid"), icon: "mdi-grid" },
|
||||
{ key: "noise", label: window.ShaarliProI18n.t("Noise"), icon: "mdi-grain" },
|
||||
{ key: "dots", label: window.ShaarliProI18n.t("Dots"), icon: "mdi-dots-grid" },
|
||||
{ key: "stripes", label: window.ShaarliProI18n.t("Stripes"), icon: "mdi-slash-forward" },
|
||||
];
|
||||
|
||||
const NOTE_FONT_COLOR_OPTIONS = [
|
||||
{ key: "auto", label: "Auto", value: "auto" },
|
||||
{ key: "light", label: "Clair", value: "#f5f7fb" },
|
||||
{ key: "dark", label: "Sombre", value: "#202124" },
|
||||
{ key: "white", label: "Blanc", value: "#ffffff" },
|
||||
{ key: "black", label: "Noir", value: "#000000" },
|
||||
{ key: "custom", label: "Personnalisé", value: "custom" },
|
||||
{ key: "auto", label: window.ShaarliProI18n.t("Auto"), value: "auto" },
|
||||
{ key: "light", label: window.ShaarliProI18n.t("Light"), value: "#f5f7fb" },
|
||||
{ key: "dark", label: window.ShaarliProI18n.t("Dark"), value: "#202124" },
|
||||
{ key: "white", label: window.ShaarliProI18n.t("White"), value: "#ffffff" },
|
||||
{ key: "black", label: window.ShaarliProI18n.t("Black"), value: "#000000" },
|
||||
{ key: "custom", label: window.ShaarliProI18n.t("Custom"), value: "custom" },
|
||||
];
|
||||
|
||||
const NOTE_FONT_COLOR_TAG_PREFIX = "font-";
|
||||
@@ -641,7 +576,7 @@ function createTagPill({ tag, onRemoveClass = "tag-remove-btn", tagClass = "tag-
|
||||
btn.className = onRemoveClass;
|
||||
btn.dataset.tag = tag;
|
||||
btn.setAttribute("aria-label", `Supprimer le tag ${tag}`);
|
||||
btn.title = "Supprimer";
|
||||
btn.title = window.ShaarliProI18n.t("Delete");
|
||||
btn.innerHTML = "×";
|
||||
wrapper.appendChild(btn);
|
||||
|
||||
@@ -788,7 +723,7 @@ function initTagDisplayAndRemoval() {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error removing tag:", err);
|
||||
alert("Erreur lors de la suppression du tag.");
|
||||
alert(window.ShaarliProI18n.t("Error removing tag."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -835,7 +770,6 @@ let backgroundOptionsLoadPromise = null;
|
||||
|
||||
window.__shaarliCustomViewsVersion = "1.0.5-inline-bg-manifest";
|
||||
if (typeof console !== "undefined" && console && typeof console.info === "function") {
|
||||
console.info("[custom_views] loaded", window.__shaarliCustomViewsVersion);
|
||||
}
|
||||
|
||||
function organizePinnedBookmarks() {
|
||||
@@ -912,19 +846,19 @@ function initPinnedBookmarksSeparation() {
|
||||
document.addEventListener("DOMContentLoaded", initPinnedBookmarksSeparation);
|
||||
|
||||
const NOTE_BACKGROUND_MANIFEST_INLINE = [
|
||||
{ key: "cafe", label: "Café", files: { light: "bg-light-cafe.jpg", dark: "bg-dark-cafe.jpg" } },
|
||||
{ key: "cafe", label: window.ShaarliProI18n.t("Coffee"), files: { light: "bg-light-cafe.jpg", dark: "bg-dark-cafe.jpg" } },
|
||||
{ key: "codes", label: "Codes", files: { light: "bg-light-codes.jpg", dark: "bg-dark-codes.jpg" } },
|
||||
{ key: "dune", label: "Dune", files: { light: "bg-light-dune.jpg", dark: "bg-dark-dune.jpg" } },
|
||||
{ key: "feuille-ligne", label: "Feuille Ligne", files: { light: "bg-light-feuilleLigne.jpg", dark: "bg-dark-feuilleLigne.jpg" } },
|
||||
{ key: "feuille-quadrillage", label: "Feuille Quadrillage", files: { light: "bg-light-feuilleQuadrille.jpg", dark: "bg-dark-feuilleQuadrille.jpg" } },
|
||||
{ key: "fleurs", label: "Fleurs", files: { light: "bg-light-fleurs.jpg", dark: "bg-dark-fleurs.jpg" } },
|
||||
{ key: "foret", label: "Forêt", files: { light: "bg-light-foret.jpg", dark: "bg-dark-foret.jpg" } },
|
||||
{ key: "foret", label: window.ShaarliProI18n.t("Forest"), files: { light: "bg-light-foret.jpg", dark: "bg-dark-foret.jpg" } },
|
||||
{ key: "grid", label: "Grid", files: { light: "bg-light-grid.jpg", dark: "bg-dark-grid.jpg" } },
|
||||
{ key: "journal", label: "Journal", files: { light: "bg-light-journal.jpg", dark: "bg-dark-journal.jpg" } },
|
||||
{ key: "lecture", label: "Lecture", files: { light: "bg-light-lecture.jpg", dark: "bg-dark-lecture.jpg" } },
|
||||
{ key: "legumes", label: "Légumes", files: { light: "bg-light-legumes.jpg", dark: "bg-dark-legumes.jpg" } },
|
||||
{ key: "legumes", label: window.ShaarliProI18n.t("Vegetables"), files: { light: "bg-light-legumes.jpg", dark: "bg-dark-legumes.jpg" } },
|
||||
{ key: "montagnes", label: "Montagnes", files: { light: "bg-light-montagnes.jpg", dark: "bg-dark-montagnes.jpg" } },
|
||||
{ key: "ocean", label: "Océan", files: { light: "bg-light-ocean.jpg", dark: "bg-dark-ocean.jpg" } },
|
||||
{ key: "ocean", label: window.ShaarliProI18n.t("Ocean"), files: { light: "bg-light-ocean.jpg", dark: "bg-dark-ocean.jpg" } },
|
||||
{ key: "radio", label: "Radio", files: { light: "bg-light-radio.jpg", dark: "bg-dark-radio.jpg" } },
|
||||
{ key: "sports", label: "Sports", files: { light: "bg-light-sports.jpg", dark: "bg-dark-sports.jpg" } },
|
||||
{ key: "vague1", label: "Vague 1", files: { light: "bg-light-vague1.jpg", dark: "bg-dark-vague1.jpg" } },
|
||||
@@ -1167,7 +1101,7 @@ function refreshBackgroundPalettes() {
|
||||
if (!noteId) return;
|
||||
|
||||
const actions = card.querySelector(".link-actions");
|
||||
const editLink = actions ? actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]') : null;
|
||||
const editLink = actions ? actions.querySelector('a[title=window.ShaarliProI18n.t("Edit")], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]') : null;
|
||||
|
||||
palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editLink && editLink.href ? editLink.href : "#", getElementVisualColor(card), getElementVisualBackground(card));
|
||||
});
|
||||
@@ -1528,7 +1462,7 @@ function openBackgroundStudioPanel({ anchorEl, mode, entityId, editUrl, currentC
|
||||
panel.dataset.background = normalizeBackgroundKey(currentBackground || "") || "none";
|
||||
panel.dataset.fontColor = currentFontColor || panel.dataset.fontColor || "auto";
|
||||
panel.dataset.query = panel.dataset.query || "";
|
||||
panel.dataset.title = title || "Mes images & couleurs";
|
||||
panel.dataset.title = title || window.ShaarliProI18n.t("My images & colors");
|
||||
panel.__anchorEl = anchorEl || null;
|
||||
|
||||
renderBackgroundStudioPanel(panel);
|
||||
@@ -1616,7 +1550,7 @@ function getBackgroundStudioItems() {
|
||||
type: "background",
|
||||
category: "solid",
|
||||
key: "none",
|
||||
label: "Couleur unie",
|
||||
label: window.ShaarliProI18n.t("Solid color"),
|
||||
keywords: ["none", "sans image", "solid", "uni"],
|
||||
},
|
||||
...getAvailableBackgroundOptionsForMode().map((bg) => ({
|
||||
@@ -1640,7 +1574,7 @@ function renderBackgroundStudioPanel(panel) {
|
||||
const caretStart = wasSearchFocused && typeof activeEl.selectionStart === "number" ? activeEl.selectionStart : null;
|
||||
const caretEnd = wasSearchFocused && typeof activeEl.selectionEnd === "number" ? activeEl.selectionEnd : null;
|
||||
|
||||
const title = panel.dataset.title || "Mes images & couleurs";
|
||||
const title = panel.dataset.title || window.ShaarliProI18n.t("My images & colors");
|
||||
const color = panel.dataset.color || "default";
|
||||
const isCustomColor = typeof color === "string" && color.startsWith("custom:");
|
||||
const currentFilter = panel.dataset.filter || "none";
|
||||
@@ -1681,7 +1615,7 @@ function renderBackgroundStudioPanel(panel) {
|
||||
})
|
||||
.join("") +
|
||||
// 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="${window.ShaarliProI18n.t("Custom color")}" aria-label="${window.ShaarliProI18n.t("Custom color")}" aria-pressed="${isCustomColor}">
|
||||
<i class="mdi mdi-plus" aria-hidden="true"></i>
|
||||
</button>`;
|
||||
|
||||
@@ -1697,7 +1631,7 @@ function renderBackgroundStudioPanel(panel) {
|
||||
})
|
||||
.join("") +
|
||||
// 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="${window.ShaarliProI18n.t("Custom font color")}" aria-label="${window.ShaarliProI18n.t("Custom font color")}" aria-pressed="${isCustomFontColor}">
|
||||
<i class="mdi mdi-plus" aria-hidden="true"></i>
|
||||
</button>`;
|
||||
|
||||
@@ -1713,21 +1647,21 @@ function renderBackgroundStudioPanel(panel) {
|
||||
panel.innerHTML = `
|
||||
<div class="bg-studio-header">
|
||||
<div class="bg-studio-title">${title}</div>
|
||||
<button class="bg-studio-close" type="button" data-bg-studio-action="close" title="Fermer" aria-label="Fermer"><i class="mdi mdi-close" aria-hidden="true"></i></button>
|
||||
<button class="bg-studio-close" type="button" data-bg-studio-action="close" title=window.ShaarliProI18n.t("Close") aria-label=window.ShaarliProI18n.t("Close")><i class="mdi mdi-close" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="bg-studio-gallery" role="list">${galleryHtml}</div>
|
||||
<div class="bg-studio-rows">
|
||||
<div class="bg-studio-row">
|
||||
<div class="bg-studio-row-heading">
|
||||
<div class="bg-studio-row-label">Colors:</div>
|
||||
<button type="button" class="bg-studio-reset" data-bg-studio-action="set-defaults" title="Réinitialiser" aria-label="Réinitialiser"><i class="mdi mdi-refresh" aria-hidden="true"></i></button>
|
||||
<button type="button" class="bg-studio-reset" data-bg-studio-action="set-defaults" title=window.ShaarliProI18n.t("Reset") aria-label=window.ShaarliProI18n.t("Reset")><i class="mdi mdi-refresh" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="bg-studio-swatches" role="list">${colorsRowHtml}</div>
|
||||
</div>
|
||||
<div class="bg-studio-row">
|
||||
<div class="bg-studio-row-heading">
|
||||
<div class="bg-studio-row-label">Font:</div>
|
||||
<button type="button" class="bg-studio-reset" data-bg-studio-action="reset-font-color" title="Réinitialiser" aria-label="Réinitialiser"><i class="mdi mdi-refresh" aria-hidden="true"></i></button>
|
||||
<button type="button" class="bg-studio-reset" data-bg-studio-action="reset-font-color" title=window.ShaarliProI18n.t("Reset") aria-label=window.ShaarliProI18n.t("Reset")><i class="mdi mdi-refresh" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="bg-studio-swatches bg-studio-swatches-font" role="list">${fontColorsRowHtml}</div>
|
||||
</div>
|
||||
@@ -1738,8 +1672,8 @@ function renderBackgroundStudioPanel(panel) {
|
||||
</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 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>
|
||||
<input class="bg-studio-search-input" type="search" placeholder=window.ShaarliProI18n.t("Search my images… (filter by color or filter)") value="${panel.dataset.query || ""}" />
|
||||
<button class="bg-studio-clear" type="button" data-bg-studio-action="set-query" title=window.ShaarliProI18n.t("Clear search") aria-label=window.ShaarliProI18n.t("Clear search") style="${showClear ? "" : "display:none"}"><i class="mdi mdi-close-circle-outline" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1922,7 +1856,7 @@ function initBookmarkPaletteButtons() {
|
||||
if (!actions) return;
|
||||
if (actions.querySelector(".bookmark-palette")) return;
|
||||
|
||||
const editLink = actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]');
|
||||
const editLink = actions.querySelector('a[title=window.ShaarliProI18n.t("Edit")], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]');
|
||||
if (!editLink || !editLink.href) return;
|
||||
const editUrl = editLink.href;
|
||||
|
||||
@@ -1931,10 +1865,10 @@ function initBookmarkPaletteButtons() {
|
||||
wrapper.className = "bookmark-palette";
|
||||
wrapper.style.position = "relative";
|
||||
wrapper.innerHTML = `
|
||||
<button type="button" title="Couleur" aria-label="Couleur" id="${paletteBtnId}"><i class="mdi mdi-palette-outline" aria-hidden="true"></i></button>
|
||||
<button type="button" title=window.ShaarliProI18n.t("Color") aria-label=window.ShaarliProI18n.t("Color") id="${paletteBtnId}"><i class="mdi mdi-palette-outline" aria-hidden="true"></i></button>
|
||||
`;
|
||||
|
||||
const pinLink = Array.from(actions.querySelectorAll('a[href*="/pin"], a[aria-label*="Épingler"], a[title*="Épingler"]'))[0];
|
||||
const pinLink = Array.from(actions.querySelectorAll('a[href*="/pin"], a[aria-label*=window.ShaarliProI18n.t("Pin")], a[title*=window.ShaarliProI18n.t("Pin")]'))[0];
|
||||
if (pinLink && pinLink.parentNode === actions) {
|
||||
actions.insertBefore(wrapper, pinLink);
|
||||
} else if (editLink && editLink.parentNode === actions) {
|
||||
@@ -1959,7 +1893,7 @@ function initBookmarkPaletteButtons() {
|
||||
currentFilter: getElementVisualFilter(card),
|
||||
currentBackground: getElementVisualBackground(card),
|
||||
currentFontColor: getElementVisualFontColor(card),
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1990,7 +1924,7 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentFi
|
||||
const onFilterClick = mode === "modal" ? (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>`,
|
||||
`<button class="palette-btn palette-btn-default ${color === "default" ? "is-active" : ""}" type="button" title="${window.ShaarliProI18n.t("Default")}" aria-label="${window.ShaarliProI18n.t("Default")}" aria-pressed="${color === "default"}" onclick="${onColorClick("default")}"><i class="mdi mdi-format-color-reset" aria-hidden="true"></i></button>`,
|
||||
...NOTE_COLOR_OPTIONS.filter((opt) => opt.key !== "default").map((opt) => {
|
||||
const swatchColor = getThemeColorValue(opt);
|
||||
return `<button class="palette-btn note-color-${opt.key} ${color === opt.key ? "is-active" : ""}" type="button" title="${opt.label}" aria-label="${opt.label}" aria-pressed="${color === opt.key}" onclick="${onColorClick(opt.key)}" style="background-color:${swatchColor}"></button>`;
|
||||
@@ -2055,9 +1989,9 @@ function initTodoView(linkList, container) {
|
||||
applyNoteVisualState(inputContainer, { color: "default", filter: "none", background: "none", fontColor: "auto" });
|
||||
inputContainer.innerHTML = `
|
||||
<div class="note-input-collapsed">
|
||||
<span class="note-input-placeholder">Créer une tâche...</span>
|
||||
<span class="note-input-placeholder">${window.ShaarliProI18n.t("Create a task...")}</span>
|
||||
<div class="note-input-actions">
|
||||
<button type="button" class="todo-open-draft-btn" title="Créer une liste"><i class="mdi mdi-check-circle-outline"></i></button>
|
||||
<button type="button" class="todo-open-draft-btn" title=window.ShaarliProI18n.t("Create a list")><i class="mdi mdi-check-circle-outline"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -2069,12 +2003,12 @@ function initTodoView(linkList, container) {
|
||||
inputContainer.classList.add("is-editing", "todo-draft-mode");
|
||||
inputContainer.innerHTML = `
|
||||
<div class="note-input-expanded todo-draft-expanded">
|
||||
<input type="text" class="note-input-title todo-draft-title" placeholder="Titre de la liste" autocomplete="off" />
|
||||
<input type="text" class="note-input-title todo-draft-title" placeholder=window.ShaarliProI18n.t("List title") autocomplete="off" />
|
||||
<div class="todo-draft-list" role="list"></div>
|
||||
<button type="button" class="todo-draft-add-btn"><i class="mdi mdi-plus"></i> Élément de liste</button>
|
||||
<button type="button" class="todo-draft-add-btn"><i class="mdi mdi-plus"></i> ${window.ShaarliProI18n.t("Add list item")}</button>
|
||||
<div class="note-input-expanded-actions">
|
||||
<div class="note-input-actions-left">
|
||||
<button type="button" class="note-input-palette-btn" title="Couleur"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button type="button" class="note-input-palette-btn" title=window.ShaarliProI18n.t("Color")><i class="mdi mdi-palette-outline"></i></button>
|
||||
</div>
|
||||
<button type="button" class="note-input-close-btn">Fermer</button>
|
||||
</div>
|
||||
@@ -2097,12 +2031,12 @@ function initTodoView(linkList, container) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "todo-draft-row";
|
||||
row.innerHTML = `
|
||||
<label class="todo-checklist-box" title="Marquer comme terminée">
|
||||
<label class="todo-checklist-box" title=window.ShaarliProI18n.t("Mark as completed")>
|
||||
<input type="checkbox" class="todo-item-checkbox" ${item.checked ? "checked" : ""} />
|
||||
<span class="todo-checklist-box-ui"></span>
|
||||
</label>
|
||||
<input type="text" class="todo-item-text" placeholder="Élément de liste" value="${escapeHtml(item.text || "")}" />
|
||||
<button type="button" class="todo-item-delete" title="Supprimer"><i class="mdi mdi-close"></i></button>
|
||||
<input type="text" class="todo-item-text" placeholder=window.ShaarliProI18n.t("List item") value="${escapeHtml(item.text || "")}" />
|
||||
<button type="button" class="todo-item-delete" title=window.ShaarliProI18n.t("Delete")><i class="mdi mdi-close"></i></button>
|
||||
`;
|
||||
listEl.appendChild(row);
|
||||
|
||||
@@ -2189,7 +2123,7 @@ function initTodoView(linkList, container) {
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error("Error creating todo:", err);
|
||||
alert("Erreur lors de la création de la tâche.");
|
||||
alert(window.ShaarliProI18n.t("Error creating todo."));
|
||||
isSaving = false;
|
||||
}
|
||||
};
|
||||
@@ -2220,7 +2154,7 @@ function initTodoView(linkList, container) {
|
||||
currentFilter: draftVisual.filter || "none",
|
||||
currentBackground: draftVisual.background || "none",
|
||||
currentFontColor: draftVisual.fontColor || "auto",
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
|
||||
const panel = document.getElementById("shaarli-bg-studio");
|
||||
@@ -2252,8 +2186,8 @@ function initTodoView(linkList, container) {
|
||||
const tools = document.createElement("div");
|
||||
tools.className = "notes-tools";
|
||||
tools.innerHTML = `
|
||||
<button class="icon-btn active" id="btn-view-grid" title="Vue grille"><i class="mdi mdi-view-dashboard-outline"></i></button>
|
||||
<button class="icon-btn" id="btn-view-list" title="Vue liste"><i class="mdi mdi-view-agenda-outline"></i></button>
|
||||
<button class="icon-btn active" id="btn-view-grid" title=window.ShaarliProI18n.t("Grid view")><i class="mdi mdi-view-dashboard-outline"></i></button>
|
||||
<button class="icon-btn" id="btn-view-list" title=window.ShaarliProI18n.t("List view")><i class="mdi mdi-view-agenda-outline"></i></button>
|
||||
`;
|
||||
topBar.appendChild(tools);
|
||||
wrapper.appendChild(topBar);
|
||||
@@ -2281,23 +2215,23 @@ function initTodoView(linkList, container) {
|
||||
<div class="note-modal note-color-default todo-modal">
|
||||
<div class="note-modal-header">
|
||||
<h2 class="note-title" id="todo-modal-title"></h2>
|
||||
<button type="button" class="note-modal-pin-toggle" id="todo-modal-pin" title="Épingler">
|
||||
<button type="button" class="note-modal-pin-toggle" id="todo-modal-pin" title=window.ShaarliProI18n.t("Pin")>
|
||||
<i class="mdi mdi-pin-outline"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="note-modal-content">
|
||||
<div class="todo-checklist" id="todo-modal-checklist"></div>
|
||||
<button type="button" class="todo-add-item-btn" id="todo-modal-add-item"><i class="mdi mdi-plus"></i> Élément de liste</button>
|
||||
<button type="button" class="todo-add-item-btn" id="todo-modal-add-item"><i class="mdi mdi-plus"></i> ${window.ShaarliProI18n.t("Add list item")}</button>
|
||||
</div>
|
||||
<div class="note-modal-tags is-empty" id="todo-modal-tags"></div>
|
||||
<div class="note-modal-actions">
|
||||
<div class="note-modal-actions-left">
|
||||
<div class="note-modal-color-picker">
|
||||
<button type="button" id="todo-modal-color-btn" title="Couleur"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button type="button" id="todo-modal-color-btn" title=window.ShaarliProI18n.t("Color")><i class="mdi mdi-palette-outline"></i></button>
|
||||
<div class="palette-popup note-modal-palette" id="todo-modal-color-popup"></div>
|
||||
</div>
|
||||
<a href="#" id="todo-modal-edit" title="Modifier"><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button type="button" id="todo-modal-delete" title="Supprimer"><i class="mdi mdi-delete-outline"></i></button>
|
||||
<a href="#" id="todo-modal-edit" title=window.ShaarliProI18n.t("Edit")><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button type="button" id="todo-modal-delete" title=window.ShaarliProI18n.t("Delete")><i class="mdi mdi-delete-outline"></i></button>
|
||||
</div>
|
||||
<button type="button" class="note-modal-close-btn" id="todo-modal-close">Fermer</button>
|
||||
</div>
|
||||
@@ -2344,7 +2278,7 @@ function initTodoView(linkList, container) {
|
||||
currentFilter: modalCard ? getElementVisualFilter(modalCard) : "none",
|
||||
currentBackground: modalCard ? getElementVisualBackground(modalCard) : "none",
|
||||
currentFontColor: modalCard ? getElementVisualFontColor(modalCard) : "auto",
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2377,7 +2311,7 @@ function initTodoView(linkList, container) {
|
||||
});
|
||||
|
||||
modalOverlay.querySelector("#todo-modal-delete").addEventListener("click", () => {
|
||||
if (!confirm("Supprimer cette tâche ?")) return;
|
||||
if (!confirm(window.ShaarliProI18n.t("Delete this task?"))) return;
|
||||
const modalCard = modalOverlay.querySelector(".note-modal");
|
||||
const deleteUrl = modalCard.dataset.deleteUrl;
|
||||
const todoId = modalCard.dataset.todoId;
|
||||
@@ -2388,7 +2322,7 @@ function initTodoView(linkList, container) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Delete failed:", err);
|
||||
alert("Erreur lors de la suppression.");
|
||||
alert(window.ShaarliProI18n.t("Error deleting."));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -2791,15 +2725,6 @@ async function hydrateTodoFromEditForm(todo) {
|
||||
todo._todoFooterLines = parsed.footerLines;
|
||||
todo.items = parsed.items;
|
||||
|
||||
if (!todo._todoDebugLogged && rawMarkdown && String(rawMarkdown).trim() && (!parsed.items || parsed.items.length === 0)) {
|
||||
todo._todoDebugLogged = true;
|
||||
console.warn("Todo parsed 0 items from lf_description; sample follows.", {
|
||||
id: todo && todo.id,
|
||||
url: todo && todo.editUrl,
|
||||
sample: String(rawMarkdown).slice(0, 240),
|
||||
});
|
||||
}
|
||||
|
||||
updateTodoCardPreview(todo);
|
||||
todo._todoHydrated = true;
|
||||
} catch (e) {
|
||||
@@ -2880,25 +2805,25 @@ function renderTodos(container, todos, viewMode) {
|
||||
|
||||
actions.innerHTML = `
|
||||
<div style="position:relative;">
|
||||
<button title="Couleur" id="${paletteBtnId}"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button title=window.ShaarliProI18n.t("Color") id="${paletteBtnId}"><i class="mdi mdi-palette-outline"></i></button>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<a href="${todo.editUrl}" title="Modifier"><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button title="Supprimer" class="todo-delete-btn"><i class="mdi mdi-delete-outline"></i></button>
|
||||
<a href="${todo.editUrl}" title=window.ShaarliProI18n.t("Edit")><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button title=window.ShaarliProI18n.t("Delete") class="todo-delete-btn"><i class="mdi mdi-delete-outline"></i></button>
|
||||
`;
|
||||
|
||||
const deleteBtn = actions.querySelector(".todo-delete-btn");
|
||||
deleteBtn?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!confirm("Supprimer cette tâche ?")) return;
|
||||
if (!confirm(window.ShaarliProI18n.t("Delete this task?"))) return;
|
||||
deleteEntitySilently(todo.deleteUrl)
|
||||
.then(() => {
|
||||
window.location.href = "/?searchtags=todo";
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Delete failed:", err);
|
||||
alert("Erreur lors de la suppression.");
|
||||
alert(window.ShaarliProI18n.t("Error deleting."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2914,7 +2839,7 @@ function renderTodos(container, todos, viewMode) {
|
||||
currentColor: getElementVisualColor(card),
|
||||
currentFilter: getElementVisualFilter(card),
|
||||
currentBackground: getElementVisualBackground(card),
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3050,13 +2975,13 @@ function renderTodoModalChecklist(modal, todo) {
|
||||
row.setAttribute("draggable", "true");
|
||||
row.classList.toggle("is-checked", !!item.checked);
|
||||
row.innerHTML = `
|
||||
<button type="button" class="todo-drag-handle" aria-label="Déplacer"><i class="mdi mdi-dots-grid"></i></button>
|
||||
<button type="button" class="todo-drag-handle" aria-label=window.ShaarliProI18n.t("Move")><i class="mdi mdi-dots-grid"></i></button>
|
||||
<label class="todo-checklist-box">
|
||||
<input type="checkbox" class="todo-item-checkbox" ${item.checked ? "checked" : ""}>
|
||||
<span class="todo-checklist-box-ui"></span>
|
||||
</label>
|
||||
<input type="text" class="todo-item-text" value="${escapeHtml(item.text || "")}" placeholder="">
|
||||
<button type="button" class="todo-item-delete" aria-label="Supprimer"><i class="mdi mdi-close"></i></button>
|
||||
<button type="button" class="todo-item-delete" aria-label=window.ShaarliProI18n.t("Delete")><i class="mdi mdi-close"></i></button>
|
||||
`;
|
||||
|
||||
const cb = row.querySelector(".todo-item-checkbox");
|
||||
@@ -3172,7 +3097,7 @@ async function openTodoModal(todo) {
|
||||
const visibleTags = (todo.tags || []).filter((tag) => tag && !isTechnicalTag(tag));
|
||||
modalCard.dataset.tags = visibleTags.join("||");
|
||||
|
||||
title.textContent = todo.title || "Sans titre";
|
||||
title.textContent = todo.title || window.ShaarliProI18n.t("Untitled");
|
||||
renderModalTags(tagsContainer, visibleTags);
|
||||
|
||||
if (editLink) {
|
||||
@@ -3263,7 +3188,7 @@ function scheduleTodoSave(modal, todo) {
|
||||
modal._todoSaveTimer = setTimeout(() => {
|
||||
persistTodoChanges(todo).catch((err) => {
|
||||
console.error("Error saving todo:", err);
|
||||
alert("Erreur lors de la sauvegarde de la tâche. Veuillez rafraîchir la page.");
|
||||
alert(window.ShaarliProI18n.t("Error saving todo.") + " " + window.ShaarliProI18n.t("Please refresh the page."));
|
||||
});
|
||||
}, 600);
|
||||
}
|
||||
@@ -3383,9 +3308,9 @@ function initNoteView(linkList, container) {
|
||||
applyNoteVisualState(inputContainer, { color: "default", filter: "none", background: "none", fontColor: "auto" });
|
||||
inputContainer.innerHTML = `
|
||||
<div class="note-input-collapsed">
|
||||
<span class="note-input-placeholder">Créer une note...</span>
|
||||
<span class="note-input-placeholder">${window.ShaarliProI18n.t("Create a note...")}</span>
|
||||
<div class="note-input-actions note-input-type-actions">
|
||||
<button type="button" class="note-create-note-btn" title="Créer une note"><i class="mdi mdi-note-outline"></i></button>
|
||||
<button type="button" class="note-create-note-btn" title=window.ShaarliProI18n.t("Create a note")><i class="mdi mdi-note-outline"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -3398,12 +3323,12 @@ function initNoteView(linkList, container) {
|
||||
inputContainer.classList.add("is-editing", "todo-draft-mode");
|
||||
inputContainer.innerHTML = `
|
||||
<div class="note-input-expanded todo-draft-expanded">
|
||||
<input type="text" class="note-input-title todo-draft-title" placeholder="Titre de la liste" autocomplete="off" />
|
||||
<input type="text" class="note-input-title todo-draft-title" placeholder=window.ShaarliProI18n.t("List title") autocomplete="off" />
|
||||
<div class="todo-draft-list" role="list"></div>
|
||||
<button type="button" class="todo-draft-add-btn"><i class="mdi mdi-plus"></i> Élément de liste</button>
|
||||
<button type="button" class="todo-draft-add-btn"><i class="mdi mdi-plus"></i> ${window.ShaarliProI18n.t("Add list item")}</button>
|
||||
<div class="note-input-expanded-actions">
|
||||
<div class="note-input-actions-left">
|
||||
<button type="button" class="note-input-palette-btn" title="Couleur"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button type="button" class="note-input-palette-btn" title=window.ShaarliProI18n.t("Color")><i class="mdi mdi-palette-outline"></i></button>
|
||||
</div>
|
||||
<button type="button" class="note-input-close-btn">Fermer</button>
|
||||
</div>
|
||||
@@ -3426,12 +3351,12 @@ function initNoteView(linkList, container) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "todo-draft-row";
|
||||
row.innerHTML = `
|
||||
<label class="todo-checklist-box" title="Marquer comme terminée">
|
||||
<label class="todo-checklist-box" title=window.ShaarliProI18n.t("Mark as completed")>
|
||||
<input type="checkbox" class="todo-item-checkbox" ${item.checked ? "checked" : ""} />
|
||||
<span class="todo-checklist-box-ui"></span>
|
||||
</label>
|
||||
<input type="text" class="todo-item-text" placeholder="Élément de liste" value="${escapeHtml(item.text || "")}" />
|
||||
<button type="button" class="todo-item-delete" title="Supprimer"><i class="mdi mdi-close"></i></button>
|
||||
<input type="text" class="todo-item-text" placeholder=window.ShaarliProI18n.t("List item") value="${escapeHtml(item.text || "")}" />
|
||||
<button type="button" class="todo-item-delete" title=window.ShaarliProI18n.t("Delete")><i class="mdi mdi-close"></i></button>
|
||||
`;
|
||||
listEl.appendChild(row);
|
||||
|
||||
@@ -3517,7 +3442,7 @@ function initNoteView(linkList, container) {
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error("Error creating checklist note:", err);
|
||||
alert("Erreur lors de la création de la note liste.");
|
||||
alert(window.ShaarliProI18n.t("Error creating checklist note."));
|
||||
isSaving = false;
|
||||
}
|
||||
};
|
||||
@@ -3548,7 +3473,7 @@ function initNoteView(linkList, container) {
|
||||
currentFilter: draftVisual.filter || "none",
|
||||
currentBackground: draftVisual.background || "none",
|
||||
currentFontColor: draftVisual.fontColor || "auto",
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
|
||||
const panel = document.getElementById("shaarli-bg-studio");
|
||||
@@ -3567,33 +3492,33 @@ function initNoteView(linkList, container) {
|
||||
inputContainer.classList.add("is-editing");
|
||||
inputContainer.innerHTML = `
|
||||
<div class="note-input-expanded">
|
||||
<input type="text" class="note-input-title" placeholder="Titre" />
|
||||
<textarea class="note-input-description-source" rows="8" placeholder="Écrivez votre note en Markdown..."></textarea>
|
||||
<input type="text" class="note-input-title" placeholder=window.ShaarliProI18n.t("Title") />
|
||||
<textarea class="note-input-description-source" rows="8" placeholder=window.ShaarliProI18n.t("Write your note in Markdown...")></textarea>
|
||||
<div class="note-formatting-bar" aria-hidden="true">
|
||||
<button type="button" class="note-format-btn" data-note-format="h1" title="Titre 1">H1</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h2" title="Titre 2">H2</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h3" title="Titre 3">H3</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="p" title="Texte">Aa</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h1" title=window.ShaarliProI18n.t("Heading 1")>H1</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h2" title=window.ShaarliProI18n.t("Heading 2")>H2</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h3" title=window.ShaarliProI18n.t("Heading 3")>H3</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="p" title=window.ShaarliProI18n.t("Text")>Aa</button>
|
||||
<span class="note-format-sep" aria-hidden="true"></span>
|
||||
<button type="button" class="note-format-btn" data-note-format="bold" title="Gras"><b>B</b></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="italic" title="Italique"><i>I</i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="underline" title="Souligné"><u>U</u></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="strike" title="Barré"><s>S</s></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="code" title="Code inline"><i class="mdi mdi-code-tags"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="bold" title=window.ShaarliProI18n.t("Bold")><b>B</b></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="italic" title=window.ShaarliProI18n.t("Italic")><i>I</i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="underline" title=window.ShaarliProI18n.t("Underline")><u>U</u></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="strike" title=window.ShaarliProI18n.t("Strikethrough")><s>S</s></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="code" title=window.ShaarliProI18n.t("Inline code")><i class="mdi mdi-code-tags"></i></button>
|
||||
<span class="note-format-sep" aria-hidden="true"></span>
|
||||
<button type="button" class="note-format-btn" data-note-format="ul" title="Liste à puces"><i class="mdi mdi-format-list-bulleted"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="ol" title="Liste numérotée"><i class="mdi mdi-format-list-numbered"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="todo" title="Checklist"><i class="mdi mdi-format-list-checks"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="quote" title="Citation"><i class="mdi mdi-format-quote-open"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="link" title="Lien"><i class="mdi mdi-link-variant"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="image" title="Image"><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="codeblock" title="Bloc de code"><i class="mdi mdi-code-braces"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="clear" title="Effacer la mise en forme">X</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="ul" title=window.ShaarliProI18n.t("Bulleted list")><i class="mdi mdi-format-list-bulleted"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="ol" title=window.ShaarliProI18n.t("Numbered list")><i class="mdi mdi-format-list-numbered"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="todo" title=window.ShaarliProI18n.t("Checklist")><i class="mdi mdi-format-list-checks"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="quote" title=window.ShaarliProI18n.t("Quote")><i class="mdi mdi-format-quote-open"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="link" title=window.ShaarliProI18n.t("Link")><i class="mdi mdi-link-variant"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="image" title=window.ShaarliProI18n.t("Image")><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="codeblock" title=window.ShaarliProI18n.t("Code block")><i class="mdi mdi-code-braces"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="clear" title=window.ShaarliProI18n.t("Clear formatting")>X</button>
|
||||
</div>
|
||||
<div class="note-input-expanded-actions">
|
||||
<div class="note-input-actions-left">
|
||||
<button type="button" class="note-input-format-btn" title="Options de mise en forme"><i class="mdi mdi-format-text"></i></button>
|
||||
<button type="button" class="note-input-palette-btn" title="Couleur"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button type="button" class="note-input-format-btn" title=window.ShaarliProI18n.t("Formatting options")><i class="mdi mdi-format-text"></i></button>
|
||||
<button type="button" class="note-input-palette-btn" title=window.ShaarliProI18n.t("Color")><i class="mdi mdi-palette-outline"></i></button>
|
||||
</div>
|
||||
<button type="button" class="note-input-close-btn">Fermer</button>
|
||||
</div>
|
||||
@@ -3676,7 +3601,7 @@ function initNoteView(linkList, container) {
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error("Error creating note:", err);
|
||||
alert("Erreur lors de la création de la note.");
|
||||
alert(window.ShaarliProI18n.t("Error creating note."));
|
||||
isSaving = false;
|
||||
}
|
||||
};
|
||||
@@ -3708,7 +3633,7 @@ function initNoteView(linkList, container) {
|
||||
currentFilter: draftVisual.filter || "none",
|
||||
currentBackground: draftVisual.background || "none",
|
||||
currentFontColor: draftVisual.fontColor || "auto",
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
|
||||
const panel = document.getElementById("shaarli-bg-studio");
|
||||
@@ -3742,8 +3667,8 @@ function initNoteView(linkList, container) {
|
||||
const tools = document.createElement("div");
|
||||
tools.className = "notes-tools";
|
||||
tools.innerHTML = `
|
||||
<button class="icon-btn active" id="btn-view-grid" title="Vue grille"><i class="mdi mdi-view-dashboard-outline"></i></button>
|
||||
<button class="icon-btn" id="btn-view-list" title="Vue liste"><i class="mdi mdi-view-agenda-outline"></i></button>
|
||||
<button class="icon-btn active" id="btn-view-grid" title=window.ShaarliProI18n.t("Grid view")><i class="mdi mdi-view-dashboard-outline"></i></button>
|
||||
<button class="icon-btn" id="btn-view-list" title=window.ShaarliProI18n.t("List view")><i class="mdi mdi-view-agenda-outline"></i></button>
|
||||
`;
|
||||
topBar.appendChild(tools);
|
||||
|
||||
@@ -3790,50 +3715,50 @@ function initNoteView(linkList, container) {
|
||||
modalOverlay.innerHTML = `
|
||||
<div class="note-modal" role="dialog" aria-modal="true">
|
||||
<div class="note-modal-header">
|
||||
<input type="text" class="note-title note-modal-title-input" id="note-modal-title" placeholder="Titre" />
|
||||
<button type="button" class="note-modal-pin-toggle" id="note-modal-pin" title="Épingler">
|
||||
<input type="text" class="note-title note-modal-title-input" id="note-modal-title" placeholder=window.ShaarliProI18n.t("Title") />
|
||||
<button type="button" class="note-modal-pin-toggle" id="note-modal-pin" title=window.ShaarliProI18n.t("Pin")>
|
||||
<i class="mdi mdi-pin-outline"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="note-modal-content">
|
||||
<div class="note-modal-description-preview note-body" id="note-modal-description-preview" role="button" tabindex="0" aria-label="Cliquer pour modifier"></div>
|
||||
<div class="note-modal-description-preview note-body" id="note-modal-description-preview" role="button" tabindex="0" aria-label=window.ShaarliProI18n.t("Click to edit")></div>
|
||||
<textarea class="note-modal-description-source" id="note-modal-description-source" rows="8"></textarea>
|
||||
<div class="note-formatting-bar" id="note-modal-formatting" aria-hidden="true">
|
||||
<button type="button" class="note-format-btn" data-note-format="h1" title="Titre 1">H1</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h2" title="Titre 2">H2</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h3" title="Titre 3">H3</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="p" title="Texte">Aa</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h1" title=window.ShaarliProI18n.t("Heading 1")>H1</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h2" title=window.ShaarliProI18n.t("Heading 2")>H2</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="h3" title=window.ShaarliProI18n.t("Heading 3")>H3</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="p" title=window.ShaarliProI18n.t("Text")>Aa</button>
|
||||
<span class="note-format-sep" aria-hidden="true"></span>
|
||||
<button type="button" class="note-format-btn" data-note-format="bold" title="Gras"><b>B</b></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="italic" title="Italique"><i>I</i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="underline" title="Souligné"><u>U</u></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="strike" title="Barré"><s>S</s></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="code" title="Code inline"><i class="mdi mdi-code-tags"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="bold" title=window.ShaarliProI18n.t("Bold")><b>B</b></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="italic" title=window.ShaarliProI18n.t("Italic")><i>I</i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="underline" title=window.ShaarliProI18n.t("Underline")><u>U</u></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="strike" title=window.ShaarliProI18n.t("Strikethrough")><s>S</s></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="code" title=window.ShaarliProI18n.t("Inline code")><i class="mdi mdi-code-tags"></i></button>
|
||||
<span class="note-format-sep" aria-hidden="true"></span>
|
||||
<button type="button" class="note-format-btn" data-note-format="ul" title="Liste à puces"><i class="mdi mdi-format-list-bulleted"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="ol" title="Liste numérotée"><i class="mdi mdi-format-list-numbered"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="todo" title="Checklist"><i class="mdi mdi-format-list-checks"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="quote" title="Citation"><i class="mdi mdi-format-quote-open"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="link" title="Lien"><i class="mdi mdi-link-variant"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="image" title="Image"><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="codeblock" title="Bloc de code"><i class="mdi mdi-code-braces"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="clear" title="Effacer la mise en forme">X</button>
|
||||
<button type="button" class="note-format-btn" data-note-format="ul" title=window.ShaarliProI18n.t("Bulleted list")><i class="mdi mdi-format-list-bulleted"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="ol" title=window.ShaarliProI18n.t("Numbered list")><i class="mdi mdi-format-list-numbered"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="todo" title=window.ShaarliProI18n.t("Checklist")><i class="mdi mdi-format-list-checks"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="quote" title=window.ShaarliProI18n.t("Quote")><i class="mdi mdi-format-quote-open"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="link" title=window.ShaarliProI18n.t("Link")><i class="mdi mdi-link-variant"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="image" title=window.ShaarliProI18n.t("Image")><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="codeblock" title=window.ShaarliProI18n.t("Code block")><i class="mdi mdi-code-braces"></i></button>
|
||||
<button type="button" class="note-format-btn" data-note-format="clear" title=window.ShaarliProI18n.t("Clear formatting")>X</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note-modal-tags is-empty" id="note-modal-tags"></div>
|
||||
<div class="note-modal-actions">
|
||||
<div class="note-modal-actions-left">
|
||||
<div class="note-modal-color-picker">
|
||||
<button type="button" id="note-modal-color-btn" title="Couleur"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button type="button" id="note-modal-color-btn" title=window.ShaarliProI18n.t("Color")><i class="mdi mdi-palette-outline"></i></button>
|
||||
<div class="palette-popup note-modal-palette" id="note-modal-color-popup"></div>
|
||||
</div>
|
||||
<button type="button" id="note-modal-format-btn" title="Options de mise en forme"><i class="mdi mdi-format-text"></i></button>
|
||||
<button type="button" title="Rappel"><i class="mdi mdi-bell-outline"></i></button>
|
||||
<button type="button" title="Collaborateur"><i class="mdi mdi-account-plus-outline"></i></button>
|
||||
<button type="button" title="Image"><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" id="note-modal-archive" title="Archiver"><i class="mdi mdi-archive-arrow-down-outline"></i></button>
|
||||
<a href="#" id="note-modal-edit" title="Modifier"><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button type="button" id="note-modal-delete" title="Supprimer"><i class="mdi mdi-delete-outline"></i></button>
|
||||
<button type="button" id="note-modal-format-btn" title=window.ShaarliProI18n.t("Formatting options")><i class="mdi mdi-format-text"></i></button>
|
||||
<button type="button" title=window.ShaarliProI18n.t("Reminder")><i class="mdi mdi-bell-outline"></i></button>
|
||||
<button type="button" title=window.ShaarliProI18n.t("Collaborator")><i class="mdi mdi-account-plus-outline"></i></button>
|
||||
<button type="button" title=window.ShaarliProI18n.t("Image")><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" id="note-modal-archive" title=window.ShaarliProI18n.t("Archive")><i class="mdi mdi-archive-arrow-down-outline"></i></button>
|
||||
<a href="#" id="note-modal-edit" title=window.ShaarliProI18n.t("Edit")><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button type="button" id="note-modal-delete" title=window.ShaarliProI18n.t("Delete")><i class="mdi mdi-delete-outline"></i></button>
|
||||
</div>
|
||||
<button type="button" class="note-modal-close-btn" id="note-modal-close">Fermer</button>
|
||||
</div>
|
||||
@@ -3864,7 +3789,7 @@ function initNoteView(linkList, container) {
|
||||
document.body.classList.remove("note-modal-open");
|
||||
} catch (e) {
|
||||
console.error("Error saving note from modal:", e);
|
||||
alert("Erreur lors de la sauvegarde de la note.");
|
||||
alert(window.ShaarliProI18n.t("Error saving note."));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3900,7 +3825,7 @@ function initNoteView(linkList, container) {
|
||||
currentFilter: modalCard ? getElementVisualFilter(modalCard) : "none",
|
||||
currentBackground: modalCard ? getElementVisualBackground(modalCard) : "none",
|
||||
currentFontColor: modalCard ? getElementVisualFontColor(modalCard) : "auto",
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3935,7 +3860,7 @@ function initNoteView(linkList, container) {
|
||||
});
|
||||
|
||||
modalOverlay.querySelector("#note-modal-delete").addEventListener("click", () => {
|
||||
if (!confirm("Supprimer cette note ?")) return;
|
||||
if (!confirm(window.ShaarliProI18n.t("Delete this note?"))) return;
|
||||
const modalCard = modalOverlay.querySelector(".note-modal");
|
||||
const deleteUrl = modalCard.dataset.deleteUrl;
|
||||
const noteId = modalCard.dataset.noteId;
|
||||
@@ -3946,7 +3871,7 @@ function initNoteView(linkList, container) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Delete failed:", err);
|
||||
alert("Erreur lors de la suppression.");
|
||||
alert(window.ShaarliProI18n.t("Error deleting."));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -3978,7 +3903,7 @@ function initNoteView(linkList, container) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error archiving note:", err);
|
||||
alert("Erreur lors de l'archivage de la note.");
|
||||
alert(window.ShaarliProI18n.t("Error archiving note."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4022,7 +3947,7 @@ function initArchiveView(linkList, container) {
|
||||
titleContainer.className = "archive-title-container";
|
||||
titleContainer.innerHTML = `
|
||||
<h1 class="archive-title"><i class="mdi mdi-archive-arrow-down-outline"></i> Archive</h1>
|
||||
<p class="archive-subtitle">Notes archivées</p>
|
||||
<p class="archive-subtitle">${window.ShaarliProI18n.t("Archived notes")}</p>
|
||||
`;
|
||||
topBar.appendChild(titleContainer);
|
||||
|
||||
@@ -4030,8 +3955,8 @@ function initArchiveView(linkList, container) {
|
||||
const tools = document.createElement("div");
|
||||
tools.className = "notes-tools";
|
||||
tools.innerHTML = `
|
||||
<button class="icon-btn active" id="btn-view-grid" title="Vue grille"><i class="mdi mdi-view-dashboard-outline"></i></button>
|
||||
<button class="icon-btn" id="btn-view-list" title="Vue liste"><i class="mdi mdi-view-agenda-outline"></i></button>
|
||||
<button class="icon-btn active" id="btn-view-grid" title=window.ShaarliProI18n.t("Grid view")><i class="mdi mdi-view-dashboard-outline"></i></button>
|
||||
<button class="icon-btn" id="btn-view-list" title=window.ShaarliProI18n.t("List view")><i class="mdi mdi-view-agenda-outline"></i></button>
|
||||
`;
|
||||
topBar.appendChild(tools);
|
||||
|
||||
@@ -4067,7 +3992,7 @@ function initArchiveView(linkList, container) {
|
||||
<div class="note-modal note-color-default">
|
||||
<div class="note-modal-header">
|
||||
<h2 class="note-title" id="note-modal-title"></h2>
|
||||
<button type="button" class="note-modal-pin-toggle" id="note-modal-pin" title="Épingler">
|
||||
<button type="button" class="note-modal-pin-toggle" id="note-modal-pin" title=window.ShaarliProI18n.t("Pin")>
|
||||
<i class="mdi mdi-pin-outline"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -4076,15 +4001,15 @@ function initArchiveView(linkList, container) {
|
||||
<div class="note-modal-actions">
|
||||
<div class="note-modal-actions-left">
|
||||
<div class="note-modal-color-picker">
|
||||
<button type="button" id="note-modal-color-btn" title="Couleur"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button type="button" id="note-modal-color-btn" title=window.ShaarliProI18n.t("Color")><i class="mdi mdi-palette-outline"></i></button>
|
||||
<div class="palette-popup note-modal-palette" id="note-modal-color-popup"></div>
|
||||
</div>
|
||||
<button type="button" title="Rappel"><i class="mdi mdi-bell-outline"></i></button>
|
||||
<button type="button" title="Collaborateur"><i class="mdi mdi-account-plus-outline"></i></button>
|
||||
<button type="button" title="Image"><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" id="note-modal-unarchive" title="Désarchiver"><i class="mdi mdi-archive-arrow-up-outline"></i></button>
|
||||
<a href="#" id="note-modal-edit" title="Modifier"><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button type="button" id="note-modal-delete" title="Supprimer"><i class="mdi mdi-delete-outline"></i></button>
|
||||
<button type="button" title=window.ShaarliProI18n.t("Reminder")><i class="mdi mdi-bell-outline"></i></button>
|
||||
<button type="button" title=window.ShaarliProI18n.t("Collaborator")><i class="mdi mdi-account-plus-outline"></i></button>
|
||||
<button type="button" title=window.ShaarliProI18n.t("Image")><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" id="note-modal-unarchive" title=window.ShaarliProI18n.t("Unarchive")><i class="mdi mdi-archive-arrow-up-outline"></i></button>
|
||||
<a href="#" id="note-modal-edit" title=window.ShaarliProI18n.t("Edit")><i class="mdi mdi-pencil-outline"></i></a>
|
||||
<button type="button" id="note-modal-delete" title=window.ShaarliProI18n.t("Delete")><i class="mdi mdi-delete-outline"></i></button>
|
||||
</div>
|
||||
<button type="button" class="note-modal-close-btn" id="note-modal-close">Fermer</button>
|
||||
</div>
|
||||
@@ -4133,7 +4058,7 @@ function initArchiveView(linkList, container) {
|
||||
currentFilter: modalCard ? getElementVisualFilter(modalCard) : "none",
|
||||
currentBackground: modalCard ? getElementVisualBackground(modalCard) : "none",
|
||||
currentFontColor: modalCard ? getElementVisualFontColor(modalCard) : "auto",
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4168,7 +4093,7 @@ function initArchiveView(linkList, container) {
|
||||
});
|
||||
|
||||
modalOverlay.querySelector("#note-modal-delete").addEventListener("click", () => {
|
||||
if (!confirm("Supprimer cette note ?")) return;
|
||||
if (!confirm(window.ShaarliProI18n.t("Delete this note?"))) return;
|
||||
const modalCard = modalOverlay.querySelector(".note-modal");
|
||||
const deleteUrl = modalCard.dataset.deleteUrl;
|
||||
const noteId = modalCard.dataset.noteId;
|
||||
@@ -4179,7 +4104,7 @@ function initArchiveView(linkList, container) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Delete failed:", err);
|
||||
alert("Erreur lors de la suppression.");
|
||||
alert(window.ShaarliProI18n.t("Error deleting."));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -4209,7 +4134,7 @@ function initArchiveView(linkList, container) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error unarchiving note:", err);
|
||||
alert("Erreur lors du désarchivage de la note.");
|
||||
alert(window.ShaarliProI18n.t("Error unarchiving note."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4397,31 +4322,31 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
|
||||
const archiveBtnId = `archive-${note.id}`;
|
||||
|
||||
actions.innerHTML = `
|
||||
<button title="Rappel"><i class="mdi mdi-bell-outline"></i></button>
|
||||
<button title="Collaborateur"><i class="mdi mdi-account-plus-outline"></i></button>
|
||||
<button title=window.ShaarliProI18n.t("Reminder")><i class="mdi mdi-bell-outline"></i></button>
|
||||
<button title=window.ShaarliProI18n.t("Collaborator")><i class="mdi mdi-account-plus-outline"></i></button>
|
||||
<div style="position:relative;">
|
||||
<button title="Couleur" id="${paletteBtnId}"><i class="mdi mdi-palette-outline"></i></button>
|
||||
<button title=window.ShaarliProI18n.t("Color") id="${paletteBtnId}"><i class="mdi mdi-palette-outline"></i></button>
|
||||
</div>
|
||||
<button title="Image"><i class="mdi mdi-image-outline"></i></button>
|
||||
<button title="Archiver" id="${archiveBtnId}"><i class="mdi mdi-archive-arrow-down-outline"></i></button>
|
||||
<button title=window.ShaarliProI18n.t("Image")><i class="mdi mdi-image-outline"></i></button>
|
||||
<button title=window.ShaarliProI18n.t("Archive") id="${archiveBtnId}"><i class="mdi mdi-archive-arrow-down-outline"></i></button>
|
||||
<div class="spacer"></div>
|
||||
<!-- Real Actions -->
|
||||
<button type="button" class="note-open-editor-btn" title="Modifier"><i class="mdi mdi-pencil-outline"></i></button>
|
||||
<button title="Supprimer" class="note-delete-btn"><i class="mdi mdi-delete-outline"></i></button>
|
||||
<button type="button" class="note-open-editor-btn" title=window.ShaarliProI18n.t("Edit")><i class="mdi mdi-pencil-outline"></i></button>
|
||||
<button title=window.ShaarliProI18n.t("Delete") class="note-delete-btn"><i class="mdi mdi-delete-outline"></i></button>
|
||||
`;
|
||||
|
||||
const deleteBtn = actions.querySelector(".note-delete-btn");
|
||||
deleteBtn?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!confirm("Supprimer cette note ?")) return;
|
||||
if (!confirm(window.ShaarliProI18n.t("Delete this note?"))) return;
|
||||
deleteEntitySilently(note.deleteUrl)
|
||||
.then(() => {
|
||||
window.location.href = isArchiveMode ? "/?searchtags=shaarli-archive" : "/?searchtags=note";
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Delete failed:", err);
|
||||
alert("Erreur lors de la suppression.");
|
||||
alert(window.ShaarliProI18n.t("Error deleting."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4446,7 +4371,7 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
|
||||
currentColor: getElementVisualColor(card),
|
||||
currentFilter: getElementVisualFilter(card),
|
||||
currentBackground: getElementVisualBackground(card),
|
||||
title: "Mes images & couleurs",
|
||||
title: window.ShaarliProI18n.t("My images & colors"),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4462,11 +4387,10 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
|
||||
.then(() => {
|
||||
// Remove the card from the view
|
||||
card.remove();
|
||||
console.log(`Note ${note.id} archived`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error archiving note:", err);
|
||||
alert("Erreur lors de l'archivage de la note.");
|
||||
alert(window.ShaarliProI18n.t("Error archiving note."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4481,7 +4405,7 @@ function setModalPinButtonState(button, isPinned) {
|
||||
if (!button) return;
|
||||
|
||||
button.classList.toggle("active", isPinned);
|
||||
button.title = isPinned ? "Désépingler" : "Épingler";
|
||||
button.title = isPinned ? window.ShaarliProI18n.t("Unpin") : window.ShaarliProI18n.t("Pin");
|
||||
|
||||
const icon = button.querySelector("i");
|
||||
if (icon) {
|
||||
@@ -4893,14 +4817,13 @@ window.setNoteColor = function (noteId, color, editUrl) {
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
console.log(`Color ${color} saved for note ${noteId}`);
|
||||
} else {
|
||||
throw new Error("Failed to save color");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error saving note color:", err);
|
||||
alert("Erreur lors de la sauvegarde de la couleur. Veuillez rafraîchir la page.");
|
||||
alert(window.ShaarliProI18n.t("Failed to save color.") + " " + window.ShaarliProI18n.t("Please refresh the page."));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4989,7 +4912,7 @@ window.setNoteFilter = function (noteId, filterKey, editUrl) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error saving note filter:", err);
|
||||
alert("Erreur lors de la sauvegarde du filtre. Veuillez rafraîchir la page.");
|
||||
alert(window.ShaarliProI18n.t("Failed to save filter.") + " " + window.ShaarliProI18n.t("Please refresh the page."));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5074,7 +4997,7 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error saving note background:", err);
|
||||
alert("Erreur lors de la sauvegarde du fond. Veuillez rafraîchir la page.");
|
||||
alert(window.ShaarliProI18n.t("Failed to save background.") + " " + window.ShaarliProI18n.t("Please refresh the page."));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5419,7 +5342,6 @@ function togglePinTag(id, editUrl, btn) {
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
console.log("Pin toggled successfully");
|
||||
if (typeof organizePinnedBookmarks === "function") {
|
||||
window.requestAnimationFrame(organizePinnedBookmarks);
|
||||
}
|
||||
@@ -5571,8 +5493,8 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
|
||||
|
||||
panel.innerHTML = `
|
||||
<div class="color-picker-header">
|
||||
<div class="color-picker-title">${type === "font" ? "Couleur du texte" : "Couleur personnalisée"}</div>
|
||||
<button class="color-picker-close" type="button" data-color-picker-action="close" title="Fermer" aria-label="Fermer"><i class="mdi mdi-close" aria-hidden="true"></i></button>
|
||||
<div class="color-picker-title">${type === "font" ? "Couleur du texte" : window.ShaarliProI18n.t("Custom color")}</div>
|
||||
<button class="color-picker-close" type="button" data-color-picker-action="close" title=window.ShaarliProI18n.t("Close") aria-label=window.ShaarliProI18n.t("Close")><i class="mdi mdi-close" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="color-picker-body">
|
||||
<div class="color-picker-gradient">
|
||||
@@ -6044,7 +5966,6 @@ function setNoteFontColor(noteId, fontColorKey, editUrl) {
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
console.log(`Font color ${fontColorKey} saved for note ${noteId}`);
|
||||
} else {
|
||||
throw new Error("Failed to save font color");
|
||||
}
|
||||
@@ -6154,7 +6075,6 @@ function setModalCustomNoteColor(color) {
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error("Failed to save custom color");
|
||||
console.log(`Custom color ${color} saved for note ${entity.id}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error saving custom color:", err);
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// Minimal internationalisation helper for Shaarli Pro theme JS.
|
||||
// Strings are kept in English so the file acts as a fallback when a translation
|
||||
// key is missing from Shaarli's language files.
|
||||
(function (root) {
|
||||
'use strict';
|
||||
|
||||
var translations = {
|
||||
'Dark Mode': 'Dark Mode',
|
||||
'Light Mode': 'Light Mode',
|
||||
'Search in tags...': 'Search in tags...',
|
||||
'Search in notes...': 'Search in notes...',
|
||||
'Search in bookmarks and notes...': 'Search in bookmarks and notes...',
|
||||
'Nothing found.': 'Nothing found.',
|
||||
'result': 'result',
|
||||
'results': 'results',
|
||||
'with status': 'with status',
|
||||
'Clear filters': 'Clear filters',
|
||||
'Clear': 'Clear',
|
||||
'The following links will be irretrievably deleted:': 'The following links will be irretrievably deleted:',
|
||||
'The following links will be set as public:': 'The following links will be set as public:',
|
||||
'The following links will be set as private:': 'The following links will be set as private:',
|
||||
'Are you sure to delete {count} link{plural}?': 'Are you sure to delete {count} link{plural}?',
|
||||
'Are you sure to set {count} link{plural} public?': 'Are you sure to set {count} link{plural} public?',
|
||||
'Are you sure to set {count} link{plural} private?': 'Are you sure to set {count} link{plural} private?',
|
||||
'DELETE {count} LINK{plural}': 'DELETE {count} LINK{plural}',
|
||||
'SET {count} LINK{plural} PUBLIC': 'SET {count} LINK{plural} PUBLIC',
|
||||
'SET {count} LINK{plural} PRIVATE': 'SET {count} LINK{plural} PRIVATE',
|
||||
'Untitled link': 'Untitled link',
|
||||
'Missing security token. Please refresh the page and try again.': 'Missing security token. Please refresh the page and try again.',
|
||||
'Processing...': 'Processing...',
|
||||
'Current thumbnail': 'Current thumbnail',
|
||||
'Thumbnail update done!': 'Thumbnail update done!',
|
||||
'Error updating thumbnail #{index}': 'Error updating thumbnail #{index}',
|
||||
'No description available.': 'No description available.',
|
||||
'Generating QR Code...': 'Generating QR Code...',
|
||||
'Failed to generate QR Code': 'Failed to generate QR Code',
|
||||
'QR Code': 'QR Code',
|
||||
'Unable to load bookmark edit form.': 'Unable to load bookmark edit form.',
|
||||
'Bookmark edit form not found.': 'Bookmark edit form not found.',
|
||||
'Unable to save bookmark tags.': 'Unable to save bookmark tags.',
|
||||
'Delete tag {tag}': 'Delete tag {tag}',
|
||||
'Delete': 'Delete',
|
||||
'Read Later': 'Read Later',
|
||||
'Remove from Read It Later': 'Remove from Read It Later',
|
||||
'Add to Read It Later': 'Add to Read It Later',
|
||||
'Failed to toggle readitlater tag.': 'Failed to toggle readitlater tag.',
|
||||
'Play media': 'Play media',
|
||||
'Open Link': 'Open Link',
|
||||
'LIVE': 'LIVE',
|
||||
'Error loading media': 'Error loading media',
|
||||
'No media': 'No media',
|
||||
'Stop & Close': 'Stop & Close',
|
||||
'Play/Pause': 'Play/Pause',
|
||||
'Mute/Unmute': 'Mute/Unmute',
|
||||
// custom_views.js
|
||||
'Default': 'Default',
|
||||
'Red': 'Red',
|
||||
'Orange': 'Orange',
|
||||
'Yellow': 'Yellow',
|
||||
'Green': 'Green',
|
||||
'Mint': 'Mint',
|
||||
'Light blue': 'Light blue',
|
||||
'Blue': 'Blue',
|
||||
'Purple': 'Purple',
|
||||
'Pink': 'Pink',
|
||||
'Beige': 'Beige',
|
||||
'Gray': 'Gray',
|
||||
'Custom': 'Custom',
|
||||
'None': 'None',
|
||||
'Glass': 'Glass',
|
||||
'Vignette': 'Vignette',
|
||||
'Lined': 'Lined',
|
||||
'Grid': 'Grid',
|
||||
'Noise': 'Noise',
|
||||
'Dots': 'Dots',
|
||||
'Stripes': 'Stripes',
|
||||
'Auto': 'Auto',
|
||||
'Light': 'Light',
|
||||
'Dark': 'Dark',
|
||||
'White': 'White',
|
||||
'Black': 'Black',
|
||||
'My images & colors': 'My images & colors',
|
||||
'Solid color': 'Solid color',
|
||||
'Custom color': 'Custom color',
|
||||
'Custom font color': 'Custom font color',
|
||||
'Close': 'Close',
|
||||
'Reset': 'Reset',
|
||||
'Search my images… (filter by color or filter)': 'Search my images… (filter by color or filter)',
|
||||
'Clear search': 'Clear search',
|
||||
'Color': 'Color',
|
||||
'Edit': 'Edit',
|
||||
'Create a list': 'Create a list',
|
||||
'List title': 'List title',
|
||||
'Mark as completed': 'Mark as completed',
|
||||
'List item': 'List item',
|
||||
'Create a note': 'Create a note',
|
||||
'Title': 'Title',
|
||||
'Heading 1': 'Heading 1',
|
||||
'Heading 2': 'Heading 2',
|
||||
'Heading 3': 'Heading 3',
|
||||
'Text': 'Text',
|
||||
'Bold': 'Bold',
|
||||
'Italic': 'Italic',
|
||||
'Underline': 'Underline',
|
||||
'Strikethrough': 'Strikethrough',
|
||||
'Inline code': 'Inline code',
|
||||
'Bulleted list': 'Bulleted list',
|
||||
'Numbered list': 'Numbered list',
|
||||
'Checklist': 'Checklist',
|
||||
'Quote': 'Quote',
|
||||
'Link': 'Link',
|
||||
'Image': 'Image',
|
||||
'Code block': 'Code block',
|
||||
'Clear formatting': 'Clear formatting',
|
||||
'Formatting options': 'Formatting options',
|
||||
'Reminder': 'Reminder',
|
||||
'Collaborator': 'Collaborator',
|
||||
'Archive': 'Archive',
|
||||
'Unarchive': 'Unarchive',
|
||||
'Pin': 'Pin',
|
||||
'Unpin': 'Unpin',
|
||||
'Untitled': 'Untitled',
|
||||
'Delete this task?': 'Delete this task?',
|
||||
'Delete this note?': 'Delete this note?',
|
||||
'Write your note in Markdown...': 'Write your note in Markdown...',
|
||||
'Click to edit': 'Click to edit',
|
||||
'Move': 'Move',
|
||||
'Error initializing view.': 'Error initializing view.',
|
||||
'Error loading backgrounds.': 'Error loading backgrounds.',
|
||||
'Could not find edit form.': 'Could not find edit form.',
|
||||
'Failed to update tags.': 'Failed to update tags.',
|
||||
'Delete request failed.': 'Delete request failed.',
|
||||
'Invalid delete URL.': 'Invalid delete URL.',
|
||||
'Error removing tag.': 'Error removing tag.',
|
||||
'Error creating todo.': 'Error creating todo.',
|
||||
'Error deleting.': 'Error deleting.',
|
||||
'Error saving todo.': 'Error saving todo.',
|
||||
'Error creating checklist note.': 'Error creating checklist note.',
|
||||
'Error creating note.': 'Error creating note.',
|
||||
'Error saving note.': 'Error saving note.',
|
||||
'Error archiving note.': 'Error archiving note.',
|
||||
'Error unarchiving note.': 'Error unarchiving note.',
|
||||
'Failed to save color.': 'Failed to save color.',
|
||||
'Failed to save filter.': 'Failed to save filter.',
|
||||
'Failed to save background.': 'Failed to save background.',
|
||||
'Failed to save font color.': 'Failed to save font color.',
|
||||
'Failed to save custom color.': 'Failed to save custom color.',
|
||||
'Please refresh the page.': 'Please refresh the page.',
|
||||
'Create a task...': 'Create a task...',
|
||||
'Add list item': 'Add list item',
|
||||
'Create a note...': 'Create a note...',
|
||||
'Archived notes': 'Archived notes',
|
||||
'Unpin': 'Unpin',
|
||||
'Coffee': 'Coffee',
|
||||
'Forest': 'Forest',
|
||||
'Vegetables': 'Vegetables',
|
||||
'Ocean': 'Ocean',
|
||||
'January': 'January',
|
||||
'February': 'February',
|
||||
'March': 'March',
|
||||
'April': 'April',
|
||||
'May': 'May',
|
||||
'June': 'June',
|
||||
'July': 'July',
|
||||
'August': 'August',
|
||||
'September': 'September',
|
||||
'October': 'October',
|
||||
'November': 'November',
|
||||
'December': 'December',
|
||||
'Mon': 'Mon',
|
||||
'Tue': 'Tue',
|
||||
'Wed': 'Wed',
|
||||
'Thu': 'Thu',
|
||||
'Fri': 'Fri',
|
||||
'Sat': 'Sat',
|
||||
'Sun': 'Sun'
|
||||
};
|
||||
|
||||
function t(key, vars) {
|
||||
var str = translations[key] || key;
|
||||
if (vars) {
|
||||
Object.keys(vars).forEach(function (name) {
|
||||
str = str.split('{' + name + '}').join(String(vars[name]));
|
||||
});
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
root.ShaarliProI18n = { t: t, translations: translations };
|
||||
}(window));
|
||||
@@ -0,0 +1,66 @@
|
||||
// =====================================================================
|
||||
// Accessibility helpers for Shaarli Pro modals and overlays.
|
||||
// =====================================================================
|
||||
(function (root) {
|
||||
'use strict';
|
||||
|
||||
const FOCUSABLE_SELECTORS = [
|
||||
'a[href]',
|
||||
'button:not([disabled])',
|
||||
'input:not([disabled])',
|
||||
'select:not([disabled])',
|
||||
'textarea:not([disabled])',
|
||||
'[tabindex]:not([tabindex="-1"])'
|
||||
].join(', ');
|
||||
|
||||
/**
|
||||
* Return a focusable element inside a container.
|
||||
*/
|
||||
function getFocusable(container, first) {
|
||||
if (!container) return null;
|
||||
const elements = Array.prototype.slice.call(container.querySelectorAll(FOCUSABLE_SELECTORS));
|
||||
return first ? elements[0] : elements[elements.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Move focus to the first focusable element inside a container.
|
||||
*/
|
||||
function focusFirst(container) {
|
||||
const el = getFocusable(container, true);
|
||||
if (el) el.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep keyboard focus inside a container while it is open.
|
||||
* Returns a cleanup function that removes the listener.
|
||||
*/
|
||||
function trapFocus(container) {
|
||||
if (!container) return function () {};
|
||||
|
||||
function handleKey(e) {
|
||||
if (e.key !== 'Tab') return;
|
||||
const first = getFocusable(container, true);
|
||||
const last = getFocusable(container, false);
|
||||
if (!first || !last) return;
|
||||
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
|
||||
container.addEventListener('keydown', handleKey);
|
||||
return function () {
|
||||
container.removeEventListener('keydown', handleKey);
|
||||
};
|
||||
}
|
||||
|
||||
root.ShaarliProA11y = {
|
||||
focusFirst: focusFirst,
|
||||
trapFocus: trapFocus,
|
||||
getFocusable: getFocusable
|
||||
};
|
||||
}(window));
|
||||
@@ -0,0 +1,275 @@
|
||||
// =====================================================================
|
||||
// Bulk actions (multi-select) for Shaarli Pro.
|
||||
// =====================================================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
'use strict';
|
||||
|
||||
const i18n = window.ShaarliProI18n || { t: function (k) { return k; } };
|
||||
|
||||
const selectModeBtn = document.getElementById('select-mode-btn');
|
||||
const bulkActionsBar = document.getElementById('bulk-actions-bar');
|
||||
const bulkCount = document.getElementById('bulk-count');
|
||||
const bulkSelectAll = document.getElementById('bulk-select-all');
|
||||
const bulkCancel = document.getElementById('bulk-cancel');
|
||||
const bulkDelete = document.getElementById('bulk-delete');
|
||||
const bulkPublic = document.getElementById('bulk-public');
|
||||
const bulkPrivate = document.getElementById('bulk-private');
|
||||
const bulkModal = document.getElementById('bulk-confirm-modal');
|
||||
const bulkModalTitle = document.getElementById('bulk-modal-title');
|
||||
const bulkModalSubtitle = document.getElementById('bulk-modal-subtitle');
|
||||
const bulkModalList = document.getElementById('bulk-modal-list');
|
||||
const bulkModalConfirm = document.getElementById('bulk-modal-confirm');
|
||||
const bulkModalCancel = document.getElementById('bulk-modal-cancel');
|
||||
const bulkModalClose = document.getElementById('bulk-modal-close');
|
||||
|
||||
let selectionMode = false;
|
||||
let selectedIds = new Set();
|
||||
let pendingBulkAction = null;
|
||||
let bulkFocusCleanup = null;
|
||||
const a11y = window.ShaarliProA11y || { focusFirst: function () {}, trapFocus: function () { return function () {}; } };
|
||||
|
||||
const BULK_ACTION_COPY = {
|
||||
delete: {
|
||||
title: (count) => i18n.t('Are you sure to delete {count} link{plural}?', { count: count, plural: count > 1 ? 's' : '' }),
|
||||
subtitle: i18n.t('The following links will be irretrievably deleted:'),
|
||||
confirm: (count) => i18n.t('DELETE {count} LINK{plural}', { count: count, plural: count > 1 ? 'S' : '' }).toUpperCase(),
|
||||
confirmClass: 'bulk-confirm-delete'
|
||||
},
|
||||
public: {
|
||||
title: (count) => i18n.t('Are you sure to set {count} link{plural} public?', { count: count, plural: count > 1 ? 's' : '' }),
|
||||
subtitle: i18n.t('The following links will be set as public:'),
|
||||
confirm: (count) => i18n.t('SET {count} LINK{plural} PUBLIC', { count: count, plural: count > 1 ? 'S' : '' }).toUpperCase(),
|
||||
confirmClass: 'bulk-confirm-public'
|
||||
},
|
||||
private: {
|
||||
title: (count) => i18n.t('Are you sure to set {count} link{plural} private?', { count: count, plural: count > 1 ? 's' : '' }),
|
||||
subtitle: i18n.t('The following links will be set as private:'),
|
||||
confirm: (count) => i18n.t('SET {count} LINK{plural} PRIVATE', { count: count, plural: count > 1 ? 'S' : '' }).toUpperCase(),
|
||||
confirmClass: 'bulk-confirm-private'
|
||||
}
|
||||
};
|
||||
|
||||
function updateBulkUI() {
|
||||
if (bulkCount) {
|
||||
bulkCount.textContent = selectedIds.size;
|
||||
}
|
||||
|
||||
document.querySelectorAll('.link-outer').forEach(card => {
|
||||
const id = card.dataset.id;
|
||||
if (selectedIds.has(id)) {
|
||||
card.classList.add('selected');
|
||||
} else {
|
||||
card.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.link-checkbox').forEach(cb => {
|
||||
cb.checked = selectedIds.has(cb.dataset.id);
|
||||
});
|
||||
}
|
||||
|
||||
function enterSelectionMode() {
|
||||
selectionMode = true;
|
||||
document.body.classList.add('selection-mode');
|
||||
bulkActionsBar?.classList.add('show');
|
||||
selectModeBtn?.classList.add('active');
|
||||
}
|
||||
|
||||
function exitSelectionMode() {
|
||||
selectionMode = false;
|
||||
selectedIds.clear();
|
||||
document.body.classList.remove('selection-mode');
|
||||
bulkActionsBar?.classList.remove('show');
|
||||
selectModeBtn?.classList.remove('active');
|
||||
updateBulkUI();
|
||||
}
|
||||
|
||||
function toggleSelection(id) {
|
||||
if (selectedIds.has(id)) {
|
||||
selectedIds.delete(id);
|
||||
} else {
|
||||
selectedIds.add(id);
|
||||
}
|
||||
|
||||
if (selectedIds.size > 0 && !selectionMode) {
|
||||
enterSelectionMode();
|
||||
} else if (selectedIds.size === 0 && selectionMode) {
|
||||
exitSelectionMode();
|
||||
}
|
||||
|
||||
updateBulkUI();
|
||||
}
|
||||
|
||||
selectModeBtn?.addEventListener('click', () => {
|
||||
if (selectionMode) {
|
||||
exitSelectionMode();
|
||||
} else {
|
||||
enterSelectionMode();
|
||||
}
|
||||
});
|
||||
|
||||
bulkCancel?.addEventListener('click', exitSelectionMode);
|
||||
|
||||
bulkSelectAll?.addEventListener('click', () => {
|
||||
document.querySelectorAll('.link-outer').forEach(card => {
|
||||
if (card.dataset.id) {
|
||||
selectedIds.add(card.dataset.id);
|
||||
}
|
||||
});
|
||||
if (!selectionMode && selectedIds.size > 0) {
|
||||
enterSelectionMode();
|
||||
}
|
||||
updateBulkUI();
|
||||
});
|
||||
|
||||
document.addEventListener('change', (e) => {
|
||||
if (e.target.classList.contains('link-checkbox')) {
|
||||
toggleSelection(e.target.dataset.id);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!selectionMode) return;
|
||||
|
||||
const card = e.target.closest('.link-outer');
|
||||
if (card && card.dataset.id) {
|
||||
if (e.target.closest('.link-actions') ||
|
||||
e.target.closest('.link-hover-actions') ||
|
||||
e.target.tagName === 'A' ||
|
||||
e.target.tagName === 'INPUT' ||
|
||||
e.target.closest('label')) {
|
||||
return;
|
||||
}
|
||||
toggleSelection(card.dataset.id);
|
||||
}
|
||||
});
|
||||
|
||||
function getSelectedIds() {
|
||||
return Array.from(selectedIds);
|
||||
}
|
||||
|
||||
function populateBulkModalList(ids) {
|
||||
if (!bulkModalList) return;
|
||||
bulkModalList.innerHTML = '';
|
||||
ids.forEach((id) => {
|
||||
const card = document.querySelector(`.link-outer[data-id="${id}"]`);
|
||||
const title = card?.querySelector('.link-title')?.textContent?.trim() ||
|
||||
card?.querySelector('.link-url')?.textContent?.trim() ||
|
||||
i18n.t('Untitled link');
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'bulk-modal-item';
|
||||
|
||||
const idSpan = document.createElement('span');
|
||||
idSpan.className = 'bulk-modal-item-id';
|
||||
idSpan.textContent = `#${id}`;
|
||||
|
||||
const titleSpan = document.createElement('span');
|
||||
titleSpan.className = 'bulk-modal-item-title';
|
||||
titleSpan.textContent = title;
|
||||
|
||||
item.appendChild(idSpan);
|
||||
item.appendChild(titleSpan);
|
||||
bulkModalList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function openBulkModal(actionType) {
|
||||
if (!bulkModal || selectedIds.size === 0) return;
|
||||
const meta = BULK_ACTION_COPY[actionType];
|
||||
if (!meta) return;
|
||||
|
||||
const ids = getSelectedIds();
|
||||
pendingBulkAction = actionType;
|
||||
|
||||
if (bulkModalTitle) {
|
||||
bulkModalTitle.textContent = meta.title(ids.length);
|
||||
}
|
||||
if (bulkModalSubtitle) {
|
||||
bulkModalSubtitle.textContent = meta.subtitle;
|
||||
}
|
||||
if (bulkModalConfirm) {
|
||||
bulkModalConfirm.textContent = meta.confirm(ids.length);
|
||||
bulkModalConfirm.className = `bulk-modal-btn bulk-modal-confirm ${meta.confirmClass}`;
|
||||
}
|
||||
|
||||
populateBulkModalList(ids);
|
||||
|
||||
bulkModal.classList.add('show');
|
||||
bulkModal.setAttribute('aria-hidden', 'false');
|
||||
document.body.style.overflow = 'hidden';
|
||||
a11y.focusFirst(bulkModal);
|
||||
bulkFocusCleanup = a11y.trapFocus(bulkModal);
|
||||
}
|
||||
|
||||
function closeBulkModal() {
|
||||
if (!bulkModal) return;
|
||||
pendingBulkAction = null;
|
||||
bulkModal.classList.remove('show');
|
||||
bulkModal.setAttribute('aria-hidden', 'true');
|
||||
document.body.style.overflow = '';
|
||||
if (typeof bulkFocusCleanup === 'function') {
|
||||
bulkFocusCleanup();
|
||||
bulkFocusCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
function buildIdsParam(ids) {
|
||||
return ids.map(id => encodeURIComponent(id)).join('+');
|
||||
}
|
||||
|
||||
function executeBulkAction() {
|
||||
if (!pendingBulkAction || selectedIds.size === 0) return;
|
||||
const ids = getSelectedIds();
|
||||
const token = (window.shaarli?.token || '').trim() ||
|
||||
document.querySelector('input[name="token"]')?.value || '';
|
||||
|
||||
if (!token) {
|
||||
alert(i18n.t('Missing security token. Please refresh the page and try again.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const basePath = window.shaarli?.basePath || '';
|
||||
const idsParam = buildIdsParam(ids);
|
||||
let targetUrl = '';
|
||||
|
||||
if (pendingBulkAction === 'delete') {
|
||||
targetUrl = `${basePath}/admin/shaare/delete?id=${idsParam}&token=${encodeURIComponent(token)}`;
|
||||
} else {
|
||||
const visibility = pendingBulkAction === 'public' ? 'public' : 'private';
|
||||
targetUrl = `${basePath}/admin/shaare/visibility?token=${encodeURIComponent(token)}&newVisibility=${visibility}&id=${idsParam}`;
|
||||
}
|
||||
|
||||
closeBulkModal();
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
|
||||
bulkDelete?.addEventListener('click', () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
openBulkModal('delete');
|
||||
});
|
||||
|
||||
bulkPublic?.addEventListener('click', () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
openBulkModal('public');
|
||||
});
|
||||
|
||||
bulkPrivate?.addEventListener('click', () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
openBulkModal('private');
|
||||
});
|
||||
|
||||
bulkModalConfirm?.addEventListener('click', executeBulkAction);
|
||||
bulkModalCancel?.addEventListener('click', closeBulkModal);
|
||||
bulkModalClose?.addEventListener('click', closeBulkModal);
|
||||
bulkModal?.addEventListener('click', (event) => {
|
||||
if (event.target === bulkModal) {
|
||||
closeBulkModal();
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && bulkModal?.classList.contains('show')) {
|
||||
closeBulkModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
// =====================================================================
|
||||
// Navigation helpers for Shaarli Pro:
|
||||
// - Add note/todo shortcut buttons in the sidebar.
|
||||
// - Mobile sidebar toggle.
|
||||
// =====================================================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
'use strict';
|
||||
|
||||
// ===== Add Note Button Handler (Android convention) =====
|
||||
const addNoteBtn = document.querySelector('.sidebar-add-note');
|
||||
if (addNoteBtn) {
|
||||
addNoteBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (!window.ShaarItRules) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[shaarit] ShaarItRules not available, cannot generate note URL');
|
||||
return;
|
||||
}
|
||||
const basePath = addNoteBtn.getAttribute('data-base-path') || '';
|
||||
const noteUrl = window.ShaarItRules.generateNoteUrl();
|
||||
const redirectUrl = `${basePath}/admin/shaare?post=${encodeURIComponent(noteUrl)}&tags=note`;
|
||||
window.location.href = redirectUrl;
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Add Todo Button Handler (Android convention) =====
|
||||
const addTodoBtn = document.querySelector('.sidebar-add-todo');
|
||||
if (addTodoBtn) {
|
||||
addTodoBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (!window.ShaarItRules) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[shaarit] ShaarItRules not available, cannot generate todo URL');
|
||||
return;
|
||||
}
|
||||
const basePath = addTodoBtn.getAttribute('data-base-path') || '';
|
||||
const todoUrl = window.ShaarItRules.generateTodoUrl();
|
||||
const redirectUrl = `${basePath}/admin/shaare?post=${encodeURIComponent(todoUrl)}&tags=todo&title=%E2%9C%85%20`;
|
||||
window.location.href = redirectUrl;
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Mobile Sidebar Toggle =====
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
|
||||
function isMobileViewport() {
|
||||
return window.matchMedia('(max-width: 1100px)').matches;
|
||||
}
|
||||
|
||||
function openSidebar() {
|
||||
if (!sidebar || !sidebarOverlay) return;
|
||||
sidebar.classList.add('show');
|
||||
sidebarOverlay.classList.add('show');
|
||||
mobileMenuBtn?.setAttribute('aria-expanded', 'true');
|
||||
sidebarOverlay.setAttribute('aria-hidden', 'false');
|
||||
if (isMobileViewport()) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
if (!sidebar || !sidebarOverlay) return;
|
||||
sidebar.classList.remove('show');
|
||||
sidebarOverlay.classList.remove('show');
|
||||
mobileMenuBtn?.setAttribute('aria-expanded', 'false');
|
||||
sidebarOverlay.setAttribute('aria-hidden', 'true');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
if (sidebar?.classList.contains('show')) {
|
||||
closeSidebar();
|
||||
return;
|
||||
}
|
||||
openSidebar();
|
||||
}
|
||||
|
||||
mobileMenuBtn?.addEventListener('click', toggleSidebar);
|
||||
sidebarOverlay?.addEventListener('click', closeSidebar);
|
||||
sidebar?.querySelectorAll('a').forEach((link) => {
|
||||
link.addEventListener('click', () => {
|
||||
if (isMobileViewport()) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (!isMobileViewport()) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,766 @@
|
||||
// =====================================================================
|
||||
// Search overlay and filter panel for Shaarli Pro.
|
||||
// =====================================================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
'use strict';
|
||||
|
||||
const utils = window.ShaarliProUtils || {
|
||||
escapeHtml: function (text) {
|
||||
if (text === null || text === undefined) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = String(text);
|
||||
return div.innerHTML;
|
||||
},
|
||||
isTagHidden: function () { return false; },
|
||||
filterHiddenTags: function (tags) { return tags || []; }
|
||||
};
|
||||
const i18n = window.ShaarliProI18n || { t: function (k) { return k; } };
|
||||
const a11y = window.ShaarliProA11y || { focusFirst: function () {}, trapFocus: function () { return function () {}; } };
|
||||
let searchFocusCleanup = null;
|
||||
let filterFocusCleanup = null;
|
||||
|
||||
// ===== Search Overlay (Spotlight Style) =====
|
||||
try {
|
||||
const searchOverlay = document.getElementById('search-overlay');
|
||||
const searchToggleBtns = document.querySelectorAll('[data-search-toggle]');
|
||||
const searchModalInput = document.getElementById('search-modal-input');
|
||||
const searchResults = document.getElementById('search-results');
|
||||
const searchTagsBtn = document.getElementById('search-tags-btn');
|
||||
const searchAllBtn = document.getElementById('search-all-btn');
|
||||
const searchNotesBtn = document.getElementById('search-notes-btn');
|
||||
const searchForm = document.getElementById('search-form');
|
||||
|
||||
let searchMode = 'search'; // 'search' | 'tags' | 'notes'
|
||||
let selectedIndex = -1;
|
||||
let searchTimeout = null;
|
||||
let cachedBookmarks = null;
|
||||
let cachedTags = null;
|
||||
|
||||
|
||||
// Highlight matching text with <mark> tags
|
||||
function highlightMatch(text, query) {
|
||||
if (!query || query.length === 0) return utils.escapeHtml(text);
|
||||
|
||||
// Escape HTML first to prevent XSS
|
||||
const escapedText = utils.escapeHtml(text);
|
||||
// Escape special regex characters in query
|
||||
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`(${escapedQuery})`, 'gi');
|
||||
return escapedText.replace(regex, '<mark>$1</mark>');
|
||||
}
|
||||
|
||||
// Fuzzy search - matches substring anywhere in text
|
||||
function fuzzyMatch(text, query) {
|
||||
if (!query || query.length === 0) return true;
|
||||
return text.toLowerCase().includes(query.toLowerCase());
|
||||
}
|
||||
|
||||
// Fetch all unique tags from the page (excluding hidden tags)
|
||||
function fetchTags() {
|
||||
if (cachedTags) return cachedTags;
|
||||
try {
|
||||
const tagsSet = new Set();
|
||||
|
||||
const addTag = (value) => {
|
||||
if (typeof value !== 'string') return;
|
||||
const cleaned = value.trim();
|
||||
if (!cleaned || cleaned.includes('•')) return;
|
||||
// Skip hidden tags
|
||||
if (utils.isTagHidden(cleaned)) return;
|
||||
tagsSet.add(cleaned);
|
||||
};
|
||||
|
||||
// Prefer explicit tag data attributes to avoid polluted text values (e.g. remove buttons)
|
||||
document.querySelectorAll('.link-tag[data-tag], .tag-item[data-tag], [data-tag-name]').forEach((el) => {
|
||||
addTag(el.getAttribute('data-tag') || el.getAttribute('data-tag-name'));
|
||||
});
|
||||
|
||||
// Fallback selectors with reliable text content
|
||||
document.querySelectorAll('.link-tag-link, .tag-link, .cloud-tag').forEach((el) => {
|
||||
addTag(el.textContent);
|
||||
});
|
||||
|
||||
// Convert to array and sort alphabetically
|
||||
cachedTags = Array.from(tagsSet).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
return cachedTags;
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch tags:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch bookmarks for live search
|
||||
async function fetchBookmarks() {
|
||||
if (cachedBookmarks) return cachedBookmarks;
|
||||
|
||||
try {
|
||||
// Try to get bookmarks from page (if already loaded)
|
||||
const linkElements = document.querySelectorAll('.link-outer');
|
||||
if (linkElements.length > 0) {
|
||||
cachedBookmarks = Array.from(linkElements).map(el => ({
|
||||
id: el.dataset.id,
|
||||
isNote: (el.querySelector('.link-title')?.getAttribute('title') || '').toLowerCase().includes('note'),
|
||||
title: el.querySelector('.link-title a')?.textContent || el.querySelector('.link-title')?.textContent || '',
|
||||
url: el.querySelector('.link-url a')?.href || el.querySelector('.link-title a')?.href || '',
|
||||
// Filter out hidden tags
|
||||
tags: utils.filterHiddenTags(
|
||||
Array.from(el.querySelectorAll('.link-tag[data-tag]')).map(t => (t.getAttribute('data-tag') || '').trim()).filter(Boolean)
|
||||
),
|
||||
description: el.querySelector('.link-description')?.textContent || ''
|
||||
}));
|
||||
return cachedBookmarks;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch bookmarks:', e);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getSearchHint(mode) {
|
||||
var i18n = window.ShaarliProI18n || { t: function (k) { return k; } };
|
||||
if (mode === 'tags') {
|
||||
return i18n.t('Search in tags...');
|
||||
}
|
||||
if (mode === 'notes') {
|
||||
return i18n.t('Search in notes...');
|
||||
}
|
||||
return i18n.t('Search in bookmarks and notes...');
|
||||
}
|
||||
|
||||
// Render search results (tags, global bookmarks, or notes)
|
||||
function renderResults(results, query, mode = 'search') {
|
||||
if (!searchResults) return;
|
||||
|
||||
if (results.length === 0) {
|
||||
if (query && query.length > 0) {
|
||||
const scopeLabel = mode === 'tags' ? 'tags' : (mode === 'notes' ? 'notes' : 'bookmarks/notes');
|
||||
searchResults.innerHTML = `
|
||||
<div class="search-no-results">
|
||||
No ${scopeLabel} found for "<strong>${utils.escapeHtml(query)}</strong>"
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
searchResults.innerHTML = `
|
||||
<div class="search-results-empty">
|
||||
<span class="search-results-hint">${getSearchHint(mode)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let html;
|
||||
if (mode === 'tags') {
|
||||
// Render tags
|
||||
html = results.slice(0, 15).map((tag, index) => {
|
||||
const highlightedTag = highlightMatch(utils.escapeHtml(tag), query);
|
||||
return `
|
||||
<div class="search-result-item${index === selectedIndex ? ' selected' : ''}"
|
||||
data-index="${index}"
|
||||
data-tag="${utils.escapeHtml(tag)}">
|
||||
<div class="search-result-item-content">
|
||||
<i class="mdi mdi-tag-outline search-result-icon"></i>
|
||||
<span class="search-result-text">${highlightedTag}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
} else {
|
||||
// Render bookmarks or notes
|
||||
html = results.slice(0, 12).map((item, index) => {
|
||||
const highlightedTitle = highlightMatch(utils.escapeHtml(item.title), query);
|
||||
const highlightedDescription = item.description ? highlightMatch(utils.escapeHtml(item.description), query) : '';
|
||||
const typeLabel = item.isNote ? 'note' : 'bookmark';
|
||||
const typeIcon = item.isNote ? 'mdi-note-text-outline' : 'mdi-bookmark-outline';
|
||||
|
||||
const highlightedTags = (item.tags || []).slice(0, 4).map((tag) => {
|
||||
const tagValue = utils.escapeHtml(tag);
|
||||
return `<span class="search-result-tag">${highlightMatch(tagValue, query)}</span>`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="search-result-item${index === selectedIndex ? ' selected' : ''}"
|
||||
data-index="${index}"
|
||||
data-url="${utils.escapeHtml(item.url)}"
|
||||
data-id="${item.id}">
|
||||
<div class="search-result-item-content">
|
||||
<i class="mdi ${typeIcon} search-result-icon"></i>
|
||||
<div class="search-result-main">
|
||||
<span class="search-result-text">${highlightedTitle}</span>
|
||||
<span class="search-result-kind">${typeLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
${highlightedDescription ? `<div class="search-result-sub">${highlightedDescription}</div>` : ''}
|
||||
${highlightedTags ? `<div class="search-result-tags">${highlightedTags}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
searchResults.innerHTML = html;
|
||||
|
||||
// Add click handlers to results
|
||||
searchResults.querySelectorAll('.search-result-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
if (mode === 'tags') {
|
||||
const tag = item.dataset.tag;
|
||||
if (tag) {
|
||||
// Navigate to tag search
|
||||
window.location.href = shaarli.basePath + '/?searchtags=' + encodeURIComponent(tag);
|
||||
}
|
||||
} else {
|
||||
const url = item.dataset.url;
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Perform live search
|
||||
async function performSearch(query) {
|
||||
const normalizedQuery = (query || '').trim();
|
||||
|
||||
if (searchMode === 'tags') {
|
||||
const tags = fetchTags();
|
||||
if (!normalizedQuery) {
|
||||
renderResults(tags.slice(0, 15), '', 'tags');
|
||||
return;
|
||||
}
|
||||
|
||||
const tagResults = tags.filter(tag => fuzzyMatch(tag, normalizedQuery));
|
||||
renderResults(tagResults, normalizedQuery, 'tags');
|
||||
return;
|
||||
}
|
||||
|
||||
const bookmarks = await fetchBookmarks();
|
||||
const searchPool = searchMode === 'notes' ? bookmarks.filter(item => item.isNote) : bookmarks;
|
||||
|
||||
if (!normalizedQuery) {
|
||||
renderResults(searchPool.slice(0, 12), '', searchMode);
|
||||
return;
|
||||
}
|
||||
|
||||
const results = searchPool.filter((item) => {
|
||||
const haystack = [
|
||||
item.title,
|
||||
item.url,
|
||||
item.description,
|
||||
(item.tags || []).join(' ')
|
||||
].join(' ').toLowerCase();
|
||||
|
||||
return haystack.includes(normalizedQuery.toLowerCase());
|
||||
});
|
||||
|
||||
renderResults(results, normalizedQuery, searchMode);
|
||||
}
|
||||
|
||||
// Update selected result
|
||||
function updateSelection(newIndex) {
|
||||
const items = searchResults?.querySelectorAll('.search-result-item');
|
||||
if (!items || items.length === 0) return;
|
||||
|
||||
// Clamp index
|
||||
if (newIndex < 0) newIndex = items.length - 1;
|
||||
if (newIndex >= items.length) newIndex = 0;
|
||||
|
||||
selectedIndex = newIndex;
|
||||
|
||||
items.forEach((item, index) => {
|
||||
item.classList.toggle('selected', index === selectedIndex);
|
||||
});
|
||||
|
||||
// Scroll into view
|
||||
items[selectedIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
// Navigate to selected result
|
||||
function navigateToSelected() {
|
||||
const selected = searchResults?.querySelector('.search-result-item.selected');
|
||||
if (selected) {
|
||||
// Check if it's a tag
|
||||
const tag = selected.dataset.tag;
|
||||
if (tag) {
|
||||
window.location.href = shaarli.basePath + '/?searchtags=' + encodeURIComponent(tag);
|
||||
return true;
|
||||
}
|
||||
// Otherwise check for URL
|
||||
const url = selected.dataset.url;
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function openSearch(mode = 'search') {
|
||||
searchOverlay?.classList.add('show');
|
||||
selectedIndex = -1;
|
||||
setTimeout(() => {
|
||||
setSearchMode(mode);
|
||||
searchModalInput?.focus();
|
||||
searchFocusCleanup = a11y.trapFocus(searchOverlay);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
searchOverlay?.classList.remove('show');
|
||||
selectedIndex = -1;
|
||||
if (typeof searchFocusCleanup === 'function') {
|
||||
searchFocusCleanup();
|
||||
searchFocusCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
function submitSearchByMode() {
|
||||
const basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : '';
|
||||
const query = (searchModalInput?.value || '').trim();
|
||||
|
||||
if (selectedIndex >= 0 && navigateToSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
if (searchMode === 'tags') {
|
||||
window.location.href = `${basePath}/tags/cloud`;
|
||||
return;
|
||||
}
|
||||
window.location.href = `${basePath}/`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchMode === 'tags') {
|
||||
window.location.href = `${basePath}/?searchtags=${encodeURIComponent(query)}`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchMode === 'notes') {
|
||||
window.location.href = `${basePath}/?searchterm=${encodeURIComponent(query)}&searchtags=note`;
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = `${basePath}/?searchterm=${encodeURIComponent(query)}`;
|
||||
}
|
||||
|
||||
// Toggle search mode (tags vs search)
|
||||
function setSearchMode(mode) {
|
||||
if (!['search', 'tags', 'notes'].includes(mode)) {
|
||||
mode = 'search';
|
||||
}
|
||||
|
||||
searchMode = mode;
|
||||
|
||||
searchAllBtn?.classList.toggle('active', mode === 'search');
|
||||
searchTagsBtn?.classList.toggle('active', mode === 'tags');
|
||||
searchNotesBtn?.classList.toggle('active', mode === 'notes');
|
||||
|
||||
searchAllBtn?.setAttribute('aria-pressed', String(mode === 'search'));
|
||||
searchTagsBtn?.setAttribute('aria-pressed', String(mode === 'tags'));
|
||||
searchNotesBtn?.setAttribute('aria-pressed', String(mode === 'notes'));
|
||||
|
||||
searchAllBtn?.setAttribute('aria-selected', String(mode === 'search'));
|
||||
searchTagsBtn?.setAttribute('aria-selected', String(mode === 'tags'));
|
||||
searchNotesBtn?.setAttribute('aria-selected', String(mode === 'notes'));
|
||||
|
||||
if (searchModalInput) {
|
||||
searchModalInput.name = mode === 'tags' ? 'searchtags' : 'searchterm';
|
||||
searchModalInput.placeholder = getSearchHint(mode);
|
||||
// Re-run search with new mode
|
||||
performSearch(searchModalInput.value);
|
||||
}
|
||||
}
|
||||
|
||||
searchToggleBtns.forEach((btn) => {
|
||||
btn.addEventListener('click', () => openSearch('search'));
|
||||
});
|
||||
|
||||
// Close search on overlay click
|
||||
searchOverlay?.addEventListener('click', (e) => {
|
||||
if (e.target === searchOverlay) {
|
||||
closeSearch();
|
||||
}
|
||||
});
|
||||
|
||||
// Search mode toggle buttons
|
||||
searchTagsBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
setSearchMode('tags');
|
||||
});
|
||||
|
||||
searchAllBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
setSearchMode('search');
|
||||
});
|
||||
|
||||
searchNotesBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
setSearchMode('notes');
|
||||
});
|
||||
|
||||
searchForm?.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
submitSearchByMode();
|
||||
});
|
||||
|
||||
// Live search on input
|
||||
searchModalInput?.addEventListener('input', (e) => {
|
||||
const query = e.target.value;
|
||||
|
||||
// Debounce search
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
selectedIndex = -1;
|
||||
performSearch(query);
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const isSearchOpen = searchOverlay?.classList.contains('show');
|
||||
const target = e.target;
|
||||
const isTyping = Boolean(
|
||||
target && (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.tagName === 'SELECT' ||
|
||||
target.isContentEditable ||
|
||||
(typeof target.closest === 'function' && target.closest('.toastui-editor-defaultUI, .toastui-editor-main, .toastui-editor-contents, .CodeMirror'))
|
||||
)
|
||||
);
|
||||
|
||||
// Handle ESC - always close search/filter
|
||||
if (e.key === 'Escape') {
|
||||
if (isSearchOpen) {
|
||||
closeSearch();
|
||||
e.preventDefault();
|
||||
}
|
||||
closeFilterPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// If search is open, handle navigation
|
||||
if (isSearchOpen && e.target === searchModalInput) {
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
updateSelection(selectedIndex + 1);
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
updateSelection(selectedIndex - 1);
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
submitSearchByMode();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// S to open search (when not typing)
|
||||
if (!isTyping && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key === 's' || e.key === 'S')) {
|
||||
e.preventDefault();
|
||||
openSearch('search');
|
||||
}
|
||||
|
||||
// T to open search directly in tags mode (when not typing)
|
||||
if (!isTyping && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key === 't' || e.key === 'T')) {
|
||||
e.preventDefault();
|
||||
openSearch('tags');
|
||||
}
|
||||
|
||||
// N to open search directly in notes mode (when not typing)
|
||||
if (!isTyping && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key === 'n' || e.key === 'N')) {
|
||||
e.preventDefault();
|
||||
openSearch('notes');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Search overlay initialization failed:', error);
|
||||
}
|
||||
|
||||
// ===== Filter Panel =====
|
||||
const filterToggleBtn = document.getElementById('filter-toggle-btn');
|
||||
const filterPanel = document.getElementById('filter-panel');
|
||||
const filterCloseBtn = document.getElementById('filter-close-btn');
|
||||
const filterPrivate = document.getElementById('filter-private');
|
||||
const filterPublic = document.getElementById('filter-public');
|
||||
const filterUntagged = document.getElementById('filter-untagged');
|
||||
|
||||
function toggleFilterPanel() {
|
||||
if (!filterPanel) return;
|
||||
const isOpen = filterPanel.classList.toggle('show');
|
||||
filterPanel.setAttribute('aria-hidden', String(!isOpen));
|
||||
filterToggleBtn?.setAttribute('aria-expanded', String(isOpen));
|
||||
if (isOpen) {
|
||||
a11y.focusFirst(filterPanel);
|
||||
filterFocusCleanup = a11y.trapFocus(filterPanel);
|
||||
} else {
|
||||
if (typeof filterFocusCleanup === 'function') {
|
||||
filterFocusCleanup();
|
||||
filterFocusCleanup = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeFilterPanel() {
|
||||
if (!filterPanel) return;
|
||||
filterPanel.classList.remove('show');
|
||||
filterPanel.setAttribute('aria-hidden', 'true');
|
||||
filterToggleBtn?.setAttribute('aria-expanded', 'false');
|
||||
if (typeof filterFocusCleanup === 'function') {
|
||||
filterFocusCleanup();
|
||||
filterFocusCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
filterToggleBtn?.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggleFilterPanel();
|
||||
});
|
||||
|
||||
filterCloseBtn?.addEventListener('click', closeFilterPanel);
|
||||
|
||||
// Close filter when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (filterPanel?.classList.contains('show')) {
|
||||
const clickedToggle = filterToggleBtn && (e.target === filterToggleBtn || filterToggleBtn.contains(e.target));
|
||||
if (!filterPanel.contains(e.target) && !clickedToggle) {
|
||||
closeFilterPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle filter toggle switches
|
||||
// Logic:
|
||||
// - Visibility: all (neither checked), private (only private checked), public (only public checked)
|
||||
// - Untagged: can be combined with any visibility
|
||||
function applyFilters() {
|
||||
const isPrivate = filterPrivate?.checked || false;
|
||||
const isPublic = filterPublic?.checked || false;
|
||||
const isUntagged = filterUntagged?.checked || false;
|
||||
|
||||
let basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : '';
|
||||
|
||||
// Get current filter state from server-side rendered variables
|
||||
const currentVisibility = (typeof shaarli !== 'undefined' && shaarli.visibility) ? shaarli.visibility : '';
|
||||
const currentUntagged = (typeof shaarli !== 'undefined' && shaarli.untaggedonly) || false;
|
||||
|
||||
let url = basePath + '/';
|
||||
|
||||
// Determine desired visibility
|
||||
let desiredVisibility = 'all';
|
||||
if (isPrivate) {
|
||||
desiredVisibility = 'private';
|
||||
} else if (isPublic) {
|
||||
desiredVisibility = 'public';
|
||||
}
|
||||
|
||||
// Build URL based on desired state
|
||||
if (desiredVisibility === 'private') {
|
||||
url = basePath + '/admin/visibility/private';
|
||||
} else if (desiredVisibility === 'public') {
|
||||
url = basePath + '/admin/visibility/public';
|
||||
} else {
|
||||
// visibility = all - need to clear visibility if it was set
|
||||
if (currentVisibility && currentVisibility !== '') {
|
||||
url = basePath + '/admin/visibility/all';
|
||||
} else if (isUntagged !== currentUntagged) {
|
||||
// Untagged state changed - /untagged-only works as toggle
|
||||
url = basePath + '/untagged-only';
|
||||
} else {
|
||||
url = basePath + '/';
|
||||
}
|
||||
}
|
||||
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
// Clear all filters - go back to showing all bookmarks
|
||||
function clearAllFilters() {
|
||||
const basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : '';
|
||||
const currentVisibility = (typeof shaarli !== 'undefined' && shaarli.visibility) ? shaarli.visibility : '';
|
||||
const currentUntagged = (typeof shaarli !== 'undefined' && shaarli.untaggedonly) || false;
|
||||
|
||||
// If visibility is set, clear it first
|
||||
if (currentVisibility && currentVisibility !== '') {
|
||||
window.location.href = basePath + '/admin/visibility/all';
|
||||
} else if (currentUntagged) {
|
||||
// Toggle untagged off using /untagged-only
|
||||
window.location.href = basePath + '/untagged-only';
|
||||
} else {
|
||||
window.location.href = basePath + '/';
|
||||
}
|
||||
}
|
||||
|
||||
// Private and public are mutually exclusive
|
||||
filterPrivate?.addEventListener('change', (e) => {
|
||||
if (e.target.checked && filterPublic) {
|
||||
filterPublic.checked = false;
|
||||
}
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
filterPublic?.addEventListener('change', (e) => {
|
||||
if (e.target.checked && filterPrivate) {
|
||||
filterPrivate.checked = false;
|
||||
}
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
filterUntagged?.addEventListener('change', () => {
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// Initialize filter states from server-side variables (set in includes.html)
|
||||
(function initFilterStates() {
|
||||
// Get filter state from server-side rendered variables
|
||||
const visibility = (typeof shaarli !== 'undefined' && shaarli.visibility) ? shaarli.visibility : '';
|
||||
const untaggedonly = (typeof shaarli !== 'undefined' && shaarli.untaggedonly) ? shaarli.untaggedonly : false;
|
||||
|
||||
// Detect active filters from server variables
|
||||
let isPrivateActive = visibility === 'private';
|
||||
let isPublicActive = visibility === 'public';
|
||||
let isUntaggedActive = untaggedonly === true;
|
||||
|
||||
// Set checkbox states
|
||||
if (filterPrivate && isPrivateActive) {
|
||||
filterPrivate.checked = true;
|
||||
}
|
||||
if (filterPublic && isPublicActive) {
|
||||
filterPublic.checked = true;
|
||||
}
|
||||
if (filterUntagged && isUntaggedActive) {
|
||||
filterUntagged.checked = true;
|
||||
}
|
||||
|
||||
const hasActiveFilter = isPrivateActive || isPublicActive || isUntaggedActive;
|
||||
|
||||
// Add/update filter indicator badge on the filter button
|
||||
if (filterToggleBtn && hasActiveFilter) {
|
||||
filterToggleBtn.classList.add('has-active-filter');
|
||||
|
||||
// Add badge indicator if not exists
|
||||
if (!filterToggleBtn.querySelector('.filter-badge')) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'filter-badge';
|
||||
filterToggleBtn.appendChild(badge);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and display the filter info banner
|
||||
if (hasActiveFilter && !document.getElementById('filter-info-banner')) {
|
||||
// Get result count from page - try multiple sources
|
||||
let resultCount = 0;
|
||||
const pagingStats = document.querySelector('.paging-stats strong:last-child');
|
||||
const linkCount = document.querySelectorAll('.link-outer').length;
|
||||
|
||||
if (pagingStats) {
|
||||
resultCount = parseInt(pagingStats.textContent) || 0;
|
||||
} else if (linkCount > 0) {
|
||||
resultCount = linkCount;
|
||||
}
|
||||
|
||||
// Check if no results (empty state)
|
||||
const emptyState = document.querySelector('.empty-state');
|
||||
const isEmptyResults = emptyState !== null || resultCount === 0;
|
||||
|
||||
// Build the message like the examples:
|
||||
// "5 results without any tag"
|
||||
// "5 results with status private without any tag"
|
||||
// "1 result with status public without any tag"
|
||||
let statusPart = '';
|
||||
let untaggedPart = '';
|
||||
|
||||
if (isPrivateActive) {
|
||||
statusPart = '<strong>private</strong>';
|
||||
} else if (isPublicActive) {
|
||||
statusPart = '<strong>public</strong>';
|
||||
}
|
||||
|
||||
if (isUntaggedActive) {
|
||||
untaggedPart = '<strong>without any tag</strong>';
|
||||
}
|
||||
|
||||
// Build the message
|
||||
let message = '';
|
||||
var i18n = window.ShaarliProI18n || { t: function (k) { return k; } };
|
||||
if (isEmptyResults) {
|
||||
message = i18n.t('Nothing found.');
|
||||
} else {
|
||||
const resultWord = resultCount === 1 ? i18n.t('result') : i18n.t('results');
|
||||
const withStatus = i18n.t('with status');
|
||||
if (statusPart && untaggedPart) {
|
||||
message = `${resultCount} ${resultWord} ${withStatus} ${statusPart} ${untaggedPart}`;
|
||||
} else if (statusPart) {
|
||||
message = `${resultCount} ${resultWord} ${withStatus} ${statusPart}`;
|
||||
} else if (untaggedPart) {
|
||||
message = `${resultCount} ${resultWord} ${untaggedPart}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (message) {
|
||||
// Get base path safely
|
||||
const basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : '';
|
||||
|
||||
const banner = document.createElement('div');
|
||||
banner.id = 'filter-info-banner';
|
||||
banner.className = 'filter-info-banner' + (isEmptyResults ? ' empty-results' : '');
|
||||
banner.innerHTML = `
|
||||
<div class="filter-info-content">
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
<button type="button" class="filter-clear-btn" id="filter-clear-btn" title="${i18n.t('Clear filters')}">
|
||||
<i class="mdi mdi-close"></i>
|
||||
<span>${i18n.t('Clear')}</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
// Try to find the best insertion point
|
||||
const contentToolbar = document.querySelector('.content-toolbar');
|
||||
const linklist = document.getElementById('linklist');
|
||||
const emptyStateDiv = document.querySelector('.empty-state');
|
||||
|
||||
// Insert after the content-toolbar (pagination)
|
||||
if (contentToolbar && contentToolbar.parentNode) {
|
||||
contentToolbar.parentNode.insertBefore(banner, contentToolbar.nextSibling);
|
||||
} else if (emptyStateDiv && emptyStateDiv.parentNode) {
|
||||
emptyStateDiv.parentNode.insertBefore(banner, emptyStateDiv);
|
||||
} else if (linklist) {
|
||||
linklist.insertBefore(banner, linklist.firstChild);
|
||||
} else {
|
||||
}
|
||||
|
||||
// Add click handler to clear button
|
||||
document.getElementById('filter-clear-btn')?.addEventListener('click', () => {
|
||||
// Clear based on what was active
|
||||
if (isPrivateActive || isPublicActive) {
|
||||
window.location.href = basePath + '/admin/visibility/all';
|
||||
} else if (isUntaggedActive) {
|
||||
// Toggle untagged off
|
||||
window.location.href = basePath + '/untagged-only';
|
||||
} else {
|
||||
window.location.href = basePath + '/';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Handle links per page options
|
||||
|
||||
// Handle custom value form submission
|
||||
const filterInput = document.querySelector('.filter-input');
|
||||
if (filterInput) {
|
||||
filterInput.closest('form')?.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const value = filterInput.value;
|
||||
if (value && value > 0) {
|
||||
window.location.href = shaarli.basePath + '/links-per-page?nb=' + value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
// =====================================================================
|
||||
// Theme configuration and dark/light toggle for Shaarli Pro.
|
||||
// Keeps theme/mode preferences client-side without creating bookmarks.
|
||||
// Used by the dark/light switch AND the theme picker (tools.html).
|
||||
// =====================================================================
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const STORAGE_KEY = 'shaarit_themes_config';
|
||||
|
||||
function readConfig() {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
const parsed = JSON.parse(raw);
|
||||
return (parsed && typeof parsed === 'object') ? parsed : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeConfig(config) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge partial updates into the persisted config and save to localStorage.
|
||||
function save(partial) {
|
||||
const base = readConfig() || {};
|
||||
const config = Object.assign(
|
||||
{ version: 2, themes: [], default: 'DEFAULT', mode: 'dark' },
|
||||
base,
|
||||
partial || {}
|
||||
);
|
||||
const ok = writeConfig(config);
|
||||
return Promise.resolve({ ok, config });
|
||||
}
|
||||
|
||||
// Reads the current persisted config from localStorage (no write).
|
||||
function load() {
|
||||
return Promise.resolve(readConfig());
|
||||
}
|
||||
|
||||
// Kept for backward compatibility with older callers.
|
||||
function findCandidates() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
window.ShaaritThemeConfig = { save, load, findCandidates };
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
'use strict';
|
||||
|
||||
const themeCheckbox = document.getElementById('theme-toggle-checkbox');
|
||||
const themeIconLight = document.getElementById('theme-icon-light');
|
||||
const themeLabelSpan = document.querySelector('.theme-toggle-label span');
|
||||
const i18n = window.ShaarliProI18n || { t: function (k) { return k; } };
|
||||
|
||||
function checkThemeRestrictions() {
|
||||
const themeId = localStorage.getItem('shaarit_theme_id') || 'DEFAULT';
|
||||
const darkOnlyThemes = ['LINEAR', 'SPOTIFY', 'NOTION', 'DISCORD', 'DRACULA', 'ONE_DARK_PRO', 'TOKYO_NIGHT', 'NORD', 'NIGHT_OWL', 'ANTHRACITE', 'CYBERPUNK', 'NAVY_ELEGANCE', 'EARTHY'];
|
||||
return darkOnlyThemes.indexOf(themeId) !== -1;
|
||||
}
|
||||
|
||||
function updateTheme(theme) {
|
||||
// Enforce dark mode if the theme only supports dark
|
||||
if (checkThemeRestrictions()) {
|
||||
theme = 'dark';
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
if (themeCheckbox) {
|
||||
themeCheckbox.checked = theme === 'dark';
|
||||
// Disable the checkbox if restricted to dark mode
|
||||
themeCheckbox.disabled = checkThemeRestrictions();
|
||||
themeCheckbox.closest('.theme-switch').style.opacity = themeCheckbox.disabled ? '0.5' : '1';
|
||||
}
|
||||
if (themeIconLight) {
|
||||
themeIconLight.className = theme === 'dark' ? 'mdi mdi-weather-night' : 'mdi mdi-weather-sunny';
|
||||
}
|
||||
if (themeLabelSpan) {
|
||||
themeLabelSpan.textContent = theme === 'dark' ? i18n.t('Dark Mode') : i18n.t('Light Mode');
|
||||
}
|
||||
}
|
||||
|
||||
async function syncModeToBookmark(mode) {
|
||||
try {
|
||||
if (!window.ShaaritThemeConfig) return;
|
||||
await window.ShaaritThemeConfig.save({ mode });
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[shaarit] Failed to sync mode to bookmark', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle themeChanged event fired by tools.html
|
||||
window.addEventListener('themeChanged', () => {
|
||||
updateTheme(localStorage.getItem('theme') || 'dark');
|
||||
});
|
||||
|
||||
// Init Theme
|
||||
const savedTheme = localStorage.getItem('theme') ||
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
updateTheme(savedTheme);
|
||||
|
||||
if (themeCheckbox) {
|
||||
themeCheckbox.addEventListener('change', () => {
|
||||
if (checkThemeRestrictions()) return; // Extra safety
|
||||
const next = themeCheckbox.checked ? 'dark' : 'light';
|
||||
updateTheme(next);
|
||||
// Sync mode to bookmark in background
|
||||
syncModeToBookmark(next);
|
||||
});
|
||||
}
|
||||
});
|
||||
+74
-1342
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user