@conduction/nextcloud-vue 2.137.0 → 2.139.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 (105) hide show
  1. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  2. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +6 -4
  3. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  4. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  6. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  7. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  8. package/dist/esm/components/CnDetailCard/CnDetailCard.vue.js +1 -1
  9. package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js +6 -0
  10. package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js +24 -7
  12. package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  14. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +2 -0
  15. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  17. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  18. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  19. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  20. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  21. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +43 -1
  22. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  23. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +133 -6
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +25 -17
  27. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  28. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +2 -0
  29. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  30. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +13 -2
  31. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  32. package/dist/esm/components/CnMailTab/dashboardRegistration.js +4 -0
  33. package/dist/esm/components/CnMailTab/dashboardRegistration.js.map +1 -1
  34. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue.js +1 -1
  35. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js.map +1 -1
  36. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue3.js.map +1 -1
  37. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  38. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
  39. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  42. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +2 -0
  43. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  44. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
  45. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  46. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  47. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +6 -0
  48. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  49. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +3 -3
  50. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  51. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  52. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +16 -0
  53. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  54. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +11 -5
  55. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  56. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue.js +1 -1
  57. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js.map +1 -1
  58. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js +1 -0
  59. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js.map +1 -1
  60. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  61. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  62. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  63. package/dist/esm/composables/indexSources.js +78 -12
  64. package/dist/esm/composables/indexSources.js.map +1 -1
  65. package/dist/esm/l10n/en.json.js +12 -1
  66. package/dist/esm/l10n/en.json.js.map +1 -1
  67. package/dist/esm/l10n/nl.json.js +12 -1
  68. package/dist/esm/l10n/nl.json.js.map +1 -1
  69. package/dist/esm/library-style.js +1 -1
  70. package/dist/esm/nextcloud-vue.css +685 -512
  71. package/dist/esm/utils/resolveFilterTokens.js +9 -0
  72. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  73. package/dist/esm/utils/sentinelTokens.js +1 -1
  74. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  75. package/dist/nextcloud-vue.cjs.js +580 -236
  76. package/dist/nextcloud-vue.cjs.js.map +2 -2
  77. package/dist/nextcloud-vue.css +685 -512
  78. package/l10n/en.json +12 -1
  79. package/l10n/nl.json +12 -1
  80. package/package.json +1 -1
  81. package/src/components/CnCellRenderer/CnCellRenderer.vue +6 -4
  82. package/src/components/CnChartWidget/CnChartWidget.vue +4 -0
  83. package/src/components/CnDetailCard/CnDetailCard.md +1 -0
  84. package/src/components/CnDetailCard/CnDetailCard.vue +25 -1
  85. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -0
  86. package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
  87. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -1
  88. package/src/components/CnIndexPage/CnIndexPage.vue +135 -7
  89. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +10 -2
  90. package/src/components/CnMailTab/dashboardRegistration.js +4 -0
  91. package/src/components/CnNotificationMatrix/CnNotificationMatrix.vue +4 -0
  92. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
  93. package/src/components/CnObjectSidebar/CnFilesTab.vue +3 -0
  94. package/src/components/CnOrderedListField/CnOrderedListField.vue +11 -1
  95. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +22 -3
  96. package/src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue +1 -0
  97. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +3 -0
  98. package/src/composables/indexSources.js +78 -12
  99. package/src/css/dialog.css +26 -0
  100. package/src/css/index-page.css +5 -0
  101. package/src/css/look-board-detail.css +36 -0
  102. package/src/css/look-board-index.css +21 -0
  103. package/src/css/look-board.css +47 -0
  104. package/src/utils/resolveFilterTokens.js +9 -0
  105. package/src/utils/sentinelTokens.js +1 -1
@@ -2,7 +2,7 @@ import script from './CnSavedViewMenuSettings.vue2.js';
2
2
  import { render } from './CnSavedViewMenuSettings.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-dfcd17c4";
5
+ script.__scopeId = "data-v-962f985c";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1 +1 @@
1
- {"version":3,"file":"CnSavedViewMenuSettings.vue2.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\t// A value that identifies a record by another field: one read per value.\n\tif (missing.length > 0 && ref.matchField) {\n\t\tfor (const value of missing) {\n\t\t\tlabelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })\n\t\t\t\t.then((data) => {\n\t\t\t\t\tconst row = Array.isArray(data && data.results) ? data.results[0] : null\n\t\t\t\t\treturn row ? pickRefLabel(row, ref.labelField) : null\n\t\t\t\t})\n\t\t\t\t.catch(() => null))\n\t\t}\n\t} else if (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnOrderedListField","t","n"],"mappings":"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"}
1
+ {"version":3,"file":"CnSavedViewMenuSettings.vue2.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:hideLabel=\"true\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\t// A value that identifies a record by another field: one read per value.\n\tif (missing.length > 0 && ref.matchField) {\n\t\tfor (const value of missing) {\n\t\t\tlabelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })\n\t\t\t\t.then((data) => {\n\t\t\t\t\tconst row = Array.isArray(data && data.results) ? data.results[0] : null\n\t\t\t\t\treturn row ? pickRefLabel(row, ref.labelField) : null\n\t\t\t\t})\n\t\t\t\t.catch(() => null))\n\t\t}\n\t} else if (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnOrderedListField","t","n"],"mappings":"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"}
@@ -15,6 +15,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
15
15
  modelValue: $options.values,
16
16
  options: $options.options,
17
17
  label: $options.t('nextcloud-vue', 'Views in my menu'),
18
+ hideLabel: true,
18
19
  addLabel: $options.t('nextcloud-vue', 'Add view'),
19
20
  addPlaceholder: $options.t('nextcloud-vue', 'Pick a saved view'),
20
21
  addHint: $options.t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.'),
@@ -1 +1 @@
1
- {"version":3,"file":"CnSavedViewMenuSettings.vue3.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue?vue&type=template&id=dfcd17c4&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\t// A value that identifies a record by another field: one read per value.\n\tif (missing.length > 0 && ref.matchField) {\n\t\tfor (const value of missing) {\n\t\t\tlabelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })\n\t\t\t\t.then((data) => {\n\t\t\t\t\tconst row = Array.isArray(data && data.results) ? data.results[0] : null\n\t\t\t\t\treturn row ? pickRefLabel(row, ref.labelField) : null\n\t\t\t\t})\n\t\t\t\t.catch(() => null))\n\t\t}\n\t} else if (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString"],"mappings":";;;AAWM,EAAA,KAAK,EAAC,6BAAU;AAAA,EAAA,aAAA,EAAA,6BAAA;;AACjB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oCAAS,GAAA;;;;;AADjB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACAC,kBAEG,CAAA,GAAA,EAFH,UAEG,EAAAC,eAAA,CADA,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,4LAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;AAGH,MAAA,UAAU,EAAE,QAAM,CAAA,MAAA;AAClB,MAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,MAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;AACR,MAAA,QAAQ,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA;AACX,MAAA,cAAc,EAAC,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,mBAAA,CAAA;AACf,MAAA,OAAO,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,yGAAA,CAAA;AACV,MAAA,WAAE,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnSavedViewMenuSettings.vue3.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue?vue&type=template&id=962f985c&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:hideLabel=\"true\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\t// A value that identifies a record by another field: one read per value.\n\tif (missing.length > 0 && ref.matchField) {\n\t\tfor (const value of missing) {\n\t\t\tlabelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })\n\t\t\t\t.then((data) => {\n\t\t\t\t\tconst row = Array.isArray(data && data.results) ? data.results[0] : null\n\t\t\t\t\treturn row ? pickRefLabel(row, ref.labelField) : null\n\t\t\t\t})\n\t\t\t\t.catch(() => null))\n\t\t}\n\t} else if (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_createVNode"],"mappings":";;;AAWM,EAAA,KAAK,EAAC,6BAAU;AAAA,EAAA,aAAA,EAAA,6BAAA;;AACjB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oCAAS,GAAA;;;;;AADjB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACAC,kBAEG,CAAA,GAAA,EAFH,UAEG,EAAAC,eAAA,CADA,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,4LAAA,CAAA,CAAA,EAAA,CAAA,YAAA;IAEJC;AACC,MAAA,UAAU,EAAE,QAAM,CAAA,MAAA;AAClB,MAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,MAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;AACR,MAAA,SAAS,EAAE,IAAI;AACf,MAAA,QAAQ,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA;AACX,MAAA,cAAc,EAAC,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,mBAAA,CAAA;AACf,MAAA,OAAO,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,yGAAA,CAAA;AACV,MAAA,WAAE,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnUserSettingsDialog.vue2.js';
2
2
  import { render } from './CnUserSettingsDialog.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-9a3971e2";
