feat: implémenter modal confirmation actions bulk avec preview liste liens (ID + titre), styles dédiés (max-width 520px, list scrollable 320px, boutons colorés par action delete/public/private), synchronisation mode thème vers bookmark via fetch/POST formulaire, refactoring actions bulk (delete/visibility) avec URLs GET params au lieu de forms POST, ajout token shaarli dans includes.html, correction tag config bookmark (shaarit_config→themes), et support fermeture modal ESC/overlay-click
This commit is contained in:
parent
1c469b2329
commit
bf1c2c6172
@ -797,6 +797,105 @@ input:checked+.theme-slider:before {
|
|||||||
margin-top: 1.75rem;
|
margin-top: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bulk-modal {
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-header p {
|
||||||
|
margin: 0.3rem 0 0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-list {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 0.65rem;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-item-id {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary);
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-item-title {
|
||||||
|
color: var(--text-main);
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-btn {
|
||||||
|
min-width: 140px;
|
||||||
|
padding: 0.75rem 1.2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: none;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.15s ease, opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-btn:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-cancel {
|
||||||
|
background: var(--bg-card-hover);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-confirm {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-confirm.bulk-confirm-delete {
|
||||||
|
background: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-confirm.bulk-confirm-public {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-confirm.bulk-confirm-private {
|
||||||
|
background: var(--info);
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-modal-btn:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@ -10,10 +10,22 @@
|
|||||||
<!-- Prevent dark mode flash (FOUC) - must run before CSS loads -->
|
<!-- Prevent dark mode flash (FOUC) - must run before CSS loads -->
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
|
// Try to load theme config from bookmark first, fallback to localStorage
|
||||||
var themeMode = localStorage.getItem('theme') ||
|
var themeMode = localStorage.getItem('theme') ||
|
||||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
var themeId = localStorage.getItem('shaarit_theme_id') || 'DEFAULT';
|
var themeId = localStorage.getItem('shaarit_theme_id') || 'DEFAULT';
|
||||||
|
|
||||||
|
// Try to fetch theme config from bookmark (async, but we'll use localStorage as fallback)
|
||||||
|
// This is a simplified check - the full sync happens in script.js
|
||||||
|
var bookmarkConfig = sessionStorage.getItem('themes_config');
|
||||||
|
if (bookmarkConfig) {
|
||||||
|
try {
|
||||||
|
var config = JSON.parse(bookmarkConfig);
|
||||||
|
if (config.default) themeId = config.default;
|
||||||
|
if (config.mode) themeMode = config.mode;
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
// Disable light mode for dark-only themes
|
// Disable light mode for dark-only themes
|
||||||
var darkOnlyThemes = ['LINEAR', 'SPOTIFY', 'NOTION', 'DISCORD', 'DRACULA', 'ONE_DARK_PRO', 'TOKYO_NIGHT', 'NORD', 'NIGHT_OWL', 'ANTHRACITE', 'CYBERPUNK', 'NAVY_ELEGANCE', 'EARTHY'];
|
var darkOnlyThemes = ['LINEAR', 'SPOTIFY', 'NOTION', 'DISCORD', 'DRACULA', 'ONE_DARK_PRO', 'TOKYO_NIGHT', 'NORD', 'NIGHT_OWL', 'ANTHRACITE', 'CYBERPUNK', 'NAVY_ELEGANCE', 'EARTHY'];
|
||||||
if (darkOnlyThemes.indexOf(themeId) !== -1) {
|
if (darkOnlyThemes.indexOf(themeId) !== -1) {
|
||||||
@ -74,6 +86,7 @@ var shaarli = {
|
|||||||
basePath: '{$base_path}',
|
basePath: '{$base_path}',
|
||||||
rootPath: '{$root_path}',
|
rootPath: '{$root_path}',
|
||||||
assetPath: '{$base_path}{$asset_path}',
|
assetPath: '{$base_path}{$asset_path}',
|
||||||
|
token: '{$token}',
|
||||||
isAuth: (function(){/*{if="$is_logged_in"}*/return true;/*{else}*/return false;/*{/if}*/})(),
|
isAuth: (function(){/*{if="$is_logged_in"}*/return true;/*{else}*/return false;/*{/if}*/})(),
|
||||||
pageName: '{$pageName}',
|
pageName: '{$pageName}',
|
||||||
visibility: '{$visibility}',
|
visibility: '{$visibility}',
|
||||||
|
|||||||
@ -65,6 +65,77 @@ 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
|
||||||
|
});
|
||||||
|
console.log('[shaarit] Mode synced to bookmark:', mode);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[shaarit] Failed to sync mode to bookmark', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle themeChanged event fired by tools.html
|
// Handle themeChanged event fired by tools.html
|
||||||
window.addEventListener('themeChanged', () => {
|
window.addEventListener('themeChanged', () => {
|
||||||
updateTheme(localStorage.getItem('theme') || 'dark');
|
updateTheme(localStorage.getItem('theme') || 'dark');
|
||||||
@ -80,6 +151,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (checkThemeRestrictions()) return; // Extra safety
|
if (checkThemeRestrictions()) return; // Extra safety
|
||||||
const next = themeCheckbox.checked ? 'dark' : 'light';
|
const next = themeCheckbox.checked ? 'dark' : 'light';
|
||||||
updateTheme(next);
|
updateTheme(next);
|
||||||
|
// Sync mode to bookmark in background
|
||||||
|
syncModeToBookmark(next);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1041,9 +1114,38 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const bulkDelete = document.getElementById('bulk-delete');
|
const bulkDelete = document.getElementById('bulk-delete');
|
||||||
const bulkPublic = document.getElementById('bulk-public');
|
const bulkPublic = document.getElementById('bulk-public');
|
||||||
const bulkPrivate = document.getElementById('bulk-private');
|
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 selectionMode = false;
|
||||||
let selectedIds = new Set();
|
let selectedIds = new Set();
|
||||||
|
let pendingBulkAction = null;
|
||||||
|
|
||||||
|
const BULK_ACTION_COPY = {
|
||||||
|
delete: {
|
||||||
|
title: (count) => `Are you sure to delete ${count} link${count > 1 ? 's' : ''}?`,
|
||||||
|
subtitle: 'The following links will be irretrievably deleted:',
|
||||||
|
confirm: (count) => `DELETE ${count} LINK${count > 1 ? 'S' : ''}`,
|
||||||
|
confirmClass: 'bulk-confirm-delete'
|
||||||
|
},
|
||||||
|
public: {
|
||||||
|
title: (count) => `Are you sure to set ${count} link${count > 1 ? 's' : ''} public?`,
|
||||||
|
subtitle: 'The following links will be set as public:',
|
||||||
|
confirm: (count) => `SET ${count} LINK${count > 1 ? 'S' : ''} PUBLIC`,
|
||||||
|
confirmClass: 'bulk-confirm-public'
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
title: (count) => `Are you sure to set ${count} link${count > 1 ? 's' : ''} private?`,
|
||||||
|
subtitle: 'The following links will be set as private:',
|
||||||
|
confirm: (count) => `SET ${count} LINK${count > 1 ? 'S' : ''} PRIVATE`,
|
||||||
|
confirmClass: 'bulk-confirm-private'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function updateBulkUI() {
|
function updateBulkUI() {
|
||||||
if (bulkCount) {
|
if (bulkCount) {
|
||||||
@ -1114,6 +1216,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
selectedIds.add(card.dataset.id);
|
selectedIds.add(card.dataset.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (!selectionMode && selectedIds.size > 0) {
|
||||||
|
enterSelectionMode();
|
||||||
|
}
|
||||||
updateBulkUI();
|
updateBulkUI();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1140,72 +1245,129 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bulk actions
|
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() ||
|
||||||
|
'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';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBulkModal() {
|
||||||
|
if (!bulkModal) return;
|
||||||
|
pendingBulkAction = null;
|
||||||
|
bulkModal.classList.remove('show');
|
||||||
|
bulkModal.setAttribute('aria-hidden', 'true');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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('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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk actions with confirmation modal
|
||||||
bulkDelete?.addEventListener('click', () => {
|
bulkDelete?.addEventListener('click', () => {
|
||||||
if (selectedIds.size === 0) return;
|
if (selectedIds.size === 0) return;
|
||||||
if (!confirm(`Delete ${selectedIds.size} bookmark(s)?`)) return;
|
openBulkModal('delete');
|
||||||
|
|
||||||
// Submit form with selected IDs
|
|
||||||
const form = document.createElement('form');
|
|
||||||
form.method = 'POST';
|
|
||||||
form.action = shaarli.basePath + '/admin/shaare/delete';
|
|
||||||
|
|
||||||
selectedIds.forEach(id => {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'hidden';
|
|
||||||
input.name = 'id[]';
|
|
||||||
input.value = id;
|
|
||||||
form.appendChild(input);
|
|
||||||
});
|
|
||||||
|
|
||||||
const tokenInput = document.createElement('input');
|
|
||||||
tokenInput.type = 'hidden';
|
|
||||||
tokenInput.name = 'token';
|
|
||||||
tokenInput.value = document.querySelector('input[name="token"]')?.value || '';
|
|
||||||
form.appendChild(tokenInput);
|
|
||||||
|
|
||||||
document.body.appendChild(form);
|
|
||||||
form.submit();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bulkPublic?.addEventListener('click', () => {
|
bulkPublic?.addEventListener('click', () => {
|
||||||
if (selectedIds.size === 0) return;
|
if (selectedIds.size === 0) return;
|
||||||
bulkVisibilityChange('public');
|
openBulkModal('public');
|
||||||
});
|
});
|
||||||
|
|
||||||
bulkPrivate?.addEventListener('click', () => {
|
bulkPrivate?.addEventListener('click', () => {
|
||||||
if (selectedIds.size === 0) return;
|
if (selectedIds.size === 0) return;
|
||||||
bulkVisibilityChange('private');
|
openBulkModal('private');
|
||||||
});
|
});
|
||||||
|
|
||||||
function bulkVisibilityChange(visibility) {
|
bulkModalConfirm?.addEventListener('click', executeBulkAction);
|
||||||
const form = document.createElement('form');
|
bulkModalCancel?.addEventListener('click', closeBulkModal);
|
||||||
form.method = 'POST';
|
bulkModalClose?.addEventListener('click', closeBulkModal);
|
||||||
form.action = shaarli.basePath + '/admin/shaare/visibility';
|
bulkModal?.addEventListener('click', (event) => {
|
||||||
|
if (event.target === bulkModal) {
|
||||||
selectedIds.forEach(id => {
|
closeBulkModal();
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'hidden';
|
|
||||||
input.name = 'id[]';
|
|
||||||
input.value = id;
|
|
||||||
form.appendChild(input);
|
|
||||||
});
|
|
||||||
|
|
||||||
const visInput = document.createElement('input');
|
|
||||||
visInput.type = 'hidden';
|
|
||||||
visInput.name = 'visibility';
|
|
||||||
visInput.value = visibility;
|
|
||||||
form.appendChild(visInput);
|
|
||||||
|
|
||||||
const tokenInput = document.createElement('input');
|
|
||||||
tokenInput.type = 'hidden';
|
|
||||||
tokenInput.name = 'token';
|
|
||||||
tokenInput.value = document.querySelector('input[name="token"]')?.value || '';
|
|
||||||
form.appendChild(tokenInput);
|
|
||||||
|
|
||||||
document.body.appendChild(form);
|
|
||||||
form.submit();
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape' && bulkModal?.classList.contains('show')) {
|
||||||
|
closeBulkModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ===== Thumbnail Update =====
|
// ===== Thumbnail Update =====
|
||||||
const thumbnailsPage = document.querySelector('.page-thumbnails');
|
const thumbnailsPage = document.querySelector('.page-thumbnails');
|
||||||
@ -3313,7 +3475,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Run sync after a slight delay to avoid blocking initial load
|
// Run sync after a slight delay to avoid blocking initial load
|
||||||
if (!window.shaarli || !window.shaarli.basePath) return;
|
if (!window.shaarli || !window.shaarli.basePath) return;
|
||||||
|
|
||||||
fetch(window.shaarli.basePath + '/?searchtags=shaarit_config')
|
fetch(window.shaarli.basePath + '/?searchtags=themes')
|
||||||
.then(res => res.text())
|
.then(res => res.text())
|
||||||
.then(text => {
|
.then(text => {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
@ -3324,23 +3486,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (descEl) {
|
if (descEl) {
|
||||||
try {
|
try {
|
||||||
const config = JSON.parse(descEl.textContent.trim());
|
const config = JSON.parse(descEl.textContent.trim());
|
||||||
const remoteThemeId = config.theme;
|
const remoteThemeId = config.default;
|
||||||
|
const remoteMode = config.mode;
|
||||||
const localThemeId = localStorage.getItem('shaarit_theme_id') || 'DEFAULT';
|
const localThemeId = localStorage.getItem('shaarit_theme_id') || 'DEFAULT';
|
||||||
|
const localMode = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
|
|
||||||
|
// Sync theme if different
|
||||||
if (remoteThemeId && remoteThemeId !== localThemeId) {
|
if (remoteThemeId && remoteThemeId !== localThemeId) {
|
||||||
localStorage.setItem('shaarit_theme_id', remoteThemeId);
|
localStorage.setItem('shaarit_theme_id', remoteThemeId);
|
||||||
document.documentElement.setAttribute('data-theme-id', remoteThemeId);
|
document.documentElement.setAttribute('data-theme-id', remoteThemeId);
|
||||||
|
console.log('[shaarit] Theme synced from bookmark:', remoteThemeId);
|
||||||
|
}
|
||||||
|
|
||||||
// Enforce light/dark constraints based on new theme
|
// Sync mode if different
|
||||||
|
if (remoteMode && remoteMode !== localMode) {
|
||||||
|
localStorage.setItem('theme', remoteMode);
|
||||||
|
document.documentElement.setAttribute('data-theme', remoteMode);
|
||||||
|
console.log('[shaarit] Mode synced from bookmark:', remoteMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce light/dark constraints based on synced theme
|
||||||
const darkOnlyThemes = ['LINEAR', 'SPOTIFY', 'NOTION', 'DISCORD', 'DRACULA', 'ONE_DARK_PRO', 'TOKYO_NIGHT', 'NORD', 'NIGHT_OWL', 'ANTHRACITE', 'CYBERPUNK', 'NAVY_ELEGANCE', 'EARTHY'];
|
const darkOnlyThemes = ['LINEAR', 'SPOTIFY', 'NOTION', 'DISCORD', 'DRACULA', 'ONE_DARK_PRO', 'TOKYO_NIGHT', 'NORD', 'NIGHT_OWL', 'ANTHRACITE', 'CYBERPUNK', 'NAVY_ELEGANCE', 'EARTHY'];
|
||||||
if (darkOnlyThemes.indexOf(remoteThemeId) !== -1) {
|
const syncedThemeId = remoteThemeId || localThemeId;
|
||||||
|
if (darkOnlyThemes.indexOf(syncedThemeId) !== -1) {
|
||||||
localStorage.setItem('theme', 'dark');
|
localStorage.setItem('theme', 'dark');
|
||||||
document.documentElement.setAttribute('data-theme', 'dark');
|
document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
}
|
}
|
||||||
|
|
||||||
window.dispatchEvent(new Event('themeChanged'));
|
window.dispatchEvent(new Event('themeChanged'));
|
||||||
console.log('[shaarit] Theme synced from bookmark:', remoteThemeId);
|
|
||||||
}
|
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bulk confirmation modal -->
|
||||||
|
<div class="modal-overlay" id="bulk-confirm-modal" aria-hidden="true">
|
||||||
|
<div class="modal-content bulk-modal" role="dialog" aria-modal="true" aria-labelledby="bulk-modal-title">
|
||||||
|
<button class="modal-close" id="bulk-modal-close" type="button" aria-label="Close confirmation">×</button>
|
||||||
|
<div class="bulk-modal-header">
|
||||||
|
<h3 id="bulk-modal-title"></h3>
|
||||||
|
<p id="bulk-modal-subtitle"></p>
|
||||||
|
</div>
|
||||||
|
<div class="bulk-modal-list" id="bulk-modal-list"></div>
|
||||||
|
<div class="bulk-modal-actions">
|
||||||
|
<button class="bulk-modal-btn bulk-modal-cancel" id="bulk-modal-cancel" type="button">Cancel</button>
|
||||||
|
<button class="bulk-modal-btn bulk-modal-confirm" id="bulk-modal-confirm" type="button"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Persistent Media Player -->
|
<!-- Persistent Media Player -->
|
||||||
<div class="media-player-bar" id="media-player-bar">
|
<div class="media-player-bar" id="media-player-bar">
|
||||||
<audio id="media-player-audio" preload="metadata"></audio>
|
<audio id="media-player-audio" preload="metadata"></audio>
|
||||||
|
|||||||
@ -655,12 +655,20 @@
|
|||||||
async function syncThemeToBookmark(themeId) {
|
async function syncThemeToBookmark(themeId) {
|
||||||
try {
|
try {
|
||||||
// We fetch the current config bookmark if any
|
// We fetch the current config bookmark if any
|
||||||
const res = await fetch(shaarli.basePath + '/?searchtags=shaarit_config');
|
const res = await fetch(shaarli.basePath + '/?searchtags=themes');
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const doc = parser.parseFromString(text, 'text/html');
|
const doc = parser.parseFromString(text, 'text/html');
|
||||||
|
|
||||||
let config = { version: 1, theme: themeId, collections: [] };
|
// Get current mode from localStorage
|
||||||
|
const currentMode = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
version: 2,
|
||||||
|
themes: THEMES,
|
||||||
|
default: themeId,
|
||||||
|
mode: currentMode
|
||||||
|
};
|
||||||
let editUrl = null;
|
let editUrl = null;
|
||||||
|
|
||||||
// Find existing config
|
// Find existing config
|
||||||
@ -670,7 +678,13 @@
|
|||||||
if (descEl) {
|
if (descEl) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(descEl.textContent.trim());
|
const parsed = JSON.parse(descEl.textContent.trim());
|
||||||
config = Object.assign({}, parsed, { theme: themeId });
|
// 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) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
const editBtn = linkCard.querySelector('.link-action-edit');
|
const editBtn = linkCard.querySelector('.link-action-edit');
|
||||||
@ -679,10 +693,11 @@
|
|||||||
|
|
||||||
// Prepare form data
|
// Prepare form data
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('lf_title', 'collections');
|
formData.append('lf_title', 'themes');
|
||||||
formData.append('lf_url', 'https://shaarit.app/config/collections');
|
formData.append('lf_url', 'https://shaarit.app/config/themes');
|
||||||
formData.append('lf_tags', 'shaarit_config');
|
formData.append('lf_tags', 'themes');
|
||||||
formData.append('lf_description', JSON.stringify(config));
|
formData.append('lf_description', JSON.stringify(config));
|
||||||
|
formData.append('lf_private', 'on'); // Private bookmark
|
||||||
formData.append('save_edit', 'Save');
|
formData.append('save_edit', 'Save');
|
||||||
|
|
||||||
let token = '';
|
let token = '';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user