@conduction/nextcloud-vue 2.140.0 → 2.142.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/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +24 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +3 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +12 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +57 -40
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +34 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +43 -3
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +21 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +14 -0
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +3 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +31 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js +52 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js +119 -65
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +86 -4
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +67 -5
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +11 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +12 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1116 -686
- package/dist/esm/utils/buildViewTree.js +23 -8
- package/dist/esm/utils/buildViewTree.js.map +1 -1
- package/dist/esm/utils/groupSublines.js +120 -0
- package/dist/esm/utils/groupSublines.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +2023 -1239
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1116 -686
- package/l10n/en.json +11 -1
- package/l10n/nl.json +12 -2
- package/package.json +1 -1
- package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +26 -4
- package/src/components/CnChartWidget/CnChartWidget.md +1 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +49 -4
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +28 -4
- package/src/components/CnFactsWidget/CnFactsWidget.vue +34 -3
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +44 -4
- package/src/components/CnIndexPage/CnIndexPage.vue +22 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +14 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +4 -1
- package/src/components/CnPartyPicker/CnPartyPicker.vue +31 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +33 -9
- package/src/css/dialog.css +13 -1
- package/src/css/look-board-detail.css +107 -9
- package/src/css/look-board-index.css +143 -7
- package/src/css/look-board.css +109 -0
- package/src/css/table.css +12 -0
- package/src/integrations/builtin/contacts/CnContactsTab.vue +101 -26
- package/src/integrations/builtin/contacts/__tests__/CnContactsTabBoardRows.spec.js +67 -0
- package/src/integrations/builtin/deck/CnDeckCard.vue +121 -2
- package/src/integrations/builtin/deck/__tests__/CnDeckCardHeaderActions.spec.js +68 -0
- package/src/utils/buildViewTree.js +23 -8
- package/src/utils/groupSublines.js +125 -0
|
@@ -164,15 +164,15 @@ function buildViewTree({ views = [], labelFilter = '', maxDepth = 3 } = {}) {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
// Seeded roots first,
|
|
168
|
-
//
|
|
169
|
-
// can build a habit with.
|
|
167
|
+
// Seeded roots first, in the order the app declares them, then the
|
|
168
|
+
// reader's own in the order they were saved (DqFilterBeheren). Both are
|
|
169
|
+
// stable between two openings, so a person can build a habit with them.
|
|
170
170
|
const seededRoots = roots.filter(({ view }) => view?.seeded === true);
|
|
171
171
|
const ownRoots = roots.filter(({ view }) => view?.seeded !== true);
|
|
172
|
-
for (const { view, orphaned } of
|
|
172
|
+
for (const { view, orphaned } of seededRoots) {
|
|
173
173
|
emit(view, 0, orphaned);
|
|
174
174
|
}
|
|
175
|
-
for (const { view, orphaned } of
|
|
175
|
+
for (const { view, orphaned } of sortRootsBySaved(ownRoots)) {
|
|
176
176
|
emit(view, 0, orphaned);
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -228,13 +228,28 @@ function sortByName(views) {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/**
|
|
231
|
-
* Root entries
|
|
231
|
+
* Root entries in the order their views were saved (`created`, oldest
|
|
232
|
+
* first); views without a saved time follow, by name.
|
|
232
233
|
*
|
|
233
234
|
* @param {Array<object>} roots - The root entries.
|
|
234
235
|
* @return {Array<object>} The same entries, sorted.
|
|
235
236
|
*/
|
|
236
|
-
function
|
|
237
|
-
|
|
237
|
+
function sortRootsBySaved(roots) {
|
|
238
|
+
const savedAt = (entry) => (typeof entry.view?.created === 'string' ? entry.view.created : '');
|
|
239
|
+
return [...roots].sort((left, right) => {
|
|
240
|
+
const a = savedAt(left);
|
|
241
|
+
const b = savedAt(right);
|
|
242
|
+
if (a !== b) {
|
|
243
|
+
if (a === '') {
|
|
244
|
+
return 1
|
|
245
|
+
}
|
|
246
|
+
if (b === '') {
|
|
247
|
+
return -1
|
|
248
|
+
}
|
|
249
|
+
return a < b ? -1 : 1
|
|
250
|
+
}
|
|
251
|
+
return String(left.view?.name ?? '').localeCompare(String(right.view?.name ?? ''))
|
|
252
|
+
})
|
|
238
253
|
}
|
|
239
254
|
|
|
240
255
|
export { VIEW_GROUPS, buildViewTree, labelsInUse, labelsOf };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildViewTree.js","sources":["../../../src/utils/buildViewTree.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The order two hundred saved views go on screen in.\n *\n * A flat dropdown of two hundred views is the problem the tree exists to\n * solve, so the ordering is the feature and not a detail. It is a pure\n * function for the same reason `resolveViewInheritance` is: the control should\n * render a list somebody else decided, and the decision should be testable\n * without mounting anything.\n *\n * 🔴 A CHILD WHOSE PARENT IS NOT IN THE LIST RENDERS AT THE ROOT. Its parent\n * may be shared with a group this reader is not in, or may have been deleted\n * while the dropdown was open. Dropping the child would take a person's own\n * view off their own screen because of somebody else's permissions; nesting it\n * under nothing would lose it just as thoroughly. It comes back at depth zero,\n * flagged, so the control can say where the gap is.\n *\n * 🔴 A CYCLE DOES NOT HANG THE DROPDOWN, AND DOES NOT SWALLOW ITS MEMBERS.\n * `resolveViewInheritance` refuses a cycle when a view is saved, but a control\n * renders whatever the store hands it, including rows written before that rule\n * existed. A pure cycle has no root, so walking from the roots alone would\n * emit neither of its members: two views a person saved would vanish from\n * their own dropdown with nothing on screen saying so. Every view is emitted\n * exactly once, and anything the walk did not reach comes back at the root.\n *\n * Pure: no store, no fetch, no Vue.\n */\n\n/**\n * The group a view belongs to.\n *\n * Seeded views ship with the app and render above the user's own. The\n * separation is not cosmetic: a user may copy a seeded view but not delete it,\n * so the control has to be able to tell them apart without asking the server\n * a second time.\n *\n * @type {object}\n */\nexport const VIEW_GROUPS = Object.freeze({\n\tSEEDED: 'seeded',\n\tOWN: 'own',\n})\n\n/**\n * Every label in use, in the order the control should offer them.\n *\n * Labels already in use come before the option to type a new one, because a\n * label set that grows one typo at a time stops being a filter. Sorted so two\n * readers of the same views see the same order.\n *\n * @param {Array<object>} views - The views.\n * @return {Array<string>} The labels, without duplicates.\n */\nexport function labelsInUse(views = []) {\n\tconst seen = new Set()\n\tfor (const view of views) {\n\t\tfor (const label of labelsOf(view)) {\n\t\t\tseen.add(label)\n\t\t}\n\t}\n\n\treturn [...seen].sort((left, right) => left.localeCompare(right))\n}\n\n/**\n * The labels one view carries.\n *\n * Accepts `labels` as an array and `label` as a single string, because the\n * manifest declares one per seeded view and OpenRegister stores a list. A\n * reader should not have to know which shape a given view came from.\n *\n * @param {object} view - The view.\n * @return {Array<string>} Its labels, trimmed, without blanks.\n */\nexport function labelsOf(view) {\n\tconst raw = []\n\tif (Array.isArray(view?.labels)) {\n\t\traw.push(...view.labels)\n\t}\n\tif (typeof view?.label === 'string') {\n\t\traw.push(view.label)\n\t}\n\n\treturn raw\n\t\t.map((label) => String(label ?? '').trim())\n\t\t.filter((label) => label !== '')\n}\n\n/**\n * The views as a flat list in tree order, each carrying its depth.\n *\n * Flat rather than nested on purpose: the control renders `NcActionButton`\n * rows, which cannot nest, so a nested structure would have to be flattened\n * again at render time and the indentation would be decided in the template.\n * Here it is decided once and asserted once.\n *\n * @param {object} options - The call.\n * @param {Array<object>} options.views - Every view the reader can see.\n * @param {string} [options.labelFilter] - Show only views carrying this label.\n * @param {number} [options.maxDepth] - How deep to indent before flattening.\n *\n * @return {Array<object>} Rows of `{ view, depth, group, orphaned }`, seeded first.\n */\nexport function buildViewTree({ views = [], labelFilter = '', maxDepth = 3 } = {}) {\n\tconst visible = filterByLabel(views, labelFilter)\n\tconst bySlug = new Map()\n\tfor (const view of visible) {\n\t\tconst slug = slugOf(view)\n\t\tif (slug !== '') {\n\t\t\tbySlug.set(slug, view)\n\t\t}\n\t}\n\n\tconst childrenOf = new Map()\n\tconst roots = []\n\tfor (const view of visible) {\n\t\tconst parent = String(view?.parent ?? '').trim()\n\t\t// A parent outside the VISIBLE set, whether because it is invisible to\n\t\t// this reader or because a label filter just hid it. Either way the\n\t\t// child is still the reader's, so it comes back at the root.\n\t\tif (parent === '' || bySlug.has(parent) === false) {\n\t\t\troots.push({ view, orphaned: parent !== '' })\n\t\t\tcontinue\n\t\t}\n\t\tif (childrenOf.has(parent) === false) {\n\t\t\tchildrenOf.set(parent, [])\n\t\t}\n\t\tchildrenOf.get(parent).push(view)\n\t}\n\n\tconst rows = []\n\tconst emitted = new Set()\n\n\t/**\n\t * Emit one view and everything under it.\n\t *\n\t * @param {object} view - The view.\n\t * @param {number} depth - Its indent level.\n\t * @param {boolean} orphaned - Whether its parent is not in the list.\n\t * @return {void}\n\t */\n\tfunction emit(view, depth, orphaned) {\n\t\tconst slug = slugOf(view)\n\t\t// Exactly once, whatever the data says. A cycle written before the\n\t\t// save-time refusal existed must not hang a dropdown.\n\t\tif (slug !== '' && emitted.has(slug) === true) {\n\t\t\treturn\n\t\t}\n\t\tif (slug !== '') {\n\t\t\temitted.add(slug)\n\t\t}\n\n\t\trows.push({\n\t\t\tview,\n\t\t\tdepth: Math.min(depth, maxDepth),\n\t\t\tgroup: view?.seeded === true ? VIEW_GROUPS.SEEDED : VIEW_GROUPS.OWN,\n\t\t\torphaned,\n\t\t})\n\n\t\tfor (const child of sortByName(childrenOf.get(slug) || [])) {\n\t\t\temit(child, depth + 1, false)\n\t\t}\n\t}\n\n\t// Seeded roots first, then the reader's own, each alphabetically. A\n\t// dropdown whose order changes between two openings is a dropdown nobody\n\t// can build a habit with.\n\tconst seededRoots = roots.filter(({ view }) => view?.seeded === true)\n\tconst ownRoots = roots.filter(({ view }) => view?.seeded !== true)\n\tfor (const { view, orphaned } of sortRootsByName(seededRoots)) {\n\t\temit(view, 0, orphaned)\n\t}\n\tfor (const { view, orphaned } of sortRootsByName(ownRoots)) {\n\t\temit(view, 0, orphaned)\n\t}\n\n\t// 🔴 ANYTHING THE WALK DID NOT REACH, AT THE ROOT. A pure cycle has no\n\t// root, so walking from the roots alone emits neither of its members and\n\t// two views a person saved disappear from their own dropdown with nothing\n\t// on screen saying so. Every view arrives exactly once or the control is\n\t// lying about what exists.\n\tfor (const view of sortByName(visible)) {\n\t\temit(view, 0, true)\n\t}\n\n\treturn rows\n}\n\n/**\n * Only the views carrying a label, when one is asked for.\n *\n * A view with no label stays reachable when no filter is active, which is the\n * half that keeps the filter optional rather than a labelling regime.\n *\n * @param {Array<object>} views - The views.\n * @param {string} labelFilter - The label, or the empty string for all.\n * @return {Array<object>} The views to render.\n */\nfunction filterByLabel(views, labelFilter) {\n\tconst wanted = String(labelFilter ?? '').trim()\n\tif (wanted === '') {\n\t\treturn [...views]\n\t}\n\n\treturn views.filter((view) => labelsOf(view).includes(wanted))\n}\n\n/**\n * A view's stable name, whatever the store called the field.\n *\n * @param {object} view - The view.\n * @return {string} Its slug, or the empty string.\n */\nfunction slugOf(view) {\n\treturn String(view?.slug ?? view?.id ?? '').trim()\n}\n\n/**\n * Views by name, so the order is the same on two screens.\n *\n * @param {Array<object>} views - The views.\n * @return {Array<object>} The same views, sorted.\n */\nfunction sortByName(views) {\n\treturn [...views].sort((left, right) => String(left?.name ?? '').localeCompare(String(right?.name ?? '')))\n}\n\n/**\n * Root entries by the name of the view they carry.\n *\n * @param {Array<object>} roots - The root entries.\n * @return {Array<object>} The same entries, sorted.\n */\nfunction sortRootsByName(roots) {\n\treturn [...roots].sort((left, right) => String(left.view?.name ?? '').localeCompare(String(right.view?.name ?? '')))\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"buildViewTree.js","sources":["../../../src/utils/buildViewTree.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The order two hundred saved views go on screen in.\n *\n * A flat dropdown of two hundred views is the problem the tree exists to\n * solve, so the ordering is the feature and not a detail. It is a pure\n * function for the same reason `resolveViewInheritance` is: the control should\n * render a list somebody else decided, and the decision should be testable\n * without mounting anything.\n *\n * 🔴 A CHILD WHOSE PARENT IS NOT IN THE LIST RENDERS AT THE ROOT. Its parent\n * may be shared with a group this reader is not in, or may have been deleted\n * while the dropdown was open. Dropping the child would take a person's own\n * view off their own screen because of somebody else's permissions; nesting it\n * under nothing would lose it just as thoroughly. It comes back at depth zero,\n * flagged, so the control can say where the gap is.\n *\n * 🔴 A CYCLE DOES NOT HANG THE DROPDOWN, AND DOES NOT SWALLOW ITS MEMBERS.\n * `resolveViewInheritance` refuses a cycle when a view is saved, but a control\n * renders whatever the store hands it, including rows written before that rule\n * existed. A pure cycle has no root, so walking from the roots alone would\n * emit neither of its members: two views a person saved would vanish from\n * their own dropdown with nothing on screen saying so. Every view is emitted\n * exactly once, and anything the walk did not reach comes back at the root.\n *\n * Pure: no store, no fetch, no Vue.\n */\n\n/**\n * The group a view belongs to.\n *\n * Seeded views ship with the app and render above the user's own. The\n * separation is not cosmetic: a user may copy a seeded view but not delete it,\n * so the control has to be able to tell them apart without asking the server\n * a second time.\n *\n * @type {object}\n */\nexport const VIEW_GROUPS = Object.freeze({\n\tSEEDED: 'seeded',\n\tOWN: 'own',\n})\n\n/**\n * Every label in use, in the order the control should offer them.\n *\n * Labels already in use come before the option to type a new one, because a\n * label set that grows one typo at a time stops being a filter. Sorted so two\n * readers of the same views see the same order.\n *\n * @param {Array<object>} views - The views.\n * @return {Array<string>} The labels, without duplicates.\n */\nexport function labelsInUse(views = []) {\n\tconst seen = new Set()\n\tfor (const view of views) {\n\t\tfor (const label of labelsOf(view)) {\n\t\t\tseen.add(label)\n\t\t}\n\t}\n\n\treturn [...seen].sort((left, right) => left.localeCompare(right))\n}\n\n/**\n * The labels one view carries.\n *\n * Accepts `labels` as an array and `label` as a single string, because the\n * manifest declares one per seeded view and OpenRegister stores a list. A\n * reader should not have to know which shape a given view came from.\n *\n * @param {object} view - The view.\n * @return {Array<string>} Its labels, trimmed, without blanks.\n */\nexport function labelsOf(view) {\n\tconst raw = []\n\tif (Array.isArray(view?.labels)) {\n\t\traw.push(...view.labels)\n\t}\n\tif (typeof view?.label === 'string') {\n\t\traw.push(view.label)\n\t}\n\n\treturn raw\n\t\t.map((label) => String(label ?? '').trim())\n\t\t.filter((label) => label !== '')\n}\n\n/**\n * The views as a flat list in tree order, each carrying its depth.\n *\n * Flat rather than nested on purpose: the control renders `NcActionButton`\n * rows, which cannot nest, so a nested structure would have to be flattened\n * again at render time and the indentation would be decided in the template.\n * Here it is decided once and asserted once.\n *\n * @param {object} options - The call.\n * @param {Array<object>} options.views - Every view the reader can see.\n * @param {string} [options.labelFilter] - Show only views carrying this label.\n * @param {number} [options.maxDepth] - How deep to indent before flattening.\n *\n * @return {Array<object>} Rows of `{ view, depth, group, orphaned }`, seeded first.\n */\nexport function buildViewTree({ views = [], labelFilter = '', maxDepth = 3 } = {}) {\n\tconst visible = filterByLabel(views, labelFilter)\n\tconst bySlug = new Map()\n\tfor (const view of visible) {\n\t\tconst slug = slugOf(view)\n\t\tif (slug !== '') {\n\t\t\tbySlug.set(slug, view)\n\t\t}\n\t}\n\n\tconst childrenOf = new Map()\n\tconst roots = []\n\tfor (const view of visible) {\n\t\tconst parent = String(view?.parent ?? '').trim()\n\t\t// A parent outside the VISIBLE set, whether because it is invisible to\n\t\t// this reader or because a label filter just hid it. Either way the\n\t\t// child is still the reader's, so it comes back at the root.\n\t\tif (parent === '' || bySlug.has(parent) === false) {\n\t\t\troots.push({ view, orphaned: parent !== '' })\n\t\t\tcontinue\n\t\t}\n\t\tif (childrenOf.has(parent) === false) {\n\t\t\tchildrenOf.set(parent, [])\n\t\t}\n\t\tchildrenOf.get(parent).push(view)\n\t}\n\n\tconst rows = []\n\tconst emitted = new Set()\n\n\t/**\n\t * Emit one view and everything under it.\n\t *\n\t * @param {object} view - The view.\n\t * @param {number} depth - Its indent level.\n\t * @param {boolean} orphaned - Whether its parent is not in the list.\n\t * @return {void}\n\t */\n\tfunction emit(view, depth, orphaned) {\n\t\tconst slug = slugOf(view)\n\t\t// Exactly once, whatever the data says. A cycle written before the\n\t\t// save-time refusal existed must not hang a dropdown.\n\t\tif (slug !== '' && emitted.has(slug) === true) {\n\t\t\treturn\n\t\t}\n\t\tif (slug !== '') {\n\t\t\temitted.add(slug)\n\t\t}\n\n\t\trows.push({\n\t\t\tview,\n\t\t\tdepth: Math.min(depth, maxDepth),\n\t\t\tgroup: view?.seeded === true ? VIEW_GROUPS.SEEDED : VIEW_GROUPS.OWN,\n\t\t\torphaned,\n\t\t})\n\n\t\tfor (const child of sortByName(childrenOf.get(slug) || [])) {\n\t\t\temit(child, depth + 1, false)\n\t\t}\n\t}\n\n\t// Seeded roots first, in the order the app declares them, then the\n\t// reader's own in the order they were saved (DqFilterBeheren). Both are\n\t// stable between two openings, so a person can build a habit with them.\n\tconst seededRoots = roots.filter(({ view }) => view?.seeded === true)\n\tconst ownRoots = roots.filter(({ view }) => view?.seeded !== true)\n\tfor (const { view, orphaned } of seededRoots) {\n\t\temit(view, 0, orphaned)\n\t}\n\tfor (const { view, orphaned } of sortRootsBySaved(ownRoots)) {\n\t\temit(view, 0, orphaned)\n\t}\n\n\t// 🔴 ANYTHING THE WALK DID NOT REACH, AT THE ROOT. A pure cycle has no\n\t// root, so walking from the roots alone emits neither of its members and\n\t// two views a person saved disappear from their own dropdown with nothing\n\t// on screen saying so. Every view arrives exactly once or the control is\n\t// lying about what exists.\n\tfor (const view of sortByName(visible)) {\n\t\temit(view, 0, true)\n\t}\n\n\treturn rows\n}\n\n/**\n * Only the views carrying a label, when one is asked for.\n *\n * A view with no label stays reachable when no filter is active, which is the\n * half that keeps the filter optional rather than a labelling regime.\n *\n * @param {Array<object>} views - The views.\n * @param {string} labelFilter - The label, or the empty string for all.\n * @return {Array<object>} The views to render.\n */\nfunction filterByLabel(views, labelFilter) {\n\tconst wanted = String(labelFilter ?? '').trim()\n\tif (wanted === '') {\n\t\treturn [...views]\n\t}\n\n\treturn views.filter((view) => labelsOf(view).includes(wanted))\n}\n\n/**\n * A view's stable name, whatever the store called the field.\n *\n * @param {object} view - The view.\n * @return {string} Its slug, or the empty string.\n */\nfunction slugOf(view) {\n\treturn String(view?.slug ?? view?.id ?? '').trim()\n}\n\n/**\n * Views by name, so the order is the same on two screens.\n *\n * @param {Array<object>} views - The views.\n * @return {Array<object>} The same views, sorted.\n */\nfunction sortByName(views) {\n\treturn [...views].sort((left, right) => String(left?.name ?? '').localeCompare(String(right?.name ?? '')))\n}\n\n/**\n * Root entries in the order their views were saved (`created`, oldest\n * first); views without a saved time follow, by name.\n *\n * @param {Array<object>} roots - The root entries.\n * @return {Array<object>} The same entries, sorted.\n */\nfunction sortRootsBySaved(roots) {\n\tconst savedAt = (entry) => (typeof entry.view?.created === 'string' ? entry.view.created : '')\n\treturn [...roots].sort((left, right) => {\n\t\tconst a = savedAt(left)\n\t\tconst b = savedAt(right)\n\t\tif (a !== b) {\n\t\t\tif (a === '') {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\tif (b === '') {\n\t\t\t\treturn -1\n\t\t\t}\n\t\t\treturn a < b ? -1 : 1\n\t\t}\n\t\treturn String(left.view?.name ?? '').localeCompare(String(right.view?.name ?? ''))\n\t})\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import axios__default from '@nextcloud/axios';
|
|
2
|
+
import { translatePlural } from '@nextcloud/l10n';
|
|
3
|
+
import { generateOcsUrl } from '@nextcloud/router';
|
|
4
|
+
import { fetchReferenceObjects } from './boardReferenceColumns.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The line under a team in a picker: how many members it has and what it is
|
|
8
|
+
* for ("6 leden · bezwaar, beroep en Woo-advies", board DqOverdragen).
|
|
9
|
+
*
|
|
10
|
+
* The count comes from Nextcloud's group details; that endpoint answers
|
|
11
|
+
* administrators and group admins only, so for anyone else the count is left
|
|
12
|
+
* out rather than guessed. Nextcloud keeps no description for a group, so the
|
|
13
|
+
* text comes from an app's own records when the app names them (`describe`:
|
|
14
|
+
* `{ register, schema, groupField?, textField? }`, by default the record's
|
|
15
|
+
* `ncGroupId` and `description`).
|
|
16
|
+
*
|
|
17
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
18
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
19
|
+
*
|
|
20
|
+
* @module utils/groupSublines
|
|
21
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const descriptionCache = new Map();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Member counts by group id for the groups a search finds. Fails soft to `{}`.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} query The search term.
|
|
31
|
+
* @return {Promise<Record<string, number>>} Counts by group id.
|
|
32
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
33
|
+
*/
|
|
34
|
+
async function memberCounts(query) {
|
|
35
|
+
try {
|
|
36
|
+
const response = await axios__default.get(generateOcsUrl('cloud/groups/details'), {
|
|
37
|
+
headers: { 'OCS-APIRequest': 'true', Accept: 'application/json' },
|
|
38
|
+
params: { search: query || '', limit: 50 },
|
|
39
|
+
});
|
|
40
|
+
const data = response && response.data && response.data.ocs ? response.data.ocs.data : (response && response.data);
|
|
41
|
+
const groups = data && Array.isArray(data.groups) ? data.groups : [];
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const group of groups) {
|
|
44
|
+
const count = Number(group && group.usercount);
|
|
45
|
+
if (group && group.id && Number.isFinite(count)) {
|
|
46
|
+
out[String(group.id)] = count;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
} catch {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Descriptions by group id from the app's records. Read once per source.
|
|
57
|
+
*
|
|
58
|
+
* @param {{register?: string, schema?: string, groupField?: string, textField?: string}|null} describe The source.
|
|
59
|
+
* @return {Promise<Record<string, string>>} Descriptions by group id.
|
|
60
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
61
|
+
*/
|
|
62
|
+
async function descriptions(describe) {
|
|
63
|
+
if (!describe || !describe.register || !describe.schema) {
|
|
64
|
+
return {}
|
|
65
|
+
}
|
|
66
|
+
const groupField = describe.groupField || 'ncGroupId';
|
|
67
|
+
const textField = describe.textField || 'description';
|
|
68
|
+
const key = [describe.register, describe.schema, groupField, textField].join('/');
|
|
69
|
+
if (!descriptionCache.has(key)) {
|
|
70
|
+
descriptionCache.set(key, fetchReferenceObjects({ register: describe.register, schema: describe.schema, limit: 500 })
|
|
71
|
+
.then((rows) => {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const row of rows) {
|
|
74
|
+
const gid = row && row[groupField];
|
|
75
|
+
const text = row && typeof row[textField] === 'string' ? row[textField].trim() : '';
|
|
76
|
+
if (gid && text && out[String(gid)] === undefined) {
|
|
77
|
+
out[String(gid)] = text;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {
|
|
83
|
+
descriptionCache.delete(key);
|
|
84
|
+
return {}
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
return descriptionCache.get(key)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The line under each group: its member count and its description, joined by
|
|
92
|
+
* " · ", either left out when unknown.
|
|
93
|
+
*
|
|
94
|
+
* @param {string[]} ids The group ids to describe.
|
|
95
|
+
* @param {string} query The search the groups came from.
|
|
96
|
+
* @param {object|null} [describe] Where the app keeps a group's description.
|
|
97
|
+
* @return {Promise<Record<string, string>>} The line by group id ('' when nothing is known).
|
|
98
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
99
|
+
*/
|
|
100
|
+
async function groupSublines(ids, query, describe = null) {
|
|
101
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
102
|
+
return {}
|
|
103
|
+
}
|
|
104
|
+
const [counts, texts] = await Promise.all([memberCounts(query), descriptions(describe)]);
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const id of ids) {
|
|
107
|
+
const parts = [];
|
|
108
|
+
if (counts[id] !== undefined) {
|
|
109
|
+
parts.push(translatePlural('nextcloud-vue', '{count} member', '{count} members', counts[id], { count: counts[id] }));
|
|
110
|
+
}
|
|
111
|
+
if (texts[id]) {
|
|
112
|
+
parts.push(texts[id]);
|
|
113
|
+
}
|
|
114
|
+
out[id] = parts.join(' · ');
|
|
115
|
+
}
|
|
116
|
+
return out
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { groupSublines };
|
|
120
|
+
//# sourceMappingURL=groupSublines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"groupSublines.js","sources":["../../../src/utils/groupSublines.js"],"sourcesContent":["/**\n * The line under a team in a picker: how many members it has and what it is\n * for (\"6 leden · bezwaar, beroep en Woo-advies\", board DqOverdragen).\n *\n * The count comes from Nextcloud's group details; that endpoint answers\n * administrators and group admins only, so for anyone else the count is left\n * out rather than guessed. Nextcloud keeps no description for a group, so the\n * text comes from an app's own records when the app names them (`describe`:\n * `{ register, schema, groupField?, textField? }`, by default the record's\n * `ncGroupId` and `description`).\n *\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * @module utils/groupSublines\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\n\nimport axios from '@nextcloud/axios'\nimport { translatePlural as n } from '@nextcloud/l10n'\nimport { generateOcsUrl } from '@nextcloud/router'\nimport { fetchReferenceObjects } from './boardReferenceColumns.js'\n\nconst descriptionCache = new Map()\n\n/**\n * Member counts by group id for the groups a search finds. Fails soft to `{}`.\n *\n * @param {string} query The search term.\n * @return {Promise<Record<string, number>>} Counts by group id.\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\nasync function memberCounts(query) {\n\ttry {\n\t\tconst response = await axios.get(generateOcsUrl('cloud/groups/details'), {\n\t\t\theaders: { 'OCS-APIRequest': 'true', Accept: 'application/json' },\n\t\t\tparams: { search: query || '', limit: 50 },\n\t\t})\n\t\tconst data = response && response.data && response.data.ocs ? response.data.ocs.data : (response && response.data)\n\t\tconst groups = data && Array.isArray(data.groups) ? data.groups : []\n\t\tconst out = {}\n\t\tfor (const group of groups) {\n\t\t\tconst count = Number(group && group.usercount)\n\t\t\tif (group && group.id && Number.isFinite(count)) {\n\t\t\t\tout[String(group.id)] = count\n\t\t\t}\n\t\t}\n\t\treturn out\n\t} catch {\n\t\treturn {}\n\t}\n}\n\n/**\n * Descriptions by group id from the app's records. Read once per source.\n *\n * @param {{register?: string, schema?: string, groupField?: string, textField?: string}|null} describe The source.\n * @return {Promise<Record<string, string>>} Descriptions by group id.\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\nasync function descriptions(describe) {\n\tif (!describe || !describe.register || !describe.schema) {\n\t\treturn {}\n\t}\n\tconst groupField = describe.groupField || 'ncGroupId'\n\tconst textField = describe.textField || 'description'\n\tconst key = [describe.register, describe.schema, groupField, textField].join('/')\n\tif (!descriptionCache.has(key)) {\n\t\tdescriptionCache.set(key, fetchReferenceObjects({ register: describe.register, schema: describe.schema, limit: 500 })\n\t\t\t.then((rows) => {\n\t\t\t\tconst out = {}\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tconst gid = row && row[groupField]\n\t\t\t\t\tconst text = row && typeof row[textField] === 'string' ? row[textField].trim() : ''\n\t\t\t\t\tif (gid && text && out[String(gid)] === undefined) {\n\t\t\t\t\t\tout[String(gid)] = text\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn out\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tdescriptionCache.delete(key)\n\t\t\t\treturn {}\n\t\t\t}))\n\t}\n\treturn descriptionCache.get(key)\n}\n\n/**\n * The line under each group: its member count and its description, joined by\n * \" · \", either left out when unknown.\n *\n * @param {string[]} ids The group ids to describe.\n * @param {string} query The search the groups came from.\n * @param {object|null} [describe] Where the app keeps a group's description.\n * @return {Promise<Record<string, string>>} The line by group id ('' when nothing is known).\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\nexport async function groupSublines(ids, query, describe = null) {\n\tif (!Array.isArray(ids) || ids.length === 0) {\n\t\treturn {}\n\t}\n\tconst [counts, texts] = await Promise.all([memberCounts(query), descriptions(describe)])\n\tconst out = {}\n\tfor (const id of ids) {\n\t\tconst parts = []\n\t\tif (counts[id] !== undefined) {\n\t\t\tparts.push(n('nextcloud-vue', '{count} member', '{count} members', counts[id], { count: counts[id] }))\n\t\t}\n\t\tif (texts[id]) {\n\t\t\tparts.push(texts[id])\n\t\t}\n\t\tout[id] = parts.join(' · ')\n\t}\n\treturn out\n}\n\n/**\n * Forget the cached descriptions (tests, or after the source changed).\n *\n * @return {void}\n */\nexport function clearGroupSublineCache() {\n\tdescriptionCache.clear()\n}\n"],"names":["axios","n"],"mappings":"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"}
|