5
+ script.__scopeId = "data-v-2743a776";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1 +1 @@
1
- {"version":3,"file":"CnUserSettingsDialog.vue2.js","sources":["../../../../src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\tclass=\"cn-user-settings-dialog\"\n\t\t:name=\"title\"\n\t\tlook=\"board\"\n\t\theadless\n\t\t:open=\"open\"\n\t\t:style=\"frameStyle\"\n\t\tdata-testid=\"cn-user-settings-dialog\"\n\t\t@update:open=\"onUpdateOpen\"\n\t\t@closing=\"close\">\n\t\t<div class=\"cn-user-settings-dialog__body\">\n\t\t\t<nav\n\t\t\t\tclass=\"cn-user-settings-dialog__nav\"\n\t\t\t\t:aria-label=\"navLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-nav\">\n\t\t\t\t<h2 class=\"cn-user-settings-dialog__title\" data-testid=\"cn-user-settings-title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</h2>\n\t\t\t\t<!-- `nextcloudLinks`: Nextcloud's own personal settings first, as\n\t\t\t\t links, then one entry for this app holding its sections. -->\n\t\t\t\t<ul v-if=\"ncLinks.length > 0\" class=\"cn-user-settings-dialog__nc-links\" data-testid=\"cn-user-settings-nc-links\">\n\t\t\t\t\t<li v-for=\"link in ncLinks\" :key=\"link.key\">\n\t\t\t\t\t\t<a :href=\"link.href\" class=\"cn-user-settings-dialog__nav-link\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ link.label }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"ncLinks.length > 0 && appName\"\n\t\t\t\t\tclass=\"cn-user-settings-dialog__group\"\n\t\t\t\t\tdata-testid=\"cn-user-settings-app-group\">\n\t\t\t\t\t{{ appName }}\n\t\t\t\t</p>\n\t\t\t\t<ul :class=\"{ 'cn-user-settings-dialog__group-sections': ncLinks.length > 0 }\">\n\t\t\t\t\t<li v-for=\"section in sections\" :key=\"section.id\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"`#settings-section_${section.id}`\"\n\t\t\t\t\t\t\tclass=\"cn-user-settings-dialog__nav-link\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-user-settings-dialog__nav-link--active': section.id === selected }\"\n\t\t\t\t\t\t\t:aria-current=\"section.id === selected ? 'true' : undefined\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-user-settings-nav-${section.id}`\"\n\t\t\t\t\t\t\t@click.prevent=\"selectSection(section.id)\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ section.name }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t\t<div class=\"cn-user-settings-dialog__sections\">\n\t\t\t\t<!-- @slot default NcAppSettingsSection children; each registers itself in the navigation. -->\n\t\t\t\t<slot />\n\t\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t\t{{ footerNote }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<NcButton\n\t\t\t\tclass=\"cn-user-settings-dialog__close\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:aria-label=\"closeLabel\"\n\t\t\t\t:title=\"closeLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-close\"\n\t\t\t\t@click=\"close\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcButton>\n\t\t</div>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton } from '@nextcloud/vue'\nimport { ref } from 'vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\n\n/**\n * The key NcAppSettingsSection injects to register itself with its dialog\n * (`Symbol.for`, so the same symbol `@nextcloud/vue` provides).\n */\nconst REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration')\n\n/** The key NcAppSettingsSection injects for the legacy design flag. */\nconst LEGACY_DESIGN_KEY = Symbol.for('NcAppSettingsDialog:legacy')\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look, as the\n * DqPersoonlijkeInstellingen board draws Nextcloud's own settings dialog:\n * 900px wide, the title at the top of a tinted navigation column, every\n * section stacked on a white pane that the navigation scrolls, a close icon\n * in the corner, and no header or footer. Under the sections a line says\n * that changes save straight away.\n *\n * The sections stay the app's: any `NcAppSettingsSection` in the default slot\n * registers itself with this dialog exactly as it does with\n * `NcAppSettingsDialog`, with its name and order. CnAppRoot renders this\n * dialog instead of `NcAppSettingsDialog` under the board look.\n *\n * @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look\n * @spec openspec/changes/user-settings-dialog-dq-board/specs/user-settings-dialog-dq-board/spec.md#requirement-the-user-settings-dialog-follows-the-dq-board\n *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { Close, CnDialog, NcButton },\n\n\tprovide() {\n\t\treturn {\n\t\t\t[REGISTRATION_KEY]: {\n\t\t\t\tregisterSection: this.registerSection,\n\t\t\t\tunregisterSection: this.unregisterSection,\n\t\t\t},\n\n\t\t\t// NcAppSettingsSection also asks its dialog whether to use the legacy design.\n\t\t\t[LEGACY_DESIGN_KEY]: ref(false),\n\t\t}\n\t},\n\n\tprops: {\n\t\t/** Whether the dialog is open. */\n\t\topen: { type: Boolean, default: false },\n\t\t/** Dialog title; also its accessible name. */\n\t\ttitle: { type: String, required: true },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line above the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line under the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\tsubtitle: { type: String, default: () => t('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },\n\t\t/** The app's name, after the footer note and as the navigation entry holding its sections. */\n\t\tappName: { type: String, default: '' },\n\t\t/**\n\t\t * Nextcloud's own personal settings, listed first in the navigation as\n\t\t * links: `[{ label, section? | href?, icon? }]`. `section` is a\n\t\t * personal settings section id (`/settings/user/<section>`). With any,\n\t\t * the app's sections sit under one entry named after the app.\n\t\t */\n\t\tnextcloudLinks: { type: Array, default: () => [] },\n\t},\n\n\temits: ['update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tsections: [],\n\t\t\tselected: '',\n\t\t\tpicked: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The Nextcloud links with their URL.\n\t\t *\n\t\t * @spec openspec/changes/user-settings-nav-groups/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-navigation-can-list-nextclouds-own-sections-first\n\t\t * @return {Array<{key: string, label: string, href: string, icon: string}>}\n\t\t */\n\t\tncLinks() {\n\t\t\treturn (Array.isArray(this.nextcloudLinks) ? this.nextcloudLinks : [])\n\t\t\t\t.filter((link) => link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '' && (link.href || link.section))\n\t\t\t\t.map((link, index) => ({\n\t\t\t\t\tkey: String(link.section || link.href || index),\n\t\t\t\t\tlabel: link.label,\n\t\t\t\t\thref: link.href || generateUrl('/settings/user/{section}', { section: String(link.section) }),\n\t\t\t\t\ticon: typeof link.icon === 'string' ? link.icon : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/** @return {object} The board's 900px width, over CnDialog's width roles. */\n\t\tframeStyle() {\n\t\t\treturn { '--cn-dialog-width': '900px' }\n\t\t},\n\n\t\t/** @return {string} The navigation's accessible name. */\n\t\tnavLabel() {\n\t\t\treturn t('nextcloud-vue', 'Sections of the settings')\n\t\t},\n\n\t\t/** @return {string} The footer note, with the app's name when known. */\n\t\tfooterNote() {\n\t\t\tconst note = t('nextcloud-vue', 'Changes are saved straight away')\n\t\t\treturn this.appName ? `${note} · ${this.appName}` : note\n\t\t},\n\n\t\t/** @return {string} The Close button label. */\n\t\tcloseLabel() {\n\t\t\treturn t('nextcloud-vue', 'Close')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Add a section to the navigation, kept in `order`; a section\n\t\t * without one goes last. Until the reader picks one, the first\n\t\t * section is current.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t * @param {string} name The section name.\n\t\t * @param {number} [order] The section's place. A section's icon, the\n\t\t * fourth argument NcAppSettingsSection passes, is not drawn: the\n\t\t * board lists the sections by name alone.\n\t\t */\n\t\tregisterSection(id, name, order) {\n\t\t\tconst rest = this.sections.filter((section) => section.id !== id)\n\t\t\tconst place = typeof order === 'number' ? order : Math.max(0, ...rest.map((section) => section.order)) + 1\n\t\t\tthis.sections = [...rest, { id, name, order: place }].sort((a, b) => a.order - b.order)\n\t\t\tif (!this.picked) {\n\t\t\t\tthis.selected = this.sections[0].id\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove a section from the navigation.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tunregisterSection(id) {\n\t\t\tthis.sections = this.sections.filter((section) => section.id !== id)\n\t\t\tif (this.selected === id) {\n\t\t\t\tthis.selected = this.sections.length > 0 ? this.sections[0].id : ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Mark a section current, scroll it to the top of the pane and focus\n\t\t * its heading. Every section stays in the pane.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tselectSection(id) {\n\t\t\tthis.picked = true\n\t\t\tthis.selected = id\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst el = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null\n\t\t\t\tif (el && typeof el.scrollIntoView === 'function') {\n\t\t\t\t\tel.scrollIntoView({ block: 'start', behavior: 'smooth' })\n\t\t\t\t}\n\t\t\t\tconst heading = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}--label`) : null\n\t\t\t\tif (heading) {\n\t\t\t\t\theading.setAttribute('tabindex', '-1')\n\t\t\t\t\theading.focus({ preventScroll: true })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Follow NcDialog's own open state.\n\t\t *\n\t\t * @param {boolean} value Whether NcDialog is open.\n\t\t */\n\t\tonUpdateOpen(value) {\n\t\t\tif (!value) {\n\t\t\t\tthis.close()\n\t\t\t}\n\t\t},\n\n\t\t/** Close the dialog. */\n\t\tclose() {\n\t\t\t/**\n\t\t\t * @event update:open Emitted with `false` when the dialog closes (Close button, close icon, Escape).\n\t\t\t */\n\t\t\tthis.$emit('update:open', false)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The DqPersoonlijkeInstellingen board: a tinted navigation column with the\n title on top, the sections on a white pane beside it, and the close icon\n in the corner. The dialog's frame (900px, its tint, no padding) is in\n dialog.css, on the teleported container. */\n.cn-user-settings-dialog__body {\n\tposition: relative;\n\tdisplay: flex;\n\tmin-height: 440px;\n\tmax-height: min(760px, calc(100vh - 112px));\n}\n\n.cn-user-settings-dialog__nav {\n\tdisplay: flex;\n\tflex: 0 0 200px;\n\tflex-direction: column;\n\tgap: 2px;\n\tbox-sizing: border-box;\n\twidth: 200px;\n\tpadding: 0 4px 12px;\n\toverflow-y: auto;\n}\n\n.cn-user-settings-dialog__title {\n\tmargin: 0;\n\tpadding: 14px 10px 12px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.25;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__nav ul {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n/* A navigation entry, not a link in running text: no underline, also not\n on hover. `!important` because a theme's `a { text-decoration }` carries\n it (thematiq), and a bare `a` with `!important` beats any class. The same\n theme paints every link `var(--nldesign-color-on-surface, <link colour>)\n !important`, so the entry hands it the main text colour through that\n token; only the open entry is marked, by its tint and bar. */\n.cn-user-settings-dialog__nav-link,\n.cn-user-settings-dialog__nav-link:visited {\n\t--nldesign-color-on-surface: var(--color-main-text);\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tmin-height: 36px;\n\tpadding: 0 10px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 8%, transparent);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The open section: a stronger tint and a 3px bar at the left edge. */\n.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 16%, var(--color-primary-element-light));\n}\n\n.cn-user-settings-dialog__nav-link--active::before {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 6px;\n\tbottom: 6px;\n\tinset-inline-start: 0;\n\twidth: 3px;\n\tborder-radius: 2px;\n\tbackground: var(--color-primary-element);\n}\n\n.cn-user-settings-dialog__group {\n\tmargin: 10px 0 2px;\n\tpadding: 0 10px;\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__group-sections {\n\tpadding-inline-start: 10px;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 auto;\n\tflex-direction: column;\n\tgap: 32px;\n\tbox-sizing: border-box;\n\tmin-width: 0;\n\tpadding: 20px 64px 20px 24px;\n\toverflow-y: auto;\n\tborder-inline-start: 1px solid var(--color-border);\n\tborder-radius: 8px 0 0 8px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 6px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n}\n\n.cn-user-settings-dialog__note {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* One class more than NcButton's own `position: relative`, which otherwise\n wins at the same weight and leaves the button stretched in the body row. */\n.cn-user-settings-dialog__body > .cn-user-settings-dialog__close {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 8px;\n}\n\n@media (max-width: 600px) {\n\t.cn-user-settings-dialog__body {\n\t\tflex-direction: column;\n\t}\n\n\t.cn-user-settings-dialog__nav {\n\t\tflex-basis: auto;\n\t\twidth: auto;\n\t\tmax-height: 40vh;\n\t}\n\n\t.cn-user-settings-dialog__sections {\n\t\tpadding: 20px 16px;\n\t\tborder-inline-start: 0;\n\t\tborder-radius: 8px 8px 0 0;\n\t}\n}\n</style>\n"],"names":["CnDialog","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnUserSettingsDialog.vue2.js","sources":["../../../../src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\tclass=\"cn-user-settings-dialog\"\n\t\t:name=\"title\"\n\t\tlook=\"board\"\n\t\theadless\n\t\t:open=\"open\"\n\t\t:style=\"frameStyle\"\n\t\tdata-testid=\"cn-user-settings-dialog\"\n\t\t@update:open=\"onUpdateOpen\"\n\t\t@closing=\"close\">\n\t\t<div class=\"cn-user-settings-dialog__body\">\n\t\t\t<nav\n\t\t\t\tclass=\"cn-user-settings-dialog__nav\"\n\t\t\t\t:aria-label=\"navLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-nav\">\n\t\t\t\t<h2 class=\"cn-user-settings-dialog__title\" data-testid=\"cn-user-settings-title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</h2>\n\t\t\t\t<!-- `nextcloudLinks`: Nextcloud's own personal settings first, as\n\t\t\t\t links, then one entry for this app holding its sections. -->\n\t\t\t\t<ul v-if=\"ncLinks.length > 0\" class=\"cn-user-settings-dialog__nc-links\" data-testid=\"cn-user-settings-nc-links\">\n\t\t\t\t\t<li v-for=\"link in ncLinks\" :key=\"link.key\">\n\t\t\t\t\t\t<a :href=\"link.href\" class=\"cn-user-settings-dialog__nav-link\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ link.label }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"ncLinks.length > 0 && appName\"\n\t\t\t\t\tclass=\"cn-user-settings-dialog__group\"\n\t\t\t\t\tdata-testid=\"cn-user-settings-app-group\">\n\t\t\t\t\t{{ appName }}\n\t\t\t\t</p>\n\t\t\t\t<ul :class=\"{ 'cn-user-settings-dialog__group-sections': ncLinks.length > 0 }\">\n\t\t\t\t\t<li v-for=\"section in sections\" :key=\"section.id\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"`#settings-section_${section.id}`\"\n\t\t\t\t\t\t\tclass=\"cn-user-settings-dialog__nav-link\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-user-settings-dialog__nav-link--active': section.id === selected }\"\n\t\t\t\t\t\t\t:aria-current=\"section.id === selected ? 'true' : undefined\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-user-settings-nav-${section.id}`\"\n\t\t\t\t\t\t\t@click.prevent=\"selectSection(section.id)\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ section.name }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t\t<div class=\"cn-user-settings-dialog__sections\">\n\t\t\t\t<!-- @slot default NcAppSettingsSection children; each registers itself in the navigation. -->\n\t\t\t\t<slot />\n\t\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t\t{{ footerNote }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<NcButton\n\t\t\t\tclass=\"cn-user-settings-dialog__close\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:aria-label=\"closeLabel\"\n\t\t\t\t:title=\"closeLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-close\"\n\t\t\t\t@click=\"close\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcButton>\n\t\t</div>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton } from '@nextcloud/vue'\nimport { ref } from 'vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\n\n/**\n * The key NcAppSettingsSection injects to register itself with its dialog\n * (`Symbol.for`, so the same symbol `@nextcloud/vue` provides).\n */\nconst REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration')\n\n/** The key NcAppSettingsSection injects for the legacy design flag. */\nconst LEGACY_DESIGN_KEY = Symbol.for('NcAppSettingsDialog:legacy')\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look, as the\n * DqPersoonlijkeInstellingen board draws Nextcloud's own settings dialog:\n * 900px wide, the title at the top of a tinted navigation column, every\n * section stacked on a white pane that the navigation scrolls, a close icon\n * in the corner, and no header or footer. Under the sections a line says\n * that changes save straight away.\n *\n * The sections stay the app's: any `NcAppSettingsSection` in the default slot\n * registers itself with this dialog exactly as it does with\n * `NcAppSettingsDialog`, with its name and order. CnAppRoot renders this\n * dialog instead of `NcAppSettingsDialog` under the board look.\n *\n * @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look\n * @spec openspec/changes/user-settings-dialog-dq-board/specs/user-settings-dialog-dq-board/spec.md#requirement-the-user-settings-dialog-follows-the-dq-board\n *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { Close, CnDialog, NcButton },\n\n\tprovide() {\n\t\treturn {\n\t\t\t[REGISTRATION_KEY]: {\n\t\t\t\tregisterSection: this.registerSection,\n\t\t\t\tunregisterSection: this.unregisterSection,\n\t\t\t},\n\n\t\t\t// NcAppSettingsSection also asks its dialog whether to use the legacy design.\n\t\t\t[LEGACY_DESIGN_KEY]: ref(false),\n\t\t}\n\t},\n\n\tprops: {\n\t\t/** Whether the dialog is open. */\n\t\topen: { type: Boolean, default: false },\n\t\t/** Dialog title; also its accessible name. */\n\t\ttitle: { type: String, required: true },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line above the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line under the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\tsubtitle: { type: String, default: () => t('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },\n\t\t/** The app's name, after the footer note and as the navigation entry holding its sections. */\n\t\tappName: { type: String, default: '' },\n\t\t/**\n\t\t * Nextcloud's own personal settings, listed first in the navigation as\n\t\t * links: `[{ label, section? | href?, icon? }]`. `section` is a\n\t\t * personal settings section id (`/settings/user/<section>`). With any,\n\t\t * the app's sections sit under one entry named after the app.\n\t\t */\n\t\tnextcloudLinks: { type: Array, default: () => [] },\n\t},\n\n\temits: ['update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tsections: [],\n\t\t\tselected: '',\n\t\t\tpicked: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The Nextcloud links with their URL.\n\t\t *\n\t\t * @spec openspec/changes/user-settings-nav-groups/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-navigation-can-list-nextclouds-own-sections-first\n\t\t * @return {Array<{key: string, label: string, href: string, icon: string}>}\n\t\t */\n\t\tncLinks() {\n\t\t\treturn (Array.isArray(this.nextcloudLinks) ? this.nextcloudLinks : [])\n\t\t\t\t.filter((link) => link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '' && (link.href || link.section))\n\t\t\t\t.map((link, index) => ({\n\t\t\t\t\tkey: String(link.section || link.href || index),\n\t\t\t\t\tlabel: link.label,\n\t\t\t\t\thref: link.href || generateUrl('/settings/user/{section}', { section: String(link.section) }),\n\t\t\t\t\ticon: typeof link.icon === 'string' ? link.icon : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/** @return {object} The board's 900px width, over CnDialog's width roles. */\n\t\tframeStyle() {\n\t\t\treturn { '--cn-dialog-width': '900px' }\n\t\t},\n\n\t\t/** @return {string} The navigation's accessible name. */\n\t\tnavLabel() {\n\t\t\treturn t('nextcloud-vue', 'Sections of the settings')\n\t\t},\n\n\t\t/** @return {string} The footer note, with the app's name when known. */\n\t\tfooterNote() {\n\t\t\tconst note = t('nextcloud-vue', 'Changes are saved straight away')\n\t\t\treturn this.appName ? `${note} · ${this.appName}` : note\n\t\t},\n\n\t\t/** @return {string} The Close button label. */\n\t\tcloseLabel() {\n\t\t\treturn t('nextcloud-vue', 'Close')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Add a section to the navigation, kept in `order`; a section\n\t\t * without one goes last. Until the reader picks one, the first\n\t\t * section is current.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t * @param {string} name The section name.\n\t\t * @param {number} [order] The section's place. A section's icon, the\n\t\t * fourth argument NcAppSettingsSection passes, is not drawn: the\n\t\t * board lists the sections by name alone.\n\t\t */\n\t\tregisterSection(id, name, order) {\n\t\t\tconst rest = this.sections.filter((section) => section.id !== id)\n\t\t\tconst place = typeof order === 'number' ? order : Math.max(0, ...rest.map((section) => section.order)) + 1\n\t\t\tthis.sections = [...rest, { id, name, order: place }].sort((a, b) => a.order - b.order)\n\t\t\tif (!this.picked) {\n\t\t\t\tthis.selected = this.sections[0].id\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove a section from the navigation.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tunregisterSection(id) {\n\t\t\tthis.sections = this.sections.filter((section) => section.id !== id)\n\t\t\tif (this.selected === id) {\n\t\t\t\tthis.selected = this.sections.length > 0 ? this.sections[0].id : ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Mark a section current, scroll it to the top of the pane and focus\n\t\t * its heading. Every section stays in the pane.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tselectSection(id) {\n\t\t\tthis.picked = true\n\t\t\tthis.selected = id\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst el = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null\n\t\t\t\tif (el && typeof el.scrollIntoView === 'function') {\n\t\t\t\t\tel.scrollIntoView({ block: 'start', behavior: 'smooth' })\n\t\t\t\t}\n\t\t\t\tconst heading = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}--label`) : null\n\t\t\t\tif (heading) {\n\t\t\t\t\theading.setAttribute('tabindex', '-1')\n\t\t\t\t\theading.focus({ preventScroll: true })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Follow NcDialog's own open state.\n\t\t *\n\t\t * @param {boolean} value Whether NcDialog is open.\n\t\t */\n\t\tonUpdateOpen(value) {\n\t\t\tif (!value) {\n\t\t\t\tthis.close()\n\t\t\t}\n\t\t},\n\n\t\t/** Close the dialog. */\n\t\tclose() {\n\t\t\t/**\n\t\t\t * @event update:open Emitted with `false` when the dialog closes (Close button, close icon, Escape).\n\t\t\t */\n\t\t\tthis.$emit('update:open', false)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The DqPersoonlijkeInstellingen board: a tinted navigation column with the\n title on top, the sections on a white pane beside it, and the close icon\n in the corner. The dialog's frame (900px, its tint, no padding) is in\n dialog.css, on the teleported container. */\n.cn-user-settings-dialog__body {\n\tposition: relative;\n\tdisplay: flex;\n\tmin-height: 440px;\n\tmax-height: min(760px, calc(100vh - 112px));\n}\n\n.cn-user-settings-dialog__nav {\n\tdisplay: flex;\n\tflex: 0 0 200px;\n\tflex-direction: column;\n\tgap: 2px;\n\tbox-sizing: border-box;\n\twidth: 200px;\n\tpadding: 0 4px 12px;\n\toverflow-y: auto;\n}\n\n.cn-user-settings-dialog__title {\n\tmargin: 0;\n\tpadding: 14px 10px 12px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.25;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__nav ul {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n/* A navigation entry, not a link in running text: no underline, also not\n on hover. `!important` because a theme's `a { text-decoration }` carries\n it (thematiq), and a bare `a` with `!important` beats any class. The same\n theme paints every link `var(--nldesign-color-on-surface, <link colour>)\n !important`, so the entry hands it the main text colour through that\n token; only the open entry is marked, by its tint and bar. */\n.cn-user-settings-dialog__nav-link,\n.cn-user-settings-dialog__nav-link:visited {\n\t--nldesign-color-on-surface: var(--color-main-text);\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tmin-height: 36px;\n\tpadding: 0 10px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 8%, transparent);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The open section: a stronger tint and a 3px bar at the left edge. */\n.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 16%, var(--color-primary-element-light));\n}\n\n.cn-user-settings-dialog__nav-link--active::before {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 6px;\n\tbottom: 6px;\n\tinset-inline-start: 0;\n\twidth: 3px;\n\tborder-radius: 2px;\n\tbackground: var(--color-primary-element);\n}\n\n.cn-user-settings-dialog__group {\n\tmargin: 10px 0 2px;\n\tpadding: 0 10px;\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__group-sections {\n\tpadding-inline-start: 10px;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 auto;\n\tflex-direction: column;\n\tgap: 32px;\n\tbox-sizing: border-box;\n\tmin-width: 0;\n\tpadding: 20px 64px 20px 24px;\n\toverflow-y: auto;\n\tborder-inline-start: 1px solid var(--color-border);\n\tborder-radius: 8px 0 0 8px;\n\tbackground: var(--color-main-background);\n}\n\n/* A section's heading and description start at the text's edge, as the\n board draws them (NcAppSettingsSection indents them 12px). */\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\t--app-settings-section-text-offset: 0;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 6px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n}\n\n.cn-user-settings-dialog__note {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* One class more than NcButton's own `position: relative`, which otherwise\n wins at the same weight and leaves the button stretched in the body row. */\n.cn-user-settings-dialog__body > .cn-user-settings-dialog__close {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 8px;\n}\n\n@media (max-width: 600px) {\n\t.cn-user-settings-dialog__body {\n\t\tflex-direction: column;\n\t}\n\n\t.cn-user-settings-dialog__nav {\n\t\tflex-basis: auto;\n\t\twidth: auto;\n\t\tmax-height: 40vh;\n\t}\n\n\t.cn-user-settings-dialog__sections {\n\t\tpadding: 20px 16px;\n\t\tborder-inline-start: 0;\n\t\tborder-radius: 8px 8px 0 0;\n\t}\n}\n</style>\n"],"names":["CnDialog","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnUserSettingsDialog.vue3.js","sources":["../../../../src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue?vue&type=template&id=9a3971e2&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\tclass=\"cn-user-settings-dialog\"\n\t\t:name=\"title\"\n\t\tlook=\"board\"\n\t\theadless\n\t\t:open=\"open\"\n\t\t:style=\"frameStyle\"\n\t\tdata-testid=\"cn-user-settings-dialog\"\n\t\t@update:open=\"onUpdateOpen\"\n\t\t@closing=\"close\">\n\t\t<div class=\"cn-user-settings-dialog__body\">\n\t\t\t<nav\n\t\t\t\tclass=\"cn-user-settings-dialog__nav\"\n\t\t\t\t:aria-label=\"navLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-nav\">\n\t\t\t\t<h2 class=\"cn-user-settings-dialog__title\" data-testid=\"cn-user-settings-title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</h2>\n\t\t\t\t<!-- `nextcloudLinks`: Nextcloud's own personal settings first, as\n\t\t\t\t links, then one entry for this app holding its sections. -->\n\t\t\t\t<ul v-if=\"ncLinks.length > 0\" class=\"cn-user-settings-dialog__nc-links\" data-testid=\"cn-user-settings-nc-links\">\n\t\t\t\t\t<li v-for=\"link in ncLinks\" :key=\"link.key\">\n\t\t\t\t\t\t<a :href=\"link.href\" class=\"cn-user-settings-dialog__nav-link\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ link.label }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"ncLinks.length > 0 && appName\"\n\t\t\t\t\tclass=\"cn-user-settings-dialog__group\"\n\t\t\t\t\tdata-testid=\"cn-user-settings-app-group\">\n\t\t\t\t\t{{ appName }}\n\t\t\t\t</p>\n\t\t\t\t<ul :class=\"{ 'cn-user-settings-dialog__group-sections': ncLinks.length > 0 }\">\n\t\t\t\t\t<li v-for=\"section in sections\" :key=\"section.id\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"`#settings-section_${section.id}`\"\n\t\t\t\t\t\t\tclass=\"cn-user-settings-dialog__nav-link\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-user-settings-dialog__nav-link--active': section.id === selected }\"\n\t\t\t\t\t\t\t:aria-current=\"section.id === selected ? 'true' : undefined\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-user-settings-nav-${section.id}`\"\n\t\t\t\t\t\t\t@click.prevent=\"selectSection(section.id)\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ section.name }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t\t<div class=\"cn-user-settings-dialog__sections\">\n\t\t\t\t<!-- @slot default NcAppSettingsSection children; each registers itself in the navigation. -->\n\t\t\t\t<slot />\n\t\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t\t{{ footerNote }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<NcButton\n\t\t\t\tclass=\"cn-user-settings-dialog__close\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:aria-label=\"closeLabel\"\n\t\t\t\t:title=\"closeLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-close\"\n\t\t\t\t@click=\"close\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcButton>\n\t\t</div>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton } from '@nextcloud/vue'\nimport { ref } from 'vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\n\n/**\n * The key NcAppSettingsSection injects to register itself with its dialog\n * (`Symbol.for`, so the same symbol `@nextcloud/vue` provides).\n */\nconst REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration')\n\n/** The key NcAppSettingsSection injects for the legacy design flag. */\nconst LEGACY_DESIGN_KEY = Symbol.for('NcAppSettingsDialog:legacy')\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look, as the\n * DqPersoonlijkeInstellingen board draws Nextcloud's own settings dialog:\n * 900px wide, the title at the top of a tinted navigation column, every\n * section stacked on a white pane that the navigation scrolls, a close icon\n * in the corner, and no header or footer. Under the sections a line says\n * that changes save straight away.\n *\n * The sections stay the app's: any `NcAppSettingsSection` in the default slot\n * registers itself with this dialog exactly as it does with\n * `NcAppSettingsDialog`, with its name and order. CnAppRoot renders this\n * dialog instead of `NcAppSettingsDialog` under the board look.\n *\n * @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look\n * @spec openspec/changes/user-settings-dialog-dq-board/specs/user-settings-dialog-dq-board/spec.md#requirement-the-user-settings-dialog-follows-the-dq-board\n *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { Close, CnDialog, NcButton },\n\n\tprovide() {\n\t\treturn {\n\t\t\t[REGISTRATION_KEY]: {\n\t\t\t\tregisterSection: this.registerSection,\n\t\t\t\tunregisterSection: this.unregisterSection,\n\t\t\t},\n\n\t\t\t// NcAppSettingsSection also asks its dialog whether to use the legacy design.\n\t\t\t[LEGACY_DESIGN_KEY]: ref(false),\n\t\t}\n\t},\n\n\tprops: {\n\t\t/** Whether the dialog is open. */\n\t\topen: { type: Boolean, default: false },\n\t\t/** Dialog title; also its accessible name. */\n\t\ttitle: { type: String, required: true },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line above the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line under the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\tsubtitle: { type: String, default: () => t('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },\n\t\t/** The app's name, after the footer note and as the navigation entry holding its sections. */\n\t\tappName: { type: String, default: '' },\n\t\t/**\n\t\t * Nextcloud's own personal settings, listed first in the navigation as\n\t\t * links: `[{ label, section? | href?, icon? }]`. `section` is a\n\t\t * personal settings section id (`/settings/user/<section>`). With any,\n\t\t * the app's sections sit under one entry named after the app.\n\t\t */\n\t\tnextcloudLinks: { type: Array, default: () => [] },\n\t},\n\n\temits: ['update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tsections: [],\n\t\t\tselected: '',\n\t\t\tpicked: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The Nextcloud links with their URL.\n\t\t *\n\t\t * @spec openspec/changes/user-settings-nav-groups/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-navigation-can-list-nextclouds-own-sections-first\n\t\t * @return {Array<{key: string, label: string, href: string, icon: string}>}\n\t\t */\n\t\tncLinks() {\n\t\t\treturn (Array.isArray(this.nextcloudLinks) ? this.nextcloudLinks : [])\n\t\t\t\t.filter((link) => link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '' && (link.href || link.section))\n\t\t\t\t.map((link, index) => ({\n\t\t\t\t\tkey: String(link.section || link.href || index),\n\t\t\t\t\tlabel: link.label,\n\t\t\t\t\thref: link.href || generateUrl('/settings/user/{section}', { section: String(link.section) }),\n\t\t\t\t\ticon: typeof link.icon === 'string' ? link.icon : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/** @return {object} The board's 900px width, over CnDialog's width roles. */\n\t\tframeStyle() {\n\t\t\treturn { '--cn-dialog-width': '900px' }\n\t\t},\n\n\t\t/** @return {string} The navigation's accessible name. */\n\t\tnavLabel() {\n\t\t\treturn t('nextcloud-vue', 'Sections of the settings')\n\t\t},\n\n\t\t/** @return {string} The footer note, with the app's name when known. */\n\t\tfooterNote() {\n\t\t\tconst note = t('nextcloud-vue', 'Changes are saved straight away')\n\t\t\treturn this.appName ? `${note} · ${this.appName}` : note\n\t\t},\n\n\t\t/** @return {string} The Close button label. */\n\t\tcloseLabel() {\n\t\t\treturn t('nextcloud-vue', 'Close')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Add a section to the navigation, kept in `order`; a section\n\t\t * without one goes last. Until the reader picks one, the first\n\t\t * section is current.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t * @param {string} name The section name.\n\t\t * @param {number} [order] The section's place. A section's icon, the\n\t\t * fourth argument NcAppSettingsSection passes, is not drawn: the\n\t\t * board lists the sections by name alone.\n\t\t */\n\t\tregisterSection(id, name, order) {\n\t\t\tconst rest = this.sections.filter((section) => section.id !== id)\n\t\t\tconst place = typeof order === 'number' ? order : Math.max(0, ...rest.map((section) => section.order)) + 1\n\t\t\tthis.sections = [...rest, { id, name, order: place }].sort((a, b) => a.order - b.order)\n\t\t\tif (!this.picked) {\n\t\t\t\tthis.selected = this.sections[0].id\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove a section from the navigation.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tunregisterSection(id) {\n\t\t\tthis.sections = this.sections.filter((section) => section.id !== id)\n\t\t\tif (this.selected === id) {\n\t\t\t\tthis.selected = this.sections.length > 0 ? this.sections[0].id : ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Mark a section current, scroll it to the top of the pane and focus\n\t\t * its heading. Every section stays in the pane.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tselectSection(id) {\n\t\t\tthis.picked = true\n\t\t\tthis.selected = id\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst el = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null\n\t\t\t\tif (el && typeof el.scrollIntoView === 'function') {\n\t\t\t\t\tel.scrollIntoView({ block: 'start', behavior: 'smooth' })\n\t\t\t\t}\n\t\t\t\tconst heading = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}--label`) : null\n\t\t\t\tif (heading) {\n\t\t\t\t\theading.setAttribute('tabindex', '-1')\n\t\t\t\t\theading.focus({ preventScroll: true })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Follow NcDialog's own open state.\n\t\t *\n\t\t * @param {boolean} value Whether NcDialog is open.\n\t\t */\n\t\tonUpdateOpen(value) {\n\t\t\tif (!value) {\n\t\t\t\tthis.close()\n\t\t\t}\n\t\t},\n\n\t\t/** Close the dialog. */\n\t\tclose() {\n\t\t\t/**\n\t\t\t * @event update:open Emitted with `false` when the dialog closes (Close button, close icon, Escape).\n\t\t\t */\n\t\t\tthis.$emit('update:open', false)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The DqPersoonlijkeInstellingen board: a tinted navigation column with the\n title on top, the sections on a white pane beside it, and the close icon\n in the corner. The dialog's frame (900px, its tint, no padding) is in\n dialog.css, on the teleported container. */\n.cn-user-settings-dialog__body {\n\tposition: relative;\n\tdisplay: flex;\n\tmin-height: 440px;\n\tmax-height: min(760px, calc(100vh - 112px));\n}\n\n.cn-user-settings-dialog__nav {\n\tdisplay: flex;\n\tflex: 0 0 200px;\n\tflex-direction: column;\n\tgap: 2px;\n\tbox-sizing: border-box;\n\twidth: 200px;\n\tpadding: 0 4px 12px;\n\toverflow-y: auto;\n}\n\n.cn-user-settings-dialog__title {\n\tmargin: 0;\n\tpadding: 14px 10px 12px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.25;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__nav ul {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n/* A navigation entry, not a link in running text: no underline, also not\n on hover. `!important` because a theme's `a { text-decoration }` carries\n it (thematiq), and a bare `a` with `!important` beats any class. The same\n theme paints every link `var(--nldesign-color-on-surface, <link colour>)\n !important`, so the entry hands it the main text colour through that\n token; only the open entry is marked, by its tint and bar. */\n.cn-user-settings-dialog__nav-link,\n.cn-user-settings-dialog__nav-link:visited {\n\t--nldesign-color-on-surface: var(--color-main-text);\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tmin-height: 36px;\n\tpadding: 0 10px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 8%, transparent);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The open section: a stronger tint and a 3px bar at the left edge. */\n.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 16%, var(--color-primary-element-light));\n}\n\n.cn-user-settings-dialog__nav-link--active::before {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 6px;\n\tbottom: 6px;\n\tinset-inline-start: 0;\n\twidth: 3px;\n\tborder-radius: 2px;\n\tbackground: var(--color-primary-element);\n}\n\n.cn-user-settings-dialog__group {\n\tmargin: 10px 0 2px;\n\tpadding: 0 10px;\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__group-sections {\n\tpadding-inline-start: 10px;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 auto;\n\tflex-direction: column;\n\tgap: 32px;\n\tbox-sizing: border-box;\n\tmin-width: 0;\n\tpadding: 20px 64px 20px 24px;\n\toverflow-y: auto;\n\tborder-inline-start: 1px solid var(--color-border);\n\tborder-radius: 8px 0 0 8px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 6px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n}\n\n.cn-user-settings-dialog__note {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* One class more than NcButton's own `position: relative`, which otherwise\n wins at the same weight and leaves the button stretched in the body row. */\n.cn-user-settings-dialog__body > .cn-user-settings-dialog__close {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 8px;\n}\n\n@media (max-width: 600px) {\n\t.cn-user-settings-dialog__body {\n\t\tflex-direction: column;\n\t}\n\n\t.cn-user-settings-dialog__nav {\n\t\tflex-basis: auto;\n\t\twidth: auto;\n\t\tmax-height: 40vh;\n\t}\n\n\t.cn-user-settings-dialog__sections {\n\t\tpadding: 20px 16px;\n\t\tborder-inline-start: 0;\n\t\tborder-radius: 8px 8px 0 0;\n\t}\n}\n</style>\n"],"names":["_createBlock","_createElementVNode","_toDisplayString","_openBlock","_createElementBlock","_Fragment","_renderList","_normalizeClass","_withModifiers","_renderSlot","_createVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnUserSettingsDialog.vue3.js","sources":["../../../../src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue?vue&type=template&id=2743a776&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\tclass=\"cn-user-settings-dialog\"\n\t\t:name=\"title\"\n\t\tlook=\"board\"\n\t\theadless\n\t\t:open=\"open\"\n\t\t:style=\"frameStyle\"\n\t\tdata-testid=\"cn-user-settings-dialog\"\n\t\t@update:open=\"onUpdateOpen\"\n\t\t@closing=\"close\">\n\t\t<div class=\"cn-user-settings-dialog__body\">\n\t\t\t<nav\n\t\t\t\tclass=\"cn-user-settings-dialog__nav\"\n\t\t\t\t:aria-label=\"navLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-nav\">\n\t\t\t\t<h2 class=\"cn-user-settings-dialog__title\" data-testid=\"cn-user-settings-title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</h2>\n\t\t\t\t<!-- `nextcloudLinks`: Nextcloud's own personal settings first, as\n\t\t\t\t links, then one entry for this app holding its sections. -->\n\t\t\t\t<ul v-if=\"ncLinks.length > 0\" class=\"cn-user-settings-dialog__nc-links\" data-testid=\"cn-user-settings-nc-links\">\n\t\t\t\t\t<li v-for=\"link in ncLinks\" :key=\"link.key\">\n\t\t\t\t\t\t<a :href=\"link.href\" class=\"cn-user-settings-dialog__nav-link\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ link.label }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"ncLinks.length > 0 && appName\"\n\t\t\t\t\tclass=\"cn-user-settings-dialog__group\"\n\t\t\t\t\tdata-testid=\"cn-user-settings-app-group\">\n\t\t\t\t\t{{ appName }}\n\t\t\t\t</p>\n\t\t\t\t<ul :class=\"{ 'cn-user-settings-dialog__group-sections': ncLinks.length > 0 }\">\n\t\t\t\t\t<li v-for=\"section in sections\" :key=\"section.id\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"`#settings-section_${section.id}`\"\n\t\t\t\t\t\t\tclass=\"cn-user-settings-dialog__nav-link\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-user-settings-dialog__nav-link--active': section.id === selected }\"\n\t\t\t\t\t\t\t:aria-current=\"section.id === selected ? 'true' : undefined\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-user-settings-nav-${section.id}`\"\n\t\t\t\t\t\t\t@click.prevent=\"selectSection(section.id)\">\n\t\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__nav-label\">{{ section.name }}</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t\t<div class=\"cn-user-settings-dialog__sections\">\n\t\t\t\t<!-- @slot default NcAppSettingsSection children; each registers itself in the navigation. -->\n\t\t\t\t<slot />\n\t\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t\t{{ footerNote }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<NcButton\n\t\t\t\tclass=\"cn-user-settings-dialog__close\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:aria-label=\"closeLabel\"\n\t\t\t\t:title=\"closeLabel\"\n\t\t\t\tdata-testid=\"cn-user-settings-close\"\n\t\t\t\t@click=\"close\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcButton>\n\t\t</div>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton } from '@nextcloud/vue'\nimport { ref } from 'vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\n\n/**\n * The key NcAppSettingsSection injects to register itself with its dialog\n * (`Symbol.for`, so the same symbol `@nextcloud/vue` provides).\n */\nconst REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration')\n\n/** The key NcAppSettingsSection injects for the legacy design flag. */\nconst LEGACY_DESIGN_KEY = Symbol.for('NcAppSettingsDialog:legacy')\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look, as the\n * DqPersoonlijkeInstellingen board draws Nextcloud's own settings dialog:\n * 900px wide, the title at the top of a tinted navigation column, every\n * section stacked on a white pane that the navigation scrolls, a close icon\n * in the corner, and no header or footer. Under the sections a line says\n * that changes save straight away.\n *\n * The sections stay the app's: any `NcAppSettingsSection` in the default slot\n * registers itself with this dialog exactly as it does with\n * `NcAppSettingsDialog`, with its name and order. CnAppRoot renders this\n * dialog instead of `NcAppSettingsDialog` under the board look.\n *\n * @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look\n * @spec openspec/changes/user-settings-dialog-dq-board/specs/user-settings-dialog-dq-board/spec.md#requirement-the-user-settings-dialog-follows-the-dq-board\n *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { Close, CnDialog, NcButton },\n\n\tprovide() {\n\t\treturn {\n\t\t\t[REGISTRATION_KEY]: {\n\t\t\t\tregisterSection: this.registerSection,\n\t\t\t\tunregisterSection: this.unregisterSection,\n\t\t\t},\n\n\t\t\t// NcAppSettingsSection also asks its dialog whether to use the legacy design.\n\t\t\t[LEGACY_DESIGN_KEY]: ref(false),\n\t\t}\n\t},\n\n\tprops: {\n\t\t/** Whether the dialog is open. */\n\t\topen: { type: Boolean, default: false },\n\t\t/** Dialog title; also its accessible name. */\n\t\ttitle: { type: String, required: true },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line above the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/**\n\t\t * Deprecated and ignored: the board draws no line under the title.\n\t\t * Kept so a host that passes it stays valid.\n\t\t *\n\t\t * @deprecated\n\t\t */\n\t\tsubtitle: { type: String, default: () => t('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },\n\t\t/** The app's name, after the footer note and as the navigation entry holding its sections. */\n\t\tappName: { type: String, default: '' },\n\t\t/**\n\t\t * Nextcloud's own personal settings, listed first in the navigation as\n\t\t * links: `[{ label, section? | href?, icon? }]`. `section` is a\n\t\t * personal settings section id (`/settings/user/<section>`). With any,\n\t\t * the app's sections sit under one entry named after the app.\n\t\t */\n\t\tnextcloudLinks: { type: Array, default: () => [] },\n\t},\n\n\temits: ['update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tsections: [],\n\t\t\tselected: '',\n\t\t\tpicked: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The Nextcloud links with their URL.\n\t\t *\n\t\t * @spec openspec/changes/user-settings-nav-groups/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-navigation-can-list-nextclouds-own-sections-first\n\t\t * @return {Array<{key: string, label: string, href: string, icon: string}>}\n\t\t */\n\t\tncLinks() {\n\t\t\treturn (Array.isArray(this.nextcloudLinks) ? this.nextcloudLinks : [])\n\t\t\t\t.filter((link) => link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '' && (link.href || link.section))\n\t\t\t\t.map((link, index) => ({\n\t\t\t\t\tkey: String(link.section || link.href || index),\n\t\t\t\t\tlabel: link.label,\n\t\t\t\t\thref: link.href || generateUrl('/settings/user/{section}', { section: String(link.section) }),\n\t\t\t\t\ticon: typeof link.icon === 'string' ? link.icon : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/** @return {object} The board's 900px width, over CnDialog's width roles. */\n\t\tframeStyle() {\n\t\t\treturn { '--cn-dialog-width': '900px' }\n\t\t},\n\n\t\t/** @return {string} The navigation's accessible name. */\n\t\tnavLabel() {\n\t\t\treturn t('nextcloud-vue', 'Sections of the settings')\n\t\t},\n\n\t\t/** @return {string} The footer note, with the app's name when known. */\n\t\tfooterNote() {\n\t\t\tconst note = t('nextcloud-vue', 'Changes are saved straight away')\n\t\t\treturn this.appName ? `${note} · ${this.appName}` : note\n\t\t},\n\n\t\t/** @return {string} The Close button label. */\n\t\tcloseLabel() {\n\t\t\treturn t('nextcloud-vue', 'Close')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Add a section to the navigation, kept in `order`; a section\n\t\t * without one goes last. Until the reader picks one, the first\n\t\t * section is current.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t * @param {string} name The section name.\n\t\t * @param {number} [order] The section's place. A section's icon, the\n\t\t * fourth argument NcAppSettingsSection passes, is not drawn: the\n\t\t * board lists the sections by name alone.\n\t\t */\n\t\tregisterSection(id, name, order) {\n\t\t\tconst rest = this.sections.filter((section) => section.id !== id)\n\t\t\tconst place = typeof order === 'number' ? order : Math.max(0, ...rest.map((section) => section.order)) + 1\n\t\t\tthis.sections = [...rest, { id, name, order: place }].sort((a, b) => a.order - b.order)\n\t\t\tif (!this.picked) {\n\t\t\t\tthis.selected = this.sections[0].id\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove a section from the navigation.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tunregisterSection(id) {\n\t\t\tthis.sections = this.sections.filter((section) => section.id !== id)\n\t\t\tif (this.selected === id) {\n\t\t\t\tthis.selected = this.sections.length > 0 ? this.sections[0].id : ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Mark a section current, scroll it to the top of the pane and focus\n\t\t * its heading. Every section stays in the pane.\n\t\t *\n\t\t * @param {string} id The section id.\n\t\t */\n\t\tselectSection(id) {\n\t\t\tthis.picked = true\n\t\t\tthis.selected = id\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst el = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null\n\t\t\t\tif (el && typeof el.scrollIntoView === 'function') {\n\t\t\t\t\tel.scrollIntoView({ block: 'start', behavior: 'smooth' })\n\t\t\t\t}\n\t\t\t\tconst heading = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}--label`) : null\n\t\t\t\tif (heading) {\n\t\t\t\t\theading.setAttribute('tabindex', '-1')\n\t\t\t\t\theading.focus({ preventScroll: true })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Follow NcDialog's own open state.\n\t\t *\n\t\t * @param {boolean} value Whether NcDialog is open.\n\t\t */\n\t\tonUpdateOpen(value) {\n\t\t\tif (!value) {\n\t\t\t\tthis.close()\n\t\t\t}\n\t\t},\n\n\t\t/** Close the dialog. */\n\t\tclose() {\n\t\t\t/**\n\t\t\t * @event update:open Emitted with `false` when the dialog closes (Close button, close icon, Escape).\n\t\t\t */\n\t\t\tthis.$emit('update:open', false)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The DqPersoonlijkeInstellingen board: a tinted navigation column with the\n title on top, the sections on a white pane beside it, and the close icon\n in the corner. The dialog's frame (900px, its tint, no padding) is in\n dialog.css, on the teleported container. */\n.cn-user-settings-dialog__body {\n\tposition: relative;\n\tdisplay: flex;\n\tmin-height: 440px;\n\tmax-height: min(760px, calc(100vh - 112px));\n}\n\n.cn-user-settings-dialog__nav {\n\tdisplay: flex;\n\tflex: 0 0 200px;\n\tflex-direction: column;\n\tgap: 2px;\n\tbox-sizing: border-box;\n\twidth: 200px;\n\tpadding: 0 4px 12px;\n\toverflow-y: auto;\n}\n\n.cn-user-settings-dialog__title {\n\tmargin: 0;\n\tpadding: 14px 10px 12px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.25;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__nav ul {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n/* A navigation entry, not a link in running text: no underline, also not\n on hover. `!important` because a theme's `a { text-decoration }` carries\n it (thematiq), and a bare `a` with `!important` beats any class. The same\n theme paints every link `var(--nldesign-color-on-surface, <link colour>)\n !important`, so the entry hands it the main text colour through that\n token; only the open entry is marked, by its tint and bar. */\n.cn-user-settings-dialog__nav-link,\n.cn-user-settings-dialog__nav-link:visited {\n\t--nldesign-color-on-surface: var(--color-main-text);\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tmin-height: 36px;\n\tpadding: 0 10px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 8%, transparent);\n\ttext-decoration: none !important;\n}\n\n.cn-user-settings-dialog__nav-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The open section: a stronger tint and a 3px bar at the left edge. */\n.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: color-mix(in srgb, var(--color-primary-element) 16%, var(--color-primary-element-light));\n}\n\n.cn-user-settings-dialog__nav-link--active::before {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 6px;\n\tbottom: 6px;\n\tinset-inline-start: 0;\n\twidth: 3px;\n\tborder-radius: 2px;\n\tbackground: var(--color-primary-element);\n}\n\n.cn-user-settings-dialog__group {\n\tmargin: 10px 0 2px;\n\tpadding: 0 10px;\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-user-settings-dialog__group-sections {\n\tpadding-inline-start: 10px;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 auto;\n\tflex-direction: column;\n\tgap: 32px;\n\tbox-sizing: border-box;\n\tmin-width: 0;\n\tpadding: 20px 64px 20px 24px;\n\toverflow-y: auto;\n\tborder-inline-start: 1px solid var(--color-border);\n\tborder-radius: 8px 0 0 8px;\n\tbackground: var(--color-main-background);\n}\n\n/* A section's heading and description start at the text's edge, as the\n board draws them (NcAppSettingsSection indents them 12px). */\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\t--app-settings-section-text-offset: 0;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 6px;\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n}\n\n.cn-user-settings-dialog__note {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* One class more than NcButton's own `position: relative`, which otherwise\n wins at the same weight and leaves the button stretched in the body row. */\n.cn-user-settings-dialog__body > .cn-user-settings-dialog__close {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 8px;\n}\n\n@media (max-width: 600px) {\n\t.cn-user-settings-dialog__body {\n\t\tflex-direction: column;\n\t}\n\n\t.cn-user-settings-dialog__nav {\n\t\tflex-basis: auto;\n\t\twidth: auto;\n\t\tmax-height: 40vh;\n\t}\n\n\t.cn-user-settings-dialog__sections {\n\t\tpadding: 20px 16px;\n\t\tborder-inline-start: 0;\n\t\tborder-radius: 8px 8px 0 0;\n\t}\n}\n</style>\n"],"names":["_createBlock","_createElementVNode","_toDisplayString","_openBlock","_createElementBlock","_Fragment","_renderList","_normalizeClass","_withModifiers","_renderSlot","_createVNode"],"mappings":"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"}