@conduction/nextcloud-vue 2.96.0 → 2.98.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +17 -0
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +106 -2
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +9 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +964 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +282 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/index.js +28 -0
- package/dist/esm/components/CnChecklistRunWidget/index.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -8
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +188 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +3 -3
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +27 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +16 -2
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +144 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +344 -21
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +9 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js.map +1 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +220 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +121 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +83 -9
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +80 -11
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/composables/indexSources.js +420 -5
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useOrganisationStore.js +115 -0
- package/dist/esm/composables/useOrganisationStore.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +21 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +36 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +725 -383
- package/dist/esm/utils/builtInFormatters.js +34 -1
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/matrixCells.js +158 -0
- package/dist/esm/utils/matrixCells.js.map +1 -0
- package/dist/esm/utils/nextcloudDirectory.js +121 -0
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -0
- package/dist/esm/utils/queryPresets.js +13 -2
- package/dist/esm/utils/queryPresets.js.map +1 -1
- package/dist/esm/utils/scopeListLayout.js +37 -1
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +14443 -10519
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +725 -383
- package/l10n/en.json +21 -1
- package/l10n/nl.json +36 -1
- package/package.json +2 -2
- package/scripts/merge-l10n.js +196 -0
- package/src/components/CnAppRoot/CnAppRoot.md +1 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +17 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
- package/src/components/CnChartWidget/CnChartWidget.md +2 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +106 -2
- package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +1269 -0
- package/src/components/CnChecklistRunWidget/index.js +27 -0
- package/src/components/CnConversationThread/CnConversationThread.md +12 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
- package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +26 -8
- package/src/components/CnDetailPage/CnDetailPage.md +48 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +188 -5
- package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +3 -3
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +38 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/src/components/CnIndexPage/CnIndexPage.md +28 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +367 -27
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
- package/src/components/CnIndexPage/useNamedSource.js +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
- package/src/components/CnOrderedListField/CnOrderedListField.vue +386 -0
- package/src/components/CnOrderedListField/index.js +7 -0
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +139 -10
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/index.js +2 -0
- package/src/composables/indexSources.js +419 -4
- package/src/composables/useOrganisationStore.js +111 -0
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +42 -5
- package/src/utils/builtInFormatters.js +33 -0
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/matrixCells.js +154 -0
- package/src/utils/nextcloudDirectory.js +117 -0
- package/src/utils/queryPresets.js +13 -2
- package/src/utils/scopeListLayout.js +37 -1
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, toDisplayString, createCommentVNode, Fragment, renderList, createElementVNode, createVNode } from 'vue';
|
|
2
|
+
|
|
3
|
+
const _hoisted_1 = {
|
|
4
|
+
class: "cn-ordered-list-field",
|
|
5
|
+
"data-testid": "cn-ordered-list-field"
|
|
6
|
+
};
|
|
7
|
+
const _hoisted_2 = ["id"];
|
|
8
|
+
const _hoisted_3 = ["aria-labelledby", "aria-label"];
|
|
9
|
+
const _hoisted_4 = ["data-testid"];
|
|
10
|
+
const _hoisted_5 = {
|
|
11
|
+
class: "cn-ordered-list-field__position",
|
|
12
|
+
"aria-hidden": "true"
|
|
13
|
+
};
|
|
14
|
+
const _hoisted_6 = { class: "cn-ordered-list-field__text" };
|
|
15
|
+
const _hoisted_7 = { class: "cn-ordered-list-field__actions" };
|
|
16
|
+
const _hoisted_8 = ["disabled", "aria-label", "data-testid", "onClick"];
|
|
17
|
+
const _hoisted_9 = ["disabled", "aria-label", "data-testid", "onClick"];
|
|
18
|
+
const _hoisted_10 = ["disabled", "aria-label", "data-testid", "onClick"];
|
|
19
|
+
const _hoisted_11 = {
|
|
20
|
+
key: 2,
|
|
21
|
+
class: "cn-ordered-list-field__empty",
|
|
22
|
+
"data-testid": "cn-ordered-list-empty"
|
|
23
|
+
};
|
|
24
|
+
const _hoisted_12 = {
|
|
25
|
+
key: 3,
|
|
26
|
+
class: "cn-ordered-list-field__add"
|
|
27
|
+
};
|
|
28
|
+
const _hoisted_13 = {
|
|
29
|
+
class: "hidden-visually",
|
|
30
|
+
"aria-live": "polite",
|
|
31
|
+
"data-testid": "cn-ordered-list-live"
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
35
|
+
const _component_ChevronUp = resolveComponent("ChevronUp");
|
|
36
|
+
const _component_ChevronDown = resolveComponent("ChevronDown");
|
|
37
|
+
const _component_Close = resolveComponent("Close");
|
|
38
|
+
const _component_NcSelect = resolveComponent("NcSelect");
|
|
39
|
+
|
|
40
|
+
return (openBlock(), createElementBlock("div", _hoisted_1, [
|
|
41
|
+
($props.label)
|
|
42
|
+
? (openBlock(), createElementBlock("p", {
|
|
43
|
+
key: 0,
|
|
44
|
+
id: $data.labelId,
|
|
45
|
+
class: "cn-ordered-list-field__label"
|
|
46
|
+
}, toDisplayString($props.label), 9 /* TEXT, PROPS */, _hoisted_2))
|
|
47
|
+
: createCommentVNode("v-if", true),
|
|
48
|
+
($options.rows.length > 0)
|
|
49
|
+
? (openBlock(), createElementBlock("ol", {
|
|
50
|
+
key: 1,
|
|
51
|
+
class: "cn-ordered-list-field__rows",
|
|
52
|
+
"aria-labelledby": $props.label ? $data.labelId : null,
|
|
53
|
+
"aria-label": $props.label ? null : $options.listLabel
|
|
54
|
+
}, [
|
|
55
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.rows, (row, index) => {
|
|
56
|
+
return (openBlock(), createElementBlock("li", {
|
|
57
|
+
key: row.value,
|
|
58
|
+
class: "cn-ordered-list-field__row",
|
|
59
|
+
"data-testid": `cn-ordered-list-row-${row.value}`
|
|
60
|
+
}, [
|
|
61
|
+
createElementVNode("span", _hoisted_5, toDisplayString(index + 1), 1 /* TEXT */),
|
|
62
|
+
createElementVNode("span", _hoisted_6, toDisplayString(row.label), 1 /* TEXT */),
|
|
63
|
+
createElementVNode("span", _hoisted_7, [
|
|
64
|
+
createElementVNode("button", {
|
|
65
|
+
type: "button",
|
|
66
|
+
class: "cn-ordered-list-field__button",
|
|
67
|
+
disabled: $props.disabled || index === 0,
|
|
68
|
+
"aria-label": $options.t('nextcloud-vue', 'Move {item} up', { item: row.label }),
|
|
69
|
+
"data-testid": `cn-ordered-list-up-${row.value}`,
|
|
70
|
+
onClick: $event => ($options.move(index, index - 1))
|
|
71
|
+
}, [
|
|
72
|
+
createVNode(_component_ChevronUp, { size: 20 })
|
|
73
|
+
], 8 /* PROPS */, _hoisted_8),
|
|
74
|
+
createElementVNode("button", {
|
|
75
|
+
type: "button",
|
|
76
|
+
class: "cn-ordered-list-field__button",
|
|
77
|
+
disabled: $props.disabled || index === $options.rows.length - 1,
|
|
78
|
+
"aria-label": $options.t('nextcloud-vue', 'Move {item} down', { item: row.label }),
|
|
79
|
+
"data-testid": `cn-ordered-list-down-${row.value}`,
|
|
80
|
+
onClick: $event => ($options.move(index, index + 1))
|
|
81
|
+
}, [
|
|
82
|
+
createVNode(_component_ChevronDown, { size: 20 })
|
|
83
|
+
], 8 /* PROPS */, _hoisted_9),
|
|
84
|
+
createElementVNode("button", {
|
|
85
|
+
type: "button",
|
|
86
|
+
class: "cn-ordered-list-field__button",
|
|
87
|
+
disabled: $props.disabled,
|
|
88
|
+
"aria-label": $options.t('nextcloud-vue', 'Remove {item}', { item: row.label }),
|
|
89
|
+
"data-testid": `cn-ordered-list-remove-${row.value}`,
|
|
90
|
+
onClick: $event => ($options.remove(index))
|
|
91
|
+
}, [
|
|
92
|
+
createVNode(_component_Close, { size: 20 })
|
|
93
|
+
], 8 /* PROPS */, _hoisted_10)
|
|
94
|
+
])
|
|
95
|
+
], 8 /* PROPS */, _hoisted_4))
|
|
96
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
97
|
+
], 8 /* PROPS */, _hoisted_3))
|
|
98
|
+
: (openBlock(), createElementBlock("p", _hoisted_11, toDisplayString($props.emptyText || $options.t('nextcloud-vue', 'Nothing added yet.')), 1 /* TEXT */)),
|
|
99
|
+
(!$props.disabled)
|
|
100
|
+
? (openBlock(), createElementBlock("div", _hoisted_12, [
|
|
101
|
+
createVNode(_component_NcSelect, {
|
|
102
|
+
modelValue: null,
|
|
103
|
+
options: $options.available,
|
|
104
|
+
label: "label",
|
|
105
|
+
inputLabel: $props.addLabel || $options.t('nextcloud-vue', 'Add'),
|
|
106
|
+
placeholder: $props.addPlaceholder,
|
|
107
|
+
loading: $props.loading,
|
|
108
|
+
clearable: false,
|
|
109
|
+
filterable: !$props.searchable,
|
|
110
|
+
"data-testid": "cn-ordered-list-add",
|
|
111
|
+
onSearch: $options.onSearch,
|
|
112
|
+
"onUpdate:modelValue": $options.add
|
|
113
|
+
}, null, 8 /* PROPS */, ["options", "inputLabel", "placeholder", "loading", "filterable", "onSearch", "onUpdate:modelValue"])
|
|
114
|
+
]))
|
|
115
|
+
: createCommentVNode("v-if", true),
|
|
116
|
+
createElementVNode("p", _hoisted_13, toDisplayString($data.announcement), 1 /* TEXT */)
|
|
117
|
+
]))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { render };
|
|
121
|
+
//# sourceMappingURL=CnOrderedListField.vue3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnOrderedListField.vue3.js","sources":["../../../../src/components/CnOrderedListField/CnOrderedListField.vue?vue&type=template&id=81cf425a&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-ordered-list-field\" data-testid=\"cn-ordered-list-field\">\n\t\t<p v-if=\"label\" :id=\"labelId\" class=\"cn-ordered-list-field__label\">\n\t\t\t{{ label }}\n\t\t</p>\n\t\t<ol\n\t\t\tv-if=\"rows.length > 0\"\n\t\t\tclass=\"cn-ordered-list-field__rows\"\n\t\t\t:aria-labelledby=\"label ? labelId : null\"\n\t\t\t:aria-label=\"label ? null : listLabel\">\n\t\t\t<li\n\t\t\t\tv-for=\"(row, index) in rows\"\n\t\t\t\t:key=\"row.value\"\n\t\t\t\tclass=\"cn-ordered-list-field__row\"\n\t\t\t\t:data-testid=\"`cn-ordered-list-row-${row.value}`\">\n\t\t\t\t<span class=\"cn-ordered-list-field__position\" aria-hidden=\"true\">{{ index + 1 }}</span>\n\t\t\t\t<span class=\"cn-ordered-list-field__text\">{{ row.label }}</span>\n\t\t\t\t<span class=\"cn-ordered-list-field__actions\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-ordered-list-field__button\"\n\t\t\t\t\t\t:disabled=\"disabled || index === 0\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Move {item} up', { item: row.label })\"\n\t\t\t\t\t\t:data-testid=\"`cn-ordered-list-up-${row.value}`\"\n\t\t\t\t\t\t@click=\"move(index, index - 1)\">\n\t\t\t\t\t\t<ChevronUp :size=\"20\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-ordered-list-field__button\"\n\t\t\t\t\t\t:disabled=\"disabled || index === rows.length - 1\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Move {item} down', { item: row.label })\"\n\t\t\t\t\t\t:data-testid=\"`cn-ordered-list-down-${row.value}`\"\n\t\t\t\t\t\t@click=\"move(index, index + 1)\">\n\t\t\t\t\t\t<ChevronDown :size=\"20\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-ordered-list-field__button\"\n\t\t\t\t\t\t:disabled=\"disabled\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Remove {item}', { item: row.label })\"\n\t\t\t\t\t\t:data-testid=\"`cn-ordered-list-remove-${row.value}`\"\n\t\t\t\t\t\t@click=\"remove(index)\">\n\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t</button>\n\t\t\t\t</span>\n\t\t\t</li>\n\t\t</ol>\n\t\t<p v-else class=\"cn-ordered-list-field__empty\" data-testid=\"cn-ordered-list-empty\">\n\t\t\t{{ emptyText || t('nextcloud-vue', 'Nothing added yet.') }}\n\t\t</p>\n\t\t<div v-if=\"!disabled\" class=\"cn-ordered-list-field__add\">\n\t\t\t<NcSelect\n\t\t\t\t:modelValue=\"null\"\n\t\t\t\t:options=\"available\"\n\t\t\t\tlabel=\"label\"\n\t\t\t\t:inputLabel=\"addLabel || t('nextcloud-vue', 'Add')\"\n\t\t\t\t:placeholder=\"addPlaceholder\"\n\t\t\t\t:loading=\"loading\"\n\t\t\t\t:clearable=\"false\"\n\t\t\t\t:filterable=\"!searchable\"\n\t\t\t\tdata-testid=\"cn-ordered-list-add\"\n\t\t\t\t@search=\"onSearch\"\n\t\t\t\t@update:modelValue=\"add\" />\n\t\t</div>\n\t\t<p class=\"hidden-visually\" aria-live=\"polite\" data-testid=\"cn-ordered-list-live\">\n\t\t\t{{ announcement }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcSelect } from '@nextcloud/vue'\nimport ChevronDown from 'vue-material-design-icons/ChevronDown.vue'\nimport ChevronUp from 'vue-material-design-icons/ChevronUp.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\n\nlet orderedListUid = 0\n\n/**\n * CnOrderedListField - an ordered list a user keeps for themselves: rows in\n * their order with a move up, a move down and a remove button each, and a\n * search-as-you-type picker under it that adds a row (board\n * DqPersoonlijkeInstellingen, \"Zaaktypen in mijn menu\").\n *\n * The app supplies the choices (`options`) and keeps the value: every change\n * emits `update:modelValue` with the new list of values. With `searchable`\n * the picker emits `search` as the user types, so the app can fetch matching\n * choices itself; otherwise it filters `options`. Every move is announced in a\n * polite live region, so reordering works with the keyboard and a screen\n * reader, not by dragging.\n *\n * ```vue\n * <CnOrderedListField\n * v-model=\"menuCaseTypes\"\n * label=\"Case types in my menu\"\n * add-label=\"Add case type\"\n * :options=\"caseTypes\" />\n * ```\n *\n * @spec openspec/changes/ordered-list-field/specs/ordered-list-field/spec.md#requirement-a-user-keeps-an-ordered-list-with-the-keyboard\n */\nexport default {\n\tname: 'CnOrderedListField',\n\n\tcomponents: { ChevronDown, ChevronUp, Close, NcSelect },\n\n\tprops: {\n\t\t/** The values in their order. */\n\t\tmodelValue: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The choices, `{ value, label }` (a plain string is its own label).\n\t\t * A value in `modelValue` without a choice shows the value itself.\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The list's label, also its accessible name. */\n\t\tlabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The picker's label. */\n\t\taddLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Placeholder in the picker. */\n\t\taddPlaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Shown when the list is empty. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Whether the app searches (`search` event) instead of the picker filtering `options`. */\n\t\tsearchable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the app is fetching choices. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** A read-only list: no buttons and no picker. */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: ['update:modelValue', 'search'],\n\n\tdata() {\n\t\torderedListUid += 1\n\t\treturn {\n\t\t\tlabelId: `cn-ordered-list-label-${orderedListUid}`,\n\t\t\tannouncement: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {Array<{value: string, label: string}>} The choices in one shape. */\n\t\tchoices() {\n\t\t\treturn (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.map((option) => (option && typeof option === 'object'\n\t\t\t\t\t? { value: String(option.value ?? option.id ?? ''), label: String(option.label ?? option.title ?? option.value ?? '') }\n\t\t\t\t\t: { value: String(option), label: String(option) }))\n\t\t\t\t.filter((option) => option.value !== '')\n\t\t},\n\n\t\t/** @return {Array<{value: string, label: string}>} The rows, in order. */\n\t\trows() {\n\t\t\treturn (Array.isArray(this.modelValue) ? this.modelValue : []).map((value) => {\n\t\t\t\tconst key = String(value)\n\t\t\t\tconst choice = this.choices.find((option) => option.value === key)\n\t\t\t\treturn { value: key, label: choice ? choice.label : key }\n\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<{value: string, label: string}>} The choices not in the list yet. */\n\t\tavailable() {\n\t\t\tconst taken = new Set(this.rows.map((row) => row.value))\n\t\t\treturn this.choices.filter((option) => !taken.has(option.value))\n\t\t},\n\n\t\t/** @return {string} The list's accessible name without a label. */\n\t\tlistLabel() {\n\t\t\treturn t('nextcloud-vue', 'Ordered list')\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Emit the new order.\n\t\t *\n\t\t * @param {Array<string>} values The values.\n\t\t */\n\t\temitValue(values) {\n\t\t\t/**\n\t\t\t * @event update:modelValue The list changed. Payload: the values in their order.\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', values)\n\t\t},\n\n\t\t/**\n\t\t * Move a row to another place and keep focus on the pressed button\n\t\t * where it still makes sense.\n\t\t *\n\t\t * @param {number} from Its index.\n\t\t * @param {number} to The index it moves to.\n\t\t */\n\t\tmove(from, to) {\n\t\t\tif (to < 0 || to >= this.rows.length) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst values = this.rows.map((row) => row.value)\n\t\t\tconst [value] = values.splice(from, 1)\n\t\t\tvalues.splice(to, 0, value)\n\t\t\tconst row = this.rows[from]\n\t\t\tthis.emitValue(values)\n\t\t\tthis.announcement = t('nextcloud-vue', '{item} moved to position {position} of {total}', { item: row.label, position: to + 1, total: values.length })\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst direction = to < from ? 'up' : 'down'\n\t\t\t\tconst atEdge = (direction === 'up' && to === 0) || (direction === 'down' && to === values.length - 1)\n\t\t\t\tconst target = this.$el.querySelector(`[data-testid=\"cn-ordered-list-${atEdge ? (direction === 'up' ? 'down' : 'up') : direction}-${row.value}\"]`)\n\t\t\t\tif (target && !target.disabled) {\n\t\t\t\t\ttarget.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Remove a row.\n\t\t *\n\t\t * @param {number} index Its index.\n\t\t */\n\t\tremove(index) {\n\t\t\tconst row = this.rows[index]\n\t\t\tthis.emitValue(this.rows.filter((_, i) => i !== index).map((r) => r.value))\n\t\t\tthis.announcement = t('nextcloud-vue', '{item} removed', { item: row.label })\n\t\t},\n\n\t\t/**\n\t\t * Add the picked choice at the end.\n\t\t *\n\t\t * @param {{value: string, label: string}|null} option The choice.\n\t\t */\n\t\tadd(option) {\n\t\t\tif (!option) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.emitValue([...this.rows.map((row) => row.value), option.value])\n\t\t\tthis.announcement = t('nextcloud-vue', '{item} added', { item: option.label })\n\t\t},\n\n\t\t/**\n\t\t * Pass the typed text on when the app searches.\n\t\t *\n\t\t * @param {string} query The text.\n\t\t */\n\t\tonSearch(query) {\n\t\t\tif (this.searchable) {\n\t\t\t\t/**\n\t\t\t\t * @event search The user typed in the picker (with `searchable`). Payload: the text.\n\t\t\t\t */\n\t\t\t\tthis.$emit('search', query)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-ordered-list-field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n}\n\n.cn-ordered-list-field__label {\n\tmargin: 0;\n\tfont-weight: 600;\n}\n\n.cn-ordered-list-field__rows {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-element, 8px);\n\tlist-style: none;\n}\n\n.cn-ordered-list-field__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-height: 44px;\n\tpadding: 2px 6px 2px 12px;\n}\n\n.cn-ordered-list-field__row + .cn-ordered-list-field__row {\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-ordered-list-field__position {\n\tflex: none;\n\tmin-width: 18px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-ordered-list-field__text {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\toverflow-wrap: anywhere;\n}\n\n.cn-ordered-list-field__actions {\n\tdisplay: flex;\n\tflex: none;\n\tgap: 2px;\n}\n\n.cn-ordered-list-field__button {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\tmin-width: 40px;\n\theight: 40px;\n\tmin-height: 40px;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: var(--border-radius-element, 8px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n}\n\n.cn-ordered-list-field__button:hover:not(:disabled) {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-ordered-list-field__button:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 1px;\n}\n\n.cn-ordered-list-field__button:disabled {\n\topacity: 0.4;\n\tcursor: default;\n}\n\n.cn-ordered-list-field__empty {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_Fragment","_renderList","_createElementVNode","_toDisplayString","_createVNode"],"mappings":"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"}
|
|
@@ -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-
|
|
5
|
+
script.__scopeId = "data-v-e537e50a";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { translate } from '@nextcloud/l10n';
|
|
2
|
+
import { generateUrl } from '@nextcloud/router';
|
|
2
3
|
import { NcButton } from '@nextcloud/vue';
|
|
3
|
-
import { markRaw } from 'vue';
|
|
4
|
+
import { ref, markRaw, cloneVNode } from 'vue';
|
|
4
5
|
import __sfc_main from '../CnDialog/CnDialog.vue.js';
|
|
6
|
+
import __sfc_main$1 from '../CnIcon/CnIcon.vue.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* The key NcAppSettingsSection injects to register itself with its dialog
|
|
@@ -9,20 +11,26 @@ import __sfc_main from '../CnDialog/CnDialog.vue.js';
|
|
|
9
11
|
*/
|
|
10
12
|
const REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration');
|
|
11
13
|
|
|
14
|
+
/** The key NcAppSettingsSection injects for the legacy design flag. */
|
|
15
|
+
const LEGACY_DESIGN_KEY = Symbol.for('NcAppSettingsDialog:legacy');
|
|
16
|
+
|
|
12
17
|
/** Renders a section's icon vnodes as they were registered. */
|
|
13
18
|
const CnVNodes = {
|
|
14
19
|
name: 'CnVNodes',
|
|
15
20
|
props: { vnodes: { type: [Array, Object], default: null } },
|
|
16
21
|
render() {
|
|
17
|
-
|
|
22
|
+
// A section's icon is drawn twice (navigation and heading), so each draw gets its own copy.
|
|
23
|
+
const list = Array.isArray(this.vnodes) ? this.vnodes : [this.vnodes];
|
|
24
|
+
return list.map((vnode) => (vnode && typeof vnode === 'object' ? cloneVNode(vnode) : vnode))
|
|
18
25
|
},
|
|
19
26
|
};
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* CnUserSettingsDialog - the user settings dialog in the board look (board
|
|
23
30
|
* PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the
|
|
24
|
-
* sections with their icons, the
|
|
25
|
-
* note that changes save straight away and
|
|
31
|
+
* sections with their icons, the current section (the others stay mounted
|
|
32
|
+
* but hidden), and a footer with a note that changes save straight away and
|
|
33
|
+
* a Close button.
|
|
26
34
|
*
|
|
27
35
|
* The sections stay the app's: any `NcAppSettingsSection` in the default slot
|
|
28
36
|
* registers itself with this dialog exactly as it does with
|
|
@@ -36,7 +44,7 @@ const CnVNodes = {
|
|
|
36
44
|
var script = {
|
|
37
45
|
name: 'CnUserSettingsDialog',
|
|
38
46
|
|
|
39
|
-
components: { CnDialog: __sfc_main, CnVNodes, NcButton },
|
|
47
|
+
components: { CnDialog: __sfc_main, CnIcon: __sfc_main$1, CnVNodes, NcButton },
|
|
40
48
|
|
|
41
49
|
provide() {
|
|
42
50
|
return {
|
|
@@ -44,6 +52,9 @@ var script = {
|
|
|
44
52
|
registerSection: this.registerSection,
|
|
45
53
|
unregisterSection: this.unregisterSection,
|
|
46
54
|
},
|
|
55
|
+
|
|
56
|
+
// NcAppSettingsSection also asks its dialog whether to use the legacy design.
|
|
57
|
+
[LEGACY_DESIGN_KEY]: ref(false),
|
|
47
58
|
}
|
|
48
59
|
},
|
|
49
60
|
|
|
@@ -56,8 +67,15 @@ var script = {
|
|
|
56
67
|
eyebrow: { type: String, default: () => translate('nextcloud-vue', 'Your account') },
|
|
57
68
|
/** Sentence under the title. */
|
|
58
69
|
subtitle: { type: String, default: () => translate('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },
|
|
59
|
-
/** The app's name, after the footer note. */
|
|
70
|
+
/** The app's name, after the footer note and as the navigation entry holding its sections. */
|
|
60
71
|
appName: { type: String, default: '' },
|
|
72
|
+
/**
|
|
73
|
+
* Nextcloud's own personal settings, listed first in the navigation as
|
|
74
|
+
* links: `[{ label, section? | href?, icon? }]`. `section` is a
|
|
75
|
+
* personal settings section id (`/settings/user/<section>`). With any,
|
|
76
|
+
* the app's sections sit under one entry named after the app.
|
|
77
|
+
*/
|
|
78
|
+
nextcloudLinks: { type: Array, default: () => [] },
|
|
61
79
|
},
|
|
62
80
|
|
|
63
81
|
emits: ['update:open'],
|
|
@@ -66,11 +84,30 @@ var script = {
|
|
|
66
84
|
return {
|
|
67
85
|
sections: [],
|
|
68
86
|
selected: '',
|
|
87
|
+
/** Ids of the sections whose heading is in the page, for the heading icons. */
|
|
88
|
+
headingTargets: [],
|
|
69
89
|
picked: false,
|
|
70
90
|
}
|
|
71
91
|
},
|
|
72
92
|
|
|
73
93
|
computed: {
|
|
94
|
+
/**
|
|
95
|
+
* The Nextcloud links with their URL.
|
|
96
|
+
*
|
|
97
|
+
* @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
|
|
98
|
+
* @return {Array<{key: string, label: string, href: string, icon: string}>}
|
|
99
|
+
*/
|
|
100
|
+
ncLinks() {
|
|
101
|
+
return (Array.isArray(this.nextcloudLinks) ? this.nextcloudLinks : [])
|
|
102
|
+
.filter((link) => link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '' && (link.href || link.section))
|
|
103
|
+
.map((link, index) => ({
|
|
104
|
+
key: String(link.section || link.href || index),
|
|
105
|
+
label: link.label,
|
|
106
|
+
href: link.href || generateUrl('/settings/user/{section}', { section: String(link.section) }),
|
|
107
|
+
icon: typeof link.icon === 'string' ? link.icon : '',
|
|
108
|
+
}))
|
|
109
|
+
},
|
|
110
|
+
|
|
74
111
|
/** @return {object} The board's eyebrow: muted, in sentence case. */
|
|
75
112
|
eyebrowStyle() {
|
|
76
113
|
return { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }
|
|
@@ -93,6 +130,21 @@ var script = {
|
|
|
93
130
|
},
|
|
94
131
|
},
|
|
95
132
|
|
|
133
|
+
watch: {
|
|
134
|
+
sections() {
|
|
135
|
+
this.$nextTick(() => {
|
|
136
|
+
this.headingTargets = typeof document === 'undefined'
|
|
137
|
+
? []
|
|
138
|
+
: this.sections.filter((section) => section.icon && document.getElementById(`settings-section_${section.id}--label`)).map((section) => section.id);
|
|
139
|
+
this.showSelected();
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
selected() {
|
|
144
|
+
this.$nextTick(() => this.showSelected());
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
|
|
96
148
|
methods: {
|
|
97
149
|
/**
|
|
98
150
|
* Add a section to the navigation, kept in `order`; a section
|
|
@@ -133,9 +185,31 @@ var script = {
|
|
|
133
185
|
selectSection(id) {
|
|
134
186
|
this.picked = true;
|
|
135
187
|
this.selected = id;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
188
|
+
this.$nextTick(() => {
|
|
189
|
+
this.showSelected();
|
|
190
|
+
const heading = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}--label`) : null;
|
|
191
|
+
if (heading) {
|
|
192
|
+
heading.setAttribute('tabindex', '-1');
|
|
193
|
+
heading.focus();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Show the current section and hide the others. They stay mounted,
|
|
200
|
+
* so what a reader typed in one survives a switch.
|
|
201
|
+
*
|
|
202
|
+
* @spec openspec/changes/user-settings-one-section/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-dialog-shows-one-section-at-a-time
|
|
203
|
+
*/
|
|
204
|
+
showSelected() {
|
|
205
|
+
if (typeof document === 'undefined') {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
for (const section of this.sections) {
|
|
209
|
+
const el = document.getElementById(`settings-section_${section.id}`);
|
|
210
|
+
if (el) {
|
|
211
|
+
el.hidden = section.id !== this.selected;
|
|
212
|
+
}
|
|
139
213
|
}
|
|
140
214
|
},
|
|
141
215
|
|
|
@@ -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\twidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:open=\"open\"\n\t\t:style=\"eyebrowStyle\"\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\tv-if=\"sections.length > 0\"\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<ul>\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 v-if=\"section.icon\" class=\"cn-user-settings-dialog__nav-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnVNodes :vnodes=\"section.icon\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span>{{ 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</div>\n\t\t</div>\n\t\t<template #actions>\n\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t{{ footerNote }}\n\t\t\t</p>\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-user-settings-close\" @click=\"close\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { markRaw } from '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/** Renders a section's icon vnodes as they were registered. */\nconst CnVNodes = {\n\tname: 'CnVNodes',\n\tprops: { vnodes: { type: [Array, Object], default: null } },\n\trender() {\n\t\treturn this.vnodes\n\t},\n}\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look (board\n * PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the\n * sections with their icons, the sections themselves, and a footer with a\n * note that changes save straight away and a Close button.\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, order and `#icon` slot. CnAppRoot\n * renders this 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 *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { CnDialog, CnVNodes, 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\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/** Context line above the title. */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/** Sentence under the title. */\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. */\n\t\tappName: { type: String, 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/** @return {object} The board's eyebrow: muted, in sentence case. */\n\t\teyebrowStyle() {\n\t\t\treturn { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }\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.\n\t\t * @param {Array} [icon] The section's icon vnodes.\n\t\t */\n\t\tregisterSection(id, name, order, icon) {\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, icon: icon ? markRaw(icon) : null }].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 and scroll it into view.\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\tconst target = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null\n\t\t\tif (target && typeof target.scrollIntoView === 'function') {\n\t\t\t\ttarget.scrollIntoView({ behavior: 'smooth', block: 'start' })\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.cn-user-settings-dialog__body {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 0 24px;\n}\n\n.cn-user-settings-dialog__nav {\n\tflex: 0 0 200px;\n\twidth: 200px;\n\tpadding-top: 18px;\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.cn-user-settings-dialog__nav-link {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-height: 40px;\n\tpadding: 0 12px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: var(--color-background-hover);\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.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n\tfont-weight: 600;\n}\n\n.cn-user-settings-dialog__nav-icon {\n\tdisplay: inline-flex;\n\tflex: none;\n\twidth: 18px;\n\theight: 18px;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 380px;\n\tflex-direction: column;\n\tmin-width: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\tmargin: 0;\n\tpadding: 18px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 12px;\n\tfont-size: 17px;\n\tfont-weight: 700;\n}\n\n.cn-user-settings-dialog__note {\n\tflex: 1 1 auto;\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\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\twidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:open=\"open\"\n\t\t:style=\"eyebrowStyle\"\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\tv-if=\"sections.length > 0\"\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<!-- `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 v-if=\"link.icon\" class=\"cn-user-settings-dialog__nav-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnIcon :name=\"link.icon\" :size=\"18\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span>{{ 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 v-if=\"section.icon\" class=\"cn-user-settings-dialog__nav-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnVNodes :vnodes=\"section.icon\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span>{{ 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<!-- A section's `#icon` also stands before its heading. -->\n\t\t\t\t<template v-for=\"section in sections\" :key=\"`heading-icon-${section.id}-${section.name}`\">\n\t\t\t\t\t<Teleport v-if=\"section.icon && headingTargets.includes(section.id)\" :to=\"`#settings-section_${section.id}--label`\">\n\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__heading-icon\" aria-hidden=\"true\" :data-testid=\"`cn-user-settings-heading-icon-${section.id}`\">\n\t\t\t\t\t\t\t<CnVNodes :vnodes=\"section.icon\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</Teleport>\n\t\t\t\t</template>\n\t\t\t</div>\n\t\t</div>\n\t\t<template #actions>\n\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t{{ footerNote }}\n\t\t\t</p>\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-user-settings-close\" @click=\"close\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\t\t</template>\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 { cloneVNode, markRaw, ref } from 'vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnIcon from '../CnIcon/CnIcon.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/** Renders a section's icon vnodes as they were registered. */\nconst CnVNodes = {\n\tname: 'CnVNodes',\n\tprops: { vnodes: { type: [Array, Object], default: null } },\n\trender() {\n\t\t// A section's icon is drawn twice (navigation and heading), so each draw gets its own copy.\n\t\tconst list = Array.isArray(this.vnodes) ? this.vnodes : [this.vnodes]\n\t\treturn list.map((vnode) => (vnode && typeof vnode === 'object' ? cloneVNode(vnode) : vnode))\n\t},\n}\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look (board\n * PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the\n * sections with their icons, the current section (the others stay mounted\n * but hidden), and a footer with a note that changes save straight away and\n * a Close button.\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, order and `#icon` slot. CnAppRoot\n * renders this 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 *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { CnDialog, CnIcon, CnVNodes, 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/** Context line above the title. */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/** Sentence under the title. */\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\t/** Ids of the sections whose heading is in the page, for the heading icons. */\n\t\t\theadingTargets: [],\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 eyebrow: muted, in sentence case. */\n\t\teyebrowStyle() {\n\t\t\treturn { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }\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\twatch: {\n\t\tsections() {\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.headingTargets = typeof document === 'undefined'\n\t\t\t\t\t? []\n\t\t\t\t\t: this.sections.filter((section) => section.icon && document.getElementById(`settings-section_${section.id}--label`)).map((section) => section.id)\n\t\t\t\tthis.showSelected()\n\t\t\t})\n\t\t},\n\n\t\tselected() {\n\t\t\tthis.$nextTick(() => this.showSelected())\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.\n\t\t * @param {Array} [icon] The section's icon vnodes.\n\t\t */\n\t\tregisterSection(id, name, order, icon) {\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, icon: icon ? markRaw(icon) : null }].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 and scroll it into view.\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\tthis.showSelected()\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()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Show the current section and hide the others. They stay mounted,\n\t\t * so what a reader typed in one survives a switch.\n\t\t *\n\t\t * @spec openspec/changes/user-settings-one-section/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-dialog-shows-one-section-at-a-time\n\t\t */\n\t\tshowSelected() {\n\t\t\tif (typeof document === 'undefined') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor (const section of this.sections) {\n\t\t\t\tconst el = document.getElementById(`settings-section_${section.id}`)\n\t\t\t\tif (el) {\n\t\t\t\t\tel.hidden = section.id !== this.selected\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.cn-user-settings-dialog__body {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 0 24px;\n}\n\n.cn-user-settings-dialog__nav {\n\tflex: 0 0 200px;\n\twidth: 200px;\n\tpadding-top: 18px;\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.cn-user-settings-dialog__nav-link {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-height: 40px;\n\tpadding: 0 12px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: var(--color-background-hover);\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.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n\tfont-weight: 600;\n}\n\n.cn-user-settings-dialog__nav-icon {\n\tdisplay: inline-flex;\n\tflex: none;\n\twidth: 18px;\n\theight: 18px;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 380px;\n\tflex-direction: column;\n\tmin-width: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\tmargin: 0;\n\tpadding: 18px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 12px;\n\tfont-size: 17px;\n\tfont-weight: 700;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n}\n\n.cn-user-settings-dialog__note {\n\tflex: 1 1 auto;\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-user-settings-dialog__nc-links {\n\tmargin-bottom: 2px;\n}\n\n.cn-user-settings-dialog__group {\n\tmargin: 10px 0 2px;\n\tpadding: 0 12px;\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: 12px;\n}\n\n.cn-user-settings-dialog__heading-icon {\n\tdisplay: inline-flex;\n\torder: -1;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* One section at a time: the others are mounted but hidden. */\n.cn-user-settings-dialog__sections :deep(.app-settings-section[hidden]) {\n\tdisplay: none;\n}\n</style>\n"],"names":["CnDialog","CnIcon","t"],"mappings":"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"}
|
|
@@ -1,20 +1,38 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createBlock, normalizeStyle, withCtx, createElementVNode, toDisplayString, createVNode, createTextVNode, createElementBlock, Fragment, renderList, normalizeClass, withModifiers,
|
|
1
|
+
import { resolveComponent, openBlock, createBlock, normalizeStyle, withCtx, createElementVNode, toDisplayString, createVNode, createTextVNode, createElementBlock, Fragment, renderList, createCommentVNode, normalizeClass, withModifiers, renderSlot, Teleport } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = { class: "cn-user-settings-dialog__body" };
|
|
4
4
|
const _hoisted_2 = ["aria-label"];
|
|
5
|
-
const _hoisted_3 =
|
|
6
|
-
|
|
5
|
+
const _hoisted_3 = {
|
|
6
|
+
key: 0,
|
|
7
|
+
class: "cn-user-settings-dialog__nc-links",
|
|
8
|
+
"data-testid": "cn-user-settings-nc-links"
|
|
9
|
+
};
|
|
10
|
+
const _hoisted_4 = ["href"];
|
|
11
|
+
const _hoisted_5 = {
|
|
7
12
|
key: 0,
|
|
8
13
|
class: "cn-user-settings-dialog__nav-icon",
|
|
9
14
|
"aria-hidden": "true"
|
|
10
15
|
};
|
|
11
|
-
const _hoisted_5 = { class: "cn-user-settings-dialog__sections" };
|
|
12
16
|
const _hoisted_6 = {
|
|
17
|
+
key: 1,
|
|
18
|
+
class: "cn-user-settings-dialog__group",
|
|
19
|
+
"data-testid": "cn-user-settings-app-group"
|
|
20
|
+
};
|
|
21
|
+
const _hoisted_7 = ["href", "aria-current", "data-testid", "onClick"];
|
|
22
|
+
const _hoisted_8 = {
|
|
23
|
+
key: 0,
|
|
24
|
+
class: "cn-user-settings-dialog__nav-icon",
|
|
25
|
+
"aria-hidden": "true"
|
|
26
|
+
};
|
|
27
|
+
const _hoisted_9 = { class: "cn-user-settings-dialog__sections" };
|
|
28
|
+
const _hoisted_10 = ["data-testid"];
|
|
29
|
+
const _hoisted_11 = {
|
|
13
30
|
class: "cn-user-settings-dialog__note",
|
|
14
31
|
"data-testid": "cn-user-settings-note"
|
|
15
32
|
};
|
|
16
33
|
|
|
17
34
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
35
|
+
const _component_CnIcon = resolveComponent("CnIcon");
|
|
18
36
|
const _component_CnVNodes = resolveComponent("CnVNodes");
|
|
19
37
|
const _component_NcButton = resolveComponent("NcButton");
|
|
20
38
|
const _component_CnDialog = resolveComponent("CnDialog");
|
|
@@ -33,7 +51,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
33
51
|
onClosing: $options.close
|
|
34
52
|
}, {
|
|
35
53
|
actions: withCtx(() => [
|
|
36
|
-
createElementVNode("p",
|
|
54
|
+
createElementVNode("p", _hoisted_11, toDisplayString($options.footerNote), 1 /* TEXT */),
|
|
37
55
|
createVNode(_component_NcButton, {
|
|
38
56
|
variant: "secondary",
|
|
39
57
|
"data-testid": "cn-user-settings-close",
|
|
@@ -54,7 +72,36 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
54
72
|
"aria-label": $options.navLabel,
|
|
55
73
|
"data-testid": "cn-user-settings-nav"
|
|
56
74
|
}, [
|
|
57
|
-
|
|
75
|
+
($options.ncLinks.length > 0)
|
|
76
|
+
? (openBlock(), createElementBlock("ul", _hoisted_3, [
|
|
77
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.ncLinks, (link) => {
|
|
78
|
+
return (openBlock(), createElementBlock("li", {
|
|
79
|
+
key: link.key
|
|
80
|
+
}, [
|
|
81
|
+
createElementVNode("a", {
|
|
82
|
+
href: link.href,
|
|
83
|
+
class: "cn-user-settings-dialog__nav-link"
|
|
84
|
+
}, [
|
|
85
|
+
(link.icon)
|
|
86
|
+
? (openBlock(), createElementBlock("span", _hoisted_5, [
|
|
87
|
+
createVNode(_component_CnIcon, {
|
|
88
|
+
name: link.icon,
|
|
89
|
+
size: 18
|
|
90
|
+
}, null, 8 /* PROPS */, ["name"])
|
|
91
|
+
]))
|
|
92
|
+
: createCommentVNode("v-if", true),
|
|
93
|
+
createElementVNode("span", null, toDisplayString(link.label), 1 /* TEXT */)
|
|
94
|
+
], 8 /* PROPS */, _hoisted_4)
|
|
95
|
+
]))
|
|
96
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
97
|
+
]))
|
|
98
|
+
: createCommentVNode("v-if", true),
|
|
99
|
+
($options.ncLinks.length > 0 && $props.appName)
|
|
100
|
+
? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString($props.appName), 1 /* TEXT */))
|
|
101
|
+
: createCommentVNode("v-if", true),
|
|
102
|
+
createElementVNode("ul", {
|
|
103
|
+
class: normalizeClass({ 'cn-user-settings-dialog__group-sections': $options.ncLinks.length > 0 })
|
|
104
|
+
}, [
|
|
58
105
|
(openBlock(true), createElementBlock(Fragment, null, renderList($data.sections, (section) => {
|
|
59
106
|
return (openBlock(), createElementBlock("li", {
|
|
60
107
|
key: section.id
|
|
@@ -67,21 +114,43 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
67
114
|
onClick: withModifiers($event => ($options.selectSection(section.id)), ["prevent"])
|
|
68
115
|
}, [
|
|
69
116
|
(section.icon)
|
|
70
|
-
? (openBlock(), createElementBlock("span",
|
|
117
|
+
? (openBlock(), createElementBlock("span", _hoisted_8, [
|
|
71
118
|
createVNode(_component_CnVNodes, {
|
|
72
119
|
vnodes: section.icon
|
|
73
120
|
}, null, 8 /* PROPS */, ["vnodes"])
|
|
74
121
|
]))
|
|
75
122
|
: createCommentVNode("v-if", true),
|
|
76
123
|
createElementVNode("span", null, toDisplayString(section.name), 1 /* TEXT */)
|
|
77
|
-
], 10 /* CLASS, PROPS */,
|
|
124
|
+
], 10 /* CLASS, PROPS */, _hoisted_7)
|
|
78
125
|
]))
|
|
79
126
|
}), 128 /* KEYED_FRAGMENT */))
|
|
80
|
-
])
|
|
127
|
+
], 2 /* CLASS */)
|
|
81
128
|
], 8 /* PROPS */, _hoisted_2))
|
|
82
129
|
: createCommentVNode("v-if", true),
|
|
83
|
-
createElementVNode("div",
|
|
84
|
-
renderSlot(_ctx.$slots, "default")
|
|
130
|
+
createElementVNode("div", _hoisted_9, [
|
|
131
|
+
renderSlot(_ctx.$slots, "default"),
|
|
132
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($data.sections, (section) => {
|
|
133
|
+
return (openBlock(), createElementBlock(Fragment, {
|
|
134
|
+
key: `heading-icon-${section.id}-${section.name}`
|
|
135
|
+
}, [
|
|
136
|
+
(section.icon && $data.headingTargets.includes(section.id))
|
|
137
|
+
? (openBlock(), createBlock(Teleport, {
|
|
138
|
+
key: 0,
|
|
139
|
+
to: `#settings-section_${section.id}--label`
|
|
140
|
+
}, [
|
|
141
|
+
createElementVNode("span", {
|
|
142
|
+
class: "cn-user-settings-dialog__heading-icon",
|
|
143
|
+
"aria-hidden": "true",
|
|
144
|
+
"data-testid": `cn-user-settings-heading-icon-${section.id}`
|
|
145
|
+
}, [
|
|
146
|
+
createVNode(_component_CnVNodes, {
|
|
147
|
+
vnodes: section.icon
|
|
148
|
+
}, null, 8 /* PROPS */, ["vnodes"])
|
|
149
|
+
], 8 /* PROPS */, _hoisted_10)
|
|
150
|
+
], 8 /* PROPS */, ["to"]))
|
|
151
|
+
: createCommentVNode("v-if", true)
|
|
152
|
+
], 64 /* STABLE_FRAGMENT */))
|
|
153
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
85
154
|
])
|
|
86
155
|
])
|
|
87
156
|
]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnUserSettingsDialog.vue3.js","sources":["../../../../src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue?vue&type=template&id=8ebfd3c8&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\twidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:open=\"open\"\n\t\t:style=\"eyebrowStyle\"\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\tv-if=\"sections.length > 0\"\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<ul>\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 v-if=\"section.icon\" class=\"cn-user-settings-dialog__nav-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnVNodes :vnodes=\"section.icon\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span>{{ 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</div>\n\t\t</div>\n\t\t<template #actions>\n\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t{{ footerNote }}\n\t\t\t</p>\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-user-settings-close\" @click=\"close\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { markRaw } from '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/** Renders a section's icon vnodes as they were registered. */\nconst CnVNodes = {\n\tname: 'CnVNodes',\n\tprops: { vnodes: { type: [Array, Object], default: null } },\n\trender() {\n\t\treturn this.vnodes\n\t},\n}\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look (board\n * PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the\n * sections with their icons, the sections themselves, and a footer with a\n * note that changes save straight away and a Close button.\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, order and `#icon` slot. CnAppRoot\n * renders this 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 *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { CnDialog, CnVNodes, 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\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/** Context line above the title. */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/** Sentence under the title. */\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. */\n\t\tappName: { type: String, 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/** @return {object} The board's eyebrow: muted, in sentence case. */\n\t\teyebrowStyle() {\n\t\t\treturn { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }\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.\n\t\t * @param {Array} [icon] The section's icon vnodes.\n\t\t */\n\t\tregisterSection(id, name, order, icon) {\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, icon: icon ? markRaw(icon) : null }].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 and scroll it into view.\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\tconst target = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null\n\t\t\tif (target && typeof target.scrollIntoView === 'function') {\n\t\t\t\ttarget.scrollIntoView({ behavior: 'smooth', block: 'start' })\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.cn-user-settings-dialog__body {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 0 24px;\n}\n\n.cn-user-settings-dialog__nav {\n\tflex: 0 0 200px;\n\twidth: 200px;\n\tpadding-top: 18px;\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.cn-user-settings-dialog__nav-link {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-height: 40px;\n\tpadding: 0 12px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: var(--color-background-hover);\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.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n\tfont-weight: 600;\n}\n\n.cn-user-settings-dialog__nav-icon {\n\tdisplay: inline-flex;\n\tflex: none;\n\twidth: 18px;\n\theight: 18px;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 380px;\n\tflex-direction: column;\n\tmin-width: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\tmargin: 0;\n\tpadding: 18px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 12px;\n\tfont-size: 17px;\n\tfont-weight: 700;\n}\n\n.cn-user-settings-dialog__note {\n\tflex: 1 1 auto;\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_toDisplayString","_createVNode","_createElementVNode","_createElementBlock","_Fragment","_renderList","_withModifiers","_openBlock","_renderSlot"],"mappings":";;AAqBO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,+BAAY,GAAA;;;;;AAehB,EAAA,KAAA,EAAA,mCAAA;AAAA,EAAA,aAAA,EAAA,MAAA;;AAQF,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,mCAAA,GAAA;;AAMF,EAAA,KAAM,EAAC,+BAAO;AAAA,EAAA,aAAA,EAAA,uBAAA;;;;;;;;;AAxCjB,IAAA,KAAK,EAAC,yBAAW;AAChB,IAAA,IAAI,EAAE,MAAK,CAAA,KAAA;AACZ,IAAA,IAAI,EAAC,OAAM;AACX,IAAA,KAAK,EAAC,QAAQ;AACb,IAAA,OAAO,EAAE,MAAO,CAAA,OAAA;AAChB,IAAA,QAAQ,EAAE,MAAQ,CAAA,QAAA;AAClB,IAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,IAAA,KAAK,iBAAE,QAAY,CAAA,YAAA,CAAA;AACnB,IAAA,aAAG,EAAA,yBAAA;mBACQ,EAAE,QAAY,CAAA,YAAA;AACxB,IAAA,SAAM,EAAE,QAAK,CAAA,KAAA;;AA6BJ,IAAA,OAAO,gBAGb;wBAAA,CAAA,GAAA,YAAA,EAAAA,eAAA,CADA,QAAM,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;MAEVC;AAAQ,QAAA,OAAA,EAAA,WAAA;AAAA,QAAA,aAAA,EAAA,wBAAA;AAAA,QAAA,OAAA,EAAA,QAAA,CAAA,KAAA;;;;;;;;qBAhCP,MA2BE;AA3BF,MAAAC,kBAAA,CA2BE,OA3BF,UA2BE,EAAA;AAzBC,QAAA,CAAA,KAAA,CAAA,QAAA,CAAA,MAAA,GAAA,CAAA;0BADHC,kBAqBI,CAAA,KAAA,EAAA;;AAnBH,cAAA,KAAI,EAAC,8BAA8B;0BACjC,EAAA,QAAA,CAAA,QAAA;AACA,cAAA,aAAQ,EAAC,sBAAsB;;cAC/BD,kBAeC,CAAA,IAAA,EAAA,IAAA,EAAA;kCAdDC,kBAaG,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CAbiB,KAAQ,CAAA,QAAA,EAAA,CAAlB,OAAM,KAAA;uCAAhBF,kBAaG,CAAA,IAAA,EAAA;oBAb4B,GAAG,EAAE,OAAO,CAAC,EAAE;;sCAY3C,CAAA,GAAA,EAAA;sBAVF,IAAI,EAAA,CAAA,kBAAA,EAAuB,OAAO,CAAC,EAAE,CAAA,CAAA;AACtC,sBAAA,KAAK,kBAAC,mCAAmC,EAAA,EAAA,2CAAA,EACc,OAAO,CAAC,EAAE,KAAK,KAAQ,CAAA,QAAA,EAAA,CAAA,CAAA;AAC7E,sBAAA,cAAY,EAAE,OAAO,CAAC,EAAE,KAAK,KAAA,CAAA,QAAK,YAAA,SAAA;sBAClC,aAAK,EAAA,CAAA,qBAAA,EAAA,OAAA,CAAA,EAAA,CAAA,CAAA;AACL,sBAAA,OAAK,EAAUG,aAAA,CAAA,MAAA,KAAA,QAAA,CAAA,aAAa,CAAC,OAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,SAAA,CAAA,CAAA;;AAC5B,sBAAA,CAAA,OAAA,CAAA,IAAA;AAAF,2BAAAC,SAAA,EAAA,EAAAJ,kBAAA,CAEC,QAFD,UAEC,EAAA;4BADAF,WAAC,CAAA,mBAAA,EAAA;8BAAA,MAAA,EAAA,OAAA,CAAA,IAAA;;;;sBAEFC,kBAAC,CAAA,MAAA,EAAA,IAAA,EAAAF,eAAA,CAAA,OAAA,CAAA,IAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;AAKL,QAAAE,kBAAA,CAGM,OAHN,UAGM,EAAA;UADLM,UAAQ,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,CAAA;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnUserSettingsDialog.vue3.js","sources":["../../../../src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue?vue&type=template&id=e537e50a&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\twidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:open=\"open\"\n\t\t:style=\"eyebrowStyle\"\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\tv-if=\"sections.length > 0\"\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<!-- `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 v-if=\"link.icon\" class=\"cn-user-settings-dialog__nav-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnIcon :name=\"link.icon\" :size=\"18\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span>{{ 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 v-if=\"section.icon\" class=\"cn-user-settings-dialog__nav-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnVNodes :vnodes=\"section.icon\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span>{{ 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<!-- A section's `#icon` also stands before its heading. -->\n\t\t\t\t<template v-for=\"section in sections\" :key=\"`heading-icon-${section.id}-${section.name}`\">\n\t\t\t\t\t<Teleport v-if=\"section.icon && headingTargets.includes(section.id)\" :to=\"`#settings-section_${section.id}--label`\">\n\t\t\t\t\t\t<span class=\"cn-user-settings-dialog__heading-icon\" aria-hidden=\"true\" :data-testid=\"`cn-user-settings-heading-icon-${section.id}`\">\n\t\t\t\t\t\t\t<CnVNodes :vnodes=\"section.icon\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</Teleport>\n\t\t\t\t</template>\n\t\t\t</div>\n\t\t</div>\n\t\t<template #actions>\n\t\t\t<p class=\"cn-user-settings-dialog__note\" data-testid=\"cn-user-settings-note\">\n\t\t\t\t{{ footerNote }}\n\t\t\t</p>\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-user-settings-close\" @click=\"close\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\t\t</template>\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 { cloneVNode, markRaw, ref } from 'vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnIcon from '../CnIcon/CnIcon.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/** Renders a section's icon vnodes as they were registered. */\nconst CnVNodes = {\n\tname: 'CnVNodes',\n\tprops: { vnodes: { type: [Array, Object], default: null } },\n\trender() {\n\t\t// A section's icon is drawn twice (navigation and heading), so each draw gets its own copy.\n\t\tconst list = Array.isArray(this.vnodes) ? this.vnodes : [this.vnodes]\n\t\treturn list.map((vnode) => (vnode && typeof vnode === 'object' ? cloneVNode(vnode) : vnode))\n\t},\n}\n\n/**\n * CnUserSettingsDialog - the user settings dialog in the board look (board\n * PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the\n * sections with their icons, the current section (the others stay mounted\n * but hidden), and a footer with a note that changes save straight away and\n * a Close button.\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, order and `#icon` slot. CnAppRoot\n * renders this 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 *\n * @event update:open Emitted with `false` when the dialog closes.\n */\nexport default {\n\tname: 'CnUserSettingsDialog',\n\n\tcomponents: { CnDialog, CnIcon, CnVNodes, 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/** Context line above the title. */\n\t\teyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },\n\t\t/** Sentence under the title. */\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\t/** Ids of the sections whose heading is in the page, for the heading icons. */\n\t\t\theadingTargets: [],\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 eyebrow: muted, in sentence case. */\n\t\teyebrowStyle() {\n\t\t\treturn { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }\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\twatch: {\n\t\tsections() {\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.headingTargets = typeof document === 'undefined'\n\t\t\t\t\t? []\n\t\t\t\t\t: this.sections.filter((section) => section.icon && document.getElementById(`settings-section_${section.id}--label`)).map((section) => section.id)\n\t\t\t\tthis.showSelected()\n\t\t\t})\n\t\t},\n\n\t\tselected() {\n\t\t\tthis.$nextTick(() => this.showSelected())\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.\n\t\t * @param {Array} [icon] The section's icon vnodes.\n\t\t */\n\t\tregisterSection(id, name, order, icon) {\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, icon: icon ? markRaw(icon) : null }].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 and scroll it into view.\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\tthis.showSelected()\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()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Show the current section and hide the others. They stay mounted,\n\t\t * so what a reader typed in one survives a switch.\n\t\t *\n\t\t * @spec openspec/changes/user-settings-one-section/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-dialog-shows-one-section-at-a-time\n\t\t */\n\t\tshowSelected() {\n\t\t\tif (typeof document === 'undefined') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor (const section of this.sections) {\n\t\t\t\tconst el = document.getElementById(`settings-section_${section.id}`)\n\t\t\t\tif (el) {\n\t\t\t\t\tel.hidden = section.id !== this.selected\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.cn-user-settings-dialog__body {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 0 24px;\n}\n\n.cn-user-settings-dialog__nav {\n\tflex: 0 0 200px;\n\twidth: 200px;\n\tpadding-top: 18px;\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.cn-user-settings-dialog__nav-link {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-height: 40px;\n\tpadding: 0 12px;\n\tborder-radius: var(--border-radius-element, 8px);\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-user-settings-dialog__nav-link:hover {\n\tbackground: var(--color-background-hover);\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.cn-user-settings-dialog__nav-link--active,\n.cn-user-settings-dialog__nav-link--active:hover {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n\tfont-weight: 600;\n}\n\n.cn-user-settings-dialog__nav-icon {\n\tdisplay: inline-flex;\n\tflex: none;\n\twidth: 18px;\n\theight: 18px;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-user-settings-dialog__sections {\n\tdisplay: flex;\n\tflex: 1 1 380px;\n\tflex-direction: column;\n\tmin-width: 0;\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section) {\n\tmargin: 0;\n\tpadding: 18px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {\n\tmargin: 0 0 12px;\n\tfont-size: 17px;\n\tfont-weight: 700;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n}\n\n.cn-user-settings-dialog__note {\n\tflex: 1 1 auto;\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-user-settings-dialog__nc-links {\n\tmargin-bottom: 2px;\n}\n\n.cn-user-settings-dialog__group {\n\tmargin: 10px 0 2px;\n\tpadding: 0 12px;\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: 12px;\n}\n\n.cn-user-settings-dialog__heading-icon {\n\tdisplay: inline-flex;\n\torder: -1;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* One section at a time: the others are mounted but hidden. */\n.cn-user-settings-dialog__sections :deep(.app-settings-section[hidden]) {\n\tdisplay: none;\n}\n</style>\n"],"names":["_toDisplayString","_createVNode","_createElementVNode","_createElementBlock","_openBlock","_Fragment","_renderList","_normalizeClass","_withModifiers","_renderSlot","_createBlock","_Teleport"],"mappings":"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"}
|