feat: centraliser gestion bookmark config thème dans helper ShaaritThemeConfig partagé pour éliminer duplications lors changements mode/thème, implémenter recherche multi-tags (shaarit_config + legacy themes) avec déduplication par ID, merge intelligent config existant avec partial updates, sérialisation saves concurrents via inflight promise, cleanup automatique duplicates avec deleteBookmark(), et refactoring syncModeToBookmark/syncThemeToBookmark pour déléguer à ShaaritThemeConfig.save
This commit is contained in:
parent
f3147bb67b
commit
f90f8146ce
@ -1,3 +1,139 @@
|
||||
// =====================================================================
|
||||
// Shared theme-config bookmark helper
|
||||
// Maintains a single private bookmark titled "themes" tagged "shaarit_config"
|
||||
// Used by the dark/light switch (script.js) AND the theme picker (tools.html)
|
||||
// Prevents creating a new bookmark on every theme change (duplicate bug)
|
||||
// =====================================================================
|
||||
(function () {
|
||||
const CONFIG_TAG = 'shaarit_config';
|
||||
const LEGACY_TAG = 'themes';
|
||||
const CONFIG_TITLE = 'themes';
|
||||
const CONFIG_URL = 'https://shaarit.app/config/themes';
|
||||
|
||||
let inflight = null; // serialize concurrent saves
|
||||
|
||||
function getBasePath() {
|
||||
return (window.shaarli && window.shaarli.basePath) || '';
|
||||
}
|
||||
|
||||
async function fetchDoc(url) {
|
||||
const res = await fetch(url, { credentials: 'same-origin' });
|
||||
const text = await res.text();
|
||||
return new DOMParser().parseFromString(text, 'text/html');
|
||||
}
|
||||
|
||||
// Returns array of { id, editUrl } for every bookmark matching the config,
|
||||
// searched across the new tag and the legacy "themes" tag (dedup by id).
|
||||
async function findCandidates() {
|
||||
const basePath = getBasePath();
|
||||
const byId = new Map();
|
||||
for (const tag of [CONFIG_TAG, LEGACY_TAG]) {
|
||||
let doc;
|
||||
try {
|
||||
doc = await fetchDoc(basePath + '/?searchtags=' + encodeURIComponent(tag));
|
||||
} catch (e) { continue; }
|
||||
doc.querySelectorAll('.link-outer[data-id]').forEach(el => {
|
||||
const id = el.getAttribute('data-id');
|
||||
if (!id || byId.has(id)) return;
|
||||
byId.set(id, { id, editUrl: basePath + '/admin/shaare/' + id });
|
||||
});
|
||||
}
|
||||
// Sort by numeric id asc so "primary" is deterministic (oldest kept)
|
||||
return [...byId.values()].sort((a, b) => Number(a.id) - Number(b.id));
|
||||
}
|
||||
|
||||
// Reads existing config JSON from the edit page's textarea (raw value,
|
||||
// not affected by the markdown plugin).
|
||||
async function readExistingConfig(editUrl) {
|
||||
try {
|
||||
const doc = await fetchDoc(editUrl);
|
||||
const ta = doc.querySelector('textarea[name="lf_description"]');
|
||||
if (!ta) return null;
|
||||
const raw = (ta.textContent || '').trim();
|
||||
if (!raw) return null;
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getTokenFrom(url) {
|
||||
const doc = await fetchDoc(url);
|
||||
const tokenEl = doc.querySelector('input[name="token"]');
|
||||
const idEl = doc.querySelector('input[name="lf_id"]');
|
||||
return {
|
||||
token: tokenEl ? tokenEl.value : '',
|
||||
lfId: idEl ? idEl.value : '',
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteBookmark(id, token) {
|
||||
if (!id || !token) return;
|
||||
const basePath = getBasePath();
|
||||
try {
|
||||
await fetch(basePath + '/admin/shaare/delete?id=' + encodeURIComponent(id)
|
||||
+ '&token=' + encodeURIComponent(token), { credentials: 'same-origin' });
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
// Merge partial updates into the persisted config and save to the single
|
||||
// bookmark. Deletes any duplicates left over from previous buggy saves.
|
||||
async function save(partial) {
|
||||
// Serialize so rapid toggles do not race and create duplicates.
|
||||
const run = async () => {
|
||||
const basePath = getBasePath();
|
||||
const candidates = await findCandidates();
|
||||
const primary = candidates[0] || null;
|
||||
const duplicates = candidates.slice(1);
|
||||
|
||||
// Build merged config
|
||||
let existing = null;
|
||||
if (primary) existing = await readExistingConfig(primary.editUrl);
|
||||
const base = existing && typeof existing === 'object' ? existing : {};
|
||||
const config = Object.assign(
|
||||
{ version: 2, themes: [], default: 'DEFAULT', mode: 'dark' },
|
||||
base,
|
||||
partial || {}
|
||||
);
|
||||
|
||||
// Get token + lf_id from the primary's edit page (or add page)
|
||||
const src = primary ? primary.editUrl : basePath + '/admin/add-shaare';
|
||||
const { token, lfId } = await getTokenFrom(src);
|
||||
if (!token) return { ok: false, reason: 'no-token' };
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('lf_title', CONFIG_TITLE);
|
||||
fd.append('lf_url', CONFIG_URL);
|
||||
fd.append('lf_tags', CONFIG_TAG);
|
||||
fd.append('lf_description', JSON.stringify(config));
|
||||
fd.append('lf_private', 'on');
|
||||
fd.append('save_edit', 'Save');
|
||||
fd.append('token', token);
|
||||
if (lfId) fd.append('lf_id', lfId);
|
||||
|
||||
await fetch(basePath + '/admin/shaare', {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
// Clean up any leftover duplicates (reuse the same token, Shaarli
|
||||
// accepts the session token for both edit and delete).
|
||||
for (const dup of duplicates) {
|
||||
await deleteBookmark(dup.id, token);
|
||||
}
|
||||
|
||||
return { ok: true, id: lfId || (primary && primary.id) || null, config };
|
||||
};
|
||||
|
||||
const p = (inflight ? inflight.catch(() => {}) : Promise.resolve()).then(run);
|
||||
inflight = p.finally(() => { if (inflight === p) inflight = null; });
|
||||
return inflight;
|
||||
}
|
||||
|
||||
window.ShaaritThemeConfig = { save, findCandidates };
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ===== Add Note Button Handler (Android convention) =====
|
||||
const addNoteBtn = document.querySelector('.sidebar-add-note');
|
||||
@ -67,68 +203,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
async function syncModeToBookmark(mode) {
|
||||
try {
|
||||
if (!window.shaarli || !window.shaarli.basePath) return;
|
||||
|
||||
// Fetch current config bookmark
|
||||
const res = await fetch(window.shaarli.basePath + '/?searchtags=themes');
|
||||
const text = await res.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, 'text/html');
|
||||
|
||||
let config = { version: 2, themes: [], default: 'DEFAULT', mode: mode };
|
||||
let editUrl = null;
|
||||
|
||||
// Find existing config
|
||||
const linkCard = doc.querySelector('.linklist-item');
|
||||
if (linkCard) {
|
||||
const descEl = linkCard.querySelector('.link-description p');
|
||||
if (descEl) {
|
||||
try {
|
||||
const parsed = JSON.parse(descEl.textContent.trim());
|
||||
// Preserve existing data
|
||||
if (parsed.themes) config.themes = parsed.themes;
|
||||
if (parsed.default) config.default = parsed.default;
|
||||
// Update mode
|
||||
config.mode = mode;
|
||||
} catch(e) {}
|
||||
}
|
||||
const editBtn = linkCard.querySelector('.link-action-edit');
|
||||
if (editBtn) editUrl = editBtn.getAttribute('href');
|
||||
}
|
||||
|
||||
// Prepare form data
|
||||
const formData = new FormData();
|
||||
formData.append('lf_title', 'themes');
|
||||
formData.append('lf_url', 'https://shaarit.app/config/themes');
|
||||
formData.append('lf_tags', 'themes');
|
||||
formData.append('lf_description', JSON.stringify(config));
|
||||
formData.append('lf_private', 'on');
|
||||
formData.append('save_edit', 'Save');
|
||||
|
||||
let token = '';
|
||||
|
||||
if (editUrl) {
|
||||
// Update existing
|
||||
const editRes = await fetch(editUrl);
|
||||
const editDoc = parser.parseFromString(await editRes.text(), 'text/html');
|
||||
const tokenEl = editDoc.querySelector('input[name="token"]');
|
||||
const idEl = editDoc.querySelector('input[name="lf_id"]');
|
||||
if (tokenEl) token = tokenEl.value;
|
||||
if (idEl) formData.append('lf_id', idEl.value);
|
||||
} else {
|
||||
// Create new
|
||||
const addRes = await fetch(window.shaarli.basePath + '/admin/add-shaare');
|
||||
const addDoc = parser.parseFromString(await addRes.text(), 'text/html');
|
||||
const tokenEl = addDoc.querySelector('input[name="token"]');
|
||||
if (tokenEl) token = tokenEl.value;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
formData.append('token', token);
|
||||
await fetch(window.shaarli.basePath + '/admin/shaare', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
if (!window.ShaaritThemeConfig) return;
|
||||
const result = await window.ShaaritThemeConfig.save({ mode });
|
||||
if (result && result.ok) {
|
||||
console.log('[shaarit] Mode synced to bookmark:', mode);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@ -654,77 +654,17 @@
|
||||
|
||||
async function syncThemeToBookmark(themeId) {
|
||||
try {
|
||||
// We fetch the current config bookmark if any
|
||||
const res = await fetch(shaarli.basePath + '/?searchtags=themes');
|
||||
const text = await res.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, 'text/html');
|
||||
|
||||
// Get current mode from localStorage
|
||||
const currentMode = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
|
||||
let config = {
|
||||
version: 2,
|
||||
if (!window.ShaaritThemeConfig) {
|
||||
console.warn('ShaaritThemeConfig helper not available');
|
||||
return;
|
||||
}
|
||||
const currentMode = localStorage.getItem('theme')
|
||||
|| (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
await window.ShaaritThemeConfig.save({
|
||||
themes: THEMES,
|
||||
default: themeId,
|
||||
mode: currentMode
|
||||
};
|
||||
let editUrl = null;
|
||||
|
||||
// Find existing config
|
||||
const linkCard = doc.querySelector('.linklist-item');
|
||||
if (linkCard) {
|
||||
const descEl = linkCard.querySelector('.link-description p');
|
||||
if (descEl) {
|
||||
try {
|
||||
const parsed = JSON.parse(descEl.textContent.trim());
|
||||
// Preserve existing themes list if it exists and is valid
|
||||
if (parsed.themes && Array.isArray(parsed.themes)) {
|
||||
config.themes = parsed.themes;
|
||||
}
|
||||
// Update default and mode
|
||||
config.default = themeId;
|
||||
config.mode = currentMode;
|
||||
} catch(e) {}
|
||||
}
|
||||
const editBtn = linkCard.querySelector('.link-action-edit');
|
||||
if (editBtn) editUrl = editBtn.getAttribute('href');
|
||||
}
|
||||
|
||||
// Prepare form data
|
||||
const formData = new FormData();
|
||||
formData.append('lf_title', 'themes');
|
||||
formData.append('lf_url', 'https://shaarit.app/config/themes');
|
||||
formData.append('lf_tags', 'themes');
|
||||
formData.append('lf_description', JSON.stringify(config));
|
||||
formData.append('lf_private', 'on'); // Private bookmark
|
||||
formData.append('save_edit', 'Save');
|
||||
|
||||
let token = '';
|
||||
|
||||
if (editUrl) {
|
||||
// Update existing
|
||||
const editRes = await fetch(editUrl);
|
||||
const editDoc = parser.parseFromString(await editRes.text(), 'text/html');
|
||||
const tokenEl = editDoc.querySelector('input[name="token"]');
|
||||
const idEl = editDoc.querySelector('input[name="lf_id"]');
|
||||
if (tokenEl) token = tokenEl.value;
|
||||
if (idEl) formData.append('lf_id', idEl.value);
|
||||
} else {
|
||||
// Create new
|
||||
const addRes = await fetch(shaarli.basePath + '/admin/add-shaare');
|
||||
const addDoc = parser.parseFromString(await addRes.text(), 'text/html');
|
||||
const tokenEl = addDoc.querySelector('input[name="token"]');
|
||||
if (tokenEl) token = tokenEl.value;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
formData.append('token', token);
|
||||
await fetch(shaarli.basePath + '/admin/shaare', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
}
|
||||
mode: currentMode,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to sync theme to bookmark', e);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user