@conduction/nextcloud-vue 2.61.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.
Files changed (61) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +128 -0
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  4. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +79 -2
  5. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  6. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
  7. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
  9. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
  11. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +30 -6
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +40 -15
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -2
  18. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  19. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
  22. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  23. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
  24. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  25. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
  31. package/dist/esm/l10n/en.json.js +5 -0
  32. package/dist/esm/l10n/en.json.js.map +1 -1
  33. package/dist/esm/l10n/nl.json.js +5 -0
  34. package/dist/esm/l10n/nl.json.js.map +1 -1
  35. package/dist/esm/nextcloud-vue.css +108 -9
  36. package/dist/esm/utils/validateManifestV2.compiled.js +1 -1
  37. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  38. package/dist/nextcloud-vue.cjs.js +857 -490
  39. package/dist/nextcloud-vue.cjs.js.map +2 -2
  40. package/dist/nextcloud-vue.css +108 -9
  41. package/l10n/en.json +5 -0
  42. package/l10n/nl.json +5 -0
  43. package/package.json +3 -2
  44. package/scripts/manifest-schema-hash.js +41 -0
  45. package/scripts/record-manifest-schema-hash.js +52 -0
  46. package/src/components/CnAppNav/CnAppNav.vue +128 -0
  47. package/src/components/CnBannerWidget/CnBannerWidget.vue +113 -2
  48. package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
  49. package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
  50. package/src/components/CnDashboardPage/CnDashboardPage.vue +30 -6
  51. package/src/components/CnDataTable/CnDataTable.md +1 -1
  52. package/src/components/CnDataTable/CnDataTable.vue +42 -17
  53. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +35 -2
  54. package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
  55. package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
  56. package/src/components/CnStorePage/CnStorePage.vue +7 -0
  57. package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
  58. package/src/css/table.css +1 -2
  59. package/src/public/index.js +5 -0
  60. package/src/schemas/app-manifest-v2.schema.json +1 -1
  61. package/src/utils/validateManifestV2.compiled.js +1 -1
@@ -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":";;;;;;AA0HA;;AAEC;;;;;;AAMA;AACA;;AAEA;;AAEA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,YAAY;;CAElB,UAAU,EAAE;AACX,EAAA,cAAc;AACd,EAAA,mBAAmB;AACnB,kBAAAA,UAAc;EACd;;AAED,CAAA,KAAK,EAAE;AACN;;;;;AAKC;AACA;;AAED,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;AACC;;AAEA;AACA;AACA;;EAED,IAAI,EAAE;GACL,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;AAED;;;;;;AAMA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;AAED,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAM;GACf;;AAED;;;;EAIA,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO;GAChB;;AAED;;;;;AAKA,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;;;AAKA,EAAA,aAAa,EAAE;GACd,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;AAIA,EAAA,eAAe,EAAE;GAChB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,UAAU;GACnB;;AAED;;AAEC;;AAED,EAAA,iBAAiB,EAAE;GAClB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;EACA,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAC;GACtD;;;AAGD,EAAA,gBAAgB,EAAE;GACjB,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,2CAA2C,CAAC;GAC9E;;;AAGD,EAAA,aAAa,EAAE;GACd,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,YAAY,CAAC;GAC/C;;;AAGD,EAAA,oBAAoB,EAAE;GACrB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,kCAAkC,CAAC;GACrE;EACD;;AAED,CAAA,KAAK,EAAE;AACN;;;;;EAKA,YAAY;AACZ;;;AAGC;;AAED,EAAA,OAAO;EACP;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;AACA;;EAED,0BAA0B,GAAG;AAC5B,GAAA,OAAO,IAAI,CAAC,qBAAqB,IAAI,CAAC,UAAS;GAC/C;;AAED;;;;;;EAMA,UAAU,GAAG;AACZ,GAAA,OAAO,OAAO,CAAC,IAAI,CAAC,aAAa,CAAE,IAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAA,IAAK,IAAI,CAAC,IAAI,CAAC,SAAS,CAAA;GACrF;;AAED;AACA,EAAA,aAAa,GAAG;AACf,GAAA,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;AAClB,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAA;AAC1C,GAAA,OAAO,OAAO,KAAM,KAAI,QAAS,GAAE,KAAI,GAAI,EAAC;GAC5C;;AAED;;AAEC;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;AAClB,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,MAAO,GAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAA;AAC7C,GAAA,IAAI;IACH,OAAO,gBAAgB,CAAC,MAAM,CAAA;IAC7B,CAAA,OAAO,KAAK,EAAE;IACf,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAA;AACzB,IAAA,OAAO,EAAC;AACT,IAAA;GACA;EACD;;AAED,CAAA,OAAO,EAAE;AACR;AACC;AACA;AACA;;AAED,EAAA,WAAW,CAAC,IAAI,EAAE;GACjB,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,IAAI,EAAA;GAC7B;;AAED;;;AAGC;;AAEA;;EAED,OAAO,CAAC,IAAI,EAAE;AACb,GAAA,IAAI,CAAC,IAAI,EAAE;AACV,IAAA,OAAO,EAAC;AACT,IAAA;GACA,IAAI,OAAO,IAAI,CAAC,EAAC,KAAM,QAAO,IAAK,OAAO,IAAI,CAAC,OAAO,QAAQ,EAAE;AAC/D,IAAA,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAA;AACtB,IAAA;GACA,MAAM,KAAM,GAAE,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAA;GAClD,IAAI,OAAO,UAAU,QAAQ,EAAE;IAC9B,OAAO,KAAI;AACZ,IAAA;GACA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAA;GAC1B;EACD;AACF;;;;"}
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -292,6 +292,10 @@ var translations = {
292
292
  Last: "Last",
293
293
  "Load more": "Load more",
294
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}",
295
299
  "To do": "To do",
