@conduction/nextcloud-vue 2.68.0 → 2.70.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/CnActionsBar/CnActionsBar.vue2.js +25 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -35
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +19 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +94 -6
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +64 -19
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
- 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/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +143 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -25
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +29 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +81 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +25 -3
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +278 -11
- package/dist/esm/mixins/pageViews.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +145 -0
- package/dist/esm/store/plugins/dashboardLayouts.js +35 -1
- package/dist/esm/store/plugins/dashboardLayouts.js.map +1 -1
- package/dist/esm/utils/capabilityComparison.js +29 -1
- package/dist/esm/utils/capabilityComparison.js.map +1 -1
- package/dist/esm/utils/formatMetric.js +137 -16
- package/dist/esm/utils/formatMetric.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2840 -1477
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +145 -0
- package/l10n/en.json +3 -0
- package/l10n/nl.json +3 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +2 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
- package/src/components/CnAppNav/CnAppNav.vue +85 -8
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
- package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
- package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
- package/src/components/CnDashboardPage/CnDashboardPage.md +2 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +59 -35
- package/src/components/CnDataTable/CnDataTable.vue +35 -1
- package/src/components/CnDetailPage/CnDetailPage.md +8 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +124 -8
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
- package/src/components/CnIndexPage/CnIndexPage.vue +171 -6
- package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
- package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +38 -2
- package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
- package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +95 -3
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
- package/src/css/detail-page.css +10 -0
- package/src/css/index-page.css +12 -0
- package/src/css/kpi-card.css +15 -0
- package/src/mixins/pageViews.js +277 -10
- package/src/schemas/app-manifest-v2.schema.json +73 -6
- package/src/store/plugins/dashboardLayouts.js +34 -0
- package/src/utils/capabilityComparison.js +28 -0
- package/src/utils/formatMetric.js +143 -15
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFeaturesAndRoadmapPage.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue"],"sourcesContent":["<!--\n CnFeaturesAndRoadmapPage — Manifest-driven Features & Roadmap surface.\n\n v2 manifest page primitive for `type: \"roadmap\"`. Wraps\n `CnFeaturesAndRoadmapView` so consuming apps can declare\n `{ id: \"FeaturesRoadmap\", route: \"/features-roadmap\", type: \"roadmap\" }`\n in their manifest without writing a per-app wrapper that just\n unpacks initialState into the view's three props.\n\n Resolution order for each of the three values:\n\n 1. Explicit `pages[].config.<key>` from the manifest.\n 2. `loadState(appId, 'features_roadmap_<key>', <fallback>)`.\n 3. Hardcoded fallback (`repo` → `ConductionNL/<appId>` on GitHub;\n `forge` → the `cnFeatureRequestForge` inject, else DEFAULT_FORGE).\n\n `appId` comes from the `cnAiContext` inject that CnAppRoot already\n provides; consumers can override it explicitly via the prop for tests.\n\n Spec: ConductionNL/nextcloud-vue#264.\n-->\n<template>\n\t<CnFeaturesAndRoadmapView\n\t\t:repo=\"resolvedRepo\"\n\t\t:forge=\"resolvedForge\"\n\t\t:features=\"resolvedFeatures\"\n\t\t:disabled=\"resolvedDisabled\"\n\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t:capabilityComparison=\"resolvedCapabilityComparison\" />\n</template>\n\n<script>\n// `@nextcloud/initial-state` is a DECLARED (non-optional) peer dependency, so\n// it is imported statically — a lazy CommonJS resolution can never succeed from\n// `dist/esm/**` (the package's `exports` map is import-only and the ESM build\n// has no such function), which meant this helper used to return its fallback\n// unconditionally.\nimport { loadState } from '@nextcloud/initial-state'\nimport CnFeaturesAndRoadmapView from '../CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue'\nimport { DEFAULT_FORGE } from '../../utils/forge.js'\n\n/**\n * Read a key from `@nextcloud/initial-state`, tolerating an unprovisioned\n * slot (mirrors resolveManifestSentinels.js).\n *\n * @param {string} appId Nextcloud app ID.\n * @param {string} key Initial-state key (full key, not prefixed).\n * @param {unknown} fallback Default value when no provisioned slot exists.\n * @return {unknown} Provisioned value or the fallback.\n */\nfunction readInitialState(appId, key, fallback) {\n\ttry {\n\t\t// `loadState` throws only when the `#initial-state-*` element is absent\n\t\t// AND no fallback is given; a fallback IS given here, so this catch\n\t\t// covers the parse-error case only. It is a runtime condition, never a\n\t\t// module-resolution failure.\n\t\treturn loadState(appId, key, fallback)\n\t} catch {\n\t\t// Slot present but unparseable — fall back.\n\t}\n\treturn fallback\n}\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapPage',\n\n\tcomponents: {\n\t\tCnFeaturesAndRoadmapView,\n\t},\n\n\tinject: {\n\t\tcnAiContext: {\n\t\t\tdefault: () => ({ appId: 'unknown' }),\n\t\t},\n\n\t\t/**\n\t\t * Forge config provided by CnAppRoot from `manifest.nav.forge`.\n\t\t * Used as the fallback for `resolvedForge` so the whole app shares\n\t\t * one forge setting. Defaults to Codeberg when no ancestor.\n\t\t */\n\t\tcnFeatureRequestForge: {\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge. When\n\t\t * omitted, falls back to the loadState value, then to\n\t\t * `ConductionNL/<appId>` — the fleet convention on GitHub.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Target forge for the feature-request deep-link. Manifest config\n\t\t * > initialState > the `cnFeatureRequestForge` inject (CnAppRoot)\n\t\t * > Codeberg.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}|null}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest. When omitted, falls back to\n\t\t * the loadState value, then to an empty array.\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: null,\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag. When omitted, falls back to the\n\t\t * loadState value, then to false.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override for the OpenBuilt sidebar CTA target. Manifest config\n\t\t * > initialState > the view's own default (in-instance /apps/openbuilt).\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 for the LLM-skills sidebar CTA target. Manifest config\n\t\t * > initialState > docs.conduction.nl/ai-skills.\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 * Override for the Suggest sidebar CTA. When set, the sidebar\n\t\t * Suggest CTA renders as an anchor pointing here; when empty\n\t\t * (default) the view derives the forge's feature-request issue\n\t\t * form URL. Manifest config > initialState > '' (forge form).\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 * URL of the app's public documentation site. When set, an info\n\t\t * banner above the card grid points users at the docs. Manifest\n\t\t * config > initialState > '' (no banner).\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 * Optional capability comparison document, forwarded to the view.\n\t\t * Manifest config > initialState (`features_roadmap_capabilities`) >\n\t\t * null. When null the view renders its two stops as it always has.\n\t\t *\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 for the appId used to namespace loadState lookups.\n\t\t * Tests pass an explicit value; production reads it from the\n\t\t * `cnAiContext` inject populated by CnAppRoot.\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective app id used to namespace `loadState` calls.\n\t\t * Prop takes precedence over the cnAiContext inject so that\n\t\t * unit tests can pass a known id without mocking the inject.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\teffectiveAppId() {\n\t\t\treturn this.appId || this.cnAiContext?.appId || 'unknown'\n\t\t},\n\n\t\t/**\n\t\t * Effective repo. Manifest config > initialState > fallback.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedRepo() {\n\t\t\tif (this.repo) {\n\t\t\t\treturn this.repo\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_repo',\n\t\t\t\t`ConductionNL/${this.effectiveAppId}`,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective forge. Manifest config > initialState >\n\t\t * `cnFeatureRequestForge` inject (CnAppRoot) > DEFAULT_FORGE.\n\t\t *\n\t\t * @return {{type: string, baseUrl?: string}}\n\t\t */\n\t\tresolvedForge() {\n\t\t\tif (this.forge) {\n\t\t\t\treturn this.forge\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_forge',\n\t\t\t\tthis.cnFeatureRequestForge || { ...DEFAULT_FORGE },\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective features list. Manifest config > initialState > [].\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tresolvedFeatures() {\n\t\t\tif (this.features !== null) {\n\t\t\t\treturn this.features\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_features', [])\n\t\t},\n\n\t\t/**\n\t\t * Effective disabled flag. Manifest config > initialState > false.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tresolvedDisabled() {\n\t\t\tif (this.disabled !== null) {\n\t\t\t\treturn this.disabled\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_disabled', false)\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\treturn this.openbuiltUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_openbuilt_url', '')\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_llm_skills_url', '')\n\t\t},\n\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_suggest_url', '')\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_documentation_url', '')\n\t\t},\n\n\t\t/**\n\t\t * Effective capability comparison. Manifest config > initialState >\n\t\t * null.\n\t\t *\n\t\t * `null` and not `{}`: an empty object would make the view believe a\n\t\t * comparison exists and grow a third toggle stop over zero rows.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedCapabilityComparison() {\n\t\t\tif (this.capabilityComparison !== null) {\n\t\t\t\treturn this.capabilityComparison\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_capabilities', null)\n\t\t},\n\t},\n}\n</script>\n"],"names":["CnFeaturesAndRoadmapView"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFeaturesAndRoadmapPage.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue"],"sourcesContent":["<!--\n CnFeaturesAndRoadmapPage — Manifest-driven Features & Roadmap surface.\n\n v2 manifest page primitive for `type: \"roadmap\"`. Wraps\n `CnFeaturesAndRoadmapView` so consuming apps can declare\n `{ id: \"FeaturesRoadmap\", route: \"/features-roadmap\", type: \"roadmap\" }`\n in their manifest without writing a per-app wrapper that just\n unpacks initialState into the view's three props.\n\n Resolution order for each of the three values:\n\n 1. Explicit `pages[].config.<key>` from the manifest.\n 2. `loadState(appId, 'features_roadmap_<key>', <fallback>)`.\n 3. Hardcoded fallback (`repo` → `ConductionNL/<appId>` on GitHub;\n `forge` → the `cnFeatureRequestForge` inject, else DEFAULT_FORGE).\n\n `appId` comes from the `cnAiContext` inject that CnAppRoot already\n provides; consumers can override it explicitly via the prop for tests.\n\n Spec: ConductionNL/nextcloud-vue#264.\n-->\n<template>\n\t<CnFeaturesAndRoadmapView\n\t\t:repo=\"resolvedRepo\"\n\t\t:forge=\"resolvedForge\"\n\t\t:features=\"resolvedFeatures\"\n\t\t:disabled=\"resolvedDisabled\"\n\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t:capabilityComparison=\"resolvedCapabilityComparison\" />\n</template>\n\n<script>\n// `@nextcloud/initial-state` is a DECLARED (non-optional) peer dependency, so\n// it is imported statically — a lazy CommonJS resolution can never succeed from\n// `dist/esm/**` (the package's `exports` map is import-only and the ESM build\n// has no such function), which meant this helper used to return its fallback\n// unconditionally.\nimport { loadState } from '@nextcloud/initial-state'\nimport CnFeaturesAndRoadmapView from '../CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { DEFAULT_FORGE } from '../../utils/forge.js'\n\n/**\n * Read a key from `@nextcloud/initial-state`, tolerating an unprovisioned\n * slot (mirrors resolveManifestSentinels.js).\n *\n * @param {string} appId Nextcloud app ID.\n * @param {string} key Initial-state key (full key, not prefixed).\n * @param {unknown} fallback Default value when no provisioned slot exists.\n * @return {unknown} Provisioned value or the fallback.\n */\nfunction readInitialState(appId, key, fallback) {\n\ttry {\n\t\t// `loadState` throws only when the `#initial-state-*` element is absent\n\t\t// AND no fallback is given; a fallback IS given here, so this catch\n\t\t// covers the parse-error case only. It is a runtime condition, never a\n\t\t// module-resolution failure.\n\t\treturn loadState(appId, key, fallback)\n\t} catch {\n\t\t// Slot present but unparseable — fall back.\n\t}\n\treturn fallback\n}\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapPage',\n\n\tcomponents: {\n\t\tCnFeaturesAndRoadmapView,\n\t},\n\n\tinject: {\n\t\tcnAiContext: {\n\t\t\tdefault: () => ({ appId: 'unknown' }),\n\t\t},\n\n\t\t/**\n\t\t * Forge config provided by CnAppRoot from `manifest.nav.forge`.\n\t\t * Used as the fallback for `resolvedForge` so the whole app shares\n\t\t * one forge setting. Defaults to Codeberg when no ancestor.\n\t\t */\n\t\tcnFeatureRequestForge: {\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge. When\n\t\t * omitted, falls back to the loadState value, then to\n\t\t * `ConductionNL/<appId>` — the fleet convention on GitHub.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Target forge for the feature-request deep-link. Manifest config\n\t\t * > initialState > the `cnFeatureRequestForge` inject (CnAppRoot)\n\t\t * > Codeberg.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}|null}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest. When omitted, falls back to\n\t\t * the loadState value, then to an empty array.\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: null,\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag. When omitted, falls back to the\n\t\t * loadState value, then to false.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override for the OpenBuilt sidebar CTA target. Manifest config\n\t\t * > initialState > the view's own default (in-instance /apps/openbuilt).\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 for the LLM-skills sidebar CTA target. Manifest config\n\t\t * > initialState > docs.conduction.nl/ai-skills.\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 * Override for the Suggest sidebar CTA. When set, the sidebar\n\t\t * Suggest CTA renders as an anchor pointing here; when empty\n\t\t * (default) the view derives the forge's feature-request issue\n\t\t * form URL. Manifest config > initialState > '' (forge form).\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 * URL of the app's public documentation site. When set, an info\n\t\t * banner above the card grid points users at the docs. Manifest\n\t\t * config > initialState > '' (no banner).\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 * Optional capability comparison document, forwarded to the view.\n\t\t * Manifest config > initialState (`features_roadmap_capabilities`) >\n\t\t * null. When null the view renders its two stops as it always has.\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. The same goes for the `features_roadmap_capabilities` initial\n\t\t * state key. A development build warns once when either 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 for the appId used to namespace loadState lookups.\n\t\t * Tests pass an explicit value; production reads it from the\n\t\t * `cnAiContext` inject populated by CnAppRoot.\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective app id used to namespace `loadState` calls.\n\t\t * Prop takes precedence over the cnAiContext inject so that\n\t\t * unit tests can pass a known id without mocking the inject.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\teffectiveAppId() {\n\t\t\treturn this.appId || this.cnAiContext?.appId || 'unknown'\n\t\t},\n\n\t\t/**\n\t\t * Effective repo. Manifest config > initialState > fallback.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedRepo() {\n\t\t\tif (this.repo) {\n\t\t\t\treturn this.repo\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_repo',\n\t\t\t\t`ConductionNL/${this.effectiveAppId}`,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective forge. Manifest config > initialState >\n\t\t * `cnFeatureRequestForge` inject (CnAppRoot) > DEFAULT_FORGE.\n\t\t *\n\t\t * @return {{type: string, baseUrl?: string}}\n\t\t */\n\t\tresolvedForge() {\n\t\t\tif (this.forge) {\n\t\t\t\treturn this.forge\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_forge',\n\t\t\t\tthis.cnFeatureRequestForge || { ...DEFAULT_FORGE },\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective features list. Manifest config > initialState > [].\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tresolvedFeatures() {\n\t\t\tif (this.features !== null) {\n\t\t\t\treturn this.features\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_features', [])\n\t\t},\n\n\t\t/**\n\t\t * Effective disabled flag. Manifest config > initialState > false.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tresolvedDisabled() {\n\t\t\tif (this.disabled !== null) {\n\t\t\t\treturn this.disabled\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_disabled', false)\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\treturn this.openbuiltUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_openbuilt_url', '')\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_llm_skills_url', '')\n\t\t},\n\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_suggest_url', '')\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_documentation_url', '')\n\t\t},\n\n\t\t/**\n\t\t * Effective capability comparison. Manifest config > initialState >\n\t\t * null.\n\t\t *\n\t\t * `null` and not `{}`: an empty object would make the view believe a\n\t\t * comparison exists and grow a third toggle stop over zero rows.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedCapabilityComparison() {\n\t\t\tif (this.capabilityComparison !== null) {\n\t\t\t\treturn this.capabilityComparison\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_capabilities', null)\n\t\t},\n\t},\n\n\tmounted() {\n\t\tif (this.resolvedCapabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapPage `capabilityComparison` / the `features_roadmap_capabilities` initial state')\n\t\t}\n\t},\n}\n</script>\n"],"names":["CnFeaturesAndRoadmapView"],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ import __sfc_main$4 from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSid
|
|
|
12
12
|
import __sfc_main$1 from '../CnFeaturesTab/CnFeaturesTab.vue.js';
|
|
13
13
|
import __sfc_main$2 from '../CnRoadmapTab/CnRoadmapTab.vue.js';
|
|
14
14
|
import __sfc_main$3 from '../CnSupportDialog/CnSupportDialog.vue.js';
|
|
15
|
+
import { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js';
|
|
15
16
|
import { DEFAULT_FORGE, buildFeatureRequestUrl } from '../../utils/forge.js';
|
|
16
17
|
|
|
17
18
|
const DEFAULT_OPENBUILT_PATH = '/apps/openbuilt';
|
|
@@ -112,6 +113,11 @@ var script = {
|
|
|
112
113
|
* When null (default) the toggle keeps its two stops and the view
|
|
113
114
|
* renders exactly as it does for every app that passes nothing.
|
|
114
115
|
*
|
|
116
|
+
* 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.
|
|
117
|
+
* A development build warns once when it is set.
|
|
118
|
+
*
|
|
119
|
+
* @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.
|
|
120
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
|
|
115
121
|
* @type {object|null}
|
|
116
122
|
*/
|
|
117
123
|
capabilityComparison: {
|
|
@@ -425,6 +431,9 @@ var script = {
|
|
|
425
431
|
|
|
426
432
|
mounted() {
|
|
427
433
|
this.publishHoistedSidebar();
|
|
434
|
+
if (this.capabilityComparison) {
|
|
435
|
+
warnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`');
|
|
436
|
+
}
|
|
428
437
|
},
|
|
429
438
|
|
|
430
439
|
beforeUnmount() {
|
|
@@ -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<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\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\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 CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\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\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},\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 * 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 * @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 * 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},\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\tif (this.disabled) {\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","CnFeaturesTab","CnRoadmapTab","CnSupportDialog","t","CnFeaturesAndRoadmapSidebar"],"mappings":";;;;;;;;;;;;;;;;AAsHA,MAAM,sBAAqB,GAAI,kBAAgB;AAC/C,MAAM,sBAAqB,GAAI,uCAAqC;AACpE,MAAM,kBAAiB,GAAI,2CAAyC;AACpE,MAAM,mBAAoB,GAAE,oCAAkC;AAC9D,MAAM,oBAAqB,GAAE,sBAAoB;AACjD,MAAM,qBAAsB,GAAE,0BAAwB;;AAEtD,aAAe;AACd,CAAA,IAAI,EAAE,0BAA0B;;CAEhC,UAAU,EAAE;AACX,EAAA,QAAQ;AACR,EAAA,cAAc;EACd,UAAU;AACV,EAAA,kBAAkB;EAClB,WAAW;EACX,IAAI;EACJ,WAAW;EACX,WAAW;AACX,qBAAAA,UAAiB;iBACjBC,YAAa;gBACbC,YAAY;AACZ,mBAAAC,YAAe;EACf;;AAED,CAAA,MAAM,EAAE;AACP;;;;;;;AAOA,EAAA,mBAAmB,EAAE,EAAE,OAAO,EAAE,OAAO;AACvC;;AAEC;;AAEA;;EAED,oBAAoB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAG,EAAG,CAAA,EAAG;EAC1D;;AAED,CAAA,KAAK,EAAE;AACN;;;EAGA,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;;AAED;;;;AAIC;AACA;AACA;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,GAAG,aAAY,EAAG,CAAC;GACrC;;AAED;AACC;AACA;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;AAED;;AAEC;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;;AAGC;;AAEA;;;AAGA;;AAEA;;;AAGA;AACA;;AAED,EAAA,oBAAoB,EAAE;GACrB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;;AAIC;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;AACA;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;;;AAIA;AACA;;;EAGD,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;AAKC;AACA;;;AAGD,EAAA,gBAAgB,EAAE;GACjB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;;AAMC;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;AACA;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;AAIC;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;;;;;;AAOA;;;AAGD,EAAA,iBAAiB,EAAE;GAClB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;AACA;;;EAGD,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,kBAAkB;GAC3B;;AAED;;;AAGC;;;EAGD,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,mBAAmB;GAC5B;;AAED;;;AAGC;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,oBAAoB;GAC7B;;AAED;;;AAGC;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,qBAAqB;GAC9B;EACD;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,UAAU,EAAE,UAAU;GACtB,iBAAiB,EAAE,KAAK;AACzB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;;AAGC;AACA;AACA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,OAAO,IAAI,CAAC,oBAAmB;AAC9B,MAAE,CAAC,UAAU,EAAE,cAAc,EAAE,SAAS,CAAA;MACtC,CAAC,UAAU,EAAE,SAAS,CAAA;GACzB;;AAED;AACC;;EAED,QAAQ,GAAG;GACV,MAAM,KAAI,GAAI,IAAI,CAAC,eAAa;AAChC,GAAA,MAAM,KAAM,GAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAA;AAC3C,GAAA,OAAO,KAAK,CAAC,CAAC,KAAM,GAAE,CAAC,IAAI,KAAK,CAAC,MAAM,CAAA;GACvC;;EAED,WAAW,GAAG;AACb,GAAA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAA;GACrC;;EAED,WAAW,GAAG;AACb,GAAA,IAAI,IAAI,CAAC,QAAS,KAAI,cAAc,EAAE;AACrC,IAAA,OAAOC,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAA;AAC9C,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,QAAO,KAAM,SAAQ;AAChC,MAAEA,SAAC,CAAC,eAAe,EAAE,cAAc,CAAA;AACnC,MAAEA,SAAC,CAAC,eAAe,EAAE,eAAe,CAAA;GACrC;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,iBAAiB,CAAA;GAC3C;;AAED,EAAA,aAAa,GAAG;AACf,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,sDAAsD,CAAA;GAChF;;AAED,EAAA,mBAAmB,GAAG;GACrB,OAAOA,SAAC,CAAC,eAAe,EAAE,qFAAqF,CAAA;GAC/G;;EAED,oBAAoB,GAAG;AACtB,GAAA,IAAI,IAAI,CAAC,YAAY,EAAE;IACtB,OAAO,IAAI,CAAC,YAAW;AACxB,IAAA;AACA,GAAA,OAAO,WAAW,CAAC,sBAAsB,CAAA;GACzC;;EAED,oBAAoB,GAAG;GACtB,OAAO,IAAI,CAAC,YAAW,IAAK,sBAAqB;GACjD;;EAED,wBAAwB,GAAG;AAC1B,GAAA,OAAO,IAAI,CAAC,gBAAe,IAAK,EAAC;GACjC;;EAED,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,IAAI,CAAC,IAAK,IAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC3C,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAC;GAC3C;;AAED,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,QAAO;GACnC;;AAED,EAAA,eAAe,GAAG;AACjB,GAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AACjB,IAAA,OAAO,IAAI,CAAC,OAAM;AACnB,IAAA;GACA,MAAM,OAAO,IAAI,CAAC,SAAO;AACzB,GAAA,IAAI,CAAC,IAAI,EAAE;AACV,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAG,GAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;GAClD;;AAED,EAAA,mBAAmB,GAAG;AACrB,GAAA,OAAO,IAAI,CAAC,WAAU,KAAM,IAAI,CAAC,eAAc;MAC5C,CAAC,gCAAgC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAA;AAC1D,MAAE,EAAE,CAAA;GACL;;EAED,yBAAyB,GAAG;GAC3B,IAAI,IAAI,CAAC,iBAAiB,EAAE;IAC3B,OAAO,IAAI,CAAC,iBAAgB;AAC7B,IAAA;AACA,GAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;AACf,IAAA,OAAO,EAAC;AACT,IAAA;;;AAGA;GACA,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAA;GACnD;;AAED;;;;AAIC;AACA;;AAED,EAAA,kBAAkB,GAAG;GACpB,OAAO,IAAI,CAAC,UAAW,IAAG,IAAI,CAAC,yBAAwB;GACvD;;EAED,0BAA0B,GAAG;GAC5B,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,CAAA;GACzD;;AAED,EAAA,eAAe,GAAG;GACjB,OAAOA,SAAC,CAAC,eAAe,EAAE,kCAAkC,CAAA;GAC5D;;AAED,EAAA,iBAAiB,GAAG;AACnB;AACA,GAAA,OAAO,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAA;GAChE;;AAED,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,2CAA2C,CAAA;GACrE;EACD;;AAED,CAAA,KAAK,EAAE;;;;AAIN;AACA,EAAA,cAAc,CAAC,KAAK,EAAE;AACrB,GAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;IACrC,IAAI,CAAC,aAAa,KAAK,CAAC,CAAC,EAAA;AAC1B,IAAA;GACA;EACD;;AAED,CAAA,OAAO,GAAG;EACT,IAAI,CAAC,qBAAqB,GAAC;EAC3B;;AAED,CAAA,aAAa,GAAG;;;EAGf,IAAI,IAAI,CAAC,mBAAkB,IAAK,IAAI,CAAC,oBAAoB,EAAE;AAC1D,GAAA,IAAI,CAAC,oBAAoB,CAAC,KAAI,GAAI,KAAG;AACtC,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;AACR;AACC;AACA;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;GACf,IAAI,IAAK,KAAI,cAAc,EAAE;AAC5B,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,cAAc,CAAA;AACzC,IAAA;AACA,GAAA,OAAO,IAAG,KAAM,SAAQ;MACrBA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;MAC5BA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;GAChC;;EAED,UAAU,GAAG;GACZ,IAAI,CAAC,aAAa,IAAI,CAAC,SAAO;GAC9B;;AAED,EAAA,iBAAiB,GAAG;GACnB,IAAI,CAAC,iBAAkB,GAAE,KAAG;GAC5B;;AAED;;;;;;AAMC;;EAED,qBAAqB,GAAG;GACvB,IAAI,CAAC,IAAI,CAAC,mBAAkB,IAAK,CAAC,IAAI,CAAC,oBAAoB,EAAE;IAC5D,MAAK;AACN,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AAClB,IAAA,IAAI,CAAC,oBAAoB,CAAC,KAAI,GAAI,KAAG;IACrC,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,oBAAoB,CAAC,QAAQ;;;IAGjC,SAAS,EAAE,OAAO,CAACC,YAA2B,CAAC;AAC/C,IAAA,KAAK,EAAE;AACN,KAAA,YAAY,EAAE,IAAI,CAAC,oBAAoB;AACvC,KAAA,YAAY,EAAE,IAAI,CAAC,oBAAoB;;;AAGvC;AACA,KAAA,UAAU,EAAE,IAAI,CAAC,kBAAkB;KACnC;;IAED,SAAS,EAAE;AACV,KAAA,OAAO,EAAE,MAAM,IAAI,CAAC,iBAAiB,EAAE;KACvC;AACF,KAAA;GACA;EACD;AACF;;;;"}
|
|
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<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\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\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 CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\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\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},\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 * 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 * 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\tif (this.disabled) {\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","CnFeaturesTab","CnRoadmapTab","CnSupportDialog","t","CnFeaturesAndRoadmapSidebar"],"mappings":";;;;;;;;;;;;;;;;;AAuHA,MAAM,sBAAqB,GAAI,kBAAgB;AAC/C,MAAM,sBAAqB,GAAI,uCAAqC;AACpE,MAAM,kBAAiB,GAAI,2CAAyC;AACpE,MAAM,mBAAoB,GAAE,oCAAkC;AAC9D,MAAM,oBAAqB,GAAE,sBAAoB;AACjD,MAAM,qBAAsB,GAAE,0BAAwB;;AAEtD,aAAe;AACd,CAAA,IAAI,EAAE,0BAA0B;;CAEhC,UAAU,EAAE;AACX,EAAA,QAAQ;AACR,EAAA,cAAc;EACd,UAAU;AACV,EAAA,kBAAkB;EAClB,WAAW;EACX,IAAI;EACJ,WAAW;EACX,WAAW;AACX,qBAAAA,UAAiB;iBACjBC,YAAa;gBACbC,YAAY;AACZ,mBAAAC,YAAe;EACf;;AAED,CAAA,MAAM,EAAE;AACP;;;;;;;AAOA,EAAA,mBAAmB,EAAE,EAAE,OAAO,EAAE,OAAO;AACvC;;AAEC;;AAEA;;EAED,oBAAoB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAG,EAAG,CAAA,EAAG;EAC1D;;AAED,CAAA,KAAK,EAAE;AACN;;;EAGA,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;;AAED;;;;AAIC;AACA;AACA;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,GAAG,aAAY,EAAG,CAAC;GACrC;;AAED;AACC;AACA;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;AAED;;AAEC;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;;AAGC;;AAEA;;;AAGA;;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAED,EAAA,oBAAoB,EAAE;GACrB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;;AAIC;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;AACA;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;;;AAIA;AACA;;;EAGD,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;AAKC;AACA;;;AAGD,EAAA,gBAAgB,EAAE;GACjB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;;AAMC;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;AACA;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;AAIC;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;;;;;;AAOA;;;AAGD,EAAA,iBAAiB,EAAE;GAClB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;AACA;;;EAGD,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,kBAAkB;GAC3B;;AAED;;;AAGC;;;EAGD,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,mBAAmB;GAC5B;;AAED;;;AAGC;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,oBAAoB;GAC7B;;AAED;;;AAGC;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,qBAAqB;GAC9B;EACD;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,UAAU,EAAE,UAAU;GACtB,iBAAiB,EAAE,KAAK;AACzB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;;AAGC;AACA;AACA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,OAAO,IAAI,CAAC,oBAAmB;AAC9B,MAAE,CAAC,UAAU,EAAE,cAAc,EAAE,SAAS,CAAA;MACtC,CAAC,UAAU,EAAE,SAAS,CAAA;GACzB;;AAED;AACC;;EAED,QAAQ,GAAG;GACV,MAAM,KAAI,GAAI,IAAI,CAAC,eAAa;AAChC,GAAA,MAAM,KAAM,GAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAA;AAC3C,GAAA,OAAO,KAAK,CAAC,CAAC,KAAM,GAAE,CAAC,IAAI,KAAK,CAAC,MAAM,CAAA;GACvC;;EAED,WAAW,GAAG;AACb,GAAA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAA;GACrC;;EAED,WAAW,GAAG;AACb,GAAA,IAAI,IAAI,CAAC,QAAS,KAAI,cAAc,EAAE;AACrC,IAAA,OAAOC,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAA;AAC9C,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,QAAO,KAAM,SAAQ;AAChC,MAAEA,SAAC,CAAC,eAAe,EAAE,cAAc,CAAA;AACnC,MAAEA,SAAC,CAAC,eAAe,EAAE,eAAe,CAAA;GACrC;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,iBAAiB,CAAA;GAC3C;;AAED,EAAA,aAAa,GAAG;AACf,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,sDAAsD,CAAA;GAChF;;AAED,EAAA,mBAAmB,GAAG;GACrB,OAAOA,SAAC,CAAC,eAAe,EAAE,qFAAqF,CAAA;GAC/G;;EAED,oBAAoB,GAAG;AACtB,GAAA,IAAI,IAAI,CAAC,YAAY,EAAE;IACtB,OAAO,IAAI,CAAC,YAAW;AACxB,IAAA;AACA,GAAA,OAAO,WAAW,CAAC,sBAAsB,CAAA;GACzC;;EAED,oBAAoB,GAAG;GACtB,OAAO,IAAI,CAAC,YAAW,IAAK,sBAAqB;GACjD;;EAED,wBAAwB,GAAG;AAC1B,GAAA,OAAO,IAAI,CAAC,gBAAe,IAAK,EAAC;GACjC;;EAED,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,IAAI,CAAC,IAAK,IAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC3C,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAC;GAC3C;;AAED,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,QAAO;GACnC;;AAED,EAAA,eAAe,GAAG;AACjB,GAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AACjB,IAAA,OAAO,IAAI,CAAC,OAAM;AACnB,IAAA;GACA,MAAM,OAAO,IAAI,CAAC,SAAO;AACzB,GAAA,IAAI,CAAC,IAAI,EAAE;AACV,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAG,GAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;GAClD;;AAED,EAAA,mBAAmB,GAAG;AACrB,GAAA,OAAO,IAAI,CAAC,WAAU,KAAM,IAAI,CAAC,eAAc;MAC5C,CAAC,gCAAgC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAA;AAC1D,MAAE,EAAE,CAAA;GACL;;EAED,yBAAyB,GAAG;GAC3B,IAAI,IAAI,CAAC,iBAAiB,EAAE;IAC3B,OAAO,IAAI,CAAC,iBAAgB;AAC7B,IAAA;AACA,GAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;AACf,IAAA,OAAO,EAAC;AACT,IAAA;;;AAGA;GACA,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAA;GACnD;;AAED;;;;AAIC;AACA;;AAED,EAAA,kBAAkB,GAAG;GACpB,OAAO,IAAI,CAAC,UAAW,IAAG,IAAI,CAAC,yBAAwB;GACvD;;EAED,0BAA0B,GAAG;GAC5B,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,CAAA;GACzD;;AAED,EAAA,eAAe,GAAG;GACjB,OAAOA,SAAC,CAAC,eAAe,EAAE,kCAAkC,CAAA;GAC5D;;AAED,EAAA,iBAAiB,GAAG;AACnB;AACA,GAAA,OAAO,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAA;GAChE;;AAED,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,2CAA2C,CAAA;GACrE;EACD;;AAED,CAAA,KAAK,EAAE;;;;AAIN;AACA,EAAA,cAAc,CAAC,KAAK,EAAE;AACrB,GAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;IACrC,IAAI,CAAC,aAAa,KAAK,CAAC,CAAC,EAAA;AAC1B,IAAA;GACA;EACD;;AAED,CAAA,OAAO,GAAG;EACT,IAAI,CAAC,qBAAqB,GAAC;EAC3B,IAAI,IAAI,CAAC,oBAAoB,EAAE;AAC9B,GAAA,kCAAkC,CAAC,iDAAiD,EAAA;AACrF,GAAA;EACA;;AAED,CAAA,aAAa,GAAG;;;EAGf,IAAI,IAAI,CAAC,mBAAkB,IAAK,IAAI,CAAC,oBAAoB,EAAE;AAC1D,GAAA,IAAI,CAAC,oBAAoB,CAAC,KAAI,GAAI,KAAG;AACtC,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;AACR;AACC;AACA;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;GACf,IAAI,IAAK,KAAI,cAAc,EAAE;AAC5B,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,cAAc,CAAA;AACzC,IAAA;AACA,GAAA,OAAO,IAAG,KAAM,SAAQ;MACrBA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;MAC5BA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;GAChC;;EAED,UAAU,GAAG;GACZ,IAAI,CAAC,aAAa,IAAI,CAAC,SAAO;GAC9B;;AAED,EAAA,iBAAiB,GAAG;GACnB,IAAI,CAAC,iBAAkB,GAAE,KAAG;GAC5B;;AAED;;;;;;AAMC;;EAED,qBAAqB,GAAG;GACvB,IAAI,CAAC,IAAI,CAAC,mBAAkB,IAAK,CAAC,IAAI,CAAC,oBAAoB,EAAE;IAC5D,MAAK;AACN,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AAClB,IAAA,IAAI,CAAC,oBAAoB,CAAC,KAAI,GAAI,KAAG;IACrC,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,oBAAoB,CAAC,QAAQ;;;IAGjC,SAAS,EAAE,OAAO,CAACC,YAA2B,CAAC;AAC/C,IAAA,KAAK,EAAE;AACN,KAAA,YAAY,EAAE,IAAI,CAAC,oBAAoB;AACvC,KAAA,YAAY,EAAE,IAAI,CAAC,oBAAoB;;;AAGvC;AACA,KAAA,UAAU,EAAE,IAAI,CAAC,kBAAkB;KACnC;;IAED,SAAS,EAAE;AACV,KAAA,OAAO,EAAE,MAAM,IAAI,CAAC,iBAAiB,EAAE;KACvC;AACF,KAAA;GACA;EACD;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFeaturesAndRoadmapView.vue3.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue?vue&type=template&id=7c44400f&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<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\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\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 CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\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\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},\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 * 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 * @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 * 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},\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\tif (this.disabled) {\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","_Fragment","_createElementVNode","_toDisplayString","_createBlock","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFeaturesAndRoadmapView.vue3.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue?vue&type=template&id=7c44400f&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<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\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\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 CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\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\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},\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 * 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 * 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\tif (this.disabled) {\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","_Fragment","_createElementVNode","_toDisplayString","_createBlock","_createTextVNode"],"mappings":"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"}
|
|
@@ -80,7 +80,7 @@ var script = {
|
|
|
80
80
|
* Persisted widget content: `{title, subtitle, backgroundImageUrl,
|
|
81
81
|
* backgroundImageFileId, backgroundColor, overlayMode, overlayColor,
|
|
82
82
|
* overlayOpacity, textColor, textAlign, verticalAlign, height, cta,
|
|
83
|
-
* greeting, showDate, plain, ground, views}`. All fields are optional except
|
|
83
|
+
* greeting, showDate, kicker, emblem, plain, ground, views}`. All fields are optional except
|
|
84
84
|
* `title` (or `greeting`); unknown enum values collapse to documented
|
|
85
85
|
* defaults and the renderer never throws. `views` is
|
|
86
86
|
* `{ ariaLabel?, options: [{ label, route, params? } | { label, view }] }`:
|
|
@@ -91,7 +91,12 @@ var script = {
|
|
|
91
91
|
* own. `ground: true`
|
|
92
92
|
* draws the greeting on the page ground: the plain look with no
|
|
93
93
|
* padding, a 32px heading and the date line 6px above it (a dashboard
|
|
94
|
-
* also drops the widget's card for it).
|
|
94
|
+
* also drops the widget's card for it). A ground greeting takes its
|
|
95
|
+
* own height rather than its grid cell's, so a view switch sits on
|
|
96
|
+
* the heading's line. `kicker` (an i18n key) prefixes the date line, as
|
|
97
|
+
* "Customer contact · Tuesday 6 October". `emblem` (a URL, or `true`
|
|
98
|
+
* for the theme's `--nldesign-emblem-url`) draws the emblem beside the
|
|
99
|
+
* date line and heading, 52px high (`--cn-header-emblem-size`).
|
|
95
100
|
*
|
|
96
101
|
* @type {object}
|
|
97
102
|
*/
|
|
@@ -336,13 +341,30 @@ var script = {
|
|
|
336
341
|
},
|
|
337
342
|
|
|
338
343
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
344
|
+
* The kicker line above the heading: `content.kicker` (through the
|
|
345
|
+
* host translate function, e.g. "Customer contact") and today's date
|
|
346
|
+
* written out ("Monday 5 October 2026") when `showDate` is on, joined
|
|
347
|
+
* by " · ". '' when neither is set. Without `kicker` this is the date
|
|
348
|
+
* line as before.
|
|
341
349
|
*
|
|
342
350
|
* @return {string}
|
|
343
351
|
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
344
352
|
*/
|
|
345
353
|
dateLine() {
|
|
354
|
+
const kicker = this.content && typeof this.content.kicker === 'string' && this.content.kicker !== ''
|
|
355
|
+
? this.effectiveTranslate(this.content.kicker)
|
|
356
|
+
: '';
|
|
357
|
+
const date = this.dateText;
|
|
358
|
+
return [kicker, date].filter((part) => part !== '').join(' · ')
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Today's date written out, or '' when `showDate` is off.
|
|
363
|
+
*
|
|
364
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-greeting-kicker-can-carry-a-prefix
|
|
365
|
+
* @return {string}
|
|
366
|
+
*/
|
|
367
|
+
dateText() {
|
|
346
368
|
if (!this.content || this.content.showDate !== true) {
|
|
347
369
|
return ''
|
|
348
370
|
}
|
|
@@ -374,6 +396,21 @@ var script = {
|
|
|
374
396
|
}
|
|
375
397
|
},
|
|
376
398
|
|
|
399
|
+
/**
|
|
400
|
+
* The emblem beside the greeting: the URL `content.emblem` names,
|
|
401
|
+
* `true` for the theme's emblem, or null without the key.
|
|
402
|
+
*
|
|
403
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-greeting-can-carry-the-emblem
|
|
404
|
+
* @return {string|true|null}
|
|
405
|
+
*/
|
|
406
|
+
emblem() {
|
|
407
|
+
const value = this.content && this.content.emblem;
|
|
408
|
+
if (value === true) {
|
|
409
|
+
return true
|
|
410
|
+
}
|
|
411
|
+
return typeof value === 'string' && value !== '' ? resolveImageUrl(value) : null
|
|
412
|
+
},
|
|
413
|
+
|
|
377
414
|
/** Whether a non-empty subtitle is set. */
|
|
378
415
|
hasSubtitle() {
|
|
379
416
|
return this.subtitle !== ''
|
|
@@ -514,7 +551,12 @@ var script = {
|
|
|
514
551
|
return this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'
|
|
515
552
|
},
|
|
516
553
|
|
|
517
|
-
/**
|
|
554
|
+
/**
|
|
555
|
+
* Inline style for the banner wrapper.
|
|
556
|
+
*
|
|
557
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-ground-greeting-lines-its-switch-up-with-the-heading
|
|
558
|
+
* @return {object}
|
|
559
|
+
*/
|
|
518
560
|
wrapperStyle() {
|
|
519
561
|
const style = {
|
|
520
562
|
position: 'relative',
|
|
@@ -523,7 +565,11 @@ var script = {
|
|
|
523
565
|
// widget. A fixed pixel height ignored the cell, which made the
|
|
524
566
|
// banner un-resizable and made it overflow/scroll inside a
|
|
525
567
|
// smaller cell. The grid controls the size now.
|
|
526
|
-
|
|
568
|
+
// On the page ground there is no banner to fill the cell with:
|
|
569
|
+
// the greeting takes its own height, so the view switch lines
|
|
570
|
+
// up with the heading instead of the bottom of a taller cell,
|
|
571
|
+
// and a size-to-content cell can shrink to it.
|
|
572
|
+
height: this.isGround ? 'auto' : '100%',
|
|
527
573
|
overflow: 'hidden',
|
|
528
574
|
'background-color': this.backgroundColor,
|
|
529
575
|
};
|
|
@@ -559,6 +605,7 @@ var script = {
|
|
|
559
605
|
* Inline style for the content flex container.
|
|
560
606
|
*
|
|
561
607
|
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
608
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-ground-greeting-lines-its-switch-up-with-the-heading
|
|
562
609
|
*/
|
|
563
610
|
contentStyle() {
|
|
564
611
|
return {
|
|
@@ -567,7 +614,7 @@ var script = {
|
|
|
567
614
|
// Beside a view switch the content shares the row instead of
|
|
568
615
|
// taking the whole width (see .cn-header-widget--with-views).
|
|
569
616
|
width: this.viewOptions.length > 0 ? 'auto' : '100%',
|
|
570
|
-
height: '100%',
|
|
617
|
+
height: this.isGround ? 'auto' : '100%',
|
|
571
618
|
display: 'flex',
|
|
572
619
|
'flex-direction': 'column',
|
|
573
620
|
'align-items': this.flexAlignFromTextAlign,
|