@conduction/nextcloud-vue 2.76.0 → 2.78.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/CnAiCompanion/CnAiCompanion.vue2.js +23 -0
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +18 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +49 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +79 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +229 -203
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +37 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +2 -2
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -4
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +48 -6
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue.js +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js +45 -2
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +16 -28
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -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/CnFormPage/CnFormPage.vue2.js +4 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +104 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +13 -9
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +36 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +35 -23
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js +6 -2
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.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/useFormDraft.js +11 -1
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -0
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useObjectPresence.js +15 -4
- package/dist/esm/composables/useObjectPresence.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +32 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +32 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +500 -75
- package/dist/esm/store/plugins/interactions.js +20 -10
- package/dist/esm/store/plugins/interactions.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +17 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/badgeVariants.js +32 -0
- package/dist/esm/utils/badgeVariants.js.map +1 -0
- package/dist/esm/utils/builtInFormatters.js +15 -7
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/groupAutocomplete.js +63 -1
- package/dist/esm/utils/groupAutocomplete.js.map +1 -1
- package/dist/esm/utils/lensAvailability.js +90 -0
- package/dist/esm/utils/lensAvailability.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +13 -6
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/recordInteractions.js +31 -13
- package/dist/esm/utils/recordInteractions.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +46 -3
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/schema.js +1 -1
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +4 -4
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +46 -1
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +4389 -2890
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +500 -75
- package/l10n/en.json +32 -1
- package/l10n/nl.json +32 -1
- package/package.json +1 -1
- package/src/components/CnAiCompanion/CnAiCompanion.vue +23 -0
- package/src/components/CnAppNav/CnAppNav.vue +44 -5
- package/src/components/CnBoardView/CnBoardView.vue +2 -1
- package/src/components/CnCellRenderer/CnCellRenderer.md +1 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +106 -9
- package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +39 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +29 -4
- package/src/components/CnDataTable/CnDataTable.md +9 -0
- package/src/components/CnDataTable/CnDataTable.vue +87 -22
- package/src/components/CnDateRangePicker/CnDateRangePicker.vue +44 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +10 -27
- package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
- package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
- package/src/components/CnFormDialog/CnFormDialog.vue +7 -1
- package/src/components/CnFormPage/CnFormPage.vue +4 -1
- package/src/components/CnIndexPage/CnIndexPage.md +36 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +112 -19
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
- package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +52 -2
- package/src/components/CnStatusBadge/CnStatusBadge.vue +6 -2
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/composables/__tests__/useObjectPresence.spec.js +24 -0
- package/src/composables/useFormDraft.js +11 -1
- package/src/composables/useListView.js +4 -0
- package/src/composables/useObjectPresence.js +15 -4
- package/src/css/badge.css +31 -0
- package/src/css/kpi-card.css +51 -0
- package/src/css/look-board-detail.css +40 -2
- package/src/css/look-board-index.css +36 -0
- package/src/css/look-board.css +8 -0
- package/src/css/table.css +82 -17
- package/src/index.js +2 -1
- package/src/schemas/app-manifest-v2.schema.json +15 -11
- package/src/store/plugins/interactions.js +20 -10
- package/src/store/useObjectStore.js +17 -0
- package/src/utils/badgeVariants.js +29 -0
- package/src/utils/builtInFormatters.js +15 -7
- package/src/utils/groupAutocomplete.js +75 -0
- package/src/utils/index.js +1 -1
- package/src/utils/lensAvailability.js +86 -0
- package/src/utils/personalLenses.js +13 -6
- package/src/utils/recordInteractions.js +31 -13
- package/src/utils/resolveFilterTokens.js +45 -2
- package/src/utils/schema.js +1 -1
- package/src/utils/sentinelTokens.js +4 -4
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/widgetVisibility.js +45 -0
|
@@ -7,10 +7,29 @@
|
|
|
7
7
|
class="cn-files-browser"
|
|
8
8
|
:class="{ 'cn-files-browser--dragging': dragging }"
|
|
9
9
|
data-testid="cn-files-browser"
|
|
10
|
-
@dragenter.prevent="
|
|
11
|
-
@dragover.prevent="
|
|
12
|
-
@dragleave.prevent="
|
|
10
|
+
@dragenter.prevent="onDragEnter"
|
|
11
|
+
@dragover.prevent="onDragOver"
|
|
12
|
+
@dragleave.prevent="onDragLeave"
|
|
13
13
|
@drop.prevent="onDrop">
|
|
14
|
+
<!-- The drop state, drawn on the browser itself when the host names it
|
|
15
|
+
(`dropOverlay`): a dashed frame and one centred line over the list,
|
|
16
|
+
so it is plain where the files go. The overlay is decoration for
|
|
17
|
+
sighted users; the live region below says the same thing to a
|
|
18
|
+
screen reader, and the upload button stays the way in for anyone
|
|
19
|
+
who cannot drag. -->
|
|
20
|
+
<div v-if="dropOverlay && dragging"
|
|
21
|
+
class="cn-files-browser__drop-overlay"
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
data-testid="cn-files-browser-drop-overlay">
|
|
24
|
+
<span class="cn-files-browser__drop-icon"><Upload :size="28" /></span>
|
|
25
|
+
<strong class="cn-files-browser__drop-label">{{ dropLabel }}</strong>
|
|
26
|
+
</div>
|
|
27
|
+
<p v-if="dropOverlay"
|
|
28
|
+
class="hidden-visually"
|
|
29
|
+
aria-live="polite"
|
|
30
|
+
data-testid="cn-files-browser-drop-status">
|
|
31
|
+
{{ dropStatus }}
|
|
32
|
+
</p>
|
|
14
33
|
<!-- The bar: where you are, and what you can add. The crumbs are the
|
|
15
34
|
Files app's own breadcrumb component; the New menu lists what the
|
|
16
35
|
Files app and its plugins registered for this folder (a new folder,
|
|
@@ -56,7 +75,7 @@
|
|
|
56
75
|
:menuName="newLabel"
|
|
57
76
|
:forceMenu="true"
|
|
58
77
|
:forceName="true"
|
|
59
|
-
variant="primary"
|
|
78
|
+
:variant="uploadButton ? 'secondary' : 'primary'"
|
|
60
79
|
:disabled="folder === null"
|
|
61
80
|
data-testid="cn-files-browser-new">
|
|
62
81
|
<template #icon>
|
|
@@ -94,6 +113,19 @@
|
|
|
94
113
|
{{ action.label }}
|
|
95
114
|
</NcActionButton>
|
|
96
115
|
</NcActions>
|
|
116
|
+
<!-- A plain upload as its own button, when the host names it
|
|
117
|
+
(`uploadButton`). It takes the primary place and the New
|
|
118
|
+
menu steps back beside it. -->
|
|
119
|
+
<NcButton v-if="uploadButton"
|
|
120
|
+
variant="primary"
|
|
121
|
+
:disabled="folder === null"
|
|
122
|
+
data-testid="cn-files-browser-upload-button"
|
|
123
|
+
@click="pickFiles">
|
|
124
|
+
<template #icon>
|
|
125
|
+
<Upload :size="20" />
|
|
126
|
+
</template>
|
|
127
|
+
{{ uploadButtonLabel }}
|
|
128
|
+
</NcButton>
|
|
97
129
|
</div>
|
|
98
130
|
|
|
99
131
|
<!-- `:ref` (dynamic), not `ref`: a static ref on a vnode with only static
|
|
@@ -344,6 +376,13 @@
|
|
|
344
376
|
</tbody>
|
|
345
377
|
</table>
|
|
346
378
|
|
|
379
|
+
<p v-if="dropHint && !loading && error === ''"
|
|
380
|
+
class="cn-files-browser__drop-hint"
|
|
381
|
+
data-testid="cn-files-browser-drop-hint">
|
|
382
|
+
<Upload :size="16" />
|
|
383
|
+
{{ dropHintLabel }}
|
|
384
|
+
</p>
|
|
385
|
+
|
|
347
386
|
<NcDialog
|
|
348
387
|
v-if="renaming !== null"
|
|
349
388
|
:name="renameLabel"
|
|
@@ -443,7 +482,7 @@ import { getClient, getDefaultPropfind, getRemoteURL, getRootPath, resultToNode
|
|
|
443
482
|
* copy read `window._nc_fileactions`, which the server's plugins never write,
|
|
444
483
|
* and saw nothing while the page held thirty actions.
|
|
445
484
|
*/
|
|
446
|
-
import { translate as t } from '@nextcloud/l10n'
|
|
485
|
+
import { translatePlural as n, translate as t } from '@nextcloud/l10n'
|
|
447
486
|
import { generateUrl } from '@nextcloud/router'
|
|
448
487
|
import { NcActionButton, NcActionCheckbox, NcActionLink, NcActions, NcActionSeparator, NcBreadcrumb, NcBreadcrumbs, NcButton, NcDateTime, NcDialog, NcIconSvgWrapper, NcLoadingIcon, NcProgressBar, NcTextField } from '@nextcloud/vue'
|
|
449
488
|
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
@@ -624,6 +663,51 @@ export default {
|
|
|
624
663
|
default: () => t('nextcloud-vue', 'Show in Files'),
|
|
625
664
|
},
|
|
626
665
|
|
|
666
|
+
/**
|
|
667
|
+
* Show a primary upload button beside the New menu, which then steps
|
|
668
|
+
* back to a secondary button. Off (the default) keeps the upload
|
|
669
|
+
* inside the New menu only.
|
|
670
|
+
*/
|
|
671
|
+
uploadButton: {
|
|
672
|
+
type: Boolean,
|
|
673
|
+
default: false,
|
|
674
|
+
},
|
|
675
|
+
|
|
676
|
+
/** Label of the upload button `uploadButton` shows. */
|
|
677
|
+
uploadButtonLabel: {
|
|
678
|
+
type: String,
|
|
679
|
+
default: () => t('nextcloud-vue', 'Add files'),
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Draw the drop state over the browser while files are dragged onto
|
|
684
|
+
* it (a dashed frame and `dropLabel`, centred) and announce it to
|
|
685
|
+
* screen readers. Off (the default) keeps the plain dashed outline and
|
|
686
|
+
* announces nothing.
|
|
687
|
+
*/
|
|
688
|
+
dropOverlay: {
|
|
689
|
+
type: Boolean,
|
|
690
|
+
default: false,
|
|
691
|
+
},
|
|
692
|
+
|
|
693
|
+
/** The line `dropOverlay` draws and announces. */
|
|
694
|
+
dropLabel: {
|
|
695
|
+
type: String,
|
|
696
|
+
default: () => t('nextcloud-vue', 'Drop to add'),
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
/** Show a line under the list saying files can be dropped on it. */
|
|
700
|
+
dropHint: {
|
|
701
|
+
type: Boolean,
|
|
702
|
+
default: false,
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
/** The line `dropHint` shows. */
|
|
706
|
+
dropHintLabel: {
|
|
707
|
+
type: String,
|
|
708
|
+
default: () => t('nextcloud-vue', 'Or drag files onto this list.'),
|
|
709
|
+
},
|
|
710
|
+
|
|
627
711
|
/** Title of the empty state. */
|
|
628
712
|
emptyLabel: {
|
|
629
713
|
type: String,
|
|
@@ -726,6 +810,10 @@ export default {
|
|
|
726
810
|
*/
|
|
727
811
|
visibleColumnKeys: null,
|
|
728
812
|
dragging: false,
|
|
813
|
+
/** Nested dragenter/dragleave pairs, so a child does not end the drag. */
|
|
814
|
+
dragDepth: 0,
|
|
815
|
+
/** What the live region says: the drop label, then what was added. */
|
|
816
|
+
dropStatus: '',
|
|
729
817
|
uploads: [],
|
|
730
818
|
previewFailed: {},
|
|
731
819
|
newFolderOpen: false,
|
|
@@ -1265,12 +1353,76 @@ export default {
|
|
|
1265
1353
|
*/
|
|
1266
1354
|
async onDrop(event) {
|
|
1267
1355
|
this.dragging = false
|
|
1356
|
+
this.dragDepth = 0
|
|
1357
|
+
this.dropStatus = ''
|
|
1268
1358
|
const files = event.dataTransfer?.files
|
|
1269
1359
|
if (files?.length) {
|
|
1270
1360
|
await this.uploadFiles(files)
|
|
1361
|
+
if (this.dropOverlay) {
|
|
1362
|
+
this.dropStatus = n('nextcloud-vue', '%n file added', '%n files added', files.length)
|
|
1363
|
+
}
|
|
1271
1364
|
}
|
|
1272
1365
|
},
|
|
1273
1366
|
|
|
1367
|
+
/**
|
|
1368
|
+
* Something is dragged into the browser: show the drop state when it
|
|
1369
|
+
* carries files, and say so once.
|
|
1370
|
+
*
|
|
1371
|
+
* @param {DragEvent} event The drag.
|
|
1372
|
+
* @return {void}
|
|
1373
|
+
*/
|
|
1374
|
+
onDragEnter(event) {
|
|
1375
|
+
if (!this.carriesFiles(event)) {
|
|
1376
|
+
return
|
|
1377
|
+
}
|
|
1378
|
+
this.dragDepth++
|
|
1379
|
+
if (!this.dragging) {
|
|
1380
|
+
this.dragging = true
|
|
1381
|
+
this.dropStatus = this.dropOverlay ? this.dropLabel : ''
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Keep the drop state while files move over the browser.
|
|
1387
|
+
*
|
|
1388
|
+
* @param {DragEvent} event The drag.
|
|
1389
|
+
* @return {void}
|
|
1390
|
+
*/
|
|
1391
|
+
onDragOver(event) {
|
|
1392
|
+
if (this.carriesFiles(event)) {
|
|
1393
|
+
this.dragging = true
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Something left the browser, or one of its children. The drop state
|
|
1399
|
+
* ends only when the drag has left the browser itself.
|
|
1400
|
+
*
|
|
1401
|
+
* @return {void}
|
|
1402
|
+
*/
|
|
1403
|
+
onDragLeave() {
|
|
1404
|
+
this.dragDepth = Math.max(0, this.dragDepth - 1)
|
|
1405
|
+
if (this.dragDepth === 0) {
|
|
1406
|
+
this.dragging = false
|
|
1407
|
+
this.dropStatus = ''
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Whether a drag carries files (not text or a link from the page).
|
|
1413
|
+
* A drag whose types cannot be read counts as files, as before.
|
|
1414
|
+
*
|
|
1415
|
+
* @param {DragEvent} event The drag.
|
|
1416
|
+
* @return {boolean}
|
|
1417
|
+
*/
|
|
1418
|
+
carriesFiles(event) {
|
|
1419
|
+
const types = event?.dataTransfer?.types
|
|
1420
|
+
if (!types) {
|
|
1421
|
+
return true
|
|
1422
|
+
}
|
|
1423
|
+
return Array.from(types).includes('Files')
|
|
1424
|
+
},
|
|
1425
|
+
|
|
1274
1426
|
/**
|
|
1275
1427
|
* PUT each file into the open folder over DAV, with a progress row
|
|
1276
1428
|
* each, then re-read the folder once.
|
|
@@ -1660,6 +1812,7 @@ export default {
|
|
|
1660
1812
|
flex-direction: column;
|
|
1661
1813
|
gap: 8px;
|
|
1662
1814
|
min-width: 0;
|
|
1815
|
+
position: relative;
|
|
1663
1816
|
border-radius: var(--border-radius-large, 8px);
|
|
1664
1817
|
}
|
|
1665
1818
|
|
|
@@ -1668,6 +1821,52 @@ export default {
|
|
|
1668
1821
|
outline-offset: 4px;
|
|
1669
1822
|
}
|
|
1670
1823
|
|
|
1824
|
+
.cn-files-browser__drop-overlay {
|
|
1825
|
+
position: absolute;
|
|
1826
|
+
inset: 0;
|
|
1827
|
+
z-index: 2;
|
|
1828
|
+
display: flex;
|
|
1829
|
+
flex-direction: column;
|
|
1830
|
+
align-items: center;
|
|
1831
|
+
justify-content: center;
|
|
1832
|
+
gap: 10px;
|
|
1833
|
+
padding: 24px;
|
|
1834
|
+
box-sizing: border-box;
|
|
1835
|
+
border: 2px dashed var(--color-primary-element);
|
|
1836
|
+
border-radius: var(--border-radius-large, 8px);
|
|
1837
|
+
background: var(--color-primary-element-light);
|
|
1838
|
+
color: var(--color-primary-element-light-text, var(--color-main-text));
|
|
1839
|
+
text-align: center;
|
|
1840
|
+
pointer-events: none;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
.cn-files-browser__drop-icon {
|
|
1844
|
+
display: flex;
|
|
1845
|
+
align-items: center;
|
|
1846
|
+
justify-content: center;
|
|
1847
|
+
width: 56px;
|
|
1848
|
+
height: 56px;
|
|
1849
|
+
border-radius: 50%;
|
|
1850
|
+
background: var(--color-main-background);
|
|
1851
|
+
color: var(--color-primary-element);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
.cn-files-browser__drop-label {
|
|
1855
|
+
font-size: 20px;
|
|
1856
|
+
font-weight: 700;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
.cn-files-browser__drop-hint {
|
|
1860
|
+
display: flex;
|
|
1861
|
+
align-items: center;
|
|
1862
|
+
gap: 8px;
|
|
1863
|
+
margin: 0;
|
|
1864
|
+
padding-top: 12px;
|
|
1865
|
+
border-top: 1px solid var(--color-border);
|
|
1866
|
+
font-size: 13px;
|
|
1867
|
+
color: var(--color-text-maxcontrast);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1671
1870
|
.cn-files-browser__bar {
|
|
1672
1871
|
display: flex;
|
|
1673
1872
|
align-items: center;
|
|
@@ -3,22 +3,41 @@
|
|
|
3
3
|
- SPDX-License-Identifier: EUPL-1.2
|
|
4
4
|
-->
|
|
5
5
|
<template>
|
|
6
|
-
<span class="cn-follow-toggle"
|
|
6
|
+
<span class="cn-follow-toggle"
|
|
7
|
+
:class="{ 'cn-follow-toggle--compact': compact }"
|
|
8
|
+
data-testid="cn-follow-toggle">
|
|
7
9
|
<NcButton
|
|
8
|
-
:variant="state ? 'secondary' : 'tertiary'"
|
|
10
|
+
:variant="state && !compact ? 'secondary' : 'tertiary'"
|
|
9
11
|
:aria-pressed="state ? 'true' : 'false'"
|
|
12
|
+
:aria-label="compact ? (state ? followingLabel : followLabel) : undefined"
|
|
10
13
|
:title="tooltip"
|
|
11
14
|
:disabled="busy"
|
|
12
15
|
data-testid="cn-follow-toggle-button"
|
|
13
16
|
@click.stop="toggle">
|
|
14
17
|
<template #icon>
|
|
15
|
-
<
|
|
16
|
-
<
|
|
18
|
+
<Eye v-if="state" :size="20" class="cn-follow-toggle__on" />
|
|
19
|
+
<EyeOutline v-else :size="20" />
|
|
20
|
+
</template>
|
|
21
|
+
<template v-if="!compact">
|
|
22
|
+
{{ state ? followingLabel : followLabel }}
|
|
23
|
+
</template>
|
|
24
|
+
</NcButton>
|
|
25
|
+
<NcButton
|
|
26
|
+
v-if="showNotifySwitch"
|
|
27
|
+
variant="tertiary"
|
|
28
|
+
:aria-pressed="notifyState ? 'true' : 'false'"
|
|
29
|
+
:aria-label="notifyLabel"
|
|
30
|
+
:title="notifyLabel"
|
|
31
|
+
:disabled="busy"
|
|
32
|
+
data-testid="cn-follow-toggle-notify"
|
|
33
|
+
@click.stop="toggleNotify">
|
|
34
|
+
<template #icon>
|
|
35
|
+
<Bell v-if="notifyState" :size="20" />
|
|
36
|
+
<BellOffOutline v-else :size="20" />
|
|
17
37
|
</template>
|
|
18
|
-
{{ state ? followingLabel : followLabel }}
|
|
19
38
|
</NcButton>
|
|
20
39
|
<NcPopover
|
|
21
|
-
v-if="count !== null"
|
|
40
|
+
v-if="count !== null && !compact"
|
|
22
41
|
v-model:shown="open"
|
|
23
42
|
placement="bottom-start">
|
|
24
43
|
<template #trigger>
|
|
@@ -82,15 +101,26 @@
|
|
|
82
101
|
import { translate as t } from '@nextcloud/l10n'
|
|
83
102
|
import { NcAvatar, NcButton, NcLoadingIcon, NcPopover, NcSelect } from '@nextcloud/vue'
|
|
84
103
|
import Bell from 'vue-material-design-icons/Bell.vue'
|
|
85
|
-
import
|
|
104
|
+
import BellOffOutline from 'vue-material-design-icons/BellOffOutline.vue'
|
|
86
105
|
import Close from 'vue-material-design-icons/Close.vue'
|
|
106
|
+
import Eye from 'vue-material-design-icons/Eye.vue'
|
|
107
|
+
import EyeOutline from 'vue-material-design-icons/EyeOutline.vue'
|
|
87
108
|
import { patchStoredSelf } from '../../utils/patchStoredSelf.js'
|
|
88
109
|
import { listWatchers, setWatcher, setWatching } from '../../utils/recordInteractions.js'
|
|
89
110
|
import { searchNextcloudUsers } from '../../utils/userAutocomplete.js'
|
|
90
111
|
|
|
91
112
|
/**
|
|
92
|
-
* CnFollowToggle —
|
|
93
|
-
*
|
|
113
|
+
* CnFollowToggle — the one Follow control of a record: a Follow toggle bound to
|
|
114
|
+
* `@self.watching`, a notifications switch bound to `@self.watchNotify`, the
|
|
115
|
+
* follower count and a followers popover for users who may see them.
|
|
116
|
+
*
|
|
117
|
+
* Following and favourites are one feature (OpenRegister's
|
|
118
|
+
* `merge-follow-and-favourites`): a favourite is a follow with notifications
|
|
119
|
+
* off, so there is no separate star. While the user follows, a bell beside the
|
|
120
|
+
* toggle turns the follow's notifications on or off (`PUT .../watch` with
|
|
121
|
+
* `{"notify": bool}`). With `notifies` false (the register sends no change
|
|
122
|
+
* notifications) the bell is not offered. `compact` renders the toggle as an
|
|
123
|
+
* icon only, without the bell, count or popover, for a column in a list.
|
|
94
124
|
*
|
|
95
125
|
* A click flips the toggle at once and sends `PUT` or `DELETE .../watch`; on
|
|
96
126
|
* failure it flips back and shows the server's message, and a 404 says the
|
|
@@ -105,13 +135,14 @@ import { searchNextcloudUsers } from '../../utils/userAutocomplete.js'
|
|
|
105
135
|
* Example:
|
|
106
136
|
* ```vue
|
|
107
137
|
* <CnFollowToggle register="pipelinq" schema="ticket" :object-id="id"
|
|
108
|
-
* :watching="self.watching" :
|
|
138
|
+
* :watching="self.watching" :notify="self.watchNotify"
|
|
139
|
+
* :watcher-count="self.watcherCount" :can-manage="self.can && self.can.manage" />
|
|
109
140
|
* ```
|
|
110
141
|
*/
|
|
111
142
|
export default {
|
|
112
143
|
name: 'CnFollowToggle',
|
|
113
144
|
|
|
114
|
-
components: { Bell,
|
|
145
|
+
components: { Bell, BellOffOutline, Close, Eye, EyeOutline, NcAvatar, NcButton, NcLoadingIcon, NcPopover, NcSelect },
|
|
115
146
|
|
|
116
147
|
props: {
|
|
117
148
|
/** Register slug of the record. */
|
|
@@ -138,6 +169,21 @@ export default {
|
|
|
138
169
|
default: false,
|
|
139
170
|
},
|
|
140
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Whether the user's follow notifies them (`@self.watchNotify`). Null
|
|
174
|
+
* (an older server, or not following) reads as on.
|
|
175
|
+
*/
|
|
176
|
+
notify: {
|
|
177
|
+
type: Boolean,
|
|
178
|
+
default: null,
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/** Icon only, without the bell, count or popover: for a column in a list. */
|
|
182
|
+
compact: {
|
|
183
|
+
type: Boolean,
|
|
184
|
+
default: false,
|
|
185
|
+
},
|
|
186
|
+
|
|
141
187
|
/** Number of followers (`@self.watcherCount`). Null hides the count and the popover. */
|
|
142
188
|
watcherCount: {
|
|
143
189
|
type: Number,
|
|
@@ -168,6 +214,7 @@ export default {
|
|
|
168
214
|
data() {
|
|
169
215
|
return {
|
|
170
216
|
state: this.watching,
|
|
217
|
+
notifyState: this.notify !== false,
|
|
171
218
|
count: this.watcherCount,
|
|
172
219
|
busy: false,
|
|
173
220
|
open: false,
|
|
@@ -196,6 +243,21 @@ export default {
|
|
|
196
243
|
return t('nextcloud-vue', 'Add a colleague')
|
|
197
244
|
},
|
|
198
245
|
|
|
246
|
+
notifyLabel() {
|
|
247
|
+
return this.notifyState
|
|
248
|
+
? t('nextcloud-vue', 'Turn notifications off')
|
|
249
|
+
: t('nextcloud-vue', 'Turn notifications on')
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Whether the bell renders: following, not compact, and the register notifies.
|
|
254
|
+
*
|
|
255
|
+
* @return {boolean}
|
|
256
|
+
*/
|
|
257
|
+
showNotifySwitch() {
|
|
258
|
+
return this.state && !this.compact && this.notifies
|
|
259
|
+
},
|
|
260
|
+
|
|
199
261
|
followersLabel() {
|
|
200
262
|
return t('nextcloud-vue', '{count} followers', { count: this.count })
|
|
201
263
|
},
|
|
@@ -216,6 +278,10 @@ export default {
|
|
|
216
278
|
this.state = value
|
|
217
279
|
},
|
|
218
280
|
|
|
281
|
+
notify(value) {
|
|
282
|
+
this.notifyState = value !== false
|
|
283
|
+
},
|
|
284
|
+
|
|
219
285
|
watcherCount(value) {
|
|
220
286
|
this.count = value
|
|
221
287
|
},
|
|
@@ -272,12 +338,47 @@ export default {
|
|
|
272
338
|
}
|
|
273
339
|
return
|
|
274
340
|
}
|
|
341
|
+
// A new follow notifies (the server's default); an unfollow has no switch.
|
|
342
|
+
this.notifyState = this.state
|
|
275
343
|
patchStoredSelf(this.register, this.schema, this.objectId, {
|
|
276
344
|
watching: this.state,
|
|
345
|
+
favourite: this.state,
|
|
346
|
+
watchNotify: this.state ? true : undefined,
|
|
277
347
|
...(this.count !== null ? { watcherCount: this.count } : {}),
|
|
278
348
|
})
|
|
279
|
-
/** @event change Emitted after a successful call. Payload: `{ watching, count }`. */
|
|
280
|
-
this.$emit('change', { watching: this.state, count: this.count })
|
|
349
|
+
/** @event change Emitted after a successful call. Payload: `{ watching, notify, count }`. */
|
|
350
|
+
this.$emit('change', { watching: this.state, notify: this.state ? this.notifyState : null, count: this.count })
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Turn the follow's notifications on or off, optimistically.
|
|
355
|
+
*
|
|
356
|
+
* @return {Promise<void>}
|
|
357
|
+
* @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-one-follow-control-with-a-notifications-switch
|
|
358
|
+
*/
|
|
359
|
+
async toggleNotify() {
|
|
360
|
+
if (this.busy || !this.state) {
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
const previous = this.notifyState
|
|
364
|
+
this.notifyState = !previous
|
|
365
|
+
this.busy = true
|
|
366
|
+
const result = await setWatching(this.register, this.schema, this.objectId, true, { notify: this.notifyState })
|
|
367
|
+
this.busy = false
|
|
368
|
+
if (!result.ok) {
|
|
369
|
+
this.notifyState = previous
|
|
370
|
+
const message = result.status === 404
|
|
371
|
+
? t('nextcloud-vue', 'You can no longer see this record.')
|
|
372
|
+
: (result.message || t('nextcloud-vue', 'Could not change notifications.'))
|
|
373
|
+
import('@nextcloud/dialogs').then(({ showError }) => showError(message)).catch(() => {})
|
|
374
|
+
this.$emit('error', message)
|
|
375
|
+
if (result.status === 404) {
|
|
376
|
+
this.$emit('not-found')
|
|
377
|
+
}
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
patchStoredSelf(this.register, this.schema, this.objectId, { watchNotify: this.notifyState })
|
|
381
|
+
this.$emit('change', { watching: true, notify: this.notifyState, count: this.count })
|
|
281
382
|
},
|
|
282
383
|
|
|
283
384
|
async loadWatchers() {
|
|
@@ -340,6 +441,10 @@ export default {
|
|
|
340
441
|
gap: 4px;
|
|
341
442
|
}
|
|
342
443
|
|
|
444
|
+
.cn-follow-toggle__on {
|
|
445
|
+
color: var(--color-primary-element);
|
|
446
|
+
}
|
|
447
|
+
|
|
343
448
|
.cn-follow-toggle__popover {
|
|
344
449
|
min-width: 260px;
|
|
345
450
|
padding: 12px;
|
|
@@ -1466,11 +1466,17 @@ export default {
|
|
|
1466
1466
|
|
|
1467
1467
|
/**
|
|
1468
1468
|
* What the draft indicator announces, or '' when it says nothing.
|
|
1469
|
+
* While the dialog shows a failed save it never says Saved: it says
|
|
1470
|
+
* the draft is kept on this device, so it cannot be read as the
|
|
1471
|
+
* server save that just failed.
|
|
1469
1472
|
*
|
|
1470
1473
|
* @return {string} The text.
|
|
1474
|
+
* @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-the-draft-indicator-never-reads-as-a-server-save-after-a-failed-save
|
|
1471
1475
|
*/
|
|
1472
1476
|
draftIndicatorLabel() {
|
|
1473
|
-
|
|
1477
|
+
const saveFailed = this.result !== null && typeof this.result === 'object'
|
|
1478
|
+
&& (Boolean(this.result.error) || this.result.success === false)
|
|
1479
|
+
return draftIndicatorText(this.draftState, this.draftSavedAt, Date.now(), { saveFailed })
|
|
1474
1480
|
},
|
|
1475
1481
|
|
|
1476
1482
|
isCreateMode() {
|
|
@@ -954,11 +954,14 @@ export default {
|
|
|
954
954
|
|
|
955
955
|
/**
|
|
956
956
|
* What the draft indicator announces: Saving, then Saved with a relative time.
|
|
957
|
+
* After a failed submit it says the draft is kept on this device instead,
|
|
958
|
+
* so it cannot be read as the server save that just failed.
|
|
957
959
|
*
|
|
958
960
|
* @return {string} The text, or '' before anything is typed.
|
|
961
|
+
* @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-the-draft-indicator-never-reads-as-a-server-save-after-a-failed-save
|
|
959
962
|
*/
|
|
960
963
|
draftIndicatorLabel() {
|
|
961
|
-
return draftIndicatorText(this.draftState, this.draftSavedAt)
|
|
964
|
+
return draftIndicatorText(this.draftState, this.draftSavedAt, Date.now(), { saveFailed: Boolean(this.lastError) })
|
|
962
965
|
},
|
|
963
966
|
},
|
|
964
967
|
|
|
@@ -358,6 +358,8 @@ export default {
|
|
|
358
358
|
| `includeColumns` | Array | `null` | Column keys to show (whitelist); `null` means all |
|
|
359
359
|
| `columnOverrides` | Object | `{}` | Per-column config overrides in schema mode |
|
|
360
360
|
| `emptyText` | String | `'No items found'` | Text shown in the empty state |
|
|
361
|
+
| `lenses` | Object \| null | `null` | The personal-lens report of the list on screen (`@self.lenses` of the response) for a host that fetches itself. In self-fetch mode the page reads it from the store. See [When a lens cannot answer](#when-a-lens-cannot-answer). |
|
|
362
|
+
| `lensReasonTexts` | Object \| null | `null` | App wording for an unavailable lens, keyed `<lens>.<reason>` or `<reason>`. |
|
|
361
363
|
| `loadingText` | String | `'Loading…'` | Accessible label for the loading spinner (NcLoadingIcon aria-label) |
|
|
362
364
|
| `rowIcon` | String \| Function | `null` | Optional leading icon for every table row — a static MDI name or `(row) => iconName`. Forwarded to CnDataTable; fed from the manifest as `pages[].config.rowIcon`. Unset = no icon column. |
|
|
363
365
|
| `rowClass` | Function | `null` | Callback returning CSS class(es) for a row |
|
|
@@ -994,6 +996,40 @@ A person who has claimed no teams gets an empty list, and the tab says so
|
|
|
994
996
|
through `narrowsToNothing`. A lens labelled "my teams" that quietly showed
|
|
995
997
|
every team would be a label stating one rule while the fetch ran another.
|
|
996
998
|
|
|
999
|
+
### When a lens cannot answer
|
|
1000
|
+
|
|
1001
|
+
OpenRegister reports, for every personal lens a list asked for, whether it
|
|
1002
|
+
could answer (openregister#4514, needed for any effect):
|
|
1003
|
+
|
|
1004
|
+
```json
|
|
1005
|
+
{ "results": [], "@self": { "lenses": { "recent": { "available": false, "reason": "audit-trail-disabled" } } } }
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
When a lens reports `available: false`, the empty state says why instead of
|
|
1009
|
+
showing `emptyText`:
|
|
1010
|
+
|
|
1011
|
+
| Reason | Text |
|
|
1012
|
+
|---|---|
|
|
1013
|
+
| `audit-trail-disabled` | This server does not keep track of what you open. |
|
|
1014
|
+
| `anonymous` | Log in to see what you opened recently. |
|
|
1015
|
+
| `read-history-unavailable` | Your recent items are not available right now. |
|
|
1016
|
+
|
|
1017
|
+
The texts are object-neutral and translated (Dutch: "Deze server houdt niet
|
|
1018
|
+
bij welke items je opent.", "Log in om te zien wat je onlangs opende.", "Je
|
|
1019
|
+
recente items zijn nu niet beschikbaar."). Say it in the app's own words with
|
|
1020
|
+
`lensReasonTexts`:
|
|
1021
|
+
|
|
1022
|
+
```html
|
|
1023
|
+
<CnIndexPage :lens-reason-texts="{ 'recent.audit-trail-disabled': t('dossiq', 'This server does not keep track of which cases you open.') }" />
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
A response without `@self.lenses`, a lens that is available, or a reason with
|
|
1027
|
+
no text keeps `emptyText`, and an `#empty` slot still wins. The lens name is a
|
|
1028
|
+
key, so another lens (`watching`) that starts reporting needs only text. The
|
|
1029
|
+
store keeps the report as `lenses[type]` and `useListView` exposes it as
|
|
1030
|
+
`lenses`; `readLensReports` and `lensUnavailableText` are in
|
|
1031
|
+
`src/utils/lensAvailability.js`.
|
|
1032
|
+
|
|
997
1033
|
### Row actions and indicators
|
|
998
1034
|
|
|
999
1035
|
See the two sections below. Row actions are the intersection of what the page
|