296
300
  "Next step": "Next step",
297
301
  Review: "Review",
@@ -609,6 +613,7 @@ var translations = {
609
613
  "No linked emails yet": "No linked emails yet",
610
614
  "Could not load emails": "Could not load emails",
611
615
  "Open in Mail": "Open in Mail",
616
+ "Open in Office": "Open in Office",
612
617
  "(no subject)": "(no subject)",
613
618
  "Unknown sender": "Unknown sender",
614
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -292,6 +292,10 @@ var translations = {
292
292
  Last: "Laatste",
293
293
  "Load more": "Meer laden",
294
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}",
295
299
  "To do": "Nog doen",
296
300
  "Next step": "Volgende stap",
297
301
  Review: "Beoordelen",
@@ -609,6 +613,7 @@ var translations = {
609
613
  "No linked emails yet": "Nog geen gekoppelde e-mails",
610
614
  "Could not load emails": "Kon e-mails niet laden",
611
615
  "Open in Mail": "Openen in Mail",
616
+ "Open in Office": "Openen in Office",
612
617
  "(no subject)": "(geen onderwerp)",
613
618
  "Unknown sender": "Onbekende afzender",
614
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) — only rendered once more than
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;
@@ -6360,8 +6359,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
6360
6359
  outline-offset: -2px;
6361
6360
  }
6362
6361
 
