@happyvertical/smrt-content 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/content-list.md +93 -14
- package/dist/chunks/{content-corrections-BxfA3tW1.js → content-corrections-B4mPd_th.js} +6 -3
- package/dist/chunks/content-corrections-B4mPd_th.js.map +1 -0
- package/dist/chunks/{content-query-B4cHfrig.js → content-query-BV41W2Dq.js} +6 -6
- package/dist/chunks/{content-query-B4cHfrig.js.map → content-query-BV41W2Dq.js.map} +1 -1
- package/dist/chunks/{content-reviews-BWrYEQc6.js → content-reviews-lBoqJY7Y.js} +6 -3
- package/dist/chunks/content-reviews-lBoqJY7Y.js.map +1 -0
- package/dist/chunks/{content-versions-OEYQVrUf.js → content-versions-dZSBU8UF.js} +6 -3
- package/dist/chunks/content-versions-dZSBU8UF.js.map +1 -0
- package/dist/chunks/{contents-BrGxWH3s.js → contents-Ba7JHu2K.js} +2 -2
- package/dist/chunks/{contents-BrGxWH3s.js.map → contents-Ba7JHu2K.js.map} +1 -1
- package/dist/content-contribution.d.ts +1 -1
- package/dist/content-contribution.d.ts.map +1 -1
- package/dist/index.js +12 -9
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2268 -4345
- package/dist/server/content-list-actions.d.ts +10 -3
- package/dist/server/content-list-actions.d.ts.map +1 -1
- package/dist/server/content-list-data-surface.d.ts +29 -0
- package/dist/server/content-list-data-surface.d.ts.map +1 -0
- package/dist/server/content-list-lifecycle.d.ts +20 -0
- package/dist/server/content-list-lifecycle.d.ts.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +247 -25
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +4291 -2510
- package/dist/svelte/components/ContentList.svelte +538 -40
- package/dist/svelte/components/ContentList.svelte.d.ts +6 -1
- package/dist/svelte/components/ContentList.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentListLifecyclePanel.svelte +387 -0
- package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts +17 -0
- package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts.map +1 -0
- package/dist/svelte/content-list-controller.d.ts +10 -0
- package/dist/svelte/content-list-controller.d.ts.map +1 -1
- package/dist/svelte/content-list-controller.js +111 -26
- package/dist/svelte/content-list-data-surface.d.ts +45 -0
- package/dist/svelte/content-list-data-surface.d.ts.map +1 -0
- package/dist/svelte/content-list-data-surface.js +196 -0
- package/dist/svelte/content-list-lifecycle.d.ts +132 -0
- package/dist/svelte/content-list-lifecycle.d.ts.map +1 -0
- package/dist/svelte/content-list-lifecycle.js +439 -0
- package/dist/svelte/i18n.contribution.d.ts +27 -0
- package/dist/svelte/i18n.contribution.d.ts.map +1 -1
- package/dist/svelte/i18n.contribution.js +27 -0
- package/dist/svelte/index.d.ts +2 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +2 -0
- package/package.json +15 -15
- package/dist/chunks/content-corrections-BxfA3tW1.js.map +0 -1
- package/dist/chunks/content-reviews-BWrYEQc6.js.map +0 -1
- package/dist/chunks/content-versions-OEYQVrUf.js.map +0 -1
|
@@ -101,8 +101,25 @@ const DEFAULT_SURFACE_LIMITS = {
|
|
|
101
101
|
maxQueryBytes: 50_000,
|
|
102
102
|
maxSelectionSize: 200,
|
|
103
103
|
};
|
|
104
|
+
/** Operators shared by the local evaluator and server query translator. */
|
|
105
|
+
const CONTENT_LIST_FILTER_OPERATORS = [
|
|
106
|
+
'equals',
|
|
107
|
+
'notEquals',
|
|
108
|
+
'contains',
|
|
109
|
+
'startsWith',
|
|
110
|
+
'endsWith',
|
|
111
|
+
'in',
|
|
112
|
+
'notIn',
|
|
113
|
+
'gt',
|
|
114
|
+
'gte',
|
|
115
|
+
'lt',
|
|
116
|
+
'lte',
|
|
117
|
+
'isNull',
|
|
118
|
+
'isNotNull',
|
|
119
|
+
];
|
|
104
120
|
/** Local table commands a mounted content list accepts from a data surface. */
|
|
105
121
|
const CONTENT_LIST_CONTROLS = [
|
|
122
|
+
{ id: 'set-view', label: 'Change list presentation' },
|
|
106
123
|
{ id: 'set-search', label: 'Search contents' },
|
|
107
124
|
{ id: 'set-filters', label: 'Filter contents' },
|
|
108
125
|
{ id: 'set-sorting', label: 'Sort contents' },
|
|
@@ -112,6 +129,8 @@ const CONTENT_LIST_CONTROLS = [
|
|
|
112
129
|
{ id: 'set-selected-rows', label: 'Replace the row selection' },
|
|
113
130
|
{ id: 'toggle-row-selection', label: 'Toggle one row selection' },
|
|
114
131
|
{ id: 'reset', label: 'Reset the list view' },
|
|
132
|
+
{ id: 'refresh', label: 'Refresh contents' },
|
|
133
|
+
{ id: 'retry', label: 'Retry the content query' },
|
|
115
134
|
{ id: 'focus', label: 'Focus the list' },
|
|
116
135
|
{ id: 'reveal', label: 'Scroll the list into view' },
|
|
117
136
|
{ id: 'highlight', label: 'Highlight the list' },
|
|
@@ -828,14 +847,11 @@ export function contentListRowActions(row, options = {}) {
|
|
|
828
847
|
actions.push('delete');
|
|
829
848
|
return actions;
|
|
830
849
|
}
|
|
831
|
-
function surfaceColumn(id, labels, order, column) {
|
|
832
|
-
const capabilities = [
|
|
833
|
-
|
|
834
|
-
'filter',
|
|
835
|
-
|
|
836
|
-
'project',
|
|
837
|
-
];
|
|
838
|
-
if (column.searchable !== false)
|
|
850
|
+
function surfaceColumn(id, labels, order, column, queryable = true) {
|
|
851
|
+
const capabilities = ['read'];
|
|
852
|
+
if (queryable)
|
|
853
|
+
capabilities.push('filter', 'sort', 'project');
|
|
854
|
+
if (queryable && column.searchable !== false)
|
|
839
855
|
capabilities.push('search');
|
|
840
856
|
const fieldName = CONTENT_LIST_COLUMN_FIELD_NAMES[id];
|
|
841
857
|
return {
|
|
@@ -846,6 +862,21 @@ function surfaceColumn(id, labels, order, column) {
|
|
|
846
862
|
visibility: 'basic',
|
|
847
863
|
order,
|
|
848
864
|
role: column.role === 'status' ? 'status' : 'data',
|
|
865
|
+
readable: true,
|
|
866
|
+
...(queryable
|
|
867
|
+
? {
|
|
868
|
+
operators: {
|
|
869
|
+
...(column.searchable !== false ? { search: ['contains'] } : {}),
|
|
870
|
+
filter: [...CONTENT_LIST_FILTER_OPERATORS],
|
|
871
|
+
sort: ['asc', 'desc'],
|
|
872
|
+
},
|
|
873
|
+
...(column.searchable !== false
|
|
874
|
+
? { searchOperators: ['contains'] }
|
|
875
|
+
: {}),
|
|
876
|
+
filterOperators: [...CONTENT_LIST_FILTER_OPERATORS],
|
|
877
|
+
sortOperators: ['asc', 'desc'],
|
|
878
|
+
}
|
|
879
|
+
: {}),
|
|
849
880
|
};
|
|
850
881
|
}
|
|
851
882
|
/**
|
|
@@ -861,7 +892,7 @@ export function buildContentListSurfaceDescriptor(options = {}) {
|
|
|
861
892
|
if (!column) {
|
|
862
893
|
throw new Error(`Missing content list column definition: ${id}`);
|
|
863
894
|
}
|
|
864
|
-
return surfaceColumn(id, columnLabels, index, column);
|
|
895
|
+
return surfaceColumn(id, columnLabels, index, column, !(options.serverBacked && id === 'site'));
|
|
865
896
|
});
|
|
866
897
|
// The row-key column must be declared even though the table renders identity
|
|
867
898
|
// through Svelte keys rather than a visible column.
|
|
@@ -871,11 +902,15 @@ export function buildContentListSurfaceDescriptor(options = {}) {
|
|
|
871
902
|
capabilities: ['read', 'project'],
|
|
872
903
|
fieldName: CONTENT_LIST_ROW_KEY,
|
|
873
904
|
role: 'row-key',
|
|
905
|
+
readable: true,
|
|
874
906
|
};
|
|
875
|
-
const columnIds = visibleColumns
|
|
907
|
+
const columnIds = visibleColumns
|
|
908
|
+
.filter((column) => column.capabilities.includes('project'))
|
|
909
|
+
.map((column) => column.id);
|
|
876
910
|
const searchableColumnIds = visibleColumns
|
|
877
911
|
.filter((column) => column.capabilities.includes('search'))
|
|
878
912
|
.map((column) => column.id);
|
|
913
|
+
const lifecycleMode = options.lifecycleMode ?? 'active';
|
|
879
914
|
const actions = [
|
|
880
915
|
{
|
|
881
916
|
id: 'view',
|
|
@@ -883,20 +918,65 @@ export function buildContentListSurfaceDescriptor(options = {}) {
|
|
|
883
918
|
selectionScopes: ['explicit-ids'],
|
|
884
919
|
columnIds: ['title'],
|
|
885
920
|
},
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
921
|
+
...(lifecycleMode === 'active'
|
|
922
|
+
? [
|
|
923
|
+
{
|
|
924
|
+
id: 'edit',
|
|
925
|
+
label: actionLabels.edit ?? DEFAULT_ACTION_LABELS.edit,
|
|
926
|
+
selectionScopes: ['explicit-ids'],
|
|
927
|
+
},
|
|
928
|
+
options.lifecycle
|
|
929
|
+
? {
|
|
930
|
+
id: 'move-to-trash',
|
|
931
|
+
label: 'Move to trash',
|
|
932
|
+
sensitivity: 'sensitive',
|
|
933
|
+
selectionScopes: [
|
|
934
|
+
'explicit-ids',
|
|
935
|
+
'current-page',
|
|
936
|
+
'all-matching',
|
|
937
|
+
],
|
|
938
|
+
requiresConfirmation: true,
|
|
939
|
+
}
|
|
940
|
+
: {
|
|
941
|
+
id: 'delete',
|
|
942
|
+
label: actionLabels.delete ?? DEFAULT_ACTION_LABELS.delete,
|
|
943
|
+
sensitivity: 'sensitive',
|
|
944
|
+
selectionScopes: [
|
|
945
|
+
'explicit-ids',
|
|
946
|
+
'current-page',
|
|
947
|
+
],
|
|
948
|
+
requiresConfirmation: true,
|
|
949
|
+
},
|
|
950
|
+
]
|
|
951
|
+
: options.lifecycle
|
|
952
|
+
? [
|
|
953
|
+
{
|
|
954
|
+
id: 'restore',
|
|
955
|
+
label: 'Restore',
|
|
956
|
+
sensitivity: 'sensitive',
|
|
957
|
+
selectionScopes: [
|
|
958
|
+
'explicit-ids',
|
|
959
|
+
'current-page',
|
|
960
|
+
'all-matching',
|
|
961
|
+
],
|
|
962
|
+
requiresConfirmation: true,
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
id: 'permanent-delete',
|
|
966
|
+
label: 'Delete permanently',
|
|
967
|
+
sensitivity: 'sensitive',
|
|
968
|
+
selectionScopes: [
|
|
969
|
+
'explicit-ids',
|
|
970
|
+
'current-page',
|
|
971
|
+
'all-matching',
|
|
972
|
+
],
|
|
973
|
+
requiresConfirmation: true,
|
|
974
|
+
},
|
|
975
|
+
]
|
|
976
|
+
: []),
|
|
898
977
|
...(options.includeWorkflows
|
|
899
|
-
? CONTENT_LIST_WORKFLOW_OPTIONS.
|
|
978
|
+
? CONTENT_LIST_WORKFLOW_OPTIONS.filter((workflow) => !(options.lifecycle && workflow.id === 'move-to-trash') &&
|
|
979
|
+
!(lifecycleMode === 'trash' && workflow.id === 'restore')).map((workflow) => ({
|
|
900
980
|
id: workflow.id,
|
|
901
981
|
label: workflow.label,
|
|
902
982
|
sensitivity: workflow.sensitivity,
|
|
@@ -925,10 +1005,15 @@ export function buildContentListSurfaceDescriptor(options = {}) {
|
|
|
925
1005
|
modes: ['rows', 'count'],
|
|
926
1006
|
projectableColumnIds: [CONTENT_LIST_ROW_KEY, ...columnIds],
|
|
927
1007
|
searchableColumnIds,
|
|
928
|
-
filterableColumnIds:
|
|
929
|
-
|
|
1008
|
+
filterableColumnIds: visibleColumns
|
|
1009
|
+
.filter((column) => column.capabilities.includes('filter'))
|
|
1010
|
+
.map((column) => column.id),
|
|
1011
|
+
sortableColumnIds: visibleColumns
|
|
1012
|
+
.filter((column) => column.capabilities.includes('sort'))
|
|
1013
|
+
.map((column) => column.id),
|
|
930
1014
|
},
|
|
931
|
-
controls: CONTENT_LIST_CONTROLS.
|
|
1015
|
+
controls: CONTENT_LIST_CONTROLS.filter((control) => (control.id !== 'refresh' || options.refresh === true) &&
|
|
1016
|
+
(control.id !== 'retry' || options.retry === true)).map((control) => ({ ...control })),
|
|
932
1017
|
actions,
|
|
933
1018
|
limits: { ...DEFAULT_SURFACE_LIMITS, ...options.limits },
|
|
934
1019
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** ContentList-owned mounted data surface, shared by every presentation. */
|
|
2
|
+
import { type DataSurfaceDescriptor, type DataSurfaceRegistry, type DataTableCommand, type DataTableController } from '@happyvertical/smrt-ui/data';
|
|
3
|
+
import type { ContentListViewMode } from './content-list-controller.js';
|
|
4
|
+
export interface ContentListSurfaceFreshness {
|
|
5
|
+
stale: boolean;
|
|
6
|
+
refreshing: boolean;
|
|
7
|
+
offline: boolean;
|
|
8
|
+
lastUpdated: string | null;
|
|
9
|
+
truncated: boolean;
|
|
10
|
+
warnings: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ContentListSurfaceContext {
|
|
13
|
+
viewMode: ContentListViewMode;
|
|
14
|
+
queryFingerprint: string | null;
|
|
15
|
+
totalRows: number | null;
|
|
16
|
+
freshness: ContentListSurfaceFreshness;
|
|
17
|
+
}
|
|
18
|
+
export interface ContentListSurfaceRegistrationOptions {
|
|
19
|
+
registry: DataSurfaceRegistry;
|
|
20
|
+
descriptor: DataSurfaceDescriptor;
|
|
21
|
+
controller: DataTableController;
|
|
22
|
+
context: ContentListSurfaceContext;
|
|
23
|
+
/** Carries the mounted identity's monotonic revision across re-registration. */
|
|
24
|
+
initialRevision?: number;
|
|
25
|
+
onRevision?: (revision: number) => void;
|
|
26
|
+
/**
|
|
27
|
+
* ContentList-owned constraints that must hold before a visible command is
|
|
28
|
+
* acknowledged. Returning false denies the command rather than publishing a
|
|
29
|
+
* transient state which a later component effect would correct.
|
|
30
|
+
*/
|
|
31
|
+
acceptsTableCommand?: (command: DataTableCommand) => boolean;
|
|
32
|
+
setViewMode(viewMode: ContentListViewMode): void;
|
|
33
|
+
refresh?: () => boolean | Promise<boolean>;
|
|
34
|
+
retry?: () => boolean | Promise<boolean>;
|
|
35
|
+
focus?: () => void;
|
|
36
|
+
reveal?: () => void;
|
|
37
|
+
highlight?: () => void;
|
|
38
|
+
}
|
|
39
|
+
export interface ContentListSurfaceHandle {
|
|
40
|
+
update(context: ContentListSurfaceContext): void;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
|
43
|
+
/** Register one stable ContentList identity independently of its view mode. */
|
|
44
|
+
export declare function registerContentListDataSurface(options: ContentListSurfaceRegistrationOptions): ContentListSurfaceHandle;
|
|
45
|
+
//# sourceMappingURL=content-list-data-surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-list-data-surface.d.ts","sourceRoot":"","sources":["../../src/svelte/content-list-data-surface.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,OAAO,EACL,KAAK,qBAAqB,EAG1B,KAAK,mBAAmB,EAGxB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EAIzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,2BAA2B,CAAC;CACxC;AAED,MAAM,WAAW,qCAAqC;IACpD,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,qBAAqB,CAAC;IAClC,UAAU,EAAE,mBAAmB,CAAC;IAChC,OAAO,EAAE,yBAAyB,CAAC;IACnC,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC;IAC7D,WAAW,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACjD,OAAO,IAAI,IAAI,CAAC;CACjB;AA8HD,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,qCAAqC,GAC7C,wBAAwB,CAyG1B"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/** ContentList-owned mounted data surface, shared by every presentation. */
|
|
2
|
+
import { dataTableCommandFromDataSurfaceCommand, dataTableRowIdKey, } from '@happyvertical/smrt-ui/data';
|
|
3
|
+
const VIEWS = new Set(['grid', 'detailed', 'compact']);
|
|
4
|
+
const revisionsByRegistry = new WeakMap();
|
|
5
|
+
function identityKey(descriptor) {
|
|
6
|
+
const { identity } = descriptor;
|
|
7
|
+
return JSON.stringify([
|
|
8
|
+
identity.kind,
|
|
9
|
+
identity.surfaceId,
|
|
10
|
+
identity.subject?.type,
|
|
11
|
+
identity.subject?.id,
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
function selectionReference(selection) {
|
|
15
|
+
if (selection.scope === 'page')
|
|
16
|
+
return { scope: 'current-page' };
|
|
17
|
+
if (selection.scope === 'allMatching') {
|
|
18
|
+
return {
|
|
19
|
+
scope: 'all-matching',
|
|
20
|
+
queryFingerprint: selection.queryFingerprint,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return selection.rowIds.length > 0
|
|
24
|
+
? { scope: 'explicit-ids', rowIds: selection.rowIds }
|
|
25
|
+
: null;
|
|
26
|
+
}
|
|
27
|
+
function contextState(context) {
|
|
28
|
+
return {
|
|
29
|
+
viewMode: context.viewMode,
|
|
30
|
+
queryFingerprint: context.queryFingerprint,
|
|
31
|
+
totalRows: context.totalRows,
|
|
32
|
+
freshness: {
|
|
33
|
+
stale: context.freshness.stale,
|
|
34
|
+
refreshing: context.freshness.refreshing,
|
|
35
|
+
offline: context.freshness.offline,
|
|
36
|
+
lastUpdated: context.freshness.lastUpdated,
|
|
37
|
+
truncated: context.freshness.truncated,
|
|
38
|
+
warnings: [...context.freshness.warnings],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function payloadObject(value) {
|
|
43
|
+
return value && !Array.isArray(value) && typeof value === 'object'
|
|
44
|
+
? value
|
|
45
|
+
: undefined;
|
|
46
|
+
}
|
|
47
|
+
function allowsFilterOperator(column, operator) {
|
|
48
|
+
if (!column)
|
|
49
|
+
return false;
|
|
50
|
+
const canonical = column.operators?.filter;
|
|
51
|
+
const alias = column.filterOperators;
|
|
52
|
+
if (canonical && alias)
|
|
53
|
+
return canonical.includes(operator) && alias.includes(operator);
|
|
54
|
+
return canonical?.includes(operator) ?? alias?.includes(operator) ?? false;
|
|
55
|
+
}
|
|
56
|
+
function commandAllowed(command, descriptor, controller) {
|
|
57
|
+
const readable = new Set(descriptor.columns
|
|
58
|
+
.filter((column) => column.capabilities.includes('read'))
|
|
59
|
+
.map((column) => column.id));
|
|
60
|
+
const filterable = new Set(descriptor.query.filterableColumnIds ?? []);
|
|
61
|
+
const sortable = new Set(descriptor.query.sortableColumnIds ?? []);
|
|
62
|
+
switch (command.type) {
|
|
63
|
+
case 'setFilters':
|
|
64
|
+
return command.filters.every((filter) => {
|
|
65
|
+
const column = descriptor.columns.find((candidate) => candidate.id === filter.columnId);
|
|
66
|
+
return (filterable.has(filter.columnId) &&
|
|
67
|
+
allowsFilterOperator(column, filter.operator));
|
|
68
|
+
});
|
|
69
|
+
case 'setSorting':
|
|
70
|
+
return command.sorting.every((sort) => sortable.has(sort.columnId));
|
|
71
|
+
case 'toggleSorting':
|
|
72
|
+
return sortable.has(command.columnId);
|
|
73
|
+
case 'setSelectedRows':
|
|
74
|
+
return command.rowIds.length <= descriptor.limits.maxSelectionSize;
|
|
75
|
+
case 'toggleRowSelection': {
|
|
76
|
+
const selection = controller.snapshot().state.selection;
|
|
77
|
+
if (selection.scope === 'allMatching')
|
|
78
|
+
return false;
|
|
79
|
+
const selected = selection.rowIds.some((rowId) => dataTableRowIdKey(rowId) === dataTableRowIdKey(command.rowId));
|
|
80
|
+
return (selected || selection.rowIds.length < descriptor.limits.maxSelectionSize);
|
|
81
|
+
}
|
|
82
|
+
case 'setPageSize':
|
|
83
|
+
return (command.pageSize === null ||
|
|
84
|
+
(Number.isSafeInteger(command.pageSize) && command.pageSize > 0));
|
|
85
|
+
case 'setColumnOrder':
|
|
86
|
+
return command.columnIds.every((columnId) => readable.has(columnId));
|
|
87
|
+
case 'setColumnVisibility':
|
|
88
|
+
return command.columns.every((column) => readable.has(column.columnId));
|
|
89
|
+
default:
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Register one stable ContentList identity independently of its view mode. */
|
|
94
|
+
export function registerContentListDataSurface(options) {
|
|
95
|
+
let revisions = revisionsByRegistry.get(options.registry);
|
|
96
|
+
if (!revisions) {
|
|
97
|
+
revisions = new Map();
|
|
98
|
+
revisionsByRegistry.set(options.registry, revisions);
|
|
99
|
+
}
|
|
100
|
+
const key = identityKey(options.descriptor);
|
|
101
|
+
const previousRevision = revisions.get(key);
|
|
102
|
+
let revision = Math.max(options.initialRevision ?? 0, previousRevision === undefined ? 0 : previousRevision + 1);
|
|
103
|
+
revisions.set(key, revision);
|
|
104
|
+
const advanceRevision = () => {
|
|
105
|
+
revision += 1;
|
|
106
|
+
revisions.set(key, revision);
|
|
107
|
+
options.onRevision?.(revision);
|
|
108
|
+
};
|
|
109
|
+
let context = options.context;
|
|
110
|
+
let contextSignature = JSON.stringify(contextState(context));
|
|
111
|
+
const updateContext = (next) => {
|
|
112
|
+
const signature = JSON.stringify(contextState(next));
|
|
113
|
+
if (signature !== contextSignature) {
|
|
114
|
+
advanceRevision();
|
|
115
|
+
contextSignature = signature;
|
|
116
|
+
}
|
|
117
|
+
context = next;
|
|
118
|
+
};
|
|
119
|
+
const unsubscribe = options.controller.subscribe((transition) => {
|
|
120
|
+
if (transition.changed)
|
|
121
|
+
advanceRevision();
|
|
122
|
+
});
|
|
123
|
+
const unregister = options.registry.register({
|
|
124
|
+
descriptor: options.descriptor,
|
|
125
|
+
getSnapshot: () => {
|
|
126
|
+
const table = options.controller.snapshot();
|
|
127
|
+
return {
|
|
128
|
+
revision,
|
|
129
|
+
state: {
|
|
130
|
+
...contextState(context),
|
|
131
|
+
table: table,
|
|
132
|
+
},
|
|
133
|
+
selection: selectionReference(table.state.selection),
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
execute: async (command) => {
|
|
137
|
+
const tableCommand = dataTableCommandFromDataSurfaceCommand(command);
|
|
138
|
+
if (tableCommand) {
|
|
139
|
+
if (!commandAllowed(tableCommand, options.descriptor, options.controller) ||
|
|
140
|
+
options.acceptsTableCommand?.(tableCommand) === false)
|
|
141
|
+
return { ok: false };
|
|
142
|
+
options.controller.dispatch(tableCommand);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
switch (command.controlId) {
|
|
146
|
+
case 'set-view': {
|
|
147
|
+
const view = payloadObject(command.payload)?.view;
|
|
148
|
+
if (typeof view !== 'string' ||
|
|
149
|
+
!VIEWS.has(view))
|
|
150
|
+
return { ok: false };
|
|
151
|
+
const viewMode = view;
|
|
152
|
+
updateContext({ ...context, viewMode });
|
|
153
|
+
options.setViewMode(viewMode);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
case 'refresh':
|
|
157
|
+
if (!options.refresh)
|
|
158
|
+
return { ok: false };
|
|
159
|
+
if ((await options.refresh()) === false)
|
|
160
|
+
return { ok: false };
|
|
161
|
+
return;
|
|
162
|
+
case 'retry':
|
|
163
|
+
if (!options.retry)
|
|
164
|
+
return { ok: false };
|
|
165
|
+
if ((await options.retry()) === false)
|
|
166
|
+
return { ok: false };
|
|
167
|
+
return;
|
|
168
|
+
case 'focus':
|
|
169
|
+
if (!options.focus)
|
|
170
|
+
return { ok: false };
|
|
171
|
+
options.focus();
|
|
172
|
+
return;
|
|
173
|
+
case 'reveal':
|
|
174
|
+
if (!options.reveal)
|
|
175
|
+
return { ok: false };
|
|
176
|
+
options.reveal();
|
|
177
|
+
return;
|
|
178
|
+
case 'highlight':
|
|
179
|
+
if (!options.highlight)
|
|
180
|
+
return { ok: false };
|
|
181
|
+
options.highlight();
|
|
182
|
+
return;
|
|
183
|
+
default:
|
|
184
|
+
return { ok: false };
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
options.onRevision?.(revision);
|
|
189
|
+
return {
|
|
190
|
+
update: updateContext,
|
|
191
|
+
destroy() {
|
|
192
|
+
unsubscribe();
|
|
193
|
+
unregister();
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe trash, restore, and permanent-delete orchestration for
|
|
3
|
+
* ContentList (#2454).
|
|
4
|
+
*
|
|
5
|
+
* Lifecycle authority stays on the server. The browser supplies a selection
|
|
6
|
+
* hint and expected count, then renders the server's resolved preview. Apply
|
|
7
|
+
* must reuse that preview's opaque confirmation token and authority-bound
|
|
8
|
+
* selection while using a fresh request id for each transport attempt; a
|
|
9
|
+
* changed query, count, permission, row revision, or eligibility is therefore
|
|
10
|
+
* rejected by the principal-bound server adapter rather than being guessed
|
|
11
|
+
* from stale list rows.
|
|
12
|
+
*/
|
|
13
|
+
import type { DataSurfaceActionResult, DataSurfaceIdentity, DataSurfaceJsonValue, DataSurfaceSelectionReference } from '@happyvertical/smrt-ui/data';
|
|
14
|
+
import type { ContentListDataQueryRequest } from './content-list-query.js';
|
|
15
|
+
export declare const CONTENT_LIST_LIFECYCLE_ACTION_IDS: readonly ["move-to-trash", "restore", "permanent-delete"];
|
|
16
|
+
export type ContentListLifecycleActionId = (typeof CONTENT_LIST_LIFECYCLE_ACTION_IDS)[number];
|
|
17
|
+
export type ContentListRestoreStatus = 'draft' | 'review' | 'published';
|
|
18
|
+
export interface ContentListLifecycleTarget {
|
|
19
|
+
/** Canonical server query. Required for all-matching selections. */
|
|
20
|
+
query?: ContentListDataQueryRequest;
|
|
21
|
+
/** Count visible when the operator requested the preview. */
|
|
22
|
+
expectedCount: number;
|
|
23
|
+
/** Operator-entered server preview count, sent only on permanent-delete apply. */
|
|
24
|
+
confirmedCount?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface ContentListLifecycleRequest {
|
|
27
|
+
version: 1;
|
|
28
|
+
requestId: string;
|
|
29
|
+
identity: DataSurfaceIdentity;
|
|
30
|
+
actionId: ContentListLifecycleActionId;
|
|
31
|
+
phase: 'preview' | 'apply';
|
|
32
|
+
selection: DataSurfaceSelectionReference;
|
|
33
|
+
payload?: DataSurfaceJsonValue;
|
|
34
|
+
expectedRevision: number;
|
|
35
|
+
target: ContentListLifecycleTarget;
|
|
36
|
+
confirmationToken?: string;
|
|
37
|
+
idempotencyKey?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ContentListLifecycleClient {
|
|
40
|
+
preview(request: ContentListLifecycleRequest): Promise<DataSurfaceActionResult>;
|
|
41
|
+
apply(request: ContentListLifecycleRequest): Promise<DataSurfaceActionResult>;
|
|
42
|
+
}
|
|
43
|
+
export interface ContentListLifecycleBinding {
|
|
44
|
+
client: ContentListLifecycleClient;
|
|
45
|
+
/** Server-owned surface revision. Never derived from list rows. */
|
|
46
|
+
revision?: number;
|
|
47
|
+
/** Override the default ContentList identity. */
|
|
48
|
+
identity?: DataSurfaceIdentity;
|
|
49
|
+
/** Server action-selection cap. Defaults to 200 and fails closed in the UI. */
|
|
50
|
+
maxSelectionSize?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface ContentListLifecycleTransportOptions {
|
|
53
|
+
apiBaseUrl?: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
fetch?: typeof globalThis.fetch;
|
|
56
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
57
|
+
credentials?: RequestCredentials;
|
|
58
|
+
}
|
|
59
|
+
export type ContentListLifecycleTransportErrorReason = 'network_failure' | 'invalid_json' | 'http_error' | 'invalid_result';
|
|
60
|
+
export declare class ContentListLifecycleTransportError extends Error {
|
|
61
|
+
readonly status?: number;
|
|
62
|
+
readonly reason: ContentListLifecycleTransportErrorReason;
|
|
63
|
+
constructor(message: string, status?: number, reason?: ContentListLifecycleTransportErrorReason);
|
|
64
|
+
}
|
|
65
|
+
export interface ContentListLifecycleOutcome {
|
|
66
|
+
rowId: string | number;
|
|
67
|
+
status: 'accepted' | 'skipped' | 'failed';
|
|
68
|
+
reason?: string;
|
|
69
|
+
/** Canonical runtime subtype, for example a Document or Mirror. */
|
|
70
|
+
resourceType?: string;
|
|
71
|
+
/** Canonical resource id used by the server mutation. */
|
|
72
|
+
resourceId?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface ContentListLifecycleSummary {
|
|
75
|
+
resolvedCount: number;
|
|
76
|
+
accepted: number;
|
|
77
|
+
skipped: number;
|
|
78
|
+
failed: number;
|
|
79
|
+
outcomes: ContentListLifecycleOutcome[];
|
|
80
|
+
representativeLabels: string[];
|
|
81
|
+
expiresAt?: number;
|
|
82
|
+
/** Correlates the visible result with principal audit metadata. */
|
|
83
|
+
auditReference: string;
|
|
84
|
+
}
|
|
85
|
+
export type ContentListLifecycleControllerStatus = 'idle' | 'previewing' | 'ready' | 'applying' | 'succeeded' | 'failed';
|
|
86
|
+
export interface ContentListLifecycleSnapshot {
|
|
87
|
+
status: ContentListLifecycleControllerStatus;
|
|
88
|
+
actionId?: ContentListLifecycleActionId;
|
|
89
|
+
selection?: DataSurfaceSelectionReference;
|
|
90
|
+
summary?: ContentListLifecycleSummary;
|
|
91
|
+
result?: DataSurfaceActionResult;
|
|
92
|
+
error?: string;
|
|
93
|
+
/** True when the only safe recovery is another server preview. */
|
|
94
|
+
renewalRequired: boolean;
|
|
95
|
+
/** True while an ambiguous apply must replay its exact authority envelope. */
|
|
96
|
+
replayRequired?: boolean;
|
|
97
|
+
}
|
|
98
|
+
export interface ContentListLifecyclePreviewInput {
|
|
99
|
+
actionId: ContentListLifecycleActionId;
|
|
100
|
+
selection: DataSurfaceSelectionReference;
|
|
101
|
+
expectedCount: number;
|
|
102
|
+
query?: ContentListDataQueryRequest;
|
|
103
|
+
restoreStatus?: ContentListRestoreStatus;
|
|
104
|
+
/** Invalidates the preview when the visible query/selection changes. */
|
|
105
|
+
viewKey: string;
|
|
106
|
+
}
|
|
107
|
+
export interface ContentListLifecycleControllerOptions extends ContentListLifecycleBinding {
|
|
108
|
+
createRequestId?: () => string;
|
|
109
|
+
createIdempotencyKey?: () => string;
|
|
110
|
+
}
|
|
111
|
+
export interface ContentListLifecycleController {
|
|
112
|
+
snapshot(): ContentListLifecycleSnapshot;
|
|
113
|
+
subscribe(listener: (snapshot: ContentListLifecycleSnapshot) => void): () => void;
|
|
114
|
+
preview(input: ContentListLifecyclePreviewInput): Promise<ContentListLifecycleSnapshot>;
|
|
115
|
+
/**
|
|
116
|
+
* Apply only after the operator confirms the server-resolved count. The
|
|
117
|
+
* number is intentionally passed again rather than inferred by the UI.
|
|
118
|
+
*/
|
|
119
|
+
apply(confirmedCount: number): Promise<ContentListLifecycleSnapshot>;
|
|
120
|
+
/** Drop a preview after any visible query or selection change. */
|
|
121
|
+
invalidate(viewKey: string): void;
|
|
122
|
+
reset(): void;
|
|
123
|
+
}
|
|
124
|
+
/** Authenticated fetch transport for the host's lifecycle route. */
|
|
125
|
+
export declare function createContentListLifecycleTransport(options?: ContentListLifecycleTransportOptions): ContentListLifecycleClient;
|
|
126
|
+
/** Parse the bounded, optional preview/apply details without trusting casts. */
|
|
127
|
+
export declare function readContentListLifecycleSummary(result: DataSurfaceActionResult): ContentListLifecycleSummary;
|
|
128
|
+
/** Framework-free state machine shared by UI and agent-driven lifecycle flows. */
|
|
129
|
+
export declare function createContentListLifecycleController(options: ContentListLifecycleControllerOptions): ContentListLifecycleController;
|
|
130
|
+
/** Keep unsuccessful rows selected so partial failures are visible/retryable. */
|
|
131
|
+
export declare function reconcileContentListLifecycleSelection(selectedRowIds: readonly (string | number)[], result: DataSurfaceActionResult): Array<string | number>;
|
|
132
|
+
//# sourceMappingURL=content-list-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-list-lifecycle.d.ts","sourceRoot":"","sources":["../../src/svelte/content-list-lifecycle.ts"],"names":[],"mappings":"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"}
|