@conduction/nextcloud-vue 2.78.0 → 2.79.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/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +80 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +115 -91
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +25 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +61 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +106 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +314 -15
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2039 -581
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +314 -15
- package/l10n/en.json +61 -1
- package/l10n/nl.json +106 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +119 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +81 -2
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +27 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/index.js +1 -1
- package/src/composables/useAppManifest.js +8 -3
- package/src/css/look-board-detail.css +10 -0
- package/src/css/look-board-index.css +92 -14
- package/src/css/look-board.css +77 -0
- package/src/css/table.css +37 -0
- package/src/index.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +46 -1
- package/src/utils/boardDate.js +189 -0
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFilesTab.vue3.js","sources":["../../../../src/components/CnObjectSidebar/CnFilesTab.vue?vue&type=template&id=6b917036&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-sidebar-tab\">\n\t\t<!-- The object's folder as a real files browser, on the Files app's own\n\t\t primitives (its actions, its New menu, its breadcrumbs), when the\n\t\t folder resolves for this user. The list below is the fallback for an\n\t\t object with no folder, or a user the folder is not shared with. -->\n\t\t<CnFilesBrowser\n\t\t\tv-if=\"browserRoot !== null\"\n\t\t\t:rootPath=\"browserRoot\"\n\t\t\t:rootLabel=\"browserRootLabel\"\n\t\t\t:uploadButton=\"uploadButton\"\n\t\t\t:dropOverlay=\"dropOverlay\"\n\t\t\t:dropHint=\"dropHint\"\n\t\t\t:rowActions=\"rowActions\"\n\t\t\t:newActions=\"newActions\"\n\t\t\t:linkedItems=\"linkedItems\"\n\t\t\t@changed=\"onBrowserChanged\" />\n\t\t<template v-else>\n\t\t\t<!-- Upload error -->\n\t\t\t<div v-if=\"uploadError\" class=\"cn-sidebar-tab__upload-error\">\n\t\t\t\t{{ uploadError }}\n\t\t\t</div>\n\n\t\t\t<!-- Share toggle — seeds from schema config (defaultAutoShare) or\n\t\t from the `defaultShare` prop. Hidden via `showShareToggle=false`. -->\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\tv-if=\"showShareToggle\"\n\t\t\t\tv-model=\"share\"\n\t\t\t\tclass=\"cn-sidebar-tab__share\"\n\t\t\t\t:disabled=\"loading\"\n\t\t\t\ttype=\"switch\">\n\t\t\t\t{{ shareLabel }}\n\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t<!-- File drop zone -->\n\t\t\t<div\n\t\t\t\tclass=\"cn-sidebar-tab__dropzone\"\n\t\t\t\t:class=\"{ 'cn-sidebar-tab__dropzone--active': isDragOver }\"\n\t\t\t\t@click=\"triggerFileInput\"\n\t\t\t\t@dragover.prevent=\"onDragOver\"\n\t\t\t\t@dragleave.prevent=\"onDragLeave\"\n\t\t\t\t@drop.prevent=\"onDrop\">\n\t\t\t\t<!--\n\t\t\t\t`:ref` (dynamic), not `ref` (static), and this is load-bearing.\n\n\t\t\t\tEvery other binding on this input is static, and Vue caches the\n\t\t\t\t`@change` handler, so with a STATIC ref the compiler hoists the\n\t\t\t\twhole vnode to module scope. A hoisted vnode is created once,\n\t\t\t\toutside any render, so its ref carries no owner instance\n\t\t\t\t(`rawRef.i === null`).\n\n\t\t\t\t`setRef()` guards that — but the guard AND its early `return`\n\t\t\t\tlive inside `if (process.env.NODE_ENV !== 'production')`. In a\n\t\t\t\tdevelopment build you get the warning \"Missing ref owner\n\t\t\t\tcontext. ref cannot be used on hoisted vnodes.\" and nothing\n\t\t\t\tbreaks. In a PRODUCTION build the guard is compiled out and\n\t\t\t\texecution falls through to `owner.refs`, throwing\n\t\t\t\t`TypeError: Cannot read properties of null (reading 'refs')`\n\t\t\t\t— which aborts the render of whatever mounted this tab.\n\n\t\t\t\tMeasured: hrmq's detail pages (the only pages in the fleet that\n\t\t\t\treach CnIntegrationWidget through a `widgets[]` grid) rendered a\n\t\t\t\tCOMPLETELY BLANK content pane in production while looking fine\n\t\t\t\tin dev. A dynamic ref is compiled as a patchable prop, so the\n\t\t\t\tvnode is created inside the render function and owns a proper\n\t\t\t\tinstance.\n\t\t\t-->\n\t\t\t\t<input\n\t\t\t\t\t:ref=\"fileInputRef\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\tmultiple\n\t\t\t\t\tclass=\"cn-sidebar-tab__file-input\"\n\t\t\t\t\t@change=\"onFileUpload\">\n\t\t\t\t<Upload :size=\"24\" class=\"cn-sidebar-tab__dropzone-icon\" />\n\t\t\t\t<span class=\"cn-sidebar-tab__dropzone-text\">{{ dropZoneLabel }}</span>\n\t\t\t</div>\n\n\t\t\t<!-- File list. Each row borrows what Nextcloud already has on this\n\t\t page: the theme's own mime icon (OC.MimeType), the core preview\n\t\t endpoint for images, the Viewer for opening, and the Files sidebar\n\t\t for details, sharing and versions. Nothing of the Files app is\n\t\t rebuilt here; the row hands over to it. -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t<NcEmptyContent\n\t\t\t\tv-else-if=\"files.length === 0\"\n\t\t\t\tclass=\"cn-sidebar-tab__empty\"\n\t\t\t\t:name=\"noFilesLabel\"\n\t\t\t\t:description=\"dropZoneLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Paperclip :size=\"44\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t\t<div v-else class=\"cn-sidebar-tab__list\">\n\t\t\t\t<NcListItem\n\t\t\t\t\tv-for=\"file in files\"\n\t\t\t\t\t:key=\"file.id\"\n\t\t\t\t\t:name=\"file.name || file.title\"\n\t\t\t\t\t:bold=\"false\"\n\t\t\t\t\t:forceDisplayActions=\"true\"\n\t\t\t\t\t@click=\"openFile(file)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"previewUrlFor(file)\"\n\t\t\t\t\t\t\tclass=\"cn-sidebar-tab__thumb\"\n\t\t\t\t\t\t\t:src=\"previewUrlFor(file)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onPreviewError(file)\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-else-if=\"mimeIconFor(file)\"\n\t\t\t\t\t\t\tclass=\"cn-sidebar-tab__mime\"\n\t\t\t\t\t\t\t:src=\"mimeIconFor(file)\"\n\t\t\t\t\t\t\talt=\"\">\n\t\t\t\t\t\t<FileOutline v-else :size=\"32\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t<span class=\"cn-sidebar-tab__meta\">\n\t\t\t\t\t\t\t<span>{{ formatFileSize(file.size) }}</span>\n\t\t\t\t\t\t\t<template v-if=\"modifiedAt(file)\">\n\t\t\t\t\t\t\t\t<span aria-hidden=\"true\"> · </span>\n\t\t\t\t\t\t\t\t<NcDateTime :timestamp=\"modifiedAt(file)\" :ignoreSeconds=\"true\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t<template v-if=\"labelsOf(file)\">\n\t\t\t\t\t\t\t\t<span aria-hidden=\"true\"> · </span>\n\t\t\t\t\t\t\t\t<span>{{ labelsOf(file) }}</span>\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"openFile(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ openLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<!-- OpenRegister's Office page applies the object rule:\n\t\t\t\t\t\t update edits, read views, no read is a 404. -->\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-if=\"officeOpens(file)\"\n\t\t\t\t\t\t\t:href=\"officeUrlFor(file)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<FileDocumentEditOutline :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ openInOfficeLabel }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-if=\"file.id\"\n\t\t\t\t\t\t\t:href=\"downloadUrlFor(file)\"\n\t\t\t\t\t\t\t:download=\"file.name || file.title || ''\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Download :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ downloadLabel }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"canShowDetails() && file.path\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"showDetails(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<InformationOutline :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ detailsLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-if=\"file.id\"\n\t\t\t\t\t\t\t:href=\"showInFilesUrl(file)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<FolderOutline :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ showInFilesLabel }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionButton v-if=\"file.id\" :closeAfterClick=\"true\" @click=\"copyLink(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariant :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ copyLinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionSeparator />\n\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"deleteFile(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</NcListItem>\n\t\t\t</div>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"files.length < total\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:wide=\"true\"\n\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\tclass=\"cn-sidebar-tab__load-more\"\n\t\t\t\t@click=\"loadMore\">\n\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ loadMoreLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t\t<CnFilePreview v-if=\"previewFile\" :file=\"previewFile\" @close=\"previewFile = null\" />\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { getCapabilities } from '@nextcloud/capabilities'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateRemoteUrl, generateUrl } from '@nextcloud/router'\nimport {\n\tNcActionButton,\n\tNcActionLink,\n\tNcActionSeparator,\n\tNcButton,\n\tNcCheckboxRadioSwitch,\n\tNcDateTime,\n\tNcEmptyContent,\n\tNcListItem,\n\tNcLoadingIcon,\n} from '@nextcloud/vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport Download from 'vue-material-design-icons/Download.vue'\nimport FileDocumentEditOutline from 'vue-material-design-icons/FileDocumentEditOutline.vue'\nimport FileOutline from 'vue-material-design-icons/FileOutline.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Paperclip from 'vue-material-design-icons/Paperclip.vue'\nimport Upload from 'vue-material-design-icons/Upload.vue'\nimport CnFilePreview from '../CnFilePreview/CnFilePreview.vue'\nimport CnFilesBrowser from '../CnFilesBrowser/CnFilesBrowser.vue'\nimport { useFileOpener } from '../../composables/useFileOpener.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { resolveObjectFolder } from '../CnFilesBrowser/filesBrowser.js'\n\n/**\n * The mime types Nextcloud Office opens by default for the current person.\n *\n * richdocuments publishes its capabilities only to a person it lets use\n * Office, so an empty list means no Office here, or none for them.\n *\n * @return {Array<string>} The mime types, or an empty list.\n */\nfunction readOfficeMimetypes() {\n\ttry {\n\t\tconst mimetypes = getCapabilities()?.richdocuments?.mimetypes\n\t\treturn Array.isArray(mimetypes) ? mimetypes.filter((mime) => typeof mime === 'string') : []\n\t} catch {\n\t\treturn []\n\t}\n}\n\nexport default {\n\tname: 'CnFilesTab',\n\n\tcomponents: {\n\t\tCnFilePreview,\n\t\tCnFilesBrowser,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcActionSeparator,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcDateTime,\n\t\tNcEmptyContent,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tDelete,\n\t\tDownload,\n\t\tFileDocumentEditOutline,\n\t\tFileOutline,\n\t\tFolderOutline,\n\t\tInformationOutline,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPaperclip,\n\t\tUpload,\n\t},\n\n\tprops: {\n\t\t/** ID of the object this tab belongs to */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register slug */\n\t\tregister: { type: String, default: '' },\n\t\t/** JSON Schema definition for the object */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base URL for the OpenRegister API */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Text shown inside the file drop zone */\n\t\tdropZoneLabel: { type: String, default: () => t('nextcloud-vue', 'Drop files here or click to browse') },\n\t\t/** Text shown when no files are attached */\n\t\tnoFilesLabel: { type: String, default: () => t('nextcloud-vue', 'No files attached') },\n\t\t/** Label for the open/view file action */\n\t\topenLabel: { type: String, default: () => t('nextcloud-vue', 'Open') },\n\t\t/**\n\t\t * Label of the action that opens a document in Nextcloud Office through\n\t\t * OpenRegister's Office page. Shown only when Office is there for this\n\t\t * person and opens the file's type; the page opens it for editing or\n\t\t * read-only by the object's own rule.\n\t\t */\n\t\topenInOfficeLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Office') },\n\t\t/** Label for the delete action */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },\n\t\t/** What the files browser's root crumb reads; null shows the folder's own name, as the Files app does. */\n\t\tbrowserRootLabel: { type: String, default: null },\n\t\t/** Forwarded to CnFilesBrowser: a primary \"Add files\" button beside the New menu. */\n\t\tuploadButton: { type: Boolean, default: false },\n\t\t/** Forwarded to CnFilesBrowser: the drop state drawn over the list (\"Drop to add\") and announced. */\n\t\tdropOverlay: { type: Boolean, default: false },\n\t\t/** Forwarded to CnFilesBrowser: \"Or drag files onto this list.\" under the list. */\n\t\tdropHint: { type: Boolean, default: false },\n\t\t/**\n\t\t * The host's own actions on each file row of the browser, forwarded\n\t\t * to CnFilesBrowser's `rowActions`: declared like any manifest action\n\t\t * and dispatched with the file's id, name and path merged in.\n\t\t *\n\t\t * @type {Array<{id: string, label: string, icon?: string, type?: string, target?: string, props?: object}>}\n\t\t */\n\t\trowActions: { type: Array, default: () => [] },\n\t\t/**\n\t\t * The host's own entries in the browser's New menu, forwarded to\n\t\t * CnFilesBrowser's `newActions`: declared like a row action and\n\t\t * dispatched with the folder rather than a row, for things like\n\t\t * \"new from template\" or \"request a file from a party\".\n\t\t *\n\t\t * @type {Array<{id: string, label: string, icon?: string, type?: string, target?: string, props?: object}>}\n\t\t */\n\t\tnewActions: { type: Array, default: () => [] },\n\t\t/**\n\t\t * An app endpoint that answers the object's linked files, rows the\n\t\t * browser shows after the folder's own (files the host joined from\n\t\t * another object's folder). App-relative; `{objectId}` is replaced.\n\t\t * The response is an array (or `{ items: [] }`) of\n\t\t * `{ id, name, mime?, size?, mtime?, href?, downloadHref?, note?, noteHref? }`.\n\t\t * Null fetches nothing.\n\t\t */\n\t\tlinkedItemsUrl: { type: String, default: null },\n\t\t/** Label of the Download action. */\n\t\tdownloadLabel: { type: String, default: () => t('nextcloud-vue', 'Download') },\n\t\t/** Label of the action that opens the Files sidebar on the file. */\n\t\tdetailsLabel: { type: String, default: () => t('nextcloud-vue', 'Details and sharing') },\n\t\t/** Label of the link that opens the file in the Files app. */\n\t\tshowInFilesLabel: { type: String, default: () => t('nextcloud-vue', 'Show in Files') },\n\t\t/** Label of the action that copies the file permalink. */\n\t\tcopyLinkLabel: { type: String, default: () => t('nextcloud-vue', 'Copy link') },\n\t\t/** Label for the load-more button */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t\t/**\n\t\t * Whether to render the \"share uploaded files\" toggle. Off by default;\n\t\t * when false, uploads never set the share flag (no auto-publish).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowShareToggle: { type: Boolean, default: false },\n\t\t/**\n\t\t * Initial state for the share toggle. `true`/`false` wins outright;\n\t\t * `null` (the default) defers to the schema's\n\t\t * `configuration.defaultAutoShare` from OpenRegister.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tdefaultShare: { type: Boolean, default: null },\n\t\t/** Label for the share toggle */\n\t\tshareLabel: { type: String, default: () => t('nextcloud-vue', 'Share uploaded files') },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpreviewFile: null,\n\t\t\tfiles: [],\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\tisDragOver: false,\n\t\t\tuploadError: '',\n\t\t\tpage: 1,\n\t\t\ttotal: 0,\n\t\t\tlimit: 20,\n\t\t\tshare: false,\n\t\t\t/** The object's folder as a user-relative path, or null while unresolved or absent. */\n\t\t\tbrowserRoot: null,\n\t\t\tlinkedItems: [],\n\t\t\t/** File ids whose preview request failed, so the row falls back to the mime icon. */\n\t\t\tpreviewFailed: {},\n\t\t\t/**\n\t\t\t * The file input element, set by the template's function ref.\n\t\t\t *\n\t\t\t * Held here rather than read back through `$refs` so the ref stays\n\t\t\t * DYNAMIC — see the long comment on the input itself for why a\n\t\t\t * static ref on this element is a production crash.\n\t\t\t *\n\t\t\t * @type {HTMLInputElement|null}\n\t\t\t */\n\t\t\tfileInputEl: null,\n\t\t\t/** The mime types Nextcloud Office opens for this person; empty without Office. */\n\t\t\tofficeMimetypes: readOfficeMimetypes(),\n\t\t}\n\t},\n\n\twatch: {\n\t\t// Match every other CnObjectSidebar tab — a single immediate\n\t\t// `objectId` watcher drives all initial loads. Folding the\n\t\t// share-toggle seed in here avoids two concurrent GETs of\n\t\t// `schemas/{schema}` on mount (one per immediate watcher).\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchFiles()\n\t\t\t\t}\n\t\t\t\tthis.applyShareDefault()\n\t\t\t\tthis.resolveBrowserRoot()\n\t\t\t\tthis.fetchLinkedItems()\n\t\t\t},\n\t\t},\n\n\t\tlinkedItemsUrl() {\n\t\t\tthis.fetchLinkedItems()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Function ref for the file input.\n\t\t *\n\t\t * A method (not an inline arrow in the template) so the binding is a\n\t\t * stable reference across renders and Vue does not detach/reattach the\n\t\t * ref on every patch. Being a function ref is what keeps the vnode\n\t\t * dynamic — see the template comment on the input.\n\t\t *\n\t\t * @param {HTMLInputElement|null} el The element, or null on unmount.\n\t\t * @return {void}\n\t\t */\n\t\tfileInputRef(el) {\n\t\t\tthis.fileInputEl = el || null\n\t\t},\n\n\t\t/**\n\t\t * Find the object's folder for the browser, or leave the legacy list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\t/**\n\t\t * The browser changed the folder: refresh the object's file list and\n\t\t * the linked rows, since a join or a move can change either.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonBrowserChanged() {\n\t\t\tthis.fetchFiles()\n\t\t\tthis.fetchLinkedItems()\n\t\t},\n\n\t\t/**\n\t\t * Read the object's linked files from the host's endpoint, if any.\n\t\t *\n\t\t * A failure leaves the last list in place and logs at debug: the\n\t\t * folder's own rows are the surface, the linked rows a supplement.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchLinkedItems() {\n\t\t\tif (!this.linkedItemsUrl || !this.objectId) {\n\t\t\t\tthis.linkedItems = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = generateUrl(this.linkedItemsUrl.replace('{objectId}', encodeURIComponent(this.objectId)))\n\t\t\t\tconst { data } = await axios.get(url)\n\t\t\t\tconst items = Array.isArray(data) ? data : (Array.isArray(data?.items) ? data.items : [])\n\t\t\t\tthis.linkedItems = items.filter((item) => item && typeof item === 'object')\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.debug('[CnFilesTab] linked items not read', error)\n\t\t\t}\n\t\t},\n\n\t\tasync resolveBrowserRoot() {\n\t\t\tthis.browserRoot = null\n\t\t\tif (!this.objectId || !this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet remoteUrl\n\t\t\ttry {\n\t\t\t\tremoteUrl = generateRemoteUrl('dav')\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.browserRoot = await resolveObjectFolder({\n\t\t\t\tapiBase: this.apiBase,\n\t\t\t\tregister: this.register,\n\t\t\t\tschema: this.schema,\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tuid: getCurrentUser()?.uid || '',\n\t\t\t\tremoteUrl,\n\t\t\t})\n\t\t},\n\n\t\tasync fetchFiles(append = false) {\n\t\t\tif (!this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (append) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ limit: this.limit, _page: this.page })\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/files?${params}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst results = data.results || data || []\n\t\t\t\t\tthis.files = append ? [...this.files, ...results] : results\n\t\t\t\t\tthis.total = data.total || this.files.length\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to fetch files', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tthis.page++\n\t\t\tthis.fetchFiles(true)\n\t\t},\n\n\t\t/**\n\t\t * Seed the `share` toggle. The `defaultShare` prop wins when set\n\t\t * (non-null); otherwise we look up the active schema's\n\t\t * `configuration.defaultAutoShare` from OpenRegister and use that.\n\t\t * Network failure or missing key → keep the safe default (false) so\n\t\t * a hiccup never silently flips uploads to \"share\".\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync applyShareDefault() {\n\t\t\tif (this.defaultShare !== null && this.defaultShare !== undefined) {\n\t\t\t\tthis.share = !!this.defaultShare\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.share = false\n\t\t\tif (!this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/schemas/${this.schema}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json().catch(() => null)\n\t\t\t\tif (data?.configuration?.defaultAutoShare === true) {\n\t\t\t\t\tthis.share = true\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// Non-fatal — keep the safe default, but surface the\n\t\t\t\t// failure so a missing toggle default isn't silent.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to fetch schema default for share toggle', err)\n\t\t\t}\n\t\t},\n\n\t\ttriggerFileInput() {\n\t\t\tthis.fileInputEl?.click()\n\t\t},\n\n\t\tonDragOver() {\n\t\t\tthis.isDragOver = true\n\t\t},\n\n\t\tonDragLeave() {\n\t\t\tthis.isDragOver = false\n\t\t},\n\n\t\tonDrop(event) {\n\t\t\tthis.isDragOver = false\n\t\t\tconst droppedFiles = event.dataTransfer?.files\n\t\t\tif (droppedFiles?.length) {\n\t\t\t\tthis.uploadFiles(droppedFiles)\n\t\t\t}\n\t\t},\n\n\t\tasync onFileUpload(event) {\n\t\t\tconst inputFiles = event.target.files\n\t\t\tif (!inputFiles?.length) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait this.uploadFiles(inputFiles)\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.value = ''\n\t\t\t}\n\t\t},\n\n\t\tasync uploadFiles(fileList) {\n\t\t\tif (!fileList?.length || !this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tconst formData = new FormData()\n\t\t\tfor (const file of fileList) {\n\t\t\t\tformData.append('files[]', file)\n\t\t\t}\n\t\t\t// Only forward the share flag when the toggle is actually visible.\n\t\t\t// Hiding the toggle (showShareToggle=false) means \"don't auto-publish\".\n\t\t\tif (this.showShareToggle) {\n\t\t\t\tformData.append('share', String(this.share))\n\t\t\t}\n\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/filesMultipart`),\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { requesttoken: OC?.requestToken || '', 'OCS-APIREQUEST': 'true' },\n\t\t\t\t\t\tbody: formData,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.uploadError = data.error || `Upload failed (${response.status})`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tawait this.fetchFiles()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to upload file', err)\n\t\t\t\tthis.uploadError = 'Upload failed: could not connect to server'\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the Files app's details sidebar is on this page.\n\t\t *\n\t\t * It is when the host page dispatched Nextcloud's `LoadSidebar` event\n\t\t * server-side; then `OCA.Files.Sidebar.open(path)` shows the same\n\t\t * sharing, versions, comments and activity tabs the Files app shows.\n\t\t * Absent, the row offers no details action rather than a dead one. A\n\t\t * method rather than a computed: the global is not reactive, so a\n\t\t * cached answer from before the sidebar script ran would stay wrong.\n\t\t *\n\t\t * @return {boolean} true when the sidebar can be opened.\n\t\t */\n\t\tcanShowDetails() {\n\t\t\treturn typeof window !== 'undefined' && typeof window.OCA?.Files?.Sidebar?.open === 'function'\n\t\t},\n\n\t\t/**\n\t\t * The file's path relative to the user's files root, which is the path\n\t\t * the Viewer and the Files sidebar take. OpenRegister reports the node's\n\t\t * full path (`/{uid}/files/…`), so the first two segments come off.\n\t\t *\n\t\t * @param {string} path The node path as OpenRegister reports it.\n\t\t * @return {string} The user-relative path, `/` at least.\n\t\t */\n\t\tuserRelativePath(path) {\n\t\t\tconst stripped = String(path || '').replace(/^\\/[^/]+\\/files(?=\\/|$)/, '')\n\t\t\treturn stripped === '' ? '/' : stripped\n\t\t},\n\n\t\t/**\n\t\t * The theme's own icon for the file's mime type, the one the Files app\n\t\t * draws, or null on a page without Nextcloud's core script.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string|null} The icon url.\n\t\t */\n\t\tmimeIconFor(file) {\n\t\t\tconst getIconUrl = typeof window !== 'undefined' ? window.OC?.MimeType?.getIconUrl : null\n\t\t\tif (typeof getIconUrl !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst mime = file.type || file.mimetype || file.mimeType || ''\n\t\t\ttry {\n\t\t\t\treturn getIconUrl(mime) || null\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A small preview for an image, through Nextcloud's core preview\n\t\t * endpoint, or null for anything else or after the preview failed once.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string|null} The preview url.\n\t\t */\n\t\tpreviewUrlFor(file) {\n\t\t\tconst mime = file.type || file.mimetype || file.mimeType || ''\n\t\t\tif (!file.id || !/^image\\//i.test(mime) || /svg/i.test(mime) || this.previewFailed[file.id]) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn generateUrl('/core/preview?fileId={fileId}&x=64&y=64&a=1', { fileId: file.id })\n\t\t},\n\n\t\t/**\n\t\t * Remember that a preview did not load, so the row shows the mime icon.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {void}\n\t\t */\n\t\tonPreviewError(file) {\n\t\t\tthis.previewFailed = { ...this.previewFailed, [file.id]: true }\n\t\t},\n\n\t\t/**\n\t\t * The file's modification moment as a Date, or null when unknown.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {Date|null} The moment.\n\t\t */\n\t\tmodifiedAt(file) {\n\t\t\tconst raw = file.modified || file.updated || ''\n\t\t\tif (!raw) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst date = new Date(raw)\n\t\t\treturn Number.isNaN(date.getTime()) ? null : date\n\t\t},\n\n\t\t/**\n\t\t * The file's labels (Nextcloud tags), joined for the meta line.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The labels, or the empty string.\n\t\t */\n\t\tlabelsOf(file) {\n\t\t\treturn Array.isArray(file.labels) ? file.labels.filter(Boolean).join(', ') : ''\n\t\t},\n\n\t\t/**\n\t\t * The download url for a file, through OpenRegister's file endpoint,\n\t\t * which streams the node the object owns.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The url.\n\t\t */\n\t\tdownloadUrlFor(file) {\n\t\t\treturn `${this.apiBase}/files/${encodeURIComponent(String(file.id))}/download`\n\t\t},\n\n\t\t/**\n\t\t * Whether Nextcloud Office opens this file for this person.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {boolean} true when the Office action applies.\n\t\t */\n\t\tofficeOpens(file) {\n\t\t\tconst mime = file.type || file.mimetype || file.mimeType || ''\n\t\t\treturn Boolean(file.id) && mime !== '' && this.officeMimetypes.includes(mime)\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister's Office page for a file of this object, which checks the\n\t\t * object rule and opens the document editable or read-only.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The url.\n\t\t */\n\t\tofficeUrlFor(file) {\n\t\t\tconst appBase = /\\/api\\/?$/.test(this.apiBase) ? this.apiBase.replace(/\\/api\\/?$/, '') : '/apps/openregister'\n\t\t\tconst segments = [this.register, this.schema, this.objectId, String(file.id)].map((part) => encodeURIComponent(String(part)))\n\t\t\treturn prefixUrl(`${appBase}/office/${segments.join('/')}`)\n\t\t},\n\n\t\t/**\n\t\t * The Files app's permalink for a file: opens the Files app at the\n\t\t * file's folder with the file selected.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The url.\n\t\t */\n\t\tshowInFilesUrl(file) {\n\t\t\treturn generateUrl('/f/{fileid}', { fileid: file.id })\n\t\t},\n\n\t\t/**\n\t\t * Open the Files sidebar on this file: details, sharing, versions,\n\t\t * comments and activity, exactly as the Files app offers them.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {void}\n\t\t */\n\t\tshowDetails(file) {\n\t\t\tif (!this.canShowDetails() || !file.path) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\twindow.OCA.Files.Sidebar.open(this.userRelativePath(file.path))\n\t\t},\n\n\t\t/**\n\t\t * Put the file's permalink on the clipboard and say so.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync copyLink(file) {\n\t\t\tconst href = new URL(this.showInFilesUrl(file), window.location.origin).href\n\t\t\ttry {\n\t\t\t\tawait navigator.clipboard.writeText(href)\n\t\t\t\tconst { showSuccess } = await import('@nextcloud/dialogs')\n\t\t\t\tshowSuccess(t('nextcloud-vue', 'Link copied'))\n\t\t\t} catch {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tshowError(t('nextcloud-vue', 'The link could not be copied'))\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Show a file in the in-page preview.\n\t\t *\n\t\t * @param {object} file The file to preview.\n\t\t */\n\t\tshowPreview(file) {\n\t\t\tthis.previewFile = file\n\t\t},\n\n\t\t/**\n\t\t * Open a file through the shared opener (Viewer, preview, browser, Files).\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t */\n\t\topenFile(file) {\n\t\t\tuseFileOpener({\n\t\t\t\tonPreview: this.showPreview,\n\t\t\t\tuserRelativePath: this.userRelativePath,\n\t\t\t\tanyAccessUrl: true,\n\t\t\t\tfilesAppUrl: (f) => {\n\t\t\t\t\tconst dirPath = f.path ? f.path.substring(0, f.path.lastIndexOf('/')) : ''\n\t\t\t\t\tconst cleanPath = dirPath.replace(/^\\/admin\\/files\\//, '/')\n\t\t\t\t\treturn `/index.php/apps/files/files/${f.id}?dir=${encodeURIComponent(cleanPath)}&openfile=true`\n\t\t\t\t},\n\t\t\t}).open(file)\n\t\t},\n\n\t\tasync deleteFile(file) {\n\t\t\tif (!this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/files/${file.id}`),\n\t\t\t\t\t{ method: 'DELETE', headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tthis.files = this.files.filter((f) => f.id !== file.id)\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to delete file', err)\n\t\t\t}\n\t\t},\n\n\t\tformatFileSize(bytes) {\n\t\t\tconst sizes = ['Bytes', 'KB', 'MB', 'GB']\n\t\t\tif (!bytes || bytes === 0) {\n\t\t\t\treturn 'n/a'\n\t\t\t}\n\t\t\tconst i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))\n\t\t\tif (i === 0) {\n\t\t\t\treturn '< 1 KB'\n\t\t\t}\n\t\t\treturn (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-sidebar-tab { padding: 12px; }\n\n.cn-sidebar-tab__upload-error {\n\tpadding: 8px 12px;\n\tmargin-bottom: 8px;\n\tborder-radius: var(--border-radius, 4px);\n\tbackground-color: var(--color-error-light, rgba(229, 57, 53, 0.1));\n\tcolor: var(--color-error, #e53935);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__dropzone {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 20px 12px;\n\tmargin-bottom: 16px;\n\tborder: 2px dashed var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tcursor: pointer;\n\ttransition: border-color 0.15s ease, background-color 0.15s ease;\n}\n\n.cn-sidebar-tab__dropzone:hover {\n\tborder-color: var(--color-primary-element);\n\tbackground-color: var(--color-primary-element-light, rgba(0, 130, 201, 0.08));\n}\n\n.cn-sidebar-tab__dropzone--active {\n\tborder-color: var(--color-primary-element);\n\tbackground-color: var(--color-primary-element-light, rgba(0, 130, 201, 0.12));\n}\n\n.cn-sidebar-tab__share { margin-bottom: 12px; }\n\n.cn-sidebar-tab__dropzone-icon { color: var(--color-text-maxcontrast); }\n\n.cn-sidebar-tab__dropzone--active .cn-sidebar-tab__dropzone-icon,\n.cn-sidebar-tab__dropzone:hover .cn-sidebar-tab__dropzone-icon { color: var(--color-primary-element); }\n\n.cn-sidebar-tab__dropzone-text { font-size: 13px; color: var(--color-text-maxcontrast); }\n\n.cn-sidebar-tab__file-input { display: none; }\n\n.cn-sidebar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__list { display: flex; flex-direction: column; gap: 2px; }\n\n.cn-sidebar-tab__mime,\n.cn-sidebar-tab__thumb {\n\twidth: 32px;\n\theight: 32px;\n\tdisplay: block;\n}\n\n.cn-sidebar-tab__thumb {\n\tobject-fit: cover;\n\tborder-radius: var(--border-radius, 4px);\n}\n\n.cn-sidebar-tab__meta { color: var(--color-text-maxcontrast); }\n\n.cn-sidebar-tab__load-more { margin-top: 8px; }\n</style>\n"],"names":["_openBlock","_createElementBlock","_createBlock","_Fragment","_toDisplayString","_createElementVNode","_normalizeClass","_createVNode","_renderList","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFilesTab.vue3.js","sources":["../../../../src/components/CnObjectSidebar/CnFilesTab.vue?vue&type=template&id=6b917036&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-sidebar-tab\">\n\t\t<!-- The object's folder as a real files browser, on the Files app's own\n\t\t primitives (its actions, its New menu, its breadcrumbs), when the\n\t\t folder resolves for this user. The list below is the fallback for an\n\t\t object with no folder, or a user the folder is not shared with. -->\n\t\t<CnFilesBrowser\n\t\t\tv-if=\"browserRoot !== null\"\n\t\t\t:rootPath=\"browserRoot\"\n\t\t\t:rootLabel=\"effectiveBrowserRootLabel\"\n\t\t\t:source=\"browserSource\"\n\t\t\t:deleteLabel=\"deleteLabel\"\n\t\t\t:uploadButton=\"uploadButton\"\n\t\t\t:dropOverlay=\"dropOverlay\"\n\t\t\t:dropHint=\"dropHint\"\n\t\t\t:rowActions=\"rowActions\"\n\t\t\t:newActions=\"newActions\"\n\t\t\t:linkedItems=\"linkedItems\"\n\t\t\t@changed=\"onBrowserChanged\" />\n\t\t<template v-else>\n\t\t\t<!-- Upload error -->\n\t\t\t<div v-if=\"uploadError\" class=\"cn-sidebar-tab__upload-error\">\n\t\t\t\t{{ uploadError }}\n\t\t\t</div>\n\n\t\t\t<!-- Share toggle — seeds from schema config (defaultAutoShare) or\n\t\t from the `defaultShare` prop. Hidden via `showShareToggle=false`. -->\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\tv-if=\"showShareToggle\"\n\t\t\t\tv-model=\"share\"\n\t\t\t\tclass=\"cn-sidebar-tab__share\"\n\t\t\t\t:disabled=\"loading\"\n\t\t\t\ttype=\"switch\">\n\t\t\t\t{{ shareLabel }}\n\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t<!-- File drop zone -->\n\t\t\t<div\n\t\t\t\tclass=\"cn-sidebar-tab__dropzone\"\n\t\t\t\t:class=\"{ 'cn-sidebar-tab__dropzone--active': isDragOver }\"\n\t\t\t\t@click=\"triggerFileInput\"\n\t\t\t\t@dragover.prevent=\"onDragOver\"\n\t\t\t\t@dragleave.prevent=\"onDragLeave\"\n\t\t\t\t@drop.prevent=\"onDrop\">\n\t\t\t\t<!--\n\t\t\t\t`:ref` (dynamic), not `ref` (static), and this is load-bearing.\n\n\t\t\t\tEvery other binding on this input is static, and Vue caches the\n\t\t\t\t`@change` handler, so with a STATIC ref the compiler hoists the\n\t\t\t\twhole vnode to module scope. A hoisted vnode is created once,\n\t\t\t\toutside any render, so its ref carries no owner instance\n\t\t\t\t(`rawRef.i === null`).\n\n\t\t\t\t`setRef()` guards that — but the guard AND its early `return`\n\t\t\t\tlive inside `if (process.env.NODE_ENV !== 'production')`. In a\n\t\t\t\tdevelopment build you get the warning \"Missing ref owner\n\t\t\t\tcontext. ref cannot be used on hoisted vnodes.\" and nothing\n\t\t\t\tbreaks. In a PRODUCTION build the guard is compiled out and\n\t\t\t\texecution falls through to `owner.refs`, throwing\n\t\t\t\t`TypeError: Cannot read properties of null (reading 'refs')`\n\t\t\t\t— which aborts the render of whatever mounted this tab.\n\n\t\t\t\tMeasured: hrmq's detail pages (the only pages in the fleet that\n\t\t\t\treach CnIntegrationWidget through a `widgets[]` grid) rendered a\n\t\t\t\tCOMPLETELY BLANK content pane in production while looking fine\n\t\t\t\tin dev. A dynamic ref is compiled as a patchable prop, so the\n\t\t\t\tvnode is created inside the render function and owns a proper\n\t\t\t\tinstance.\n\t\t\t-->\n\t\t\t\t<input\n\t\t\t\t\t:ref=\"fileInputRef\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\tmultiple\n\t\t\t\t\tclass=\"cn-sidebar-tab__file-input\"\n\t\t\t\t\t@change=\"onFileUpload\">\n\t\t\t\t<Upload :size=\"24\" class=\"cn-sidebar-tab__dropzone-icon\" />\n\t\t\t\t<span class=\"cn-sidebar-tab__dropzone-text\">{{ dropZoneLabel }}</span>\n\t\t\t</div>\n\n\t\t\t<!-- File list. Each row borrows what Nextcloud already has on this\n\t\t page: the theme's own mime icon (OC.MimeType), the core preview\n\t\t endpoint for images, the Viewer for opening, and the Files sidebar\n\t\t for details, sharing and versions. Nothing of the Files app is\n\t\t rebuilt here; the row hands over to it. -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t<NcEmptyContent\n\t\t\t\tv-else-if=\"files.length === 0\"\n\t\t\t\tclass=\"cn-sidebar-tab__empty\"\n\t\t\t\t:name=\"noFilesLabel\"\n\t\t\t\t:description=\"dropZoneLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Paperclip :size=\"44\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t\t<div v-else class=\"cn-sidebar-tab__list\">\n\t\t\t\t<NcListItem\n\t\t\t\t\tv-for=\"file in files\"\n\t\t\t\t\t:key=\"file.id\"\n\t\t\t\t\t:name=\"file.name || file.title\"\n\t\t\t\t\t:bold=\"false\"\n\t\t\t\t\t:forceDisplayActions=\"true\"\n\t\t\t\t\t@click=\"openFile(file)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"previewUrlFor(file)\"\n\t\t\t\t\t\t\tclass=\"cn-sidebar-tab__thumb\"\n\t\t\t\t\t\t\t:src=\"previewUrlFor(file)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onPreviewError(file)\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-else-if=\"mimeIconFor(file)\"\n\t\t\t\t\t\t\tclass=\"cn-sidebar-tab__mime\"\n\t\t\t\t\t\t\t:src=\"mimeIconFor(file)\"\n\t\t\t\t\t\t\talt=\"\">\n\t\t\t\t\t\t<FileOutline v-else :size=\"32\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t<span class=\"cn-sidebar-tab__meta\">\n\t\t\t\t\t\t\t<span>{{ formatFileSize(file.size) }}</span>\n\t\t\t\t\t\t\t<template v-if=\"modifiedAt(file)\">\n\t\t\t\t\t\t\t\t<span aria-hidden=\"true\"> · </span>\n\t\t\t\t\t\t\t\t<NcDateTime :timestamp=\"modifiedAt(file)\" :ignoreSeconds=\"true\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t<template v-if=\"labelsOf(file)\">\n\t\t\t\t\t\t\t\t<span aria-hidden=\"true\"> · </span>\n\t\t\t\t\t\t\t\t<span>{{ labelsOf(file) }}</span>\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"openFile(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ openLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<!-- OpenRegister's Office page applies the object rule:\n\t\t\t\t\t\t update edits, read views, no read is a 404. -->\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-if=\"officeOpens(file)\"\n\t\t\t\t\t\t\t:href=\"officeUrlFor(file)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<FileDocumentEditOutline :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ openInOfficeLabel }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-if=\"file.id\"\n\t\t\t\t\t\t\t:href=\"downloadUrlFor(file)\"\n\t\t\t\t\t\t\t:download=\"file.name || file.title || ''\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Download :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ downloadLabel }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"canShowDetails() && file.path\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"showDetails(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<InformationOutline :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ detailsLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\tv-if=\"file.id\"\n\t\t\t\t\t\t\t:href=\"showInFilesUrl(file)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<FolderOutline :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ showInFilesLabel }}\n\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t<NcActionButton v-if=\"file.id\" :closeAfterClick=\"true\" @click=\"copyLink(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariant :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ copyLinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionSeparator />\n\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"deleteFile(file)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</NcListItem>\n\t\t\t</div>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"files.length < total\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:wide=\"true\"\n\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\tclass=\"cn-sidebar-tab__load-more\"\n\t\t\t\t@click=\"loadMore\">\n\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ loadMoreLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t\t<CnFilePreview v-if=\"previewFile\" :file=\"previewFile\" @close=\"previewFile = null\" />\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { getCapabilities } from '@nextcloud/capabilities'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateRemoteUrl, generateUrl } from '@nextcloud/router'\nimport {\n\tNcActionButton,\n\tNcActionLink,\n\tNcActionSeparator,\n\tNcButton,\n\tNcCheckboxRadioSwitch,\n\tNcDateTime,\n\tNcEmptyContent,\n\tNcListItem,\n\tNcLoadingIcon,\n} from '@nextcloud/vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport Download from 'vue-material-design-icons/Download.vue'\nimport FileDocumentEditOutline from 'vue-material-design-icons/FileDocumentEditOutline.vue'\nimport FileOutline from 'vue-material-design-icons/FileOutline.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Paperclip from 'vue-material-design-icons/Paperclip.vue'\nimport Upload from 'vue-material-design-icons/Upload.vue'\nimport CnFilePreview from '../CnFilePreview/CnFilePreview.vue'\nimport CnFilesBrowser from '../CnFilesBrowser/CnFilesBrowser.vue'\nimport { useFileOpener } from '../../composables/useFileOpener.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { objectDisplayName, resolveObjectFolderInfo } from '../CnFilesBrowser/filesBrowser.js'\nimport { createOpenRegisterSource } from '../CnFilesBrowser/openRegisterSource.js'\n\n/**\n * The mime types Nextcloud Office opens by default for the current person.\n *\n * richdocuments publishes its capabilities only to a person it lets use\n * Office, so an empty list means no Office here, or none for them.\n *\n * @return {Array<string>} The mime types, or an empty list.\n */\nfunction readOfficeMimetypes() {\n\ttry {\n\t\tconst mimetypes = getCapabilities()?.richdocuments?.mimetypes\n\t\treturn Array.isArray(mimetypes) ? mimetypes.filter((mime) => typeof mime === 'string') : []\n\t} catch {\n\t\treturn []\n\t}\n}\n\nexport default {\n\tname: 'CnFilesTab',\n\n\tcomponents: {\n\t\tCnFilePreview,\n\t\tCnFilesBrowser,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcActionSeparator,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcDateTime,\n\t\tNcEmptyContent,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tDelete,\n\t\tDownload,\n\t\tFileDocumentEditOutline,\n\t\tFileOutline,\n\t\tFolderOutline,\n\t\tInformationOutline,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPaperclip,\n\t\tUpload,\n\t},\n\n\tprops: {\n\t\t/** ID of the object this tab belongs to */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register slug */\n\t\tregister: { type: String, default: '' },\n\t\t/** JSON Schema definition for the object */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base URL for the OpenRegister API */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Text shown inside the file drop zone */\n\t\tdropZoneLabel: { type: String, default: () => t('nextcloud-vue', 'Drop files here or click to browse') },\n\t\t/** Text shown when no files are attached */\n\t\tnoFilesLabel: { type: String, default: () => t('nextcloud-vue', 'No files attached') },\n\t\t/** Label for the open/view file action */\n\t\topenLabel: { type: String, default: () => t('nextcloud-vue', 'Open') },\n\t\t/**\n\t\t * Label of the action that opens a document in Nextcloud Office through\n\t\t * OpenRegister's Office page. Shown only when Office is there for this\n\t\t * person and opens the file's type; the page opens it for editing or\n\t\t * read-only by the object's own rule.\n\t\t */\n\t\topenInOfficeLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Office') },\n\t\t/** Label for the delete action */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },\n\t\t/** What the files browser's root crumb reads; null shows the object's name, else the folder's own name. */\n\t\tbrowserRootLabel: { type: String, default: null },\n\t\t/**\n\t\t * Where the files browser reads the object's folder from.\n\t\t * `openregister` (the default) goes through OpenRegister's files API under\n\t\t * the object's access rule, so anyone who may read the object sees its\n\t\t * files, without a share. `webdav` is the earlier behaviour: the folder is\n\t\t * looked up in the person's own Files over WebDAV and the browser shows\n\t\t * only when it is shared with them.\n\t\t */\n\t\tsource: {\n\t\t\ttype: String,\n\t\t\tdefault: 'openregister',\n\t\t\tvalidator: (value) => ['openregister', 'webdav'].includes(value),\n\t\t},\n\n\t\t/** Forwarded to CnFilesBrowser: a primary \"Add files\" button beside the New menu. */\n\t\tuploadButton: { type: Boolean, default: false },\n\t\t/** Forwarded to CnFilesBrowser: the drop state drawn over the list (\"Drop to add\") and announced. */\n\t\tdropOverlay: { type: Boolean, default: false },\n\t\t/** Forwarded to CnFilesBrowser: \"Or drag files onto this list.\" under the list. */\n\t\tdropHint: { type: Boolean, default: false },\n\t\t/**\n\t\t * The host's own actions on each file row of the browser, forwarded\n\t\t * to CnFilesBrowser's `rowActions`: declared like any manifest action\n\t\t * and dispatched with the file's id, name and path merged in.\n\t\t *\n\t\t * @type {Array<{id: string, label: string, icon?: string, type?: string, target?: string, props?: object}>}\n\t\t */\n\t\trowActions: { type: Array, default: () => [] },\n\t\t/**\n\t\t * The host's own entries in the browser's New menu, forwarded to\n\t\t * CnFilesBrowser's `newActions`: declared like a row action and\n\t\t * dispatched with the folder rather than a row, for things like\n\t\t * \"new from template\" or \"request a file from a party\".\n\t\t *\n\t\t * @type {Array<{id: string, label: string, icon?: string, type?: string, target?: string, props?: object}>}\n\t\t */\n\t\tnewActions: { type: Array, default: () => [] },\n\t\t/**\n\t\t * An app endpoint that answers the object's linked files, rows the\n\t\t * browser shows after the folder's own (files the host joined from\n\t\t * another object's folder). App-relative; `{objectId}` is replaced.\n\t\t * The response is an array (or `{ items: [] }`) of\n\t\t * `{ id, name, mime?, size?, mtime?, href?, downloadHref?, note?, noteHref? }`.\n\t\t * Null fetches nothing.\n\t\t */\n\t\tlinkedItemsUrl: { type: String, default: null },\n\t\t/** Label of the Download action. */\n\t\tdownloadLabel: { type: String, default: () => t('nextcloud-vue', 'Download') },\n\t\t/** Label of the action that opens the Files sidebar on the file. */\n\t\tdetailsLabel: { type: String, default: () => t('nextcloud-vue', 'Details and sharing') },\n\t\t/** Label of the link that opens the file in the Files app. */\n\t\tshowInFilesLabel: { type: String, default: () => t('nextcloud-vue', 'Show in Files') },\n\t\t/** Label of the action that copies the file permalink. */\n\t\tcopyLinkLabel: { type: String, default: () => t('nextcloud-vue', 'Copy link') },\n\t\t/** Label for the load-more button */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t\t/**\n\t\t * Whether to render the \"share uploaded files\" toggle. Off by default;\n\t\t * when false, uploads never set the share flag (no auto-publish).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowShareToggle: { type: Boolean, default: false },\n\t\t/**\n\t\t * Initial state for the share toggle. `true`/`false` wins outright;\n\t\t * `null` (the default) defers to the schema's\n\t\t * `configuration.defaultAutoShare` from OpenRegister.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tdefaultShare: { type: Boolean, default: null },\n\t\t/** Label for the share toggle */\n\t\tshareLabel: { type: String, default: () => t('nextcloud-vue', 'Share uploaded files') },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpreviewFile: null,\n\t\t\tfiles: [],\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\tisDragOver: false,\n\t\t\tuploadError: '',\n\t\t\tpage: 1,\n\t\t\ttotal: 0,\n\t\t\tlimit: 20,\n\t\t\tshare: false,\n\t\t\t/** The object's folder as a user-relative path, or null while unresolved or absent. */\n\t\t\tbrowserRoot: null,\n\t\t\t/** The browser's data source for `source: 'openregister'`, or null for WebDAV. */\n\t\t\tbrowserSource: null,\n\t\t\tbrowserObjectName: null,\n\t\t\tlinkedItems: [],\n\t\t\t/** File ids whose preview request failed, so the row falls back to the mime icon. */\n\t\t\tpreviewFailed: {},\n\t\t\t/**\n\t\t\t * The file input element, set by the template's function ref.\n\t\t\t *\n\t\t\t * Held here rather than read back through `$refs` so the ref stays\n\t\t\t * DYNAMIC — see the long comment on the input itself for why a\n\t\t\t * static ref on this element is a production crash.\n\t\t\t *\n\t\t\t * @type {HTMLInputElement|null}\n\t\t\t */\n\t\t\tfileInputEl: null,\n\t\t\t/** The mime types Nextcloud Office opens for this person; empty without Office. */\n\t\t\tofficeMimetypes: readOfficeMimetypes(),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The root crumb: the host's label, else the object's name (its folder\n\t\t * on disk is called after its uuid), else the folder's own name.\n\t\t *\n\t\t * @return {string|null}\n\t\t * @spec openspec/changes/dutch-library-strings-and-files-crumb/specs/dutch-library-strings-and-files-crumb/spec.md#requirement-the-files-browser-names-the-object-in-its-root-crumb\n\t\t */\n\t\teffectiveBrowserRootLabel() {\n\t\t\treturn this.browserRootLabel || this.browserObjectName || null\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Match every other CnObjectSidebar tab — a single immediate\n\t\t// `objectId` watcher drives all initial loads. Folding the\n\t\t// share-toggle seed in here avoids two concurrent GETs of\n\t\t// `schemas/{schema}` on mount (one per immediate watcher).\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchFiles()\n\t\t\t\t}\n\t\t\t\tthis.applyShareDefault()\n\t\t\t\tthis.resolveBrowserRoot()\n\t\t\t\tthis.fetchLinkedItems()\n\t\t\t},\n\t\t},\n\n\t\tlinkedItemsUrl() {\n\t\t\tthis.fetchLinkedItems()\n\t\t},\n\n\t\tsource() {\n\t\t\tthis.resolveBrowserRoot()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Function ref for the file input.\n\t\t *\n\t\t * A method (not an inline arrow in the template) so the binding is a\n\t\t * stable reference across renders and Vue does not detach/reattach the\n\t\t * ref on every patch. Being a function ref is what keeps the vnode\n\t\t * dynamic — see the template comment on the input.\n\t\t *\n\t\t * @param {HTMLInputElement|null} el The element, or null on unmount.\n\t\t * @return {void}\n\t\t */\n\t\tfileInputRef(el) {\n\t\t\tthis.fileInputEl = el || null\n\t\t},\n\n\t\t/**\n\t\t * Find the object's folder for the browser, or leave the legacy list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\t/**\n\t\t * The browser changed the folder: refresh the object's file list and\n\t\t * the linked rows, since a join or a move can change either.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonBrowserChanged() {\n\t\t\tthis.fetchFiles()\n\t\t\tthis.fetchLinkedItems()\n\t\t},\n\n\t\t/**\n\t\t * Read the object's linked files from the host's endpoint, if any.\n\t\t *\n\t\t * A failure leaves the last list in place and logs at debug: the\n\t\t * folder's own rows are the surface, the linked rows a supplement.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchLinkedItems() {\n\t\t\tif (!this.linkedItemsUrl || !this.objectId) {\n\t\t\t\tthis.linkedItems = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = generateUrl(this.linkedItemsUrl.replace('{objectId}', encodeURIComponent(this.objectId)))\n\t\t\t\tconst { data } = await axios.get(url)\n\t\t\t\tconst items = Array.isArray(data) ? data : (Array.isArray(data?.items) ? data.items : [])\n\t\t\t\tthis.linkedItems = items.filter((item) => item && typeof item === 'object')\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.debug('[CnFilesTab] linked items not read', error)\n\t\t\t}\n\t\t},\n\n\t\tasync resolveBrowserRoot() {\n\t\t\tthis.browserRoot = null\n\t\t\tthis.browserSource = null\n\t\t\tthis.browserObjectName = null\n\t\t\tif (!this.objectId || !this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.source === 'openregister') {\n\t\t\t\tawait this.resolveOpenRegisterSource()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet remoteUrl\n\t\t\ttry {\n\t\t\t\tremoteUrl = generateRemoteUrl('dav')\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst info = await resolveObjectFolderInfo({\n\t\t\t\tapiBase: this.apiBase,\n\t\t\t\tregister: this.register,\n\t\t\t\tschema: this.schema,\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tuid: getCurrentUser()?.uid || '',\n\t\t\t\tremoteUrl,\n\t\t\t})\n\t\t\tthis.browserRoot = info === null ? null : info.path\n\t\t\tthis.browserObjectName = info === null ? null : info.name\n\t\t},\n\n\t\t/**\n\t\t * Use OpenRegister's files API for the browser, when this person may read\n\t\t * the object's folder through it. A listing that fails (no read on the\n\t\t * object, or an OpenRegister without the folder endpoints) leaves the\n\t\t * legacy list, as an unresolved WebDAV folder did.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/files-browser-openregister-source/specs/files-browser/spec.md\n\t\t */\n\t\tasync resolveOpenRegisterSource() {\n\t\t\tconst objectId = this.objectId\n\t\t\tconst source = createOpenRegisterSource({\n\t\t\t\tapiBase: this.apiBase,\n\t\t\t\tregister: this.register,\n\t\t\t\tschema: this.schema,\n\t\t\t\tobjectId,\n\t\t\t})\n\t\t\ttry {\n\t\t\t\tawait source.list('/')\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (objectId !== this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.browserSource = source\n\t\t\tthis.browserRoot = '/'\n\t\t\tthis.fetchObjectName()\n\t\t},\n\n\t\t/**\n\t\t * Read the object's name for the root crumb, by the same rule the WebDAV\n\t\t * path uses (`objectDisplayName`: never the uuid). The OpenRegister path\n\t\t * has no folder lookup to piggyback on, so this is its one object read.\n\t\t * A failure keeps the crumb on its default.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchObjectName() {\n\t\t\tconst objectId = this.objectId\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(`${this.apiBase}/objects/{register}/{schema}/{objectId}`, {\n\t\t\t\t\tregister: this.register,\n\t\t\t\t\tschema: this.schema,\n\t\t\t\t\tobjectId,\n\t\t\t\t}))\n\t\t\t\tif (objectId === this.objectId) {\n\t\t\t\t\tthis.browserObjectName = objectDisplayName(data, objectId)\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// The crumb keeps its default.\n\t\t\t}\n\t\t},\n\n\t\tasync fetchFiles(append = false) {\n\t\t\tif (!this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (append) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ limit: this.limit, _page: this.page })\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/files?${params}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst results = data.results || data || []\n\t\t\t\t\tthis.files = append ? [...this.files, ...results] : results\n\t\t\t\t\tthis.total = data.total || this.files.length\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to fetch files', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tthis.page++\n\t\t\tthis.fetchFiles(true)\n\t\t},\n\n\t\t/**\n\t\t * Seed the `share` toggle. The `defaultShare` prop wins when set\n\t\t * (non-null); otherwise we look up the active schema's\n\t\t * `configuration.defaultAutoShare` from OpenRegister and use that.\n\t\t * Network failure or missing key → keep the safe default (false) so\n\t\t * a hiccup never silently flips uploads to \"share\".\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync applyShareDefault() {\n\t\t\tif (this.defaultShare !== null && this.defaultShare !== undefined) {\n\t\t\t\tthis.share = !!this.defaultShare\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.share = false\n\t\t\tif (!this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/schemas/${this.schema}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json().catch(() => null)\n\t\t\t\tif (data?.configuration?.defaultAutoShare === true) {\n\t\t\t\t\tthis.share = true\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// Non-fatal — keep the safe default, but surface the\n\t\t\t\t// failure so a missing toggle default isn't silent.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to fetch schema default for share toggle', err)\n\t\t\t}\n\t\t},\n\n\t\ttriggerFileInput() {\n\t\t\tthis.fileInputEl?.click()\n\t\t},\n\n\t\tonDragOver() {\n\t\t\tthis.isDragOver = true\n\t\t},\n\n\t\tonDragLeave() {\n\t\t\tthis.isDragOver = false\n\t\t},\n\n\t\tonDrop(event) {\n\t\t\tthis.isDragOver = false\n\t\t\tconst droppedFiles = event.dataTransfer?.files\n\t\t\tif (droppedFiles?.length) {\n\t\t\t\tthis.uploadFiles(droppedFiles)\n\t\t\t}\n\t\t},\n\n\t\tasync onFileUpload(event) {\n\t\t\tconst inputFiles = event.target.files\n\t\t\tif (!inputFiles?.length) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait this.uploadFiles(inputFiles)\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.value = ''\n\t\t\t}\n\t\t},\n\n\t\tasync uploadFiles(fileList) {\n\t\t\tif (!fileList?.length || !this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tconst formData = new FormData()\n\t\t\tfor (const file of fileList) {\n\t\t\t\tformData.append('files[]', file)\n\t\t\t}\n\t\t\t// Only forward the share flag when the toggle is actually visible.\n\t\t\t// Hiding the toggle (showShareToggle=false) means \"don't auto-publish\".\n\t\t\tif (this.showShareToggle) {\n\t\t\t\tformData.append('share', String(this.share))\n\t\t\t}\n\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/filesMultipart`),\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { requesttoken: OC?.requestToken || '', 'OCS-APIREQUEST': 'true' },\n\t\t\t\t\t\tbody: formData,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.uploadError = data.error || `Upload failed (${response.status})`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tawait this.fetchFiles()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to upload file', err)\n\t\t\t\tthis.uploadError = 'Upload failed: could not connect to server'\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the Files app's details sidebar is on this page.\n\t\t *\n\t\t * It is when the host page dispatched Nextcloud's `LoadSidebar` event\n\t\t * server-side; then `OCA.Files.Sidebar.open(path)` shows the same\n\t\t * sharing, versions, comments and activity tabs the Files app shows.\n\t\t * Absent, the row offers no details action rather than a dead one. A\n\t\t * method rather than a computed: the global is not reactive, so a\n\t\t * cached answer from before the sidebar script ran would stay wrong.\n\t\t *\n\t\t * @return {boolean} true when the sidebar can be opened.\n\t\t */\n\t\tcanShowDetails() {\n\t\t\treturn typeof window !== 'undefined' && typeof window.OCA?.Files?.Sidebar?.open === 'function'\n\t\t},\n\n\t\t/**\n\t\t * The file's path relative to the user's files root, which is the path\n\t\t * the Viewer and the Files sidebar take. OpenRegister reports the node's\n\t\t * full path (`/{uid}/files/…`), so the first two segments come off.\n\t\t *\n\t\t * @param {string} path The node path as OpenRegister reports it.\n\t\t * @return {string} The user-relative path, `/` at least.\n\t\t */\n\t\tuserRelativePath(path) {\n\t\t\tconst stripped = String(path || '').replace(/^\\/[^/]+\\/files(?=\\/|$)/, '')\n\t\t\treturn stripped === '' ? '/' : stripped\n\t\t},\n\n\t\t/**\n\t\t * The theme's own icon for the file's mime type, the one the Files app\n\t\t * draws, or null on a page without Nextcloud's core script.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string|null} The icon url.\n\t\t */\n\t\tmimeIconFor(file) {\n\t\t\tconst getIconUrl = typeof window !== 'undefined' ? window.OC?.MimeType?.getIconUrl : null\n\t\t\tif (typeof getIconUrl !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst mime = file.type || file.mimetype || file.mimeType || ''\n\t\t\ttry {\n\t\t\t\treturn getIconUrl(mime) || null\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A small preview for an image, through Nextcloud's core preview\n\t\t * endpoint, or null for anything else or after the preview failed once.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string|null} The preview url.\n\t\t */\n\t\tpreviewUrlFor(file) {\n\t\t\tconst mime = file.type || file.mimetype || file.mimeType || ''\n\t\t\tif (!file.id || !/^image\\//i.test(mime) || /svg/i.test(mime) || this.previewFailed[file.id]) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn generateUrl('/core/preview?fileId={fileId}&x=64&y=64&a=1', { fileId: file.id })\n\t\t},\n\n\t\t/**\n\t\t * Remember that a preview did not load, so the row shows the mime icon.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {void}\n\t\t */\n\t\tonPreviewError(file) {\n\t\t\tthis.previewFailed = { ...this.previewFailed, [file.id]: true }\n\t\t},\n\n\t\t/**\n\t\t * The file's modification moment as a Date, or null when unknown.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {Date|null} The moment.\n\t\t */\n\t\tmodifiedAt(file) {\n\t\t\tconst raw = file.modified || file.updated || ''\n\t\t\tif (!raw) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst date = new Date(raw)\n\t\t\treturn Number.isNaN(date.getTime()) ? null : date\n\t\t},\n\n\t\t/**\n\t\t * The file's labels (Nextcloud tags), joined for the meta line.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The labels, or the empty string.\n\t\t */\n\t\tlabelsOf(file) {\n\t\t\treturn Array.isArray(file.labels) ? file.labels.filter(Boolean).join(', ') : ''\n\t\t},\n\n\t\t/**\n\t\t * The download url for a file, through OpenRegister's file endpoint,\n\t\t * which streams the node the object owns.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The url.\n\t\t */\n\t\tdownloadUrlFor(file) {\n\t\t\treturn `${this.apiBase}/files/${encodeURIComponent(String(file.id))}/download`\n\t\t},\n\n\t\t/**\n\t\t * Whether Nextcloud Office opens this file for this person.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {boolean} true when the Office action applies.\n\t\t */\n\t\tofficeOpens(file) {\n\t\t\tconst mime = file.type || file.mimetype || file.mimeType || ''\n\t\t\treturn Boolean(file.id) && mime !== '' && this.officeMimetypes.includes(mime)\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister's Office page for a file of this object, which checks the\n\t\t * object rule and opens the document editable or read-only.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The url.\n\t\t */\n\t\tofficeUrlFor(file) {\n\t\t\tconst appBase = /\\/api\\/?$/.test(this.apiBase) ? this.apiBase.replace(/\\/api\\/?$/, '') : '/apps/openregister'\n\t\t\tconst segments = [this.register, this.schema, this.objectId, String(file.id)].map((part) => encodeURIComponent(String(part)))\n\t\t\treturn prefixUrl(`${appBase}/office/${segments.join('/')}`)\n\t\t},\n\n\t\t/**\n\t\t * The Files app's permalink for a file: opens the Files app at the\n\t\t * file's folder with the file selected.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {string} The url.\n\t\t */\n\t\tshowInFilesUrl(file) {\n\t\t\treturn generateUrl('/f/{fileid}', { fileid: file.id })\n\t\t},\n\n\t\t/**\n\t\t * Open the Files sidebar on this file: details, sharing, versions,\n\t\t * comments and activity, exactly as the Files app offers them.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {void}\n\t\t */\n\t\tshowDetails(file) {\n\t\t\tif (!this.canShowDetails() || !file.path) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\twindow.OCA.Files.Sidebar.open(this.userRelativePath(file.path))\n\t\t},\n\n\t\t/**\n\t\t * Put the file's permalink on the clipboard and say so.\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync copyLink(file) {\n\t\t\tconst href = new URL(this.showInFilesUrl(file), window.location.origin).href\n\t\t\ttry {\n\t\t\t\tawait navigator.clipboard.writeText(href)\n\t\t\t\tconst { showSuccess } = await import('@nextcloud/dialogs')\n\t\t\t\tshowSuccess(t('nextcloud-vue', 'Link copied'))\n\t\t\t} catch {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tshowError(t('nextcloud-vue', 'The link could not be copied'))\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Show a file in the in-page preview.\n\t\t *\n\t\t * @param {object} file The file to preview.\n\t\t */\n\t\tshowPreview(file) {\n\t\t\tthis.previewFile = file\n\t\t},\n\n\t\t/**\n\t\t * Open a file through the shared opener (Viewer, preview, browser, Files).\n\t\t *\n\t\t * @param {object} file The file row.\n\t\t */\n\t\topenFile(file) {\n\t\t\tuseFileOpener({\n\t\t\t\tonPreview: this.showPreview,\n\t\t\t\tuserRelativePath: this.userRelativePath,\n\t\t\t\tanyAccessUrl: true,\n\t\t\t\tfilesAppUrl: (f) => {\n\t\t\t\t\tconst dirPath = f.path ? f.path.substring(0, f.path.lastIndexOf('/')) : ''\n\t\t\t\t\tconst cleanPath = dirPath.replace(/^\\/admin\\/files\\//, '/')\n\t\t\t\t\treturn `/index.php/apps/files/files/${f.id}?dir=${encodeURIComponent(cleanPath)}&openfile=true`\n\t\t\t\t},\n\t\t\t}).open(file)\n\t\t},\n\n\t\tasync deleteFile(file) {\n\t\t\tif (!this.register || !this.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/files/${file.id}`),\n\t\t\t\t\t{ method: 'DELETE', headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tthis.files = this.files.filter((f) => f.id !== file.id)\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnFilesTab: Failed to delete file', err)\n\t\t\t}\n\t\t},\n\n\t\tformatFileSize(bytes) {\n\t\t\tconst sizes = ['Bytes', 'KB', 'MB', 'GB']\n\t\t\tif (!bytes || bytes === 0) {\n\t\t\t\treturn 'n/a'\n\t\t\t}\n\t\t\tconst i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))\n\t\t\tif (i === 0) {\n\t\t\t\treturn '< 1 KB'\n\t\t\t}\n\t\t\treturn (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-sidebar-tab { padding: 12px; }\n\n.cn-sidebar-tab__upload-error {\n\tpadding: 8px 12px;\n\tmargin-bottom: 8px;\n\tborder-radius: var(--border-radius, 4px);\n\tbackground-color: var(--color-error-light, rgba(229, 57, 53, 0.1));\n\tcolor: var(--color-error, #e53935);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__dropzone {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 20px 12px;\n\tmargin-bottom: 16px;\n\tborder: 2px dashed var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tcursor: pointer;\n\ttransition: border-color 0.15s ease, background-color 0.15s ease;\n}\n\n.cn-sidebar-tab__dropzone:hover {\n\tborder-color: var(--color-primary-element);\n\tbackground-color: var(--color-primary-element-light, rgba(0, 130, 201, 0.08));\n}\n\n.cn-sidebar-tab__dropzone--active {\n\tborder-color: var(--color-primary-element);\n\tbackground-color: var(--color-primary-element-light, rgba(0, 130, 201, 0.12));\n}\n\n.cn-sidebar-tab__share { margin-bottom: 12px; }\n\n.cn-sidebar-tab__dropzone-icon { color: var(--color-text-maxcontrast); }\n\n.cn-sidebar-tab__dropzone--active .cn-sidebar-tab__dropzone-icon,\n.cn-sidebar-tab__dropzone:hover .cn-sidebar-tab__dropzone-icon { color: var(--color-primary-element); }\n\n.cn-sidebar-tab__dropzone-text { font-size: 13px; color: var(--color-text-maxcontrast); }\n\n.cn-sidebar-tab__file-input { display: none; }\n\n.cn-sidebar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__list { display: flex; flex-direction: column; gap: 2px; }\n\n.cn-sidebar-tab__mime,\n.cn-sidebar-tab__thumb {\n\twidth: 32px;\n\theight: 32px;\n\tdisplay: block;\n}\n\n.cn-sidebar-tab__thumb {\n\tobject-fit: cover;\n\tborder-radius: var(--border-radius, 4px);\n}\n\n.cn-sidebar-tab__meta { color: var(--color-text-maxcontrast); }\n\n.cn-sidebar-tab__load-more { margin-top: 8px; }\n</style>\n"],"names":["_openBlock","_createElementBlock","_createBlock","_Fragment","_toDisplayString","_createElementVNode","_normalizeClass","_createVNode","_renderList","_createTextVNode"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { translate } from '@nextcloud/l10n';
|
|
1
|
+
import { translate, translatePlural } from '@nextcloud/l10n';
|
|
2
2
|
import { NcPopover, NcSelect } from '@nextcloud/vue';
|
|
3
3
|
import DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue';
|
|
4
4
|
import __sfc_main from '../CnIcon/CnIcon.vue.js';
|
|
@@ -237,7 +237,8 @@ var script = {
|
|
|
237
237
|
if (this.overflowLabel) {
|
|
238
238
|
return translate('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
const count = this.overflowEntries.length;
|
|
241
|
+
return translatePlural('nextcloud-vue', '{count} more filter', '{count} more filters', count, { count })
|
|
241
242
|
},
|
|
242
243
|
|
|
243
244
|
/** @return {string} Heading above the hidden chips in the panel. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnQuickFilterBar.vue2.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn this.tl(active[0].tab.label)\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} more filters', { count: this.overflowEntries.length })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnIcon","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnQuickFilterBar.vue2.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn this.tl(active[0].tab.label)\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\tconst count = this.overflowEntries.length\n\t\t\treturn n('nextcloud-vue', '{count} more filter', '{count} more filters', count, { count })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnIcon","t","n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnQuickFilterBar.vue3.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue?vue&type=template&id=8e121822&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn this.tl(active[0].tab.label)\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} more filters', { count: this.overflowEntries.length })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_normalizeClass","_createVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnQuickFilterBar.vue3.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue?vue&type=template&id=8e121822&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn this.tl(active[0].tab.label)\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\tconst count = this.overflowEntries.length\n\t\t\treturn n('nextcloud-vue', '{count} more filter', '{count} more filters', count, { count })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_normalizeClass","_createVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
|