@conduction/nextcloud-vue 2.73.0 → 2.73.1
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 +29 -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/CnDetailPage/CnDetailPage.vue2.js +18 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +9 -6
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +16 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +5 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +16 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +53 -5
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +133 -0
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +22 -14
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +15 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +3 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue2.js +15 -1
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue3.js +2 -2
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue3.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/notificationRuleLabel.js +132 -0
- package/dist/esm/utils/notificationRuleLabel.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +473 -43
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.md +1 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +29 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +24 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +22 -4
- package/src/components/CnIndexPage/CnIndexPage.vue +17 -2
- package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +54 -6
- package/src/components/CnPageRenderer/CnPageRenderer.vue +147 -6
- package/src/components/CnTabsWidget/CnTabsWidget.vue +17 -2
- package/src/components/CnWidgetGrid/CnWidgetGrid.vue +19 -3
- package/src/index.js +2 -0
- package/src/utils/notificationRuleLabel.js +129 -0
- package/webpack/index.d.ts +16 -0
- package/webpack/index.js +70 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import axios__default from '@nextcloud/axios';
|
|
2
|
-
import { translate } from '@nextcloud/l10n';
|
|
2
|
+
import { translate, getLanguage } from '@nextcloud/l10n';
|
|
3
3
|
import { generateUrl } from '@nextcloud/router';
|
|
4
4
|
import { NcAppSettingsSection, NcButton, NcCheckboxRadioSwitch, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue';
|
|
5
5
|
import BellOffOutline from 'vue-material-design-icons/BellOffOutline.vue';
|
|
6
6
|
import BellOutline from 'vue-material-design-icons/BellOutline.vue';
|
|
7
|
+
import { notificationRuleLabel } from '../../utils/notificationRuleLabel.js';
|
|
7
8
|
|
|
8
9
|
const PREFS_PATH = '/apps/openregister/api/notification-preferences';
|
|
9
10
|
|
|
@@ -28,6 +29,28 @@ var script = {
|
|
|
28
29
|
* than an always-empty pane.
|
|
29
30
|
*/
|
|
30
31
|
cnAppId: { default: () => '' },
|
|
32
|
+
/**
|
|
33
|
+
* App-supplied labels for its notification rules, provided by the
|
|
34
|
+
* CnAppRoot ancestor from its `notificationLabels` prop. Keyed
|
|
35
|
+
* `<schema>.<key>` or `<key>`.
|
|
36
|
+
*/
|
|
37
|
+
cnNotificationLabels: { default: () => ({}) },
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
props: {
|
|
41
|
+
/**
|
|
42
|
+
* Labels for the notification rules, keyed `<schema>.<key>` or
|
|
43
|
+
* `<key>`; values are strings or per-locale maps (`{ nl, en }`).
|
|
44
|
+
* Optional: inside CnAppRoot the app's `notificationLabels` arrive by
|
|
45
|
+
* inject, and a rule without a label still reads as words, never as
|
|
46
|
+
* its raw key. A prop given here wins over the injected map.
|
|
47
|
+
*
|
|
48
|
+
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
|
|
49
|
+
*/
|
|
50
|
+
labels: {
|
|
51
|
+
type: Object,
|
|
52
|
+
default: null,
|
|
53
|
+
},
|
|
31
54
|
},
|
|
32
55
|
|
|
33
56
|
data() {
|
|
@@ -153,18 +176,43 @@ var script = {
|
|
|
153
176
|
},
|
|
154
177
|
|
|
155
178
|
/**
|
|
156
|
-
* Human-readable label for a notification
|
|
179
|
+
* Human-readable label for a notification rule. Never the raw key: the
|
|
180
|
+
* rule's own label, then the app's, then the library's wording for the
|
|
181
|
+
* generic keys, then the key split into words.
|
|
157
182
|
*
|
|
158
|
-
* @param {
|
|
183
|
+
* @param {object} entry The preference row.
|
|
159
184
|
* @return {string} A display label.
|
|
185
|
+
*
|
|
186
|
+
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
|
|
160
187
|
*/
|
|
161
|
-
notificationLabel(
|
|
188
|
+
notificationLabel(entry) {
|
|
162
189
|
const known = {
|
|
163
190
|
object_created: translate('nextcloud-vue', 'When an item is created'),
|
|
164
191
|
object_updated: translate('nextcloud-vue', 'When an item is updated'),
|
|
165
192
|
object_transitioned: translate('nextcloud-vue', 'When an item is assigned or changes status'),
|
|
166
193
|
};
|
|
167
|
-
|
|
194
|
+
const language = this.currentLanguage();
|
|
195
|
+
const injected = typeof this.cnNotificationLabels === 'function'
|
|
196
|
+
? this.cnNotificationLabels()
|
|
197
|
+
: this.cnNotificationLabels;
|
|
198
|
+
return notificationRuleLabel(entry, {
|
|
199
|
+
labels: { ...(injected || {}), ...(this.labels || {}) },
|
|
200
|
+
known,
|
|
201
|
+
language,
|
|
202
|
+
})
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The person's language, `en` when it cannot be read.
|
|
207
|
+
*
|
|
208
|
+
* @return {string} The language code.
|
|
209
|
+
*/
|
|
210
|
+
currentLanguage() {
|
|
211
|
+
try {
|
|
212
|
+
return (typeof getLanguage === 'function' && getLanguage()) || 'en'
|
|
213
|
+
} catch {
|
|
214
|
+
return 'en'
|
|
215
|
+
}
|
|
168
216
|
},
|
|
169
217
|
|
|
170
218
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnNotificationPreferences.vue2.js","sources":["../../../../src/components/CnNotificationPreferences/CnNotificationPreferences.vue"],"sourcesContent":["<template>\n\t<NcAppSettingsSection id=\"notifications\" :name=\"t('nextcloud-vue', 'Notifications')\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'Notification preferences unavailable')\"\n\t\t\t:description=\"t('nextcloud-vue', 'Notification preferences are provided by OpenRegister and could not be loaded here.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOffOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<NcEmptyContent v-else-if=\"scopedEntries.length === 0\"\n\t\t\t:name=\"t('nextcloud-vue', 'No notifications')\"\n\t\t\t:description=\"t('nextcloud-vue', 'This app has no notifications.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<div v-else class=\"cn-notif-prefs\">\n\t\t\t<p class=\"cn-notif-prefs__intro\">\n\t\t\t\t{{ t('nextcloud-vue', 'Choose which notifications you want to receive. Items you leave unchanged use the app default.') }}\n\t\t\t</p>\n\t\t\t<div v-for=\"group in groupedEntries\" :key=\"group.schema\" class=\"cn-notif-prefs__group\">\n\t\t\t\t<h4 v-if=\"groupedEntries.length > 1\" class=\"cn-notif-prefs__group-title\">\n\t\t\t\t\t{{ group.schemaTitle }}\n\t\t\t\t</h4>\n\t\t\t\t<div class=\"cn-notif-prefs__grid\">\n\t\t\t\t\t<div v-for=\"entry in group.items\" :key=\"entry.key\" class=\"cn-notif-prefs__card\">\n\t\t\t\t\t\t<NcCheckboxRadioSwitch type=\"switch\"\n\t\t\t\t\t\t\t:modelValue=\"entry.enabled\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@update:modelValue=\"onToggle(entry, $event)\">\n\t\t\t\t\t\t\t{{ notificationLabel(entry.notification) }}\n\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t<NcButton v-if=\"entry.source === 'user-override'\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@click=\"onReset(entry)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Reset to default') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</NcAppSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\n/**\n * CnNotificationPreferences\n *\n * A self-contained `NcAppSettingsSection` that lets the current user turn\n * the notifications declared by their accessible schemas on or off. It reads\n * the effective preferences (schema default merged with the user's overrides)\n * and writes override-only values, both via OpenRegister's\n * `/api/notification-preferences` endpoint:\n *\n * GET → { results: [{ schema, schemaTitle, application, notification, enabled, channels, source }], total }\n * PUT → { schema, notification, enabled } | { schema, notification, reset: true }\n *\n * Preferences are override-only: leaving an item unchanged keeps the app\n * default, so apps that add new schemas/notifications keep working without\n * any per-user migration. Designed to render inside CnAppRoot's\n * `#user-settings` slot (its default fallback mounts this component).\n *\n * The API is multi-tenant per USER (every schema the user can read), not\n * per APP, so the raw `results` list mixes notifications from every app the\n * user has access to (e.g. \"Data-Subject Request\", \"Subsidie\",\n * \"Applicatieversie\" side by side). This component scopes the list down to\n * the app whose settings modal is currently open by matching each entry's\n * `application` field (the owning app id OpenRegister recorded on the\n * schema, e.g. \"pipelinq\") against `cnAppId`, injected by the CnAppRoot\n * ancestor. Entries with no known owning app (`application` is null/empty —\n * legacy or hand-authored schemas) are excluded from the scoped list rather\n * than guessed into the wrong app.\n */\nimport { NcAppSettingsSection, NcButton, NcCheckboxRadioSwitch, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport BellOffOutline from 'vue-material-design-icons/BellOffOutline.vue'\nimport BellOutline from 'vue-material-design-icons/BellOutline.vue'\n\nconst PREFS_PATH = '/apps/openregister/api/notification-preferences'\n\nexport default {\n\tname: 'CnNotificationPreferences',\n\tcomponents: {\n\t\tNcAppSettingsSection,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tBellOutline,\n\t\tBellOffOutline,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consuming app's id (e.g. \"pipelinq\"), provided by the CnAppRoot\n\t\t * ancestor. Used to scope the notification list to the app whose\n\t\t * settings modal is currently open. Empty when no CnAppRoot ancestor\n\t\t * exists, in which case the full (unscoped) list is shown rather\n\t\t * than an always-empty pane.\n\t\t */\n\t\tcnAppId: { default: () => '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tentries: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Entries scoped to the current app. Falls back to the full list\n\t\t * when there is no `cnAppId` context to scope by (e.g. the\n\t\t * component is used standalone, outside CnAppRoot).\n\t\t *\n\t\t * @return {Array<object>} The app-scoped preference entries.\n\t\t */\n\t\tscopedEntries() {\n\t\t\tif (!this.cnAppId) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.filter((entry) => entry.application === this.cnAppId)\n\t\t},\n\n\t\t/**\n\t\t * Group the scoped effective-preference list by schema for display.\n\t\t *\n\t\t * @return {Array<{schema: string, schemaTitle: string, items: Array}>} Grouped entries.\n\t\t */\n\t\tgroupedEntries() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.scopedEntries) {\n\t\t\t\tif (!groups.has(entry.schema)) {\n\t\t\t\t\tgroups.set(entry.schema, {\n\t\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\t\tschemaTitle: entry.schemaTitle || entry.schema,\n\t\t\t\t\t\titems: [],\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tgroups.get(entry.schema).items.push(entry)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load the current user's effective notification preferences.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(PREFS_PATH))\n\t\t\t\tconst results = Array.isArray(data?.results) ? data.results : []\n\t\t\t\tthis.entries = results.map((e) => ({\n\t\t\t\t\t...e,\n\t\t\t\t\tkey: `${e.schema}/${e.notification}`,\n\t\t\t\t\tenabled: e.enabled === true,\n\t\t\t\t\tsaving: false,\n\t\t\t\t}))\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Persist an on/off override for a single notification.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @param {boolean} checked The new on/off value.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onToggle(entry, checked) {\n\t\t\tconst previous = entry.enabled\n\t\t\tentry.saving = true\n\t\t\tentry.enabled = checked\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\tenabled: checked,\n\t\t\t\t})\n\t\t\t\tentry.source = 'user-override'\n\t\t\t} catch {\n\t\t\t\tentry.enabled = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not save notification preference'))\n\t\t\t} finally {\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear a user override so the schema default applies again.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onReset(entry) {\n\t\t\tentry.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\treset: true,\n\t\t\t\t})\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not reset notification preference'))\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable label for a notification key, falling back to the key.\n\t\t *\n\t\t * @param {string} key The notification annotation key.\n\t\t * @return {string} A display label.\n\t\t */\n\t\tnotificationLabel(key) {\n\t\t\tconst known = {\n\t\t\t\tobject_created: t('nextcloud-vue', 'When an item is created'),\n\t\t\t\tobject_updated: t('nextcloud-vue', 'When an item is updated'),\n\t\t\t\tobject_transitioned: t('nextcloud-vue', 'When an item is assigned or changes status'),\n\t\t\t}\n\t\t\treturn known[key] || key\n\t\t},\n\n\t\t/**\n\t\t * Surface an error toast (lazy-loaded like the rest of the lib).\n\t\t *\n\t\t * @param {string} message The message to show.\n\t\t * @return {void}\n\t\t */\n\t\tshowError(message) {\n\t\t\timport('@nextcloud/dialogs').then(({ showError }) => showError(message))\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-notif-prefs__intro {\n\tmargin-bottom: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-notif-prefs__group {\n\tmargin-bottom: 20px;\n}\n\n.cn-notif-prefs__group-title {\n\tmargin: 8px 0 8px;\n\tfont-weight: bold;\n}\n\n.cn-notif-prefs__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 8px;\n}\n\n.cn-notif-prefs__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 4px;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground-color: var(--color-background-hover);\n}\n</style>\n"],"names":["t","axios"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnNotificationPreferences.vue2.js","sources":["../../../../src/components/CnNotificationPreferences/CnNotificationPreferences.vue"],"sourcesContent":["<template>\n\t<NcAppSettingsSection id=\"notifications\" :name=\"t('nextcloud-vue', 'Notifications')\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'Notification preferences unavailable')\"\n\t\t\t:description=\"t('nextcloud-vue', 'Notification preferences are provided by OpenRegister and could not be loaded here.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOffOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<NcEmptyContent v-else-if=\"scopedEntries.length === 0\"\n\t\t\t:name=\"t('nextcloud-vue', 'No notifications')\"\n\t\t\t:description=\"t('nextcloud-vue', 'This app has no notifications.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<div v-else class=\"cn-notif-prefs\">\n\t\t\t<p class=\"cn-notif-prefs__intro\">\n\t\t\t\t{{ t('nextcloud-vue', 'Choose which notifications you want to receive. Items you leave unchanged use the app default.') }}\n\t\t\t</p>\n\t\t\t<div v-for=\"group in groupedEntries\" :key=\"group.schema\" class=\"cn-notif-prefs__group\">\n\t\t\t\t<h4 v-if=\"groupedEntries.length > 1\" class=\"cn-notif-prefs__group-title\">\n\t\t\t\t\t{{ group.schemaTitle }}\n\t\t\t\t</h4>\n\t\t\t\t<div class=\"cn-notif-prefs__grid\">\n\t\t\t\t\t<div v-for=\"entry in group.items\" :key=\"entry.key\" class=\"cn-notif-prefs__card\">\n\t\t\t\t\t\t<NcCheckboxRadioSwitch type=\"switch\"\n\t\t\t\t\t\t\t:modelValue=\"entry.enabled\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@update:modelValue=\"onToggle(entry, $event)\">\n\t\t\t\t\t\t\t{{ notificationLabel(entry) }}\n\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t<NcButton v-if=\"entry.source === 'user-override'\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@click=\"onReset(entry)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Reset to default') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</NcAppSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { getLanguage, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\n/**\n * CnNotificationPreferences\n *\n * A self-contained `NcAppSettingsSection` that lets the current user turn\n * the notifications declared by their accessible schemas on or off. It reads\n * the effective preferences (schema default merged with the user's overrides)\n * and writes override-only values, both via OpenRegister's\n * `/api/notification-preferences` endpoint:\n *\n * GET → { results: [{ schema, schemaTitle, application, notification, enabled, channels, source }], total }\n * PUT → { schema, notification, enabled } | { schema, notification, reset: true }\n *\n * Preferences are override-only: leaving an item unchanged keeps the app\n * default, so apps that add new schemas/notifications keep working without\n * any per-user migration. Designed to render inside CnAppRoot's\n * `#user-settings` slot (its default fallback mounts this component).\n *\n * The API is multi-tenant per USER (every schema the user can read), not\n * per APP, so the raw `results` list mixes notifications from every app the\n * user has access to (e.g. \"Data-Subject Request\", \"Subsidie\",\n * \"Applicatieversie\" side by side). This component scopes the list down to\n * the app whose settings modal is currently open by matching each entry's\n * `application` field (the owning app id OpenRegister recorded on the\n * schema, e.g. \"pipelinq\") against `cnAppId`, injected by the CnAppRoot\n * ancestor. Entries with no known owning app (`application` is null/empty —\n * legacy or hand-authored schemas) are excluded from the scoped list rather\n * than guessed into the wrong app.\n */\nimport { NcAppSettingsSection, NcButton, NcCheckboxRadioSwitch, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport BellOffOutline from 'vue-material-design-icons/BellOffOutline.vue'\nimport BellOutline from 'vue-material-design-icons/BellOutline.vue'\nimport { notificationRuleLabel } from '../../utils/notificationRuleLabel.js'\n\nconst PREFS_PATH = '/apps/openregister/api/notification-preferences'\n\nexport default {\n\tname: 'CnNotificationPreferences',\n\tcomponents: {\n\t\tNcAppSettingsSection,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tBellOutline,\n\t\tBellOffOutline,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consuming app's id (e.g. \"pipelinq\"), provided by the CnAppRoot\n\t\t * ancestor. Used to scope the notification list to the app whose\n\t\t * settings modal is currently open. Empty when no CnAppRoot ancestor\n\t\t * exists, in which case the full (unscoped) list is shown rather\n\t\t * than an always-empty pane.\n\t\t */\n\t\tcnAppId: { default: () => '' },\n\t\t/**\n\t\t * App-supplied labels for its notification rules, provided by the\n\t\t * CnAppRoot ancestor from its `notificationLabels` prop. Keyed\n\t\t * `<schema>.<key>` or `<key>`.\n\t\t */\n\t\tcnNotificationLabels: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Labels for the notification rules, keyed `<schema>.<key>` or\n\t\t * `<key>`; values are strings or per-locale maps (`{ nl, en }`).\n\t\t * Optional: inside CnAppRoot the app's `notificationLabels` arrive by\n\t\t * inject, and a rule without a label still reads as words, never as\n\t\t * its raw key. A prop given here wins over the injected map.\n\t\t *\n\t\t * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md\n\t\t */\n\t\tlabels: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tentries: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Entries scoped to the current app. Falls back to the full list\n\t\t * when there is no `cnAppId` context to scope by (e.g. the\n\t\t * component is used standalone, outside CnAppRoot).\n\t\t *\n\t\t * @return {Array<object>} The app-scoped preference entries.\n\t\t */\n\t\tscopedEntries() {\n\t\t\tif (!this.cnAppId) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.filter((entry) => entry.application === this.cnAppId)\n\t\t},\n\n\t\t/**\n\t\t * Group the scoped effective-preference list by schema for display.\n\t\t *\n\t\t * @return {Array<{schema: string, schemaTitle: string, items: Array}>} Grouped entries.\n\t\t */\n\t\tgroupedEntries() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.scopedEntries) {\n\t\t\t\tif (!groups.has(entry.schema)) {\n\t\t\t\t\tgroups.set(entry.schema, {\n\t\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\t\tschemaTitle: entry.schemaTitle || entry.schema,\n\t\t\t\t\t\titems: [],\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tgroups.get(entry.schema).items.push(entry)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load the current user's effective notification preferences.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(PREFS_PATH))\n\t\t\t\tconst results = Array.isArray(data?.results) ? data.results : []\n\t\t\t\tthis.entries = results.map((e) => ({\n\t\t\t\t\t...e,\n\t\t\t\t\tkey: `${e.schema}/${e.notification}`,\n\t\t\t\t\tenabled: e.enabled === true,\n\t\t\t\t\tsaving: false,\n\t\t\t\t}))\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Persist an on/off override for a single notification.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @param {boolean} checked The new on/off value.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onToggle(entry, checked) {\n\t\t\tconst previous = entry.enabled\n\t\t\tentry.saving = true\n\t\t\tentry.enabled = checked\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\tenabled: checked,\n\t\t\t\t})\n\t\t\t\tentry.source = 'user-override'\n\t\t\t} catch {\n\t\t\t\tentry.enabled = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not save notification preference'))\n\t\t\t} finally {\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear a user override so the schema default applies again.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onReset(entry) {\n\t\t\tentry.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\treset: true,\n\t\t\t\t})\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not reset notification preference'))\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable label for a notification rule. Never the raw key: the\n\t\t * rule's own label, then the app's, then the library's wording for the\n\t\t * generic keys, then the key split into words.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @return {string} A display label.\n\t\t *\n\t\t * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md\n\t\t */\n\t\tnotificationLabel(entry) {\n\t\t\tconst known = {\n\t\t\t\tobject_created: t('nextcloud-vue', 'When an item is created'),\n\t\t\t\tobject_updated: t('nextcloud-vue', 'When an item is updated'),\n\t\t\t\tobject_transitioned: t('nextcloud-vue', 'When an item is assigned or changes status'),\n\t\t\t}\n\t\t\tconst language = this.currentLanguage()\n\t\t\tconst injected = typeof this.cnNotificationLabels === 'function'\n\t\t\t\t? this.cnNotificationLabels()\n\t\t\t\t: this.cnNotificationLabels\n\t\t\treturn notificationRuleLabel(entry, {\n\t\t\t\tlabels: { ...(injected || {}), ...(this.labels || {}) },\n\t\t\t\tknown,\n\t\t\t\tlanguage,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The person's language, `en` when it cannot be read.\n\t\t *\n\t\t * @return {string} The language code.\n\t\t */\n\t\tcurrentLanguage() {\n\t\t\ttry {\n\t\t\t\treturn (typeof getLanguage === 'function' && getLanguage()) || 'en'\n\t\t\t} catch {\n\t\t\t\treturn 'en'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Surface an error toast (lazy-loaded like the rest of the lib).\n\t\t *\n\t\t * @param {string} message The message to show.\n\t\t * @return {void}\n\t\t */\n\t\tshowError(message) {\n\t\t\timport('@nextcloud/dialogs').then(({ showError }) => showError(message))\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-notif-prefs__intro {\n\tmargin-bottom: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-notif-prefs__group {\n\tmargin-bottom: 20px;\n}\n\n.cn-notif-prefs__group-title {\n\tmargin: 8px 0 8px;\n\tfont-weight: bold;\n}\n\n.cn-notif-prefs__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 8px;\n}\n\n.cn-notif-prefs__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 4px;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground-color: var(--color-background-hover);\n}\n</style>\n"],"names":["t","axios"],"mappings":"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"}
|
|
@@ -75,7 +75,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
75
75
|
"onUpdate:modelValue": $event => ($options.onToggle(entry, $event))
|
|
76
76
|
}, {
|
|
77
77
|
default: withCtx(() => [
|
|
78
|
-
createTextVNode(toDisplayString($options.notificationLabel(entry
|
|
78
|
+
createTextVNode(toDisplayString($options.notificationLabel(entry)), 1 /* TEXT */)
|
|
79
79
|
]),
|
|
80
80
|
_: 2 /* DYNAMIC */
|
|
81
81
|
}, 1032 /* PROPS, DYNAMIC_SLOTS */, ["modelValue", "disabled", "onUpdate:modelValue"]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnNotificationPreferences.vue3.js","sources":["../../../../src/components/CnNotificationPreferences/CnNotificationPreferences.vue?vue&type=template&id=72cc0056&lang.js"],"sourcesContent":["<template>\n\t<NcAppSettingsSection id=\"notifications\" :name=\"t('nextcloud-vue', 'Notifications')\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'Notification preferences unavailable')\"\n\t\t\t:description=\"t('nextcloud-vue', 'Notification preferences are provided by OpenRegister and could not be loaded here.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOffOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<NcEmptyContent v-else-if=\"scopedEntries.length === 0\"\n\t\t\t:name=\"t('nextcloud-vue', 'No notifications')\"\n\t\t\t:description=\"t('nextcloud-vue', 'This app has no notifications.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<div v-else class=\"cn-notif-prefs\">\n\t\t\t<p class=\"cn-notif-prefs__intro\">\n\t\t\t\t{{ t('nextcloud-vue', 'Choose which notifications you want to receive. Items you leave unchanged use the app default.') }}\n\t\t\t</p>\n\t\t\t<div v-for=\"group in groupedEntries\" :key=\"group.schema\" class=\"cn-notif-prefs__group\">\n\t\t\t\t<h4 v-if=\"groupedEntries.length > 1\" class=\"cn-notif-prefs__group-title\">\n\t\t\t\t\t{{ group.schemaTitle }}\n\t\t\t\t</h4>\n\t\t\t\t<div class=\"cn-notif-prefs__grid\">\n\t\t\t\t\t<div v-for=\"entry in group.items\" :key=\"entry.key\" class=\"cn-notif-prefs__card\">\n\t\t\t\t\t\t<NcCheckboxRadioSwitch type=\"switch\"\n\t\t\t\t\t\t\t:modelValue=\"entry.enabled\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@update:modelValue=\"onToggle(entry, $event)\">\n\t\t\t\t\t\t\t{{ notificationLabel(entry.notification) }}\n\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t<NcButton v-if=\"entry.source === 'user-override'\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@click=\"onReset(entry)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Reset to default') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</NcAppSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\n/**\n * CnNotificationPreferences\n *\n * A self-contained `NcAppSettingsSection` that lets the current user turn\n * the notifications declared by their accessible schemas on or off. It reads\n * the effective preferences (schema default merged with the user's overrides)\n * and writes override-only values, both via OpenRegister's\n * `/api/notification-preferences` endpoint:\n *\n * GET → { results: [{ schema, schemaTitle, application, notification, enabled, channels, source }], total }\n * PUT → { schema, notification, enabled } | { schema, notification, reset: true }\n *\n * Preferences are override-only: leaving an item unchanged keeps the app\n * default, so apps that add new schemas/notifications keep working without\n * any per-user migration. Designed to render inside CnAppRoot's\n * `#user-settings` slot (its default fallback mounts this component).\n *\n * The API is multi-tenant per USER (every schema the user can read), not\n * per APP, so the raw `results` list mixes notifications from every app the\n * user has access to (e.g. \"Data-Subject Request\", \"Subsidie\",\n * \"Applicatieversie\" side by side). This component scopes the list down to\n * the app whose settings modal is currently open by matching each entry's\n * `application` field (the owning app id OpenRegister recorded on the\n * schema, e.g. \"pipelinq\") against `cnAppId`, injected by the CnAppRoot\n * ancestor. Entries with no known owning app (`application` is null/empty —\n * legacy or hand-authored schemas) are excluded from the scoped list rather\n * than guessed into the wrong app.\n */\nimport { NcAppSettingsSection, NcButton, NcCheckboxRadioSwitch, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport BellOffOutline from 'vue-material-design-icons/BellOffOutline.vue'\nimport BellOutline from 'vue-material-design-icons/BellOutline.vue'\n\nconst PREFS_PATH = '/apps/openregister/api/notification-preferences'\n\nexport default {\n\tname: 'CnNotificationPreferences',\n\tcomponents: {\n\t\tNcAppSettingsSection,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tBellOutline,\n\t\tBellOffOutline,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consuming app's id (e.g. \"pipelinq\"), provided by the CnAppRoot\n\t\t * ancestor. Used to scope the notification list to the app whose\n\t\t * settings modal is currently open. Empty when no CnAppRoot ancestor\n\t\t * exists, in which case the full (unscoped) list is shown rather\n\t\t * than an always-empty pane.\n\t\t */\n\t\tcnAppId: { default: () => '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tentries: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Entries scoped to the current app. Falls back to the full list\n\t\t * when there is no `cnAppId` context to scope by (e.g. the\n\t\t * component is used standalone, outside CnAppRoot).\n\t\t *\n\t\t * @return {Array<object>} The app-scoped preference entries.\n\t\t */\n\t\tscopedEntries() {\n\t\t\tif (!this.cnAppId) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.filter((entry) => entry.application === this.cnAppId)\n\t\t},\n\n\t\t/**\n\t\t * Group the scoped effective-preference list by schema for display.\n\t\t *\n\t\t * @return {Array<{schema: string, schemaTitle: string, items: Array}>} Grouped entries.\n\t\t */\n\t\tgroupedEntries() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.scopedEntries) {\n\t\t\t\tif (!groups.has(entry.schema)) {\n\t\t\t\t\tgroups.set(entry.schema, {\n\t\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\t\tschemaTitle: entry.schemaTitle || entry.schema,\n\t\t\t\t\t\titems: [],\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tgroups.get(entry.schema).items.push(entry)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load the current user's effective notification preferences.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(PREFS_PATH))\n\t\t\t\tconst results = Array.isArray(data?.results) ? data.results : []\n\t\t\t\tthis.entries = results.map((e) => ({\n\t\t\t\t\t...e,\n\t\t\t\t\tkey: `${e.schema}/${e.notification}`,\n\t\t\t\t\tenabled: e.enabled === true,\n\t\t\t\t\tsaving: false,\n\t\t\t\t}))\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Persist an on/off override for a single notification.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @param {boolean} checked The new on/off value.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onToggle(entry, checked) {\n\t\t\tconst previous = entry.enabled\n\t\t\tentry.saving = true\n\t\t\tentry.enabled = checked\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\tenabled: checked,\n\t\t\t\t})\n\t\t\t\tentry.source = 'user-override'\n\t\t\t} catch {\n\t\t\t\tentry.enabled = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not save notification preference'))\n\t\t\t} finally {\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear a user override so the schema default applies again.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onReset(entry) {\n\t\t\tentry.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\treset: true,\n\t\t\t\t})\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not reset notification preference'))\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable label for a notification key, falling back to the key.\n\t\t *\n\t\t * @param {string} key The notification annotation key.\n\t\t * @return {string} A display label.\n\t\t */\n\t\tnotificationLabel(key) {\n\t\t\tconst known = {\n\t\t\t\tobject_created: t('nextcloud-vue', 'When an item is created'),\n\t\t\t\tobject_updated: t('nextcloud-vue', 'When an item is updated'),\n\t\t\t\tobject_transitioned: t('nextcloud-vue', 'When an item is assigned or changes status'),\n\t\t\t}\n\t\t\treturn known[key] || key\n\t\t},\n\n\t\t/**\n\t\t * Surface an error toast (lazy-loaded like the rest of the lib).\n\t\t *\n\t\t * @param {string} message The message to show.\n\t\t * @return {void}\n\t\t */\n\t\tshowError(message) {\n\t\t\timport('@nextcloud/dialogs').then(({ showError }) => showError(message))\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-notif-prefs__intro {\n\tmargin-bottom: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-notif-prefs__group {\n\tmargin-bottom: 20px;\n}\n\n.cn-notif-prefs__group-title {\n\tmargin: 8px 0 8px;\n\tfont-weight: bold;\n}\n\n.cn-notif-prefs__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 8px;\n}\n\n.cn-notif-prefs__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 4px;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground-color: var(--color-background-hover);\n}\n</style>\n"],"names":["_createBlock","_createVNode","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnNotificationPreferences.vue3.js","sources":["../../../../src/components/CnNotificationPreferences/CnNotificationPreferences.vue?vue&type=template&id=72cc0056&lang.js"],"sourcesContent":["<template>\n\t<NcAppSettingsSection id=\"notifications\" :name=\"t('nextcloud-vue', 'Notifications')\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'Notification preferences unavailable')\"\n\t\t\t:description=\"t('nextcloud-vue', 'Notification preferences are provided by OpenRegister and could not be loaded here.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOffOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<NcEmptyContent v-else-if=\"scopedEntries.length === 0\"\n\t\t\t:name=\"t('nextcloud-vue', 'No notifications')\"\n\t\t\t:description=\"t('nextcloud-vue', 'This app has no notifications.')\">\n\t\t\t<template #icon>\n\t\t\t\t<BellOutline :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<div v-else class=\"cn-notif-prefs\">\n\t\t\t<p class=\"cn-notif-prefs__intro\">\n\t\t\t\t{{ t('nextcloud-vue', 'Choose which notifications you want to receive. Items you leave unchanged use the app default.') }}\n\t\t\t</p>\n\t\t\t<div v-for=\"group in groupedEntries\" :key=\"group.schema\" class=\"cn-notif-prefs__group\">\n\t\t\t\t<h4 v-if=\"groupedEntries.length > 1\" class=\"cn-notif-prefs__group-title\">\n\t\t\t\t\t{{ group.schemaTitle }}\n\t\t\t\t</h4>\n\t\t\t\t<div class=\"cn-notif-prefs__grid\">\n\t\t\t\t\t<div v-for=\"entry in group.items\" :key=\"entry.key\" class=\"cn-notif-prefs__card\">\n\t\t\t\t\t\t<NcCheckboxRadioSwitch type=\"switch\"\n\t\t\t\t\t\t\t:modelValue=\"entry.enabled\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@update:modelValue=\"onToggle(entry, $event)\">\n\t\t\t\t\t\t\t{{ notificationLabel(entry) }}\n\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t<NcButton v-if=\"entry.source === 'user-override'\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t:disabled=\"entry.saving\"\n\t\t\t\t\t\t\t@click=\"onReset(entry)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Reset to default') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</NcAppSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { getLanguage, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\n/**\n * CnNotificationPreferences\n *\n * A self-contained `NcAppSettingsSection` that lets the current user turn\n * the notifications declared by their accessible schemas on or off. It reads\n * the effective preferences (schema default merged with the user's overrides)\n * and writes override-only values, both via OpenRegister's\n * `/api/notification-preferences` endpoint:\n *\n * GET → { results: [{ schema, schemaTitle, application, notification, enabled, channels, source }], total }\n * PUT → { schema, notification, enabled } | { schema, notification, reset: true }\n *\n * Preferences are override-only: leaving an item unchanged keeps the app\n * default, so apps that add new schemas/notifications keep working without\n * any per-user migration. Designed to render inside CnAppRoot's\n * `#user-settings` slot (its default fallback mounts this component).\n *\n * The API is multi-tenant per USER (every schema the user can read), not\n * per APP, so the raw `results` list mixes notifications from every app the\n * user has access to (e.g. \"Data-Subject Request\", \"Subsidie\",\n * \"Applicatieversie\" side by side). This component scopes the list down to\n * the app whose settings modal is currently open by matching each entry's\n * `application` field (the owning app id OpenRegister recorded on the\n * schema, e.g. \"pipelinq\") against `cnAppId`, injected by the CnAppRoot\n * ancestor. Entries with no known owning app (`application` is null/empty —\n * legacy or hand-authored schemas) are excluded from the scoped list rather\n * than guessed into the wrong app.\n */\nimport { NcAppSettingsSection, NcButton, NcCheckboxRadioSwitch, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport BellOffOutline from 'vue-material-design-icons/BellOffOutline.vue'\nimport BellOutline from 'vue-material-design-icons/BellOutline.vue'\nimport { notificationRuleLabel } from '../../utils/notificationRuleLabel.js'\n\nconst PREFS_PATH = '/apps/openregister/api/notification-preferences'\n\nexport default {\n\tname: 'CnNotificationPreferences',\n\tcomponents: {\n\t\tNcAppSettingsSection,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tBellOutline,\n\t\tBellOffOutline,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consuming app's id (e.g. \"pipelinq\"), provided by the CnAppRoot\n\t\t * ancestor. Used to scope the notification list to the app whose\n\t\t * settings modal is currently open. Empty when no CnAppRoot ancestor\n\t\t * exists, in which case the full (unscoped) list is shown rather\n\t\t * than an always-empty pane.\n\t\t */\n\t\tcnAppId: { default: () => '' },\n\t\t/**\n\t\t * App-supplied labels for its notification rules, provided by the\n\t\t * CnAppRoot ancestor from its `notificationLabels` prop. Keyed\n\t\t * `<schema>.<key>` or `<key>`.\n\t\t */\n\t\tcnNotificationLabels: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Labels for the notification rules, keyed `<schema>.<key>` or\n\t\t * `<key>`; values are strings or per-locale maps (`{ nl, en }`).\n\t\t * Optional: inside CnAppRoot the app's `notificationLabels` arrive by\n\t\t * inject, and a rule without a label still reads as words, never as\n\t\t * its raw key. A prop given here wins over the injected map.\n\t\t *\n\t\t * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md\n\t\t */\n\t\tlabels: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tentries: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Entries scoped to the current app. Falls back to the full list\n\t\t * when there is no `cnAppId` context to scope by (e.g. the\n\t\t * component is used standalone, outside CnAppRoot).\n\t\t *\n\t\t * @return {Array<object>} The app-scoped preference entries.\n\t\t */\n\t\tscopedEntries() {\n\t\t\tif (!this.cnAppId) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.filter((entry) => entry.application === this.cnAppId)\n\t\t},\n\n\t\t/**\n\t\t * Group the scoped effective-preference list by schema for display.\n\t\t *\n\t\t * @return {Array<{schema: string, schemaTitle: string, items: Array}>} Grouped entries.\n\t\t */\n\t\tgroupedEntries() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.scopedEntries) {\n\t\t\t\tif (!groups.has(entry.schema)) {\n\t\t\t\t\tgroups.set(entry.schema, {\n\t\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\t\tschemaTitle: entry.schemaTitle || entry.schema,\n\t\t\t\t\t\titems: [],\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tgroups.get(entry.schema).items.push(entry)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load the current user's effective notification preferences.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(PREFS_PATH))\n\t\t\t\tconst results = Array.isArray(data?.results) ? data.results : []\n\t\t\t\tthis.entries = results.map((e) => ({\n\t\t\t\t\t...e,\n\t\t\t\t\tkey: `${e.schema}/${e.notification}`,\n\t\t\t\t\tenabled: e.enabled === true,\n\t\t\t\t\tsaving: false,\n\t\t\t\t}))\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Persist an on/off override for a single notification.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @param {boolean} checked The new on/off value.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onToggle(entry, checked) {\n\t\t\tconst previous = entry.enabled\n\t\t\tentry.saving = true\n\t\t\tentry.enabled = checked\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\tenabled: checked,\n\t\t\t\t})\n\t\t\t\tentry.source = 'user-override'\n\t\t\t} catch {\n\t\t\t\tentry.enabled = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not save notification preference'))\n\t\t\t} finally {\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear a user override so the schema default applies again.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onReset(entry) {\n\t\t\tentry.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(PREFS_PATH), {\n\t\t\t\t\tschema: entry.schema,\n\t\t\t\t\tnotification: entry.notification,\n\t\t\t\t\treset: true,\n\t\t\t\t})\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not reset notification preference'))\n\t\t\t\tentry.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable label for a notification rule. Never the raw key: the\n\t\t * rule's own label, then the app's, then the library's wording for the\n\t\t * generic keys, then the key split into words.\n\t\t *\n\t\t * @param {object} entry The preference row.\n\t\t * @return {string} A display label.\n\t\t *\n\t\t * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md\n\t\t */\n\t\tnotificationLabel(entry) {\n\t\t\tconst known = {\n\t\t\t\tobject_created: t('nextcloud-vue', 'When an item is created'),\n\t\t\t\tobject_updated: t('nextcloud-vue', 'When an item is updated'),\n\t\t\t\tobject_transitioned: t('nextcloud-vue', 'When an item is assigned or changes status'),\n\t\t\t}\n\t\t\tconst language = this.currentLanguage()\n\t\t\tconst injected = typeof this.cnNotificationLabels === 'function'\n\t\t\t\t? this.cnNotificationLabels()\n\t\t\t\t: this.cnNotificationLabels\n\t\t\treturn notificationRuleLabel(entry, {\n\t\t\t\tlabels: { ...(injected || {}), ...(this.labels || {}) },\n\t\t\t\tknown,\n\t\t\t\tlanguage,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The person's language, `en` when it cannot be read.\n\t\t *\n\t\t * @return {string} The language code.\n\t\t */\n\t\tcurrentLanguage() {\n\t\t\ttry {\n\t\t\t\treturn (typeof getLanguage === 'function' && getLanguage()) || 'en'\n\t\t\t} catch {\n\t\t\t\treturn 'en'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Surface an error toast (lazy-loaded like the rest of the lib).\n\t\t *\n\t\t * @param {string} message The message to show.\n\t\t * @return {void}\n\t\t */\n\t\tshowError(message) {\n\t\t\timport('@nextcloud/dialogs').then(({ showError }) => showError(message))\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-notif-prefs__intro {\n\tmargin-bottom: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-notif-prefs__group {\n\tmargin-bottom: 20px;\n}\n\n.cn-notif-prefs__group-title {\n\tmargin: 8px 0 8px;\n\tfont-weight: bold;\n}\n\n.cn-notif-prefs__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 8px;\n}\n\n.cn-notif-prefs__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 4px;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground-color: var(--color-background-hover);\n}\n</style>\n"],"names":["_createBlock","_createVNode","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createTextVNode"],"mappings":"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"}
|
|
@@ -21,6 +21,53 @@ import __sfc_main$4 from '../CnMassExportDialog/CnMassExportDialog.vue.js';
|
|
|
21
21
|
import { defaultPageTypes } from './pageTypes.js';
|
|
22
22
|
import { useObjectStore } from '../../store/useObjectStore.js';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* OpenRegister registers and schemas already described by id, so a click on
|
|
26
|
+
* a related object costs at most one request per register or schema for as
|
|
27
|
+
* long as this module stays loaded (the SPA's lifetime, across page
|
|
28
|
+
* changes). Keyed by `${kind}:${id}`; a failed lookup is dropped again so the
|
|
29
|
+
* next click retries it.
|
|
30
|
+
*
|
|
31
|
+
* @type {Map<string, Promise<{ id: string, slug: string }>>}
|
|
32
|
+
*/
|
|
33
|
+
const entityDescriptorCache = new Map();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Describe an OpenRegister register or schema (`{ id, slug }`) by its id or
|
|
37
|
+
* slug, or null when it cannot be fetched. An object's `@self` names its
|
|
38
|
+
* register and schema by id while a manifest may name them by slug; this is
|
|
39
|
+
* the bridge. Lazy-imports axios and the router helper like the widgets do.
|
|
40
|
+
*
|
|
41
|
+
* @param {'registers'|'schemas'} kind The collection to ask.
|
|
42
|
+
* @param {string|number} idOrSlug The id (or slug) to describe.
|
|
43
|
+
* @return {Promise<{ id: string, slug: string }|null>} The descriptor.
|
|
44
|
+
*/
|
|
45
|
+
function describeOpenRegisterEntity(kind, idOrSlug) {
|
|
46
|
+
const key = `${kind}:${idOrSlug}`;
|
|
47
|
+
if (!entityDescriptorCache.has(key)) {
|
|
48
|
+
entityDescriptorCache.set(key, (async () => {
|
|
49
|
+
try {
|
|
50
|
+
const [{ default: axios }, { generateUrl }] = await Promise.all([
|
|
51
|
+
import('@nextcloud/axios'),
|
|
52
|
+
import('@nextcloud/router'),
|
|
53
|
+
]);
|
|
54
|
+
const url = generateUrl('/apps/openregister/api/{kind}/{id}', { kind, id: String(idOrSlug) });
|
|
55
|
+
const res = await axios.get(url);
|
|
56
|
+
const data = res && res.data;
|
|
57
|
+
if (!data || typeof data !== 'object') {
|
|
58
|
+
throw new Error(`no descriptor for ${key}`)
|
|
59
|
+
}
|
|
60
|
+
return { id: String(data.id ?? idOrSlug), slug: String(data.slug ?? '') }
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// One failed request must not pin every later click to nowhere.
|
|
63
|
+
entityDescriptorCache.delete(key);
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
66
|
+
})());
|
|
67
|
+
}
|
|
68
|
+
return entityDescriptorCache.get(key).catch(() => null)
|
|
69
|
+
}
|
|
70
|
+
|
|
24
71
|
/** Recognised fixed slot names for v2 manifests. */
|
|
25
72
|
const KNOWN_SLOTS = new Set(['body', 'sidebar', 'header-actions', 'footer', 'modal']);
|
|
26
73
|
|
|
@@ -1548,6 +1595,92 @@ var script = {
|
|
|
1548
1595
|
})
|
|
1549
1596
|
},
|
|
1550
1597
|
|
|
1598
|
+
/**
|
|
1599
|
+
* Open the detail page of an object clicked in a Related widget —
|
|
1600
|
+
* reaching here as `CnDetailPage`'s `related-object-click`, or as
|
|
1601
|
+
* `CnWidgetGrid`'s `select-object` on a v2 widget-grid page. Nothing
|
|
1602
|
+
* listened to either, so the rows looked like links and went nowhere.
|
|
1603
|
+
*
|
|
1604
|
+
* @param {object} raw The clicked object as the Related widget holds it.
|
|
1605
|
+
* @return {Promise<void>}
|
|
1606
|
+
*/
|
|
1607
|
+
async onRelatedObjectOpen(raw) {
|
|
1608
|
+
const target = await this.relatedObjectTarget(raw);
|
|
1609
|
+
if (target) {
|
|
1610
|
+
this.$router.push(target).catch(() => {});
|
|
1611
|
+
}
|
|
1612
|
+
},
|
|
1613
|
+
|
|
1614
|
+
/**
|
|
1615
|
+
* Where a related object opens: the first `type: 'detail'` page bound
|
|
1616
|
+
* to the object's register and schema, with the id in whichever param
|
|
1617
|
+
* that page's route declares. The object's `@self` names register and
|
|
1618
|
+
* schema by id and a manifest may name them by slug, so a miss on the
|
|
1619
|
+
* literal pair describes both (one request each, cached) and matches
|
|
1620
|
+
* again. Null, silently, when there is no router or the object carries
|
|
1621
|
+
* no register, schema or id (nothing to open); null with a warning,
|
|
1622
|
+
* like a dead row click, when no detail page matches or the router
|
|
1623
|
+
* lacks the route.
|
|
1624
|
+
*
|
|
1625
|
+
* @param {object} raw The clicked object.
|
|
1626
|
+
* @return {Promise<object|null>} The router location, or null.
|
|
1627
|
+
*/
|
|
1628
|
+
async relatedObjectTarget(raw) {
|
|
1629
|
+
const router = this.$router;
|
|
1630
|
+
if (!router || !raw || typeof raw !== 'object') {
|
|
1631
|
+
return null
|
|
1632
|
+
}
|
|
1633
|
+
const self = raw['@self'] || {};
|
|
1634
|
+
const id = raw.id ?? self.id ?? self.uuid ?? raw.uuid;
|
|
1635
|
+
const register = self.register ?? raw.register;
|
|
1636
|
+
const schema = self.schema ?? raw.schema;
|
|
1637
|
+
const missing = (v) => v === undefined || v === null || v === '';
|
|
1638
|
+
if (missing(id) || missing(register) || missing(schema)) {
|
|
1639
|
+
return null
|
|
1640
|
+
}
|
|
1641
|
+
let page = this.detailPageFor([String(register)], [String(schema)]);
|
|
1642
|
+
if (!page) {
|
|
1643
|
+
const [reg, sch] = await Promise.all([
|
|
1644
|
+
describeOpenRegisterEntity('registers', register),
|
|
1645
|
+
describeOpenRegisterEntity('schemas', schema),
|
|
1646
|
+
]);
|
|
1647
|
+
page = this.detailPageFor(
|
|
1648
|
+
[String(register), reg?.id, reg?.slug].filter(Boolean),
|
|
1649
|
+
[String(schema), sch?.id, sch?.slug].filter(Boolean),
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
if (!page) {
|
|
1653
|
+
// eslint-disable-next-line no-console
|
|
1654
|
+
console.warn(`[CnPageRenderer] No detail page is bound to register "${register}" and schema "${schema}", so related object "${id}" opens nowhere.`);
|
|
1655
|
+
return null
|
|
1656
|
+
}
|
|
1657
|
+
if (this.routeNameIsKnown(page.id) === false) {
|
|
1658
|
+
// eslint-disable-next-line no-console
|
|
1659
|
+
console.warn(`[CnPageRenderer] Detail page "${page.id}" is not a route the router has, so related object "${id}" opens nowhere.`);
|
|
1660
|
+
return null
|
|
1661
|
+
}
|
|
1662
|
+
const path = page.route ?? routePathFor(router, page.id);
|
|
1663
|
+
return { name: page.id, params: buildRouteParams(path, id, this.$route?.params) }
|
|
1664
|
+
},
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* The first `type: 'detail'` page whose register and schema are both
|
|
1668
|
+
* among the given names (ids or slugs, compared as strings).
|
|
1669
|
+
*
|
|
1670
|
+
* @param {Array<string>} registerNames Names the register goes by.
|
|
1671
|
+
* @param {Array<string>} schemaNames Names the schema goes by.
|
|
1672
|
+
* @return {object|null} The page, or null.
|
|
1673
|
+
*/
|
|
1674
|
+
detailPageFor(registerNames, schemaNames) {
|
|
1675
|
+
for (const page of this.detailPageByRegisterSchema.values()) {
|
|
1676
|
+
const cfg = page.config || {};
|
|
1677
|
+
if (registerNames.includes(String(cfg.register)) && schemaNames.includes(String(cfg.schema))) {
|
|
1678
|
+
return page
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
return null
|
|
1682
|
+
},
|
|
1683
|
+
|
|
1551
1684
|
/**
|
|
1552
1685
|
* Whether the router has a route by this name.
|
|
1553
1686
|
*
|