6363
- /* Multi-column sort priority badge (1, 2, 3) — only rendered once more than
6364
- 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. */
6365
6363
  .cn-table-sort-badge {
6366
6364
  display: inline-flex;
6367
6365
  align-items: center;
@@ -17513,6 +17511,30 @@ to { background-position: -200% 0;
17513
17511
  color: inherit;
17514
17512
  }
17515
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
+
17516
17538
  /* Attention card. The severity edge is an inset shadow, so it takes no width. */
17517
17539
  .cn-banner-widget--attention[data-v-3531692f] {
17518
17540
  --cn-banner-accent: var(--color-primary-element);
@@ -31815,6 +31837,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
31815
31837
  .cn-wiki-page__header[data-v-25a3d3cf] {
31816
31838
  margin-bottom: 16px;
31817
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
+ }
31818
31847
  .cn-wiki-page__title[data-v-25a3d3cf] {
31819
31848
  font-size: 1.6em;
31820
31849
  margin: 0;
@@ -31874,6 +31903,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
31874
31903
  .cn-reports-page[data-v-41525752] {
31875
31904
  padding: 16px;
31876
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
+ }
31877
31913
  .cn-reports-page__title[data-v-41525752] {
31878
31914
  margin: 0 0 4px;
31879
31915
  }
@@ -31930,6 +31966,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
31930
31966
  padding: 16px;
31931
31967
  max-width: 1200px;
31932
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
+ }
31933
31976
  .cn-store-page__heading[data-v-25c8171b] {
31934
31977
  display: flex;
31935
31978
  flex-wrap: wrap;
@@ -32019,6 +32062,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
32019
32062
  gap: calc(6 * var(--default-grid-baseline));
32020
32063
  padding: calc(5 * var(--default-grid-baseline));
32021
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
+ }
32022
32072
  .cn-link-cards-page__title[data-v-2f9a546b] {
32023
32073
  margin: 0 0 var(--default-grid-baseline);
32024
32074
  }
@@ -32063,11 +32113,31 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
32063
32113
  gap: calc(3 * var(--default-grid-baseline));
32064
32114
  min-width: 0;
32065
32115
  padding: calc(4 * var(--default-grid-baseline));
32066
- text-decoration: none;
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;
32067
32129
  }
32130
+
32131
+ /* Hover moves the whole card: border, background and a soft lift. */
32068
32132
  .cn-link-cards-page__card[data-v-2f9a546b]:hover {
32069
32133
  background: var(--color-background-hover);
32070
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
+ }
32071
32141
  }
32072
32142
  .cn-link-cards-page__card[data-v-2f9a546b]:focus-visible {
32073
32143
  border-color: var(--color-primary-element);
@@ -32084,8 +32154,15 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
32084
32154
  gap: var(--default-grid-baseline);
32085
32155
  min-width: 0;
32086
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. */
32087
32161
  .cn-link-cards-page__label[data-v-2f9a546b] {
32088
- font-weight: 600;
32162
+ color: var(--color-main-text);
32163
+ font-size: 1.05em;
32164
+ font-weight: 700;
32165
+ line-height: 1.3;
32089
32166
  overflow-wrap: anywhere;
32090
32167
  }
32091
32168
  .cn-link-cards-page__card-description[data-v-2f9a546b] {
@@ -34308,15 +34385,40 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
34308
34385
 
34309
34386
 
34310
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
+ );
34311
34404
  display: flex;
34312
34405
  flex: none;
34313
34406
  width: 100%;
34314
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)));
34315
34414
  }
34316
34415
  .cn-brand-stripe--vertical[data-v-6fdfa763] {
34416
+ --cn-brand-stripe-direction: to bottom;
34317
34417
  flex-direction: column;
34318
34418
  width: var(--cn-brand-stripe-height, 4px);
34319
34419
  height: 100%;
34420
+ background-repeat: repeat-y;
34421
+ background-size: 100% auto;
34320
34422
  }
34321
34423
  .cn-brand-stripe__band[data-v-6fdfa763] {
34322
34424
  flex-basis: 0;
@@ -34324,15 +34426,12 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
34324
34426
  }
34325
34427
  .cn-brand-stripe__band--1[data-v-6fdfa763] {
34326
34428
  flex-grow: var(--cn-brand-stripe-ratio-1, 1);
34327
- background-color: var(--cn-brand-stripe-color-1, var(--color-primary-element));
34328
34429
  }
34329
34430
  .cn-brand-stripe__band--2[data-v-6fdfa763] {
34330
34431
  flex-grow: var(--cn-brand-stripe-ratio-2, 1);
34331
- background-color: var(--cn-brand-stripe-color-2, var(--color-primary-element));
34332
34432
  }
34333
34433
  .cn-brand-stripe__band--3[data-v-6fdfa763] {
34334
34434
  flex-grow: var(--cn-brand-stripe-ratio-3, 1);
34335
- background-color: var(--cn-brand-stripe-color-3, var(--color-primary-element));
34336
34435
  }
34337
34436
  @media print {
34338
34437
  .cn-brand-stripe[data-v-6fdfa763] {