@conduction/nextcloud-vue 2.91.0 → 2.93.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 +57 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +44 -17
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +44 -2
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +140 -59
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +20 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +10 -39
- 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 +5 -3
- 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 +6 -4
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +46 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js +6 -2
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +82 -9
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +104 -90
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +9 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +355 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +462 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +39 -10
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +418 -399
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +156 -3
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +168 -52
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js +9 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js.map +1 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js +83 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js.map +1 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js +119 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js.map +1 -0
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +22 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +20 -14
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue.js +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +2 -0
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +15 -5
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue.js +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js +8 -30
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue.js +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js +63 -4
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js +19 -2
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +9 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +164 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +93 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +12 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +45 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +44 -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 +1182 -328
- package/dist/esm/utils/boardDate.js +35 -1
- package/dist/esm/utils/boardDate.js.map +1 -1
- package/dist/esm/utils/featureCatalog.js +135 -0
- package/dist/esm/utils/featureCatalog.js.map +1 -0
- package/dist/esm/utils/fillTemplate.js +84 -4
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerMeta.js +62 -12
- package/dist/esm/utils/headerMeta.js.map +1 -1
- package/dist/esm/utils/roadmapIssues.js +86 -0
- package/dist/esm/utils/roadmapIssues.js.map +1 -0
- package/dist/esm/utils/userDisplayName.js +47 -0
- package/dist/esm/utils/userDisplayName.js.map +1 -0
- 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 +15618 -13097
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1182 -328
- package/l10n/en.json +45 -1
- package/l10n/nl.json +44 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.md +2 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +66 -5
- package/src/components/CnChoiceCards/CnChoiceCards.md +27 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +168 -3
- package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -6
- package/src/components/CnDataTable/CnDataTable.vue +10 -39
- package/src/components/CnDetailPage/CnDetailPage.vue +5 -3
- package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +51 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +98 -13
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +1053 -0
- package/src/components/CnFormDialog/CnFormDialog.md +1 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +619 -562
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +338 -5
- package/src/components/CnModuleCard/CnModuleCard.vue +258 -0
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +23 -2
- package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +5 -0
- package/src/components/CnRoadmapTab/CnRoadmapTab.vue +6 -28
- package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -2
- package/src/components/CnTextWidget/CnTextWidget.vue +89 -4
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +305 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +12 -0
- package/src/css/dialog.css +47 -0
- package/src/css/look-board-detail.css +8 -0
- package/src/css/look-board.css +33 -0
- package/src/schemas/app-manifest-v2.schema.json +67 -4
- package/src/utils/boardDate.js +34 -0
- package/src/utils/featureCatalog.js +132 -0
- package/src/utils/fillTemplate.js +83 -3
- package/src/utils/headerMeta.js +62 -12
- package/src/utils/roadmapIssues.js +82 -0
- package/src/utils/userDisplayName.js +54 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -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) }}\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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"}
|
|
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<template v-if=\"cnLook === 'board'\" #icon>\n\t\t\t<BellOutline :size=\"18\" />\n\t\t</template>\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/** The app's look; `board` gives the section its navigation icon. */\n\t\tcnLook: { default: 'nextcloud' },\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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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createBlock, withCtx, createVNode, createElementBlock, createElementVNode, toDisplayString, Fragment, renderList, createCommentVNode, createTextVNode } from 'vue';
|
|
1
|
+
import { resolveComponent, openBlock, createBlock, createSlots, withCtx, createVNode, createElementBlock, createElementVNode, toDisplayString, Fragment, renderList, createCommentVNode, createTextVNode } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = {
|
|
4
4
|
key: 3,
|
|
@@ -12,10 +12,10 @@ const _hoisted_3 = {
|
|
|
12
12
|
const _hoisted_4 = { class: "cn-notif-prefs__grid" };
|
|
13
13
|
|
|
14
14
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
15
|
+
const _component_BellOutline = resolveComponent("BellOutline");
|
|
15
16
|
const _component_NcLoadingIcon = resolveComponent("NcLoadingIcon");
|
|
16
17
|
const _component_BellOffOutline = resolveComponent("BellOffOutline");
|
|
17
18
|
const _component_NcEmptyContent = resolveComponent("NcEmptyContent");
|
|
18
|
-
const _component_BellOutline = resolveComponent("BellOutline");
|
|
19
19
|
const _component_NcCheckboxRadioSwitch = resolveComponent("NcCheckboxRadioSwitch");
|
|
20
20
|
const _component_NcButton = resolveComponent("NcButton");
|
|
21
21
|
const _component_NcAppSettingsSection = resolveComponent("NcAppSettingsSection");
|
|
@@ -23,7 +23,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
23
23
|
return (openBlock(), createBlock(_component_NcAppSettingsSection, {
|
|
24
24
|
id: "notifications",
|
|
25
25
|
name: $options.t('nextcloud-vue', 'Notifications')
|
|
26
|
-
}, {
|
|
26
|
+
}, createSlots({
|
|
27
27
|
default: withCtx(() => [
|
|
28
28
|
($data.loading)
|
|
29
29
|
? (openBlock(), createBlock(_component_NcLoadingIcon, {
|
|
@@ -99,8 +99,18 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
99
99
|
}), 128 /* KEYED_FRAGMENT */))
|
|
100
100
|
]))
|
|
101
101
|
]),
|
|
102
|
-
_:
|
|
103
|
-
},
|
|
102
|
+
_: 2 /* DYNAMIC */
|
|
103
|
+
}, [
|
|
104
|
+
($options.cnLook === 'board')
|
|
105
|
+
? {
|
|
106
|
+
name: "icon",
|
|
107
|
+
fn: withCtx(() => [
|
|
108
|
+
createVNode(_component_BellOutline, { size: 18 })
|
|
109
|
+
]),
|
|
110
|
+
key: "0"
|
|
111
|
+
}
|
|
112
|
+
: undefined
|
|
113
|
+
]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["name"]))
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
export { render };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnNotificationPreferences.vue3.js","sources":["../../../../src/components/CnNotificationPreferences/CnNotificationPreferences.vue?vue&type=template&id=2a94b3e4&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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"}
|
|
1
|
+
{"version":3,"file":"CnNotificationPreferences.vue3.js","sources":["../../../../src/components/CnNotificationPreferences/CnNotificationPreferences.vue?vue&type=template&id=c746b496&lang.js"],"sourcesContent":["<template>\n\t<NcAppSettingsSection id=\"notifications\" :name=\"t('nextcloud-vue', 'Notifications')\">\n\t\t<template v-if=\"cnLook === 'board'\" #icon>\n\t\t\t<BellOutline :size=\"18\" />\n\t\t</template>\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/** The app's look; `board` gives the section its navigation icon. */\n\t\tcnLook: { default: 'nextcloud' },\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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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnRoadmapTab.vue2.js';
|
|
|
2
2
|
import { render } from './CnRoadmapTab.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-732ef80f";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import axios__default from '@nextcloud/axios';
|
|
2
1
|
import { translate } from '@nextcloud/l10n';
|
|
3
|
-
import { generateUrl } from '@nextcloud/router';
|
|
4
2
|
import { NcEmptyContent, NcLoadingIcon, NcButton } from '@nextcloud/vue';
|
|
5
3
|
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue';
|
|
6
4
|
import ClockOutline from 'vue-material-design-icons/ClockOutline.vue';
|
|
7
5
|
import InformationOutline from 'vue-material-design-icons/InformationOutline.vue';
|
|
8
|
-
import
|
|
6
|
+
import KeyOutline from 'vue-material-design-icons/KeyOutline.vue';
|
|
9
7
|
import RoadVariant from 'vue-material-design-icons/RoadVariant.vue';
|
|
10
8
|
import __sfc_main from '../CnRoadmapItem/CnRoadmapItem.vue.js';
|
|
9
|
+
import { sortByReactions, fetchRoadmapIssues } from '../../utils/roadmapIssues.js';
|
|
11
10
|
|
|
12
11
|
var script = {
|
|
13
12
|
name: 'CnRoadmapTab',
|
|
@@ -16,7 +15,7 @@ var script = {
|
|
|
16
15
|
AlertCircleOutline,
|
|
17
16
|
ClockOutline,
|
|
18
17
|
InformationOutline,
|
|
19
|
-
KeyOutline
|
|
18
|
+
KeyOutline,
|
|
20
19
|
RoadVariant,
|
|
21
20
|
NcEmptyContent,
|
|
22
21
|
NcLoadingIcon,
|
|
@@ -53,7 +52,7 @@ var script = {
|
|
|
53
52
|
|
|
54
53
|
computed: {
|
|
55
54
|
sortedItems() {
|
|
56
|
-
return
|
|
55
|
+
return sortByReactions(this.items)
|
|
57
56
|
},
|
|
58
57
|
|
|
59
58
|
disabledLabel() {
|
|
@@ -104,31 +103,10 @@ var script = {
|
|
|
104
103
|
this.loading = true;
|
|
105
104
|
this.degraded = null;
|
|
106
105
|
this.items = [];
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
repo: this.repo,
|
|
112
|
-
labels: 'enhancement,feature',
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
const data = response.data || {};
|
|
116
|
-
if (data.hint === 'github_pat_not_configured') {
|
|
117
|
-
this.degraded = 'pat_not_configured';
|
|
118
|
-
} else if (Array.isArray(data.items)) {
|
|
119
|
-
this.items = data.items;
|
|
120
|
-
}
|
|
121
|
-
} catch (e) {
|
|
122
|
-
const status = e?.response?.status;
|
|
123
|
-
const code = e?.response?.data?.error;
|
|
124
|
-
if (status === 429 || code === 'github_rate_limited' || code === 'rate_limited') {
|
|
125
|
-
this.degraded = 'rate_limited';
|
|
126
|
-
} else {
|
|
127
|
-
this.degraded = 'error';
|
|
128
|
-
}
|
|
129
|
-
} finally {
|
|
130
|
-
this.loading = false;
|
|
131
|
-
}
|
|
106
|
+
const { items, degraded } = await fetchRoadmapIssues(this.repo);
|
|
107
|
+
this.items = items;
|
|
108
|
+
this.degraded = degraded;
|
|
109
|
+
this.loading = false;
|
|
132
110
|
},
|
|
133
111
|
},
|
|
134
112
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnRoadmapTab.vue2.js","sources":["../../../../src/components/CnRoadmapTab/CnRoadmapTab.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-roadmap-tab\">\n\t\t<div v-if=\"disabled\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"disabledLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<InformationOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"loading\" class=\"cn-roadmap-tab__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'pat_not_configured'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"patNotConfiguredTitle\" :description=\"patNotConfiguredDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<KeyOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'rate_limited'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"rateLimitedTitle\" :description=\"rateLimitedDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'error'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"errorTitle\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t\t<template #action>\n\t\t\t\t\t<NcButton variant=\"primary\" @click=\"fetchItems\">\n\t\t\t\t\t\t{{ retryLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"sortedItems.length === 0\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"emptyTitle\" :description=\"emptyDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<RoadVariant :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else class=\"cn-roadmap-tab__grid\">\n\t\t\t<CnRoadmapItem\n\t\t\t\tv-for=\"item in sortedItems\"\n\t\t\t\t:key=\"item.number\"\n\t\t\t\t:item=\"item\"\n\t\t\t\tclass=\"cn-roadmap-tab__card\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * RoadmapTab — reaction-sorted GitHub issues feed. Fetches from the\n * OpenRegister `github-issue-proxy` GET endpoint on mount with the curated\n * label filter (`labels=enhancement,feature`, per openregister design D23)\n * and renders each item via `RoadmapItem`.\n *\n * Handles four documented degraded states with distinct localized messages:\n * `hint: github_pat_not_configured`, HTTP 429 `github_rate_limited`, network\n * errors, and the empty-array edge case.\n *\n * Spec: features-roadmap-component — Requirement \"RoadmapTab\".\n */\nimport
|
|
1
|
+
{"version":3,"file":"CnRoadmapTab.vue2.js","sources":["../../../../src/components/CnRoadmapTab/CnRoadmapTab.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-roadmap-tab\">\n\t\t<div v-if=\"disabled\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"disabledLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<InformationOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"loading\" class=\"cn-roadmap-tab__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'pat_not_configured'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"patNotConfiguredTitle\" :description=\"patNotConfiguredDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<KeyOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'rate_limited'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"rateLimitedTitle\" :description=\"rateLimitedDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'error'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"errorTitle\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t\t<template #action>\n\t\t\t\t\t<NcButton variant=\"primary\" @click=\"fetchItems\">\n\t\t\t\t\t\t{{ retryLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"sortedItems.length === 0\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"emptyTitle\" :description=\"emptyDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<RoadVariant :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else class=\"cn-roadmap-tab__grid\">\n\t\t\t<CnRoadmapItem\n\t\t\t\tv-for=\"item in sortedItems\"\n\t\t\t\t:key=\"item.number\"\n\t\t\t\t:item=\"item\"\n\t\t\t\tclass=\"cn-roadmap-tab__card\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * RoadmapTab — reaction-sorted GitHub issues feed. Fetches from the\n * OpenRegister `github-issue-proxy` GET endpoint on mount with the curated\n * label filter (`labels=enhancement,feature`, per openregister design D23)\n * and renders each item via `RoadmapItem`.\n *\n * Handles four documented degraded states with distinct localized messages:\n * `hint: github_pat_not_configured`, HTTP 429 `github_rate_limited`, network\n * errors, and the empty-array edge case.\n *\n * Spec: features-roadmap-component — Requirement \"RoadmapTab\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\nimport KeyOutline from 'vue-material-design-icons/KeyOutline.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport CnRoadmapItem from '../CnRoadmapItem/CnRoadmapItem.vue'\nimport { fetchRoadmapIssues, sortByReactions } from '../../utils/roadmapIssues.js'\n\nexport default {\n\tname: 'CnRoadmapTab',\n\n\tcomponents: {\n\t\tAlertCircleOutline,\n\t\tClockOutline,\n\t\tInformationOutline,\n\t\tKeyOutline,\n\t\tRoadVariant,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tNcButton,\n\t\tCnRoadmapItem,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's GitHub repository. Passed through\n\t\t * to the proxy as the `repo` query parameter.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the tab renders the disabled empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\titems: [],\n\t\t\tdegraded: null, // null | 'pat_not_configured' | 'rate_limited' | 'error'\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tsortedItems() {\n\t\t\treturn sortByReactions(this.items)\n\t\t},\n\n\t\tdisabledLabel() {\n\t\t\treturn t('nextcloud-vue', 'Roadmap is disabled by your administrator')\n\t\t},\n\n\t\tpatNotConfiguredTitle() {\n\t\t\treturn t('nextcloud-vue', 'Roadmap not yet configured')\n\t\t},\n\n\t\tpatNotConfiguredDescription() {\n\t\t\treturn t('nextcloud-vue', 'Ask your administrator to add a GitHub Personal Access Token in the OpenRegister settings to enable the roadmap.')\n\t\t},\n\n\t\trateLimitedTitle() {\n\t\t\treturn t('nextcloud-vue', 'GitHub temporarily unavailable')\n\t\t},\n\n\t\trateLimitedDescription() {\n\t\t\treturn t('nextcloud-vue', 'GitHub rate limited the request — please retry in a few minutes.')\n\t\t},\n\n\t\terrorTitle() {\n\t\t\treturn t('nextcloud-vue', 'Could not load the roadmap')\n\t\t},\n\n\t\temptyTitle() {\n\t\t\treturn t('nextcloud-vue', 'No roadmap items yet')\n\t\t},\n\n\t\temptyDescription() {\n\t\t\treturn t('nextcloud-vue', 'When the maintainers label issues \"enhancement\" or \"feature\" on GitHub they will appear here.')\n\t\t},\n\n\t\tretryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Retry')\n\t\t},\n\t},\n\n\tmounted() {\n\t\tif (!this.disabled) {\n\t\t\tthis.fetchItems()\n\t\t}\n\t},\n\n\tmethods: {\n\t\tasync fetchItems() {\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = null\n\t\t\tthis.items = []\n\t\t\tconst { items, degraded } = await fetchRoadmapIssues(this.repo)\n\t\t\tthis.items = items\n\t\t\tthis.degraded = degraded\n\t\t\tthis.loading = false\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-roadmap-tab__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n\tgap: 16px;\n}\n\n.cn-roadmap-tab__card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\ttransition: border-color 120ms ease, box-shadow 120ms ease;\n}\n\n.cn-roadmap-tab__card:hover {\n\tborder-color: var(--color-primary-element);\n\tbox-shadow: 0 2px 8px var(--color-box-shadow, rgba(0, 0, 0, 0.08));\n}\n\n.cn-roadmap-tab__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 32px;\n}\n</style>\n"],"names":["CnRoadmapItem","t"],"mappings":";;;;;;;;;;AAiFA,aAAe;AACd,CAAA,IAAI,EAAE,cAAc;;CAEpB,UAAU,EAAE;AACX,EAAA,kBAAkB;EAClB,YAAY;AACZ,EAAA,kBAAkB;EAClB,UAAU;EACV,WAAW;AACX,EAAA,cAAc;EACd,aAAa;AACb,EAAA,QAAQ;iBACRA,UAAa;EACb;;AAED,CAAA,KAAK,EAAE;AACN;;AAEC;;EAED,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;;AAED;;;AAGA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;EACD;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,OAAO,EAAE,KAAK;AACd,GAAA,KAAK,EAAE,EAAE;AACT,GAAA,QAAQ,EAAE,IAAI;AACf,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;EACT,WAAW,GAAG;AACb,GAAA,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAA;GACjC;;AAED,EAAA,aAAa,GAAG;AACf,GAAA,OAAOC,SAAC,CAAC,eAAe,EAAE,2CAA2C,CAAA;GACrE;;EAED,qBAAqB,GAAG;GACvB,OAAOA,SAAC,CAAC,eAAe,EAAE,4BAA4B,CAAA;GACtD;;EAED,2BAA2B,GAAG;GAC7B,OAAOA,SAAC,CAAC,eAAe,EAAE,kHAAkH,CAAA;GAC5I;;AAED,EAAA,gBAAgB,GAAG;GAClB,OAAOA,SAAC,CAAC,eAAe,EAAE,gCAAgC,CAAA;GAC1D;;EAED,sBAAsB,GAAG;AACxB,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,kEAAkE,CAAA;GAC5F;;EAED,UAAU,GAAG;GACZ,OAAOA,SAAC,CAAC,eAAe,EAAE,4BAA4B,CAAA;GACtD;;EAED,UAAU,GAAG;AACZ,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,sBAAsB,CAAA;GAChD;;AAED,EAAA,gBAAgB,GAAG;GAClB,OAAOA,SAAC,CAAC,eAAe,EAAE,+FAA+F,CAAA;GACzH;;EAED,UAAU,GAAG;AACZ,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA;GACjC;EACD;;AAED,CAAA,OAAO,GAAG;AACT,EAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AACnB,GAAA,IAAI,CAAC,UAAU,GAAC;AACjB,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;AACR,EAAA,MAAM,UAAU,GAAG;AAClB,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,QAAO,GAAI,KAAG;GACnB,IAAI,CAAC,KAAM,GAAE,GAAC;GACd,MAAM,EAAE,KAAK,EAAE,QAAO,KAAM,MAAM,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAA;AAC9D,GAAA,IAAI,CAAC,KAAM,GAAE,MAAI;AACjB,GAAA,IAAI,CAAC,QAAO,GAAI,SAAO;AACvB,GAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;GACnB;EACD;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnRoadmapTab.vue3.js","sources":["../../../../src/components/CnRoadmapTab/CnRoadmapTab.vue?vue&type=template&id=
|
|
1
|
+
{"version":3,"file":"CnRoadmapTab.vue3.js","sources":["../../../../src/components/CnRoadmapTab/CnRoadmapTab.vue?vue&type=template&id=732ef80f&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-roadmap-tab\">\n\t\t<div v-if=\"disabled\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"disabledLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<InformationOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"loading\" class=\"cn-roadmap-tab__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'pat_not_configured'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"patNotConfiguredTitle\" :description=\"patNotConfiguredDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<KeyOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'rate_limited'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"rateLimitedTitle\" :description=\"rateLimitedDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"degraded === 'error'\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"errorTitle\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t\t<template #action>\n\t\t\t\t\t<NcButton variant=\"primary\" @click=\"fetchItems\">\n\t\t\t\t\t\t{{ retryLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else-if=\"sortedItems.length === 0\" class=\"cn-roadmap-tab__empty\">\n\t\t\t<NcEmptyContent :name=\"emptyTitle\" :description=\"emptyDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<RoadVariant :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<div v-else class=\"cn-roadmap-tab__grid\">\n\t\t\t<CnRoadmapItem\n\t\t\t\tv-for=\"item in sortedItems\"\n\t\t\t\t:key=\"item.number\"\n\t\t\t\t:item=\"item\"\n\t\t\t\tclass=\"cn-roadmap-tab__card\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * RoadmapTab — reaction-sorted GitHub issues feed. Fetches from the\n * OpenRegister `github-issue-proxy` GET endpoint on mount with the curated\n * label filter (`labels=enhancement,feature`, per openregister design D23)\n * and renders each item via `RoadmapItem`.\n *\n * Handles four documented degraded states with distinct localized messages:\n * `hint: github_pat_not_configured`, HTTP 429 `github_rate_limited`, network\n * errors, and the empty-array edge case.\n *\n * Spec: features-roadmap-component — Requirement \"RoadmapTab\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\nimport KeyOutline from 'vue-material-design-icons/KeyOutline.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport CnRoadmapItem from '../CnRoadmapItem/CnRoadmapItem.vue'\nimport { fetchRoadmapIssues, sortByReactions } from '../../utils/roadmapIssues.js'\n\nexport default {\n\tname: 'CnRoadmapTab',\n\n\tcomponents: {\n\t\tAlertCircleOutline,\n\t\tClockOutline,\n\t\tInformationOutline,\n\t\tKeyOutline,\n\t\tRoadVariant,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tNcButton,\n\t\tCnRoadmapItem,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's GitHub repository. Passed through\n\t\t * to the proxy as the `repo` query parameter.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the tab renders the disabled empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\titems: [],\n\t\t\tdegraded: null, // null | 'pat_not_configured' | 'rate_limited' | 'error'\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tsortedItems() {\n\t\t\treturn sortByReactions(this.items)\n\t\t},\n\n\t\tdisabledLabel() {\n\t\t\treturn t('nextcloud-vue', 'Roadmap is disabled by your administrator')\n\t\t},\n\n\t\tpatNotConfiguredTitle() {\n\t\t\treturn t('nextcloud-vue', 'Roadmap not yet configured')\n\t\t},\n\n\t\tpatNotConfiguredDescription() {\n\t\t\treturn t('nextcloud-vue', 'Ask your administrator to add a GitHub Personal Access Token in the OpenRegister settings to enable the roadmap.')\n\t\t},\n\n\t\trateLimitedTitle() {\n\t\t\treturn t('nextcloud-vue', 'GitHub temporarily unavailable')\n\t\t},\n\n\t\trateLimitedDescription() {\n\t\t\treturn t('nextcloud-vue', 'GitHub rate limited the request — please retry in a few minutes.')\n\t\t},\n\n\t\terrorTitle() {\n\t\t\treturn t('nextcloud-vue', 'Could not load the roadmap')\n\t\t},\n\n\t\temptyTitle() {\n\t\t\treturn t('nextcloud-vue', 'No roadmap items yet')\n\t\t},\n\n\t\temptyDescription() {\n\t\t\treturn t('nextcloud-vue', 'When the maintainers label issues \"enhancement\" or \"feature\" on GitHub they will appear here.')\n\t\t},\n\n\t\tretryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Retry')\n\t\t},\n\t},\n\n\tmounted() {\n\t\tif (!this.disabled) {\n\t\t\tthis.fetchItems()\n\t\t}\n\t},\n\n\tmethods: {\n\t\tasync fetchItems() {\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = null\n\t\t\tthis.items = []\n\t\t\tconst { items, degraded } = await fetchRoadmapIssues(this.repo)\n\t\t\tthis.items = items\n\t\t\tthis.degraded = degraded\n\t\t\tthis.loading = false\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-roadmap-tab__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n\tgap: 16px;\n}\n\n.cn-roadmap-tab__card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\ttransition: border-color 120ms ease, box-shadow 120ms ease;\n}\n\n.cn-roadmap-tab__card:hover {\n\tborder-color: var(--color-primary-element);\n\tbox-shadow: 0 2px 8px var(--color-box-shadow, rgba(0, 0, 0, 0.08));\n}\n\n.cn-roadmap-tab__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 32px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_Fragment","_renderList","_createBlock"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnTabsWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnTabsWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-febbfc68";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -12,7 +12,7 @@ import { resolveTabCount } from '../../utils/detailActionModel.js';
|
|
|
12
12
|
import { fetchListTotal } from '../../utils/fetchFilterCounts.js';
|
|
13
13
|
import { isActivityWidget } from '../../utils/headerMeta.js';
|
|
14
14
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js';
|
|
15
|
-
import { isLocallyDecidableVisibleWhen, evaluateVisibleWhenLocal, evaluateVisibleWhen } from '../../utils/visibleWhen.js';
|
|
15
|
+
import { isLocallyDecidableVisibleWhen, isWorkspaceCondition, evaluateWorkspaceCondition, evaluateVisibleWhenLocal, evaluateVisibleWhen } from '../../utils/visibleWhen.js';
|
|
16
16
|
import { widgetTitleOf } from '../../utils/widgetDispatch.js';
|
|
17
17
|
import __sfc_main from '../CnActionsMenu/CnActionsMenu.vue.js';
|
|
18
18
|
|
|
@@ -156,6 +156,13 @@ var script = {
|
|
|
156
156
|
* `visibleWhen` reads them as `@context.<name>`. Null on any other page.
|
|
157
157
|
*/
|
|
158
158
|
cnDetailVisibilityRows: { default: null },
|
|
159
|
+
/**
|
|
160
|
+
* The page's workspace bag (CnDashboardPage provides it), which a
|
|
161
|
+
* page control such as a chips filter writes. A widget in a stack is
|
|
162
|
+
* left out while its workspace `visibleWhen` does not hold. Null off a
|
|
163
|
+
* dashboard.
|
|
164
|
+
*/
|
|
165
|
+
cnWorkspaceContext: { default: null },
|
|
159
166
|
/**
|
|
160
167
|
* The host's translate (CnAppRoot provides `cnTranslate`), so a tab
|
|
161
168
|
* label written in the manifest reads in the user's language.
|
|
@@ -413,7 +420,8 @@ var script = {
|
|
|
413
420
|
// widget is skipped. The first that resolves is the panel's.
|
|
414
421
|
const ids = this.tabWidgetIds(tab);
|
|
415
422
|
const widgets = ids.length > 1
|
|
416
|
-
? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null)
|
|
423
|
+
? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null)
|
|
424
|
+
.filter((w) => w && !this.hiddenByWorkspace(w))
|
|
417
425
|
: [];
|
|
418
426
|
const widget = ids.length > 1
|
|
419
427
|
? (widgets[0] || null)
|
|
@@ -662,6 +670,29 @@ var script = {
|
|
|
662
670
|
},
|
|
663
671
|
|
|
664
672
|
methods: {
|
|
673
|
+
/**
|
|
674
|
+
* Whether a page control hides a widget of a stack right now: its
|
|
675
|
+
* `visibleWhen` (on the widget, its content or its props) is a
|
|
676
|
+
* workspace condition that does not hold. Off a dashboard there is no
|
|
677
|
+
* workspace, and nothing is hidden. Other conditions are not looked at
|
|
678
|
+
* here.
|
|
679
|
+
*
|
|
680
|
+
* @param {object} widget The widget definition.
|
|
681
|
+
* @return {boolean} True when the widget must not draw.
|
|
682
|
+
* @spec openspec/changes/tabs-stack-follows-page-controls/specs/tabs-widget/spec.md#requirement-a-widget-in-a-tab-stack-follows-the-page-controls
|
|
683
|
+
*/
|
|
684
|
+
hiddenByWorkspace(widget) {
|
|
685
|
+
const cond = widget.visibleWhen || widget.content?.visibleWhen || widget.props?.visibleWhen || null;
|
|
686
|
+
if (!isWorkspaceCondition(cond)) {
|
|
687
|
+
return false
|
|
688
|
+
}
|
|
689
|
+
const bag = this.cnWorkspaceContext;
|
|
690
|
+
if (!bag || typeof bag !== 'object') {
|
|
691
|
+
return false
|
|
692
|
+
}
|
|
693
|
+
return !evaluateWorkspaceCondition(cond, 'value' in bag ? bag.value : bag)
|
|
694
|
+
},
|
|
695
|
+
|
|
665
696
|
/**
|
|
666
697
|
* The grid a stack lays its widgets out in, as one column span (of 12)
|
|
667
698
|
* per widget in order, or null for the plain vertical stack. A tab says
|