@conduction/nextcloud-vue 2.96.0 → 2.97.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.
Files changed (79) hide show
  1. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  2. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
  3. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  4. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  6. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  7. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
  8. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  9. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
  10. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  11. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
  12. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
  13. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
  15. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  17. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
  18. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  19. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
  20. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  22. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +144 -0
  23. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  24. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
  25. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  26. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  27. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  28. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +301 -18
  29. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
  31. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  33. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +2 -2
  34. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  35. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
  36. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  37. package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
  38. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  39. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  40. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
  41. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  42. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
  43. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  44. package/dist/esm/composables/indexSources.js +182 -3
  45. package/dist/esm/composables/indexSources.js.map +1 -1
  46. package/dist/esm/l10n/en.json.js +7 -1
  47. package/dist/esm/l10n/en.json.js.map +1 -1
  48. package/dist/esm/l10n/nl.json.js +7 -1
  49. package/dist/esm/l10n/nl.json.js.map +1 -1
  50. package/dist/esm/library-style.js +1 -1
  51. package/dist/esm/nextcloud-vue.css +301 -177
  52. package/dist/esm/utils/queryPresets.js +13 -2
  53. package/dist/esm/utils/queryPresets.js.map +1 -1
  54. package/dist/esm/utils/scopeListLayout.js +37 -1
  55. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  56. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  57. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  58. package/dist/nextcloud-vue.cjs.js +3009 -1907
  59. package/dist/nextcloud-vue.cjs.js.map +2 -2
  60. package/dist/nextcloud-vue.css +301 -177
  61. package/l10n/en.json +7 -1
  62. package/l10n/nl.json +7 -1
  63. package/package.json +1 -1
  64. package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
  65. package/src/components/CnConversationThread/CnConversationThread.md +12 -0
  66. package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
  67. package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
  68. package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
  69. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
  70. package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  71. package/src/components/CnIndexPage/CnIndexPage.vue +324 -24
  72. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
  73. package/src/components/CnIndexPage/useNamedSource.js +1 -1
  74. package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
  75. package/src/composables/indexSources.js +182 -3
  76. package/src/schemas/app-manifest-v2.schema.json +11 -4
  77. package/src/utils/queryPresets.js +13 -2
  78. package/src/utils/scopeListLayout.js +37 -1
  79. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -144,7 +144,8 @@ function attributePropertiesFor(columns) {
144
144
  }
145
145
 
