@conduction/nextcloud-vue 2.139.0 → 2.140.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/CnAppRoot/CnAppRoot.vue2.js +9 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +2 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +52 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +23 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +106 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +30 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +59 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +44 -34
- 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 +28 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +7 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -3
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +12 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +23 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +43 -13
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +5 -2
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useTaskInboxStore.js +4 -3
- package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -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 +466 -371
- package/dist/esm/utils/builtInFormatters.js +23 -1
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/myTeam.js +24 -4
- package/dist/esm/utils/myTeam.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +5 -4
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/transitionInputs.js +43 -2
- package/dist/esm/utils/transitionInputs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +771 -222
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +466 -371
- package/l10n/en.json +2 -1
- package/l10n/nl.json +5 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +9 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -3
- package/src/components/CnChartWidget/CnChartWidget.md +1 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +72 -1
- package/src/components/CnConversationThread/CnConversationWidget.vue +126 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +72 -5
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +28 -1
- package/src/components/CnFilesBrowser/filesBrowser.js +7 -1
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +30 -2
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +28 -3
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +10 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +68 -2
- package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +43 -1
- package/src/composables/indexSources.js +5 -2
- package/src/composables/useTaskInboxStore.js +4 -3
- package/src/css/look-board.css +10 -2
- package/src/css/table.css +13 -0
- package/src/utils/builtInFormatters.js +22 -0
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/myTeam.js +24 -4
- package/src/utils/resolveFilterTokens.js +5 -4
- package/src/utils/transitionInputs.js +42 -1
|
@@ -4670,6 +4670,8 @@ export default {
|
|
|
4670
4670
|
savedViews: [],
|
|
4671
4671
|
savedViewsLoading: false,
|
|
4672
4672
|
showSaveViewDialog: false,
|
|
4673
|
+
/** Whether the reader changed the view since it was opened or saved. */
|
|
4674
|
+
viewChanged: false,
|
|
4673
4675
|
viewPendingDelete: null,
|
|
4674
4676
|
/** An `api-call` header action waiting on its confirmation, or null. */
|
|
4675
4677
|
headerApiCallPending: null,
|
|
@@ -8706,12 +8708,20 @@ export default {
|
|
|
8706
8708
|
|
|
8707
8709
|
/**
|
|
8708
8710
|
* Whether the toolbar draws Save view: not when the page switches it
|
|
8709
|
-
* off (`savedViewSave: false`) or its header carries one
|
|
8711
|
+
* off (`savedViewSave: false`) or its header carries one, and in the
|
|
8712
|
+
* board look only once the view has changed.
|
|
8710
8713
|
*
|
|
8714
|
+
* @spec openspec/changes/board-save-view-only-when-changed/specs/saved-views/spec.md#requirement-the-board-look-offers-save-view-once-the-view-has-changed
|
|
8711
8715
|
* @return {boolean}
|
|
8712
8716
|
*/
|
|
8713
8717
|
toolbarSavesView() {
|
|
8714
|
-
|
|
8718
|
+
if (this.savedViewSave === false || this.headerSavesView) {
|
|
8719
|
+
return false
|
|
8720
|
+
}
|
|
8721
|
+
// The board look offers it once the view differs from the one
|
|
8722
|
+
// opened: changed by the reader, or, with no saved view open,
|
|
8723
|
+
// filtered by the address.
|
|
8724
|
+
return !this.isBoardLook || this.viewChanged || (!this.appliedSavedViewId && this.activeFilterChips.length > 0)
|
|
8715
8725
|
},
|
|
8716
8726
|
|
|
8717
8727
|
/**
|
|
@@ -10849,6 +10859,7 @@ export default {
|
|
|
10849
10859
|
* @return {void}
|
|
10850
10860
|
*/
|
|
10851
10861
|
onSearchEvent(value) {
|
|
10862
|
+
this.viewChanged = true
|
|
10852
10863
|
if (this.namedSourceSearches) {
|
|
10853
10864
|
this.namedSearchTerm = value || ''
|
|
10854
10865
|
}
|
|
@@ -10868,6 +10879,7 @@ export default {
|
|
|
10868
10879
|
* @return {void}
|
|
10869
10880
|
*/
|
|
10870
10881
|
onQuickFilterChange(index) {
|
|
10882
|
+
this.viewChanged = true
|
|
10871
10883
|
// `activeQuickFilterIndex` is a setup-returned ref; the
|
|
10872
10884
|
// Vue 2 ref-unwrap proxy makes plain assignment work.
|
|
10873
10885
|
this.activeQuickFilterIndex = index
|
|
@@ -10884,6 +10896,7 @@ export default {
|
|
|
10884
10896
|
* @return {void}
|
|
10885
10897
|
*/
|
|
10886
10898
|
onQuickFilterMultiChange(indices) {
|
|
10899
|
+
this.viewChanged = true
|
|
10887
10900
|
this.selectedQuickFilterIndices = Array.isArray(indices) ? indices : []
|
|
10888
10901
|
this.$emit('quick-filter-change', this.selectedQuickFilterIndices)
|
|
10889
10902
|
},
|
|
@@ -10899,6 +10912,7 @@ export default {
|
|
|
10899
10912
|
* @return {void}
|
|
10900
10913
|
*/
|
|
10901
10914
|
onSidebarSortChange(keys, value) {
|
|
10915
|
+
this.viewChanged = true
|
|
10902
10916
|
if (value !== undefined && this.selectSortOption(value)) {
|
|
10903
10917
|
return
|
|
10904
10918
|
}
|
|
@@ -10978,6 +10992,7 @@ export default {
|
|
|
10978
10992
|
* @return {void}
|
|
10979
10993
|
*/
|
|
10980
10994
|
onSortEvent(payload) {
|
|
10995
|
+
this.viewChanged = true
|
|
10981
10996
|
// An order by keys (a column header, a field in the Sort section)
|
|
10982
10997
|
// leaves an endpoint-ranked order.
|
|
10983
10998
|
if (this.sortSource.value) {
|
|
@@ -11181,6 +11196,7 @@ export default {
|
|
|
11181
11196
|
* @return {void}
|
|
11182
11197
|
*/
|
|
11183
11198
|
onClearFilters() {
|
|
11199
|
+
this.viewChanged = true
|
|
11184
11200
|
// "Clear all" on the Active line also undoes the quick filters it
|
|
11185
11201
|
// listed (a tab without an `activeLabel` is not a filter there).
|
|
11186
11202
|
for (const chip of this.quickFilterActiveChips) {
|
|
@@ -11289,6 +11305,7 @@ export default {
|
|
|
11289
11305
|
* @spec openspec/changes/screens-board-reference-columns/specs/board-view/spec.md#requirement-a-board-can-switch-the-field-its-columns-come-from
|
|
11290
11306
|
*/
|
|
11291
11307
|
onBoardColumnFieldChange(field) {
|
|
11308
|
+
this.viewChanged = true
|
|
11292
11309
|
this.boardColumnFieldChoice = field
|
|
11293
11310
|
/**
|
|
11294
11311
|
* @event board-column-field-change The board's column switch picked another field. Payload: the field name.
|
|
@@ -11304,6 +11321,7 @@ export default {
|
|
|
11304
11321
|
* @return {void}
|
|
11305
11322
|
*/
|
|
11306
11323
|
onBoardNarrowChange(value) {
|
|
11324
|
+
this.viewChanged = true
|
|
11307
11325
|
this.onFilterEvent({ key: this.boardNarrow.field, values: value === '' ? [] : [value] })
|
|
11308
11326
|
},
|
|
11309
11327
|
|
|
@@ -11366,6 +11384,7 @@ export default {
|
|
|
11366
11384
|
* @return {void}
|
|
11367
11385
|
*/
|
|
11368
11386
|
onFilterEvent(payload) {
|
|
11387
|
+
this.viewChanged = true
|
|
11369
11388
|
if (this.isSelfFetchMode && typeof this.list.onFilterChange === 'function') {
|
|
11370
11389
|
this.list.onFilterChange(payload.key, payload.values)
|
|
11371
11390
|
this.persistViewStateToRoute(this.currentViewState())
|
|
@@ -11388,6 +11407,7 @@ export default {
|
|
|
11388
11407
|
* @spec openspec/changes/screens-index-list-parity/specs/index-list-board-look/spec.md#requirement-the-toolbar-sits-on-the-ground-in-two-rows
|
|
11389
11408
|
*/
|
|
11390
11409
|
onRemoveActiveFilter(chip) {
|
|
11410
|
+
this.viewChanged = true
|
|
11391
11411
|
if (chip && chip.searchTerm === true) {
|
|
11392
11412
|
this.onSearchEvent('')
|
|
11393
11413
|
return
|
|
@@ -12356,6 +12376,7 @@ export default {
|
|
|
12356
12376
|
* @return {void}
|
|
12357
12377
|
*/
|
|
12358
12378
|
onColumnsEvent(columns) {
|
|
12379
|
+
this.viewChanged = true
|
|
12359
12380
|
if (this.isSelfFetchMode && this.list.visibleColumns) {
|
|
12360
12381
|
this.list.visibleColumns.value = columns
|
|
12361
12382
|
}
|
|
@@ -12403,6 +12424,7 @@ export default {
|
|
|
12403
12424
|
* @return {void}
|
|
12404
12425
|
*/
|
|
12405
12426
|
onColumnsReorder(columns) {
|
|
12427
|
+
this.viewChanged = true
|
|
12406
12428
|
this.onColumnsEvent(columns)
|
|
12407
12429
|
},
|
|
12408
12430
|
|
|
@@ -12413,6 +12435,7 @@ export default {
|
|
|
12413
12435
|
* @return {void}
|
|
12414
12436
|
*/
|
|
12415
12437
|
onPinChange(count) {
|
|
12438
|
+
this.viewChanged = true
|
|
12416
12439
|
if (!this.personalColumns || Array.isArray(this.appliedViewColumns)) {
|
|
12417
12440
|
return
|
|
12418
12441
|
}
|
|
@@ -13316,6 +13339,10 @@ export default {
|
|
|
13316
13339
|
loadMyTeam().then(() => this.onApplySavedView(view, { keepSavedViewKey }))
|
|
13317
13340
|
return
|
|
13318
13341
|
}
|
|
13342
|
+
// An opened view is unchanged until the reader changes it.
|
|
13343
|
+
this.$nextTick(() => {
|
|
13344
|
+
this.viewChanged = false
|
|
13345
|
+
})
|
|
13319
13346
|
const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {}
|
|
13320
13347
|
state.filters = dropOptionalUnresolved(resolveFilterTokens(state.filters, ctx))
|
|
13321
13348
|
this.appliedViewFiltersKey = this.viewFiltersKey(state.filters)
|
|
@@ -13441,6 +13468,7 @@ export default {
|
|
|
13441
13468
|
}
|
|
13442
13469
|
this.savedViews = [...this.savedViews, view]
|
|
13443
13470
|
this.showSaveViewDialog = false
|
|
13471
|
+
this.viewChanged = false
|
|
13444
13472
|
this.sidebarActiveTab = 'filters-tab'
|
|
13445
13473
|
this.sidebarOpen = true
|
|
13446
13474
|
this.toastSavedView('success', t('nextcloud-vue', 'View "{name}" saved', { name }))
|
|
@@ -720,6 +720,7 @@ export default {
|
|
|
720
720
|
* - `hidden` (boolean) — Hide this property
|
|
721
721
|
* - `editable` (boolean) — Override editability (default: based on schema readOnly)
|
|
722
722
|
* - `label` (string) — Override the display label
|
|
723
|
+
* - `emptyValue` (string) — Words shown, translated, for an empty or false value instead of a dash
|
|
723
724
|
* - `widget` (string) — Override the widget type for editing
|
|
724
725
|
* - `formatter` (string) — Display the value through the formatter
|
|
725
726
|
* registered under this id on CnAppRoot (`cnFormatters`), called as
|
|
@@ -739,7 +740,8 @@ export default {
|
|
|
739
740
|
},
|
|
740
741
|
|
|
741
742
|
/**
|
|
742
|
-
* Number of grid columns.
|
|
743
|
+
* Number of grid columns. The board look fits 200px columns instead,
|
|
744
|
+
* except for one column, which it keeps.
|
|
743
745
|
*/
|
|
744
746
|
columns: {
|
|
745
747
|
type: Number,
|
|
@@ -1100,7 +1102,8 @@ export default {
|
|
|
1100
1102
|
// still showing the stored code — `submitted` under a Dutch
|
|
1101
1103
|
// label, beside a list that already read "Ingediend".
|
|
1102
1104
|
const enumLabel = this.enumDisplayLabel(field, raw)
|
|
1103
|
-
|
|
1105
|
+
const empty = this.emptyValueFor(field, raw)
|
|
1106
|
+
values[field.key] = empty !== null ? empty : (enumLabel !== null ? enumLabel : formatValue(raw, prop || {}))
|
|
1104
1107
|
}
|
|
1105
1108
|
return values
|
|
1106
1109
|
},
|
|
@@ -1116,8 +1119,12 @@ export default {
|
|
|
1116
1119
|
* CSS grid template for the container.
|
|
1117
1120
|
*/
|
|
1118
1121
|
gridStyle() {
|
|
1119
|
-
// The board look: as many 200px columns as the card has room for
|
|
1122
|
+
// The board look: as many 200px columns as the card has room for,
|
|
1123
|
+
// unless the config asks for one column (a stacked list of pairs).
|
|
1120
1124
|
if (normalizeLook(this.cnLook) === 'board') {
|
|
1125
|
+
if (this.columns === 1) {
|
|
1126
|
+
return { 'grid-template-columns': 'minmax(0, 1fr)' }
|
|
1127
|
+
}
|
|
1121
1128
|
return {
|
|
1122
1129
|
'grid-template-columns': 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
1123
1130
|
}
|
|
@@ -1842,6 +1849,24 @@ export default {
|
|
|
1842
1849
|
return this.isPlainObject(raw)
|
|
1843
1850
|
},
|
|
1844
1851
|
|
|
1852
|
+
/**
|
|
1853
|
+
* The words an override gives an empty or false value
|
|
1854
|
+
* (`overrides[key].emptyValue`, translated), or null to format it as usual.
|
|
1855
|
+
*
|
|
1856
|
+
* @param {object} field - Resolved field definition from resolvedFields
|
|
1857
|
+
* @param {*} raw - The field's value
|
|
1858
|
+
* @return {string|null}
|
|
1859
|
+
* @spec openspec/changes/data-widget-empty-value-one-column/specs/data-widget/spec.md
|
|
1860
|
+
*/
|
|
1861
|
+
emptyValueFor(field, raw) {
|
|
1862
|
+
const override = this.resolvedOverrides[field.key]
|
|
1863
|
+
const words = override && typeof override.emptyValue === 'string' ? override.emptyValue : ''
|
|
1864
|
+
if (words === '' || !(raw === null || raw === undefined || raw === '' || raw === false)) {
|
|
1865
|
+
return null
|
|
1866
|
+
}
|
|
1867
|
+
return this.cnTranslate(words)
|
|
1868
|
+
},
|
|
1869
|
+
|
|
1845
1870
|
/**
|
|
1846
1871
|
* Check if a field is editable.
|
|
1847
1872
|
*
|
|
@@ -117,6 +117,13 @@ import { readPath } from '../../utils/readPath.js'
|
|
|
117
117
|
/** The bus channel every endpoint-bound widget on the page refetches on. */
|
|
118
118
|
const PAGE_REFRESH_CHANNEL = 'cn:page:refresh'
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* The document event a recorded note sends, so a row's leaf (humaniq's
|
|
122
|
+
* hours) can record what it holds for the same object at the same moment.
|
|
123
|
+
* `detail` is the row's leaf props plus `note`.
|
|
124
|
+
*/
|
|
125
|
+
const NOTE_RECORDED_EVENT = 'cn-record-notes:recorded'
|
|
126
|
+
|
|
120
127
|
/**
|
|
121
128
|
* The rows of a record list drawn as notes to record (`presentation:
|
|
122
129
|
* "notes"`, DqDagAfsluiten "Nog open van vandaag"): per row its pill, title,
|
|
@@ -351,6 +358,9 @@ export default {
|
|
|
351
358
|
try {
|
|
352
359
|
const { default: axios } = await import('@nextcloud/axios')
|
|
353
360
|
await axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })
|
|
361
|
+
if (this.leaf) {
|
|
362
|
+
document.dispatchEvent(new CustomEvent(NOTE_RECORDED_EVENT, { detail: { ...this.leafProps(item.row), note: text } }))
|
|
363
|
+
}
|
|
354
364
|
const { [item.key]: done, ...rest } = this.drafts
|
|
355
365
|
this.drafts = rest
|
|
356
366
|
if (refresh) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
SPDX-License-Identifier: EUPL-1.2
|
|
4
4
|
-->
|
|
5
5
|
<template>
|
|
6
|
-
<div class="cn-stacked-bar" :class="{ 'cn-stacked-bar--inset': inset }" data-testid="cn-stacked-bar">
|
|
6
|
+
<div class="cn-stacked-bar" :class="{ 'cn-stacked-bar--inset': inset, 'cn-stacked-bar--compact': compact }" data-testid="cn-stacked-bar">
|
|
7
7
|
<div v-if="isLoading" class="cn-stacked-bar__state" role="status">
|
|
8
8
|
<NcLoadingIcon :size="20" />
|
|
9
9
|
<span>{{ loadingLabel }}</span>
|
|
@@ -183,7 +183,7 @@ export default {
|
|
|
183
183
|
* segments to the card (`header` event, `count`), for a chip after the
|
|
184
184
|
* title or a `{count}` in it.
|
|
185
185
|
*
|
|
186
|
-
* @type {{inset?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
|
|
186
|
+
* @type {{inset?: boolean, compact?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
|
|
187
187
|
*/
|
|
188
188
|
content: {
|
|
189
189
|
type: Object,
|
|
@@ -241,6 +241,17 @@ export default {
|
|
|
241
241
|
return Boolean(this.content && this.content.inset === true)
|
|
242
242
|
},
|
|
243
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Whether the widget is the compact summary box (`content.compact`):
|
|
246
|
+
* a 15px heading, one thin bar and the legend on one line.
|
|
247
|
+
*
|
|
248
|
+
* @spec openspec/changes/stacked-bar-compact/specs/stacked-bar-compact/spec.md#requirement-a-stacked-bar-can-be-drawn-as-the-compact-summary-box
|
|
249
|
+
* @return {boolean}
|
|
250
|
+
*/
|
|
251
|
+
compact() {
|
|
252
|
+
return Boolean(this.content && this.content.compact === true)
|
|
253
|
+
},
|
|
254
|
+
|
|
244
255
|
/**
|
|
245
256
|
* The translate function in use.
|
|
246
257
|
*
|
|
@@ -651,4 +662,59 @@ export default {
|
|
|
651
662
|
line-height: 1.2;
|
|
652
663
|
color: var(--color-main-text);
|
|
653
664
|
}
|
|
665
|
+
|
|
666
|
+
/* `content.compact: true`: the summary box (DqZaakDocumenten "4 van 7
|
|
667
|
+
beoordeeld"): the sunken ground, a 15px heading, one 8px bar of joined
|
|
668
|
+
segments and the legend on one line, each count beside its label. */
|
|
669
|
+
.cn-stacked-bar--compact {
|
|
670
|
+
box-sizing: border-box;
|
|
671
|
+
gap: 10px;
|
|
672
|
+
padding: 14px 16px;
|
|
673
|
+
border-radius: 10px;
|
|
674
|
+
background: var(--cn-board-sunken-ground, var(--color-background-hover));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.cn-stacked-bar--compact .cn-stacked-bar__heading {
|
|
678
|
+
flex-wrap: wrap;
|
|
679
|
+
gap: 8px;
|
|
680
|
+
font-size: 15px;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.cn-stacked-bar--compact .cn-stacked-bar__bar {
|
|
684
|
+
gap: 0;
|
|
685
|
+
height: 8px;
|
|
686
|
+
border-radius: 4px;
|
|
687
|
+
overflow: hidden;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.cn-stacked-bar--compact .cn-stacked-bar__segment {
|
|
691
|
+
border-radius: 0;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.cn-stacked-bar--compact .cn-stacked-bar__legend {
|
|
695
|
+
display: flex;
|
|
696
|
+
flex-wrap: wrap;
|
|
697
|
+
gap: 8px 18px;
|
|
698
|
+
font-size: 14px;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.cn-stacked-bar--compact .cn-stacked-bar__legend-item {
|
|
702
|
+
flex-direction: row;
|
|
703
|
+
align-items: center;
|
|
704
|
+
gap: 8px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.cn-stacked-bar--compact .cn-stacked-bar__label {
|
|
708
|
+
color: var(--color-main-text);
|
|
709
|
+
font-size: 1em;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.cn-stacked-bar--compact .cn-stacked-bar__dot {
|
|
713
|
+
border-radius: 50%;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.cn-stacked-bar--compact .cn-stacked-bar__count {
|
|
717
|
+
font-size: 1em;
|
|
718
|
+
line-height: inherit;
|
|
719
|
+
}
|
|
654
720
|
</style>
|
|
@@ -162,6 +162,26 @@
|
|
|
162
162
|
</p>
|
|
163
163
|
</div>
|
|
164
164
|
|
|
165
|
+
<!-- A days input set as a date (`dateField`): today plus the days,
|
|
166
|
+
and a chosen date sets the days. -->
|
|
167
|
+
<div v-if="field.input.dateField"
|
|
168
|
+
:key="`${field.key}-date`"
|
|
169
|
+
class="cn-transition-input-fields__field cn-transition-input-fields__wide"
|
|
170
|
+
:data-testid="field.input.dateField.testid || `cn-transition-input-date-${field.key}`">
|
|
171
|
+
<label class="cn-transition-input-fields__label"
|
|
172
|
+
:for="`${inputId(field)}-date`">{{ dateFieldLabel(field) }}</label>
|
|
173
|
+
<NcTextField :id="`${inputId(field)}-date`"
|
|
174
|
+
:modelValue="field.dateValue"
|
|
175
|
+
type="date"
|
|
176
|
+
:label="dateFieldLabel(field)"
|
|
177
|
+
:labelOutside="true"
|
|
178
|
+
:disabled="disabled"
|
|
179
|
+
@update:modelValue="setValue(field.key, daysUntilIso($event))" />
|
|
180
|
+
<span v-if="field.input.dateField.note" class="cn-transition-input-fields__muted">
|
|
181
|
+
{{ field.input.dateField.note }}
|
|
182
|
+
</span>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
165
185
|
<!-- What the input means once filled in, such as the day a paused
|
|
166
186
|
term comes back. Shown only once it can be worked out. -->
|
|
167
187
|
<div v-if="field.previewText"
|
|
@@ -209,7 +229,7 @@ import { NcCheckboxRadioSwitch, NcSelect, NcTextArea, NcTextField } from '@nextc
|
|
|
209
229
|
import CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'
|
|
210
230
|
import CnTemplateSelect from '../CnTemplateSelect/CnTemplateSelect.vue'
|
|
211
231
|
import { readPath } from '../../utils/readPath.js'
|
|
212
|
-
import { applyTemplateText, isInputFilled, resolveInputFields } from '../../utils/transitionInputs.js'
|
|
232
|
+
import { applyTemplateText, daysUntilIso, isInputFilled, resolveInputFields } from '../../utils/transitionInputs.js'
|
|
213
233
|
import { CnIcon } from '../CnIcon/index.js'
|
|
214
234
|
|
|
215
235
|
let uid = 0
|
|
@@ -505,6 +525,28 @@ export default {
|
|
|
505
525
|
return this.markOptional && !field.required ? `${field.label} (${t('nextcloud-vue', 'optional')})` : field.label
|
|
506
526
|
},
|
|
507
527
|
|
|
528
|
+
/**
|
|
529
|
+
* The label of a days input's date field (`dateField`), with the
|
|
530
|
+
* optional marker when asked for: the date is never required.
|
|
531
|
+
*
|
|
532
|
+
* @param {object} field The descriptor.
|
|
533
|
+
* @return {string}
|
|
534
|
+
*/
|
|
535
|
+
dateFieldLabel(field) {
|
|
536
|
+
const label = String(field.input.dateField.label || '')
|
|
537
|
+
return this.markOptional ? `${label} (${t('nextcloud-vue', 'optional')})` : label
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* The days a chosen date stands for.
|
|
542
|
+
*
|
|
543
|
+
* @param {string} iso The date input's value.
|
|
544
|
+
* @return {string} The days, '' without a date.
|
|
545
|
+
*/
|
|
546
|
+
daysUntilIso(iso) {
|
|
547
|
+
return daysUntilIso(iso)
|
|
548
|
+
},
|
|
549
|
+
|
|
508
550
|
/**
|
|
509
551
|
* Label of a sub-field, with the required marker.
|
|
510
552
|
*
|
|
@@ -702,12 +702,15 @@ function tasksSource() {
|
|
|
702
702
|
*
|
|
703
703
|
* @param {string} key The column key.
|
|
704
704
|
* @param {string} order `asc` or `desc`.
|
|
705
|
-
*
|
|
705
|
+
* Open tasks come before finished ones (`openFirst`), each in that order.
|
|
706
|
+
*
|
|
707
|
+
* @return {{sort: string, openFirst: boolean}|null} The parameters, or null for a column the inbox cannot sort by.
|
|
706
708
|
* @spec openspec/changes/tasks-source-sorts-on-server/specs/index-sources/spec.md#requirement-a-named-source-can-sort-on-the-server
|
|
709
|
+
* @spec openspec/changes/tasks-source-open-first/specs/index-sources/spec.md#requirement-a-sorted-task-list-puts-open-tasks-first
|
|
707
710
|
*/
|
|
708
711
|
sortParam: (key, order) => {
|
|
709
712
|
const field = TASK_SORT_FIELDS[key]
|
|
710
|
-
return field ? { sort: (order === 'desc' ? '-' : '') + field } : null
|
|
713
|
+
return field ? { sort: (order === 'desc' ? '-' : '') + field, openFirst: true } : null
|
|
711
714
|
},
|
|
712
715
|
|
|
713
716
|
// The scope tabs. Each tab's filter is merged over the page's
|
|
@@ -41,7 +41,8 @@ export const FLOW_TASKS_URL = '/apps/openregister/api/flow-tasks'
|
|
|
41
41
|
// creating app named it, so it is the one filter that lets a Tasks page say
|
|
42
42
|
// "the reminders" without the engine learning what a reminder is. It widens
|
|
43
43
|
// nothing: it narrows within the inbox the session already decides.
|
|
44
|
-
|
|
44
|
+
// `openFirst` asks open tasks before finished ones, a sort the engine applies.
|
|
45
|
+
const ALLOWED_PARAMS = ['scope', 'state', 'isTerminal', 'priority', 'kind', 'overdue', 'dueAfter', 'dueBefore', 'objectUuid', 'sort', 'openFirst', 'limit', 'offset']
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* The flat body `POST /api/flow-tasks` takes for a task on a record.
|
|
@@ -178,7 +179,7 @@ export const useTaskInboxStore = defineStore('cnTaskInbox', {
|
|
|
178
179
|
if (value === undefined || value === null || value === '') {
|
|
179
180
|
continue
|
|
180
181
|
}
|
|
181
|
-
params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value
|
|
182
|
+
params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value
|
|
182
183
|
}
|
|
183
184
|
const response = await axios.get(generateUrl(FLOW_TASKS_URL), { params })
|
|
184
185
|
const results = response.data?.results || []
|
|
@@ -358,7 +359,7 @@ export const useTaskInboxStore = defineStore('cnTaskInbox', {
|
|
|
358
359
|
}
|
|
359
360
|
// The endpoint reads `overdue` as a boolean-ish string; a quick
|
|
360
361
|
// filter authors it as `true` for legibility.
|
|
361
|
-
params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value
|
|
362
|
+
params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value
|
|
362
363
|
}
|
|
363
364
|
|
|
364
365
|
this.loading = true
|
package/src/css/look-board.css
CHANGED
|
@@ -3004,8 +3004,8 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
|
|
|
3004
3004
|
|
|
3005
3005
|
/* A dialog's options as the boards draw them (DqVolgendeStap, DqBesluit,
|
|
3006
3006
|
DqDocumentGenereren, DqFilterBewerken): rounded at 10px, an idle option
|
|
3007
|
-
edged in the dark border grey, and an 18px radio. The chosen
|
|
3008
|
-
|
|
3007
|
+
edged in the dark border grey, and an 18px radio. The chosen option keeps
|
|
3008
|
+
its own edge. */
|
|
3009
3009
|
.cn-look-board .cn-choice-cards .cn-choice-cards__row:not(.cn-look-nextcloud *),
|
|
3010
3010
|
.cn-look-board .cn-choice-cards .cn-choice-cards__cell--with-details:not(.cn-look-nextcloud *) {
|
|
3011
3011
|
border-radius: 10px;
|
|
@@ -3015,6 +3015,14 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
|
|
|
3015
3015
|
border-color: var(--color-border-maxcontrast);
|
|
3016
3016
|
}
|
|
3017
3017
|
|
|
3018
|
+
/* A refused option keeps that edge and sits on the sunken ground
|
|
3019
|
+
(DqVolgendeStap: Hervatten, Archiveren, Heropenen). */
|
|
3020
|
+
.cn-look-board .cn-choice-cards .cn-choice-cards__row--disabled:not(.cn-look-nextcloud *),
|
|
3021
|
+
.cn-look-board .cn-choice-cards .cn-choice-cards__row--disabled:hover:not(.cn-look-nextcloud *) {
|
|
3022
|
+
border-color: var(--color-border-maxcontrast);
|
|
3023
|
+
background: var(--cn-board-sunken-ground);
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3018
3026
|
/* A chosen option stays white under its primary edge, as every Dq dialog board
|
|
3019
3027
|
draws it (design-system #371); the Nextcloud look keeps the tint.
|
|
3020
3028
|
@spec openspec/changes/board-choice-row-chosen-white/specs/board-choice-row-chosen-white/spec.md#requirement-a-chosen-board-option-is-white */
|
package/src/css/table.css
CHANGED
|
@@ -289,6 +289,19 @@
|
|
|
289
289
|
white-space: nowrap;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
/* A growing column that opts into wrapping (`cn-cell--wrap`, DqDoorlooptijd
|
|
293
|
+
"Zaken met risico" titles) wraps its text over lines instead of cutting
|
|
294
|
+
it, and keeps a readable width: with `max-width: 0` the wide one-line
|
|
295
|
+
cells beside it squeezed it to its padding (24px).
|
|
296
|
+
@spec openspec/changes/fit-table-wrap-column-keeps-width/specs/fit-table-wrap-column-keeps-width/spec.md#requirement-a-wrapping-growing-column-keeps-a-readable-width */
|
|
297
|
+
.cn-data-table--fit th.cn-table-col--grow.cn-cell--wrap,
|
|
298
|
+
.cn-data-table--fit td.cn-table-col--grow.cn-cell--wrap {
|
|
299
|
+
min-width: 8em;
|
|
300
|
+
max-width: none;
|
|
301
|
+
overflow-wrap: anywhere;
|
|
302
|
+
white-space: normal;
|
|
303
|
+
}
|
|
304
|
+
|
|
292
305
|
/* The secondary line of the growing column ("2026-0004" under a case title)
|
|
293
306
|
is a line of its own with its own ellipsis. The cell does not wrap, so in
|
|
294
307
|
the board look, where the secondary line is an inline block (the title
|
|
@@ -470,6 +470,27 @@ export function formatMasked(value, _row, _property, options) {
|
|
|
470
470
|
return '*'.repeat(Math.min(text.length - visible, 5)) + (visible > 0 ? text.slice(-visible) : '')
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
/**
|
|
474
|
+
* A stored value through a display map (`valueMap` registry key): the label
|
|
475
|
+
* `options.map` gives the value, else the value as stored. "Besloten
|
|
476
|
+
* Vennootschap" reads "BV" on DqOrganisaties.
|
|
477
|
+
*
|
|
478
|
+
* @param {unknown} value The stored value.
|
|
479
|
+
* @param {object} [_row] The full row (unused).
|
|
480
|
+
* @param {object} [_property] The schema property (unused).
|
|
481
|
+
* @param {{map?: Object<string, string>}} [options] The column's `formatterOptions`.
|
|
482
|
+
* @return {string} The label, or the value.
|
|
483
|
+
* @spec openspec/changes/value-map-formatter/specs/index-page/spec.md#requirement-a-column-can-show-stored-values-through-a-display-map
|
|
484
|
+
*/
|
|
485
|
+
export function formatValueMap(value, _row, _property, options) {
|
|
486
|
+
if (value === null || value === undefined || value === '') {
|
|
487
|
+
return ''
|
|
488
|
+
}
|
|
489
|
+
const map = options && options.map && typeof options.map === 'object' ? options.map : {}
|
|
490
|
+
const label = Object.hasOwn(map, String(value)) ? map[String(value)] : undefined
|
|
491
|
+
return typeof label === 'string' && label !== '' ? label : String(value)
|
|
492
|
+
}
|
|
493
|
+
|
|
473
494
|
/**
|
|
474
495
|
* Built-in formatter registry merged under any consumer-registered
|
|
475
496
|
* `formatters` in `CnAppRoot`'s `cnFormatters` provide.
|
|
@@ -516,4 +537,5 @@ export const BUILT_IN_FORMATTERS = {
|
|
|
516
537
|
connectionSettingsLabel: formatConnectionSettingsLabel,
|
|
517
538
|
masked: formatMasked,
|
|
518
539
|
isoDuration: formatIsoDuration,
|
|
540
|
+
valueMap: formatValueMap,
|
|
519
541
|
}
|
|
@@ -64,7 +64,7 @@ export function formatLongDate(value, now = new Date(), withYear = false) {
|
|
|
64
64
|
* Format one token value.
|
|
65
65
|
*
|
|
66
66
|
* @param {unknown} value The raw value.
|
|
67
|
-
* @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower` or '' (as is).
|
|
67
|
+
* @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower`, `mailbox` or '' (as is).
|
|
68
68
|
* @param {Date} now The reference moment.
|
|
69
69
|
* @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).
|
|
70
70
|
* @param {object|null} [object] The row, for a currency read from a field.
|
|
@@ -125,6 +125,12 @@ function formatValue(value, format, now, arg = '', object = null, config = null,
|
|
|
125
125
|
if (format === 'ago') {
|
|
126
126
|
return formatAgo(value, now)
|
|
127
127
|
}
|
|
128
|
+
if (format === 'mailbox') {
|
|
129
|
+
// An address up to and with its @ ("gemeente@" from gemeente@zuiddrecht.nl).
|
|
130
|
+
const text = String(value)
|
|
131
|
+
const at = text.indexOf('@')
|
|
132
|
+
return at > 0 ? text.slice(0, at + 1) : text
|
|
133
|
+
}
|
|
128
134
|
if (format === 'lower') {
|
|
129
135
|
return String(value).toLocaleLowerCase()
|
|
130
136
|
}
|
package/src/utils/myTeam.js
CHANGED
|
@@ -30,12 +30,32 @@ let _source = null
|
|
|
30
30
|
/** The read in flight, shared by every caller. */
|
|
31
31
|
let _pending = null
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The team the app answered, as kept: one group id, a list of two or more,
|
|
35
|
+
* or `''` for none. Blank entries are dropped and a list of one reads as that
|
|
36
|
+
* one id.
|
|
37
|
+
*
|
|
38
|
+
* @spec openspec/changes/my-team-source-may-answer-several-teams/specs/schema-utilities/spec.md#requirement-myteam-may-resolve-to-several-teams
|
|
39
|
+
* @param {unknown} team What the app's loader answered.
|
|
40
|
+
* @return {string|string[]} The kept team.
|
|
41
|
+
*/
|
|
42
|
+
function normalizeTeam(team) {
|
|
43
|
+
if (Array.isArray(team)) {
|
|
44
|
+
const ids = team.filter((id) => typeof id === 'string').map((id) => id.trim()).filter((id) => id !== '')
|
|
45
|
+
if (ids.length === 0) {
|
|
46
|
+
return ''
|
|
47
|
+
}
|
|
48
|
+
return ids.length === 1 ? ids[0] : ids
|
|
49
|
+
}
|
|
50
|
+
return typeof team === 'string' ? team.trim() : ''
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
/**
|
|
34
54
|
* Name where the current user's primary team is read: a function answering
|
|
35
55
|
* the team's Nextcloud group id, or null/'' for a user in no team.
|
|
36
56
|
*
|
|
37
57
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
38
|
-
* @param {(() => Promise<string|null>)|null} loader The app's loader, or null to unregister.
|
|
58
|
+
* @param {(() => Promise<string|string[]|null>)|null} loader The app's loader (a group id, or the ids of several teams), or null to unregister.
|
|
39
59
|
* @return {void}
|
|
40
60
|
*/
|
|
41
61
|
export function registerMyTeamSource(loader) {
|
|
@@ -49,7 +69,7 @@ export function registerMyTeamSource(loader) {
|
|
|
49
69
|
* when no source is registered, or when the read fails.
|
|
50
70
|
*
|
|
51
71
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
52
|
-
* @return {Promise<string>} The team's group id, or ''.
|
|
72
|
+
* @return {Promise<string|string[]>} The team's group id (or ids), or ''.
|
|
53
73
|
*/
|
|
54
74
|
export function loadMyTeam() {
|
|
55
75
|
if (_state.team !== null) {
|
|
@@ -64,7 +84,7 @@ export function loadMyTeam() {
|
|
|
64
84
|
}
|
|
65
85
|
_pending = Promise.resolve()
|
|
66
86
|
.then(() => _source())
|
|
67
|
-
.then((team) => (
|
|
87
|
+
.then((team) => normalizeTeam(team))
|
|
68
88
|
.catch(() => '')
|
|
69
89
|
.then((team) => {
|
|
70
90
|
_state.team = team
|
|
@@ -79,7 +99,7 @@ export function loadMyTeam() {
|
|
|
79
99
|
* it is being read (and starts the read on first use).
|
|
80
100
|
*
|
|
81
101
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
82
|
-
* @return {string|null} The team, '' or null.
|
|
102
|
+
* @return {string|string[]|null} The team (or teams), '' or null.
|
|
83
103
|
*/
|
|
84
104
|
export function peekMyTeam() {
|
|
85
105
|
if (_state.team === null) {
|
|
@@ -89,15 +89,16 @@ export const MY_GROUPS_TOKEN = '@myGroups'
|
|
|
89
89
|
export const MY_TEAM_TOKEN = '@myTeam'
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* Resolve `@myTeam`: the team's group id
|
|
92
|
+
* Resolve `@myTeam`: the team's group id, or the ids of several teams when the
|
|
93
|
+
* app answers a list (an IN filter); `@myTeam` while it is being read (the
|
|
93
94
|
* caller waits); `@myTeam?` for a user in no team (the caller drops the key).
|
|
94
95
|
*
|
|
95
96
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
96
|
-
* @param {{myTeam?: string}} [ctx] Optional context; `ctx.myTeam` wins.
|
|
97
|
-
* @return {string} The group id or the token.
|
|
97
|
+
* @param {{myTeam?: (string|string[])}} [ctx] Optional context; `ctx.myTeam` wins.
|
|
98
|
+
* @return {string|string[]} The group id(s) or the token.
|
|
98
99
|
*/
|
|
99
100
|
function resolveMyTeam(ctx) {
|
|
100
|
-
const team = ctx && typeof ctx.myTeam === 'string' ? ctx.myTeam : peekMyTeam()
|
|
101
|
+
const team = ctx && (typeof ctx.myTeam === 'string' || Array.isArray(ctx.myTeam)) ? ctx.myTeam : peekMyTeam()
|
|
101
102
|
if (team === null) {
|
|
102
103
|
return MY_TEAM_TOKEN
|
|
103
104
|
}
|