@conduction/nextcloud-vue 2.99.0 → 2.101.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/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +6 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.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 +51 -7
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +104 -73
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +5 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -8
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.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/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- 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/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
- 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 +387 -78
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +317 -209
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +4 -1
- 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/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/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.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/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +1 -1
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +38 -9
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +51 -4
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
- 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/CnWizardDialog/CnWizardDialog.vue.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +167 -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 +43 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +41 -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 +1101 -396
- 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/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +9996 -7378
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1101 -396
- package/l10n/en.json +43 -1
- package/l10n/nl.json +41 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
- package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
- package/src/components/CnDashboardPage/CnDashboardPage.vue +89 -20
- package/src/components/CnDetailPage/CnDetailPage.vue +28 -9
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
- package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
- package/src/components/CnExportsWidget/index.js +7 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
- package/src/components/CnFilesBrowser/filesReview.js +57 -0
- package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
- package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
- package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
- package/src/components/CnGroupedCardsWidget/index.js +28 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +376 -11
- package/src/components/CnIndexPage/useNamedSource.js +4 -1
- package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
- 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/CnRecordListWidget/CnRecordListWidget.vue +114 -7
- package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
- package/src/components/CnSavedViewAlertWidget/index.js +7 -0
- package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +38 -9
- package/src/components/CnSetupWizard/CnSetupWizard.vue +72 -5
- package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
- package/src/components/index.js +3 -0
- package/src/composables/indexSources.js +166 -19
- package/src/composables/useEndpointSource.js +28 -6
- package/src/css/detail-page.css +15 -0
- package/src/css/dialog.css +48 -0
- package/src/css/index-page.css +117 -0
- package/src/css/look-board.css +127 -1
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +55 -1
- package/src/utils/languageMapText.js +46 -0
- package/src/utils/libraryWidgetKeys.js +3 -0
- package/src/utils/mapList.js +67 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -233,6 +233,15 @@
|
|
|
233
233
|
{{ moreLabel }}
|
|
234
234
|
</p>
|
|
235
235
|
</template>
|
|
236
|
+
<!-- `content.footnote`: a line under the table ("Awb 4:17 en 4:18"),
|
|
237
|
+
through the host translate. Not while the list waits for its
|
|
238
|
+
context or failed to load. -->
|
|
239
|
+
<p
|
|
240
|
+
v-if="resolvedFootnote && !waitingForContext && !error"
|
|
241
|
+
class="cn-object-list-widget__footnote"
|
|
242
|
+
data-testid="cn-object-list-widget-footnote">
|
|
243
|
+
{{ resolvedFootnote }}
|
|
244
|
+
</p>
|
|
236
245
|
<!-- Create affordance (ADR-062): every collection carries its Add at
|
|
237
246
|
the bottom of the widget; the host card's Actions menu calls the
|
|
238
247
|
same openCreate() through the public method. Suppressed while the
|
|
@@ -430,8 +439,10 @@ export default {
|
|
|
430
439
|
* `hideHeader: true` drops the table's column header row (CnDataTable's
|
|
431
440
|
* `hideHeader`), for a short list that reads as rows, not as a table.
|
|
432
441
|
*
|
|
442
|
+
* `footnote` is a line under the table, through the host translate.
|
|
443
|
+
*
|
|
433
444
|
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes
|
|
434
|
-
* @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean}}
|
|
445
|
+
* @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, footnote?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean}}
|
|
435
446
|
*/
|
|
436
447
|
content: {
|
|
437
448
|
type: Object,
|
|
@@ -918,6 +929,17 @@ export default {
|
|
|
918
929
|
return this.emptyText ? fn(this.emptyText) : this.emptyText
|
|
919
930
|
},
|
|
920
931
|
|
|
932
|
+
/**
|
|
933
|
+
* `content.footnote` run through the host translate function, or ''.
|
|
934
|
+
*
|
|
935
|
+
* @return {string}
|
|
936
|
+
*/
|
|
937
|
+
resolvedFootnote() {
|
|
938
|
+
const note = typeof this.content.footnote === 'string' ? this.content.footnote : ''
|
|
939
|
+
const fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k
|
|
940
|
+
return note ? fn(note) : ''
|
|
941
|
+
},
|
|
942
|
+
|
|
921
943
|
/**
|
|
922
944
|
* The rows actually rendered: capped to what fits the host grid cell
|
|
923
945
|
* (ADR-062 — content adapts to the cell, never a nested scrollbar).
|
|
@@ -2027,6 +2049,12 @@ export default {
|
|
|
2027
2049
|
padding: 4px;
|
|
2028
2050
|
}
|
|
2029
2051
|
|
|
2052
|
+
.cn-object-list-widget__footnote {
|
|
2053
|
+
color: var(--color-text-maxcontrast);
|
|
2054
|
+
font-size: 13px;
|
|
2055
|
+
margin: 8px 0 0;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2030
2058
|
/* Footer Add — same pattern as the integration leaves' footer action
|
|
2031
2059
|
("Open in Calendar"): full-width, centered, divider-topped, pinned to
|
|
2032
2060
|
the card bottom. */
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
:dropHint="dropHint"
|
|
16
16
|
:rowActions="rowActions"
|
|
17
17
|
:newActions="newActions"
|
|
18
|
+
:headerActions="headerActions"
|
|
19
|
+
:rowButton="rowButton"
|
|
20
|
+
:reviewSummary="reviewSummary"
|
|
21
|
+
:footnote="footnote"
|
|
22
|
+
:nameSecondary="nameSecondary"
|
|
18
23
|
:linkedItems="linkedItems"
|
|
19
24
|
:columns="columns"
|
|
20
25
|
:rowData="rowDataSource"
|
|
@@ -420,6 +425,16 @@ export default {
|
|
|
420
425
|
* @type {string[]}
|
|
421
426
|
*/
|
|
422
427
|
facets: { type: Array, default: () => [] },
|
|
428
|
+
/** Forwarded to CnFilesBrowser: the host's own buttons in the bar. */
|
|
429
|
+
headerActions: { type: Array, default: () => [] },
|
|
430
|
+
/** Forwarded to CnFilesBrowser: one visible button on every file row. */
|
|
431
|
+
rowButton: { type: Object, default: null },
|
|
432
|
+
/** Forwarded to CnFilesBrowser: the review summary above the list. */
|
|
433
|
+
reviewSummary: { type: Object, default: null },
|
|
434
|
+
/** Forwarded to CnFilesBrowser: the host's own line under the list. */
|
|
435
|
+
footnote: { type: String, default: '' },
|
|
436
|
+
/** Forwarded to CnFilesBrowser: the second line under each file's name. */
|
|
437
|
+
nameSecondary: { type: String, default: '' },
|
|
423
438
|
/** A dotted path to the list of records in the `rowDataUrl` answer; empty reads the answer itself. */
|
|
424
439
|
rowDataPath: { type: String, default: '' },
|
|
425
440
|
/** The record field that holds the Nextcloud file id. */
|
|
@@ -17,7 +17,30 @@
|
|
|
17
17
|
v-for="(row, index) in rows"
|
|
18
18
|
:key="row.value"
|
|
19
19
|
class="cn-ordered-list-field__row"
|
|
20
|
-
:
|
|
20
|
+
:class="{
|
|
21
|
+
'cn-ordered-list-field__row--dragging': dragIndex === index,
|
|
22
|
+
'cn-ordered-list-field__row--over': dropIndex === index && dragIndex !== index,
|
|
23
|
+
}"
|
|
24
|
+
:data-testid="`cn-ordered-list-row-${row.value}`"
|
|
25
|
+
@dragover.prevent="onDragOver(index)"
|
|
26
|
+
@drop.prevent="onDrop(index)">
|
|
27
|
+
<!-- A drag handle (DqPersoonlijkeInstellingen). It is a button: a
|
|
28
|
+
keyboard user moves the row with the arrow keys on it, and the
|
|
29
|
+
up and down buttons stay for anyone who neither drags nor uses
|
|
30
|
+
arrow keys. -->
|
|
31
|
+
<button
|
|
32
|
+
v-if="!disabled"
|
|
33
|
+
type="button"
|
|
34
|
+
class="cn-ordered-list-field__handle"
|
|
35
|
+
draggable="true"
|
|
36
|
+
:aria-label="t('nextcloud-vue', 'Reorder {item}: drag, or press the up and down arrow keys', { item: row.label })"
|
|
37
|
+
:data-testid="`cn-ordered-list-handle-${row.value}`"
|
|
38
|
+
@dragstart="onDragStart($event, index)"
|
|
39
|
+
@dragend="onDragEnd"
|
|
40
|
+
@keydown.up.prevent="move(index, index - 1, 'handle')"
|
|
41
|
+
@keydown.down.prevent="move(index, index + 1, 'handle')">
|
|
42
|
+
<DragVertical :size="20" />
|
|
43
|
+
</button>
|
|
21
44
|
<span class="cn-ordered-list-field__position" aria-hidden="true">{{ index + 1 }}</span>
|
|
22
45
|
<span v-if="row.icon" class="cn-ordered-list-field__tile" aria-hidden="true">
|
|
23
46
|
<CnIcon :name="row.icon" :size="18" />
|
|
@@ -104,6 +127,7 @@ import { NcSelect } from '@nextcloud/vue'
|
|
|
104
127
|
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
|
|
105
128
|
import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
|
|
106
129
|
import Close from 'vue-material-design-icons/Close.vue'
|
|
130
|
+
import DragVertical from 'vue-material-design-icons/DragVertical.vue'
|
|
107
131
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
108
132
|
|
|
109
133
|
let orderedListUid = 0
|
|
@@ -134,7 +158,7 @@ let orderedListUid = 0
|
|
|
134
158
|
export default {
|
|
135
159
|
name: 'CnOrderedListField',
|
|
136
160
|
|
|
137
|
-
components: { ChevronDown, ChevronUp, Close, CnIcon, NcSelect },
|
|
161
|
+
components: { ChevronDown, ChevronUp, Close, CnIcon, DragVertical, NcSelect },
|
|
138
162
|
|
|
139
163
|
props: {
|
|
140
164
|
/** The values in their order. */
|
|
@@ -205,6 +229,12 @@ export default {
|
|
|
205
229
|
return {
|
|
206
230
|
labelId: `cn-ordered-list-label-${orderedListUid}`,
|
|
207
231
|
announcement: '',
|
|
232
|
+
/** The value whose handle takes focus after the next render, or null. */
|
|
233
|
+
pendingHandleFocus: null,
|
|
234
|
+
/** The index of the row being dragged, or -1. */
|
|
235
|
+
dragIndex: -1,
|
|
236
|
+
/** The index of the row the drag is over, or -1. */
|
|
237
|
+
dropIndex: -1,
|
|
208
238
|
}
|
|
209
239
|
},
|
|
210
240
|
|
|
@@ -244,6 +274,20 @@ export default {
|
|
|
244
274
|
},
|
|
245
275
|
},
|
|
246
276
|
|
|
277
|
+
watch: {
|
|
278
|
+
// The parent's new order arrives as new rows; the moved row's handle
|
|
279
|
+
// lost focus when its row moved in the DOM, so it takes it back.
|
|
280
|
+
rows: {
|
|
281
|
+
flush: 'post',
|
|
282
|
+
handler() {
|
|
283
|
+
if (this.pendingHandleFocus !== null) {
|
|
284
|
+
this.focusPendingHandle()
|
|
285
|
+
this.pendingHandleFocus = null
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
|
|
247
291
|
methods: {
|
|
248
292
|
t,
|
|
249
293
|
|
|
@@ -270,13 +314,15 @@ export default {
|
|
|
270
314
|
},
|
|
271
315
|
|
|
272
316
|
/**
|
|
273
|
-
* Move a row to another place and keep focus on the pressed
|
|
274
|
-
* where it still makes sense
|
|
317
|
+
* Move a row to another place and keep focus on the pressed control
|
|
318
|
+
* where it still makes sense: the row's handle after an arrow key on
|
|
319
|
+
* it, else the up or down button.
|
|
275
320
|
*
|
|
276
321
|
* @param {number} from Its index.
|
|
277
322
|
* @param {number} to The index it moves to.
|
|
323
|
+
* @param {string} [via] `handle` when the move came from the handle.
|
|
278
324
|
*/
|
|
279
|
-
move(from, to) {
|
|
325
|
+
move(from, to, via = '') {
|
|
280
326
|
if (to < 0 || to >= this.rows.length) {
|
|
281
327
|
return
|
|
282
328
|
}
|
|
@@ -286,7 +332,16 @@ export default {
|
|
|
286
332
|
const row = this.rows[from]
|
|
287
333
|
this.emitValue(values)
|
|
288
334
|
this.announcement = t('nextcloud-vue', '{item} moved to position {position} of {total}', { item: row.label, position: to + 1, total: values.length })
|
|
335
|
+
if (via === 'handle') {
|
|
336
|
+
// Re-focused once the parent hands the new order back: moving the
|
|
337
|
+
// row in the DOM takes focus off its handle.
|
|
338
|
+
this.pendingHandleFocus = row.value
|
|
339
|
+
}
|
|
289
340
|
this.$nextTick(() => {
|
|
341
|
+
if (via === 'handle') {
|
|
342
|
+
this.focusPendingHandle()
|
|
343
|
+
return
|
|
344
|
+
}
|
|
290
345
|
const direction = to < from ? 'up' : 'down'
|
|
291
346
|
const atEdge = (direction === 'up' && to === 0) || (direction === 'down' && to === values.length - 1)
|
|
292
347
|
const target = this.$el.querySelector(`[data-testid="cn-ordered-list-${atEdge ? (direction === 'up' ? 'down' : 'up') : direction}-${row.value}"]`)
|
|
@@ -296,6 +351,61 @@ export default {
|
|
|
296
351
|
})
|
|
297
352
|
},
|
|
298
353
|
|
|
354
|
+
/** Put focus back on the handle of the row just moved by its handle. */
|
|
355
|
+
focusPendingHandle() {
|
|
356
|
+
if (this.pendingHandleFocus === null) {
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
const handle = this.$el.querySelector(`[data-testid="cn-ordered-list-handle-${this.pendingHandleFocus}"]`)
|
|
360
|
+
if (handle) {
|
|
361
|
+
handle.focus()
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Start dragging a row by its handle.
|
|
367
|
+
*
|
|
368
|
+
* @param {DragEvent} event The drag event.
|
|
369
|
+
* @param {number} index The row's index.
|
|
370
|
+
*/
|
|
371
|
+
onDragStart(event, index) {
|
|
372
|
+
this.dragIndex = index
|
|
373
|
+
if (event && event.dataTransfer) {
|
|
374
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
375
|
+
event.dataTransfer.setData('text/plain', String(this.rows[index].value))
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Mark the row a drag is over as where the row will land.
|
|
381
|
+
*
|
|
382
|
+
* @param {number} index The row's index.
|
|
383
|
+
*/
|
|
384
|
+
onDragOver(index) {
|
|
385
|
+
if (this.dragIndex !== -1) {
|
|
386
|
+
this.dropIndex = index
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Drop the dragged row in the place of the row it is over.
|
|
392
|
+
*
|
|
393
|
+
* @param {number} index The row's index.
|
|
394
|
+
*/
|
|
395
|
+
onDrop(index) {
|
|
396
|
+
const from = this.dragIndex
|
|
397
|
+
this.onDragEnd()
|
|
398
|
+
if (from !== -1 && from !== index) {
|
|
399
|
+
this.move(from, index, 'handle')
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
/** End a drag, dropped or not. */
|
|
404
|
+
onDragEnd() {
|
|
405
|
+
this.dragIndex = -1
|
|
406
|
+
this.dropIndex = -1
|
|
407
|
+
},
|
|
408
|
+
|
|
299
409
|
/**
|
|
300
410
|
* Remove a row.
|
|
301
411
|
*
|
|
@@ -371,6 +481,42 @@ export default {
|
|
|
371
481
|
border-top: 1px solid var(--color-border);
|
|
372
482
|
}
|
|
373
483
|
|
|
484
|
+
.cn-ordered-list-field__row--dragging {
|
|
485
|
+
opacity: 0.5;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Where a dragged row will land: a primary line on the row's top edge. */
|
|
489
|
+
.cn-ordered-list-field__row--over {
|
|
490
|
+
box-shadow: inset 0 2px 0 var(--color-primary-element);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.cn-ordered-list-field__handle {
|
|
494
|
+
display: inline-flex;
|
|
495
|
+
flex: none;
|
|
496
|
+
align-items: center;
|
|
497
|
+
justify-content: center;
|
|
498
|
+
width: 28px;
|
|
499
|
+
min-width: 28px;
|
|
500
|
+
height: 34px;
|
|
501
|
+
min-height: 34px;
|
|
502
|
+
margin: 0 -6px 0 -8px;
|
|
503
|
+
padding: 0;
|
|
504
|
+
border: 0;
|
|
505
|
+
border-radius: var(--border-radius-element, 8px);
|
|
506
|
+
background: transparent;
|
|
507
|
+
color: var(--color-text-maxcontrast);
|
|
508
|
+
cursor: grab;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.cn-ordered-list-field__handle:active {
|
|
512
|
+
cursor: grabbing;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.cn-ordered-list-field__handle:focus-visible {
|
|
516
|
+
outline: 2px solid var(--color-primary-element);
|
|
517
|
+
outline-offset: 1px;
|
|
518
|
+
}
|
|
519
|
+
|
|
374
520
|
.cn-ordered-list-field__position {
|
|
375
521
|
flex: none;
|
|
376
522
|
min-width: 18px;
|
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
<p v-else-if="rows.length === 0" class="cn-record-list__empty" data-testid="cn-record-list-empty">
|
|
18
18
|
{{ emptyLabel }}
|
|
19
19
|
</p>
|
|
20
|
+
<!-- notice: the rows as sentences in one toned box ("2 waarschuwingen: ..."). -->
|
|
21
|
+
<div
|
|
22
|
+
v-else-if="presentation === 'notice'"
|
|
23
|
+
class="cn-record-list__notice"
|
|
24
|
+
:class="`cn-record-list__notice--${noticeVariant}`"
|
|
25
|
+
role="note"
|
|
26
|
+
data-testid="cn-record-list-notice">
|
|
27
|
+
<CnIcon
|
|
28
|
+
:name="noticeIcon"
|
|
29
|
+
:size="18"
|
|
30
|
+
class="cn-record-list__notice-icon"
|
|
31
|
+
aria-hidden="true" />
|
|
32
|
+
<p class="cn-record-list__notice-text">
|
|
33
|
+
{{ noticeText }}
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
20
36
|
<ul v-else class="cn-record-list__list">
|
|
21
37
|
<li
|
|
22
38
|
v-for="item in items"
|
|
@@ -210,7 +226,22 @@ import { readPath } from '../../utils/readPath.js'
|
|
|
210
226
|
import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
211
227
|
|
|
212
228
|
const PAGE_REFRESH_CHANNEL = 'cn:page:refresh'
|
|
213
|
-
const PRESENTATIONS = ['rows', 'feed', 'inline', 'cards']
|
|
229
|
+
const PRESENTATIONS = ['rows', 'feed', 'inline', 'cards', 'notice']
|
|
230
|
+
|
|
231
|
+
/** The tones a `notice` list takes. */
|
|
232
|
+
const NOTICE_VARIANTS = ['info', 'warning', 'error']
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A row that is a plain string or number (an endpoint answering `["...", "..."]`)
|
|
236
|
+
* read as `{ value }`, so the templates reach it as `{value}` and a list
|
|
237
|
+
* without a `title` shows the value itself. An object row is returned as is.
|
|
238
|
+
*
|
|
239
|
+
* @param {(object|string|number)} row The row.
|
|
240
|
+
* @return {(object|string|number)} The row, or `{ value }` for a string or number.
|
|
241
|
+
*/
|
|
242
|
+
function scalarRow(row) {
|
|
243
|
+
return typeof row === 'string' || typeof row === 'number' ? { value: String(row) } : row
|
|
244
|
+
}
|
|
214
245
|
|
|
215
246
|
/**
|
|
216
247
|
* Unwrap a value that may be a ref.
|
|
@@ -266,7 +297,7 @@ export default {
|
|
|
266
297
|
props: {
|
|
267
298
|
/**
|
|
268
299
|
* The list config: `{ register, schema, filter?, sort?, limit?, extend?,
|
|
269
|
-
* presentation?, title?, meta?, text?, label?, pill?, trailing?, icon?,
|
|
300
|
+
* presentation?, variant?, lead?, title?, meta?, text?, label?, pill?, trailing?, icon?,
|
|
270
301
|
* rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText?,
|
|
271
302
|
* valueLabels?, endpointSource?, bar? }`. `bar` (`{ valueField, maxField?,
|
|
272
303
|
* overVariant? }`) draws a thin track under each row: filled to
|
|
@@ -275,7 +306,14 @@ export default {
|
|
|
275
306
|
* largest row. `endpointSource` (`{ url, method?,
|
|
276
307
|
* params?, responsePath? }`) reads the rows from an app endpoint instead
|
|
277
308
|
* of `register` / `schema`. `valueLabels` maps a field's stored values to the labels
|
|
278
|
-
* the templates show.
|
|
309
|
+
* the templates show. `headerNote` and `headerLink.label` fill `{total}`
|
|
310
|
+
* and `{count:<field>=<value>}`, the loaded rows with that value.
|
|
311
|
+
* A row that is a plain string or number (an endpoint
|
|
312
|
+
* answering `["…", "…"]`) is read as `{ value }`: templates reach it as
|
|
313
|
+
* `{value}`, and without `title` a row shows the value itself.
|
|
314
|
+
* `presentation: "notice"` draws the rows as sentences in one box toned by
|
|
315
|
+
* `variant` (`info`, `warning` or `error`), after an optional `lead`
|
|
316
|
+
* (`{total}` filled in).
|
|
279
317
|
*
|
|
280
318
|
* @type {object}
|
|
281
319
|
*/
|
|
@@ -334,6 +372,35 @@ export default {
|
|
|
334
372
|
|
|
335
373
|
computed: {
|
|
336
374
|
/** @return {string} `rows`, `feed`, `inline` or `cards`. */
|
|
375
|
+
/**
|
|
376
|
+
* The tone of a `notice` list (`content.variant`): info, warning or error.
|
|
377
|
+
*
|
|
378
|
+
* @return {string} The variant, `info` when unset or unknown.
|
|
379
|
+
* @spec openspec/changes/record-list-string-rows/specs/record-list-string-rows/spec.md#requirement-a-record-list-draws-rows-that-are-plain-strings
|
|
380
|
+
*/
|
|
381
|
+
noticeVariant() {
|
|
382
|
+
const value = this.content?.variant
|
|
383
|
+
return NOTICE_VARIANTS.includes(value) ? value : 'info'
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
/** @return {string} The icon of the notice tone. */
|
|
387
|
+
noticeIcon() {
|
|
388
|
+
return this.noticeVariant === 'info' ? 'InformationOutline' : 'AlertOutline'
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The rows of a `notice` list as one paragraph: each row's title, as
|
|
393
|
+
* sentences, after the optional `lead` with `{total}` filled in.
|
|
394
|
+
*
|
|
395
|
+
* @return {string} The text.
|
|
396
|
+
* @spec openspec/changes/record-list-string-rows/specs/record-list-string-rows/spec.md#requirement-a-record-list-draws-rows-that-are-plain-strings
|
|
397
|
+
*/
|
|
398
|
+
noticeText() {
|
|
399
|
+
const parts = this.items.map((item) => item.title).filter((text) => text !== '')
|
|
400
|
+
const lead = typeof this.content?.lead === 'string' && this.content.lead !== '' ? this.withTotal(this.content.lead) : ''
|
|
401
|
+
return [lead, ...parts].filter(Boolean).join(' ')
|
|
402
|
+
},
|
|
403
|
+
|
|
337
404
|
presentation() {
|
|
338
405
|
const value = this.content?.presentation
|
|
339
406
|
return PRESENTATIONS.includes(value) ? value : 'rows'
|
|
@@ -421,7 +488,8 @@ export default {
|
|
|
421
488
|
items() {
|
|
422
489
|
const c = this.content || {}
|
|
423
490
|
const barScale = this.barScale
|
|
424
|
-
return this.rows.slice(0, this.limit).map((
|
|
491
|
+
return this.rows.slice(0, this.limit).map((raw, index) => {
|
|
492
|
+
const row = scalarRow(raw)
|
|
425
493
|
const link = this.rowLink(row)
|
|
426
494
|
return {
|
|
427
495
|
key: (row && (row.id || row['@self']?.id)) || `row-${index}`,
|
|
@@ -430,7 +498,7 @@ export default {
|
|
|
430
498
|
tag: link ? 'router-link' : 'div',
|
|
431
499
|
attrs: link ? { to: link } : {},
|
|
432
500
|
initials: this.initialsFor(row),
|
|
433
|
-
title: this.fill(c.title, row),
|
|
501
|
+
title: this.fill(c.title || (row !== raw ? '{value}' : ''), row),
|
|
434
502
|
meta: this.metaOf(row),
|
|
435
503
|
text: this.fill(c.text, row),
|
|
436
504
|
label: this.fill(c.label, row),
|
|
@@ -638,13 +706,22 @@ export default {
|
|
|
638
706
|
},
|
|
639
707
|
|
|
640
708
|
/**
|
|
641
|
-
*
|
|
709
|
+
* Fill a translated label's counts: `{total}` is the list's total, and
|
|
710
|
+
* `{count:<field>=<value>}` the loaded rows whose field reads that
|
|
711
|
+
* value ("{count:status=done} van {total} stappen klaar"). The count
|
|
712
|
+
* reads the rows the list loaded, so it is exact when `limit` covers
|
|
713
|
+
* the list.
|
|
642
714
|
*
|
|
643
715
|
* @param {string} label The label.
|
|
644
716
|
* @return {string} The text.
|
|
645
717
|
*/
|
|
646
718
|
withTotal(label) {
|
|
647
|
-
return this.translate(label)
|
|
719
|
+
return this.translate(label)
|
|
720
|
+
.replace(/\{total\}/g, String(this.total))
|
|
721
|
+
.replace(/\{count:([^=}]+)=([^}]*)\}/g, (match, field, value) => {
|
|
722
|
+
const wanted = value.trim()
|
|
723
|
+
return String(this.rows.filter((row) => String(readPath(row, field.trim())) === wanted).length)
|
|
724
|
+
})
|
|
648
725
|
},
|
|
649
726
|
|
|
650
727
|
/**
|
|
@@ -1125,6 +1202,36 @@ export default {
|
|
|
1125
1202
|
margin: 0;
|
|
1126
1203
|
}
|
|
1127
1204
|
|
|
1205
|
+
.cn-record-list__notice {
|
|
1206
|
+
display: flex;
|
|
1207
|
+
gap: 10px;
|
|
1208
|
+
align-items: flex-start;
|
|
1209
|
+
padding: 12px 14px;
|
|
1210
|
+
border-radius: var(--border-radius-large, 10px);
|
|
1211
|
+
background: var(--color-primary-element-light, var(--color-background-dark));
|
|
1212
|
+
color: var(--color-main-text);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
.cn-record-list__notice--warning {
|
|
1216
|
+
background: var(--color-warning-light, rgba(255, 196, 0, 0.12));
|
|
1217
|
+
color: var(--color-warning-text, var(--color-main-text));
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
.cn-record-list__notice--error {
|
|
1221
|
+
background: var(--color-error-light, rgba(219, 0, 0, 0.08));
|
|
1222
|
+
color: var(--color-error-text, var(--color-main-text));
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.cn-record-list__notice-icon {
|
|
1226
|
+
flex: 0 0 auto;
|
|
1227
|
+
margin-top: 1px;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.cn-record-list__notice-text {
|
|
1231
|
+
margin: 0;
|
|
1232
|
+
line-height: 1.5;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1128
1235
|
.cn-record-list__loading {
|
|
1129
1236
|
display: flex;
|
|
1130
1237
|
justify-content: center;
|