@conduction/nextcloud-vue 2.60.0 → 2.62.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/CnAppNav/CnAppNav.vue2.js +169 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +88 -4
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +40 -8
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +40 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +6 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +3 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
- package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
- package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
- package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
- package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +6 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +6 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +198 -9
- package/dist/esm/utils/headers.js +28 -2
- package/dist/esm/utils/headers.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 +9162 -8283
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +198 -9
- package/l10n/en.json +6 -0
- package/l10n/nl.json +6 -0
- package/package.json +3 -2
- package/scripts/.jsdoc-baselines.json +1 -0
- package/scripts/manifest-schema-hash.js +41 -0
- package/scripts/record-manifest-schema-hash.js +52 -0
- package/src/components/CnAppNav/CnAppNav.vue +169 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +122 -4
- package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
- package/src/components/CnDashboardPage/CnDashboardPage.vue +40 -8
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +42 -17
- package/src/components/CnDetailPage/CnDetailPage.vue +4 -1
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +3 -1
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +517 -0
- package/src/components/CnLinkCardsPage/index.js +4 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
- package/src/components/CnPageRenderer/pageTypes.js +1 -0
- package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
- package/src/components/CnStorePage/CnStorePage.vue +7 -0
- package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
- package/src/components/index.js +1 -0
- package/src/css/detail-page.css +10 -2
- package/src/css/table.css +1 -2
- package/src/index.js +7 -0
- package/src/public/index.js +5 -0
- package/src/schemas/app-manifest-v2.schema.json +45 -3
- package/src/types/manifest.d.ts +1 -1
- package/src/utils/headers.js +28 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWikiPage.vue2.js","sources":["../../../../src/components/CnWikiPage/CnWikiPage.vue"],"sourcesContent":["<!--\n CnWikiPage — Manifest-driven markdown article surface.\n\n Renders one wiki / docs / knowledge-base article declared in\n `pages[].config` for `type: \"wiki\"` pages. Closes the gap that\n forces every consumer's docs / help / article-detail route onto\n `type: \"custom\"`.\n\n The page reads its content from props rather than fetching itself.\n Following the precedent set by CnFilesPage and CnFormPage, the\n consumer (or a wiki-aware wrapper) populates `article` (object)\n and `tree` (array) — typically from a `useDetailView()` /\n list-call against the manifest-declared register/schema. This\n keeps the lib zero-dep on a particular OpenRegister fetcher\n while still making the manifest config the source of truth for\n field names.\n\n Markdown rendering is delegated to the `cnRenderMarkdown` composable\n which configures `marked` with `{ gfm: true, breaks: false }` —\n GitHub-flavoured markdown, no auto-`<br>` on newlines.\n\n Slots:\n - `#header` — overrides the default header. Scope `{ title, article }`.\n - `#sidebar` — overrides the default tree sidebar. Scope `{ tree, onClick }`.\n - `#body` — overrides the default markdown body. Scope `{ article, html }`.\n - `#empty` — replaces the empty-state when `article` is null.\n\n Events:\n - `@tree-click` — `node` whenever a sidebar tree node is clicked.\n - `@error` — error object if markdown parsing throws.\n\n Spec: REQ-MWPT-* (manifest-wiki-page-type).\n-->\n<template>\n\t<div class=\"cn-wiki-page\" data-testid=\"cn-wiki-page\" :class=\"{ 'cn-wiki-page--has-sidebar': hasSidebar }\">\n\t\t<!--\n\t\t\t@slot sidebar\n\t\t\t@description Replaces the default sidebar tree. Scoped props `{ tree, onClick }` —\n\t\t\t`onClick(node)` re-emits `@tree-click` with the node.\n\t\t-->\n\t\t<slot\n\t\t\tv-if=\"hasSidebar\"\n\t\t\tname=\"sidebar\"\n\t\t\t:tree=\"tree\"\n\t\t\t:onClick=\"onTreeClick\">\n\t\t\t<nav class=\"cn-wiki-page__sidebar\">\n\t\t\t\t<ul class=\"cn-wiki-page__tree\">\n\t\t\t\t\t<CnWikiTreeNode\n\t\t\t\t\t\tv-for=\"node in tree\"\n\t\t\t\t\t\t:key=\"treeKey(node)\"\n\t\t\t\t\t\t:node=\"node\"\n\t\t\t\t\t\t:titleField=\"effectiveSidebarTitleField\"\n\t\t\t\t\t\t:treeField=\"treeField\"\n\t\t\t\t\t\t@click=\"onTreeClick\" />\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t</slot>\n\n\t\t<div class=\"cn-wiki-page__main\">\n\t\t\t<!--\n\t\t\t\t@slot header\n\t\t\t\t@description Replaces the default page header. Scoped props `{ title, article }`.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tname=\"header\"\n\t\t\t\t:title=\"resolvedTitle\"\n\t\t\t\t:article=\"article\">\n\t\t\t\t<header v-if=\"resolvedTitle\" class=\"cn-wiki-page__header\">\n\t\t\t\t\t<h1 class=\"cn-wiki-page__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h1>\n\t\t\t\t</header>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot empty\n\t\t\t\t@description Replaces the empty-state shown when `article` is null/undefined.\n\t\t\t-->\n\t\t\t<slot v-if=\"!article\" name=\"empty\">\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\t:name=\"emptyText\"\n\t\t\t\t\t:description=\"emptyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot body\n\t\t\t\t@description Replaces the default markdown body. Scoped props `{ article, html }` —\n\t\t\t\t`html` is the pre-parsed markdown so a custom renderer can wrap it.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tv-else\n\t\t\t\tname=\"body\"\n\t\t\t\t:article=\"article\"\n\t\t\t\t:html=\"renderedBody\">\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"renderedBody\"\n\t\t\t\t\tclass=\"cn-wiki-page__body\"\n\t\t\t\t\tv-html=\"renderedBody\" /><!-- eslint-disable-line vue/no-v-html -->\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else\n\t\t\t\t\t:name=\"emptyBodyText\"\n\t\t\t\t\t:description=\"emptyBodyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport CnWikiTreeNode from './CnWikiTreeNode.vue'\nimport { cnRenderMarkdown } from '../../composables/cnRenderMarkdown.js'\n\n/**\n * CnWikiPage — Manifest-driven markdown article surface.\n *\n * Reads `article` (object) and `tree` (array) as props rather than\n * fetching itself; consumers populate these from their own data\n * store or a wrapper composable. The manifest config still drives\n * which fields/registers are involved (via `contentField`,\n * `titleField`, `sidebarSchema`, …) so the validator stays\n * meaningful even though the lib does not own the fetch.\n *\n * Basic usage:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :register=\"'pipelinq'\"\n * :schema=\"'article'\"\n * :content-field=\"'body'\" />\n *\n * With sidebar tree:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :tree=\"categoryTree\"\n * :sidebar-schema=\"'category'\"\n *\n * @tree-click=\"onCategoryClick\" />\n */\nexport default {\n\tname: 'CnWikiPage',\n\n\tcomponents: {\n\t\tNcEmptyContent,\n\t\tFileDocumentOutline,\n\t\tCnWikiTreeNode,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The article record to render. When null/undefined the\n\t\t * component shows its empty-state. The shape is consumer-defined;\n\t\t * the component reads only `article[contentField]` and\n\t\t * `article[titleField]`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tarticle: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The (optional) sidebar tree. Each node:\n\t\t * `{ id, [titleField], children?: [...] }`. Empty array hides\n\t\t * the sidebar entirely.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\ttree: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Informational at this layer\n\t\t * (the consumer does the fetch); declared as a prop so the\n\t\t * manifest-driven prop forwarder in CnPageRenderer surfaces it\n\t\t * for child components / debugging.\n\t\t */\n\t\tregister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Informational at this layer —\n\t\t * see `register` above.\n\t\t */\n\t\tschema: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the markdown body. Defaults\n\t\t * to `'body'`.\n\t\t */\n\t\tcontentField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'body',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the page title. Defaults\n\t\t * to `'title'`.\n\t\t */\n\t\ttitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'title',\n\t\t},\n\n\t\t/**\n\t\t * `$route.params` key holding the article id. Informational at\n\t\t * this layer; declared so the consumer's data store can wire\n\t\t * the same default the validator and renderer both reference.\n\t\t */\n\t\tidParam: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Schema slug for the sidebar tree. When set AND `tree` is\n\t\t * non-empty the sidebar renders. Unset suppresses the sidebar\n\t\t * regardless of `tree` content.\n\t\t */\n\t\tsidebarSchema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Register slug for the sidebar tree. Defaults to `register`\n\t\t * when unset (the common case).\n\t\t */\n\t\tsidebarRegister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its child array.\n\t\t * Defaults to `'children'`.\n\t\t */\n\t\ttreeField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'children',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its label. Defaults\n\t\t * to `titleField` (so `'title'` unless overridden).\n\t\t */\n\t\tsidebarTitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Empty-state heading when `article` is null. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Article not found'),\n\t\t},\n\n\t\t/** Empty-state description when `article` is null. */\n\t\temptyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'The requested article could not be found.'),\n\t\t},\n\n\t\t/** Empty-body heading when the article has no `contentField`. */\n\t\temptyBodyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No content'),\n\t\t},\n\n\t\t/** Empty-body description when the article has no `contentField`. */\n\t\temptyBodyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This article has no content yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when a sidebar tree node is clicked. The payload is\n\t\t * the clicked node object (the same shape the consumer placed\n\t\t * in `tree`).\n\t\t */\n\t\t'tree-click',\n\t\t/**\n\t\t * Emitted when markdown parsing throws. Rare — `marked` and\n\t\t * the helper are defensive — but the contract is explicit so\n\t\t * consumers can log / surface a banner.\n\t\t */\n\t\t'error',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective sidebar-title field — falls back to the article\n\t\t * title field when unset so consumers can leave it off in\n\t\t * the simple case.\n\t\t */\n\t\teffectiveSidebarTitleField() {\n\t\t\treturn this.sidebarTitleField || this.titleField\n\t\t},\n\n\t\t/**\n\t\t * Whether the sidebar should render. Both a non-empty tree\n\t\t * AND an explicit `sidebarSchema` are required — the schema\n\t\t * acts as the manifest-author's \"yes, I want a sidebar\" signal,\n\t\t * matching the spec's REQ-MWPT-5.\n\t\t */\n\t\thasSidebar() {\n\t\t\treturn Boolean(this.sidebarSchema) && Array.isArray(this.tree) && this.tree.length > 0\n\t\t},\n\n\t\t/** Article title resolved through `titleField`. */\n\t\tresolvedTitle() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = this.article[this.titleField]\n\t\t\treturn typeof value === 'string' ? value : ''\n\t\t},\n\n\t\t/**\n\t\t * Article body parsed to HTML through `cnRenderMarkdown`.\n\t\t * Empty string when the article is null or has no body.\n\t\t */\n\t\trenderedBody() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst source = this.article[this.contentField]\n\t\t\ttry {\n\t\t\t\treturn cnRenderMarkdown(source)\n\t\t\t} catch (error) {\n\t\t\t\tthis.$emit('error', error)\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit `@tree-click` with the clicked node.\n\t\t *\n\t\t * @param {object} node The tree node the user clicked.\n\t\t */\n\t\tonTreeClick(node) {\n\t\t\tthis.$emit('tree-click', node)\n\t\t},\n\n\t\t/**\n\t\t * Stable v-for key for tree nodes — prefer `id`, fall back\n\t\t * to the title field, finally to JSON-stringified shape.\n\t\t *\n\t\t * @param {object} node Tree node.\n\t\t * @return {string}\n\t\t */\n\t\ttreeKey(node) {\n\t\t\tif (!node) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof node.id === 'string' || typeof node.id === 'number') {\n\t\t\t\treturn String(node.id)\n\t\t\t}\n\t\t\tconst title = node[this.effectiveSidebarTitleField]\n\t\t\tif (typeof title === 'string') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn JSON.stringify(node)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wiki-page {\n\tdisplay: flex;\n\tgap: 24px;\n\tpadding: 20px;\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n}\n\n.cn-wiki-page__sidebar {\n\tflex: 0 0 240px;\n\tborder-right: 1px solid var(--color-border);\n\tpadding-right: 16px;\n}\n\n.cn-wiki-page__tree {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-wiki-page__main {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-wiki-page__header {\n\tmargin-bottom: 16px;\n}\n\n.cn-wiki-page__title {\n\tfont-size: 1.6em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body {\n\tline-height: 1.7;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body :deep(h1),\n.cn-wiki-page__body :deep(h2),\n.cn-wiki-page__body :deep(h3) {\n\tcolor: var(--color-main-text);\n\tmargin-top: 1.4em;\n\tmargin-bottom: 0.5em;\n}\n\n.cn-wiki-page__body :deep(p) {\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(code) {\n\tbackground: var(--color-background-dark);\n\tpadding: 2px 6px;\n\tborder-radius: var(--border-radius);\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.9em;\n}\n\n.cn-wiki-page__body :deep(pre) {\n\tbackground: var(--color-background-dark);\n\tpadding: 12px;\n\tborder-radius: var(--border-radius);\n\toverflow-x: auto;\n}\n\n.cn-wiki-page__body :deep(pre code) {\n\tbackground: transparent;\n\tpadding: 0;\n}\n\n.cn-wiki-page__body :deep(blockquote) {\n\tborder-left: 3px solid var(--color-border);\n\tpadding-left: 12px;\n\tcolor: var(--color-text-lighter);\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(table) {\n\tborder-collapse: collapse;\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(th),\n.cn-wiki-page__body :deep(td) {\n\tborder: 1px solid var(--color-border);\n\tpadding: 6px 10px;\n}\n\n.cn-wiki-page--has-sidebar .cn-wiki-page__main {\n\tpadding-left: 8px;\n}\n</style>\n"],"names":["CnWikiTreeNode","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnWikiPage.vue2.js","sources":["../../../../src/components/CnWikiPage/CnWikiPage.vue"],"sourcesContent":["<!--\n CnWikiPage — Manifest-driven markdown article surface.\n\n Renders one wiki / docs / knowledge-base article declared in\n `pages[].config` for `type: \"wiki\"` pages. Closes the gap that\n forces every consumer's docs / help / article-detail route onto\n `type: \"custom\"`.\n\n The page reads its content from props rather than fetching itself.\n Following the precedent set by CnFilesPage and CnFormPage, the\n consumer (or a wiki-aware wrapper) populates `article` (object)\n and `tree` (array) — typically from a `useDetailView()` /\n list-call against the manifest-declared register/schema. This\n keeps the lib zero-dep on a particular OpenRegister fetcher\n while still making the manifest config the source of truth for\n field names.\n\n Markdown rendering is delegated to the `cnRenderMarkdown` composable\n which configures `marked` with `{ gfm: true, breaks: false }` —\n GitHub-flavoured markdown, no auto-`<br>` on newlines.\n\n Slots:\n - `#header` — overrides the default header. Scope `{ title, article }`.\n - `#sidebar` — overrides the default tree sidebar. Scope `{ tree, onClick }`.\n - `#body` — overrides the default markdown body. Scope `{ article, html }`.\n - `#empty` — replaces the empty-state when `article` is null.\n\n Events:\n - `@tree-click` — `node` whenever a sidebar tree node is clicked.\n - `@error` — error object if markdown parsing throws.\n\n Spec: REQ-MWPT-* (manifest-wiki-page-type).\n-->\n<template>\n\t<div class=\"cn-wiki-page\" data-testid=\"cn-wiki-page\" :class=\"{ 'cn-wiki-page--has-sidebar': hasSidebar }\">\n\t\t<!--\n\t\t\t@slot sidebar\n\t\t\t@description Replaces the default sidebar tree. Scoped props `{ tree, onClick }` —\n\t\t\t`onClick(node)` re-emits `@tree-click` with the node.\n\t\t-->\n\t\t<slot\n\t\t\tv-if=\"hasSidebar\"\n\t\t\tname=\"sidebar\"\n\t\t\t:tree=\"tree\"\n\t\t\t:onClick=\"onTreeClick\">\n\t\t\t<nav class=\"cn-wiki-page__sidebar\">\n\t\t\t\t<ul class=\"cn-wiki-page__tree\">\n\t\t\t\t\t<CnWikiTreeNode\n\t\t\t\t\t\tv-for=\"node in tree\"\n\t\t\t\t\t\t:key=\"treeKey(node)\"\n\t\t\t\t\t\t:node=\"node\"\n\t\t\t\t\t\t:titleField=\"effectiveSidebarTitleField\"\n\t\t\t\t\t\t:treeField=\"treeField\"\n\t\t\t\t\t\t@click=\"onTreeClick\" />\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t</slot>\n\n\t\t<div class=\"cn-wiki-page__main\">\n\t\t\t<!--\n\t\t\t\t@slot header\n\t\t\t\t@description Replaces the default page header. Scoped props `{ title, article }`.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tname=\"header\"\n\t\t\t\t:title=\"resolvedTitle\"\n\t\t\t\t:article=\"article\">\n\t\t\t\t<header v-if=\"resolvedTitle\" class=\"cn-wiki-page__header\">\n\t\t\t\t\t<h1 class=\"cn-wiki-page__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h1>\n\t\t\t\t</header>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot empty\n\t\t\t\t@description Replaces the empty-state shown when `article` is null/undefined.\n\t\t\t-->\n\t\t\t<slot v-if=\"!article\" name=\"empty\">\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\t:name=\"emptyText\"\n\t\t\t\t\t:description=\"emptyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot body\n\t\t\t\t@description Replaces the default markdown body. Scoped props `{ article, html }` —\n\t\t\t\t`html` is the pre-parsed markdown so a custom renderer can wrap it.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tv-else\n\t\t\t\tname=\"body\"\n\t\t\t\t:article=\"article\"\n\t\t\t\t:html=\"renderedBody\">\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"renderedBody\"\n\t\t\t\t\tclass=\"cn-wiki-page__body\"\n\t\t\t\t\tv-html=\"renderedBody\" /><!-- eslint-disable-line vue/no-v-html -->\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else\n\t\t\t\t\t:name=\"emptyBodyText\"\n\t\t\t\t\t:description=\"emptyBodyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport CnWikiTreeNode from './CnWikiTreeNode.vue'\nimport { cnRenderMarkdown } from '../../composables/cnRenderMarkdown.js'\n\n/**\n * CnWikiPage — Manifest-driven markdown article surface.\n *\n * Reads `article` (object) and `tree` (array) as props rather than\n * fetching itself; consumers populate these from their own data\n * store or a wrapper composable. The manifest config still drives\n * which fields/registers are involved (via `contentField`,\n * `titleField`, `sidebarSchema`, …) so the validator stays\n * meaningful even though the lib does not own the fetch.\n *\n * Basic usage:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :register=\"'pipelinq'\"\n * :schema=\"'article'\"\n * :content-field=\"'body'\" />\n *\n * With sidebar tree:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :tree=\"categoryTree\"\n * :sidebar-schema=\"'category'\"\n *\n * @tree-click=\"onCategoryClick\" />\n */\nexport default {\n\tname: 'CnWikiPage',\n\n\tcomponents: {\n\t\tNcEmptyContent,\n\t\tFileDocumentOutline,\n\t\tCnWikiTreeNode,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The article record to render. When null/undefined the\n\t\t * component shows its empty-state. The shape is consumer-defined;\n\t\t * the component reads only `article[contentField]` and\n\t\t * `article[titleField]`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tarticle: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The (optional) sidebar tree. Each node:\n\t\t * `{ id, [titleField], children?: [...] }`. Empty array hides\n\t\t * the sidebar entirely.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\ttree: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Informational at this layer\n\t\t * (the consumer does the fetch); declared as a prop so the\n\t\t * manifest-driven prop forwarder in CnPageRenderer surfaces it\n\t\t * for child components / debugging.\n\t\t */\n\t\tregister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Informational at this layer —\n\t\t * see `register` above.\n\t\t */\n\t\tschema: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the markdown body. Defaults\n\t\t * to `'body'`.\n\t\t */\n\t\tcontentField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'body',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the page title. Defaults\n\t\t * to `'title'`.\n\t\t */\n\t\ttitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'title',\n\t\t},\n\n\t\t/**\n\t\t * `$route.params` key holding the article id. Informational at\n\t\t * this layer; declared so the consumer's data store can wire\n\t\t * the same default the validator and renderer both reference.\n\t\t */\n\t\tidParam: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Schema slug for the sidebar tree. When set AND `tree` is\n\t\t * non-empty the sidebar renders. Unset suppresses the sidebar\n\t\t * regardless of `tree` content.\n\t\t */\n\t\tsidebarSchema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Register slug for the sidebar tree. Defaults to `register`\n\t\t * when unset (the common case).\n\t\t */\n\t\tsidebarRegister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its child array.\n\t\t * Defaults to `'children'`.\n\t\t */\n\t\ttreeField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'children',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its label. Defaults\n\t\t * to `titleField` (so `'title'` unless overridden).\n\t\t */\n\t\tsidebarTitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Empty-state heading when `article` is null. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Article not found'),\n\t\t},\n\n\t\t/** Empty-state description when `article` is null. */\n\t\temptyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'The requested article could not be found.'),\n\t\t},\n\n\t\t/** Empty-body heading when the article has no `contentField`. */\n\t\temptyBodyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No content'),\n\t\t},\n\n\t\t/** Empty-body description when the article has no `contentField`. */\n\t\temptyBodyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This article has no content yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when a sidebar tree node is clicked. The payload is\n\t\t * the clicked node object (the same shape the consumer placed\n\t\t * in `tree`).\n\t\t */\n\t\t'tree-click',\n\t\t/**\n\t\t * Emitted when markdown parsing throws. Rare — `marked` and\n\t\t * the helper are defensive — but the contract is explicit so\n\t\t * consumers can log / surface a banner.\n\t\t */\n\t\t'error',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective sidebar-title field — falls back to the article\n\t\t * title field when unset so consumers can leave it off in\n\t\t * the simple case.\n\t\t */\n\t\teffectiveSidebarTitleField() {\n\t\t\treturn this.sidebarTitleField || this.titleField\n\t\t},\n\n\t\t/**\n\t\t * Whether the sidebar should render. Both a non-empty tree\n\t\t * AND an explicit `sidebarSchema` are required — the schema\n\t\t * acts as the manifest-author's \"yes, I want a sidebar\" signal,\n\t\t * matching the spec's REQ-MWPT-5.\n\t\t */\n\t\thasSidebar() {\n\t\t\treturn Boolean(this.sidebarSchema) && Array.isArray(this.tree) && this.tree.length > 0\n\t\t},\n\n\t\t/** Article title resolved through `titleField`. */\n\t\tresolvedTitle() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = this.article[this.titleField]\n\t\t\treturn typeof value === 'string' ? value : ''\n\t\t},\n\n\t\t/**\n\t\t * Article body parsed to HTML through `cnRenderMarkdown`.\n\t\t * Empty string when the article is null or has no body.\n\t\t */\n\t\trenderedBody() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst source = this.article[this.contentField]\n\t\t\ttry {\n\t\t\t\treturn cnRenderMarkdown(source)\n\t\t\t} catch (error) {\n\t\t\t\tthis.$emit('error', error)\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit `@tree-click` with the clicked node.\n\t\t *\n\t\t * @param {object} node The tree node the user clicked.\n\t\t */\n\t\tonTreeClick(node) {\n\t\t\tthis.$emit('tree-click', node)\n\t\t},\n\n\t\t/**\n\t\t * Stable v-for key for tree nodes — prefer `id`, fall back\n\t\t * to the title field, finally to JSON-stringified shape.\n\t\t *\n\t\t * @param {object} node Tree node.\n\t\t * @return {string}\n\t\t */\n\t\ttreeKey(node) {\n\t\t\tif (!node) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof node.id === 'string' || typeof node.id === 'number') {\n\t\t\t\treturn String(node.id)\n\t\t\t}\n\t\t\tconst title = node[this.effectiveSidebarTitleField]\n\t\t\tif (typeof title === 'string') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn JSON.stringify(node)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wiki-page {\n\tdisplay: flex;\n\tgap: 24px;\n\tpadding: 20px;\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n}\n\n.cn-wiki-page__sidebar {\n\tflex: 0 0 240px;\n\tborder-right: 1px solid var(--color-border);\n\tpadding-right: 16px;\n}\n\n.cn-wiki-page__tree {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-wiki-page__main {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-wiki-page__header {\n\tmargin-bottom: 16px;\n}\n\n/* Without a sidebar the title starts at the left edge of the content, under\n the Nextcloud navigation toggle button (44px wide). Clear it as CnPageHeader\n does. With a sidebar the tree sits there instead and the title is clear. */\n.cn-wiki-page:not(.cn-wiki-page--has-sidebar) .cn-wiki-page__header {\n\tpadding-inline-start: 56px;\n}\n\n.cn-wiki-page__title {\n\tfont-size: 1.6em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body {\n\tline-height: 1.7;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body :deep(h1),\n.cn-wiki-page__body :deep(h2),\n.cn-wiki-page__body :deep(h3) {\n\tcolor: var(--color-main-text);\n\tmargin-top: 1.4em;\n\tmargin-bottom: 0.5em;\n}\n\n.cn-wiki-page__body :deep(p) {\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(code) {\n\tbackground: var(--color-background-dark);\n\tpadding: 2px 6px;\n\tborder-radius: var(--border-radius);\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.9em;\n}\n\n.cn-wiki-page__body :deep(pre) {\n\tbackground: var(--color-background-dark);\n\tpadding: 12px;\n\tborder-radius: var(--border-radius);\n\toverflow-x: auto;\n}\n\n.cn-wiki-page__body :deep(pre code) {\n\tbackground: transparent;\n\tpadding: 0;\n}\n\n.cn-wiki-page__body :deep(blockquote) {\n\tborder-left: 3px solid var(--color-border);\n\tpadding-left: 12px;\n\tcolor: var(--color-text-lighter);\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(table) {\n\tborder-collapse: collapse;\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(th),\n.cn-wiki-page__body :deep(td) {\n\tborder: 1px solid var(--color-border);\n\tpadding: 6px 10px;\n}\n\n.cn-wiki-page--has-sidebar .cn-wiki-page__main {\n\tpadding-left: 8px;\n}\n</style>\n"],"names":["CnWikiTreeNode","t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWikiPage.vue3.js","sources":["../../../../src/components/CnWikiPage/CnWikiPage.vue?vue&type=template&id=25a3d3cf&lang.js"],"sourcesContent":["<!--\n CnWikiPage — Manifest-driven markdown article surface.\n\n Renders one wiki / docs / knowledge-base article declared in\n `pages[].config` for `type: \"wiki\"` pages. Closes the gap that\n forces every consumer's docs / help / article-detail route onto\n `type: \"custom\"`.\n\n The page reads its content from props rather than fetching itself.\n Following the precedent set by CnFilesPage and CnFormPage, the\n consumer (or a wiki-aware wrapper) populates `article` (object)\n and `tree` (array) — typically from a `useDetailView()` /\n list-call against the manifest-declared register/schema. This\n keeps the lib zero-dep on a particular OpenRegister fetcher\n while still making the manifest config the source of truth for\n field names.\n\n Markdown rendering is delegated to the `cnRenderMarkdown` composable\n which configures `marked` with `{ gfm: true, breaks: false }` —\n GitHub-flavoured markdown, no auto-`<br>` on newlines.\n\n Slots:\n - `#header` — overrides the default header. Scope `{ title, article }`.\n - `#sidebar` — overrides the default tree sidebar. Scope `{ tree, onClick }`.\n - `#body` — overrides the default markdown body. Scope `{ article, html }`.\n - `#empty` — replaces the empty-state when `article` is null.\n\n Events:\n - `@tree-click` — `node` whenever a sidebar tree node is clicked.\n - `@error` — error object if markdown parsing throws.\n\n Spec: REQ-MWPT-* (manifest-wiki-page-type).\n-->\n<template>\n\t<div class=\"cn-wiki-page\" data-testid=\"cn-wiki-page\" :class=\"{ 'cn-wiki-page--has-sidebar': hasSidebar }\">\n\t\t<!--\n\t\t\t@slot sidebar\n\t\t\t@description Replaces the default sidebar tree. Scoped props `{ tree, onClick }` —\n\t\t\t`onClick(node)` re-emits `@tree-click` with the node.\n\t\t-->\n\t\t<slot\n\t\t\tv-if=\"hasSidebar\"\n\t\t\tname=\"sidebar\"\n\t\t\t:tree=\"tree\"\n\t\t\t:onClick=\"onTreeClick\">\n\t\t\t<nav class=\"cn-wiki-page__sidebar\">\n\t\t\t\t<ul class=\"cn-wiki-page__tree\">\n\t\t\t\t\t<CnWikiTreeNode\n\t\t\t\t\t\tv-for=\"node in tree\"\n\t\t\t\t\t\t:key=\"treeKey(node)\"\n\t\t\t\t\t\t:node=\"node\"\n\t\t\t\t\t\t:titleField=\"effectiveSidebarTitleField\"\n\t\t\t\t\t\t:treeField=\"treeField\"\n\t\t\t\t\t\t@click=\"onTreeClick\" />\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t</slot>\n\n\t\t<div class=\"cn-wiki-page__main\">\n\t\t\t<!--\n\t\t\t\t@slot header\n\t\t\t\t@description Replaces the default page header. Scoped props `{ title, article }`.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tname=\"header\"\n\t\t\t\t:title=\"resolvedTitle\"\n\t\t\t\t:article=\"article\">\n\t\t\t\t<header v-if=\"resolvedTitle\" class=\"cn-wiki-page__header\">\n\t\t\t\t\t<h1 class=\"cn-wiki-page__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h1>\n\t\t\t\t</header>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot empty\n\t\t\t\t@description Replaces the empty-state shown when `article` is null/undefined.\n\t\t\t-->\n\t\t\t<slot v-if=\"!article\" name=\"empty\">\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\t:name=\"emptyText\"\n\t\t\t\t\t:description=\"emptyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot body\n\t\t\t\t@description Replaces the default markdown body. Scoped props `{ article, html }` —\n\t\t\t\t`html` is the pre-parsed markdown so a custom renderer can wrap it.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tv-else\n\t\t\t\tname=\"body\"\n\t\t\t\t:article=\"article\"\n\t\t\t\t:html=\"renderedBody\">\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"renderedBody\"\n\t\t\t\t\tclass=\"cn-wiki-page__body\"\n\t\t\t\t\tv-html=\"renderedBody\" /><!-- eslint-disable-line vue/no-v-html -->\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else\n\t\t\t\t\t:name=\"emptyBodyText\"\n\t\t\t\t\t:description=\"emptyBodyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport CnWikiTreeNode from './CnWikiTreeNode.vue'\nimport { cnRenderMarkdown } from '../../composables/cnRenderMarkdown.js'\n\n/**\n * CnWikiPage — Manifest-driven markdown article surface.\n *\n * Reads `article` (object) and `tree` (array) as props rather than\n * fetching itself; consumers populate these from their own data\n * store or a wrapper composable. The manifest config still drives\n * which fields/registers are involved (via `contentField`,\n * `titleField`, `sidebarSchema`, …) so the validator stays\n * meaningful even though the lib does not own the fetch.\n *\n * Basic usage:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :register=\"'pipelinq'\"\n * :schema=\"'article'\"\n * :content-field=\"'body'\" />\n *\n * With sidebar tree:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :tree=\"categoryTree\"\n * :sidebar-schema=\"'category'\"\n *\n * @tree-click=\"onCategoryClick\" />\n */\nexport default {\n\tname: 'CnWikiPage',\n\n\tcomponents: {\n\t\tNcEmptyContent,\n\t\tFileDocumentOutline,\n\t\tCnWikiTreeNode,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The article record to render. When null/undefined the\n\t\t * component shows its empty-state. The shape is consumer-defined;\n\t\t * the component reads only `article[contentField]` and\n\t\t * `article[titleField]`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tarticle: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The (optional) sidebar tree. Each node:\n\t\t * `{ id, [titleField], children?: [...] }`. Empty array hides\n\t\t * the sidebar entirely.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\ttree: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Informational at this layer\n\t\t * (the consumer does the fetch); declared as a prop so the\n\t\t * manifest-driven prop forwarder in CnPageRenderer surfaces it\n\t\t * for child components / debugging.\n\t\t */\n\t\tregister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Informational at this layer —\n\t\t * see `register` above.\n\t\t */\n\t\tschema: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the markdown body. Defaults\n\t\t * to `'body'`.\n\t\t */\n\t\tcontentField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'body',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the page title. Defaults\n\t\t * to `'title'`.\n\t\t */\n\t\ttitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'title',\n\t\t},\n\n\t\t/**\n\t\t * `$route.params` key holding the article id. Informational at\n\t\t * this layer; declared so the consumer's data store can wire\n\t\t * the same default the validator and renderer both reference.\n\t\t */\n\t\tidParam: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Schema slug for the sidebar tree. When set AND `tree` is\n\t\t * non-empty the sidebar renders. Unset suppresses the sidebar\n\t\t * regardless of `tree` content.\n\t\t */\n\t\tsidebarSchema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Register slug for the sidebar tree. Defaults to `register`\n\t\t * when unset (the common case).\n\t\t */\n\t\tsidebarRegister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its child array.\n\t\t * Defaults to `'children'`.\n\t\t */\n\t\ttreeField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'children',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its label. Defaults\n\t\t * to `titleField` (so `'title'` unless overridden).\n\t\t */\n\t\tsidebarTitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Empty-state heading when `article` is null. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Article not found'),\n\t\t},\n\n\t\t/** Empty-state description when `article` is null. */\n\t\temptyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'The requested article could not be found.'),\n\t\t},\n\n\t\t/** Empty-body heading when the article has no `contentField`. */\n\t\temptyBodyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No content'),\n\t\t},\n\n\t\t/** Empty-body description when the article has no `contentField`. */\n\t\temptyBodyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This article has no content yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when a sidebar tree node is clicked. The payload is\n\t\t * the clicked node object (the same shape the consumer placed\n\t\t * in `tree`).\n\t\t */\n\t\t'tree-click',\n\t\t/**\n\t\t * Emitted when markdown parsing throws. Rare — `marked` and\n\t\t * the helper are defensive — but the contract is explicit so\n\t\t * consumers can log / surface a banner.\n\t\t */\n\t\t'error',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective sidebar-title field — falls back to the article\n\t\t * title field when unset so consumers can leave it off in\n\t\t * the simple case.\n\t\t */\n\t\teffectiveSidebarTitleField() {\n\t\t\treturn this.sidebarTitleField || this.titleField\n\t\t},\n\n\t\t/**\n\t\t * Whether the sidebar should render. Both a non-empty tree\n\t\t * AND an explicit `sidebarSchema` are required — the schema\n\t\t * acts as the manifest-author's \"yes, I want a sidebar\" signal,\n\t\t * matching the spec's REQ-MWPT-5.\n\t\t */\n\t\thasSidebar() {\n\t\t\treturn Boolean(this.sidebarSchema) && Array.isArray(this.tree) && this.tree.length > 0\n\t\t},\n\n\t\t/** Article title resolved through `titleField`. */\n\t\tresolvedTitle() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = this.article[this.titleField]\n\t\t\treturn typeof value === 'string' ? value : ''\n\t\t},\n\n\t\t/**\n\t\t * Article body parsed to HTML through `cnRenderMarkdown`.\n\t\t * Empty string when the article is null or has no body.\n\t\t */\n\t\trenderedBody() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst source = this.article[this.contentField]\n\t\t\ttry {\n\t\t\t\treturn cnRenderMarkdown(source)\n\t\t\t} catch (error) {\n\t\t\t\tthis.$emit('error', error)\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit `@tree-click` with the clicked node.\n\t\t *\n\t\t * @param {object} node The tree node the user clicked.\n\t\t */\n\t\tonTreeClick(node) {\n\t\t\tthis.$emit('tree-click', node)\n\t\t},\n\n\t\t/**\n\t\t * Stable v-for key for tree nodes — prefer `id`, fall back\n\t\t * to the title field, finally to JSON-stringified shape.\n\t\t *\n\t\t * @param {object} node Tree node.\n\t\t * @return {string}\n\t\t */\n\t\ttreeKey(node) {\n\t\t\tif (!node) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof node.id === 'string' || typeof node.id === 'number') {\n\t\t\t\treturn String(node.id)\n\t\t\t}\n\t\t\tconst title = node[this.effectiveSidebarTitleField]\n\t\t\tif (typeof title === 'string') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn JSON.stringify(node)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wiki-page {\n\tdisplay: flex;\n\tgap: 24px;\n\tpadding: 20px;\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n}\n\n.cn-wiki-page__sidebar {\n\tflex: 0 0 240px;\n\tborder-right: 1px solid var(--color-border);\n\tpadding-right: 16px;\n}\n\n.cn-wiki-page__tree {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-wiki-page__main {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-wiki-page__header {\n\tmargin-bottom: 16px;\n}\n\n.cn-wiki-page__title {\n\tfont-size: 1.6em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body {\n\tline-height: 1.7;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body :deep(h1),\n.cn-wiki-page__body :deep(h2),\n.cn-wiki-page__body :deep(h3) {\n\tcolor: var(--color-main-text);\n\tmargin-top: 1.4em;\n\tmargin-bottom: 0.5em;\n}\n\n.cn-wiki-page__body :deep(p) {\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(code) {\n\tbackground: var(--color-background-dark);\n\tpadding: 2px 6px;\n\tborder-radius: var(--border-radius);\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.9em;\n}\n\n.cn-wiki-page__body :deep(pre) {\n\tbackground: var(--color-background-dark);\n\tpadding: 12px;\n\tborder-radius: var(--border-radius);\n\toverflow-x: auto;\n}\n\n.cn-wiki-page__body :deep(pre code) {\n\tbackground: transparent;\n\tpadding: 0;\n}\n\n.cn-wiki-page__body :deep(blockquote) {\n\tborder-left: 3px solid var(--color-border);\n\tpadding-left: 12px;\n\tcolor: var(--color-text-lighter);\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(table) {\n\tborder-collapse: collapse;\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(th),\n.cn-wiki-page__body :deep(td) {\n\tborder: 1px solid var(--color-border);\n\tpadding: 6px 10px;\n}\n\n.cn-wiki-page--has-sidebar .cn-wiki-page__main {\n\tpadding-left: 8px;\n}\n</style>\n"],"names":["_createElementVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_renderSlot","_toDisplayString","_createVNode"],"mappings":";;AA8EO,MAAA,UAAA,GAAA,EAAA,KAAM,EAAC,uBAAuB,GAAA;AAC7B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oBAAI,GAAA;AAYX,MAAA,UAAA,GAAA,EAAA,KAAK,EAAE,oBAAmB,GAAA;;;AASH,EAAA,KAAA,EAAA,sBAAA;;AACrB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;;;;;;;IAlC9B,uBAAM,cAAc,EAAA,EAAA,2BAAA,EAAmC,QAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAAlC,IAAA,aAAW,EAAC,cAAc;;KAO/C,QAAA,CAAA,UAAA;kBAeI,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;AAbN,UAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,UAAA,OAAC,EAAA,QAAA,CAAA,WAAA;iBAYK;AAXP,UAAAA,kBAAA,CAUG,OAVH,UAUG,EAAA;AATF,YAAAA,kBAAA,OAAA;gCACCC,kBAMI,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CALY,MAAI,CAAA,IAAA,EAAA,CAAZ,IAAI,KAAA;qCADZC,WAMI,CAAA,yBAAA,EAAA;kBAJF,GAAG,EAAE,QAAO,CAAA,OAAA,CAAC,IAAA,CAAA;AACb,kBAAA,IAAI,EAAE,IAAI;AACV,kBAAA,UAAQ,EAAA,QAAA,CAAA,0BAAA;AACR,kBAAA,SAAS,EAAE,MAAE,CAAA,SAAA;AACb,kBAAA,OAAE,EAAA,QAAA,CAAA,WAAA;;;;;;;;MAULC,UASM,CAAA,IAAA,CAAA,MAAA,EAAA,QAAA,EAAA;AAPL,QAAA,KAAE,EAAA,QAAA,CAAA,aAAA;AACF,QAAA,OAAO,EAAE,MAAO,CAAA,OAAA;SAHjB,MASM;SALQ,QAAY,CAAA,aAAA;6CAIjB,oBAAA,EAAA;cAHRL,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAM,eAAA,CADD,QAAS,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;;;QASX,MAAA,CAAA,OAAA;AAAJ,UAAAD,UAAA,2BAAA;YACCE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnWikiPage.vue3.js","sources":["../../../../src/components/CnWikiPage/CnWikiPage.vue?vue&type=template&id=25a3d3cf&lang.js"],"sourcesContent":["<!--\n CnWikiPage — Manifest-driven markdown article surface.\n\n Renders one wiki / docs / knowledge-base article declared in\n `pages[].config` for `type: \"wiki\"` pages. Closes the gap that\n forces every consumer's docs / help / article-detail route onto\n `type: \"custom\"`.\n\n The page reads its content from props rather than fetching itself.\n Following the precedent set by CnFilesPage and CnFormPage, the\n consumer (or a wiki-aware wrapper) populates `article` (object)\n and `tree` (array) — typically from a `useDetailView()` /\n list-call against the manifest-declared register/schema. This\n keeps the lib zero-dep on a particular OpenRegister fetcher\n while still making the manifest config the source of truth for\n field names.\n\n Markdown rendering is delegated to the `cnRenderMarkdown` composable\n which configures `marked` with `{ gfm: true, breaks: false }` —\n GitHub-flavoured markdown, no auto-`<br>` on newlines.\n\n Slots:\n - `#header` — overrides the default header. Scope `{ title, article }`.\n - `#sidebar` — overrides the default tree sidebar. Scope `{ tree, onClick }`.\n - `#body` — overrides the default markdown body. Scope `{ article, html }`.\n - `#empty` — replaces the empty-state when `article` is null.\n\n Events:\n - `@tree-click` — `node` whenever a sidebar tree node is clicked.\n - `@error` — error object if markdown parsing throws.\n\n Spec: REQ-MWPT-* (manifest-wiki-page-type).\n-->\n<template>\n\t<div class=\"cn-wiki-page\" data-testid=\"cn-wiki-page\" :class=\"{ 'cn-wiki-page--has-sidebar': hasSidebar }\">\n\t\t<!--\n\t\t\t@slot sidebar\n\t\t\t@description Replaces the default sidebar tree. Scoped props `{ tree, onClick }` —\n\t\t\t`onClick(node)` re-emits `@tree-click` with the node.\n\t\t-->\n\t\t<slot\n\t\t\tv-if=\"hasSidebar\"\n\t\t\tname=\"sidebar\"\n\t\t\t:tree=\"tree\"\n\t\t\t:onClick=\"onTreeClick\">\n\t\t\t<nav class=\"cn-wiki-page__sidebar\">\n\t\t\t\t<ul class=\"cn-wiki-page__tree\">\n\t\t\t\t\t<CnWikiTreeNode\n\t\t\t\t\t\tv-for=\"node in tree\"\n\t\t\t\t\t\t:key=\"treeKey(node)\"\n\t\t\t\t\t\t:node=\"node\"\n\t\t\t\t\t\t:titleField=\"effectiveSidebarTitleField\"\n\t\t\t\t\t\t:treeField=\"treeField\"\n\t\t\t\t\t\t@click=\"onTreeClick\" />\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t</slot>\n\n\t\t<div class=\"cn-wiki-page__main\">\n\t\t\t<!--\n\t\t\t\t@slot header\n\t\t\t\t@description Replaces the default page header. Scoped props `{ title, article }`.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tname=\"header\"\n\t\t\t\t:title=\"resolvedTitle\"\n\t\t\t\t:article=\"article\">\n\t\t\t\t<header v-if=\"resolvedTitle\" class=\"cn-wiki-page__header\">\n\t\t\t\t\t<h1 class=\"cn-wiki-page__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h1>\n\t\t\t\t</header>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot empty\n\t\t\t\t@description Replaces the empty-state shown when `article` is null/undefined.\n\t\t\t-->\n\t\t\t<slot v-if=\"!article\" name=\"empty\">\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\t:name=\"emptyText\"\n\t\t\t\t\t:description=\"emptyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot body\n\t\t\t\t@description Replaces the default markdown body. Scoped props `{ article, html }` —\n\t\t\t\t`html` is the pre-parsed markdown so a custom renderer can wrap it.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tv-else\n\t\t\t\tname=\"body\"\n\t\t\t\t:article=\"article\"\n\t\t\t\t:html=\"renderedBody\">\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"renderedBody\"\n\t\t\t\t\tclass=\"cn-wiki-page__body\"\n\t\t\t\t\tv-html=\"renderedBody\" /><!-- eslint-disable-line vue/no-v-html -->\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else\n\t\t\t\t\t:name=\"emptyBodyText\"\n\t\t\t\t\t:description=\"emptyBodyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport CnWikiTreeNode from './CnWikiTreeNode.vue'\nimport { cnRenderMarkdown } from '../../composables/cnRenderMarkdown.js'\n\n/**\n * CnWikiPage — Manifest-driven markdown article surface.\n *\n * Reads `article` (object) and `tree` (array) as props rather than\n * fetching itself; consumers populate these from their own data\n * store or a wrapper composable. The manifest config still drives\n * which fields/registers are involved (via `contentField`,\n * `titleField`, `sidebarSchema`, …) so the validator stays\n * meaningful even though the lib does not own the fetch.\n *\n * Basic usage:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :register=\"'pipelinq'\"\n * :schema=\"'article'\"\n * :content-field=\"'body'\" />\n *\n * With sidebar tree:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :tree=\"categoryTree\"\n * :sidebar-schema=\"'category'\"\n *\n * @tree-click=\"onCategoryClick\" />\n */\nexport default {\n\tname: 'CnWikiPage',\n\n\tcomponents: {\n\t\tNcEmptyContent,\n\t\tFileDocumentOutline,\n\t\tCnWikiTreeNode,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The article record to render. When null/undefined the\n\t\t * component shows its empty-state. The shape is consumer-defined;\n\t\t * the component reads only `article[contentField]` and\n\t\t * `article[titleField]`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tarticle: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The (optional) sidebar tree. Each node:\n\t\t * `{ id, [titleField], children?: [...] }`. Empty array hides\n\t\t * the sidebar entirely.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\ttree: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Informational at this layer\n\t\t * (the consumer does the fetch); declared as a prop so the\n\t\t * manifest-driven prop forwarder in CnPageRenderer surfaces it\n\t\t * for child components / debugging.\n\t\t */\n\t\tregister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Informational at this layer —\n\t\t * see `register` above.\n\t\t */\n\t\tschema: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the markdown body. Defaults\n\t\t * to `'body'`.\n\t\t */\n\t\tcontentField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'body',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the page title. Defaults\n\t\t * to `'title'`.\n\t\t */\n\t\ttitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'title',\n\t\t},\n\n\t\t/**\n\t\t * `$route.params` key holding the article id. Informational at\n\t\t * this layer; declared so the consumer's data store can wire\n\t\t * the same default the validator and renderer both reference.\n\t\t */\n\t\tidParam: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Schema slug for the sidebar tree. When set AND `tree` is\n\t\t * non-empty the sidebar renders. Unset suppresses the sidebar\n\t\t * regardless of `tree` content.\n\t\t */\n\t\tsidebarSchema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Register slug for the sidebar tree. Defaults to `register`\n\t\t * when unset (the common case).\n\t\t */\n\t\tsidebarRegister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its child array.\n\t\t * Defaults to `'children'`.\n\t\t */\n\t\ttreeField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'children',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its label. Defaults\n\t\t * to `titleField` (so `'title'` unless overridden).\n\t\t */\n\t\tsidebarTitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Empty-state heading when `article` is null. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Article not found'),\n\t\t},\n\n\t\t/** Empty-state description when `article` is null. */\n\t\temptyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'The requested article could not be found.'),\n\t\t},\n\n\t\t/** Empty-body heading when the article has no `contentField`. */\n\t\temptyBodyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No content'),\n\t\t},\n\n\t\t/** Empty-body description when the article has no `contentField`. */\n\t\temptyBodyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This article has no content yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when a sidebar tree node is clicked. The payload is\n\t\t * the clicked node object (the same shape the consumer placed\n\t\t * in `tree`).\n\t\t */\n\t\t'tree-click',\n\t\t/**\n\t\t * Emitted when markdown parsing throws. Rare — `marked` and\n\t\t * the helper are defensive — but the contract is explicit so\n\t\t * consumers can log / surface a banner.\n\t\t */\n\t\t'error',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective sidebar-title field — falls back to the article\n\t\t * title field when unset so consumers can leave it off in\n\t\t * the simple case.\n\t\t */\n\t\teffectiveSidebarTitleField() {\n\t\t\treturn this.sidebarTitleField || this.titleField\n\t\t},\n\n\t\t/**\n\t\t * Whether the sidebar should render. Both a non-empty tree\n\t\t * AND an explicit `sidebarSchema` are required — the schema\n\t\t * acts as the manifest-author's \"yes, I want a sidebar\" signal,\n\t\t * matching the spec's REQ-MWPT-5.\n\t\t */\n\t\thasSidebar() {\n\t\t\treturn Boolean(this.sidebarSchema) && Array.isArray(this.tree) && this.tree.length > 0\n\t\t},\n\n\t\t/** Article title resolved through `titleField`. */\n\t\tresolvedTitle() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = this.article[this.titleField]\n\t\t\treturn typeof value === 'string' ? value : ''\n\t\t},\n\n\t\t/**\n\t\t * Article body parsed to HTML through `cnRenderMarkdown`.\n\t\t * Empty string when the article is null or has no body.\n\t\t */\n\t\trenderedBody() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst source = this.article[this.contentField]\n\t\t\ttry {\n\t\t\t\treturn cnRenderMarkdown(source)\n\t\t\t} catch (error) {\n\t\t\t\tthis.$emit('error', error)\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit `@tree-click` with the clicked node.\n\t\t *\n\t\t * @param {object} node The tree node the user clicked.\n\t\t */\n\t\tonTreeClick(node) {\n\t\t\tthis.$emit('tree-click', node)\n\t\t},\n\n\t\t/**\n\t\t * Stable v-for key for tree nodes — prefer `id`, fall back\n\t\t * to the title field, finally to JSON-stringified shape.\n\t\t *\n\t\t * @param {object} node Tree node.\n\t\t * @return {string}\n\t\t */\n\t\ttreeKey(node) {\n\t\t\tif (!node) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof node.id === 'string' || typeof node.id === 'number') {\n\t\t\t\treturn String(node.id)\n\t\t\t}\n\t\t\tconst title = node[this.effectiveSidebarTitleField]\n\t\t\tif (typeof title === 'string') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn JSON.stringify(node)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wiki-page {\n\tdisplay: flex;\n\tgap: 24px;\n\tpadding: 20px;\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n}\n\n.cn-wiki-page__sidebar {\n\tflex: 0 0 240px;\n\tborder-right: 1px solid var(--color-border);\n\tpadding-right: 16px;\n}\n\n.cn-wiki-page__tree {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-wiki-page__main {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-wiki-page__header {\n\tmargin-bottom: 16px;\n}\n\n/* Without a sidebar the title starts at the left edge of the content, under\n the Nextcloud navigation toggle button (44px wide). Clear it as CnPageHeader\n does. With a sidebar the tree sits there instead and the title is clear. */\n.cn-wiki-page:not(.cn-wiki-page--has-sidebar) .cn-wiki-page__header {\n\tpadding-inline-start: 56px;\n}\n\n.cn-wiki-page__title {\n\tfont-size: 1.6em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body {\n\tline-height: 1.7;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body :deep(h1),\n.cn-wiki-page__body :deep(h2),\n.cn-wiki-page__body :deep(h3) {\n\tcolor: var(--color-main-text);\n\tmargin-top: 1.4em;\n\tmargin-bottom: 0.5em;\n}\n\n.cn-wiki-page__body :deep(p) {\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(code) {\n\tbackground: var(--color-background-dark);\n\tpadding: 2px 6px;\n\tborder-radius: var(--border-radius);\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.9em;\n}\n\n.cn-wiki-page__body :deep(pre) {\n\tbackground: var(--color-background-dark);\n\tpadding: 12px;\n\tborder-radius: var(--border-radius);\n\toverflow-x: auto;\n}\n\n.cn-wiki-page__body :deep(pre code) {\n\tbackground: transparent;\n\tpadding: 0;\n}\n\n.cn-wiki-page__body :deep(blockquote) {\n\tborder-left: 3px solid var(--color-border);\n\tpadding-left: 12px;\n\tcolor: var(--color-text-lighter);\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(table) {\n\tborder-collapse: collapse;\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(th),\n.cn-wiki-page__body :deep(td) {\n\tborder: 1px solid var(--color-border);\n\tpadding: 6px 10px;\n}\n\n.cn-wiki-page--has-sidebar .cn-wiki-page__main {\n\tpadding-left: 8px;\n}\n</style>\n"],"names":["_createElementVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_renderSlot","_toDisplayString","_createVNode"],"mappings":";;AA8EO,MAAA,UAAA,GAAA,EAAA,KAAM,EAAC,uBAAuB,GAAA;AAC7B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oBAAI,GAAA;AAYX,MAAA,UAAA,GAAA,EAAA,KAAK,EAAE,oBAAmB,GAAA;;;AASH,EAAA,KAAA,EAAA,sBAAA;;AACrB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;;;;;;;IAlC9B,uBAAM,cAAc,EAAA,EAAA,2BAAA,EAAmC,QAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAAlC,IAAA,aAAW,EAAC,cAAc;;KAO/C,QAAA,CAAA,UAAA;kBAeI,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;AAbN,UAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,UAAA,OAAC,EAAA,QAAA,CAAA,WAAA;iBAYK;AAXP,UAAAA,kBAAA,CAUG,OAVH,UAUG,EAAA;AATF,YAAAA,kBAAA,OAAA;gCACCC,kBAMI,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CALY,MAAI,CAAA,IAAA,EAAA,CAAZ,IAAI,KAAA;qCADZC,WAMI,CAAA,yBAAA,EAAA;kBAJF,GAAG,EAAE,QAAO,CAAA,OAAA,CAAC,IAAA,CAAA;AACb,kBAAA,IAAI,EAAE,IAAI;AACV,kBAAA,UAAQ,EAAA,QAAA,CAAA,0BAAA;AACR,kBAAA,SAAS,EAAE,MAAE,CAAA,SAAA;AACb,kBAAA,OAAE,EAAA,QAAA,CAAA,WAAA;;;;;;;;MAULC,UASM,CAAA,IAAA,CAAA,MAAA,EAAA,QAAA,EAAA;AAPL,QAAA,KAAE,EAAA,QAAA,CAAA,aAAA;AACF,QAAA,OAAO,EAAE,MAAO,CAAA,OAAA;SAHjB,MASM;SALQ,QAAY,CAAA,aAAA;6CAIjB,oBAAA,EAAA;cAHRL,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAM,eAAA,CADD,QAAS,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;;;QASX,MAAA,CAAA,OAAA;AAAJ,UAAAD,UAAA,2BAAA;YACCE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/esm/index.js
CHANGED
|
@@ -312,6 +312,12 @@ export { default as CnStackedBarWidget } from './components/CnStackedBarWidget/C
|
|
|
312
312
|
export { default as CnStackedBarWidgetForm } from './components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js';
|
|
313
313
|
export { default as CnWeekStripWidget } from './components/CnWeekStripWidget/CnWeekStripWidget.vue.js';
|
|
314
314
|
export { default as CnWeekStripWidgetForm } from './components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js';
|
|
315
|
+
export { default as CnFormPage } from './components/CnFormPage/CnFormPage.vue.js';
|
|
316
|
+
export { default as CnLinkCardsPage } from './components/CnLinkCardsPage/CnLinkCardsPage.vue.js';
|
|
317
|
+
export { default as CnMapPage } from './components/CnMapPage/CnMapPage.vue.js';
|
|
318
|
+
export { default as CnReportsPage } from './components/CnReportsPage/CnReportsPage.vue.js';
|
|
319
|
+
export { default as CnStorePage } from './components/CnStorePage/CnStorePage.vue.js';
|
|
320
|
+
export { default as CnWikiPage } from './components/CnWikiPage/CnWikiPage.vue.js';
|
|
315
321
|
export { default as CnAiAgentPicker } from './components/CnAiCompanion/CnAiAgentPicker.vue.js';
|
|
316
322
|
export { default as CnAiChatPanel } from './components/CnAiCompanion/CnAiChatPanel.vue.js';
|
|
317
323
|
export { default as CnAiCompanion } from './components/CnAiCompanion/CnAiCompanion.vue.js';
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/esm/l10n/en.json.js
CHANGED
|
@@ -291,6 +291,11 @@ var translations = {
|
|
|
291
291
|
"JSON pointer": "JSON pointer",
|
|
292
292
|
Last: "Last",
|
|
293
293
|
"Load more": "Load more",
|
|
294
|
+
"Nothing to open here.": "Nothing to open here.",
|
|
295
|
+
"Could not check": "Could not check",
|
|
296
|
+
"Could not check: {subject}": "Could not check: {subject}",
|
|
297
|
+
"The check failed.": "The check failed.",
|
|
298
|
+
"The check failed: {reason}": "The check failed: {reason}",
|
|
294
299
|
"To do": "To do",
|
|
295
300
|
"Next step": "Next step",
|
|
296
301
|
Review: "Review",
|
|
@@ -608,6 +613,7 @@ var translations = {
|
|
|
608
613
|
"No linked emails yet": "No linked emails yet",
|
|
609
614
|
"Could not load emails": "Could not load emails",
|
|
610
615
|
"Open in Mail": "Open in Mail",
|
|
616
|
+
"Open in Office": "Open in Office",
|
|
611
617
|
"(no subject)": "(no subject)",
|
|
612
618
|
"Unknown sender": "Unknown sender",
|
|
613
619
|
"No conversations linked yet": "No conversations linked yet",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/esm/l10n/nl.json.js
CHANGED
|
@@ -291,6 +291,11 @@ var translations = {
|
|
|
291
291
|
"JSON pointer": "JSON-aanwijzer",
|
|
292
292
|
Last: "Laatste",
|
|
293
293
|
"Load more": "Meer laden",
|
|
294
|
+
"Nothing to open here.": "Hier is niets te openen.",
|
|
295
|
+
"Could not check": "Kon niet controleren",
|
|
296
|
+
"Could not check: {subject}": "Kon niet controleren: {subject}",
|
|
297
|
+
"The check failed.": "De controle is mislukt.",
|
|
298
|
+
"The check failed: {reason}": "De controle is mislukt: {reason}",
|
|
294
299
|
"To do": "Nog doen",
|
|
295
300
|
"Next step": "Volgende stap",
|
|
296
301
|
Review: "Beoordelen",
|
|
@@ -608,6 +613,7 @@ var translations = {
|
|
|
608
613
|
"No linked emails yet": "Nog geen gekoppelde e-mails",
|
|
609
614
|
"Could not load emails": "Kon e-mails niet laden",
|
|
610
615
|
"Open in Mail": "Openen in Mail",
|
|
616
|
+
"Open in Office": "Openen in Office",
|
|
611
617
|
"(no subject)": "(geen onderwerp)",
|
|
612
618
|
"Unknown sender": "Onbekende afzender",
|
|
613
619
|
"No conversations linked yet": "Nog geen gesprekken gekoppeld",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -325,8 +325,7 @@
|
|
|
325
325
|
outline-offset: -2px;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
/* Multi-column sort priority badge (1, 2, 3)
|
|
329
|
-
one sort key is active; a single active sort shows the arrow alone. */
|
|
328
|
+
/* Multi-column sort priority badge (1, 2, 3), rendered only when two or more rendered, sortable columns are sorted. */
|
|
330
329
|
.cn-table-sort-badge {
|
|
331
330
|
display: inline-flex;
|
|
332
331
|
align-items: center;
|
|
@@ -2660,9 +2659,17 @@
|
|
|
2660
2659
|
font-weight: 700;
|
|
2661
2660
|
line-height: 1.3;
|
|
2662
2661
|
color: var(--color-main-text);
|
|
2663
|
-
|
|
2662
|
+
/* Two lines, then an ellipsis. One line cut a long case title short on a
|
|
2663
|
+
page with room to spare, and the title is the one thing that says which
|
|
2664
|
+
record this is. The element carries the full text as its `title`, so a
|
|
2665
|
+
title cut after two lines is still reachable. `overflow-wrap` breaks a
|
|
2666
|
+
word longer than the line, which nowrap used to hide. */
|
|
2667
|
+
display: -webkit-box;
|
|
2668
|
+
-webkit-box-orient: vertical;
|
|
2669
|
+
-webkit-line-clamp: 2;
|
|
2670
|
+
line-clamp: 2;
|
|
2664
2671
|
overflow: hidden;
|
|
2665
|
-
|
|
2672
|
+
overflow-wrap: anywhere;
|
|
2666
2673
|
}
|
|
2667
2674
|
|
|
2668
2675
|
.cn-detail-page__subtitle {
|
|
@@ -6352,8 +6359,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
|
|
6352
6359
|
outline-offset: -2px;
|
|
6353
6360
|
}
|
|
6354
6361
|
|
|
6355
|
-
/* Multi-column sort priority badge (1, 2, 3)
|
|
6356
|
-
one sort key is active; a single active sort shows the arrow alone. */
|
|
6362
|
+
/* Multi-column sort priority badge (1, 2, 3), rendered only when two or more rendered, sortable columns are sorted. */
|
|
6357
6363
|
.cn-table-sort-badge {
|
|
6358
6364
|
display: inline-flex;
|
|
6359
6365
|
align-items: center;
|
|
@@ -17505,6 +17511,30 @@ to { background-position: -200% 0;
|
|
|
17505
17511
|
color: inherit;
|
|
17506
17512
|
}
|
|
17507
17513
|
|
|
17514
|
+
/* The check failed. Muted text, no fill, no severity colour: it is a note
|
|
17515
|
+
that something is not known, not a warning. */
|
|
17516
|
+
.cn-banner-widget--unchecked[data-v-3531692f] {
|
|
17517
|
+
box-sizing: border-box;
|
|
17518
|
+
margin: 0;
|
|
17519
|
+
padding: 8px 12px;
|
|
17520
|
+
color: var(--color-text-maxcontrast);
|
|
17521
|
+
font-size: 0.9em;
|
|
17522
|
+
cursor: help;
|
|
17523
|
+
}
|
|
17524
|
+
|
|
17525
|
+
/* The reason, for assistive technology; sighted readers get the tooltip. */
|
|
17526
|
+
.cn-banner-widget__unchecked-reason[data-v-3531692f] {
|
|
17527
|
+
position: absolute;
|
|
17528
|
+
width: 1px;
|
|
17529
|
+
height: 1px;
|
|
17530
|
+
margin: -1px;
|
|
17531
|
+
padding: 0;
|
|
17532
|
+
overflow: hidden;
|
|
17533
|
+
clip-path: inset(50%);
|
|
17534
|
+
white-space: nowrap;
|
|
17535
|
+
border: 0;
|
|
17536
|
+
}
|
|
17537
|
+
|
|
17508
17538
|
/* Attention card. The severity edge is an inset shadow, so it takes no width. */
|
|
17509
17539
|
.cn-banner-widget--attention[data-v-3531692f] {
|
|
17510
17540
|
--cn-banner-accent: var(--color-primary-element);
|
|
@@ -31807,6 +31837,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
31807
31837
|
.cn-wiki-page__header[data-v-25a3d3cf] {
|
|
31808
31838
|
margin-bottom: 16px;
|
|
31809
31839
|
}
|
|
31840
|
+
|
|
31841
|
+
/* Without a sidebar the title starts at the left edge of the content, under
|
|
31842
|
+
the Nextcloud navigation toggle button (44px wide). Clear it as CnPageHeader
|
|
31843
|
+
does. With a sidebar the tree sits there instead and the title is clear. */
|
|
31844
|
+
.cn-wiki-page:not(.cn-wiki-page--has-sidebar) .cn-wiki-page__header[data-v-25a3d3cf] {
|
|
31845
|
+
padding-inline-start: 56px;
|
|
31846
|
+
}
|
|
31810
31847
|
.cn-wiki-page__title[data-v-25a3d3cf] {
|
|
31811
31848
|
font-size: 1.6em;
|
|
31812
31849
|
margin: 0;
|
|
@@ -31866,6 +31903,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
31866
31903
|
.cn-reports-page[data-v-41525752] {
|
|
31867
31904
|
padding: 16px;
|
|
31868
31905
|
}
|
|
31906
|
+
|
|
31907
|
+
/* Clear the Nextcloud navigation toggle button (44px wide, absolutely
|
|
31908
|
+
positioned at the left edge of .app-content) plus 12px breathing room, as
|
|
31909
|
+
CnPageHeader does. Only the header shifts; the body keeps the full width. */
|
|
31910
|
+
.cn-reports-page__header[data-v-41525752] {
|
|
31911
|
+
padding-inline-start: 56px;
|
|
31912
|
+
}
|
|
31869
31913
|
.cn-reports-page__title[data-v-41525752] {
|
|
31870
31914
|
margin: 0 0 4px;
|
|
31871
31915
|
}
|
|
@@ -31922,6 +31966,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
31922
31966
|
padding: 16px;
|
|
31923
31967
|
max-width: 1200px;
|
|
31924
31968
|
}
|
|
31969
|
+
|
|
31970
|
+
/* Clear the Nextcloud navigation toggle button (44px wide, absolutely
|
|
31971
|
+
positioned at the left edge of .app-content) plus 12px breathing room, as
|
|
31972
|
+
CnPageHeader does. Only the header shifts; the body keeps the full width. */
|
|
31973
|
+
.cn-store-page__header[data-v-25c8171b] {
|
|
31974
|
+
padding-inline-start: 56px;
|
|
31975
|
+
}
|
|
31925
31976
|
.cn-store-page__heading[data-v-25c8171b] {
|
|
31926
31977
|
display: flex;
|
|
31927
31978
|
flex-wrap: wrap;
|
|
@@ -32005,6 +32056,122 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
32005
32056
|
}
|
|
32006
32057
|
|
|
32007
32058
|
|
|
32059
|
+
.cn-link-cards-page[data-v-2f9a546b] {
|
|
32060
|
+
display: flex;
|
|
32061
|
+
flex-direction: column;
|
|
32062
|
+
gap: calc(6 * var(--default-grid-baseline));
|
|
32063
|
+
padding: calc(5 * var(--default-grid-baseline));
|
|
32064
|
+
}
|
|
32065
|
+
|
|
32066
|
+
/* Clear the Nextcloud navigation toggle button (44px wide, absolutely
|
|
32067
|
+
positioned at the left edge of .app-content) plus 12px breathing room, as
|
|
32068
|
+
CnPageHeader does. Only the header shifts; the cards keep the full width. */
|
|
32069
|
+
.cn-link-cards-page__header[data-v-2f9a546b] {
|
|
32070
|
+
padding-inline-start: 56px;
|
|
32071
|
+
}
|
|
32072
|
+
.cn-link-cards-page__title[data-v-2f9a546b] {
|
|
32073
|
+
margin: 0 0 var(--default-grid-baseline);
|
|
32074
|
+
}
|
|
32075
|
+
.cn-link-cards-page__description[data-v-2f9a546b],
|
|
32076
|
+
.cn-link-cards-page__empty[data-v-2f9a546b] {
|
|
32077
|
+
color: var(--color-text-maxcontrast);
|
|
32078
|
+
margin: 0;
|
|
32079
|
+
max-width: 70ch;
|
|
32080
|
+
}
|
|
32081
|
+
.cn-link-cards-page__group[data-v-2f9a546b] {
|
|
32082
|
+
display: flex;
|
|
32083
|
+
flex-direction: column;
|
|
32084
|
+
gap: calc(3 * var(--default-grid-baseline));
|
|
32085
|
+
}
|
|
32086
|
+
.cn-link-cards-page__caption[data-v-2f9a546b] {
|
|
32087
|
+
color: var(--color-text-maxcontrast);
|
|
32088
|
+
font-size: 0.85em;
|
|
32089
|
+
font-weight: 600;
|
|
32090
|
+
letter-spacing: 0.06em;
|
|
32091
|
+
margin: 0;
|
|
32092
|
+
text-transform: uppercase;
|
|
32093
|
+
}
|
|
32094
|
+
.cn-link-cards-page__grid[data-v-2f9a546b] {
|
|
32095
|
+
display: grid;
|
|
32096
|
+
gap: calc(3 * var(--default-grid-baseline));
|
|
32097
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
32098
|
+
list-style: none;
|
|
32099
|
+
margin: 0;
|
|
32100
|
+
padding: 0;
|
|
32101
|
+
}
|
|
32102
|
+
.cn-link-cards-page__cell[data-v-2f9a546b] {
|
|
32103
|
+
display: flex;
|
|
32104
|
+
}
|
|
32105
|
+
.cn-link-cards-page__card[data-v-2f9a546b] {
|
|
32106
|
+
align-items: flex-start;
|
|
32107
|
+
background: var(--color-main-background);
|
|
32108
|
+
border: 1px solid var(--color-border-dark);
|
|
32109
|
+
border-radius: var(--border-radius-large);
|
|
32110
|
+
color: var(--color-main-text);
|
|
32111
|
+
display: flex;
|
|
32112
|
+
flex: 1;
|
|
32113
|
+
gap: calc(3 * var(--default-grid-baseline));
|
|
32114
|
+
min-width: 0;
|
|
32115
|
+
padding: calc(4 * var(--default-grid-baseline));
|
|
32116
|
+
transition: background-color var(--animation-quick, 100ms) ease, border-color var(--animation-quick, 100ms) ease, box-shadow var(--animation-quick, 100ms) ease;
|
|
32117
|
+
}
|
|
32118
|
+
|
|
32119
|
+
/* A card is a link, and it must not look like a line of prose. A theme may
|
|
32120
|
+
underline every link with `!important` (thematiq: `a { text-decoration:
|
|
32121
|
+
underline !important }`, specificity 0,0,1). Among `!important` rules the
|
|
32122
|
+
higher specificity wins, so this class rule has to carry it too; without it
|
|
32123
|
+
the label and the description both rendered underlined on a live Nextcloud. */
|
|
32124
|
+
.cn-link-cards-page__card[data-v-2f9a546b],
|
|
32125
|
+
.cn-link-cards-page__card[data-v-2f9a546b]:hover,
|
|
32126
|
+
.cn-link-cards-page__card[data-v-2f9a546b]:focus,
|
|
32127
|
+
.cn-link-cards-page__card[data-v-2f9a546b]:active {
|
|
32128
|
+
text-decoration: none !important;
|
|
32129
|
+
}
|
|
32130
|
+
|
|
32131
|
+
/* Hover moves the whole card: border, background and a soft lift. */
|
|
32132
|
+
.cn-link-cards-page__card[data-v-2f9a546b]:hover {
|
|
32133
|
+
background: var(--color-background-hover);
|
|
32134
|
+
border-color: var(--color-primary-element);
|
|
32135
|
+
box-shadow: 0 2px 6px var(--color-box-shadow);
|
|
32136
|
+
}
|
|
32137
|
+
@media (prefers-reduced-motion: reduce) {
|
|
32138
|
+
.cn-link-cards-page__card[data-v-2f9a546b] {
|
|
32139
|
+
transition: none;
|
|
32140
|
+
}
|
|
32141
|
+
}
|
|
32142
|
+
.cn-link-cards-page__card[data-v-2f9a546b]:focus-visible {
|
|
32143
|
+
border-color: var(--color-primary-element);
|
|
32144
|
+
outline: 2px solid var(--color-primary-element);
|
|
32145
|
+
outline-offset: 2px;
|
|
32146
|
+
}
|
|
32147
|
+
.cn-link-cards-page__icon[data-v-2f9a546b] {
|
|
32148
|
+
color: var(--color-primary-element);
|
|
32149
|
+
flex: none;
|
|
32150
|
+
}
|
|
32151
|
+
.cn-link-cards-page__body[data-v-2f9a546b] {
|
|
32152
|
+
display: flex;
|
|
32153
|
+
flex-direction: column;
|
|
32154
|
+
gap: var(--default-grid-baseline);
|
|
32155
|
+
min-width: 0;
|
|
32156
|
+
}
|
|
32157
|
+
|
|
32158
|
+
/* The label is the card's title. Its colour is set here, on the span, because
|
|
32159
|
+
a theme may force its link colour on the `a` itself; an inherited link
|
|
32160
|
+
colour made the title read as a hyperlink. */
|
|
32161
|
+
.cn-link-cards-page__label[data-v-2f9a546b] {
|
|
32162
|
+
color: var(--color-main-text);
|
|
32163
|
+
font-size: 1.05em;
|
|
32164
|
+
font-weight: 700;
|
|
32165
|
+
line-height: 1.3;
|
|
32166
|
+
overflow-wrap: anywhere;
|
|
32167
|
+
}
|
|
32168
|
+
.cn-link-cards-page__card-description[data-v-2f9a546b] {
|
|
32169
|
+
color: var(--color-text-maxcontrast);
|
|
32170
|
+
font-size: 0.9em;
|
|
32171
|
+
line-height: 1.4;
|
|
32172
|
+
}
|
|
32173
|
+
|
|
32174
|
+
|
|
32008
32175
|
/* NcDialog supplies the padding and the heading (via `name`); this div keeps only
|
|
32009
32176
|
the body's own column layout. */
|
|
32010
32177
|
.cn-page-config[data-v-9e9a9cf8] {
|
|
@@ -34218,15 +34385,40 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
34218
34385
|
|
|
34219
34386
|
|
|
34220
34387
|
.cn-brand-stripe[data-v-6fdfa763] {
|
|
34388
|
+
--cn-brand-stripe-first: var(--cn-brand-stripe-ratio-1, 1);
|
|
34389
|
+
--cn-brand-stripe-second: var(--cn-brand-stripe-ratio-2, 1);
|
|
34390
|
+
--cn-brand-stripe-total: calc(
|
|
34391
|
+
var(--cn-brand-stripe-first) + var(--cn-brand-stripe-second) + var(--cn-brand-stripe-ratio-3, 1)
|
|
34392
|
+
);
|
|
34393
|
+
--cn-brand-stripe-stop-1: calc(var(--cn-brand-stripe-first) / var(--cn-brand-stripe-total) * 100%);
|
|
34394
|
+
--cn-brand-stripe-stop-2: calc(
|
|
34395
|
+
(var(--cn-brand-stripe-first) + var(--cn-brand-stripe-second)) / var(--cn-brand-stripe-total) * 100%
|
|
34396
|
+
);
|
|
34397
|
+
--cn-brand-stripe-direction: to right;
|
|
34398
|
+
--cn-brand-stripe-bands: linear-gradient(
|
|
34399
|
+
var(--cn-brand-stripe-direction),
|
|
34400
|
+
var(--cn-brand-stripe-color-1, var(--color-primary-element)) 0 var(--cn-brand-stripe-stop-1),
|
|
34401
|
+
var(--cn-brand-stripe-color-2, var(--color-primary-element)) var(--cn-brand-stripe-stop-1) var(--cn-brand-stripe-stop-2),
|
|
34402
|
+
var(--cn-brand-stripe-color-3, var(--color-primary-element)) var(--cn-brand-stripe-stop-2) 100%
|
|
34403
|
+
);
|
|
34221
34404
|
display: flex;
|
|
34222
34405
|
flex: none;
|
|
34223
34406
|
width: 100%;
|
|
34224
34407
|
height: var(--cn-brand-stripe-height, 4px);
|
|
34408
|
+
background-image: var(--cn-brand-stripe-image, var(--cn-brand-stripe-bands));
|
|
34409
|
+
background-repeat: repeat-x;
|
|
34410
|
+
background-size: auto 100%;
|
|
34411
|
+
}
|
|
34412
|
+
.cn-brand-stripe--inverse[data-v-6fdfa763] {
|
|
34413
|
+
background-image: var(--cn-brand-stripe-image-inverse, var(--cn-brand-stripe-image, var(--cn-brand-stripe-bands)));
|
|
34225
34414
|
}
|
|
34226
34415
|
.cn-brand-stripe--vertical[data-v-6fdfa763] {
|
|
34416
|
+
--cn-brand-stripe-direction: to bottom;
|
|
34227
34417
|
flex-direction: column;
|
|
34228
34418
|
width: var(--cn-brand-stripe-height, 4px);
|
|
34229
34419
|
height: 100%;
|
|
34420
|
+
background-repeat: repeat-y;
|
|
34421
|
+
background-size: 100% auto;
|
|
34230
34422
|
}
|
|
34231
34423
|
.cn-brand-stripe__band[data-v-6fdfa763] {
|
|
34232
34424
|
flex-basis: 0;
|
|
@@ -34234,15 +34426,12 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
34234
34426
|
}
|
|
34235
34427
|
.cn-brand-stripe__band--1[data-v-6fdfa763] {
|
|
34236
34428
|
flex-grow: var(--cn-brand-stripe-ratio-1, 1);
|
|
34237
|
-
background-color: var(--cn-brand-stripe-color-1, var(--color-primary-element));
|
|
34238
34429
|
}
|
|
34239
34430
|
.cn-brand-stripe__band--2[data-v-6fdfa763] {
|
|
34240
34431
|
flex-grow: var(--cn-brand-stripe-ratio-2, 1);
|
|
34241
|
-
background-color: var(--cn-brand-stripe-color-2, var(--color-primary-element));
|
|
34242
34432
|
}
|
|
34243
34433
|
.cn-brand-stripe__band--3[data-v-6fdfa763] {
|
|
34244
34434
|
flex-grow: var(--cn-brand-stripe-ratio-3, 1);
|
|
34245
|
-
background-color: var(--cn-brand-stripe-color-3, var(--color-primary-element));
|
|
34246
34435
|
}
|
|
34247
34436
|
@media print {
|
|
34248
34437
|
.cn-brand-stripe[data-v-6fdfa763] {
|
|
@@ -130,10 +130,15 @@ function capitalize(str) {
|
|
|
130
130
|
*
|
|
131
131
|
* Array values are serialized with PHP bracket notation (`key[]=a&key[]=b`)
|
|
132
132
|
* so the Nextcloud/OpenRegister backend parses them as an array — a plain
|
|
133
|
-
* repeated `key=a&key=b` collapses to the last value in PHP.
|
|
134
|
-
*
|
|
133
|
+
* repeated `key=a&key=b` collapses to the last value in PHP. A plain object
|
|
134
|
+
* under a filter key is a set of operators and goes out in bracket form
|
|
135
|
+
* (`{ deadline: { lt: 'x' } }` becomes `deadline[lt]=x`); a key already
|
|
136
|
+
* written that way (`'deadline[lt]'`) passes through unchanged. Reserved keys
|
|
137
|
+
* that start with `_` keep their JSON form (`_order`). Skips
|
|
135
138
|
* null/undefined/empty values.
|
|
136
139
|
*
|
|
140
|
+
* @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-filter-operator-is-serialised-in-bracket-form
|
|
141
|
+
*
|
|
137
142
|
* @param {object} params Key-value pairs for query parameters
|
|
138
143
|
* @return {string} Query string including leading '?' or empty string
|
|
139
144
|
*/
|
|
@@ -159,6 +164,27 @@ function buildQueryString(params = {}) {
|
|
|
159
164
|
queryParams.append(arrayKey, String(item));
|
|
160
165
|
}
|
|
161
166
|
}
|
|
167
|
+
} else if (typeof value === 'object' && !key.startsWith('_')) {
|
|
168
|
+
// A filter operator: `{ deadline: { lt: '2026-10-06' } }` goes out
|
|
169
|
+
// as `deadline[lt]=2026-10-06`, the form OpenRegister reads. As
|
|
170
|
+
// JSON it reached the database as the literal value and a date
|
|
171
|
+
// column answered 500. Reserved `_` keys (`_order`) keep JSON.
|
|
172
|
+
for (const [op, operand] of Object.entries(value)) {
|
|
173
|
+
if (operand === undefined || operand === null || operand === '') {
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
if (Array.isArray(operand)) {
|
|
177
|
+
for (const item of operand) {
|
|
178
|
+
if (item !== undefined && item !== null && item !== '') {
|
|
179
|
+
queryParams.append(`${key}[${op}][]`, String(item));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} else if (typeof operand === 'object') {
|
|
183
|
+
queryParams.set(`${key}[${op}]`, JSON.stringify(operand));
|
|
184
|
+
} else {
|
|
185
|
+
queryParams.set(`${key}[${op}]`, String(operand));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
162
188
|
} else if (typeof value === 'object') {
|
|
163
189
|
queryParams.set(key, JSON.stringify(value));
|
|
164
190
|
} else {
|