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:
@@ -6,26 +6,40 @@ Row-Navigation discoverable und keyboard-freundlich machen.
|
|||||||
|
|
||||||
## Standardverhalten
|
## Standardverhalten
|
||||||
|
|
||||||
1. Sichtbare Action-Spalte rechts (`Open`/`Edit`) fuer Listen mit `rowDblClick`.
|
1. Konfigurierbares Link-Column: Die primaere Datenspalte (z.B. Name, Beschreibung) wird als klickbarer `<a>`-Link gerendert.
|
||||||
2. Enter auf fokussierter Row loest dieselbe Navigation aus.
|
2. Enter auf fokussierter Row loest dieselbe Navigation aus.
|
||||||
3. Doppelklick bleibt als Fallback fuer Power-User.
|
3. Doppelklick bleibt als Fallback fuer Power-User.
|
||||||
4. Nur Rows mit Ziel-URL erhalten `data-row-open-url`.
|
4. Nur Rows mit Ziel-URL erhalten `data-row-open-url` und den Link im Link-Column.
|
||||||
|
5. Rows ohne URL (z.B. fehlende Berechtigung) zeigen den Inhalt als Plain-Text ohne Link.
|
||||||
|
|
||||||
## Contract
|
## Contract
|
||||||
|
|
||||||
```js
|
```js
|
||||||
rowInteraction: {
|
rowInteraction: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
showActionButton: true,
|
linkColumn: 0, // Index der sichtbaren Datenspalte, die als Link gerendert wird (default: 0)
|
||||||
keepDblClick: true,
|
keepDblClick: true, // Doppelklick-Navigation beibehalten (default: true)
|
||||||
enableEnterOnRow: true,
|
enableEnterOnRow: true, // Enter-Key-Navigation auf fokussierter Row (default: true)
|
||||||
actionColumnLabel: 'Actions',
|
rowEnterHint: 'Press Enter to open row'
|
||||||
resolveActionLabel: (url, rowData) => (url.includes('/edit/') ? 'Edit' : 'Open')
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### linkColumn-Zuordnung
|
||||||
|
|
||||||
|
| Seite | linkColumn | Zielspalte |
|
||||||
|
|---|---|---|
|
||||||
|
| Users | 2 | First name (nach Avatar + hidden UUID) |
|
||||||
|
| Tenants | 1 | Description (nach Avatar) |
|
||||||
|
| Roles, Departments, Permissions | 0 | Description |
|
||||||
|
| Address book | 1 | Name (nach hidden UUID) |
|
||||||
|
| Mail log | 3 | Subject |
|
||||||
|
| Scheduled jobs | 0 | Job label |
|
||||||
|
| System audit, Import audit, User lifecycle audit | 2 | Event/Profile/Action |
|
||||||
|
| API audit | 3 | Path |
|
||||||
|
|
||||||
## Guardrails
|
## Guardrails
|
||||||
|
|
||||||
1. Keine globale Tab-Stop-Flut auf allen Rows.
|
1. Keine globale Tab-Stop-Flut auf allen Rows.
|
||||||
2. Fokusstil fuer Row und Action-Button sichtbar.
|
2. Fokusstil fuer Row und Link-Element sichtbar.
|
||||||
3. Explizite `actions.enabled: true` pro Seite darf Auto-Action uebersteuern.
|
3. Explizite `actions.enabled: true` pro Seite darf Link-Column uebersteuern (eigene Action-Spalte).
|
||||||
|
4. Link-Column darf keine Spalte mit verschachtelten `<a>`-Elementen umwickeln (z.B. Avatar mit Lightbox).
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { initMultiSelectCascade } from '../../../../js/components/app-multiselec
|
|||||||
import { initStandardListPage } from '../../../../js/core/app-grid-factory.js';
|
import { initStandardListPage } from '../../../../js/core/app-grid-factory.js';
|
||||||
import { readPageConfig } from '../../../../js/core/app-page-config.js';
|
import { readPageConfig } from '../../../../js/core/app-page-config.js';
|
||||||
import { warnOnce } from '../../../../js/core/app-dom.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');
|
const config = readPageConfig('address-book-index');
|
||||||
if (config) {
|
if (config) {
|
||||||
@@ -50,7 +50,7 @@ if (config) {
|
|||||||
{
|
{
|
||||||
name: labels.name || 'Name',
|
name: labels.name || 'Name',
|
||||||
sort: true,
|
sort: true,
|
||||||
formatter: (cell) => {
|
formatter: (cell, row) => {
|
||||||
const nameValue = typeof cell === 'object' && cell !== null
|
const nameValue = typeof cell === 'object' && cell !== null
|
||||||
? (cell.name ?? '')
|
? (cell.name ?? '')
|
||||||
: cell;
|
: cell;
|
||||||
@@ -58,13 +58,18 @@ if (config) {
|
|||||||
? (cell.avatar_uuid ?? '')
|
? (cell.avatar_uuid ?? '')
|
||||||
: '';
|
: '';
|
||||||
const name = escapeHtml(nameValue || '-');
|
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) {
|
if (!avatarUuid) {
|
||||||
const initials = escapeHtml(initialsForName(nameValue));
|
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 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();
|
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 },
|
{ name: labels.email || 'Email', sort: true },
|
||||||
@@ -99,6 +104,7 @@ if (config) {
|
|||||||
rowDataset: (row) => ({
|
rowDataset: (row) => ({
|
||||||
uuid: row?.cells?.[uuidIndex]?.data,
|
uuid: row?.cells?.[uuidIndex]?.data,
|
||||||
}),
|
}),
|
||||||
|
rowInteraction: { linkColumn: false },
|
||||||
rowDblClick: {
|
rowDblClick: {
|
||||||
getUrl: (rowData) => {
|
getUrl: (rowData) => {
|
||||||
const uuid = rowData?.cells?.[uuidIndex]?.data;
|
const uuid = rowData?.cells?.[uuidIndex]?.data;
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class TypographyTokenContractTest extends TestCase
|
|||||||
'--text-label' => '--text-sm',
|
'--text-label' => '--text-sm',
|
||||||
'--text-caption' => '--text-xs',
|
'--text-caption' => '--text-xs',
|
||||||
'--text-title' => '--text-xl',
|
'--text-title' => '--text-xl',
|
||||||
'--text-page-title' => '--text-2xl',
|
'--text-page-title' => '--text-3xl',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($aliases as $alias => $primitive) {
|
foreach ($aliases as $alias => $primitive) {
|
||||||
|
|||||||
@@ -233,16 +233,20 @@
|
|||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gridjs-container .grid-actions-row-open {
|
.app-grid-link-cell {
|
||||||
justify-content: flex-end;
|
color: inherit;
|
||||||
width: 100%;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gridjs-container .grid-actions .app-grid-row-open {
|
.app-grid-link-cell:hover {
|
||||||
padding: 4px 10px;
|
text-decoration: underline;
|
||||||
font-size: 12px;
|
color: var(--app-primary);
|
||||||
line-height: 1.2;
|
}
|
||||||
white-space: nowrap;
|
|
||||||
|
.app-grid-link-cell:focus-visible {
|
||||||
|
outline: 2px solid var(--app-primary-focus);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.gridjs-tr[data-row-open-url] {
|
tr.gridjs-tr[data-row-open-url] {
|
||||||
@@ -276,10 +280,6 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gridjs-container table td .grid-actions .app-grid-row-open:focus-visible {
|
|
||||||
box-shadow: 0 0 0 var(--app-outline-width) var(--app-primary-focus);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"] button.gridjs-sort,
|
html[data-theme="dark"] button.gridjs-sort,
|
||||||
html[data-theme="dark"] button.gridjs-sort-neutral {
|
html[data-theme="dark"] button.gridjs-sort-neutral {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
|
|||||||
@@ -255,28 +255,15 @@ export function createServerGrid(options) {
|
|||||||
const rowInteractionEnabled = hasRowOpenHandler && rowInteractionConfig.enabled !== false;
|
const rowInteractionEnabled = hasRowOpenHandler && rowInteractionConfig.enabled !== false;
|
||||||
const keepDblClick = rowInteractionEnabled && rowInteractionConfig.keepDblClick !== false;
|
const keepDblClick = rowInteractionEnabled && rowInteractionConfig.keepDblClick !== false;
|
||||||
const enableEnterOnRow = rowInteractionEnabled && rowInteractionConfig.enableEnterOnRow !== false;
|
const enableEnterOnRow = rowInteractionEnabled && rowInteractionConfig.enableEnterOnRow !== false;
|
||||||
const showRowActionButton = rowInteractionEnabled && rowInteractionConfig.showActionButton !== false;
|
const linkColumnIndex = rowInteractionEnabled && rowInteractionConfig.linkColumn !== false
|
||||||
const rowActionColumnLabel = String(
|
? (Number.isInteger(rowInteractionConfig.linkColumn) ? rowInteractionConfig.linkColumn : 0)
|
||||||
rowInteractionConfig.actionColumnLabel
|
: -1;
|
||||||
|| language?.actions?.column
|
|
||||||
|| 'Actions'
|
|
||||||
).trim() || 'Actions';
|
|
||||||
const rowActionOpenLabel = String(language?.actions?.open || 'Open').trim() || 'Open';
|
|
||||||
const rowActionEditLabel = String(language?.actions?.edit || 'Edit').trim() || 'Edit';
|
|
||||||
const rowEnterHint = String(
|
const rowEnterHint = String(
|
||||||
rowInteractionConfig.rowEnterHint
|
rowInteractionConfig.rowEnterHint
|
||||||
|| language?.actions?.rowEnterHint
|
|| language?.actions?.rowEnterHint
|
||||||
|| 'Press Enter to open row'
|
|| 'Press Enter to open row'
|
||||||
).trim() || 'Press Enter to open row';
|
).trim() || 'Press Enter to open row';
|
||||||
const gridRetryLabel = String(language?.errorRetry || 'Retry').trim() || 'Retry';
|
const gridRetryLabel = String(language?.errorRetry || 'Retry').trim() || 'Retry';
|
||||||
const defaultResolveRowActionLabel = (url) => (
|
|
||||||
/(^|\/)edit(\/|$|\?)/i.test(String(url || '').trim())
|
|
||||||
? rowActionEditLabel
|
|
||||||
: rowActionOpenLabel
|
|
||||||
);
|
|
||||||
const resolveActionLabel = typeof rowInteractionConfig.resolveActionLabel === 'function'
|
|
||||||
? rowInteractionConfig.resolveActionLabel
|
|
||||||
: defaultResolveRowActionLabel;
|
|
||||||
const resolveRowOpenUrl = (rowData, rowIndex = -1) => {
|
const resolveRowOpenUrl = (rowData, rowIndex = -1) => {
|
||||||
if (!rowInteractionEnabled) {
|
if (!rowInteractionEnabled) {
|
||||||
return '';
|
return '';
|
||||||
@@ -467,31 +454,29 @@ export function createServerGrid(options) {
|
|||||||
wrapperClass: actions.wrapperClass || 'grid-actions',
|
wrapperClass: actions.wrapperClass || 'grid-actions',
|
||||||
onAction: typeof actions.onAction === 'function' ? actions.onAction : null
|
onAction: typeof actions.onAction === 'function' ? actions.onAction : null
|
||||||
} : null;
|
} : null;
|
||||||
const rowOpenActionConfig = !explicitActionConfig && showRowActionButton ? {
|
const actionConfig = explicitActionConfig || null;
|
||||||
id: 'row_actions',
|
|
||||||
label: gridjsLib.html(
|
// Link-column: wrap the target column's formatter output in an <a> for row navigation.
|
||||||
`<span class="app-grid-actions-header" data-tooltip="${escapeHtmlAttr(rowEnterHint)}" data-tooltip-pos="top" aria-label="${escapeHtmlAttr(rowEnterHint)}">${escapeHtmlAttr(rowActionColumnLabel)}</span>`
|
if (linkColumnIndex >= 0 && linkColumnIndex < normalizedColumns.length) {
|
||||||
),
|
const targetCol = normalizedColumns[linkColumnIndex];
|
||||||
index: null,
|
const originalFormatter = targetCol.formatter || null;
|
||||||
items: [{
|
targetCol.formatter = (cell, row) => {
|
||||||
key: 'row-open',
|
const rowOpenUrl = resolveRowOpenUrl(row);
|
||||||
type: 'link',
|
const inner = originalFormatter ? originalFormatter(cell, row) : cell;
|
||||||
className: 'outline secondary app-grid-row-open',
|
if (!rowOpenUrl) {
|
||||||
label: (rowData, url) => {
|
return inner;
|
||||||
const dynamicLabel = resolveActionLabel(url, rowData);
|
}
|
||||||
const normalizedLabel = String(dynamicLabel || '').trim();
|
const innerHtml = (inner && typeof inner === 'object' && typeof inner.props?.content === 'string')
|
||||||
return normalizedLabel || defaultResolveRowActionLabel(url);
|
? inner.props.content
|
||||||
},
|
: escapeHtmlAttr(String(inner ?? ''));
|
||||||
ariaLabel: (_rowData, _url, label) => `${String(label || '').trim()}. ${rowEnterHint}`,
|
// Avoid nesting <a> inside <a> — if the formatter already has links, skip wrapping.
|
||||||
title: (_rowData, _url) => rowEnterHint,
|
if (/<a[\s>]/i.test(innerHtml)) {
|
||||||
href: ({ id }) => id
|
return inner;
|
||||||
}],
|
}
|
||||||
// For row-open actions the URL itself is the stable row identifier.
|
return gridjsLib.html(`<a class="app-grid-link-cell" href="${escapeHtmlAttr(rowOpenUrl)}">${innerHtml}</a>`);
|
||||||
getRowId: (rowData) => resolveRowOpenUrl(rowData),
|
};
|
||||||
wrapperClass: 'grid-actions grid-actions-row-open',
|
}
|
||||||
onAction: null
|
|
||||||
} : null;
|
|
||||||
const actionConfig = explicitActionConfig || rowOpenActionConfig;
|
|
||||||
const selectionConfig = selection && selection.enabled ? {
|
const selectionConfig = selection && selection.enabled ? {
|
||||||
id: selection.id || 'selectRow',
|
id: selection.id || 'selectRow',
|
||||||
index: Number.isInteger(selection.index) ? selection.index : 0,
|
index: Number.isInteger(selection.index) ? selection.index : 0,
|
||||||
@@ -543,12 +528,13 @@ export function createServerGrid(options) {
|
|||||||
const titleAttr = titleRaw ? ` title="${escapeHtmlAttr(titleRaw)}"` : '';
|
const titleAttr = titleRaw ? ` title="${escapeHtmlAttr(titleRaw)}"` : '';
|
||||||
const className = item.className ? ` ${item.className}` : '';
|
const className = item.className ? ` ${item.className}` : '';
|
||||||
const type = item.type || 'button';
|
const type = item.type || 'button';
|
||||||
|
const safeLabel = escapeHtmlAttr(String(label));
|
||||||
if (type === 'link') {
|
if (type === 'link') {
|
||||||
const href = typeof item.href === 'function' ? item.href({ row, id }) : item.href;
|
const href = typeof item.href === 'function' ? item.href({ row, id }) : item.href;
|
||||||
const url = href || '#';
|
const url = escapeHtmlAttr(href || '#');
|
||||||
return `<a role="button" class="${className.trim()}" data-grid-action="${item.key}" href="${url}"${ariaAttr}${titleAttr}>${label}</a>`;
|
return `<a role="button" class="${className.trim()}" data-grid-action="${item.key}" href="${url}"${ariaAttr}${titleAttr}>${safeLabel}</a>`;
|
||||||
}
|
}
|
||||||
return `<button type="button" class="${className.trim()}" data-grid-action="${item.key}"${ariaAttr}${titleAttr}>${label}</button>`;
|
return `<button type="button" class="${className.trim()}" data-grid-action="${item.key}"${ariaAttr}${titleAttr}>${safeLabel}</button>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
return gridjsLib.html(`<div class="${actionConfig.wrapperClass}" role="group">${buttons}</div>`);
|
return gridjsLib.html(`<div class="${actionConfig.wrapperClass}" role="group">${buttons}</div>`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,9 +117,7 @@ if (config) {
|
|||||||
row.ip || '',
|
row.ip || '',
|
||||||
row.id,
|
row.id,
|
||||||
]),
|
]),
|
||||||
actions: {
|
rowInteraction: { linkColumn: 3 },
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ if (config) {
|
|||||||
row.modified,
|
row.modified,
|
||||||
row.uuid,
|
row.uuid,
|
||||||
]),
|
]),
|
||||||
actions: { enabled: false },
|
rowInteraction: { linkColumn: 0 },
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ if (config) {
|
|||||||
{ uuid: row.user_uuid || '', label: row.user_label || '-' },
|
{ uuid: row.user_uuid || '', label: row.user_label || '-' },
|
||||||
row.id,
|
row.id,
|
||||||
]),
|
]),
|
||||||
actions: { enabled: false },
|
rowInteraction: { linkColumn: 2 },
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -71,9 +71,7 @@ if (config) {
|
|||||||
row.template || '-',
|
row.template || '-',
|
||||||
row.id,
|
row.id,
|
||||||
]),
|
]),
|
||||||
actions: {
|
rowInteraction: { linkColumn: 3 },
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ if (config) {
|
|||||||
row.created,
|
row.created,
|
||||||
row.id,
|
row.id,
|
||||||
]),
|
]),
|
||||||
|
rowInteraction: { linkColumn: 0 },
|
||||||
rowDblClick: {
|
rowDblClick: {
|
||||||
getUrl: (row) => {
|
getUrl: (row) => {
|
||||||
const id = row?.cells?.[5]?.data;
|
const id = row?.cells?.[5]?.data;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ if (config) {
|
|||||||
row.modified,
|
row.modified,
|
||||||
row.uuid,
|
row.uuid,
|
||||||
]),
|
]),
|
||||||
actions: { enabled: false },
|
rowInteraction: { linkColumn: 0 },
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ if (config) {
|
|||||||
row.last_error || '-',
|
row.last_error || '-',
|
||||||
row.id,
|
row.id,
|
||||||
]),
|
]),
|
||||||
actions: { enabled: false },
|
rowInteraction: { linkColumn: 0 },
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -79,9 +79,7 @@ if (config) {
|
|||||||
row.error_code || '',
|
row.error_code || '',
|
||||||
row.id,
|
row.id,
|
||||||
]),
|
]),
|
||||||
actions: {
|
rowInteraction: { linkColumn: 2 },
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
rowDblClick: {
|
rowDblClick: {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ if (config) {
|
|||||||
row.created,
|
row.created,
|
||||||
row.uuid,
|
row.uuid,
|
||||||
]),
|
]),
|
||||||
actions: { enabled: false },
|
rowInteraction: { linkColumn: 1 },
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ if (config) {
|
|||||||
row.restored_at || '-',
|
row.restored_at || '-',
|
||||||
row.id,
|
row.id,
|
||||||
]),
|
]),
|
||||||
actions: { enabled: false },
|
rowInteraction: { linkColumn: 2 },
|
||||||
search: gridSearch,
|
search: gridSearch,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
urlSync: true,
|
urlSync: true,
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ if (config) {
|
|||||||
rowDataset: (row) => ({
|
rowDataset: (row) => ({
|
||||||
uuid: row?.cells?.[uuidIndex]?.data,
|
uuid: row?.cells?.[uuidIndex]?.data,
|
||||||
}),
|
}),
|
||||||
|
rowInteraction: { linkColumn: 2 },
|
||||||
rowDblClick: {
|
rowDblClick: {
|
||||||
getUrl: (rowData) => {
|
getUrl: (rowData) => {
|
||||||
if (!canEditRow(rowData)) {
|
if (!canEditRow(rowData)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user