@conduction/nextcloud-vue 2.100.0 → 2.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +22 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +70 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +105 -74
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
- package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
- package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +320 -74
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +323 -213
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +30 -3
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +35 -2
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
- 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/indexSources.js +176 -20
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useEndpointSource.js +28 -6
- package/dist/esm/composables/useEndpointSource.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 +41 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +39 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +939 -376
- package/dist/esm/utils/builtInFormatters.js +24 -4
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/languageMapText.js +50 -0
- package/dist/esm/utils/languageMapText.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +3 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/mapList.js +71 -0
- package/dist/esm/utils/mapList.js.map +1 -0
- package/dist/esm/utils/rowClassRules.js +43 -0
- package/dist/esm/utils/rowClassRules.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +19 -2
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +9681 -7136
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +939 -376
- package/l10n/en.json +41 -1
- package/l10n/nl.json +39 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
- package/src/components/CnChartWidget/CnChartWidget.md +3 -2
- package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
- package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
- package/src/components/CnDashboardPage/CnDashboardPage.vue +110 -21
- package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
- package/src/components/CnExportsWidget/index.js +7 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
- package/src/components/CnFilesBrowser/filesReview.js +57 -0
- package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
- package/src/components/CnGroupedCardsWidget/index.js +28 -0
- package/src/components/CnIndexPage/CnIndexPage.md +5 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +313 -9
- package/src/components/CnIndexPage/useNamedSource.js +30 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
- package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
- package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
- package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
- package/src/components/CnSavedViewAlertWidget/index.js +7 -0
- package/src/components/CnSetupWizard/CnSetupWizard.vue +35 -2
- package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
- package/src/components/index.js +3 -0
- package/src/composables/indexSources.js +175 -19
- package/src/composables/useEndpointSource.js +28 -6
- package/src/css/index-page.css +117 -0
- package/src/css/look-board-index.css +10 -0
- package/src/css/look-board.css +44 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +85 -3
- package/src/utils/builtInFormatters.js +24 -4
- package/src/utils/languageMapText.js +46 -0
- package/src/utils/libraryWidgetKeys.js +3 -0
- package/src/utils/mapList.js +67 -0
- package/src/utils/rowClassRules.js +39 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +19 -2
|
@@ -11,6 +11,8 @@ import HandBackRight from 'vue-material-design-icons/HandBackRight.vue'
|
|
|
11
11
|
import LightningBolt from 'vue-material-design-icons/LightningBolt.vue'
|
|
12
12
|
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
|
13
13
|
import { formatRelativeDate } from '../utils/boardDate.js'
|
|
14
|
+
import { fetchReferenceObjects } from '../utils/boardReferenceColumns.js'
|
|
15
|
+
import { fillTemplate } from '../utils/fillTemplate.js'
|
|
14
16
|
import { FLOW_RUN_ERROR_STATUSES, flowRunStatuses } from '../utils/flowRunStatus.js'
|
|
15
17
|
import { fetchGroupMembers, fetchPresence, fetchStatuses } from '../utils/nextcloudDirectory.js'
|
|
16
18
|
import { readPath } from '../utils/readPath.js'
|
|
@@ -528,6 +530,15 @@ function tasksSource() {
|
|
|
528
530
|
*/
|
|
529
531
|
loading: () => !!store.loading,
|
|
530
532
|
|
|
533
|
+
// The inbox pages: the page sends `limit` and `offset` and draws the
|
|
534
|
+
// pager from the datastore total.
|
|
535
|
+
pages: true,
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* @return {number} The datastore total, independent of the page size.
|
|
539
|
+
*/
|
|
540
|
+
total: () => Number(store.total) || 0,
|
|
541
|
+
|
|
531
542
|
/**
|
|
532
543
|
* Count the inbox for a config without touching the rows on screen,
|
|
533
544
|
* for a quick-filter tab with `showCount`.
|
|
@@ -648,6 +659,10 @@ function readRowsPath(body, path) {
|
|
|
648
659
|
* requested: page-config keys must never become query parameters. The list
|
|
649
660
|
* stays empty and one console error names the page.
|
|
650
661
|
*
|
|
662
|
+
* A quick filter with `showCount` is counted with `count(config)`: the same
|
|
663
|
+
* request with that tab's filter, its total read at `sourceConfig.totalPath`
|
|
664
|
+
* (`total` by default), else the number of rows the answer holds.
|
|
665
|
+
*
|
|
651
666
|
* @return {object} The source adapter.
|
|
652
667
|
* @spec openspec/changes/index-endpoint-source/specs/index-page/spec.md#requirement-an-index-page-can-list-rows-from-an-app-endpoint
|
|
653
668
|
*/
|
|
@@ -656,6 +671,36 @@ function endpointSource() {
|
|
|
656
671
|
let seq = 0
|
|
657
672
|
let warned = false
|
|
658
673
|
|
|
674
|
+
/**
|
|
675
|
+
* The query parameters for a config: every key but the url and the paths,
|
|
676
|
+
* empty values left out, a list joined by commas.
|
|
677
|
+
*
|
|
678
|
+
* @param {object} rest The config without `url`, `rowsPath` and `totalPath`.
|
|
679
|
+
* @return {object} The parameters.
|
|
680
|
+
*/
|
|
681
|
+
const paramsOf = (rest) => {
|
|
682
|
+
const params = {}
|
|
683
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
684
|
+
if (value === undefined || value === null || value === '') {
|
|
685
|
+
continue
|
|
686
|
+
}
|
|
687
|
+
params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value)
|
|
688
|
+
}
|
|
689
|
+
return params
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Whether a config can be requested: it names a url and is not the page
|
|
694
|
+
* config handed over in its place.
|
|
695
|
+
*
|
|
696
|
+
* @param {object} config The config.
|
|
697
|
+
* @return {boolean}
|
|
698
|
+
*/
|
|
699
|
+
const requestable = (config) => {
|
|
700
|
+
const url = config && config.url
|
|
701
|
+
return typeof url === 'string' && url !== '' && !Object.hasOwn(config, 'entitySource')
|
|
702
|
+
}
|
|
703
|
+
|
|
659
704
|
return {
|
|
660
705
|
/**
|
|
661
706
|
* Fetch the rows.
|
|
@@ -664,10 +709,9 @@ function endpointSource() {
|
|
|
664
709
|
* @return {Promise<void>} Resolves when loaded; a failed request leaves no rows and rethrows.
|
|
665
710
|
*/
|
|
666
711
|
load: async (config) => {
|
|
667
|
-
const { url, rowsPath, ...rest } = config || {}
|
|
712
|
+
const { url, rowsPath, totalPath, ...rest } = config || {}
|
|
668
713
|
const mine = ++seq
|
|
669
|
-
|
|
670
|
-
if (typeof url !== 'string' || url === '' || pageConfig) {
|
|
714
|
+
if (!requestable(config)) {
|
|
671
715
|
state.rows = []
|
|
672
716
|
if (!warned) {
|
|
673
717
|
warned = true
|
|
@@ -677,13 +721,7 @@ function endpointSource() {
|
|
|
677
721
|
}
|
|
678
722
|
return
|
|
679
723
|
}
|
|
680
|
-
const params =
|
|
681
|
-
for (const [key, value] of Object.entries(rest)) {
|
|
682
|
-
if (value === undefined || value === null || value === '') {
|
|
683
|
-
continue
|
|
684
|
-
}
|
|
685
|
-
params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value)
|
|
686
|
-
}
|
|
724
|
+
const params = paramsOf(rest)
|
|
687
725
|
state.loading = true
|
|
688
726
|
try {
|
|
689
727
|
const { default: axios } = await import('@nextcloud/axios')
|
|
@@ -705,6 +743,29 @@ function endpointSource() {
|
|
|
705
743
|
}
|
|
706
744
|
},
|
|
707
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Count the rows for a config without touching the rows on screen, for
|
|
748
|
+
* a quick-filter tab with `showCount`.
|
|
749
|
+
*
|
|
750
|
+
* @param {object} config The page's source config with the tab's filter merged in.
|
|
751
|
+
* @return {Promise<number>} The total, or NaN when nothing can be requested.
|
|
752
|
+
*/
|
|
753
|
+
count: async (config) => {
|
|
754
|
+
if (!requestable(config)) {
|
|
755
|
+
return Number.NaN
|
|
756
|
+
}
|
|
757
|
+
const { url, rowsPath, totalPath, ...rest } = config
|
|
758
|
+
const { default: axios } = await import('@nextcloud/axios')
|
|
759
|
+
const res = await axios.get(generateUrl(url), { params: paramsOf(rest) })
|
|
760
|
+
const body = res ? res.data : null
|
|
761
|
+
const total = body && typeof body === 'object' && !Array.isArray(body) ? readRowsPath(body, totalPath || 'total') : undefined
|
|
762
|
+
if (typeof total === 'number' && Number.isFinite(total)) {
|
|
763
|
+
return total
|
|
764
|
+
}
|
|
765
|
+
const rows = Array.isArray(body) ? body : readRowsPath(body, rowsPath || 'results')
|
|
766
|
+
return Array.isArray(rows) ? rows.length : Number.NaN
|
|
767
|
+
},
|
|
768
|
+
|
|
708
769
|
/**
|
|
709
770
|
* @return {Array<object>} The rows the endpoint returned.
|
|
710
771
|
*/
|
|
@@ -737,39 +798,102 @@ function fillRowTemplate(template, row) {
|
|
|
737
798
|
* and its count (DqZoekresultaten).
|
|
738
799
|
*
|
|
739
800
|
* `sourceConfig.kinds` lists what is searched:
|
|
740
|
-
* `{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }`
|
|
801
|
+
* `{ id, label, register, schema, titleField?, subtitleField?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
|
|
741
802
|
* for objects (OpenRegister's `_search`, so the reader sees what they may
|
|
742
803
|
* read), or `{ id, label, files: true, limit? }` for the reader's files
|
|
743
804
|
* (Nextcloud's unified search). The term is `sourceConfig.term`, else the
|
|
744
805
|
* address's `?q=` (or `sourceConfig.termQuery`). A kind that fails shows no
|
|
745
806
|
* rows and no count; the others still answer.
|
|
746
807
|
*
|
|
808
|
+
* The kind's `label` goes through the app's translate (the "Soort" cell and
|
|
809
|
+
* the quick filters). `statusResolve: { register?, schema, labelField? }`
|
|
810
|
+
* reads `statusField` as a reference and shows the referenced object's label
|
|
811
|
+
* (`labelField`, else `title`, then `name`); the status column is a pill,
|
|
812
|
+
* coloured by `sourceConfig.statusColorMap`. `linkedTemplate` (a field
|
|
813
|
+
* template, "{case.title}") or `linkedField` (a path) fills the "Linked"
|
|
814
|
+
* column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
|
|
815
|
+
* The page's title and count line can name the term as `{term}`, and the
|
|
816
|
+
* count is the active kind's total. Kind, link and status are filterable.
|
|
817
|
+
*
|
|
747
818
|
* @param {object} [config] The page's source config.
|
|
819
|
+
* @param {object} [context] The page's helpers.
|
|
820
|
+
* @param {Function} [context.translate] The app's translate.
|
|
748
821
|
* @return {object} The source adapter.
|
|
749
822
|
* @spec openspec/changes/index-search-source/specs/index-page/spec.md#requirement-an-index-page-can-search-several-schemas-and-files
|
|
750
823
|
*/
|
|
751
|
-
function searchSource(config = {}) {
|
|
824
|
+
function searchSource(config = {}, context = {}) {
|
|
825
|
+
const translate = typeof context.translate === 'function' ? context.translate : (text) => text
|
|
752
826
|
const kinds = (Array.isArray(config && config.kinds) ? config.kinds : [])
|
|
753
827
|
.filter((kind) => kind && typeof kind.id === 'string' && kind.id !== '' && (kind.files === true || (kind.register && kind.schema)))
|
|
754
|
-
|
|
828
|
+
.map((kind) => ({ ...kind, label: translate(String(kind.label || kind.id)) }))
|
|
829
|
+
const hasLinked = kinds.some((kind) => kind.linkedTemplate || kind.linkedField)
|
|
830
|
+
const state = reactive({ rows: [], totals: {}, loading: false, kind: 'all', term: '', filters: {} })
|
|
755
831
|
let seq = 0
|
|
756
832
|
|
|
833
|
+
/**
|
|
834
|
+
* The labels of the objects a kind's status points at, by id: the
|
|
835
|
+
* referenced schema read once per search.
|
|
836
|
+
*
|
|
837
|
+
* @param {object} kind The kind with `statusResolve`.
|
|
838
|
+
* @return {Promise<Map<string, string>>} Labels by id; empty when it cannot be read.
|
|
839
|
+
*/
|
|
840
|
+
const statusLabels = async (kind) => {
|
|
841
|
+
const resolve = kind.statusResolve
|
|
842
|
+
if (!resolve || !resolve.schema) {
|
|
843
|
+
return new Map()
|
|
844
|
+
}
|
|
845
|
+
const objects = await fetchReferenceObjects({ register: resolve.register || kind.register, schema: resolve.schema }).catch(() => [])
|
|
846
|
+
const labels = new Map()
|
|
847
|
+
for (const object of objects) {
|
|
848
|
+
const id = object.id || object.uuid || (object['@self'] && object['@self'].id)
|
|
849
|
+
const label = readPath(object, resolve.labelField || 'title') ?? object.name
|
|
850
|
+
if (id !== undefined && label !== undefined && label !== null) {
|
|
851
|
+
labels.set(String(id), String(label))
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return labels
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* What a row shows in the Linked column.
|
|
859
|
+
*
|
|
860
|
+
* @param {object} kind The row's kind.
|
|
861
|
+
* @param {object} object The found object.
|
|
862
|
+
* @return {string}
|
|
863
|
+
*/
|
|
864
|
+
const linkedOf = (kind, object) => {
|
|
865
|
+
if (kind.linkedTemplate) {
|
|
866
|
+
return fillTemplate(translate(kind.linkedTemplate), object)
|
|
867
|
+
}
|
|
868
|
+
if (kind.linkedField) {
|
|
869
|
+
const value = readPath(object, kind.linkedField)
|
|
870
|
+
return value === undefined || value === null ? '' : String(value)
|
|
871
|
+
}
|
|
872
|
+
return ''
|
|
873
|
+
}
|
|
874
|
+
|
|
757
875
|
const searchObjects = async (axios, kind, term) => {
|
|
758
876
|
const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema })
|
|
759
877
|
const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } })
|
|
760
878
|
const body = (res && res.data) || {}
|
|
761
879
|
const results = Array.isArray(body.results) ? body.results : (Array.isArray(body) ? body : [])
|
|
880
|
+
const labels = kind.statusResolve && kind.statusField ? await statusLabels(kind) : null
|
|
762
881
|
return {
|
|
763
882
|
total: typeof body.total === 'number' ? body.total : results.length,
|
|
764
883
|
rows: results.map((object) => {
|
|
765
884
|
const id = object.id || object.uuid || (object['@self'] && object['@self'].id)
|
|
885
|
+
const rawStatus = kind.statusField ? readPath(object, kind.statusField) : undefined
|
|
886
|
+
const status = rawStatus === undefined || rawStatus === null
|
|
887
|
+
? ''
|
|
888
|
+
: (labels ? (labels.get(String(rawStatus)) || '') : String(rawStatus))
|
|
766
889
|
return {
|
|
767
890
|
id: `${kind.id}:${id}`,
|
|
768
891
|
kind: kind.id,
|
|
769
892
|
kindLabel: kind.label,
|
|
770
893
|
title: String(readPath(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
|
|
771
894
|
subtitle: kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : '',
|
|
772
|
-
status
|
|
895
|
+
status,
|
|
896
|
+
linked: linkedOf(kind, object),
|
|
773
897
|
href: kind.href ? generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
|
|
774
898
|
object,
|
|
775
899
|
}
|
|
@@ -792,6 +916,7 @@ function searchSource(config = {}) {
|
|
|
792
916
|
title: entry.title || '',
|
|
793
917
|
subtitle: entry.subline || '',
|
|
794
918
|
status: '',
|
|
919
|
+
linked: '',
|
|
795
920
|
href: entry.resourceUrl || '',
|
|
796
921
|
})),
|
|
797
922
|
}
|
|
@@ -806,6 +931,17 @@ function searchSource(config = {}) {
|
|
|
806
931
|
*/
|
|
807
932
|
load: async (cfg) => {
|
|
808
933
|
state.kind = cfg && typeof cfg.kind === 'string' ? cfg.kind : 'all'
|
|
934
|
+
// The header filters on Kind, Linked and Status narrow the answer
|
|
935
|
+
// already fetched; they do not search again.
|
|
936
|
+
const filters = {}
|
|
937
|
+
for (const key of ['kindLabel', 'linked', 'status']) {
|
|
938
|
+
const value = cfg && cfg[key]
|
|
939
|
+
const values = (Array.isArray(value) ? value : (value === undefined || value === null || value === '' ? [] : [value])).map(String)
|
|
940
|
+
if (values.length > 0) {
|
|
941
|
+
filters[key] = values
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
state.filters = filters
|
|
809
945
|
const query = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : null
|
|
810
946
|
const term = String((cfg && cfg.term) || (query && query.get((cfg && cfg.termQuery) || 'q')) || '').trim()
|
|
811
947
|
const mine = ++seq
|
|
@@ -837,7 +973,20 @@ function searchSource(config = {}) {
|
|
|
837
973
|
/**
|
|
838
974
|
* @return {Array<object>} The results of the active kind, or all of them.
|
|
839
975
|
*/
|
|
840
|
-
rows: () => (state.kind === 'all' ? state.rows : state.rows.filter((row) => row.kind === state.kind))
|
|
976
|
+
rows: () => (state.kind === 'all' ? state.rows : state.rows.filter((row) => row.kind === state.kind))
|
|
977
|
+
.filter((row) => Object.entries(state.filters).every(([key, values]) => values.includes(String(row[key])))),
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* @return {string} The term searched for, for a `{term}` in the title or count line.
|
|
981
|
+
*/
|
|
982
|
+
term: () => state.term,
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* @return {number} The active kind's total, or every kind's together on All.
|
|
986
|
+
*/
|
|
987
|
+
total: () => (state.kind === 'all'
|
|
988
|
+
? Object.values(state.totals).reduce((sum, value) => sum + value, 0)
|
|
989
|
+
: (state.totals[state.kind] || 0)),
|
|
841
990
|
|
|
842
991
|
/**
|
|
843
992
|
* @return {boolean} Whether a search is in flight.
|
|
@@ -846,8 +995,14 @@ function searchSource(config = {}) {
|
|
|
846
995
|
|
|
847
996
|
columns: [
|
|
848
997
|
{ key: 'title', label: t('nextcloud-vue', 'Result'), secondary: 'subtitle' },
|
|
849
|
-
{ key: 'kindLabel', label: t('nextcloud-vue', 'Kind') },
|
|
850
|
-
{ key: '
|
|
998
|
+
{ key: 'kindLabel', label: t('nextcloud-vue', 'Kind'), enum: kinds.map((kind) => kind.label) },
|
|
999
|
+
...(hasLinked ? [{ key: 'linked', label: config.linkedLabel ? translate(config.linkedLabel) : t('nextcloud-vue', 'Linked') }] : []),
|
|
1000
|
+
{
|
|
1001
|
+
key: 'status',
|
|
1002
|
+
label: t('nextcloud-vue', 'Status'),
|
|
1003
|
+
widget: 'badge',
|
|
1004
|
+
widgetProps: { colorMap: (config && config.statusColorMap) || {} },
|
|
1005
|
+
},
|
|
851
1006
|
],
|
|
852
1007
|
|
|
853
1008
|
quickFilters: kinds.length > 0
|
|
@@ -1125,10 +1280,11 @@ export const indexSources = {
|
|
|
1125
1280
|
*
|
|
1126
1281
|
* @param {string} name The source name from `config.source`.
|
|
1127
1282
|
* @param {object} [config] The page's source config, for a source that builds its tabs from it.
|
|
1283
|
+
* @param {object} [context] The page's helpers a source may use, such as `translate` (the app's catalogue).
|
|
1128
1284
|
*
|
|
1129
1285
|
* @return {object|null} The adapter, or null when the name is not registered.
|
|
1130
1286
|
*/
|
|
1131
|
-
export function resolveIndexSource(name, config) {
|
|
1287
|
+
export function resolveIndexSource(name, config, context) {
|
|
1132
1288
|
const key = String(name || '')
|
|
1133
1289
|
if (!key) {
|
|
1134
1290
|
return null
|
|
@@ -1141,7 +1297,7 @@ export function resolveIndexSource(name, config) {
|
|
|
1141
1297
|
return null
|
|
1142
1298
|
}
|
|
1143
1299
|
|
|
1144
|
-
return factory(config || {})
|
|
1300
|
+
return factory(config || {}, context || {})
|
|
1145
1301
|
}
|
|
1146
1302
|
|
|
1147
1303
|
/**
|
|
@@ -155,7 +155,7 @@ export function interpolateUrlTokens(str, ctx) {
|
|
|
155
155
|
*
|
|
156
156
|
* @param {{url: string, method?: string, params?: object}} config The endpointSource block.
|
|
157
157
|
* @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object, range?: object}} [ctx] The token context.
|
|
158
|
-
* @return {{url: string, method: ('GET'|'POST'), params: object, blocked: boolean}} The resolved request.
|
|
158
|
+
* @return {{url: string, method: ('GET'|'POST'), params: object, blocked: boolean, responsePath: string}} The resolved request.
|
|
159
159
|
*/
|
|
160
160
|
export function resolveEndpointRequest(config, ctx) {
|
|
161
161
|
const cfg = config || {}
|
|
@@ -163,8 +163,30 @@ export function resolveEndpointRequest(config, ctx) {
|
|
|
163
163
|
const resolved = resolveFilterTokens(cfg.params || {}, ctx)
|
|
164
164
|
const params = dropOptionalUnresolved(resolved)
|
|
165
165
|
const method = String(cfg.method || 'GET').toUpperCase() === 'POST' ? 'POST' : 'GET'
|
|
166
|
-
const
|
|
167
|
-
|
|
166
|
+
const responsePath = resolveResponsePath(cfg.responsePath, ctx)
|
|
167
|
+
const blocked = hasUnresolvedTokens(params) || hasEmptyPathSegment(cfg.url || '', url) || responsePath === null
|
|
168
|
+
return { url, method, params, blocked, responsePath: responsePath || '' }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resolve the tokens in a `responsePath` (`phaseDays.@workspace.measure`), so a
|
|
173
|
+
* page control can choose which part of one response a widget reads. Null when
|
|
174
|
+
* a token resolved to nothing and left an empty path segment: the read waits
|
|
175
|
+
* instead of plucking the wrong part.
|
|
176
|
+
*
|
|
177
|
+
* @param {string|undefined} path The configured path.
|
|
178
|
+
* @param {object} [ctx] The token context.
|
|
179
|
+
* @return {string|null} The resolved path, '' for none, null when blocked.
|
|
180
|
+
*/
|
|
181
|
+
function resolveResponsePath(path, ctx) {
|
|
182
|
+
if (typeof path !== 'string' || path === '') {
|
|
183
|
+
return ''
|
|
184
|
+
}
|
|
185
|
+
if (path.indexOf('@') === -1) {
|
|
186
|
+
return path
|
|
187
|
+
}
|
|
188
|
+
const resolved = interpolateUrlTokens(path, ctx)
|
|
189
|
+
return resolved.split('.').some((segment) => segment === '') ? null : resolved
|
|
168
190
|
}
|
|
169
191
|
|
|
170
192
|
/**
|
|
@@ -309,7 +331,7 @@ export async function fetchEndpointSource(config, ctx, opts) {
|
|
|
309
331
|
return null
|
|
310
332
|
}
|
|
311
333
|
const body = await fetchSharedResponse(request, opts)
|
|
312
|
-
const payload = getByPath(body,
|
|
334
|
+
const payload = getByPath(body, request.responsePath)
|
|
313
335
|
return payload === undefined ? null : payload
|
|
314
336
|
}
|
|
315
337
|
|
|
@@ -378,7 +400,7 @@ export function useEndpointSource(source, options) {
|
|
|
378
400
|
u: request.url,
|
|
379
401
|
m: request.method,
|
|
380
402
|
p: request.params,
|
|
381
|
-
r:
|
|
403
|
+
r: request.responsePath,
|
|
382
404
|
b: request.blocked,
|
|
383
405
|
})
|
|
384
406
|
})
|
|
@@ -418,7 +440,7 @@ export function useEndpointSource(source, options) {
|
|
|
418
440
|
if (seq !== fetchSeq) {
|
|
419
441
|
return
|
|
420
442
|
}
|
|
421
|
-
const payload = getByPath(body,
|
|
443
|
+
const payload = getByPath(body, request.responsePath)
|
|
422
444
|
data.value = payload === undefined ? null : payload
|
|
423
445
|
} catch (e) {
|
|
424
446
|
if (seq !== fetchSeq) {
|
package/src/css/index-page.css
CHANGED
|
@@ -73,6 +73,123 @@
|
|
|
73
73
|
min-height: 300px;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/* The map with its numbered list (`mapConfig.list`, DqZakenKaart): one framed
|
|
77
|
+
card, the map taking the larger share and the list beside it, stacking
|
|
78
|
+
under a narrow width. */
|
|
79
|
+
.cn-index-page__map-card {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex: 1;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
gap: 16px;
|
|
84
|
+
min-height: 0;
|
|
85
|
+
padding: 16px;
|
|
86
|
+
border: 1px solid var(--color-border);
|
|
87
|
+
border-radius: var(--cn-board-card-radius, 12px);
|
|
88
|
+
background: var(--color-main-background);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.cn-index-page__map-card .cn-index-page__map {
|
|
92
|
+
flex: 3 1 420px;
|
|
93
|
+
min-width: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.cn-index-page__map-side {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex: 2 1 320px;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
gap: 12px;
|
|
101
|
+
min-width: 0;
|
|
102
|
+
overflow-y: auto;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.cn-index-page__map-list {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 0;
|
|
110
|
+
border: 1px solid var(--color-border);
|
|
111
|
+
border-radius: 10px;
|
|
112
|
+
list-style: none;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.cn-index-page__map-item {
|
|
116
|
+
display: flex;
|
|
117
|
+
gap: 12px;
|
|
118
|
+
padding: 12px 14px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.cn-index-page__map-item + .cn-index-page__map-item {
|
|
122
|
+
border-top: 1px solid var(--color-border);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.cn-index-page__map-item--active {
|
|
126
|
+
background: var(--color-primary-element-light);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.cn-index-page__map-number {
|
|
130
|
+
display: inline-flex;
|
|
131
|
+
flex: none;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
width: 26px;
|
|
135
|
+
height: 26px;
|
|
136
|
+
border-radius: 13px;
|
|
137
|
+
background: var(--color-primary-element);
|
|
138
|
+
color: var(--color-primary-element-text);
|
|
139
|
+
font-size: 13px;
|
|
140
|
+
font-weight: 700;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.cn-index-page__map-item--active .cn-index-page__map-number {
|
|
144
|
+
background: var(--color-primary-element-light-text);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.cn-index-page__map-text {
|
|
148
|
+
display: flex;
|
|
149
|
+
flex: 1;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: 4px;
|
|
152
|
+
min-width: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.cn-index-page__map-title {
|
|
156
|
+
margin: 0;
|
|
157
|
+
padding: 0;
|
|
158
|
+
border: 0;
|
|
159
|
+
background: none;
|
|
160
|
+
color: var(--color-main-text);
|
|
161
|
+
font: inherit;
|
|
162
|
+
font-size: 15px;
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
line-height: 1.35;
|
|
165
|
+
text-align: start;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.cn-index-page__map-sub,
|
|
170
|
+
.cn-index-page__map-due {
|
|
171
|
+
color: var(--color-text-maxcontrast);
|
|
172
|
+
font-size: 13px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.cn-index-page__map-pills {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-wrap: wrap;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 6px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.cn-index-page__map-missing {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
align-items: flex-start;
|
|
186
|
+
gap: 8px;
|
|
187
|
+
padding: 14px;
|
|
188
|
+
border: 1px solid var(--color-border);
|
|
189
|
+
border-radius: 10px;
|
|
190
|
+
font-size: 14px;
|
|
191
|
+
}
|
|
192
|
+
|
|
76
193
|
/* Opt-in folder navigation pane (left of the list). */
|
|
77
194
|
.cn-index-page__folder-pane {
|
|
78
195
|
flex: 0 0 240px;
|
|
@@ -128,6 +128,16 @@
|
|
|
128
128
|
gap: 8px;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/* `quickFilterPlacement: "toolbar"` (DqTeamwachtrij): the chips take the room
|
|
132
|
+
the Filter button and the view switch leave on row 1, so the three stay on
|
|
133
|
+
one row in the column beside a side panel; the chips that do not fit move
|
|
134
|
+
into the overflow chip (CnQuickFilterBar `fit`) instead of pushing the
|
|
135
|
+
buttons onto a row of their own. */
|
|
136
|
+
.cn-look-board.cn-index-page--quick-filters-toolbar .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-quick-filter-bar {
|
|
137
|
+
flex: 1 1 0;
|
|
138
|
+
min-width: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
/* Row 2: the search field and the active filters on a line of their own. */
|
|
132
142
|
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--search {
|
|
133
143
|
display: flex;
|
package/src/css/look-board.css
CHANGED
|
@@ -99,6 +99,50 @@
|
|
|
99
99
|
padding-inline-start: 56px;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/* The dashboard header as the board draws it (DqTermijnen): the actions on
|
|
103
|
+
the title's row, the description and its segmented period under both, at
|
|
104
|
+
the header's full width. As two flex columns the description sat in the
|
|
105
|
+
title's column only, so a long period group wrapped under its text and the
|
|
106
|
+
actions dropped to a line of their own. The title column never shrinks
|
|
107
|
+
below its longest word; the actions wrap their buttons first.
|
|
108
|
+
@spec openspec/changes/dashboard-board-header-row/specs/dashboard-page/spec.md#requirement-the-board-dashboard-header-keeps-its-actions-on-the-title-row */
|
|
109
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header {
|
|
110
|
+
display: grid;
|
|
111
|
+
grid-template-columns: minmax(min-content, 1fr) auto;
|
|
112
|
+
align-items: start;
|
|
113
|
+
column-gap: 16px;
|
|
114
|
+
row-gap: 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header-left {
|
|
118
|
+
display: contents;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__title-row {
|
|
122
|
+
grid-column: 1;
|
|
123
|
+
grid-row: 1;
|
|
124
|
+
align-self: center;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header-left > :not(.cn-dashboard-page__title-row) {
|
|
128
|
+
grid-column: 1 / -1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header > .cn-dashboard-page__header-actions {
|
|
132
|
+
grid-column: 2;
|
|
133
|
+
grid-row: 1;
|
|
134
|
+
justify-content: flex-end;
|
|
135
|
+
margin-inline-start: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* A stat tile in the board's KPI row carries the board's own 18px 20px inset
|
|
139
|
+
(`cn-kpi-card--board`), so the card-fit cell adds none of its own: with
|
|
140
|
+
both the label sat 29px from the tile's top and 37px from its side, and
|
|
141
|
+
the row stood about 1.4 times the board's height. */
|
|
142
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__kpi-cell .cn-dashboard-page__card-fit .cn-widget-wrapper__content:has(> .cn-kpi-card--board.cn-kpi-card--stacked) {
|
|
143
|
+
padding: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
102
146
|
/* ---- The header button ------------------------------------------------- */
|
|
103
147
|
.cn-look-board .cn-index-page__header-buttons:not(.cn-look-nextcloud *),
|
|
104
148
|
.cn-look-board .cn-detail-page__header-actions:not(.cn-look-nextcloud *),
|