@conduction/nextcloud-vue 2.138.0 → 2.139.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/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- 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.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +99 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +2 -0
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +13 -2
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +43 -4
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -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 +340 -238
- package/dist/nextcloud-vue.cjs.js +166 -19
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +340 -238
- package/l10n/en.json +3 -1
- package/l10n/nl.json +3 -1
- package/package.json +1 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +4 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +11 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +101 -6
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +10 -2
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
- package/src/composables/indexSources.js +43 -4
- package/src/css/dialog.css +18 -0
- package/src/css/look-board-detail.css +24 -0
- package/src/css/look-board-index.css +21 -0
- package/src/css/look-board.css +21 -0
package/l10n/en.json
CHANGED
|
@@ -2378,7 +2378,9 @@
|
|
|
2378
2378
|
"Search a person or group": "Search a person or group",
|
|
2379
2379
|
"Now: by {order}.": "Now: by {order}.",
|
|
2380
2380
|
"You save the order along with a view.": "You save the order along with a view.",
|
|
2381
|
-
"me": "me"
|
|
2381
|
+
"me": "me",
|
|
2382
|
+
"Open profile": "Open profile",
|
|
2383
|
+
"Link a contact from your address book.": "Link a contact from your address book."
|
|
2382
2384
|
},
|
|
2383
2385
|
"plurals": {
|
|
2384
2386
|
"{count} member": [
|
package/l10n/nl.json
CHANGED
|
@@ -2628,7 +2628,9 @@
|
|
|
2628
2628
|
"Search a person or group": "Zoek een persoon of groep",
|
|
2629
2629
|
"Now: by {order}.": "Nu: op {order}.",
|
|
2630
2630
|
"You save the order along with a view.": "De sortering bewaart u mee in een weergave.",
|
|
2631
|
-
"me": "ik"
|
|
2631
|
+
"me": "ik",
|
|
2632
|
+
"Open profile": "Profiel openen",
|
|
2633
|
+
"Link a contact from your address book.": "Koppel een contact uit je adresboek."
|
|
2632
2634
|
},
|
|
2633
2635
|
"plurals": {
|
|
2634
2636
|
"{count} member": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.139.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -2964,10 +2964,14 @@ export default {
|
|
|
2964
2964
|
display: contents;
|
|
2965
2965
|
}
|
|
2966
2966
|
|
|
2967
|
+
/* A column takes its share of the width (minmax(0, 1fr)), so a long phase
|
|
2968
|
+
name breaks inside its own column instead of running into the next. */
|
|
2967
2969
|
.cn-chart-widget__matrix-head {
|
|
2968
2970
|
--nldesign-color-on-surface: var(--color-text-maxcontrast);
|
|
2969
2971
|
color: var(--color-text-maxcontrast);
|
|
2970
2972
|
padding: 6px;
|
|
2973
|
+
hyphens: auto;
|
|
2974
|
+
overflow-wrap: anywhere;
|
|
2971
2975
|
}
|
|
2972
2976
|
|
|
2973
2977
|
.cn-chart-widget__matrix-label {
|
|
@@ -1845,11 +1845,13 @@ export default {
|
|
|
1845
1845
|
display: contents;
|
|
1846
1846
|
}
|
|
1847
1847
|
|
|
1848
|
-
/* Nextcloud core styles `dt` and `dd` (text-align end, 12px padding
|
|
1849
|
-
pushed the label to the right of its column
|
|
1850
|
-
|
|
1851
|
-
|
|
1848
|
+
/* Nextcloud core styles `dt` and `dd` (text-align end, 12px padding, a
|
|
1849
|
+
130px-wide `dt`), which pushed the label to the right of its column, added
|
|
1850
|
+
12px around both and widened the label column to 130px. The class is
|
|
1851
|
+
doubled so these resets outrank that rule, and a dossiq-style `:deep()`
|
|
1852
|
+
override in an app is not needed. */
|
|
1852
1853
|
.cn-facts-widget__label.cn-facts-widget__label {
|
|
1854
|
+
width: auto;
|
|
1853
1855
|
margin: 0;
|
|
1854
1856
|
padding: 0;
|
|
1855
1857
|
text-align: start;
|
|
@@ -3278,6 +3278,17 @@ export default {
|
|
|
3278
3278
|
font-size: 14px;
|
|
3279
3279
|
}
|
|
3280
3280
|
|
|
3281
|
+
/* Core's apps.css draws every dt/dd inline-block with a fixed, end-aligned dt.
|
|
3282
|
+
@spec openspec/changes/files-side-facts-core-dl/specs/files-side-facts-core-dl/spec.md#requirement-a-file-side-cards-facts-are-not-drawn-by-cores-dl-rules */
|
|
3283
|
+
.cn-files-browser__side-facts dt,
|
|
3284
|
+
.cn-files-browser__side-facts dd {
|
|
3285
|
+
display: block;
|
|
3286
|
+
width: auto;
|
|
3287
|
+
padding: 0;
|
|
3288
|
+
text-align: start;
|
|
3289
|
+
white-space: normal;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3281
3292
|
.cn-files-browser__side-facts dt {
|
|
3282
3293
|
color: var(--color-text-maxcontrast);
|
|
3283
3294
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cn-index-page"
|
|
3
|
-
:class="[lookClass, { 'cn-index-page--with-side-panel': hasSidePanel, 'cn-index-page--quick-filters-toolbar': effectiveQuickFilterPlacement === 'toolbar' }]"
|
|
3
|
+
:class="[lookClass, { 'cn-index-page--with-side-panel': hasSidePanel, 'cn-index-page--sidebar-open': sidebarPanelOpen, 'cn-index-page--quick-filters-toolbar': effectiveQuickFilterPlacement === 'toolbar' }]"
|
|
4
4
|
data-testid="cn-index-page"
|
|
5
5
|
@keydown="onListKeydown">
|
|
6
6
|
<!-- Header — overridable via #header slot. CnPageHeader ALWAYS renders:
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
:maxVisible="quickFilterMaxVisible"
|
|
173
173
|
:fit="false"
|
|
174
174
|
:multiple="quickFilterMultiple"
|
|
175
|
-
:activeIndex="
|
|
175
|
+
:activeIndex="shownQuickFilterIndex"
|
|
176
176
|
:selectedIndices="selectedQuickFilterIndices"
|
|
177
177
|
:counts="effectiveTabCounts"
|
|
178
178
|
@update:activeIndex="onQuickFilterChange"
|
|
@@ -2240,6 +2240,21 @@ export default {
|
|
|
2240
2240
|
default: '',
|
|
2241
2241
|
},
|
|
2242
2242
|
|
|
2243
|
+
/**
|
|
2244
|
+
* Board look: the count line while a saved view is applied (manifest
|
|
2245
|
+
* `config.savedViewCountText`), a template as `countText` that may name
|
|
2246
|
+
* the view's conditions as `{view}`: its description, else its filters
|
|
2247
|
+
* in words (DqFilterInMenu "3 van 48 lopende zaken · termijn binnen 7
|
|
2248
|
+
* dagen, van mij · gesorteerd op termijn"). A view's own `countText`
|
|
2249
|
+
* override and a scope's come first.
|
|
2250
|
+
*
|
|
2251
|
+
* @spec openspec/changes/applied-view-lights-its-lens/specs/applied-view-lights-its-lens/spec.md
|
|
2252
|
+
*/
|
|
2253
|
+
savedViewCountText: {
|
|
2254
|
+
type: String,
|
|
2255
|
+
default: '',
|
|
2256
|
+
},
|
|
2257
|
+
|
|
2243
2258
|
/**
|
|
2244
2259
|
* Board look: a count line per view (manifest `config.countTextByView`),
|
|
2245
2260
|
* `{ table?, cards?, list?, board?, map? }`, each a template as
|
|
@@ -4580,6 +4595,8 @@ export default {
|
|
|
4580
4595
|
savedViewCounts: null,
|
|
4581
4596
|
// The id (or slug) of the saved view that is applied; its chip shows selected under the board look.
|
|
4582
4597
|
appliedSavedViewId: '',
|
|
4598
|
+
/** Whether the reader picked a lens chip after the applied view (then their chip shows). */
|
|
4599
|
+
lensChosenByHand: false,
|
|
4583
4600
|
// The sort option the applied view's `overrides.sortOption` selected, so leaving the view can undo it.
|
|
4584
4601
|
viewSortOption: '',
|
|
4585
4602
|
// The applied view's filters as one comparable key, so the address keeps naming the view until they change.
|
|
@@ -6419,13 +6436,53 @@ export default {
|
|
|
6419
6436
|
* @spec openspec/changes/working-list-row-actions/specs/index-page/spec.md
|
|
6420
6437
|
*/
|
|
6421
6438
|
/**
|
|
6422
|
-
* The chip
|
|
6423
|
-
*
|
|
6424
|
-
*
|
|
6439
|
+
* The lens chip an applied saved view narrows within: the non-default
|
|
6440
|
+
* tab whose every condition the view's own conditions (its filters
|
|
6441
|
+
* over its locked filters, tokens as written) hold, the most specific
|
|
6442
|
+
* first (DqFilterInMenu: "Termijn deze week" lit for the view of that
|
|
6443
|
+
* name). Null without an applied view or a fitting tab.
|
|
6444
|
+
*
|
|
6445
|
+
* @return {number|null} The tab index.
|
|
6446
|
+
* @spec openspec/changes/applied-view-lights-its-lens/specs/applied-view-lights-its-lens/spec.md
|
|
6447
|
+
*/
|
|
6448
|
+
appliedViewLensIndex() {
|
|
6449
|
+
const view = this.appliedSavedView
|
|
6450
|
+
const tabs = Array.isArray(this.tabStripEntries) ? this.tabStripEntries : []
|
|
6451
|
+
if (!view || tabs.length === 0) {
|
|
6452
|
+
return null
|
|
6453
|
+
}
|
|
6454
|
+
const query = view.query && typeof view.query === 'object' ? view.query : {}
|
|
6455
|
+
const locked = query.overrides && typeof query.overrides.lockedFilters === 'object' ? query.overrides.lockedFilters : {}
|
|
6456
|
+
const conditions = { ...(locked || {}), ...(query.filters || view.filters || {}) }
|
|
6457
|
+
const same = (a, b) => JSON.stringify(Array.isArray(a) ? a.map(String) : String(a)) === JSON.stringify(Array.isArray(b) ? b.map(String) : String(b))
|
|
6458
|
+
let best = null
|
|
6459
|
+
let bestSize = 0
|
|
6460
|
+
tabs.forEach((tab, index) => {
|
|
6461
|
+
const filter = tab && tab.filter && typeof tab.filter === 'object' ? tab.filter : {}
|
|
6462
|
+
const keys = Object.keys(filter)
|
|
6463
|
+
if (tab.default === true || keys.length === 0 || keys.length <= bestSize) {
|
|
6464
|
+
return
|
|
6465
|
+
}
|
|
6466
|
+
if (keys.every((key) => Object.hasOwn(conditions, key) && same(conditions[key], filter[key]))) {
|
|
6467
|
+
best = index
|
|
6468
|
+
bestSize = keys.length
|
|
6469
|
+
}
|
|
6470
|
+
})
|
|
6471
|
+
return best
|
|
6472
|
+
},
|
|
6473
|
+
|
|
6474
|
+
/**
|
|
6475
|
+
* The chip the strip lights: the lens an applied saved view narrows
|
|
6476
|
+
* within (until the reader picks a chip), else the active tab, else
|
|
6477
|
+
* the default tab of tabs that arrived after setup
|
|
6478
|
+
* (`quickFiltersFrom`), which is the one the fetch applies.
|
|
6425
6479
|
*
|
|
6426
6480
|
* @return {number|null} The tab index, or null without tabs.
|
|
6427
6481
|
*/
|
|
6428
6482
|
shownQuickFilterIndex() {
|
|
6483
|
+
if (!this.quickFilterMultiple && !this.lensChosenByHand && this.appliedViewLensIndex !== null) {
|
|
6484
|
+
return this.appliedViewLensIndex
|
|
6485
|
+
}
|
|
6429
6486
|
if (this.activeQuickFilterIndex !== null && this.activeQuickFilterIndex !== undefined) {
|
|
6430
6487
|
return this.activeQuickFilterIndex
|
|
6431
6488
|
}
|
|
@@ -8815,6 +8872,11 @@ export default {
|
|
|
8815
8872
|
* @return {string}
|
|
8816
8873
|
*/
|
|
8817
8874
|
presentedCountText() {
|
|
8875
|
+
const viewLine = this.savedViewCountText
|
|
8876
|
+
const view = this.appliedSavedView
|
|
8877
|
+
if (view && typeof viewLine === 'string' && viewLine !== '' && this.activeScopePresentation.countText === undefined && !savedViewOverrides(view)?.countText) {
|
|
8878
|
+
return viewLine
|
|
8879
|
+
}
|
|
8818
8880
|
if (this.activeScopePresentation.countText === undefined) {
|
|
8819
8881
|
const byView = this.countTextByView
|
|
8820
8882
|
const own = byView && typeof byView === 'object' ? byView[this.currentViewMode] : undefined
|
|
@@ -8825,6 +8887,23 @@ export default {
|
|
|
8825
8887
|
return this.presented('countText')
|
|
8826
8888
|
},
|
|
8827
8889
|
|
|
8890
|
+
/**
|
|
8891
|
+
* The applied saved view's conditions for the count line's `{view}`:
|
|
8892
|
+
* its description, else its filters in words, starting lower case.
|
|
8893
|
+
*
|
|
8894
|
+
* @return {string}
|
|
8895
|
+
* @spec openspec/changes/applied-view-lights-its-lens/specs/applied-view-lights-its-lens/spec.md
|
|
8896
|
+
*/
|
|
8897
|
+
appliedViewConditionsText() {
|
|
8898
|
+
const view = this.appliedSavedView
|
|
8899
|
+
if (!view) {
|
|
8900
|
+
return ''
|
|
8901
|
+
}
|
|
8902
|
+
const own = typeof view.description === 'string' ? view.description.trim() : ''
|
|
8903
|
+
const text = own !== '' ? own : String(this.viewFilterSummary(view) || '')
|
|
8904
|
+
return text.charAt(0).toLowerCase() + text.slice(1)
|
|
8905
|
+
},
|
|
8906
|
+
|
|
8828
8907
|
headerDescriptionBase() {
|
|
8829
8908
|
const total = this.effectivePagination?.total
|
|
8830
8909
|
// Board look: the count line is a template over the rows on this page
|
|
@@ -8848,6 +8927,7 @@ export default {
|
|
|
8848
8927
|
.replace('{shown}', String(shown))
|
|
8849
8928
|
.replace('{total}', String(total))
|
|
8850
8929
|
.replace(/\{term\}/g, this.sourceTerm)
|
|
8930
|
+
.replace(/\{view\}/g, this.appliedViewConditionsText)
|
|
8851
8931
|
}
|
|
8852
8932
|
return this.countLine(this.countSubtitle, total, shown)
|
|
8853
8933
|
}
|
|
@@ -8913,6 +8993,17 @@ export default {
|
|
|
8913
8993
|
return !!this.resolvedSidebar.enabled && this.resolvedSidebar.show !== false
|
|
8914
8994
|
},
|
|
8915
8995
|
|
|
8996
|
+
/**
|
|
8997
|
+
* Whether the page's side panel is open now; the board look then lays
|
|
8998
|
+
* the header out narrower (DqZakenSorteren).
|
|
8999
|
+
*
|
|
9000
|
+
* @spec openspec/changes/board-header-buttons-under-title-beside-side-panel/specs/board-header-buttons-under-title-beside-side-panel/spec.md#requirement-the-header-buttons-sit-under-the-title-while-the-side-panel-is-open
|
|
9001
|
+
* @return {boolean}
|
|
9002
|
+
*/
|
|
9003
|
+
sidebarPanelOpen() {
|
|
9004
|
+
return Boolean(this.sidebarOpen && this.hasSidebar)
|
|
9005
|
+
},
|
|
9006
|
+
|
|
8916
9007
|
/**
|
|
8917
9008
|
* Whether the board toolbar draws the Filter button as a menu: the
|
|
8918
9009
|
* board look, `filterMenu` on, something to filter by, and no filter
|
|
@@ -8955,7 +9046,9 @@ export default {
|
|
|
8955
9046
|
|
|
8956
9047
|
/** Search props forwarded to the embedded CnIndexSidebar (defaults applied per CnIndexSidebar). */
|
|
8957
9048
|
sidebarSearchProps() {
|
|
8958
|
-
|
|
9049
|
+
// The page's own search hint, unless `sidebar.search` names one.
|
|
9050
|
+
const own = this.shownSearchPlaceholder ? { searchPlaceholder: this.shownSearchPlaceholder } : {}
|
|
9051
|
+
return { ...own, ...((this.sidebar && this.sidebar.search) || {}) }
|
|
8959
9052
|
},
|
|
8960
9053
|
|
|
8961
9054
|
/**
|
|
@@ -10778,6 +10871,7 @@ export default {
|
|
|
10778
10871
|
// `activeQuickFilterIndex` is a setup-returned ref; the
|
|
10779
10872
|
// Vue 2 ref-unwrap proxy makes plain assignment work.
|
|
10780
10873
|
this.activeQuickFilterIndex = index
|
|
10874
|
+
this.lensChosenByHand = true
|
|
10781
10875
|
this.$emit('quick-filter-change', index)
|
|
10782
10876
|
},
|
|
10783
10877
|
|
|
@@ -13213,6 +13307,7 @@ export default {
|
|
|
13213
13307
|
*/
|
|
13214
13308
|
onApplySavedView(view, { keepSavedViewKey = false } = {}) {
|
|
13215
13309
|
this.appliedSavedViewId = view && (view.id || view.slug) ? String(view.id || view.slug) : ''
|
|
13310
|
+
this.lensChosenByHand = false
|
|
13216
13311
|
const state = extractViewState(view)
|
|
13217
13312
|
// A view's filters may carry tokens (`@me`, `@myTeam`): resolved here,
|
|
13218
13313
|
// and an optional one that answers nothing (no team) is dropped. A
|
|
@@ -32,11 +32,17 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
<div class="cn-index-sidebar__section">
|
|
35
|
+
<!-- The board look names the field by its hint, as the list's
|
|
36
|
+
own search field, with the magnifier (DqZakenSorteren). -->
|
|
35
37
|
<NcTextField
|
|
36
38
|
:modelValue="searchValue || ''"
|
|
37
39
|
:placeholder="searchPlaceholder"
|
|
38
|
-
:label="searchLabel"
|
|
39
|
-
@update:modelValue="$emit('search', $event)"
|
|
40
|
+
:label="isBoardLook ? searchPlaceholder : searchLabel"
|
|
41
|
+
@update:modelValue="$emit('search', $event)">
|
|
42
|
+
<template v-if="isBoardLook" #icon>
|
|
43
|
+
<Magnify :size="18" />
|
|
44
|
+
</template>
|
|
45
|
+
</NcTextField>
|
|
40
46
|
</div>
|
|
41
47
|
|
|
42
48
|
<!-- Sort (list, card and map views; the table sorts by its headers):
|
|
@@ -355,6 +361,7 @@ import DragVertical from 'vue-material-design-icons/DragVertical.vue'
|
|
|
355
361
|
import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
|
|
356
362
|
import FilterRemoveOutline from 'vue-material-design-icons/FilterRemoveOutline.vue'
|
|
357
363
|
import InformationOutline from 'vue-material-design-icons/InformationOutline.vue'
|
|
364
|
+
import Magnify from 'vue-material-design-icons/Magnify.vue'
|
|
358
365
|
import Pin from 'vue-material-design-icons/Pin.vue'
|
|
359
366
|
import PinOutline from 'vue-material-design-icons/PinOutline.vue'
|
|
360
367
|
import ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'
|
|
@@ -410,6 +417,7 @@ export default {
|
|
|
410
417
|
Pin,
|
|
411
418
|
PinOutline,
|
|
412
419
|
InformationOutline,
|
|
420
|
+
Magnify,
|
|
413
421
|
},
|
|
414
422
|
|
|
415
423
|
inject: {
|
|
@@ -860,7 +860,7 @@ export default {
|
|
|
860
860
|
// `secondary` (a second, muted line under the cell) rides
|
|
861
861
|
// along too: the table draws it, and dropping it here took the
|
|
862
862
|
// list rows' second line away (zuiddrecht-pixel-gaps-3).
|
|
863
|
-
for (const k of ['format', 'widget', 'widgetProps', 'formatter', 'align', 'width', 'type', 'enum', 'enumLabels', 'sortable', 'secondary', 'colorMap', 'grow']) {
|
|
863
|
+
for (const k of ['format', 'widget', 'widgetProps', 'formatter', 'align', 'width', 'type', 'enum', 'enumLabels', 'sortable', 'secondary', 'colorMap', 'grow', 'cellClass']) {
|
|
864
864
|
if (c[k] !== undefined) {
|
|
865
865
|
out[k] = c[k]
|
|
866
866
|
}
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import { getCanonicalLocale, translate as t } from '@nextcloud/l10n'
|
|
7
7
|
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
|
|
8
8
|
import { reactive } from 'vue'
|
|
9
|
+
import AccountOutline from 'vue-material-design-icons/AccountOutline.vue'
|
|
9
10
|
import CalendarClock from 'vue-material-design-icons/CalendarClock.vue'
|
|
10
11
|
import Cancel from 'vue-material-design-icons/Cancel.vue'
|
|
11
12
|
import Check from 'vue-material-design-icons/Check.vue'
|
|
13
|
+
import EmailOutline from 'vue-material-design-icons/EmailOutline.vue'
|
|
12
14
|
import HandBackRight from 'vue-material-design-icons/HandBackRight.vue'
|
|
13
15
|
import LightningBolt from 'vue-material-design-icons/LightningBolt.vue'
|
|
14
16
|
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
|
@@ -19,6 +21,7 @@ import { fillTemplate } from '../utils/fillTemplate.js'
|
|
|
19
21
|
import { FLOW_RUN_ERROR_STATUSES, flowRunStatuses } from '../utils/flowRunStatus.js'
|
|
20
22
|
import { fetchGroupMembers, fetchPresence, fetchStatuses, fetchUsers } from '../utils/nextcloudDirectory.js'
|
|
21
23
|
import { readPath } from '../utils/readPath.js'
|
|
24
|
+
import { resolveNextcloudUser } from '../utils/userAutocomplete.js'
|
|
22
25
|
import { useFlowStore } from './useFlowStore.js'
|
|
23
26
|
import { organisationRecord, useOrganisationStore } from './useOrganisationStore.js'
|
|
24
27
|
import { useTaskInboxStore } from './useTaskInboxStore.js'
|
|
@@ -1088,7 +1091,7 @@ function fillRowTemplate(template, row) {
|
|
|
1088
1091
|
* and its count (DqZoekresultaten).
|
|
1089
1092
|
*
|
|
1090
1093
|
* `sourceConfig.kinds` lists what is searched:
|
|
1091
|
-
* `{ id, label, register, schema, titleField?, subtitleField?, subtitleTemplate?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
|
|
1094
|
+
* `{ id, label, register, schema, titleField?, subtitleField?, subtitleTemplate?, statusField?, statusResolve?, linkedField?, linkedTemplate?, linkedUser?, href?, limit? }`
|
|
1092
1095
|
* for objects (OpenRegister's `_search`, so the reader sees what they may
|
|
1093
1096
|
* read), or `{ id, label, files: true, limit? }` for the reader's files
|
|
1094
1097
|
* (Nextcloud's unified search). The term is `sourceConfig.term`, else the
|
|
@@ -1104,6 +1107,8 @@ function fillRowTemplate(template, row) {
|
|
|
1104
1107
|
* column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
|
|
1105
1108
|
* `subtitleTemplate` ("Case {identifier}", translated) fills the line under a
|
|
1106
1109
|
* result, else `subtitleField`.
|
|
1110
|
+
* `linkedUser: true` reads that value as a user id and shows the user's
|
|
1111
|
+
* display name. `sourceConfig.columnWidths` sets a width per column key.
|
|
1107
1112
|
* The page's title and count line can name the term as `{term}`, and the
|
|
1108
1113
|
* count is the active kind's total. Kind, link and status are filterable.
|
|
1109
1114
|
*
|
|
@@ -1113,6 +1118,23 @@ function fillRowTemplate(template, row) {
|
|
|
1113
1118
|
* @return {object} The source adapter.
|
|
1114
1119
|
* @spec openspec/changes/index-search-source/specs/index-page/spec.md#requirement-an-index-page-can-search-several-schemas-and-files
|
|
1115
1120
|
*/
|
|
1121
|
+
/**
|
|
1122
|
+
* Columns with the widths a page names per column key
|
|
1123
|
+
* (`sourceConfig.columnWidths`, `{ title: '250px' }`); a column it does not
|
|
1124
|
+
* name keeps none.
|
|
1125
|
+
*
|
|
1126
|
+
* @param {Array<object>} columns The columns.
|
|
1127
|
+
* @param {object|undefined} widths The widths by key.
|
|
1128
|
+
* @return {Array<object>} The columns.
|
|
1129
|
+
* @spec openspec/changes/search-linked-user-and-widths/specs/search-linked-user-and-widths/spec.md
|
|
1130
|
+
*/
|
|
1131
|
+
function withColumnWidths(columns, widths) {
|
|
1132
|
+
if (!widths || typeof widths !== 'object') {
|
|
1133
|
+
return columns
|
|
1134
|
+
}
|
|
1135
|
+
return columns.map((col) => (typeof widths[col.key] === 'string' && widths[col.key] !== '' ? { ...col, width: widths[col.key] } : col))
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1116
1138
|
function searchSource(config = {}, context = {}) {
|
|
1117
1139
|
const translate = typeof context.translate === 'function' ? context.translate : (text) => text
|
|
1118
1140
|
const kinds = (Array.isArray(config && config.kinds) ? config.kinds : [])
|
|
@@ -1208,12 +1230,20 @@ function searchSource(config = {}, context = {}) {
|
|
|
1208
1230
|
return kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : ''
|
|
1209
1231
|
}
|
|
1210
1232
|
|
|
1233
|
+
// `linkedUser`: the Linked cell holds a user id; name the user instead.
|
|
1234
|
+
const linkedUserNames = async (kind, objects) => {
|
|
1235
|
+
const ids = [...new Set(objects.map((object) => linkedOf(kind, object)).filter((id) => id !== ''))]
|
|
1236
|
+
const options = await Promise.all(ids.map((id) => resolveNextcloudUser(id).catch(() => null)))
|
|
1237
|
+
return new Map(ids.map((id, index) => [id, (options[index] && options[index].label) || id]))
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1211
1240
|
const searchObjects = async (axios, kind, term) => {
|
|
1212
1241
|
const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema })
|
|
1213
1242
|
const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } })
|
|
1214
1243
|
const body = (res && res.data) || {}
|
|
1215
1244
|
const results = Array.isArray(body.results) ? body.results : (Array.isArray(body) ? body : [])
|
|
1216
1245
|
const labels = kind.statusResolve && kind.statusField ? await statusLabels(kind) : null
|
|
1246
|
+
const userNames = kind.linkedUser === true ? await linkedUserNames(kind, results) : null
|
|
1217
1247
|
return {
|
|
1218
1248
|
total: typeof body.total === 'number' ? body.total : results.length,
|
|
1219
1249
|
rows: results.map((object) => {
|
|
@@ -1230,7 +1260,7 @@ function searchSource(config = {}, context = {}) {
|
|
|
1230
1260
|
title: String(readPath(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
|
|
1231
1261
|
subtitle: subtitleOf(kind, object),
|
|
1232
1262
|
status,
|
|
1233
|
-
linked: linkedOf(kind, object),
|
|
1263
|
+
linked: userNames ? (userNames.get(linkedOf(kind, object)) || linkedOf(kind, object)) : linkedOf(kind, object),
|
|
1234
1264
|
href: kind.href ? generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
|
|
1235
1265
|
object,
|
|
1236
1266
|
}
|
|
@@ -1338,7 +1368,7 @@ function searchSource(config = {}, context = {}) {
|
|
|
1338
1368
|
*/
|
|
1339
1369
|
loading: () => state.loading,
|
|
1340
1370
|
|
|
1341
|
-
columns: [
|
|
1371
|
+
columns: withColumnWidths([
|
|
1342
1372
|
{ key: 'title', label: t('nextcloud-vue', 'Result'), secondary: 'subtitle', sortable: true },
|
|
1343
1373
|
// `kindAsText`: what one row is, as plain text ("Zaak", "Lantaarnpaal",
|
|
1344
1374
|
// DqZoekresultaten), instead of the plural kind as a pill.
|
|
@@ -1353,7 +1383,7 @@ function searchSource(config = {}, context = {}) {
|
|
|
1353
1383
|
widgetProps: { colorMap: (config && config.statusColorMap) || {} },
|
|
1354
1384
|
sortable: true,
|
|
1355
1385
|
},
|
|
1356
|
-
],
|
|
1386
|
+
], config && config.columnWidths),
|
|
1357
1387
|
|
|
1358
1388
|
/**
|
|
1359
1389
|
* Every column header carries a filter button (DqZoekresultaten); the
|
|
@@ -1603,6 +1633,15 @@ function usersSource() {
|
|
|
1603
1633
|
/** A filter button on every head (DqAfdelingen); the members read are narrowed here. */
|
|
1604
1634
|
headerFilters: true,
|
|
1605
1635
|
|
|
1636
|
+
/**
|
|
1637
|
+
* A member's row menu (DqAfdelingen): their Nextcloud profile, and an
|
|
1638
|
+
* e-mail when they have an address. Both are links.
|
|
1639
|
+
*/
|
|
1640
|
+
rowActions: [
|
|
1641
|
+
{ id: 'open-profile', label: t('nextcloud-vue', 'Open profile'), icon: AccountOutline, href: (row) => generateUrl(`/u/${encodeURIComponent(row.id)}`) },
|
|
1642
|
+
{ id: 'send-email', label: t('nextcloud-vue', 'Send email'), icon: EmailOutline, href: (row) => `mailto:${row.email}`, visible: (row) => Boolean(row && row.email) },
|
|
1643
|
+
],
|
|
1644
|
+
|
|
1606
1645
|
showAdd: false,
|
|
1607
1646
|
}
|
|
1608
1647
|
}
|
package/src/css/dialog.css
CHANGED
|
@@ -204,6 +204,24 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
|
|
|
204
204
|
gap: 10px;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/* A choice card with a checkbox (CnAssignDialog's toggles) is as tight as
|
|
208
|
+
the board draws it (DqOverdragen, 76px for a label and a two-line note):
|
|
209
|
+
10px 14px padding, the checkbox row at the 24px minimum target instead of
|
|
210
|
+
NcCheckboxRadioSwitch's 34px, the note at 1.2 line height. */
|
|
211
|
+
.cn-dialog-board .cn-assign-dialog__toggle {
|
|
212
|
+
padding: 10px 14px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.cn-dialog-board .cn-assign-dialog__toggle .checkbox-radio-switch-checkbox .checkbox-content,
|
|
216
|
+
.cn-dialog-board .cn-assign-dialog__toggle .checkbox-radio-switch__input {
|
|
217
|
+
min-height: 24px;
|
|
218
|
+
padding-block: 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.cn-dialog-board .cn-assign-dialog__toggle-note {
|
|
222
|
+
line-height: 1.2;
|
|
223
|
+
}
|
|
224
|
+
|
|
207
225
|
/* Body: padding 20px 24px, 18px between blocks. */
|
|
208
226
|
.cn-dialog-board .dialog__content {
|
|
209
227
|
display: flex;
|
|
@@ -579,9 +579,16 @@
|
|
|
579
579
|
.cn-look-board .cn-conversation-thread .cn-conversation-thread__meta:not(.cn-look-nextcloud *),
|
|
580
580
|
.cn-look-board .cn-conversation-thread .cn-conversation-thread__meta time:not(.cn-look-nextcloud *) {
|
|
581
581
|
color: var(--cn-board-text-soft);
|
|
582
|
+
font-size: 13px;
|
|
582
583
|
line-height: normal;
|
|
583
584
|
}
|
|
584
585
|
|
|
586
|
+
/* 6px between the meta line and the text.
|
|
587
|
+
@spec openspec/changes/board-conversation-bubble-meta-gap/specs/board-conversation-bubble-meta-gap/spec.md#requirement-a-board-message-sets-its-meta-line-at-13px-6px-above-the-text */
|
|
588
|
+
.cn-look-board .cn-conversation-thread .cn-conversation-thread__bubble:not(.cn-look-nextcloud *) {
|
|
589
|
+
gap: 6px;
|
|
590
|
+
}
|
|
591
|
+
|
|
585
592
|
.cn-look-board .cn-conversation-thread .cn-conversation-thread__message--us .cn-conversation-thread__bubble:not(.cn-look-nextcloud *) {
|
|
586
593
|
color: var(--color-main-text);
|
|
587
594
|
}
|
|
@@ -1270,6 +1277,11 @@
|
|
|
1270
1277
|
line-height: normal;
|
|
1271
1278
|
}
|
|
1272
1279
|
|
|
1280
|
+
/* Head and body text at line height 1.2, as the board draws the table. */
|
|
1281
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper__content > .cn-tasks-widget .cn-tasks-widget__table :is(th, td) {
|
|
1282
|
+
line-height: 1.2;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1273
1285
|
/* A long task name wraps under itself, as the board draws it, rather than
|
|
1274
1286
|
widening the table past the card. */
|
|
1275
1287
|
.cn-look-board .cn-detail-page .cn-widget-wrapper__content > .cn-tasks-widget .cn-tasks-widget__table :is(.cn-tasks-widget__name, .cn-tasks-widget__meta) {
|
|
@@ -1569,3 +1581,15 @@
|
|
|
1569
1581
|
font-size: 14px;
|
|
1570
1582
|
line-height: 1.2;
|
|
1571
1583
|
}
|
|
1584
|
+
|
|
1585
|
+
/* The contacts tab inside a detail card (DqZaakBetrokkenen "Partijen") takes
|
|
1586
|
+
the card's padding instead of its own, and its buttons start at the card's
|
|
1587
|
+
edge under the subtitle.
|
|
1588
|
+
@spec openspec/changes/board-contacts-in-card/specs/contacts/spec.md */
|
|
1589
|
+
.cn-look-board .cn-detail-page__leaf-card .cn-contacts-tab {
|
|
1590
|
+
padding: 0;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.cn-look-board .cn-detail-page__leaf-card .cn-contacts-tab .cn-contacts-tab__header {
|
|
1594
|
+
justify-content: flex-start;
|
|
1595
|
+
}
|
|
@@ -71,6 +71,27 @@
|
|
|
71
71
|
margin-inline-start: 0;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/* With the side panel open the buttons take the row under the title, the
|
|
75
|
+
breadcrumb and the count line follow (DqZakenSorteren).
|
|
76
|
+
@spec openspec/changes/board-header-buttons-under-title-beside-side-panel/specs/board-header-buttons-under-title-beside-side-panel/spec.md#requirement-the-header-buttons-sit-under-the-title-while-the-side-panel-is-open */
|
|
77
|
+
.cn-look-board.cn-index-page--sidebar-open .cn-page-header .cn-index-page__header-buttons,
|
|
78
|
+
.cn-look-board .cn-index-page--sidebar-open .cn-page-header .cn-index-page__header-buttons {
|
|
79
|
+
grid-column: 1 / -1;
|
|
80
|
+
grid-row: 2;
|
|
81
|
+
justify-self: start;
|
|
82
|
+
margin-block: 6px 3px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.cn-look-board.cn-index-page--sidebar-open .cn-page-header .cn-header-trail,
|
|
86
|
+
.cn-look-board .cn-index-page--sidebar-open .cn-page-header .cn-header-trail {
|
|
87
|
+
grid-row: 3;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.cn-look-board.cn-index-page--sidebar-open .cn-page-header .cn-page-header__text > .cn-page-header__description,
|
|
91
|
+
.cn-look-board .cn-index-page--sidebar-open .cn-page-header .cn-page-header__text > .cn-page-header__description {
|
|
92
|
+
grid-row: 4;
|
|
93
|
+
}
|
|
94
|
+
|
|
74
95
|
/* The header buttons keep the board's button under a theme that styles every
|
|
75
96
|
secondary or primary button with `!important` (the nldesign theme: a
|
|
76
97
|
primary border, 8px 16px padding): Download and the Actions menu are 40px,
|
package/src/css/look-board.css
CHANGED
|
@@ -321,6 +321,20 @@
|
|
|
321
321
|
border: 0;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/* A dashboard banner's kicker is a 13px bold line in sentence case, no
|
|
325
|
+
tracking (DqMijnWerk "Vandaag eerst", BqMijnGoedkeuringen "First, today");
|
|
326
|
+
the plain look keeps its spaced capitals. The kicker is a paragraph, so its
|
|
327
|
+
size and weight also go to the theme's paragraph tokens.
|
|
328
|
+
@spec openspec/changes/board-banner-kicker-case/specs/board-banner-kicker-case/spec.md#requirement-a-board-dashboard-banners-kicker-is-in-sentence-case */
|
|
329
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-banner-widget__kicker {
|
|
330
|
+
--nldesign-component-paragraph-font-size: 13px;
|
|
331
|
+
--nldesign-component-paragraph-font-weight: 700;
|
|
332
|
+
font-size: 13px;
|
|
333
|
+
font-weight: 700;
|
|
334
|
+
letter-spacing: 0;
|
|
335
|
+
text-transform: none;
|
|
336
|
+
}
|
|
337
|
+
|
|
324
338
|
/* ---- A workspace filter form (CnWorkspaceFilterWidget) ----------------- */
|
|
325
339
|
/* The boards draw the form's field and its load button 40px tall, the button
|
|
326
340
|
16px in at both sides (DqTermijnen "Jaar" with "Rapport laden"); the form
|
|
@@ -647,6 +661,13 @@
|
|
|
647
661
|
--button-size: 34px;
|
|
648
662
|
}
|
|
649
663
|
|
|
664
|
+
/* Two buttons in a card's title row stand 8px apart (DqZaakContact "Live
|
|
665
|
+
gesprek", DqZaakEmail "Mail koppelen" / "Mail opstellen").
|
|
666
|
+
@spec openspec/changes/board-card-header-actions-gap/specs/board-card-header-actions-gap/spec.md#requirement-a-board-cards-title-row-buttons-stand-8px-apart */
|
|
667
|
+
.cn-look-board .cn-widget-wrapper__actions:not(.cn-look-nextcloud *) {
|
|
668
|
+
gap: 8px;
|
|
669
|
+
}
|
|
670
|
+
|
|
650
671
|
/* A detail page marked `cardButtons: "regular"` draws them at the 40px
|
|
651
672
|
control height: the case page's tab cards (DqZaakContact "Live gesprek",
|
|
652
673
|
DqZaakHistorie, DqZaakBetrokkenen), where setting and case-type pages keep 34.
|