@conduction/nextcloud-vue 2.104.0 → 2.105.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/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +59 -1
- 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/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +54 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +3 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +4 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +4 -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 +566 -295
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/nextcloudApps.js +45 -0
- package/dist/esm/utils/nextcloudApps.js.map +1 -0
- package/dist/esm/utils/scopeListLayout.js +9 -2
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +12 -4
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +3054 -2517
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +566 -295
- package/l10n/en.json +4 -1
- package/l10n/nl.json +4 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
- package/src/components/CnChartWidget/CnChartWidget.md +2 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
- package/src/components/CnDetailPage/CnDetailPage.md +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +59 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
- package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
- package/src/components/CnIndexPage/CnIndexPage.md +1 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +54 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +3 -2
- package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
- package/src/components/CnNoticeWidget/index.js +12 -0
- package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/index.js +1 -0
- package/src/css/dialog.css +20 -2
- package/src/css/look-board-detail.css +77 -4
- package/src/css/look-board-index.css +36 -2
- package/src/css/look-board.css +97 -12
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +36 -3
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/nextcloudApps.js +50 -0
- package/src/utils/scopeListLayout.js +9 -2
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +12 -4
|
@@ -2,7 +2,7 @@ import script from './CnFeaturesAndRoadmapView.vue2.js';
|
|
|
2
2
|
import { render } from './CnFeaturesAndRoadmapView.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-2dd578ca";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -346,7 +346,7 @@ var script = {
|
|
|
346
346
|
* @return {object}
|
|
347
347
|
*/
|
|
348
348
|
boardOptions() {
|
|
349
|
-
const keys = ['layout', 'tabs', 'filter', 'statusLabels', 'next', 'issueLabels', 'subtitle', 'notice', 'noticeLink', 'summaries', 'documentationButton', 'suggestButton'];
|
|
349
|
+
const keys = ['layout', 'tabs', 'filter', 'statusLabels', 'next', 'issueLabels', 'subtitle', 'notice', 'noticeLink', 'summaries', 'documentationButton', 'suggestButton', 'actionsMenu'];
|
|
350
350
|
const options = {};
|
|
351
351
|
for (const key of keys) {
|
|
352
352
|
if (this.board && this.board[key] !== undefined && this.board[key] !== null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFeaturesAndRoadmapView.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<CnFeaturesBoard\n\t\t\tv-else-if=\"isBoardLook\"\n\t\t\t:features=\"features\"\n\t\t\t:categories=\"categories\"\n\t\t\t:repo=\"repo\"\n\t\t\t:title=\"title\"\n\t\t\t:appVersion=\"appVersion\"\n\t\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t\tv-bind=\"boardOptions\"\n\t\t\t@support=\"openSupportDialog\" />\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\t\t</template>\n\t\t<CnSupportDialog\n\t\t\tv-if=\"showSupportDialog && !disabled\"\n\t\t\t:appName=\"resolvedAppName\"\n\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t:founderName=\"founderName\"\n\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t@close=\"showSupportDialog = false\" />\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 * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesBoard from '../CnFeaturesBoard/CnFeaturesBoard.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesBoard,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t\t/** The app's look, provided by CnAppRoot (`nextcloud` or `board`). */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\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 * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Feature category key to caption, in the order the board look shows\n\t\t * the groups. A feature names its group with `category`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Board look options, passed to CnFeaturesBoard: `layout` (`cards` or\n\t\t * `rows`), `tabs`, `filter` (`chips` or `segmented`), `statusLabels`,\n\t\t * `next`, `issueLabels`, `subtitle`, `notice`, `noticeLink`,\n\t\t * `summaries`, `documentationButton` and `suggestButton`. Ignored in the default look.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tboard: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Page title in the board look. Empty reads \"Features and roadmap\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The app version, for `{appVersion}` in the board look's subtitle.\n\t\t */\n\t\tappVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.\n\t\t * A development build warns once when it is set.\n\t\t *\n\t\t * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the page is drawn in the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * The board options CnFeaturesBoard declares, so an unknown key in the\n\t\t * manifest never lands on its root element as an attribute.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tboardOptions() {\n\t\t\tconst keys = ['layout', 'tabs', 'filter', 'statusLabels', 'next', 'issueLabels', 'subtitle', 'notice', 'noticeLink', 'summaries', 'documentationButton', 'suggestButton']\n\t\t\tconst options = {}\n\t\t\tfor (const key of keys) {\n\t\t\t\tif (this.board && this.board[key] !== undefined && this.board[key] !== null) {\n\t\t\t\t\toptions[key] = this.board[key]\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t\tif (this.capabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`')\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The board look has no right-hand column: its links sit in the\n\t\t\t// board's Actions menu.\n\t\t\tif (this.disabled || this.isBoardLook) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["CnCapabilityTable","CnFeaturesBoard","CnFeaturesTab","CnRoadmapTab","CnSupportDialog","t","CnFeaturesAndRoadmapSidebar"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFeaturesAndRoadmapView.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<CnFeaturesBoard\n\t\t\tv-else-if=\"isBoardLook\"\n\t\t\t:features=\"features\"\n\t\t\t:categories=\"categories\"\n\t\t\t:repo=\"repo\"\n\t\t\t:title=\"title\"\n\t\t\t:appVersion=\"appVersion\"\n\t\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t\tv-bind=\"boardOptions\"\n\t\t\t@support=\"openSupportDialog\" />\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\t\t</template>\n\t\t<CnSupportDialog\n\t\t\tv-if=\"showSupportDialog && !disabled\"\n\t\t\t:appName=\"resolvedAppName\"\n\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t:founderName=\"founderName\"\n\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t@close=\"showSupportDialog = false\" />\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 * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesBoard from '../CnFeaturesBoard/CnFeaturesBoard.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesBoard,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t\t/** The app's look, provided by CnAppRoot (`nextcloud` or `board`). */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\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 * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Feature category key to caption, in the order the board look shows\n\t\t * the groups. A feature names its group with `category`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Board look options, passed to CnFeaturesBoard: `layout` (`cards` or\n\t\t * `rows`), `tabs`, `filter` (`chips` or `segmented`), `statusLabels`,\n\t\t * `next`, `issueLabels`, `subtitle`, `notice`, `noticeLink`,\n\t\t * `summaries`, `documentationButton` and `suggestButton`. Ignored in the default look.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tboard: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Page title in the board look. Empty reads \"Features and roadmap\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The app version, for `{appVersion}` in the board look's subtitle.\n\t\t */\n\t\tappVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.\n\t\t * A development build warns once when it is set.\n\t\t *\n\t\t * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the page is drawn in the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * The board options CnFeaturesBoard declares, so an unknown key in the\n\t\t * manifest never lands on its root element as an attribute.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tboardOptions() {\n\t\t\tconst keys = ['layout', 'tabs', 'filter', 'statusLabels', 'next', 'issueLabels', 'subtitle', 'notice', 'noticeLink', 'summaries', 'documentationButton', 'suggestButton', 'actionsMenu']\n\t\t\tconst options = {}\n\t\t\tfor (const key of keys) {\n\t\t\t\tif (this.board && this.board[key] !== undefined && this.board[key] !== null) {\n\t\t\t\t\toptions[key] = this.board[key]\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t\tif (this.capabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`')\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The board look has no right-hand column: its links sit in the\n\t\t\t// board's Actions menu.\n\t\t\tif (this.disabled || this.isBoardLook) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["CnCapabilityTable","CnFeaturesBoard","CnFeaturesTab","CnRoadmapTab","CnSupportDialog","t","CnFeaturesAndRoadmapSidebar"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFeaturesAndRoadmapView.vue3.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue?vue&type=template&id=3db485a4&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<CnFeaturesBoard\n\t\t\tv-else-if=\"isBoardLook\"\n\t\t\t:features=\"features\"\n\t\t\t:categories=\"categories\"\n\t\t\t:repo=\"repo\"\n\t\t\t:title=\"title\"\n\t\t\t:appVersion=\"appVersion\"\n\t\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t\tv-bind=\"boardOptions\"\n\t\t\t@support=\"openSupportDialog\" />\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\t\t</template>\n\t\t<CnSupportDialog\n\t\t\tv-if=\"showSupportDialog && !disabled\"\n\t\t\t:appName=\"resolvedAppName\"\n\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t:founderName=\"founderName\"\n\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t@close=\"showSupportDialog = false\" />\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 * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesBoard from '../CnFeaturesBoard/CnFeaturesBoard.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesBoard,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t\t/** The app's look, provided by CnAppRoot (`nextcloud` or `board`). */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\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 * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Feature category key to caption, in the order the board look shows\n\t\t * the groups. A feature names its group with `category`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Board look options, passed to CnFeaturesBoard: `layout` (`cards` or\n\t\t * `rows`), `tabs`, `filter` (`chips` or `segmented`), `statusLabels`,\n\t\t * `next`, `issueLabels`, `subtitle`, `notice`, `noticeLink`,\n\t\t * `summaries`, `documentationButton` and `suggestButton`. Ignored in the default look.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tboard: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Page title in the board look. Empty reads \"Features and roadmap\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The app version, for `{appVersion}` in the board look's subtitle.\n\t\t */\n\t\tappVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.\n\t\t * A development build warns once when it is set.\n\t\t *\n\t\t * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the page is drawn in the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * The board options CnFeaturesBoard declares, so an unknown key in the\n\t\t * manifest never lands on its root element as an attribute.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tboardOptions() {\n\t\t\tconst keys = ['layout', 'tabs', 'filter', 'statusLabels', 'next', 'issueLabels', 'subtitle', 'notice', 'noticeLink', 'summaries', 'documentationButton', 'suggestButton']\n\t\t\tconst options = {}\n\t\t\tfor (const key of keys) {\n\t\t\t\tif (this.board && this.board[key] !== undefined && this.board[key] !== null) {\n\t\t\t\t\toptions[key] = this.board[key]\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t\tif (this.capabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`')\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The board look has no right-hand column: its links sit in the\n\t\t\t// board's Actions menu.\n\t\t\tif (this.disabled || this.isBoardLook) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createBlock","_mergeProps","_Fragment","_createElementVNode","_toDisplayString","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFeaturesAndRoadmapView.vue3.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue?vue&type=template&id=2dd578ca&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<CnFeaturesBoard\n\t\t\tv-else-if=\"isBoardLook\"\n\t\t\t:features=\"features\"\n\t\t\t:categories=\"categories\"\n\t\t\t:repo=\"repo\"\n\t\t\t:title=\"title\"\n\t\t\t:appVersion=\"appVersion\"\n\t\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t\tv-bind=\"boardOptions\"\n\t\t\t@support=\"openSupportDialog\" />\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\t\t</template>\n\t\t<CnSupportDialog\n\t\t\tv-if=\"showSupportDialog && !disabled\"\n\t\t\t:appName=\"resolvedAppName\"\n\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t:founderName=\"founderName\"\n\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t@close=\"showSupportDialog = false\" />\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 * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesBoard from '../CnFeaturesBoard/CnFeaturesBoard.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesBoard,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t\t/** The app's look, provided by CnAppRoot (`nextcloud` or `board`). */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\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 * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Feature category key to caption, in the order the board look shows\n\t\t * the groups. A feature names its group with `category`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Board look options, passed to CnFeaturesBoard: `layout` (`cards` or\n\t\t * `rows`), `tabs`, `filter` (`chips` or `segmented`), `statusLabels`,\n\t\t * `next`, `issueLabels`, `subtitle`, `notice`, `noticeLink`,\n\t\t * `summaries`, `documentationButton` and `suggestButton`. Ignored in the default look.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tboard: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Page title in the board look. Empty reads \"Features and roadmap\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The app version, for `{appVersion}` in the board look's subtitle.\n\t\t */\n\t\tappVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.\n\t\t * A development build warns once when it is set.\n\t\t *\n\t\t * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the page is drawn in the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * The board options CnFeaturesBoard declares, so an unknown key in the\n\t\t * manifest never lands on its root element as an attribute.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tboardOptions() {\n\t\t\tconst keys = ['layout', 'tabs', 'filter', 'statusLabels', 'next', 'issueLabels', 'subtitle', 'notice', 'noticeLink', 'summaries', 'documentationButton', 'suggestButton', 'actionsMenu']\n\t\t\tconst options = {}\n\t\t\tfor (const key of keys) {\n\t\t\t\tif (this.board && this.board[key] !== undefined && this.board[key] !== null) {\n\t\t\t\t\toptions[key] = this.board[key]\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t\tif (this.capabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`')\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The board look has no right-hand column: its links sit in the\n\t\t\t// board's Actions menu.\n\t\t\tif (this.disabled || this.isBoardLook) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createBlock","_mergeProps","_Fragment","_createElementVNode","_toDisplayString","_createTextVNode"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnFeaturesBoard.vue2.js';
|
|
|
2
2
|
import { render } from './CnFeaturesBoard.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-022166a7";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -85,6 +85,8 @@ var script = {
|
|
|
85
85
|
|
|
86
86
|
/** Show the Features and Roadmap folder tabs. */
|
|
87
87
|
tabs: { type: Boolean, default: true },
|
|
88
|
+
/** Show the Actions menu (suggest, buildiq, AI skills, support), in the header or the tab row; false draws none. */
|
|
89
|
+
actionsMenu: { type: Boolean, default: true },
|
|
88
90
|
/** `chips`: pill buttons with a count. `segmented`: a segmented control without counts. */
|
|
89
91
|
filter: {
|
|
90
92
|
type: String,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFeaturesBoard.vue2.js","sources":["../../../../src/components/CnFeaturesBoard/CnFeaturesBoard.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-features-board\" :class=\"`cn-features-board--${layout}`\" data-testid=\"cn-features-board\">\n\t\t<header class=\"cn-features-board__header\">\n\t\t\t<div class=\"cn-features-board__title-row\">\n\t\t\t\t<h1 class=\"cn-features-board__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h1>\n\t\t\t\t<div class=\"cn-features-board__header-actions\">\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"documentationButton && documentationUrl\"\n\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t:href=\"documentationUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-docs\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<BookOpenVariant :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Documentation') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<CnBuildiqEditButton />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"suggestUrl && suggestButton\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"suggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-suggest\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Suggest feature') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcActions\n\t\t\t\t\t\tv-if=\"!tabs\"\n\t\t\t\t\t\t:menuName=\"t('nextcloud-vue', 'Actions')\"\n\t\t\t\t\t\t:forceName=\"true\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-actions\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<DotsHorizontal :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-for=\"action in menuLinks\"\n\t\t\t\t\t\t\t:key=\"action.key\"\n\t\t\t\t\t\t\t:href=\"action.href\"\n\t\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionButton @click=\"onSupport\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Support this app') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<p v-if=\"resolvedSubtitle\" class=\"cn-features-board__subtitle\" data-testid=\"cn-features-board-subtitle\">\n\t\t\t\t{{ resolvedSubtitle }}\n\t\t\t</p>\n\t\t</header>\n\n\t\t<div v-if=\"tabs\" class=\"cn-features-board__tab-row\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-features-board__tabs\"\n\t\t\t\trole=\"tablist\"\n\t\t\t\t:aria-label=\"resolvedTitle\"\n\t\t\t\t@keydown=\"onTabKeydown\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"tab in tabList\"\n\t\t\t\t\t:id=\"`${uid}-tab-${tab.key}`\"\n\t\t\t\t\t:key=\"tab.key\"\n\t\t\t\t\tref=\"tabButtons\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\tclass=\"cn-features-board__tab\"\n\t\t\t\t\t:class=\"{ 'cn-features-board__tab--active': activeTab === tab.key }\"\n\t\t\t\t\t:aria-selected=\"activeTab === tab.key ? 'true' : 'false'\"\n\t\t\t\t\t:aria-controls=\"`${uid}-panel`\"\n\t\t\t\t\t:tabindex=\"activeTab === tab.key ? 0 : -1\"\n\t\t\t\t\t:data-testid=\"`cn-features-board-tab-${tab.key}`\"\n\t\t\t\t\t@click=\"activeTab = tab.key\">\n\t\t\t\t\t{{ tab.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<NcActions\n\t\t\t\t:menuName=\"t('nextcloud-vue', 'Actions')\"\n\t\t\t\t:forceName=\"true\"\n\t\t\t\tclass=\"cn-features-board__tab-actions\"\n\t\t\t\tdata-testid=\"cn-features-board-actions\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<DotsHorizontal :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t<NcActionLink\n\t\t\t\t\tv-for=\"action in menuLinks\"\n\t\t\t\t\t:key=\"action.key\"\n\t\t\t\t\t:href=\"action.href\"\n\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton @click=\"onSupport\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Support this app') }}\n\t\t\t\t</NcActionButton>\n\t\t\t</NcActions>\n\t\t</div>\n\n\t\t<div\n\t\t\t:id=\"`${uid}-panel`\"\n\t\t\tclass=\"cn-features-board__panel\"\n\t\t\t:class=\"{ 'cn-features-board__panel--tabbed': tabs }\"\n\t\t\t:role=\"tabs ? 'tabpanel' : null\"\n\t\t\t:aria-labelledby=\"tabs ? `${uid}-tab-${activeTab}` : null\">\n\t\t\t<CnRoadmapTab v-if=\"tabs && activeTab === 'roadmap'\" :repo=\"repo\" />\n\t\t\t<template v-else>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"cn-features-board__filter\"\n\t\t\t\t\t:class=\"`cn-features-board__filter--${filter}`\"\n\t\t\t\t\trole=\"group\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Status')\"\n\t\t\t\t\tdata-testid=\"cn-features-board-filter\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"option in filterOptions\"\n\t\t\t\t\t\t:key=\"option.key\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-features-board__filter-option\"\n\t\t\t\t\t\t:class=\"{ 'cn-features-board__filter-option--active': status === option.key }\"\n\t\t\t\t\t\t:aria-pressed=\"status === option.key ? 'true' : 'false'\"\n\t\t\t\t\t\t:data-status=\"option.key\"\n\t\t\t\t\t\t@click=\"status = option.key\">\n\t\t\t\t\t\t{{ option.label }}<span\n\t\t\t\t\t\t\tv-if=\"filter === 'chips'\"\n\t\t\t\t\t\t\tclass=\"cn-features-board__filter-count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-features-board-filter-count\">{{ ' ' + option.count }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\n\t\t\t\t<div class=\"cn-features-board__body\">\n\t\t\t\t\t<div class=\"cn-features-board__groups\" :class=\"`cn-features-board__groups--${layout}`\">\n\t\t\t\t\t\t<p v-if=\"shownGroups.length === 0\" class=\"cn-features-board__empty\" data-testid=\"cn-features-board-empty\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No features with this status') }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<section\n\t\t\t\t\t\t\tv-for=\"group in shownGroups\"\n\t\t\t\t\t\t\t:key=\"group.key\"\n\t\t\t\t\t\t\tclass=\"cn-features-board__group\"\n\t\t\t\t\t\t\t:aria-labelledby=\"group.label ? `${uid}-group-${group.key}` : null\"\n\t\t\t\t\t\t\t:aria-label=\"group.label ? null : resolvedTitle\"\n\t\t\t\t\t\t\tdata-testid=\"cn-features-board-group\"\n\t\t\t\t\t\t\t:data-group=\"group.key\">\n\t\t\t\t\t\t\t<div v-if=\"group.label\" class=\"cn-features-board__group-head\">\n\t\t\t\t\t\t\t\t<h2 :id=\"`${uid}-group-${group.key}`\" class=\"cn-features-board__group-title\">\n\t\t\t\t\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"layout === 'cards'\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__group-caption\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-group-caption\">{{ groupCaption(group) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"layout === 'cards'\" class=\"cn-features-board__cards\">\n\t\t\t\t\t\t\t\t<article\n\t\t\t\t\t\t\t\t\tv-for=\"feature in group.shown\"\n\t\t\t\t\t\t\t\t\t:key=\"feature.slug\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__card\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-feature\">\n\t\t\t\t\t\t\t\t\t<div class=\"cn-features-board__card-head\">\n\t\t\t\t\t\t\t\t\t\t<h3 class=\"cn-features-board__card-title\">\n\t\t\t\t\t\t\t\t\t\t\t{{ feature.title }}\n\t\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\t\t\t:label=\"statusText(feature)\"\n\t\t\t\t\t\t\t\t\t\t\t:variant=\"variantOf(feature.status)\"\n\t\t\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-status\" />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<p v-if=\"feature.summary\" class=\"cn-features-board__card-summary\">\n\t\t\t\t\t\t\t\t\t\t{{ feature.summary }}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t<div v-if=\"readMoreHref(feature)\" class=\"cn-features-board__card-footer\">\n\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__read-more\"\n\t\t\t\t\t\t\t\t\t\t\t:href=\"readMoreHref(feature)\"\n\t\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Learn more about {title}', { title: feature.title })\">\n\t\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Learn more') }}\n\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</article>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<ul v-else class=\"cn-features-board__rows\">\n\t\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\t\tv-for=\"feature in group.shown\"\n\t\t\t\t\t\t\t\t\t:key=\"feature.slug\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__row\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-feature\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-features-board__row-text\">\n\t\t\t\t\t\t\t\t\t\t<span class=\"cn-features-board__row-title\">{{ feature.title }}</span>\n\t\t\t\t\t\t\t\t\t\t<span v-if=\"summaries && feature.summary\" class=\"cn-features-board__row-summary\">{{ feature.summary }}</span>\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\t\t:label=\"statusText(feature)\"\n\t\t\t\t\t\t\t\t\t\t:variant=\"variantOf(feature.status)\"\n\t\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-status\" />\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t</section>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<aside\n\t\t\t\t\t\tv-if=\"next > 0 && repo\"\n\t\t\t\t\t\tclass=\"cn-features-board__next\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Next on the roadmap')\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-next\">\n\t\t\t\t\t\t<section class=\"cn-features-board__next-card\">\n\t\t\t\t\t\t\t<div class=\"cn-features-board__next-head\">\n\t\t\t\t\t\t\t\t<RoadVariant :size=\"20\" />\n\t\t\t\t\t\t\t\t<h2 class=\"cn-features-board__next-title\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Coming next') }}\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p class=\"cn-features-board__next-lead\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'The most requested items from the issue tracker.') }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<ol v-if=\"nextIssues.length > 0\" class=\"cn-features-board__next-list\">\n\t\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\t\tv-for=\"issue in nextIssues\"\n\t\t\t\t\t\t\t\t\t:key=\"issue.number\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-item\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-next-item\">\n\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-link\"\n\t\t\t\t\t\t\t\t\t\t:href=\"safeHref(issue.html_url)\"\n\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\">{{ issue.title }}</a>\n\t\t\t\t\t\t\t\t\t<div class=\"cn-features-board__next-meta\">\n\t\t\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\t\t\tv-if=\"issue.status\"\n\t\t\t\t\t\t\t\t\t\t\t:label=\"statusLabel(issue.status)\"\n\t\t\t\t\t\t\t\t\t\t\t:variant=\"variantOf(issue.status)\" />\n\t\t\t\t\t\t\t\t\t\t<span v-if=\"issue.opened\" class=\"cn-features-board__next-opened\">\n\t\t\t\t\t\t\t\t\t\t\t<CalendarBlankOutline :size=\"14\" />{{ t('nextcloud-vue', 'Opened {date}', { date: issue.opened }) }}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<span>#{{ issue.number }}</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t</ol>\n\t\t\t\t\t\t\t<p v-else-if=\"!issuesLoading\" class=\"cn-features-board__next-lead\" data-testid=\"cn-features-board-next-empty\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No roadmap items yet') }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\tv-if=\"tabs\"\n\t\t\t\t\t\t\t\thref=\"#\"\n\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-more\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-whole-roadmap\"\n\t\t\t\t\t\t\t\t@click.prevent=\"activeTab = 'roadmap'\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'See the whole roadmap') }}<ChevronRight :size=\"16\" />\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\tv-if=\"documentationUrl\"\n\t\t\t\t\t\t\t\t:href=\"documentationUrl\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-more\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Documentation') }}<ChevronRight :size=\"16\" />\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</section>\n\t\t\t\t\t</aside>\n\t\t\t\t</div>\n\n\t\t\t\t<NcNoteCard v-if=\"notice\"\n\t\t\t\t\ttype=\"info\"\n\t\t\t\t\tclass=\"cn-features-board__notice\"\n\t\t\t\t\tdata-testid=\"cn-features-board-notice\">\n\t\t\t\t\t<template v-if=\"noticeParts.link\">\n\t\t\t\t\t\t{{ noticeParts.before }}<a\n\t\t\t\t\t\t\t:href=\"noticeParts.link.href\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tdata-testid=\"cn-features-board-notice-link\">{{ noticeParts.link.text }}</a>{{ noticeParts.after }}\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t{{ noticeParts.before }}\n\t\t\t\t\t</template>\n\t\t\t\t</NcNoteCard>\n\t\t\t</template>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { getLanguage, translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcActions, NcButton, NcNoteCard } from '@nextcloud/vue'\nimport BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'\nimport CalendarBlankOutline from 'vue-material-design-icons/CalendarBlankOutline.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { formatBoardDate } from '../../utils/boardDate.js'\nimport { countByStatus, FEATURE_STATUS_VARIANTS, FEATURE_STATUSES, groupFeatures } from '../../utils/featureCatalog.js'\nimport { fetchRoadmapIssues, issueStatus } from '../../utils/roadmapIssues.js'\nimport { safeHref } from '../../utils/safeHref.js'\n\nlet featuresBoardUid = 0\n\n/**\n * Read the reader's language, which @nextcloud/l10n throws on outside a\n * Nextcloud page.\n *\n * @return {string} The language tag, or ''.\n */\nfunction readerLanguage() {\n\ttry {\n\t\treturn getLanguage() || ''\n\t} catch {\n\t\treturn ''\n\t}\n}\n\n/**\n * CnFeaturesBoard - the Features and roadmap page in the board look.\n *\n * A title with a subtitle, optional folder tabs (Features, Roadmap), a status\n * filter (count chips or a segmented control), the features grouped by\n * category (as two-up cards or as rows inside one card per group), an\n * optional \"Next\" card with the most requested forge issues, and an optional\n * notice under the groups. The links the default look puts in its right-hand\n * sidebar (suggest, buildiq, AI skills, support) sit in an Actions menu.\n *\n * Rendered by CnFeaturesAndRoadmapView when the page is in the board look.\n *\n * @spec openspec/changes/screens-roadmap-board-look/specs/features-roadmap-board-look/spec.md\n *\n * @event support Emitted when the reader picks \"Support this app\" in the Actions menu.\n */\nexport default {\n\tname: 'CnFeaturesBoard',\n\n\tcomponents: {\n\t\tBookOpenVariant,\n\t\tCalendarBlankOutline,\n\t\tChevronRight,\n\t\tCnBuildiqEditButton,\n\t\tCnRoadmapTab,\n\t\tCnStatusBadge,\n\t\tDotsHorizontal,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcActions,\n\t\tNcButton,\n\t\tNcNoteCard,\n\t\tPlus,\n\t\tRoadVariant,\n\t},\n\n\tinject: {\n\t\t/** The app's translate function, provided by CnAppRoot. Identity outside one. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The features: `{ slug, title, summary, status, category, expected, docsUrl, title_<lang>, summary_<lang> }`. */\n\t\tfeatures: { type: Array, default: () => [] },\n\t\t/** Category key to caption; sets the order of the groups. */\n\t\tcategories: { type: Object, default: () => ({}) },\n\t\t/** `cards`: a section per category with two-up feature cards. `rows`: one card per category with a row per feature. */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: 'cards',\n\t\t\tvalidator: (value) => ['cards', 'rows'].includes(value),\n\t\t},\n\n\t\t/** Show the Features and Roadmap folder tabs. */\n\t\ttabs: { type: Boolean, default: true },\n\t\t/** `chips`: pill buttons with a count. `segmented`: a segmented control without counts. */\n\t\tfilter: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (value) => ['chips', 'segmented'].includes(value),\n\t\t},\n\n\t\t/** Overrides of the status labels, by key (`all`, `available`, `beta`, `in_progress`, `planned`). */\n\t\tstatusLabels: { type: Object, default: () => ({}) },\n\t\t/** How many issues the Next card lists; 0 hides the card. */\n\t\tnext: { type: Number, default: 3 },\n\t\t/** Forge label to issue status (`in_progress`, `planned`) for the badges in the Next card. */\n\t\tissueLabels: { type: Object, default: () => ({}) },\n\t\t/** `<owner>/<repo>` on the forge, for the Roadmap tab and the Next card. */\n\t\trepo: { type: String, default: '' },\n\t\t/** Page title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Subtitle template; `{count}` is the number of features and `{appVersion}` the app version. */\n\t\tsubtitle: { type: String, default: '' },\n\t\t/** The app version, for `{appVersion}` in the subtitle. */\n\t\tappVersion: { type: String, default: '' },\n\t\t/** An info notice under the groups. */\n\t\tnotice: { type: String, default: '' },\n\t\t/** The app's documentation site: the Next card's link and the target of \"Learn more\". */\n\t\tdocumentationUrl: { type: String, default: '' },\n\t\t/** Show a Documentation button in the header. */\n\t\tdocumentationButton: { type: Boolean, default: false },\n\t\t/** The feature-request form; the header's primary button. */\n\t\tsuggestUrl: { type: String, default: '' },\n\t\t/** Show the feature-request form as the header's primary button; without it, it is an entry in the Actions menu. */\n\t\tsuggestButton: { type: Boolean, default: true },\n\t\t/** In the `rows` layout, draw each feature's summary under its title; false shows the title and status only. */\n\t\tsummaries: { type: Boolean, default: true },\n\t\t/** A link inside the notice: `{ text, href }`; the first occurrence of `text` in the translated notice opens `href` in a new tab. Only http(s) and site-relative hrefs link. */\n\t\tnoticeLink: { type: Object, default: null },\n\t\t/** Target of the OpenBuilt entry in the Actions menu. */\n\t\topenbuiltUrl: { type: String, default: '' },\n\t\t/** Target of the AI skills entry in the Actions menu. */\n\t\tllmSkillsUrl: { type: String, default: '' },\n\t},\n\n\temits: ['support'],\n\n\tdata() {\n\t\tfeaturesBoardUid += 1\n\t\treturn {\n\t\t\tuid: `cn-features-board-${featuresBoardUid}`,\n\t\t\tactiveTab: 'features',\n\t\t\tstatus: 'all',\n\t\t\tissues: [],\n\t\t\tissuesLoading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tlanguage() {\n\t\t\treturn readerLanguage()\n\t\t},\n\n\t\t/** The groups, their captions through the app's translate function. */\n\t\tgroups() {\n\t\t\treturn groupFeatures(this.features, this.categories, this.language)\n\t\t\t\t.map((group) => ({ ...group, label: group.label ? this.tr(group.label) : '' }))\n\t\t},\n\n\t\tallFeatures() {\n\t\t\treturn this.groups.flatMap((group) => group.features)\n\t\t},\n\n\t\tcounts() {\n\t\t\treturn countByStatus(this.allFeatures)\n\t\t},\n\n\t\t/** The groups with the features the filter lets through; an emptied group is left out. */\n\t\tshownGroups() {\n\t\t\treturn this.groups\n\t\t\t\t.map((group) => ({\n\t\t\t\t\t...group,\n\t\t\t\t\tshown: this.status === 'all' ? group.features : group.features.filter((feature) => feature.status === this.status),\n\t\t\t\t}))\n\t\t\t\t.filter((group) => group.shown.length > 0)\n\t\t},\n\n\t\t/** All, then each status; `in_progress` only when a feature has it. */\n\t\tfilterOptions() {\n\t\t\tconst options = [{ key: 'all', label: this.statusLabel('all'), count: this.counts.all }]\n\t\t\tfor (const key of FEATURE_STATUSES) {\n\t\t\t\tif (key === 'in_progress' && this.counts.in_progress === 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toptions.push({ key, label: this.statusLabel(key), count: this.counts[key] })\n\t\t\t}\n\t\t\treturn options\n\t\t},\n\n\t\ttabList() {\n\t\t\treturn [\n\t\t\t\t{ key: 'features', label: t('nextcloud-vue', 'Features') },\n\t\t\t\t{ key: 'roadmap', label: t('nextcloud-vue', 'Roadmap') },\n\t\t\t]\n\t\t},\n\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.tr(this.title) : t('nextcloud-vue', 'Features and roadmap')\n\t\t},\n\n\t\t/**\n\t\t * The notice split around its link: the text before, the link and the\n\t\t * text after. Without a usable `noticeLink` (no match in the\n\t\t * translated notice, or an href that is not http(s) or site-relative)\n\t\t * `before` is the whole notice.\n\t\t *\n\t\t * @return {{before: string, link: ({text: string, href: string}|null), after: string}} The parts.\n\t\t */\n\t\tnoticeParts() {\n\t\t\tconst text = this.notice ? this.tr(this.notice) : ''\n\t\t\tconst link = this.noticeLink\n\t\t\tconst label = link && typeof link.text === 'string' ? this.tr(link.text) : ''\n\t\t\tconst href = link && typeof link.href === 'string' ? link.href.trim() : ''\n\t\t\tconst index = label ? text.indexOf(label) : -1\n\t\t\tconst allowed = (/^https?:\\/\\//i.test(href) || href.startsWith('/')) && safeHref(href) === href\n\t\t\tif (index === -1 || !allowed) {\n\t\t\t\treturn { before: text, link: null, after: '' }\n\t\t\t}\n\t\t\treturn { before: text.slice(0, index), link: { text: label, href }, after: text.slice(index + label.length) }\n\t\t},\n\n\t\tresolvedSubtitle() {\n\t\t\tif (!this.subtitle) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.tr(this.subtitle)\n\t\t\t\t.replace(/\\{count\\}/g, String(this.counts.all))\n\t\t\t\t.replace(/\\{appVersion\\}/g, this.appVersion)\n\t\t},\n\n\t\tmenuLinks() {\n\t\t\tconst links = []\n\t\t\tif (this.suggestUrl && !this.suggestButton) {\n\t\t\t\tlinks.push({ key: 'suggest', href: this.suggestUrl, label: t('nextcloud-vue', 'Suggest feature') })\n\t\t\t}\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\tlinks.push({ key: 'openbuilt', href: this.openbuiltUrl, label: t('nextcloud-vue', 'Build on this app') })\n\t\t\t}\n\t\t\tif (this.llmSkillsUrl) {\n\t\t\t\tlinks.push({ key: 'llm', href: this.llmSkillsUrl, label: t('nextcloud-vue', 'AI skills') })\n\t\t\t}\n\t\t\treturn links\n\t\t},\n\n\t\tnextIssues() {\n\t\t\treturn this.issues.slice(0, this.next).map((issue) => ({\n\t\t\t\tnumber: issue.number,\n\t\t\t\ttitle: issue.title,\n\t\t\t\thtml_url: issue.html_url,\n\t\t\t\tstatus: issueStatus(issue, this.issueLabels),\n\t\t\t\topened: issue.created_at ? formatBoardDate(issue.created_at) : '',\n\t\t\t}))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tif (this.next > 0 && this.repo) {\n\t\t\tthis.loadIssues()\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\t\tsafeHref,\n\n\t\t/**\n\t\t * Run an app-supplied string through the app's translate function.\n\t\t *\n\t\t * @param {string} key The string.\n\t\t * @return {string} The translated string.\n\t\t */\n\t\ttr(key) {\n\t\t\treturn this.cnTranslate(key)\n\t\t},\n\n\t\t/**\n\t\t * The label of a status, or of `all`: the app's override, else the library's.\n\t\t *\n\t\t * @param {string} key The status key.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(key) {\n\t\t\tif (typeof this.statusLabels[key] === 'string' && this.statusLabels[key] !== '') {\n\t\t\t\treturn this.tr(this.statusLabels[key])\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tall: t('nextcloud-vue', 'All'),\n\t\t\t\tavailable: t('nextcloud-vue', 'Available'),\n\t\t\t\tbeta: t('nextcloud-vue', 'Beta'),\n\t\t\t\tin_progress: t('nextcloud-vue', 'In progress'),\n\t\t\t\tplanned: t('nextcloud-vue', 'Planned'),\n\t\t\t}[key] || key\n\t\t},\n\n\t\t/**\n\t\t * The badge text of a feature: its status, with the expected moment\n\t\t * when it has one (\"Planned, Q4 2026\").\n\t\t *\n\t\t * @param {{status: string, expected: string}} feature The feature.\n\t\t * @return {string} The badge text.\n\t\t */\n\t\tstatusText(feature) {\n\t\t\tconst label = this.statusLabel(feature.status)\n\t\t\treturn feature.expected ? t('nextcloud-vue', '{status}, {expected}', { status: label, expected: feature.expected }) : label\n\t\t},\n\n\t\t/**\n\t\t * The badge variant of a status.\n\t\t *\n\t\t * @param {string} status The status key.\n\t\t * @return {string} The CnStatusBadge variant.\n\t\t */\n\t\tvariantOf(status) {\n\t\t\treturn FEATURE_STATUS_VARIANTS[status] || 'default'\n\t\t},\n\n\t\t/**\n\t\t * \"14 features, 14 available\": the group's size, then each status it holds.\n\t\t *\n\t\t * @param {{features: Array<{status: string}>}} group The group.\n\t\t * @return {string} The caption.\n\t\t */\n\t\tgroupCaption(group) {\n\t\t\tconst counts = countByStatus(group.features)\n\t\t\tconst parts = [n('nextcloud-vue', '%n feature', '%n features', counts.all)]\n\t\t\tfor (const key of FEATURE_STATUSES) {\n\t\t\t\tif (counts[key] > 0) {\n\t\t\t\t\tparts.push(`${counts[key]} ${this.statusLabel(key).toLocaleLowerCase()}`)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(', ')\n\t\t},\n\n\t\t/**\n\t\t * Where \"Learn more\" goes: the feature's own page when it is a web\n\t\t * address, else the documentation site, else nowhere.\n\t\t *\n\t\t * @param {{docsUrl: string}} feature The feature.\n\t\t * @return {string} The address, or ''.\n\t\t */\n\t\treadMoreHref(feature) {\n\t\t\tif (/^https?:\\/\\//i.test(feature.docsUrl)) {\n\t\t\t\treturn safeHref(feature.docsUrl)\n\t\t\t}\n\t\t\treturn this.documentationUrl ? safeHref(this.documentationUrl) : ''\n\t\t},\n\n\t\t/** Ask the host to open the support dialog. */\n\t\tonSupport() {\n\t\t\t/**\n\t\t\t * @event support Emitted when the reader picks \"Support this app\" in the Actions menu.\n\t\t\t */\n\t\t\tthis.$emit('support')\n\t\t},\n\n\t\t/** Fetch the forge issues for the Next card. */\n\t\tasync loadIssues() {\n\t\t\tthis.issuesLoading = true\n\t\t\tconst { items } = await fetchRoadmapIssues(this.repo)\n\t\t\tthis.issues = items\n\t\t\tthis.issuesLoading = false\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home and End move between the folder tabs.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key press.\n\t\t */\n\t\tonTabKeydown(event) {\n\t\t\tconst keys = this.tabList.map((tab) => tab.key)\n\t\t\tconst index = keys.indexOf(this.activeTab)\n\t\t\tconst target = {\n\t\t\t\tArrowRight: (index + 1) % keys.length,\n\t\t\t\tArrowLeft: (index - 1 + keys.length) % keys.length,\n\t\t\t\tHome: 0,\n\t\t\t\tEnd: keys.length - 1,\n\t\t\t}[event.key]\n\t\t\tif (target === undefined) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.activeTab = keys[target]\n\t\t\tthis.$nextTick(() => this.$refs.tabButtons?.[target]?.focus())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-board {\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--cn-board-section-gap, 20px);\n\tpadding: var(--cn-board-content-padding, 24px 28px);\n\tmax-width: 1240px;\n\twidth: 100%;\n}\n\n.cn-features-board__header {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 4px 0 8px;\n}\n\n.cn-features-board__title-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: space-between;\n\talign-items: flex-start;\n\tgap: 12px 24px;\n}\n\n.cn-features-board__title {\n\tflex: 1 1 320px;\n\tmin-width: 0;\n\tmargin: 0;\n\tfont-size: 28px;\n\tfont-weight: 700;\n\tline-height: 1.2;\n}\n\n.cn-features-board__header-actions {\n\tflex: none;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 10px;\n}\n\n.cn-features-board__subtitle {\n\tmargin: 0;\n\tfont-size: 15px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__tab-row {\n\tdisplay: flex;\n\talign-items: flex-end;\n\tgap: 8px;\n\tmargin-bottom: calc(-1 * var(--cn-board-section-gap, 20px));\n}\n\n.cn-features-board__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-features-board__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tbox-sizing: border-box;\n\tmin-height: 34px;\n\tmargin: 0 0 -1px;\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--cn-board-hairline, var(--color-border));\n\tborder-bottom: none;\n\tborder-radius: 8px 8px 0 0;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tcursor: pointer;\n\twhite-space: nowrap;\n}\n\n.cn-features-board__tab--active {\n\tposition: relative;\n\tbackground: var(--color-main-background);\n\tborder-bottom: 1px solid var(--color-main-background);\n\tfont-weight: 700;\n}\n\n.cn-features-board__tab-actions {\n\tflex: none;\n\talign-self: flex-start;\n\tmargin: 0 8px 8px 0;\n}\n\n.cn-features-board__panel {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 22px;\n}\n\n.cn-features-board__panel--tabbed {\n\tpadding: 22px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 0 var(--cn-board-card-radius, 12px) var(--cn-board-card-radius, 12px) var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__filter {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-features-board__filter-option {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcursor: pointer;\n}\n\n.cn-features-board__filter--chips .cn-features-board__filter-option {\n\theight: 36px;\n\tpadding: 0 14px;\n\tborder: 1px solid var(--color-border-dark);\n\tborder-radius: 18px;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-board__filter--chips .cn-features-board__filter-option--active {\n\tborder-color: var(--color-main-text);\n\tbackground: var(--color-main-text);\n\tcolor: var(--color-main-background);\n}\n\n.cn-features-board__filter-count {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__filter--chips .cn-features-board__filter-option--active .cn-features-board__filter-count {\n\tcolor: var(--color-border-dark);\n}\n\n.cn-features-board__filter--segmented {\n\tdisplay: inline-flex;\n\talign-self: flex-start;\n\tgap: 2px;\n\tpadding: 3px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n}\n\n.cn-features-board__filter--segmented .cn-features-board__filter-option {\n\theight: 34px;\n\tpadding: 0 14px;\n\tborder: 0;\n\tborder-radius: 6px;\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-board__filter--segmented .cn-features-board__filter-option--active {\n\tbackground: var(--color-main-background);\n\tbox-shadow: 0 1px 2px rgba(27, 28, 29, 0.12);\n}\n\n.cn-features-board__body {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-start;\n\tgap: 24px;\n}\n\n.cn-features-board__groups {\n\tflex: 999 1 520px;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 26px;\n}\n\n.cn-features-board__groups--rows {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(340px, 1fr));\n\tgap: 20px;\n}\n\n.cn-features-board__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmax-width: none;\n\tmargin: 0;\n}\n\n.cn-features-board__groups--rows .cn-features-board__group {\n\tpadding: var(--cn-board-card-padding, 20px 22px);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__group-head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tgap: 10px;\n}\n\n.cn-features-board__group-title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-features-board__groups--rows .cn-features-board__group-title {\n\tfont-size: 17px;\n}\n\n.cn-features-board__group-caption {\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__cards {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n\tgap: 16px;\n}\n\n.cn-features-board__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 18px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__card-head {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 4px;\n}\n\n.cn-features-board__card-title {\n\tmargin: 0;\n\tfont-size: 17px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n}\n\n.cn-features-board__card-summary {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-board__card-footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: center;\n\tmin-height: 34px;\n\tmargin-top: auto;\n\tpadding-top: 10px;\n\tborder-top: 1px solid var(--color-background-dark);\n}\n\n.cn-features-board__read-more {\n\tdisplay: inline-flex;\n\talign-items: center;\n\theight: 34px;\n\tpadding: 0 12px;\n\tborder: 1px solid var(--color-border-dark);\n\tborder-radius: 8px;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttext-decoration: none;\n}\n\n.cn-features-board__rows {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-features-board__row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px 16px;\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-background-dark);\n}\n\n.cn-features-board__row-text {\n\tflex: 1 1 240px;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-features-board__row-title {\n\tfont-size: 15px;\n\tfont-weight: 500;\n}\n\n.cn-features-board__row-summary {\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__next {\n\tflex: 1 1 280px;\n\tmin-width: 0;\n\tmax-width: 340px;\n}\n\n.cn-features-board__next-card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 18px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-features-board__next-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-features-board__next-title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-features-board__next-lead {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__next-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-features-board__next-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 10px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__next-link {\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tline-height: 1.3;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-features-board__next-meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__next-opened {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n}\n\n.cn-features-board__next-more {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-features-board__empty {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__notice {\n\tmargin: 0;\n}\n</style>\n"],"names":["CnBuildiqEditButton","CnRoadmapTab","CnStatusBadge","t","n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFeaturesBoard.vue2.js","sources":["../../../../src/components/CnFeaturesBoard/CnFeaturesBoard.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-features-board\" :class=\"`cn-features-board--${layout}`\" data-testid=\"cn-features-board\">\n\t\t<header class=\"cn-features-board__header\">\n\t\t\t<div class=\"cn-features-board__title-row\">\n\t\t\t\t<h1 class=\"cn-features-board__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h1>\n\t\t\t\t<div class=\"cn-features-board__header-actions\">\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"documentationButton && documentationUrl\"\n\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t:href=\"documentationUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-docs\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<BookOpenVariant :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Documentation') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<CnBuildiqEditButton />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"suggestUrl && suggestButton\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"suggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-suggest\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Suggest feature') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcActions\n\t\t\t\t\t\tv-if=\"!tabs && actionsMenu\"\n\t\t\t\t\t\t:menuName=\"t('nextcloud-vue', 'Actions')\"\n\t\t\t\t\t\t:forceName=\"true\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-actions\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<DotsHorizontal :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-for=\"action in menuLinks\"\n\t\t\t\t\t\t\t:key=\"action.key\"\n\t\t\t\t\t\t\t:href=\"action.href\"\n\t\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionButton @click=\"onSupport\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Support this app') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<p v-if=\"resolvedSubtitle\" class=\"cn-features-board__subtitle\" data-testid=\"cn-features-board-subtitle\">\n\t\t\t\t{{ resolvedSubtitle }}\n\t\t\t</p>\n\t\t</header>\n\n\t\t<div v-if=\"tabs\" class=\"cn-features-board__tab-row\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-features-board__tabs\"\n\t\t\t\trole=\"tablist\"\n\t\t\t\t:aria-label=\"resolvedTitle\"\n\t\t\t\t@keydown=\"onTabKeydown\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"tab in tabList\"\n\t\t\t\t\t:id=\"`${uid}-tab-${tab.key}`\"\n\t\t\t\t\t:key=\"tab.key\"\n\t\t\t\t\tref=\"tabButtons\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\tclass=\"cn-features-board__tab\"\n\t\t\t\t\t:class=\"{ 'cn-features-board__tab--active': activeTab === tab.key }\"\n\t\t\t\t\t:aria-selected=\"activeTab === tab.key ? 'true' : 'false'\"\n\t\t\t\t\t:aria-controls=\"`${uid}-panel`\"\n\t\t\t\t\t:tabindex=\"activeTab === tab.key ? 0 : -1\"\n\t\t\t\t\t:data-testid=\"`cn-features-board-tab-${tab.key}`\"\n\t\t\t\t\t@click=\"activeTab = tab.key\">\n\t\t\t\t\t{{ tab.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<NcActions\n\t\t\t\tv-if=\"actionsMenu\"\n\t\t\t\t:menuName=\"t('nextcloud-vue', 'Actions')\"\n\t\t\t\t:forceName=\"true\"\n\t\t\t\tclass=\"cn-features-board__tab-actions\"\n\t\t\t\tdata-testid=\"cn-features-board-actions\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<DotsHorizontal :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t<NcActionLink\n\t\t\t\t\tv-for=\"action in menuLinks\"\n\t\t\t\t\t:key=\"action.key\"\n\t\t\t\t\t:href=\"action.href\"\n\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton @click=\"onSupport\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Support this app') }}\n\t\t\t\t</NcActionButton>\n\t\t\t</NcActions>\n\t\t</div>\n\n\t\t<div\n\t\t\t:id=\"`${uid}-panel`\"\n\t\t\tclass=\"cn-features-board__panel\"\n\t\t\t:class=\"{ 'cn-features-board__panel--tabbed': tabs }\"\n\t\t\t:role=\"tabs ? 'tabpanel' : null\"\n\t\t\t:aria-labelledby=\"tabs ? `${uid}-tab-${activeTab}` : null\">\n\t\t\t<CnRoadmapTab v-if=\"tabs && activeTab === 'roadmap'\" :repo=\"repo\" />\n\t\t\t<template v-else>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"cn-features-board__filter\"\n\t\t\t\t\t:class=\"`cn-features-board__filter--${filter}`\"\n\t\t\t\t\trole=\"group\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Status')\"\n\t\t\t\t\tdata-testid=\"cn-features-board-filter\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"option in filterOptions\"\n\t\t\t\t\t\t:key=\"option.key\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-features-board__filter-option\"\n\t\t\t\t\t\t:class=\"{ 'cn-features-board__filter-option--active': status === option.key }\"\n\t\t\t\t\t\t:aria-pressed=\"status === option.key ? 'true' : 'false'\"\n\t\t\t\t\t\t:data-status=\"option.key\"\n\t\t\t\t\t\t@click=\"status = option.key\">\n\t\t\t\t\t\t{{ option.label }}<span\n\t\t\t\t\t\t\tv-if=\"filter === 'chips'\"\n\t\t\t\t\t\t\tclass=\"cn-features-board__filter-count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-features-board-filter-count\">{{ ' ' + option.count }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\n\t\t\t\t<div class=\"cn-features-board__body\">\n\t\t\t\t\t<div class=\"cn-features-board__groups\" :class=\"`cn-features-board__groups--${layout}`\">\n\t\t\t\t\t\t<p v-if=\"shownGroups.length === 0\" class=\"cn-features-board__empty\" data-testid=\"cn-features-board-empty\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No features with this status') }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<section\n\t\t\t\t\t\t\tv-for=\"group in shownGroups\"\n\t\t\t\t\t\t\t:key=\"group.key\"\n\t\t\t\t\t\t\tclass=\"cn-features-board__group\"\n\t\t\t\t\t\t\t:aria-labelledby=\"group.label ? `${uid}-group-${group.key}` : null\"\n\t\t\t\t\t\t\t:aria-label=\"group.label ? null : resolvedTitle\"\n\t\t\t\t\t\t\tdata-testid=\"cn-features-board-group\"\n\t\t\t\t\t\t\t:data-group=\"group.key\">\n\t\t\t\t\t\t\t<div v-if=\"group.label\" class=\"cn-features-board__group-head\">\n\t\t\t\t\t\t\t\t<h2 :id=\"`${uid}-group-${group.key}`\" class=\"cn-features-board__group-title\">\n\t\t\t\t\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"layout === 'cards'\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__group-caption\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-group-caption\">{{ groupCaption(group) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"layout === 'cards'\" class=\"cn-features-board__cards\">\n\t\t\t\t\t\t\t\t<article\n\t\t\t\t\t\t\t\t\tv-for=\"feature in group.shown\"\n\t\t\t\t\t\t\t\t\t:key=\"feature.slug\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__card\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-feature\">\n\t\t\t\t\t\t\t\t\t<div class=\"cn-features-board__card-head\">\n\t\t\t\t\t\t\t\t\t\t<h3 class=\"cn-features-board__card-title\">\n\t\t\t\t\t\t\t\t\t\t\t{{ feature.title }}\n\t\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\t\t\t:label=\"statusText(feature)\"\n\t\t\t\t\t\t\t\t\t\t\t:variant=\"variantOf(feature.status)\"\n\t\t\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-status\" />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<p v-if=\"feature.summary\" class=\"cn-features-board__card-summary\">\n\t\t\t\t\t\t\t\t\t\t{{ feature.summary }}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t<div v-if=\"readMoreHref(feature)\" class=\"cn-features-board__card-footer\">\n\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__read-more\"\n\t\t\t\t\t\t\t\t\t\t\t:href=\"readMoreHref(feature)\"\n\t\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Learn more about {title}', { title: feature.title })\">\n\t\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Learn more') }}\n\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</article>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<ul v-else class=\"cn-features-board__rows\">\n\t\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\t\tv-for=\"feature in group.shown\"\n\t\t\t\t\t\t\t\t\t:key=\"feature.slug\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__row\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-feature\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-features-board__row-text\">\n\t\t\t\t\t\t\t\t\t\t<span class=\"cn-features-board__row-title\">{{ feature.title }}</span>\n\t\t\t\t\t\t\t\t\t\t<span v-if=\"summaries && feature.summary\" class=\"cn-features-board__row-summary\">{{ feature.summary }}</span>\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\t\t:label=\"statusText(feature)\"\n\t\t\t\t\t\t\t\t\t\t:variant=\"variantOf(feature.status)\"\n\t\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-status\" />\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t</section>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<aside\n\t\t\t\t\t\tv-if=\"next > 0 && repo\"\n\t\t\t\t\t\tclass=\"cn-features-board__next\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Next on the roadmap')\"\n\t\t\t\t\t\tdata-testid=\"cn-features-board-next\">\n\t\t\t\t\t\t<section class=\"cn-features-board__next-card\">\n\t\t\t\t\t\t\t<div class=\"cn-features-board__next-head\">\n\t\t\t\t\t\t\t\t<RoadVariant :size=\"20\" />\n\t\t\t\t\t\t\t\t<h2 class=\"cn-features-board__next-title\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Coming next') }}\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p class=\"cn-features-board__next-lead\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'The most requested items from the issue tracker.') }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<ol v-if=\"nextIssues.length > 0\" class=\"cn-features-board__next-list\">\n\t\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\t\tv-for=\"issue in nextIssues\"\n\t\t\t\t\t\t\t\t\t:key=\"issue.number\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-item\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-next-item\">\n\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-link\"\n\t\t\t\t\t\t\t\t\t\t:href=\"safeHref(issue.html_url)\"\n\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\">{{ issue.title }}</a>\n\t\t\t\t\t\t\t\t\t<div class=\"cn-features-board__next-meta\">\n\t\t\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\t\t\tv-if=\"issue.status\"\n\t\t\t\t\t\t\t\t\t\t\t:label=\"statusLabel(issue.status)\"\n\t\t\t\t\t\t\t\t\t\t\t:variant=\"variantOf(issue.status)\" />\n\t\t\t\t\t\t\t\t\t\t<span v-if=\"issue.opened\" class=\"cn-features-board__next-opened\">\n\t\t\t\t\t\t\t\t\t\t\t<CalendarBlankOutline :size=\"14\" />{{ t('nextcloud-vue', 'Opened {date}', { date: issue.opened }) }}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<span>#{{ issue.number }}</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t</ol>\n\t\t\t\t\t\t\t<p v-else-if=\"!issuesLoading\" class=\"cn-features-board__next-lead\" data-testid=\"cn-features-board-next-empty\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No roadmap items yet') }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\tv-if=\"tabs\"\n\t\t\t\t\t\t\t\thref=\"#\"\n\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-more\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-features-board-whole-roadmap\"\n\t\t\t\t\t\t\t\t@click.prevent=\"activeTab = 'roadmap'\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'See the whole roadmap') }}<ChevronRight :size=\"16\" />\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\tv-if=\"documentationUrl\"\n\t\t\t\t\t\t\t\t:href=\"documentationUrl\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclass=\"cn-features-board__next-more\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Documentation') }}<ChevronRight :size=\"16\" />\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</section>\n\t\t\t\t\t</aside>\n\t\t\t\t</div>\n\n\t\t\t\t<NcNoteCard v-if=\"notice\"\n\t\t\t\t\ttype=\"info\"\n\t\t\t\t\tclass=\"cn-features-board__notice\"\n\t\t\t\t\tdata-testid=\"cn-features-board-notice\">\n\t\t\t\t\t<template v-if=\"noticeParts.link\">\n\t\t\t\t\t\t{{ noticeParts.before }}<a\n\t\t\t\t\t\t\t:href=\"noticeParts.link.href\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tdata-testid=\"cn-features-board-notice-link\">{{ noticeParts.link.text }}</a>{{ noticeParts.after }}\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t{{ noticeParts.before }}\n\t\t\t\t\t</template>\n\t\t\t\t</NcNoteCard>\n\t\t\t</template>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { getLanguage, translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcActions, NcButton, NcNoteCard } from '@nextcloud/vue'\nimport BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'\nimport CalendarBlankOutline from 'vue-material-design-icons/CalendarBlankOutline.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { formatBoardDate } from '../../utils/boardDate.js'\nimport { countByStatus, FEATURE_STATUS_VARIANTS, FEATURE_STATUSES, groupFeatures } from '../../utils/featureCatalog.js'\nimport { fetchRoadmapIssues, issueStatus } from '../../utils/roadmapIssues.js'\nimport { safeHref } from '../../utils/safeHref.js'\n\nlet featuresBoardUid = 0\n\n/**\n * Read the reader's language, which @nextcloud/l10n throws on outside a\n * Nextcloud page.\n *\n * @return {string} The language tag, or ''.\n */\nfunction readerLanguage() {\n\ttry {\n\t\treturn getLanguage() || ''\n\t} catch {\n\t\treturn ''\n\t}\n}\n\n/**\n * CnFeaturesBoard - the Features and roadmap page in the board look.\n *\n * A title with a subtitle, optional folder tabs (Features, Roadmap), a status\n * filter (count chips or a segmented control), the features grouped by\n * category (as two-up cards or as rows inside one card per group), an\n * optional \"Next\" card with the most requested forge issues, and an optional\n * notice under the groups. The links the default look puts in its right-hand\n * sidebar (suggest, buildiq, AI skills, support) sit in an Actions menu.\n *\n * Rendered by CnFeaturesAndRoadmapView when the page is in the board look.\n *\n * @spec openspec/changes/screens-roadmap-board-look/specs/features-roadmap-board-look/spec.md\n *\n * @event support Emitted when the reader picks \"Support this app\" in the Actions menu.\n */\nexport default {\n\tname: 'CnFeaturesBoard',\n\n\tcomponents: {\n\t\tBookOpenVariant,\n\t\tCalendarBlankOutline,\n\t\tChevronRight,\n\t\tCnBuildiqEditButton,\n\t\tCnRoadmapTab,\n\t\tCnStatusBadge,\n\t\tDotsHorizontal,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcActions,\n\t\tNcButton,\n\t\tNcNoteCard,\n\t\tPlus,\n\t\tRoadVariant,\n\t},\n\n\tinject: {\n\t\t/** The app's translate function, provided by CnAppRoot. Identity outside one. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The features: `{ slug, title, summary, status, category, expected, docsUrl, title_<lang>, summary_<lang> }`. */\n\t\tfeatures: { type: Array, default: () => [] },\n\t\t/** Category key to caption; sets the order of the groups. */\n\t\tcategories: { type: Object, default: () => ({}) },\n\t\t/** `cards`: a section per category with two-up feature cards. `rows`: one card per category with a row per feature. */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: 'cards',\n\t\t\tvalidator: (value) => ['cards', 'rows'].includes(value),\n\t\t},\n\n\t\t/** Show the Features and Roadmap folder tabs. */\n\t\ttabs: { type: Boolean, default: true },\n\t\t/** Show the Actions menu (suggest, buildiq, AI skills, support), in the header or the tab row; false draws none. */\n\t\tactionsMenu: { type: Boolean, default: true },\n\t\t/** `chips`: pill buttons with a count. `segmented`: a segmented control without counts. */\n\t\tfilter: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (value) => ['chips', 'segmented'].includes(value),\n\t\t},\n\n\t\t/** Overrides of the status labels, by key (`all`, `available`, `beta`, `in_progress`, `planned`). */\n\t\tstatusLabels: { type: Object, default: () => ({}) },\n\t\t/** How many issues the Next card lists; 0 hides the card. */\n\t\tnext: { type: Number, default: 3 },\n\t\t/** Forge label to issue status (`in_progress`, `planned`) for the badges in the Next card. */\n\t\tissueLabels: { type: Object, default: () => ({}) },\n\t\t/** `<owner>/<repo>` on the forge, for the Roadmap tab and the Next card. */\n\t\trepo: { type: String, default: '' },\n\t\t/** Page title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Subtitle template; `{count}` is the number of features and `{appVersion}` the app version. */\n\t\tsubtitle: { type: String, default: '' },\n\t\t/** The app version, for `{appVersion}` in the subtitle. */\n\t\tappVersion: { type: String, default: '' },\n\t\t/** An info notice under the groups. */\n\t\tnotice: { type: String, default: '' },\n\t\t/** The app's documentation site: the Next card's link and the target of \"Learn more\". */\n\t\tdocumentationUrl: { type: String, default: '' },\n\t\t/** Show a Documentation button in the header. */\n\t\tdocumentationButton: { type: Boolean, default: false },\n\t\t/** The feature-request form; the header's primary button. */\n\t\tsuggestUrl: { type: String, default: '' },\n\t\t/** Show the feature-request form as the header's primary button; without it, it is an entry in the Actions menu. */\n\t\tsuggestButton: { type: Boolean, default: true },\n\t\t/** In the `rows` layout, draw each feature's summary under its title; false shows the title and status only. */\n\t\tsummaries: { type: Boolean, default: true },\n\t\t/** A link inside the notice: `{ text, href }`; the first occurrence of `text` in the translated notice opens `href` in a new tab. Only http(s) and site-relative hrefs link. */\n\t\tnoticeLink: { type: Object, default: null },\n\t\t/** Target of the OpenBuilt entry in the Actions menu. */\n\t\topenbuiltUrl: { type: String, default: '' },\n\t\t/** Target of the AI skills entry in the Actions menu. */\n\t\tllmSkillsUrl: { type: String, default: '' },\n\t},\n\n\temits: ['support'],\n\n\tdata() {\n\t\tfeaturesBoardUid += 1\n\t\treturn {\n\t\t\tuid: `cn-features-board-${featuresBoardUid}`,\n\t\t\tactiveTab: 'features',\n\t\t\tstatus: 'all',\n\t\t\tissues: [],\n\t\t\tissuesLoading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tlanguage() {\n\t\t\treturn readerLanguage()\n\t\t},\n\n\t\t/** The groups, their captions through the app's translate function. */\n\t\tgroups() {\n\t\t\treturn groupFeatures(this.features, this.categories, this.language)\n\t\t\t\t.map((group) => ({ ...group, label: group.label ? this.tr(group.label) : '' }))\n\t\t},\n\n\t\tallFeatures() {\n\t\t\treturn this.groups.flatMap((group) => group.features)\n\t\t},\n\n\t\tcounts() {\n\t\t\treturn countByStatus(this.allFeatures)\n\t\t},\n\n\t\t/** The groups with the features the filter lets through; an emptied group is left out. */\n\t\tshownGroups() {\n\t\t\treturn this.groups\n\t\t\t\t.map((group) => ({\n\t\t\t\t\t...group,\n\t\t\t\t\tshown: this.status === 'all' ? group.features : group.features.filter((feature) => feature.status === this.status),\n\t\t\t\t}))\n\t\t\t\t.filter((group) => group.shown.length > 0)\n\t\t},\n\n\t\t/** All, then each status; `in_progress` only when a feature has it. */\n\t\tfilterOptions() {\n\t\t\tconst options = [{ key: 'all', label: this.statusLabel('all'), count: this.counts.all }]\n\t\t\tfor (const key of FEATURE_STATUSES) {\n\t\t\t\tif (key === 'in_progress' && this.counts.in_progress === 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toptions.push({ key, label: this.statusLabel(key), count: this.counts[key] })\n\t\t\t}\n\t\t\treturn options\n\t\t},\n\n\t\ttabList() {\n\t\t\treturn [\n\t\t\t\t{ key: 'features', label: t('nextcloud-vue', 'Features') },\n\t\t\t\t{ key: 'roadmap', label: t('nextcloud-vue', 'Roadmap') },\n\t\t\t]\n\t\t},\n\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.tr(this.title) : t('nextcloud-vue', 'Features and roadmap')\n\t\t},\n\n\t\t/**\n\t\t * The notice split around its link: the text before, the link and the\n\t\t * text after. Without a usable `noticeLink` (no match in the\n\t\t * translated notice, or an href that is not http(s) or site-relative)\n\t\t * `before` is the whole notice.\n\t\t *\n\t\t * @return {{before: string, link: ({text: string, href: string}|null), after: string}} The parts.\n\t\t */\n\t\tnoticeParts() {\n\t\t\tconst text = this.notice ? this.tr(this.notice) : ''\n\t\t\tconst link = this.noticeLink\n\t\t\tconst label = link && typeof link.text === 'string' ? this.tr(link.text) : ''\n\t\t\tconst href = link && typeof link.href === 'string' ? link.href.trim() : ''\n\t\t\tconst index = label ? text.indexOf(label) : -1\n\t\t\tconst allowed = (/^https?:\\/\\//i.test(href) || href.startsWith('/')) && safeHref(href) === href\n\t\t\tif (index === -1 || !allowed) {\n\t\t\t\treturn { before: text, link: null, after: '' }\n\t\t\t}\n\t\t\treturn { before: text.slice(0, index), link: { text: label, href }, after: text.slice(index + label.length) }\n\t\t},\n\n\t\tresolvedSubtitle() {\n\t\t\tif (!this.subtitle) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.tr(this.subtitle)\n\t\t\t\t.replace(/\\{count\\}/g, String(this.counts.all))\n\t\t\t\t.replace(/\\{appVersion\\}/g, this.appVersion)\n\t\t},\n\n\t\tmenuLinks() {\n\t\t\tconst links = []\n\t\t\tif (this.suggestUrl && !this.suggestButton) {\n\t\t\t\tlinks.push({ key: 'suggest', href: this.suggestUrl, label: t('nextcloud-vue', 'Suggest feature') })\n\t\t\t}\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\tlinks.push({ key: 'openbuilt', href: this.openbuiltUrl, label: t('nextcloud-vue', 'Build on this app') })\n\t\t\t}\n\t\t\tif (this.llmSkillsUrl) {\n\t\t\t\tlinks.push({ key: 'llm', href: this.llmSkillsUrl, label: t('nextcloud-vue', 'AI skills') })\n\t\t\t}\n\t\t\treturn links\n\t\t},\n\n\t\tnextIssues() {\n\t\t\treturn this.issues.slice(0, this.next).map((issue) => ({\n\t\t\t\tnumber: issue.number,\n\t\t\t\ttitle: issue.title,\n\t\t\t\thtml_url: issue.html_url,\n\t\t\t\tstatus: issueStatus(issue, this.issueLabels),\n\t\t\t\topened: issue.created_at ? formatBoardDate(issue.created_at) : '',\n\t\t\t}))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tif (this.next > 0 && this.repo) {\n\t\t\tthis.loadIssues()\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\t\tsafeHref,\n\n\t\t/**\n\t\t * Run an app-supplied string through the app's translate function.\n\t\t *\n\t\t * @param {string} key The string.\n\t\t * @return {string} The translated string.\n\t\t */\n\t\ttr(key) {\n\t\t\treturn this.cnTranslate(key)\n\t\t},\n\n\t\t/**\n\t\t * The label of a status, or of `all`: the app's override, else the library's.\n\t\t *\n\t\t * @param {string} key The status key.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(key) {\n\t\t\tif (typeof this.statusLabels[key] === 'string' && this.statusLabels[key] !== '') {\n\t\t\t\treturn this.tr(this.statusLabels[key])\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tall: t('nextcloud-vue', 'All'),\n\t\t\t\tavailable: t('nextcloud-vue', 'Available'),\n\t\t\t\tbeta: t('nextcloud-vue', 'Beta'),\n\t\t\t\tin_progress: t('nextcloud-vue', 'In progress'),\n\t\t\t\tplanned: t('nextcloud-vue', 'Planned'),\n\t\t\t}[key] || key\n\t\t},\n\n\t\t/**\n\t\t * The badge text of a feature: its status, with the expected moment\n\t\t * when it has one (\"Planned, Q4 2026\").\n\t\t *\n\t\t * @param {{status: string, expected: string}} feature The feature.\n\t\t * @return {string} The badge text.\n\t\t */\n\t\tstatusText(feature) {\n\t\t\tconst label = this.statusLabel(feature.status)\n\t\t\treturn feature.expected ? t('nextcloud-vue', '{status}, {expected}', { status: label, expected: feature.expected }) : label\n\t\t},\n\n\t\t/**\n\t\t * The badge variant of a status.\n\t\t *\n\t\t * @param {string} status The status key.\n\t\t * @return {string} The CnStatusBadge variant.\n\t\t */\n\t\tvariantOf(status) {\n\t\t\treturn FEATURE_STATUS_VARIANTS[status] || 'default'\n\t\t},\n\n\t\t/**\n\t\t * \"14 features, 14 available\": the group's size, then each status it holds.\n\t\t *\n\t\t * @param {{features: Array<{status: string}>}} group The group.\n\t\t * @return {string} The caption.\n\t\t */\n\t\tgroupCaption(group) {\n\t\t\tconst counts = countByStatus(group.features)\n\t\t\tconst parts = [n('nextcloud-vue', '%n feature', '%n features', counts.all)]\n\t\t\tfor (const key of FEATURE_STATUSES) {\n\t\t\t\tif (counts[key] > 0) {\n\t\t\t\t\tparts.push(`${counts[key]} ${this.statusLabel(key).toLocaleLowerCase()}`)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(', ')\n\t\t},\n\n\t\t/**\n\t\t * Where \"Learn more\" goes: the feature's own page when it is a web\n\t\t * address, else the documentation site, else nowhere.\n\t\t *\n\t\t * @param {{docsUrl: string}} feature The feature.\n\t\t * @return {string} The address, or ''.\n\t\t */\n\t\treadMoreHref(feature) {\n\t\t\tif (/^https?:\\/\\//i.test(feature.docsUrl)) {\n\t\t\t\treturn safeHref(feature.docsUrl)\n\t\t\t}\n\t\t\treturn this.documentationUrl ? safeHref(this.documentationUrl) : ''\n\t\t},\n\n\t\t/** Ask the host to open the support dialog. */\n\t\tonSupport() {\n\t\t\t/**\n\t\t\t * @event support Emitted when the reader picks \"Support this app\" in the Actions menu.\n\t\t\t */\n\t\t\tthis.$emit('support')\n\t\t},\n\n\t\t/** Fetch the forge issues for the Next card. */\n\t\tasync loadIssues() {\n\t\t\tthis.issuesLoading = true\n\t\t\tconst { items } = await fetchRoadmapIssues(this.repo)\n\t\t\tthis.issues = items\n\t\t\tthis.issuesLoading = false\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home and End move between the folder tabs.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key press.\n\t\t */\n\t\tonTabKeydown(event) {\n\t\t\tconst keys = this.tabList.map((tab) => tab.key)\n\t\t\tconst index = keys.indexOf(this.activeTab)\n\t\t\tconst target = {\n\t\t\t\tArrowRight: (index + 1) % keys.length,\n\t\t\t\tArrowLeft: (index - 1 + keys.length) % keys.length,\n\t\t\t\tHome: 0,\n\t\t\t\tEnd: keys.length - 1,\n\t\t\t}[event.key]\n\t\t\tif (target === undefined) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.activeTab = keys[target]\n\t\t\tthis.$nextTick(() => this.$refs.tabButtons?.[target]?.focus())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-board {\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--cn-board-section-gap, 20px);\n\tpadding: var(--cn-board-content-padding, 24px 28px);\n\tmax-width: 1240px;\n\twidth: 100%;\n}\n\n.cn-features-board__header {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 4px 0 8px;\n}\n\n.cn-features-board__title-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: space-between;\n\talign-items: flex-start;\n\tgap: 12px 24px;\n}\n\n.cn-features-board__title {\n\tflex: 1 1 320px;\n\tmin-width: 0;\n\tmargin: 0;\n\tfont-size: 28px;\n\tfont-weight: 700;\n\tline-height: 1.2;\n}\n\n.cn-features-board__header-actions {\n\tflex: none;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 10px;\n}\n\n.cn-features-board__subtitle {\n\tmargin: 0;\n\tfont-size: 15px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__tab-row {\n\tdisplay: flex;\n\talign-items: flex-end;\n\tgap: 8px;\n\tmargin-bottom: calc(-1 * var(--cn-board-section-gap, 20px));\n}\n\n.cn-features-board__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-features-board__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tbox-sizing: border-box;\n\tmin-height: 34px;\n\tmargin: 0 0 -1px;\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--cn-board-hairline, var(--color-border));\n\tborder-bottom: none;\n\tborder-radius: 8px 8px 0 0;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tcursor: pointer;\n\twhite-space: nowrap;\n}\n\n.cn-features-board__tab--active {\n\tposition: relative;\n\tbackground: var(--color-main-background);\n\tborder-bottom: 1px solid var(--color-main-background);\n\tfont-weight: 700;\n}\n\n.cn-features-board__tab-actions {\n\tflex: none;\n\talign-self: flex-start;\n\tmargin: 0 8px 8px 0;\n}\n\n.cn-features-board__panel {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 22px;\n}\n\n.cn-features-board__panel--tabbed {\n\tpadding: 22px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 0 var(--cn-board-card-radius, 12px) var(--cn-board-card-radius, 12px) var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__filter {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-features-board__filter-option {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcursor: pointer;\n}\n\n.cn-features-board__filter--chips .cn-features-board__filter-option {\n\theight: 36px;\n\tpadding: 0 14px;\n\tborder: 1px solid var(--color-border-dark);\n\tborder-radius: 18px;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-board__filter--chips .cn-features-board__filter-option--active {\n\tborder-color: var(--color-main-text);\n\tbackground: var(--color-main-text);\n\tcolor: var(--color-main-background);\n}\n\n.cn-features-board__filter-count {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__filter--chips .cn-features-board__filter-option--active .cn-features-board__filter-count {\n\tcolor: var(--color-border-dark);\n}\n\n.cn-features-board__filter--segmented {\n\tdisplay: inline-flex;\n\talign-self: flex-start;\n\tgap: 2px;\n\tpadding: 3px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n}\n\n.cn-features-board__filter--segmented .cn-features-board__filter-option {\n\theight: 34px;\n\tpadding: 0 14px;\n\tborder: 0;\n\tborder-radius: 6px;\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-board__filter--segmented .cn-features-board__filter-option--active {\n\tbackground: var(--color-main-background);\n\tbox-shadow: 0 1px 2px rgba(27, 28, 29, 0.12);\n}\n\n.cn-features-board__body {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-start;\n\tgap: 24px;\n}\n\n.cn-features-board__groups {\n\tflex: 999 1 520px;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 26px;\n}\n\n.cn-features-board__groups--rows {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(340px, 1fr));\n\tgap: 20px;\n}\n\n.cn-features-board__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmax-width: none;\n\tmargin: 0;\n}\n\n.cn-features-board__groups--rows .cn-features-board__group {\n\tpadding: var(--cn-board-card-padding, 20px 22px);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__group-head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tgap: 10px;\n}\n\n.cn-features-board__group-title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-features-board__groups--rows .cn-features-board__group-title {\n\tfont-size: 17px;\n}\n\n.cn-features-board__group-caption {\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__cards {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n\tgap: 16px;\n}\n\n.cn-features-board__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 18px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__card-head {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 4px;\n}\n\n.cn-features-board__card-title {\n\tmargin: 0;\n\tfont-size: 17px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n}\n\n.cn-features-board__card-summary {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-board__card-footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: center;\n\tmin-height: 34px;\n\tmargin-top: auto;\n\tpadding-top: 10px;\n\tborder-top: 1px solid var(--color-background-dark);\n}\n\n.cn-features-board__read-more {\n\tdisplay: inline-flex;\n\talign-items: center;\n\theight: 34px;\n\tpadding: 0 12px;\n\tborder: 1px solid var(--color-border-dark);\n\tborder-radius: 8px;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttext-decoration: none;\n}\n\n.cn-features-board__rows {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-features-board__row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px 16px;\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-background-dark);\n}\n\n.cn-features-board__row-text {\n\tflex: 1 1 240px;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-features-board__row-title {\n\tfont-size: 15px;\n\tfont-weight: 500;\n}\n\n.cn-features-board__row-summary {\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__next {\n\tflex: 1 1 280px;\n\tmin-width: 0;\n\tmax-width: 340px;\n}\n\n.cn-features-board__next-card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 18px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--cn-board-card-radius, 12px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-features-board__next-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-features-board__next-title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-features-board__next-lead {\n\tmargin: 0;\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__next-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-features-board__next-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 10px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-features-board__next-link {\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tline-height: 1.3;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-features-board__next-meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__next-opened {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n}\n\n.cn-features-board__next-more {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-features-board__empty {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-features-board__notice {\n\tmargin: 0;\n}\n</style>\n"],"names":["CnBuildiqEditButton","CnRoadmapTab","CnStatusBadge","t","n"],"mappings":"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"}
|