fix: replace grid action column with link-column on primary data cell
Remove the dedicated "Edit"/"Open" action button column from all list pages. Instead, wrap the primary data column (name, description, subject, etc.) in a clickable <a> link via the new rowInteraction.linkColumn option. Double-click, Enter-key, and click-to-focus row navigation preserved. Pages with explicit actions config are unaffected. Address-book opts out of auto-wrapping (linkColumn: false) and renders the link in its own formatter to avoid nested <a> with avatar lightbox. Also fixes pre-existing XSS in insertActionColumn (unescaped href/label) and updates typography test for --text-page-title token change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { initMultiSelectCascade } from '../../../../js/components/app-multiselec
|
||||
import { initStandardListPage } from '../../../../js/core/app-grid-factory.js';
|
||||
import { readPageConfig } from '../../../../js/core/app-page-config.js';
|
||||
import { warnOnce } from '../../../../js/core/app-dom.js';
|
||||
import { getAppBase, escapeHtml, buildBadgeList, gridFilterMultiCsv, gridFilterText } from '../../../../js/pages/app-list-utils.js';
|
||||
import { getAppBase, escapeHtml, buildBadgeList, gridFilterMultiCsv, gridFilterText, withCurrentListReturn } from '../../../../js/pages/app-list-utils.js';
|
||||
|
||||
const config = readPageConfig('address-book-index');
|
||||
if (config) {
|
||||
@@ -50,7 +50,7 @@ if (config) {
|
||||
{
|
||||
name: labels.name || 'Name',
|
||||
sort: true,
|
||||
formatter: (cell) => {
|
||||
formatter: (cell, row) => {
|
||||
const nameValue = typeof cell === 'object' && cell !== null
|
||||
? (cell.name ?? '')
|
||||
: cell;
|
||||
@@ -58,13 +58,18 @@ if (config) {
|
||||
? (cell.avatar_uuid ?? '')
|
||||
: '';
|
||||
const name = escapeHtml(nameValue || '-');
|
||||
const uuid = row?.cells?.[uuidIndex]?.data;
|
||||
const editUrl = uuid ? withCurrentListReturn(new URL(`address-book/view/${uuid}`, appBase).toString()) : '';
|
||||
const nameHtml = editUrl
|
||||
? `<a class="app-grid-link-cell" href="${escapeHtml(editUrl)}">${name}</a>`
|
||||
: `<span>${name}</span>`;
|
||||
if (!avatarUuid) {
|
||||
const initials = escapeHtml(initialsForName(nameValue));
|
||||
return gridjs.html(`<span class="grid-name-cell"><span class="grid-avatar grid-avatar-placeholder">${initials}</span><span>${name}</span></span>`);
|
||||
return gridjs.html(`<span class="grid-name-cell"><span class="grid-avatar grid-avatar-placeholder">${initials}</span>${nameHtml}</span>`);
|
||||
}
|
||||
const src = new URL(`admin/users/avatar-file?uuid=${avatarUuid}&size=64`, appBase).toString();
|
||||
const full = new URL(`admin/users/avatar-file?uuid=${avatarUuid}&size=256`, appBase).toString();
|
||||
return gridjs.html(`<span class="grid-name-cell"><a data-fslightbox="address-book-avatars" href="${full}"><img class="grid-avatar" src="${src}" alt="" loading="lazy"></a><span>${name}</span></span>`);
|
||||
return gridjs.html(`<span class="grid-name-cell"><a data-fslightbox="address-book-avatars" href="${full}"><img class="grid-avatar" src="${src}" alt="" loading="lazy"></a>${nameHtml}</span>`);
|
||||
},
|
||||
},
|
||||
{ name: labels.email || 'Email', sort: true },
|
||||
@@ -99,6 +104,7 @@ if (config) {
|
||||
rowDataset: (row) => ({
|
||||
uuid: row?.cells?.[uuidIndex]?.data,
|
||||
}),
|
||||
rowInteraction: { linkColumn: false },
|
||||
rowDblClick: {
|
||||
getUrl: (rowData) => {
|
||||
const uuid = rowData?.cells?.[uuidIndex]?.data;
|
||||
|
||||
Reference in New Issue
Block a user