146
146
  /**
147
- * The value one column shows for one node.
147
+ * The value one column shows for one node. A `row` column's key may be a
148
+ * dotted path into the host's record.
148
149
  *
149
150
  * @param {object} column A resolved column.
150
151
  * @param {object} node The node.
@@ -169,7 +170,12 @@ function fileColumnValue(column, node, rowData = null) {
169
170
  return undefined
170
171
  }
171
172
  const entry = byId[node.fileid] ?? byId[String(node.fileid)];
172
- return entry ? entry[column.key] : undefined
173
+ if (!entry) {
174
+ return undefined
175
+ }
176
+ // A key the record holds as written wins; otherwise a dotted key reads
177
+ // a nested field (`scanVerdict.state`, `integrity.value`).
178
+ return Object.hasOwn(entry, column.key) ? entry[column.key] : atPath(entry, column.key)
173
179
  }
174
180
  return atPath(node, column.nodeKey || column.key)
175
181
  }
@@ -1 +1 @@
1
- {"version":3,"file":"filesBrowserColumns.js","sources":["../../../../src/components/CnFilesBrowser/filesBrowserColumns.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * The built-in columns a host may name by string, and what each one reads.\n *\n * `nodeKey` is the property on the @nextcloud/files node. `sortKey` is what\n * `sortNodes` understands, so a built-in sorts the way the Files app sorts;\n * a built-in with no `sortKey` sorts client-side like a declared column.\n *\n * @type {object}\n */\nexport const BUILT_IN_FILE_COLUMNS = {\n\tname: { key: 'name', source: 'node', nodeKey: 'basename', sortKey: 'basename', sortable: true },\n\tsize: { key: 'size', source: 'node', nodeKey: 'size', sortKey: 'size', sortable: true },\n\tmodified: { key: 'modified', source: 'node', nodeKey: 'mtime', sortKey: 'mtime', sortable: true },\n\towner: { key: 'owner', source: 'node', nodeKey: 'owner', sortable: true },\n\ttype: { key: 'type', source: 'node', nodeKey: 'mime', sortable: true },\n\ttags: { key: 'tags', source: 'node', nodeKey: 'attributes.system-tags', sortable: false },\n}\n\n/**\n * The columns a browser shows when its host declares none: what the table has\n * always shown, in the order it has always shown it.\n *\n * @type {string[]}\n */\nexport const DEFAULT_FILE_COLUMNS = ['name', 'size', 'modified']\n\n/**\n * Read a dotted path off a node without throwing on a missing segment.\n *\n * @param {object} node The node.\n * @param {string} path A dotted path.\n * @return {(Array|object|string|number|boolean|undefined)} The value.\n */\nfunction atPath(node, path) {\n\tif (!node || typeof path !== 'string' || path === '') {\n\t\treturn undefined\n\t}\n\treturn path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), node)\n}\n\n/**\n * Whether a string names a built-in column.\n *\n * @param {string} name The candidate name.\n * @return {boolean} True when it is a built-in.\n */\nexport function isBuiltInFileColumn(name) {\n\treturn typeof name === 'string' && Object.hasOwn(BUILT_IN_FILE_COLUMNS, name)\n}\n\n/**\n * Resolve a host's `columns` declaration into column objects.\n *\n * A string names a built-in. An object declares its own: `key`, `label`,\n * `source` (`node`, `attribute` or `row`), `attribute`, `formatter` and\n * `sortable`. An entry naming an unknown built-in is dropped here and refused\n * by the manifest validator, so an author hears about it at authoring time\n * rather than finding a column missing at runtime.\n *\n * @param {(Array|null|undefined)} columns The declaration, or nothing.\n * @return {Array<object>} The resolved columns, in the declared order.\n */\nexport function normaliseFileColumns(columns) {\n\tconst declared = Array.isArray(columns) && columns.length > 0 ? columns : DEFAULT_FILE_COLUMNS\n\tconst out = []\n\tdeclared.forEach((entry) => {\n\t\tif (typeof entry === 'string') {\n\t\t\tif (isBuiltInFileColumn(entry)) {\n\t\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry], builtIn: true })\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\treturn\n\t\t}\n\t\tif (isBuiltInFileColumn(entry.key) && entry.source === undefined) {\n\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry.key], ...entry, builtIn: true })\n\t\t\treturn\n\t\t}\n\t\tout.push({\n\t\t\tsource: 'node',\n\t\t\tsortable: false,\n\t\t\t...entry,\n\t\t\tbuiltIn: false,\n\t\t})\n\t})\n\treturn out\n}\n\n/**\n * The columns a browser renders: the host's declaration, narrowed by what this\n * user chose to see.\n *\n * Which side wins on what:\n *\n * - The HOST decides MEMBERSHIP. The result is built by walking the\n * declaration, so a column exists only because the host declared it, in the\n * order the host declared it.\n * - The USER decides VISIBILITY, and only downward. A stored preference can\n * hide a declared column; it can never add one. A key in the stored list\n * that the host no longer declares is passed over, so unticking a column\n * last month cannot bring it back once the host removed it, and neither can\n * a preference written while a different column set was declared.\n *\n * A stored value of `null` is a user who has never chosen, which shows\n * everything. An empty list is a user who hid everything, which is a choice\n * and is honoured.\n *\n * @param {Array<object>} declared The resolved declared columns.\n * @param {(string[]|null|undefined)} visibleKeys The user's stored visible keys.\n * @return {Array<object>} The columns to render.\n */\nexport function visibleFileColumns(declared, visibleKeys) {\n\tconst columns = Array.isArray(declared) ? declared : []\n\tif (!Array.isArray(visibleKeys)) {\n\t\treturn columns\n\t}\n\tconst wanted = new Set(visibleKeys)\n\treturn columns.filter((column) => wanted.has(column.key))\n}\n\n/**\n * The DAV properties a declaration needs in the PROPFIND, so an `attribute`\n * column's value is there when the folder lists rather than one request later.\n *\n * @param {Array<object>} columns The resolved columns.\n * @return {string[]} The property names, deduplicated, in declaration order.\n */\nexport function attributePropertiesFor(columns) {\n\tconst out = []\n\t;(Array.isArray(columns) ? columns : []).forEach((column) => {\n\t\tif (column && column.source === 'attribute' && typeof column.attribute === 'string' && column.attribute !== '') {\n\t\t\tif (!out.includes(column.attribute)) {\n\t\t\t\tout.push(column.attribute)\n\t\t\t}\n\t\t}\n\t})\n\treturn out\n}\n\n/**\n * The value one column shows for one node.\n *\n * @param {object} column A resolved column.\n * @param {object} node The node.\n * @param {object} [rowData] The host's per-file data, keyed by file id.\n * @return {(Array|object|string|number|boolean|undefined)} The cell value.\n */\nexport function fileColumnValue(column, node, rowData = null) {\n\tif (!column || !node) {\n\t\treturn undefined\n\t}\n\tif (column.source === 'attribute') {\n\t\t// Read the key directly, NOT through the dotted-path reader: a DAV\n\t\t// property name is `{http://owncloud.org/ns}av-status`, and splitting\n\t\t// that on dots looks for a property called `org/ns}av-status` inside\n\t\t// one called `owncloud`, which is undefined every time.\n\t\tconst attributes = node.attributes\n\t\treturn attributes && typeof attributes === 'object' ? attributes[column.attribute] : undefined\n\t}\n\tif (column.source === 'row') {\n\t\tconst byId = rowData && typeof rowData === 'object' ? rowData : null\n\t\tif (!byId) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst entry = byId[node.fileid] ?? byId[String(node.fileid)]\n\t\treturn entry ? entry[column.key] : undefined\n\t}\n\treturn atPath(node, column.nodeKey || column.key)\n}\n\n/**\n * Sort nodes on a declared column's resolved value, within the listed folder.\n *\n * Folders stay first, the way the Files app lists them and the way the\n * built-in sorts already do, so switching to a declared column does not\n * reshuffle the folders into the files.\n *\n * @param {Array<object>} nodes The nodes.\n * @param {object} column The resolved column.\n * @param {boolean} ascending Sort direction.\n * @param {object} [rowData] The host's per-file data.\n * @param {(node: object) => boolean} [isFolder] Whether a node is a folder.\n * @return {Array<object>} A new, sorted array.\n */\nexport function sortNodesByColumn(nodes, column, ascending, rowData = null, isFolder = () => false) {\n\tconst list = Array.isArray(nodes) ? [...nodes] : []\n\tif (!column) {\n\t\treturn list\n\t}\n\tconst direction = ascending ? 1 : -1\n\treturn list.sort((a, b) => {\n\t\tconst folderA = isFolder(a)\n\t\tconst folderB = isFolder(b)\n\t\tif (folderA !== folderB) {\n\t\t\treturn folderA ? -1 : 1\n\t\t}\n\t\tconst left = fileColumnValue(column, a, rowData)\n\t\tconst right = fileColumnValue(column, b, rowData)\n\t\tconst leftMissing = left === undefined || left === null || left === ''\n\t\tconst rightMissing = right === undefined || right === null || right === ''\n\t\t// A value nobody filled in sorts last in BOTH directions. Letting it\n\t\t// ride to the top on a descending sort hides the rows that do carry\n\t\t// one, which is the opposite of what sorting was asked for.\n\t\tif (leftMissing !== rightMissing) {\n\t\t\treturn leftMissing ? 1 : -1\n\t\t}\n\t\tif (leftMissing && rightMissing) {\n\t\t\treturn 0\n\t\t}\n\t\tif (typeof left === 'number' && typeof right === 'number') {\n\t\t\treturn (left - right) * direction\n\t\t}\n\t\treturn String(left).localeCompare(String(right)) * direction\n\t})\n}\n\n/**\n * Whether a cell value counts as filled in.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} value The value.\n * @return {boolean} False for nothing, an empty string or an empty list.\n */\nfunction isFilled(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn false\n\t}\n\treturn !(Array.isArray(value) && value.length === 0)\n}\n\n/**\n * The single value a node is grouped under, as a string key: a list groups\n * under its first entry, an object under its label, title, name or id.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} value The cell value.\n * @return {(string|null)} The group key, or null when nothing is filled in.\n */\nfunction groupKeyOf(value) {\n\tconst first = Array.isArray(value) ? value[0] : value\n\tif (!isFilled(first)) {\n\t\treturn null\n\t}\n\tif (typeof first === 'object') {\n\t\tconst label = first.label ?? first.title ?? first.name ?? first.id\n\t\treturn isFilled(label) ? String(label) : null\n\t}\n\treturn String(first)\n}\n\n/**\n * The values a node carries in a column, as strings, for counting and\n * filtering: every entry of a list, the one value otherwise.\n *\n * @param {object} column A resolved column.\n * @param {object} node The node.\n * @param {object} [rowData] The host's per-file data.\n * @return {string[]} The values, deduplicated.\n */\nfunction facetValuesOf(column, node, rowData) {\n\tconst value = fileColumnValue(column, node, rowData)\n\tconst entries = Array.isArray(value) ? value : [value]\n\tconst out = []\n\tentries.forEach((entry) => {\n\t\tconst key = groupKeyOf(entry)\n\t\tif (key !== null && !out.includes(key)) {\n\t\t\tout.push(key)\n\t\t}\n\t})\n\treturn out\n}\n\n/**\n * Group nodes on a column's value, in the order each value is first met.\n * Nodes with no value come last, in one group whose value is null.\n *\n * @param {Array<object>} nodes The nodes, already in display order.\n * @param {object} column The resolved column.\n * @param {object} [rowData] The host's per-file data.\n * @return {Array<{value: (string|null), nodes: Array<object>}>} The groups.\n */\nexport function groupNodes(nodes, column, rowData = null) {\n\tconst groups = []\n\tconst byKey = new Map()\n\tconst empty = { value: null, nodes: [] }\n\t;(Array.isArray(nodes) ? nodes : []).forEach((node) => {\n\t\tconst key = column ? groupKeyOf(fileColumnValue(column, node, rowData)) : null\n\t\tif (key === null) {\n\t\t\tempty.nodes.push(node)\n\t\t\treturn\n\t\t}\n\t\tif (!byKey.has(key)) {\n\t\t\tconst group = { value: key, nodes: [] }\n\t\t\tbyKey.set(key, group)\n\t\t\tgroups.push(group)\n\t\t}\n\t\tbyKey.get(key).nodes.push(node)\n\t})\n\tif (empty.nodes.length > 0) {\n\t\tgroups.push(empty)\n\t}\n\treturn groups\n}\n\n/**\n * The values in use in a column across the nodes, with how many nodes carry\n * each; a list counts once per entry. Most used first, then in first-seen\n * order.\n *\n * @param {Array<object>} nodes The nodes.\n * @param {object} column The resolved column.\n * @param {object} [rowData] The host's per-file data.\n * @return {Array<{value: string, count: number}>} The values and counts.\n */\nexport function facetCounts(nodes, column, rowData = null) {\n\tconst counts = []\n\tconst byValue = new Map()\n\t;(Array.isArray(nodes) ? nodes : []).forEach((node) => {\n\t\tfacetValuesOf(column, node, rowData).forEach((value) => {\n\t\t\tif (!byValue.has(value)) {\n\t\t\t\tconst entry = { value, count: 0 }\n\t\t\t\tbyValue.set(value, entry)\n\t\t\t\tcounts.push(entry)\n\t\t\t}\n\t\t\tbyValue.get(value).count += 1\n\t\t})\n\t})\n\treturn counts\n\t\t.map((entry, index) => ({ entry, index }))\n\t\t.sort((a, b) => (b.entry.count - a.entry.count) || (a.index - b.index))\n\t\t.map(({ entry }) => entry)\n}\n\n/**\n * Whether a node passes the chosen facet values: within one facet any chosen\n * value will do, and every facet with a choice must pass. A facet with\n * nothing chosen does not filter.\n *\n * @param {object} node The node.\n * @param {object} selections Chosen values by column key.\n * @param {object} columnsByKey Resolved columns by key.\n * @param {object} [rowData] The host's per-file data.\n * @return {boolean} True when the node stays in the list.\n */\nexport function nodeMatchesFacets(node, selections, columnsByKey, rowData = null) {\n\treturn Object.entries(selections || {}).every(([key, chosen]) => {\n\t\tif (!Array.isArray(chosen) || chosen.length === 0) {\n\t\t\treturn true\n\t\t}\n\t\tconst column = columnsByKey && columnsByKey[key]\n\t\tif (!column) {\n\t\t\treturn true\n\t\t}\n\t\tconst values = facetValuesOf(column, node, rowData)\n\t\treturn chosen.some((value) => values.includes(value))\n\t})\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"filesBrowserColumns.js","sources":["../../../../src/components/CnFilesBrowser/filesBrowserColumns.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * The built-in columns a host may name by string, and what each one reads.\n *\n * `nodeKey` is the property on the @nextcloud/files node. `sortKey` is what\n * `sortNodes` understands, so a built-in sorts the way the Files app sorts;\n * a built-in with no `sortKey` sorts client-side like a declared column.\n *\n * @type {object}\n */\nexport const BUILT_IN_FILE_COLUMNS = {\n\tname: { key: 'name', source: 'node', nodeKey: 'basename', sortKey: 'basename', sortable: true },\n\tsize: { key: 'size', source: 'node', nodeKey: 'size', sortKey: 'size', sortable: true },\n\tmodified: { key: 'modified', source: 'node', nodeKey: 'mtime', sortKey: 'mtime', sortable: true },\n\towner: { key: 'owner', source: 'node', nodeKey: 'owner', sortable: true },\n\ttype: { key: 'type', source: 'node', nodeKey: 'mime', sortable: true },\n\ttags: { key: 'tags', source: 'node', nodeKey: 'attributes.system-tags', sortable: false },\n}\n\n/**\n * The columns a browser shows when its host declares none: what the table has\n * always shown, in the order it has always shown it.\n *\n * @type {string[]}\n */\nexport const DEFAULT_FILE_COLUMNS = ['name', 'size', 'modified']\n\n/**\n * Read a dotted path off a node without throwing on a missing segment.\n *\n * @param {object} node The node.\n * @param {string} path A dotted path.\n * @return {(Array|object|string|number|boolean|undefined)} The value.\n */\nfunction atPath(node, path) {\n\tif (!node || typeof path !== 'string' || path === '') {\n\t\treturn undefined\n\t}\n\treturn path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), node)\n}\n\n/**\n * Whether a string names a built-in column.\n *\n * @param {string} name The candidate name.\n * @return {boolean} True when it is a built-in.\n */\nexport function isBuiltInFileColumn(name) {\n\treturn typeof name === 'string' && Object.hasOwn(BUILT_IN_FILE_COLUMNS, name)\n}\n\n/**\n * Resolve a host's `columns` declaration into column objects.\n *\n * A string names a built-in. An object declares its own: `key`, `label`,\n * `source` (`node`, `attribute` or `row`), `attribute`, `formatter` and\n * `sortable`. An entry naming an unknown built-in is dropped here and refused\n * by the manifest validator, so an author hears about it at authoring time\n * rather than finding a column missing at runtime.\n *\n * @param {(Array|null|undefined)} columns The declaration, or nothing.\n * @return {Array<object>} The resolved columns, in the declared order.\n */\nexport function normaliseFileColumns(columns) {\n\tconst declared = Array.isArray(columns) && columns.length > 0 ? columns : DEFAULT_FILE_COLUMNS\n\tconst out = []\n\tdeclared.forEach((entry) => {\n\t\tif (typeof entry === 'string') {\n\t\t\tif (isBuiltInFileColumn(entry)) {\n\t\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry], builtIn: true })\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\treturn\n\t\t}\n\t\tif (isBuiltInFileColumn(entry.key) && entry.source === undefined) {\n\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry.key], ...entry, builtIn: true })\n\t\t\treturn\n\t\t}\n\t\tout.push({\n\t\t\tsource: 'node',\n\t\t\tsortable: false,\n\t\t\t...entry,\n\t\t\tbuiltIn: false,\n\t\t})\n\t})\n\treturn out\n}\n\n/**\n * The columns a browser renders: the host's declaration, narrowed by what this\n * user chose to see.\n *\n * Which side wins on what:\n *\n * - The HOST decides MEMBERSHIP. The result is built by walking the\n * declaration, so a column exists only because the host declared it, in the\n * order the host declared it.\n * - The USER decides VISIBILITY, and only downward. A stored preference can\n * hide a declared column; it can never add one. A key in the stored list\n * that the host no longer declares is passed over, so unticking a column\n * last month cannot bring it back once the host removed it, and neither can\n * a preference written while a different column set was declared.\n *\n * A stored value of `null` is a user who has never chosen, which shows\n * everything. An empty list is a user who hid everything, which is a choice\n * and is honoured.\n *\n * @param {Array<object>} declared The resolved declared columns.\n * @param {(string[]|null|undefined)} visibleKeys The user's stored visible keys.\n * @return {Array<object>} The columns to render.\n */\nexport function visibleFileColumns(declared, visibleKeys) {\n\tconst columns = Array.isArray(declared) ? declared : []\n\tif (!Array.isArray(visibleKeys)) {\n\t\treturn columns\n\t}\n\tconst wanted = new Set(visibleKeys)\n\treturn columns.filter((column) => wanted.has(column.key))\n}\n\n/**\n * The DAV properties a declaration needs in the PROPFIND, so an `attribute`\n * column's value is there when the folder lists rather than one request later.\n *\n * @param {Array<object>} columns The resolved columns.\n * @return {string[]} The property names, deduplicated, in declaration order.\n */\nexport function attributePropertiesFor(columns) {\n\tconst out = []\n\t;(Array.isArray(columns) ? columns : []).forEach((column) => {\n\t\tif (column && column.source === 'attribute' && typeof column.attribute === 'string' && column.attribute !== '') {\n\t\t\tif (!out.includes(column.attribute)) {\n\t\t\t\tout.push(column.attribute)\n\t\t\t}\n\t\t}\n\t})\n\treturn out\n}\n\n/**\n * The value one column shows for one node. A `row` column's key may be a\n * dotted path into the host's record.\n *\n * @param {object} column A resolved column.\n * @param {object} node The node.\n * @param {object} [rowData] The host's per-file data, keyed by file id.\n * @return {(Array|object|string|number|boolean|undefined)} The cell value.\n */\nexport function fileColumnValue(column, node, rowData = null) {\n\tif (!column || !node) {\n\t\treturn undefined\n\t}\n\tif (column.source === 'attribute') {\n\t\t// Read the key directly, NOT through the dotted-path reader: a DAV\n\t\t// property name is `{http://owncloud.org/ns}av-status`, and splitting\n\t\t// that on dots looks for a property called `org/ns}av-status` inside\n\t\t// one called `owncloud`, which is undefined every time.\n\t\tconst attributes = node.attributes\n\t\treturn attributes && typeof attributes === 'object' ? attributes[column.attribute] : undefined\n\t}\n\tif (column.source === 'row') {\n\t\tconst byId = rowData && typeof rowData === 'object' ? rowData : null\n\t\tif (!byId) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst entry = byId[node.fileid] ?? byId[String(node.fileid)]\n\t\tif (!entry) {\n\t\t\treturn undefined\n\t\t}\n\t\t// A key the record holds as written wins; otherwise a dotted key reads\n\t\t// a nested field (`scanVerdict.state`, `integrity.value`).\n\t\treturn Object.hasOwn(entry, column.key) ? entry[column.key] : atPath(entry, column.key)\n\t}\n\treturn atPath(node, column.nodeKey || column.key)\n}\n\n/**\n * Sort nodes on a declared column's resolved value, within the listed folder.\n *\n * Folders stay first, the way the Files app lists them and the way the\n * built-in sorts already do, so switching to a declared column does not\n * reshuffle the folders into the files.\n *\n * @param {Array<object>} nodes The nodes.\n * @param {object} column The resolved column.\n * @param {boolean} ascending Sort direction.\n * @param {object} [rowData] The host's per-file data.\n * @param {(node: object) => boolean} [isFolder] Whether a node is a folder.\n * @return {Array<object>} A new, sorted array.\n */\nexport function sortNodesByColumn(nodes, column, ascending, rowData = null, isFolder = () => false) {\n\tconst list = Array.isArray(nodes) ? [...nodes] : []\n\tif (!column) {\n\t\treturn list\n\t}\n\tconst direction = ascending ? 1 : -1\n\treturn list.sort((a, b) => {\n\t\tconst folderA = isFolder(a)\n\t\tconst folderB = isFolder(b)\n\t\tif (folderA !== folderB) {\n\t\t\treturn folderA ? -1 : 1\n\t\t}\n\t\tconst left = fileColumnValue(column, a, rowData)\n\t\tconst right = fileColumnValue(column, b, rowData)\n\t\tconst leftMissing = left === undefined || left === null || left === ''\n\t\tconst rightMissing = right === undefined || right === null || right === ''\n\t\t// A value nobody filled in sorts last in BOTH directions. Letting it\n\t\t// ride to the top on a descending sort hides the rows that do carry\n\t\t// one, which is the opposite of what sorting was asked for.\n\t\tif (leftMissing !== rightMissing) {\n\t\t\treturn leftMissing ? 1 : -1\n\t\t}\n\t\tif (leftMissing && rightMissing) {\n\t\t\treturn 0\n\t\t}\n\t\tif (typeof left === 'number' && typeof right === 'number') {\n\t\t\treturn (left - right) * direction\n\t\t}\n\t\treturn String(left).localeCompare(String(right)) * direction\n\t})\n}\n\n/**\n * Whether a cell value counts as filled in.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} value The value.\n * @return {boolean} False for nothing, an empty string or an empty list.\n */\nfunction isFilled(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn false\n\t}\n\treturn !(Array.isArray(value) && value.length === 0)\n}\n\n/**\n * The single value a node is grouped under, as a string key: a list groups\n * under its first entry, an object under its label, title, name or id.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} value The cell value.\n * @return {(string|null)} The group key, or null when nothing is filled in.\n */\nfunction groupKeyOf(value) {\n\tconst first = Array.isArray(value) ? value[0] : value\n\tif (!isFilled(first)) {\n\t\treturn null\n\t}\n\tif (typeof first === 'object') {\n\t\tconst label = first.label ?? first.title ?? first.name ?? first.id\n\t\treturn isFilled(label) ? String(label) : null\n\t}\n\treturn String(first)\n}\n\n/**\n * The values a node carries in a column, as strings, for counting and\n * filtering: every entry of a list, the one value otherwise.\n *\n * @param {object} column A resolved column.\n * @param {object} node The node.\n * @param {object} [rowData] The host's per-file data.\n * @return {string[]} The values, deduplicated.\n */\nfunction facetValuesOf(column, node, rowData) {\n\tconst value = fileColumnValue(column, node, rowData)\n\tconst entries = Array.isArray(value) ? value : [value]\n\tconst out = []\n\tentries.forEach((entry) => {\n\t\tconst key = groupKeyOf(entry)\n\t\tif (key !== null && !out.includes(key)) {\n\t\t\tout.push(key)\n\t\t}\n\t})\n\treturn out\n}\n\n/**\n * Group nodes on a column's value, in the order each value is first met.\n * Nodes with no value come last, in one group whose value is null.\n *\n * @param {Array<object>} nodes The nodes, already in display order.\n * @param {object} column The resolved column.\n * @param {object} [rowData] The host's per-file data.\n * @return {Array<{value: (string|null), nodes: Array<object>}>} The groups.\n */\nexport function groupNodes(nodes, column, rowData = null) {\n\tconst groups = []\n\tconst byKey = new Map()\n\tconst empty = { value: null, nodes: [] }\n\t;(Array.isArray(nodes) ? nodes : []).forEach((node) => {\n\t\tconst key = column ? groupKeyOf(fileColumnValue(column, node, rowData)) : null\n\t\tif (key === null) {\n\t\t\tempty.nodes.push(node)\n\t\t\treturn\n\t\t}\n\t\tif (!byKey.has(key)) {\n\t\t\tconst group = { value: key, nodes: [] }\n\t\t\tbyKey.set(key, group)\n\t\t\tgroups.push(group)\n\t\t}\n\t\tbyKey.get(key).nodes.push(node)\n\t})\n\tif (empty.nodes.length > 0) {\n\t\tgroups.push(empty)\n\t}\n\treturn groups\n}\n\n/**\n * The values in use in a column across the nodes, with how many nodes carry\n * each; a list counts once per entry. Most used first, then in first-seen\n * order.\n *\n * @param {Array<object>} nodes The nodes.\n * @param {object} column The resolved column.\n * @param {object} [rowData] The host's per-file data.\n * @return {Array<{value: string, count: number}>} The values and counts.\n */\nexport function facetCounts(nodes, column, rowData = null) {\n\tconst counts = []\n\tconst byValue = new Map()\n\t;(Array.isArray(nodes) ? nodes : []).forEach((node) => {\n\t\tfacetValuesOf(column, node, rowData).forEach((value) => {\n\t\t\tif (!byValue.has(value)) {\n\t\t\t\tconst entry = { value, count: 0 }\n\t\t\t\tbyValue.set(value, entry)\n\t\t\t\tcounts.push(entry)\n\t\t\t}\n\t\t\tbyValue.get(value).count += 1\n\t\t})\n\t})\n\treturn counts\n\t\t.map((entry, index) => ({ entry, index }))\n\t\t.sort((a, b) => (b.entry.count - a.entry.count) || (a.index - b.index))\n\t\t.map(({ entry }) => entry)\n}\n\n/**\n * Whether a node passes the chosen facet values: within one facet any chosen\n * value will do, and every facet with a choice must pass. A facet with\n * nothing chosen does not filter.\n *\n * @param {object} node The node.\n * @param {object} selections Chosen values by column key.\n * @param {object} columnsByKey Resolved columns by key.\n * @param {object} [rowData] The host's per-file data.\n * @return {boolean} True when the node stays in the list.\n */\nexport function nodeMatchesFacets(node, selections, columnsByKey, rowData = null) {\n\treturn Object.entries(selections || {}).every(([key, chosen]) => {\n\t\tif (!Array.isArray(chosen) || chosen.length === 0) {\n\t\t\treturn true\n\t\t}\n\t\tconst column = columnsByKey && columnsByKey[key]\n\t\tif (!column) {\n\t\t\treturn true\n\t\t}\n\t\tconst values = facetValuesOf(column, node, rowData)\n\t\treturn chosen.some((value) => values.includes(value))\n\t})\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,qBAAqB,GAAG;AACrC,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE;AAChG,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;AACxF,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;AAClG,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;AAC1E,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;AACvE,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,KAAK,EAAE;AAC1F,EAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE;AAC5B,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,EAAE;AACvD,EAAE,OAAO,SAAS;AAClB,EAAE;AACF,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,GAAG,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC;AAC9G,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,IAAI,EAAE;AAC1C,CAAC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC;AAC9E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,OAAO,EAAE;AAC9C,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,OAAO,GAAG,qBAAoB;AAC/F,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AAC7B,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACjC,GAAG,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE;AACnC,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAC;AAChE,IAAI;AACJ,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,EAAE;AAChG,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE;AACpE,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAC;AAC7E,GAAG,MAAM;AACT,GAAG;AACH,EAAE,GAAG,CAAC,IAAI,CAAC;AACX,GAAG,MAAM,EAAE,MAAM;AACjB,GAAG,QAAQ,EAAE,KAAK;AAClB,GAAG,GAAG,KAAK;AACX,GAAG,OAAO,EAAE,KAAK;AACjB,GAAG,EAAC;AACJ,EAAE,EAAC;AACH,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE;AAC1D,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAAG,GAAE;AACxD,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE;AAClC,EAAE,OAAO,OAAO;AAChB,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,WAAW,EAAC;AACpC,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,sBAAsB,CAAC,OAAO,EAAE;AAChD,CAAC,MAAM,GAAG,GAAG,EAAE;AACf,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK;AAC9D,EAAE,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,KAAK,EAAE,EAAE;AAClH,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE;AACxC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAC;AAC9B,IAAI;AACJ,GAAG;AACH,EAAE,EAAC;AACH,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE;AAC9D,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE;AACvB,EAAE,OAAO,SAAS;AAClB,EAAE;AACF,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE;AACpC;AACA;AACA;AACA;AACA,EAAE,MAAM,UAAU,GAAG,IAAI,CAAC,WAAU;AACpC,EAAE,OAAO,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,SAAS;AAChG,EAAE;AACF,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,EAAE;AAC9B,EAAE,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,GAAG,OAAO,GAAG,KAAI;AACtE,EAAE,IAAI,CAAC,IAAI,EAAE;AACb,GAAG,OAAO,SAAS;AACnB,GAAG;AACH,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAC;AAC9D,EAAE,IAAI,CAAC,KAAK,EAAE;AACd,GAAG,OAAO,SAAS;AACnB,GAAG;AACH;AACA;AACA,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC;AACzF,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC;AAClD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,GAAG,MAAM,KAAK,EAAE;AACpG,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,GAAE;AACpD,CAAC,IAAI,CAAC,MAAM,EAAE;AACd,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,SAAS,GAAG,SAAS,GAAG,CAAC,GAAG,CAAC,EAAC;AACrC,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;AAC5B,EAAE,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,EAAC;AAC7B,EAAE,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,EAAC;AAC7B,EAAE,IAAI,OAAO,KAAK,OAAO,EAAE;AAC3B,GAAG,OAAO,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC;AAC1B,GAAG;AACH,EAAE,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAC;AAClD,EAAE,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAC;AACnD,EAAE,MAAM,WAAW,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAE;AACxE,EAAE,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,GAAE;AAC5E;AACA;AACA;AACA,EAAE,IAAI,WAAW,KAAK,YAAY,EAAE;AACpC,GAAG,OAAO,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC;AAC9B,GAAG;AACH,EAAE,IAAI,WAAW,IAAI,YAAY,EAAE;AACnC,GAAG,OAAO,CAAC;AACX,GAAG;AACH,EAAE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC7D,GAAG,OAAO,CAAC,IAAI,GAAG,KAAK,IAAI,SAAS;AACpC,GAAG;AACH,EAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS;AAC9D,EAAE,CAAC;AACH,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,QAAQ,CAAC,KAAK,EAAE;AACzB,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;AACrD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,UAAU,CAAC,KAAK,EAAE;AAC3B,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAK;AACtD,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;AACvB,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAChC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,GAAE;AACpE,EAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI;AAC/C,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;AAC9C,CAAC,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAC;AACrD,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,EAAC;AACvD,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AAC5B,EAAE,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,EAAC;AAC/B,EAAE,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC1C,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,EAAC;AAChB,GAAG;AACH,EAAE,EAAC;AACH,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE;AAC1D,CAAC,MAAM,MAAM,GAAG,GAAE;AAClB,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,GAAE;AACxB,CAAC,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;AACzC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK;AACxD,EAAE,MAAM,GAAG,GAAG,MAAM,GAAG,UAAU,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,GAAG,KAAI;AAChF,EAAE,IAAI,GAAG,KAAK,IAAI,EAAE;AACpB,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAC;AACzB,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACvB,GAAG,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,GAAE;AAC1C,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAC;AACxB,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAC;AACrB,GAAG;AACH,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAC;AACjC,EAAE,EAAC;AACH,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;AAC7B,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAC;AACpB,EAAE;AACF,CAAC,OAAO,MAAM;AACd,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE;AAC3D,CAAC,MAAM,MAAM,GAAG,GAAE;AAClB,CAAC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE;AAC1B,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK;AACxD,EAAE,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AAC1D,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AAC5B,IAAI,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,GAAE;AACrC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAC;AAC7B,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,EAAC;AACtB,IAAI;AACJ,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAC;AAChC,GAAG,EAAC;AACJ,EAAE,EAAC;AACH,CAAC,OAAO,MAAM;AACd,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AACzE,GAAG,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,CAAC;AAC5B,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,GAAG,IAAI,EAAE;AAClF,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK;AAClE,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE;AACrD,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE,MAAM,MAAM,GAAG,YAAY,IAAI,YAAY,CAAC,GAAG,EAAC;AAClD,EAAE,IAAI,CAAC,MAAM,EAAE;AACf,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAC;AACrD,EAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACvD,EAAE,CAAC;AACH;;;;"}
@@ -1,6 +1,6 @@
1
1
  import { getCurrentUser } from '@nextcloud/auth';
2
2
  import { translate, translatePlural } from '@nextcloud/l10n';
3
- import { NcLoadingIcon, NcActions, NcActionLink, NcActionButton, NcActionCaption, NcActionSeparator, NcActionCheckbox, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue';
3
+ import { NcNoteCard, NcLoadingIcon, NcActions, NcActionLink, NcActionButton, NcActionCaption, NcActionSeparator, NcActionCheckbox, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue';
4
4
  import { ref, getCurrentInstance, markRaw, inject } from 'vue';
5
5
  import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue';
6
6
  import Close from 'vue-material-design-icons/Close.vue';
@@ -31,7 +31,7 @@ import { resolveRegisteredHandler, buildOnSuccessRoute } from '../../utils/actio
31
31
  import { columnFieldsOf, refIdOf, buildValueStages, buildReferenceStages, columnKeyOfRow, resolveDropValue, fullTargetOf, referenceTargetOf, fetchReferenceObjects } from '../../utils/boardReferenceColumns.js';
32
32
  import { reportBindingProblems } from '../../utils/diagnostics.js';
33
33
  import { fetchGroupedCounts } from '../../utils/fetchAggregate.js';
34
- import { fetchFilterCounts } from '../../utils/fetchFilterCounts.js';
34
+ import { fetchListTotal, fetchFilterCounts } from '../../utils/fetchFilterCounts.js';
35
35
  import { buildExportUrl } from '../../utils/indexExportHelpers.js';
36
36
  import { lensUnavailableText } from '../../utils/lensAvailability.js';
37
37
  import { isModifiedClick, openRowTarget } from '../../utils/linkNavigation.js';
@@ -50,7 +50,7 @@ import { isNewTabClick, preventMiddleClickAutoscroll, isRowMiddleClick, isNewTab
50
50
  import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js';
51
51
  import { viewMatchesScope, extractViewState, buildRouteQueryFromViewState, extractViewStateFromRouteQuery, buildViewCreatePayload, savedViewScope, normalizeSharedWith } from '../../utils/savedViewHelpers.js';
52
52
  import { fieldsFromSchema, columnsFromSchema } from '../../utils/schema.js';
53
- import { resolveScopeLayout } from '../../utils/scopeListLayout.js';
53
+ import { resolveScopeLayout, resolveScopePresentation } from '../../utils/scopeListLayout.js';
54
54
  import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js';
55
55
  import __sfc_main$6 from '../CnActionButtons/CnActionButtons.vue.js';
56
56
  import __sfc_main$7 from '../CnActionsBar/CnActionsBar.vue.js';
@@ -325,6 +325,7 @@ var script = {
325
325
 
326
326
  components: {
327
327
  CnIndexPageWidgets: __sfc_main,
328
+ NcNoteCard,
328
329
  CnBuildiqEditButton: __sfc_main$1,
329
330
  CnFollowToggle: __sfc_main$2,
330
331
  NcLoadingIcon,
@@ -493,6 +494,35 @@ var script = {
493
494
  default: '',
494
495
  },
495
496
 
497
+ /**
498
+ * Tabs to sibling pages above the toolbar (manifest `config.pageTabs`),
499
+ * each `{ id?, label, route?, params?, query?, href?, countFrom?: {
500
+ * register, schema, filter? } }`: DqPrivacy's "AVG-verzoeken |
501
+ * Datalekken". The current page's tab is marked; `countFrom` adds the
502
+ * count of that list. Wins over a named source's own page tabs.
503
+ *
504
+ * @type {Array<object>|null}
505
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
506
+ */
507
+ pageTabs: {
508
+ type: Array,
509
+ default: null,
510
+ },
511
+
512
+ /**
513
+ * A notice under the list (manifest `config.notice`): `{ text,
514
+ * variant?: 'info' | 'success' | 'warning' }`, a band that explains
515
+ * the list, such as who decides what. The text goes through the host
516
+ * translate.
517
+ *
518
+ * @type {{text: string, variant?: string}|null}
519
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-show-a-notice-under-its-list
520
+ */
521
+ notice: {
522
+ type: Object,
523
+ default: null,
524
+ },
525
+
496
526
  /** Optional description shown below the title */
497
527
  description: {
498
528
  type: String,
@@ -1880,6 +1910,33 @@ var script = {
1880
1910
  default: () => [],
1881
1911
  },
1882
1912
 
1913
+ /**
1914
+ * Cards after the list (manifest `config.afterList`): dashboard widget
1915
+ * definitions `{ id, type, title?, content, headerLink? }` stacked
1916
+ * under the list in the listed order, the counterpart of `statRow`
1917
+ * above it. A query preset or a scope can set its own.
1918
+ *
1919
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-an-index-page-can-draw-widgets-after-its-list
1920
+ * @type {Array<object>}
1921
+ */
1922
+ afterList: {
1923
+ type: Array,
1924
+ default: () => [],
1925
+ },
1926
+
1927
+ /**
1928
+ * The route query pairs that chose this page's query preset, set by
1929
+ * CnPageRenderer. They scope the list, so they show no removable
1930
+ * active-filter chip.
1931
+ *
1932
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-the-query-that-chose-a-preset-is-not-a-removable-filter
1933
+ * @type {object|null}
1934
+ */
1935
+ fixedFilters: {
1936
+ type: Object,
1937
+ default: null,
1938
+ },
1939
+
1883
1940
  /**
1884
1941
  * Where a row carries the actions the server says this caller may run
1885
1942
  * on it: a dotted path, read off the rows the list already fetched, so
@@ -2480,6 +2537,8 @@ var script = {
2480
2537
  tabCounts: null,
2481
2538
  // The counts of the `filterStrip` pills, by pill key ('all', 'v0', ...).
2482
2539
  filterStripCounts: null,
2540
+ /** The `countFrom` counts of the manifest page tabs, by index. */
2541
+ pageTabCounts: {},
2483
2542
  /** The tabs `quickFiltersFrom` produced, or null before they load. */
2484
2543
  generatedQuickFilters: null,
2485
2544
  /** Count per saved-view id, for the views control; null = none. */
@@ -3029,7 +3088,8 @@ var script = {
3029
3088
  }
3030
3089
  // The index boards draw all four segments, so the board look does
3031
3090
  // too unless the manifest names its own list.
3032
- const listed = Array.isArray(this.viewSwitch) ? this.viewSwitch : ['table', 'cards', 'board', 'map'];
3091
+ const viewSwitch = this.presented('viewSwitch');
3092
+ const listed = Array.isArray(viewSwitch) ? viewSwitch : ['table', 'cards', 'board', 'map'];
3033
3093
  const offered = [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])];
3034
3094
  return listed.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
3035
3095
  },
@@ -3500,6 +3560,58 @@ var script = {
3500
3560
  })
3501
3561
  },
3502
3562
 
3563
+ /**
3564
+ * This page's own value of each presentation key, which a query preset
3565
+ * may have set: what `presented()` falls back to without a scope.
3566
+ *
3567
+ * @return {object}
3568
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
3569
+ */
3570
+ ownPresentation() {
3571
+ return {
3572
+ title: this.title,
3573
+ quickFilters: this.quickFilters,
3574
+ cardFields: this.cardFields,
3575
+ countText: this.countText,
3576
+ searchPlaceholder: this.searchPlaceholder,
3577
+ footerNote: this.footerNote,
3578
+ viewSwitch: this.viewSwitch,
3579
+ addLabel: this.addLabel,
3580
+ headerButtons: this.headerButtons,
3581
+ breadcrumb: this.breadcrumb,
3582
+ headerMeta: this.headerMeta,
3583
+ afterList: this.afterList,
3584
+ }
3585
+ },
3586
+
3587
+ /**
3588
+ * The presentation the active scope declares (`title`, `countText`,
3589
+ * `quickFilters`, `addLabel`, `afterList` and the other query preset
3590
+ * keys), from its folder entry or its row's `x-index`. Empty without a
3591
+ * scope, so the page's own values stand.
3592
+ *
3593
+ * @return {object}
3594
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
3595
+ */
3596
+ activeScopePresentation() {
3597
+ if (!this.folderSidebar) {
3598
+ return {}
3599
+ }
3600
+ return resolveScopePresentation({ scope: this.activeScope, row: this.activeScopeRow })
3601
+ },
3602
+
3603
+ /**
3604
+ * The widgets drawn after the list: the active scope's `afterList`,
3605
+ * else the page's.
3606
+ *
3607
+ * @return {Array<object>}
3608
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-an-index-page-can-draw-widgets-after-its-list
3609
+ */
3610
+ presentedAfterList() {
3611
+ const list = this.presented('afterList');
3612
+ return Array.isArray(list) ? list : []
3613
+ },
3614
+
3503
3615
  /**
3504
3616
  * The fields the search box searches over while a scope declaring
3505
3617
  * `searchFields` is active. Read by `useSelfFetchList`'s fixed-filter
@@ -3959,8 +4071,9 @@ var script = {
3959
4071
  * @return {?Array<string>}
3960
4072
  */
3961
4073
  resolvedCardFields() {
3962
- if (Array.isArray(this.cardFields) && this.cardFields.length > 0) {
3963
- return this.cardFields
4074
+ const cardFields = this.presented('cardFields');
4075
+ if (Array.isArray(cardFields) && cardFields.length > 0) {
4076
+ return cardFields
3964
4077
  }
3965
4078
  if (!this.isBoardLook) {
3966
4079
  return null
@@ -3999,6 +4112,10 @@ var script = {
3999
4112
  }
4000
4113
  });
4001
4114
  }
4115
+ // An `actions` column: the page's row actions it names, as buttons.
4116
+ cols = cols.map((col) => (col && typeof col === 'object' && col.widget === 'actions'
4117
+ ? { ...col, sortable: false, widgetProps: { ...(col.widgetProps || {}), items: (row) => this.actionColumnItems(col, row), run: this.runColumnAction } }
4118
+ : col));
4002
4119
  // The Recent lens owns the order: no column sorts while it is on.
4003
4120
  if (this.recentLensActive) {
4004
4121
  cols = cols.map((col) => (col && typeof col === 'object' ? { ...col, sortable: false } : col));
@@ -4875,6 +4992,51 @@ var script = {
4875
4992
  return (source.pageTabs() || []).filter((tab) => tab && tab.to && tab.label)
4876
4993
  },
4877
4994
 
4995
+ /**
4996
+ * The page tabs to draw: the manifest's `pageTabs`, each with its
4997
+ * `countFrom` count once known, else a named source's own.
4998
+ *
4999
+ * @return {Array<object>} The tabs, `{ id, label, to, count? }`.
5000
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
5001
+ */
5002
+ /**
5003
+ * The notice band's NcNoteCard type: its `variant`, else info.
5004
+ *
5005
+ * @return {string}
5006
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-show-a-notice-under-its-list
5007
+ */
5008
+ noticeType() {
5009
+ const variant = this.notice && this.notice.variant;
5010
+ return ['success', 'warning', 'error'].includes(variant) ? variant : 'info'
5011
+ },
5012
+
5013
+ effectivePageTabs() {
5014
+ if (!Array.isArray(this.pageTabs) || this.pageTabs.length === 0) {
5015
+ return this.sourcePageTabs
5016
+ }
5017
+ return this.pageTabs
5018
+ .filter((tab) => tab && typeof tab.label === 'string' && tab.label !== '' && (tab.route || tab.to || tab.href))
5019
+ .map((tab, index) => {
5020
+ const to = tab.route
5021
+ ? { name: tab.route, ...(tab.params ? { params: tab.params } : {}), ...(tab.query ? { query: tab.query } : {}) }
5022
+ : (tab.to || tab.href);
5023
+ const count = this.pageTabCounts[index];
5024
+ return { id: tab.id || `page-tab-${index}`, label: this.cnTranslate(tab.label), to, ...(typeof count === 'number' ? { count } : {}) }
5025
+ })
5026
+ },
5027
+
5028
+ /**
5029
+ * What the page tab counts depend on, as one key.
5030
+ *
5031
+ * @return {string}
5032
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
5033
+ */
5034
+ pageTabCountKey() {
5035
+ const tabs = Array.isArray(this.pageTabs) ? this.pageTabs : [];
5036
+ const sources = tabs.map((tab) => (tab && tab.countFrom && tab.countFrom.register && tab.countFrom.schema ? tab.countFrom : null));
5037
+ return sources.some(Boolean) ? JSON.stringify(sources) : ''
5038
+ },
5039
+
4878
5040
  /**
4879
5041
  * The numbers on the quick filters: a named source counts its own
4880
5042
  * tabs (`tabCounts()`), a self-fetching page asks OpenRegister.
@@ -4896,11 +5058,15 @@ var script = {
4896
5058
  * @return {string} The note, translated, or ''.
4897
5059
  */
4898
5060
  effectiveFooterNote() {
4899
- if (this.footerNote) {
4900
- return this.cnTranslate(this.footerNote)
5061
+ // The scope's note, else the page's (a preset may have set it),
5062
+ // else the named source's.
5063
+ const scoped = this.activeScopePresentation ? this.activeScopePresentation.footerNote : undefined;
5064
+ const footerNote = scoped !== undefined ? scoped : this.footerNote;
5065
+ if (footerNote) {
5066
+ return this.cnTranslate(footerNote)
4901
5067
  }
4902
5068
  const source = this.isNamedSource ? this.namedSource : null;
4903
- return (source && source.footerNote) || this.footerNote
5069
+ return (source && source.footerNote) || footerNote
4904
5070
  },
4905
5071
 
4906
5072
  /**
@@ -4911,7 +5077,7 @@ var script = {
4911
5077
  * @return {Array<object>|null} The tabs, or null when there are none.
4912
5078
  */
4913
5079
  effectiveQuickFilters() {
4914
- const own = withPersonalLenses(this.quickFilters || this.generatedQuickFilters, this.personalLenses);
5080
+ const own = withPersonalLenses(this.presented('quickFilters') || this.generatedQuickFilters, this.personalLenses);
4915
5081
  if (own && own.length > 0) {
4916
5082
  return own
4917
5083
  }
@@ -4977,7 +5143,8 @@ var script = {
4977
5143
  * @return {Array<{key: string, label: string, action: string, variant: string, icon: string, format: string}>}
4978
5144
  */
4979
5145
  resolvedHeaderButtons() {
4980
- const declared = Array.isArray(this.headerButtons) ? this.headerButtons : [];
5146
+ const headerButtons = this.presented('headerButtons');
5147
+ const declared = Array.isArray(headerButtons) ? headerButtons : [];
4981
5148
  return declared
4982
5149
  .filter((button) => button && typeof button === 'object' && typeof button.action === 'string' && button.action !== '')
4983
5150
  .map((button, index) => {
@@ -5102,6 +5269,7 @@ var script = {
5102
5269
  for (const [key, raw] of Object.entries(this.effectiveActiveFilters || {})) {
5103
5270
  const values = (Array.isArray(raw) ? raw : [raw])
5104
5271
  .filter((value) => value !== null && value !== undefined && value !== '')
5272
+ .filter((value) => !this.isFixedFilter(key, value))
5105
5273
  .map((value) => this.activeFilterValueLabel(key, value));
5106
5274
  if (values.length === 0) {
5107
5275
  continue
@@ -5285,11 +5453,12 @@ var script = {
5285
5453
  * @return {string}
5286
5454
  */
5287
5455
  headerMetaText() {
5288
- if (!this.headerMeta) {
5456
+ const headerMeta = this.presented('headerMeta');
5457
+ if (!headerMeta) {
5289
5458
  return ''
5290
5459
  }
5291
5460
  const total = this.effectivePagination?.total;
5292
- return this.cnTranslate(this.headerMeta)
5461
+ return this.cnTranslate(headerMeta)
5293
5462
  .replace('{shown}', String(this.effectiveObjects.length))
5294
5463
  .replace('{total}', typeof total === 'number' ? String(total) : '')
5295
5464
  },
@@ -5303,11 +5472,12 @@ var script = {
5303
5472
  // The default count line is the library's own string, so it
5304
5473
  // reads "14 van 14" in Dutch; a page's own template goes
5305
5474
  // through the app's label lookup.
5306
- if (!this.countText && !this.countSubtitle) {
5475
+ const countText = this.presented('countText');
5476
+ if (!countText && !this.countSubtitle) {
5307
5477
  return translate('nextcloud-vue', '{shown} of {total}', { shown, total })
5308
5478
  }
5309
- if (this.countText) {
5310
- return this.cnTranslate(this.countText)
5479
+ if (countText) {
5480
+ return this.cnTranslate(countText)
5311
5481
  .replace('{shown}', String(shown))
5312
5482
  .replace('{total}', String(total))
5313
5483
  }
@@ -5321,8 +5491,10 @@ var script = {
5321
5491
 
5322
5492
  /** Add button label — derived from schema.title if not explicitly set */
5323
5493
  resolvedAddLabel() {
5324
- if (this.addLabel) {
5325
- return this.cnTranslate(this.addLabel)
5494
+ const scoped = this.activeScopePresentation ? this.activeScopePresentation.addLabel : undefined;
5495
+ const addLabel = scoped !== undefined ? scoped : this.addLabel;
5496
+ if (addLabel) {
5497
+ return this.cnTranslate(addLabel)
5326
5498
  }
5327
5499
  // A named source names its own create action. Without this the button
5328
5500
  // falls back to a schema-derived noun, and a named source has no
@@ -5547,6 +5719,14 @@ var script = {
5547
5719
  },
5548
5720
  },
5549
5721
 
5722
+ pageTabCountKey: {
5723
+ immediate: true,
5724
+ /** @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs */
5725
+ handler() {
5726
+ this.loadPageTabCounts();
5727
+ },
5728
+ },
5729
+
5550
5730
  filterStripCountKey: {
5551
5731
  immediate: true,
5552
5732
  /** @spec openspec/changes/screens-filter-strip/specs/index-filter-strip/spec.md#requirement-an-index-page-can-draw-a-second-filter-strip */
@@ -5814,6 +5994,36 @@ var script = {
5814
5994
  },
5815
5995
 
5816
5996
  methods: {
5997
+ /**
5998
+ * A presentation value: the active scope's when it declares the key,
5999
+ * else this page's own prop (which a query preset may have set).
6000
+ *
6001
+ * @param {string} key One of the query preset keys.
6002
+ * @return {unknown} The value.
6003
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
6004
+ */
6005
+ presented(key) {
6006
+ const scoped = this.activeScopePresentation;
6007
+ return scoped[key] !== undefined ? scoped[key] : this.ownPresentation[key]
6008
+ },
6009
+
6010
+ /**
6011
+ * Whether a filter value is one of the query pairs that chose the
6012
+ * preset, so it shows no removable chip.
6013
+ *
6014
+ * @param {string} key The filter key.
6015
+ * @param {unknown} value One filter value.
6016
+ * @return {boolean}
6017
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-the-query-that-chose-a-preset-is-not-a-removable-filter
6018
+ */
6019
+ isFixedFilter(key, value) {
6020
+ const fixed = this.fixedFilters;
6021
+ if (!fixed || !Object.hasOwn(fixed, key)) {
6022
+ return false
6023
+ }
6024
+ return String(fixed[key]) === String(value)
6025
+ },
6026
+
5817
6027
  /**
5818
6028
  * The manifest page that shows one object of a schema, for a linked
5819
6029
  * label. Null without a manifest or a matching detail page.
@@ -5927,6 +6137,32 @@ var script = {
5927
6137
  ];
5928
6138
  },
5929
6139
 
6140
+ /**
6141
+ * Count the lists behind the manifest page tabs (`countFrom`), one list
6142
+ * request each. A failed count shows no number.
6143
+ *
6144
+ * @return {Promise<void>}
6145
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
6146
+ */
6147
+ async loadPageTabCounts() {
6148
+ const key = this.pageTabCountKey;
6149
+ if (key === '') {
6150
+ this.pageTabCounts = {};
6151
+ return
6152
+ }
6153
+ const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {};
6154
+ const counts = {};
6155
+ await Promise.all(this.pageTabs.map(async (tab, index) => {
6156
+ const from = tab && tab.countFrom;
6157
+ if (from && from.register && from.schema) {
6158
+ counts[index] = await fetchListTotal(from.register, from.schema, from.filter || {}, ctx).catch(() => null);
6159
+ }
6160
+ }));
6161
+ if (key === this.pageTabCountKey) {
6162
+ this.pageTabCounts = counts;
6163
+ }
6164
+ },
6165
+
5930
6166
  /**
5931
6167
  * Read the counts of the `filterStrip` pills in one grouped request,
5932
6168
  * narrowed by the page filter and the active quick filter. A failed
@@ -7360,6 +7596,50 @@ var script = {
7360
7596
  this.$emit('quick-edit-save', { id, patch });
7361
7597
  },
7362
7598
 
7599
+ /**
7600
+ * The buttons an `actions` column shows on a row: the row actions its
7601
+ * `widgetProps.actions` names (by id, else by label), in that order,
7602
+ * that are visible for the row.
7603
+ *
7604
+ * @param {object} col The column.
7605
+ * @param {object} row The row.
7606
+ * @return {Array<{key: string, label: string, variant: string, disabled: boolean}>}
7607
+ * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
7608
+ */
7609
+ actionColumnItems(col, row) {
7610
+ const wanted = Array.isArray(col.widgetProps && col.widgetProps.actions) ? col.widgetProps.actions : [];
7611
+ const available = this.rowActionsFor(row);
7612
+ return wanted
7613
+ .map((ref) => available.find((action) => action.id === ref) || available.find((action) => action.label === ref))
7614
+ .filter((action) => action && isRowActionVisible(action, row))
7615
+ .map((action, index) => ({
7616
+ key: action.id || action.label,
7617
+ label: this.cnTranslate(action.label),
7618
+ variant: action.variant || (index === 0 ? 'secondary' : 'tertiary'),
7619
+ disabled: typeof action.disabled === 'function' ? !!action.disabled(row) : action.disabled === true,
7620
+ }))
7621
+ },
7622
+
7623
+ /**
7624
+ * Run one `actions` column button: its handler, then the same `action`
7625
+ * event a row menu entry emits.
7626
+ *
7627
+ * @param {string} key The action's id, else its label.
7628
+ * @param {object} row The row.
7629
+ * @return {void}
7630
+ * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
7631
+ */
7632
+ runColumnAction(key, row) {
7633
+ const action = this.rowActionsFor(row).find((entry) => (entry.id || entry.label) === key);
7634
+ if (!action) {
7635
+ return
7636
+ }
7637
+ if (typeof action.handler === 'function') {
7638
+ action.handler(row);
7639
+ }
7640
+ this.onRowAction(rowActionPayload(action, row));
7641
+ },
7642
+
7363
7643
  /**
7364
7644
  * Take the server's version of a row after a conflict. Nothing of this
7365
7645
  * person's edit is written, and the row in the list is refreshed to
@@ -8212,6 +8492,9 @@ var script = {
8212
8492
  * @return {boolean} True for the current page.
8213
8493
  */
8214
8494
  isActivePageTab(tab) {
8495
+ if (tab.to && typeof tab.to === 'object') {
8496
+ return !!this.$route && tab.to.name !== undefined && this.$route.name === tab.to.name
8497
+ }
8215
8498
  const path = this.$route && this.$route.path;
8216
8499
  return !!path && String(path).replace(/\/+$/, '') === String(tab.to).replace(/\/+$/, '')
8217
8500
  },