fix: address code review findings for bookmark feature (H1–L5)
Fix broken tests (H1), align interface signatures with implementations (H3), remove dead code (H4), add missing input guard (M1), replace raw $_SESSION access with SessionStoreInterface (M2), sync update script schema (M4), use shared getAppBase utility (L2), document fragment stripping (L3), and apply app- CSS class prefix convention (L5). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,29 +5,43 @@
|
||||
|
||||
import { showAsyncFlash } from './app-async-flash.js';
|
||||
import { warnOnce } from '../core/app-dom.js';
|
||||
import { getAppBase } from '../pages/app-list-utils.js';
|
||||
|
||||
export function init() {
|
||||
const openBtn = document.querySelector('[data-bookmark-open-dialog]');
|
||||
const bookmarkDialog = document.querySelector('[data-app-bookmark-dialog]');
|
||||
if (!(bookmarkDialog instanceof HTMLDialogElement) || !(openBtn instanceof HTMLButtonElement)) return;
|
||||
export function initBookmarkSave(root = document) {
|
||||
const host = root && typeof root.querySelector === 'function' ? root : document;
|
||||
const openBtn = host.querySelector('[data-bookmark-open-dialog]') || document.querySelector('[data-bookmark-open-dialog]');
|
||||
const bookmarkDialog = host.matches?.('[data-app-bookmark-dialog]')
|
||||
? host
|
||||
: (host.querySelector('[data-app-bookmark-dialog]') || document.querySelector('[data-app-bookmark-dialog]'));
|
||||
if (!(bookmarkDialog instanceof HTMLDialogElement) || !(openBtn instanceof HTMLButtonElement)) {
|
||||
return { destroy: () => {} };
|
||||
}
|
||||
if (bookmarkDialog.dataset.bookmarkSaveBound === '1' && bookmarkDialog._bookmarkSaveApi) {
|
||||
return bookmarkDialog._bookmarkSaveApi;
|
||||
}
|
||||
|
||||
const bookmarkForm = bookmarkDialog.querySelector('[data-bookmark-save-form]');
|
||||
if (!(bookmarkForm instanceof HTMLFormElement)) return;
|
||||
if (!(bookmarkForm instanceof HTMLFormElement)) {
|
||||
return { destroy: () => {} };
|
||||
}
|
||||
|
||||
const groupDialog = document.querySelector('[data-app-bookmark-group-dialog]');
|
||||
const groupForm = groupDialog instanceof HTMLDialogElement
|
||||
? groupDialog.querySelector('[data-bookmark-group-save-form]')
|
||||
: null;
|
||||
const hasGroupDialog = groupDialog instanceof HTMLDialogElement && groupForm instanceof HTMLFormElement;
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
const timerIds = [];
|
||||
|
||||
const root = document.documentElement;
|
||||
const csrfKey = String(root.dataset.csrfKey || '').trim();
|
||||
const csrfToken = String(root.dataset.csrfToken || '').trim();
|
||||
const documentRoot = document.documentElement;
|
||||
const csrfKey = String(documentRoot.dataset.csrfKey || '').trim();
|
||||
const csrfToken = String(documentRoot.dataset.csrfToken || '').trim();
|
||||
const moduleContext = { module: 'bookmark-save' };
|
||||
|
||||
if (!csrfKey || !csrfToken) {
|
||||
warnOnce('UI_DATA_MISSING', 'Missing CSRF metadata for bookmark dialog', moduleContext);
|
||||
return;
|
||||
return { destroy: () => {} };
|
||||
}
|
||||
|
||||
const nameInput = bookmarkForm.querySelector('input[name="name"]');
|
||||
@@ -83,7 +97,8 @@ export function init() {
|
||||
|
||||
const runReloadWithSuccess = (message) => {
|
||||
showAsyncFlash('success', message, 2500);
|
||||
window.setTimeout(() => window.location.reload(), 450);
|
||||
const timerId = window.setTimeout(() => window.location.reload(), 450);
|
||||
timerIds.push(timerId);
|
||||
};
|
||||
|
||||
const closeBookmarkDialog = () => {
|
||||
@@ -210,33 +225,33 @@ export function init() {
|
||||
if (openBookmarkDialog() && nameInput instanceof HTMLInputElement) {
|
||||
nameInput.select();
|
||||
}
|
||||
});
|
||||
}, { signal });
|
||||
|
||||
document.addEventListener('app:bookmark-edit', (event) => {
|
||||
const detail = event instanceof CustomEvent ? event.detail : null;
|
||||
openBookmarkEditFromSidebar(detail);
|
||||
});
|
||||
}, { signal });
|
||||
|
||||
closeBookmarkBtns.forEach((btn) => {
|
||||
btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
closeBookmarkDialog();
|
||||
});
|
||||
}, { signal });
|
||||
});
|
||||
|
||||
bookmarkDialog.addEventListener('click', (event) => {
|
||||
if (event.target === bookmarkDialog) {
|
||||
closeBookmarkDialog();
|
||||
}
|
||||
});
|
||||
}, { signal });
|
||||
bookmarkDialog.addEventListener('close', () => {
|
||||
bookmarkForm.removeAttribute('aria-busy');
|
||||
syncModalBodyClass();
|
||||
});
|
||||
}, { signal });
|
||||
bookmarkDialog.addEventListener('cancel', () => {
|
||||
bookmarkForm.removeAttribute('aria-busy');
|
||||
syncModalBodyClass();
|
||||
});
|
||||
}, { signal });
|
||||
|
||||
// Save bookmark
|
||||
bookmarkForm.addEventListener('submit', async (event) => {
|
||||
@@ -310,10 +325,22 @@ export function init() {
|
||||
} finally {
|
||||
setBookmarkSubmitPending(false);
|
||||
}
|
||||
});
|
||||
}, { signal });
|
||||
|
||||
const destroy = () => {
|
||||
abortController.abort();
|
||||
timerIds.forEach((timerId) => window.clearTimeout(timerId));
|
||||
timerIds.length = 0;
|
||||
bookmarkForm.removeAttribute('aria-busy');
|
||||
delete bookmarkDialog.dataset.bookmarkSaveBound;
|
||||
delete bookmarkDialog._bookmarkSaveApi;
|
||||
};
|
||||
const api = { destroy };
|
||||
bookmarkDialog.dataset.bookmarkSaveBound = '1';
|
||||
bookmarkDialog._bookmarkSaveApi = api;
|
||||
|
||||
if (!hasGroupDialog) {
|
||||
return;
|
||||
return api;
|
||||
}
|
||||
|
||||
const groupNameInput = groupForm.querySelector('input[name="name"]');
|
||||
@@ -381,10 +408,10 @@ export function init() {
|
||||
groupIconInput.value = value;
|
||||
}
|
||||
if (groupIconPicker instanceof HTMLElement) {
|
||||
groupIconPicker.querySelectorAll('.active').forEach((item) => item.classList.remove('active'));
|
||||
groupIconPicker.querySelectorAll('.app-bookmark-icon-active').forEach((item) => item.classList.remove('app-bookmark-icon-active'));
|
||||
const selected = groupIconPicker.querySelector(`[data-icon="${value}"]`);
|
||||
if (selected instanceof HTMLElement) {
|
||||
selected.classList.add('active');
|
||||
selected.classList.add('app-bookmark-icon-active');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -459,7 +486,7 @@ export function init() {
|
||||
newGroupBtn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
openCreateGroupFromBookmarkDialog();
|
||||
});
|
||||
}, { signal });
|
||||
}
|
||||
|
||||
if (groupIconPicker instanceof HTMLElement) {
|
||||
@@ -468,29 +495,29 @@ export function init() {
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
event.preventDefault();
|
||||
setGroupIcon(target.dataset.icon || 'bi-folder');
|
||||
});
|
||||
}, { signal });
|
||||
}
|
||||
|
||||
groupCloseBtns.forEach((btn) => {
|
||||
btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
handleGroupCancel();
|
||||
});
|
||||
}, { signal });
|
||||
});
|
||||
|
||||
groupDialog.addEventListener('click', (event) => {
|
||||
if (event.target === groupDialog) {
|
||||
handleGroupCancel();
|
||||
}
|
||||
});
|
||||
}, { signal });
|
||||
groupDialog.addEventListener('cancel', (event) => {
|
||||
event.preventDefault();
|
||||
handleGroupCancel();
|
||||
});
|
||||
}, { signal });
|
||||
groupDialog.addEventListener('close', () => {
|
||||
groupForm.removeAttribute('aria-busy');
|
||||
syncModalBodyClass();
|
||||
});
|
||||
}, { signal });
|
||||
|
||||
document.addEventListener('app:bookmark-group-edit', (event) => {
|
||||
const detail = event instanceof CustomEvent ? event.detail : null;
|
||||
@@ -513,7 +540,7 @@ export function init() {
|
||||
groupNameInput.focus();
|
||||
groupNameInput.select();
|
||||
}
|
||||
});
|
||||
}, { signal });
|
||||
|
||||
groupForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
@@ -574,12 +601,9 @@ export function init() {
|
||||
} finally {
|
||||
setGroupSubmitPending(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, { signal });
|
||||
|
||||
function getAppBase() {
|
||||
const base = document.querySelector('base');
|
||||
return base ? base.href : '/';
|
||||
return api;
|
||||
}
|
||||
|
||||
function currentRelativeUrl() {
|
||||
|
||||
Reference in New Issue
Block a user