@conduction/nextcloud-vue 2.101.0 → 2.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +22 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +20 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +70 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +26 -2
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/l10n/en.json.js +5 -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 +232 -187
- package/dist/esm/utils/builtInFormatters.js +24 -4
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/rowClassRules.js +43 -0
- package/dist/esm/utils/rowClassRules.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +19 -2
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1464 -1053
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +232 -187
- package/l10n/en.json +5 -1
- package/l10n/nl.json +5 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
- package/src/components/CnChartWidget/CnChartWidget.md +3 -2
- package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
- package/src/components/CnDashboardPage/CnDashboardPage.vue +21 -1
- package/src/components/CnIndexPage/CnIndexPage.md +5 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +74 -6
- package/src/components/CnIndexPage/useNamedSource.js +26 -2
- package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
- package/src/composables/indexSources.js +9 -0
- package/src/css/look-board-index.css +10 -0
- package/src/schemas/app-manifest-v2.schema.json +55 -3
- package/src/utils/builtInFormatters.js +24 -4
- package/src/utils/rowClassRules.js +39 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +19 -2
|
@@ -24,8 +24,28 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<!-- Chips mode (default): the clickable tab strip. -->
|
|
26
26
|
<div v-else
|
|
27
|
+
ref="chipsRoot"
|
|
27
28
|
class="cn-quick-filter-bar"
|
|
28
|
-
:class="{ 'cn-quick-filter-bar--inline': inline }">
|
|
29
|
+
:class="{ 'cn-quick-filter-bar--inline': inline, 'cn-quick-filter-bar--fit': fit }">
|
|
30
|
+
<!-- With `fit`: an invisible copy of every chip and the overflow chip,
|
|
31
|
+
measured to work out how many chips the bar's width holds. -->
|
|
32
|
+
<div v-if="fit"
|
|
33
|
+
ref="measure"
|
|
34
|
+
class="cn-quick-filter-bar__measure"
|
|
35
|
+
aria-hidden="true">
|
|
36
|
+
<span v-for="entry in entries"
|
|
37
|
+
:key="'m:' + entry.tab.label + ':' + entry.index"
|
|
38
|
+
class="cn-quick-filter-bar__tab"
|
|
39
|
+
:class="{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }"
|
|
40
|
+
data-measure="chip">
|
|
41
|
+
<span class="cn-quick-filter-bar__label">{{ tl(entry.tab.label) }}</span>
|
|
42
|
+
<span v-if="countOf(entry) !== null" class="cn-quick-filter-bar__count">{{ ' ' + countOf(entry) }}</span>
|
|
43
|
+
</span>
|
|
44
|
+
<span class="cn-quick-filter-bar__tab cn-quick-filter-bar__more" data-measure="more">
|
|
45
|
+
<span v-if="overflowLabel" class="cn-quick-filter-bar__label">{{ overflowLabel }}</span>
|
|
46
|
+
<DotsHorizontal :size="16" class="cn-quick-filter-bar__icon" />
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
29
49
|
<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a
|
|
30
50
|
child, because a role="tablist" may not contain a menu button. -->
|
|
31
51
|
<!-- Board look: the tabs are chips, pressed buttons in a group, not a
|
|
@@ -266,6 +286,19 @@ export default {
|
|
|
266
286
|
type: [Object, Array],
|
|
267
287
|
default: null,
|
|
268
288
|
},
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Chips mode only: show as many chips as the bar's own width holds and
|
|
292
|
+
* move the rest into the overflow chip, measured again whenever the
|
|
293
|
+
* width, the tabs or the counts change. The bar takes the room it is
|
|
294
|
+
* given (CnIndexPage's `quickFilterPlacement: "toolbar"` gives it what
|
|
295
|
+
* the Filter button and the view switch leave on their row). With
|
|
296
|
+
* `maxVisible` too, the smaller of the two wins.
|
|
297
|
+
*/
|
|
298
|
+
fit: {
|
|
299
|
+
type: Boolean,
|
|
300
|
+
default: false,
|
|
301
|
+
},
|
|
269
302
|
},
|
|
270
303
|
|
|
271
304
|
emits: ['update:active-index', 'update:selected-indices'],
|
|
@@ -274,6 +307,8 @@ export default {
|
|
|
274
307
|
return {
|
|
275
308
|
/** Whether the overflow chip's panel is open. */
|
|
276
309
|
moreOpen: false,
|
|
310
|
+
/** With `fit`: how many chips the width holds; null before the first measure. */
|
|
311
|
+
fitCount: null,
|
|
277
312
|
}
|
|
278
313
|
},
|
|
279
314
|
|
|
@@ -303,10 +338,24 @@ export default {
|
|
|
303
338
|
* @return {Array<{tab: object, index: number}>}
|
|
304
339
|
*/
|
|
305
340
|
visibleEntries() {
|
|
306
|
-
|
|
341
|
+
const limit = this.effectiveMax
|
|
342
|
+
if (!limit || this.tabs.length <= limit) {
|
|
307
343
|
return this.entries
|
|
308
344
|
}
|
|
309
|
-
return this.entries.slice(0,
|
|
345
|
+
return this.entries.slice(0, limit)
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* How many chips render: `maxVisible`, and with `fit` no more than the
|
|
350
|
+
* width holds; 0 for no limit.
|
|
351
|
+
*
|
|
352
|
+
* @return {number}
|
|
353
|
+
*/
|
|
354
|
+
effectiveMax() {
|
|
355
|
+
if (!this.fit || this.fitCount === null) {
|
|
356
|
+
return this.maxVisible
|
|
357
|
+
}
|
|
358
|
+
return this.maxVisible ? Math.min(this.maxVisible, this.fitCount) : this.fitCount
|
|
310
359
|
},
|
|
311
360
|
|
|
312
361
|
/**
|
|
@@ -316,10 +365,11 @@ export default {
|
|
|
316
365
|
* @return {Array<{tab: object, index: number}>}
|
|
317
366
|
*/
|
|
318
367
|
overflowEntries() {
|
|
319
|
-
|
|
368
|
+
const limit = this.effectiveMax
|
|
369
|
+
if (!limit || this.tabs.length <= limit) {
|
|
320
370
|
return []
|
|
321
371
|
}
|
|
322
|
-
return this.entries.slice(
|
|
372
|
+
return this.entries.slice(limit)
|
|
323
373
|
},
|
|
324
374
|
|
|
325
375
|
/**
|
|
@@ -399,7 +449,79 @@ export default {
|
|
|
399
449
|
},
|
|
400
450
|
},
|
|
401
451
|
|
|
452
|
+
watch: {
|
|
453
|
+
tabs() {
|
|
454
|
+
this.scheduleFit()
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
counts: {
|
|
458
|
+
deep: true,
|
|
459
|
+
handler() {
|
|
460
|
+
this.scheduleFit()
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
mounted() {
|
|
466
|
+
if (this.fit && typeof ResizeObserver === 'function' && this.$refs.chipsRoot) {
|
|
467
|
+
this.fitObserver = new ResizeObserver(() => this.measureFit())
|
|
468
|
+
this.fitObserver.observe(this.$refs.chipsRoot)
|
|
469
|
+
}
|
|
470
|
+
this.scheduleFit()
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
beforeUnmount() {
|
|
474
|
+
if (this.fitObserver) {
|
|
475
|
+
this.fitObserver.disconnect()
|
|
476
|
+
this.fitObserver = null
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
|
|
402
480
|
methods: {
|
|
481
|
+
/** Measure the fit after the next render. */
|
|
482
|
+
scheduleFit() {
|
|
483
|
+
if (this.fit) {
|
|
484
|
+
this.$nextTick(() => this.measureFit())
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Work out how many chips the bar's width holds, from the invisible
|
|
490
|
+
* copy: every chip that fits, and when not all do, room left for the
|
|
491
|
+
* overflow chip. At least one chip always shows.
|
|
492
|
+
*/
|
|
493
|
+
measureFit() {
|
|
494
|
+
const box = this.$refs.measure
|
|
495
|
+
const root = this.$refs.chipsRoot
|
|
496
|
+
if (!this.fit || !box || !root) {
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
const available = root.clientWidth
|
|
500
|
+
const chips = [...box.querySelectorAll('[data-measure="chip"]')].map((el) => el.offsetWidth)
|
|
501
|
+
const more = box.querySelector('[data-measure="more"]')
|
|
502
|
+
const moreWidth = more ? more.offsetWidth : 0
|
|
503
|
+
const gap = parseFloat(getComputedStyle(box).columnGap) || 8
|
|
504
|
+
if (available <= 0 || chips.length === 0) {
|
|
505
|
+
this.fitCount = null
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
const total = chips.reduce((sum, width) => sum + width, 0) + gap * (chips.length - 1)
|
|
509
|
+
if (total <= available) {
|
|
510
|
+
this.fitCount = null
|
|
511
|
+
return
|
|
512
|
+
}
|
|
513
|
+
let used = moreWidth
|
|
514
|
+
let count = 0
|
|
515
|
+
for (const width of chips) {
|
|
516
|
+
if (used + gap + width > available) {
|
|
517
|
+
break
|
|
518
|
+
}
|
|
519
|
+
used += gap + width
|
|
520
|
+
count++
|
|
521
|
+
}
|
|
522
|
+
this.fitCount = Math.max(1, count)
|
|
523
|
+
},
|
|
524
|
+
|
|
403
525
|
/**
|
|
404
526
|
* A manifest string through the host's label lookup (`cnTranslate`), so a key
|
|
405
527
|
* such as "Mine" reads in the user's language.
|
|
@@ -531,6 +653,38 @@ export default {
|
|
|
531
653
|
/* Inline variant — bare strip for embedding inside another bar (e.g. the
|
|
532
654
|
CnActionsBar #filters slot), so it reads as a control beside the view toggle
|
|
533
655
|
rather than a separate bordered row. */
|
|
656
|
+
/* With `fit`: one line, as wide as it is given; the chips it cannot hold
|
|
657
|
+
are in the overflow chip. The measuring copy takes no room and is never
|
|
658
|
+
seen or read. */
|
|
659
|
+
.cn-quick-filter-bar--fit {
|
|
660
|
+
position: relative;
|
|
661
|
+
flex-wrap: nowrap;
|
|
662
|
+
min-width: 0;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.cn-quick-filter-bar--fit .cn-quick-filter-bar__tabs {
|
|
666
|
+
flex-wrap: nowrap;
|
|
667
|
+
min-width: 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.cn-quick-filter-bar__measure {
|
|
671
|
+
position: absolute;
|
|
672
|
+
inset-block-start: 0;
|
|
673
|
+
inset-inline-start: 0;
|
|
674
|
+
display: flex;
|
|
675
|
+
gap: 8px;
|
|
676
|
+
width: 0;
|
|
677
|
+
height: 0;
|
|
678
|
+
overflow: hidden;
|
|
679
|
+
visibility: hidden;
|
|
680
|
+
pointer-events: none;
|
|
681
|
+
white-space: nowrap;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.cn-quick-filter-bar__measure > * {
|
|
685
|
+
flex: none;
|
|
686
|
+
}
|
|
687
|
+
|
|
534
688
|
.cn-quick-filter-bar--inline {
|
|
535
689
|
padding: 0;
|
|
536
690
|
border-bottom: none;
|
|
@@ -88,8 +88,8 @@ import { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatch
|
|
|
88
88
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
|
|
89
89
|
import { pageFilter, pageSort } from '../../utils/pageControls.js'
|
|
90
90
|
import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
91
|
+
import { compileRowClass } from '../../utils/rowClassRules.js'
|
|
91
92
|
import { fieldsFromSchema } from '../../utils/schema.js'
|
|
92
|
-
import { compareVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'
|
|
93
93
|
import { CnIcon } from '../CnIcon/index.js'
|
|
94
94
|
import { CnRowActions } from '../CnRowActions/index.js'
|
|
95
95
|
import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
|
|
@@ -762,28 +762,7 @@ export default {
|
|
|
762
762
|
* @return {?(row: object) => string} The row-class function, or null.
|
|
763
763
|
*/
|
|
764
764
|
compiledRowClass() {
|
|
765
|
-
|
|
766
|
-
if (typeof rc === 'function') {
|
|
767
|
-
return rc
|
|
768
|
-
}
|
|
769
|
-
if (!Array.isArray(rc) || rc.length === 0) {
|
|
770
|
-
return null
|
|
771
|
-
}
|
|
772
|
-
const rules = rc
|
|
773
|
-
return (row) => {
|
|
774
|
-
const classes = []
|
|
775
|
-
for (const rule of rules) {
|
|
776
|
-
if (!rule || !rule.class) {
|
|
777
|
-
continue
|
|
778
|
-
}
|
|
779
|
-
const when = rule.when || {}
|
|
780
|
-
const actual = readVisibleWhenPath(row, when.field)
|
|
781
|
-
if (compareVisibleWhen(actual, when.op || 'eq', when.value)) {
|
|
782
|
-
classes.push(rule.class)
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
return classes.join(' ')
|
|
786
|
-
}
|
|
765
|
+
return compileRowClass(this.rowClass)
|
|
787
766
|
},
|
|
788
767
|
|
|
789
768
|
/**
|
|
@@ -530,6 +530,15 @@ function tasksSource() {
|
|
|
530
530
|
*/
|
|
531
531
|
loading: () => !!store.loading,
|
|
532
532
|
|
|
533
|
+
// The inbox pages: the page sends `limit` and `offset` and draws the
|
|
534
|
+
// pager from the datastore total.
|
|
535
|
+
pages: true,
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* @return {number} The datastore total, independent of the page size.
|
|
539
|
+
*/
|
|
540
|
+
total: () => Number(store.total) || 0,
|
|
541
|
+
|
|
533
542
|
/**
|
|
534
543
|
* Count the inbox for a config without touching the rows on screen,
|
|
535
544
|
* for a quick-filter tab with `showCount`.
|
|
@@ -128,6 +128,16 @@
|
|
|
128
128
|
gap: 8px;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/* `quickFilterPlacement: "toolbar"` (DqTeamwachtrij): the chips take the room
|
|
132
|
+
the Filter button and the view switch leave on row 1, so the three stay on
|
|
133
|
+
one row in the column beside a side panel; the chips that do not fit move
|
|
134
|
+
into the overflow chip (CnQuickFilterBar `fit`) instead of pushing the
|
|
135
|
+
buttons onto a row of their own. */
|
|
136
|
+
.cn-look-board.cn-index-page--quick-filters-toolbar .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-quick-filter-bar {
|
|
137
|
+
flex: 1 1 0;
|
|
138
|
+
min-width: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
/* Row 2: the search field and the active filters on a line of their own. */
|
|
132
142
|
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--search {
|
|
133
143
|
display: flex;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
|
|
4
4
|
"title": "Conduction App Manifest v2",
|
|
5
5
|
"description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.125.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -2221,7 +2221,8 @@
|
|
|
2221
2221
|
},
|
|
2222
2222
|
"emptyLabel": { "type": "string", "description": "Empty-state message rendered instead of the chart when the resolved series contain no data points." },
|
|
2223
2223
|
"barMax": { "type": "number", "exclusiveMinimum": 0, "description": "chartKind 'bar-list': the value a full track stands for (100 for percentages). Omitted = the largest value." },
|
|
2224
|
-
"footnote": { "type": "string", "description": "
|
|
2224
|
+
"footnote": { "type": "string", "description": "A line under the chart, through the host's translate: under the rows of a bar-list or matrix, and under the plot of a line, area or bar chart." },
|
|
2225
|
+
"emptyCellLabel": { "type": "string", "description": "chartKind 'matrix': the text of a cell without a value (\"geen\"), through the host's translate. Empty leaves the cell blank. Passed through to CnChartWidget." },
|
|
2225
2226
|
"highlight": {
|
|
2226
2227
|
"type": "object",
|
|
2227
2228
|
"additionalProperties": false,
|
|
@@ -3738,7 +3739,58 @@
|
|
|
3738
3739
|
"enum": ["flows", "flowRuns", "tasks", "endpoint", "search", "users", "organisations"],
|
|
3739
3740
|
"description": "Name a registered NON-OBJECT entity collection for a type='index' page, instead of register+schema. Deliberately NOT called `source`: that key is already taken on page config and is polymorphic (a URL string in some manifests, an object with `params` in others), so reusing it would give one key two meanings and break six deployed shillinq pages. A flow definition is deliberately not an OpenRegister object, so an index had nothing to bind to and such lists became bespoke type='custom' pages. With a source the index loads the list itself. Takes precedence over register/schema; non-empty `objects` still wins over both. Registered sources live in src/composables/indexSources.js. `tasks` lists the viewer's task inbox from OpenRegister's flow-tasks read (cn-tasks-entity-source). `organisations` lists the OpenRegister organisations the viewer belongs to. On a type='detail' page a source that reads one record (today `organisations`) loads the record whose id is the route's objectId, instead of register+schema, and feeds the header, pills, widgets and @objectId / @object.* tokens like a fetched object (organisations-entity-source)."
|
|
3740
3741
|
},
|
|
3741
|
-
"
|
|
3742
|
+
"bulkBandAlways": { "type": "boolean", "default": false, "description": "type='index', board look: draw the bulk band (its actions and hint) before a selection too, its buttons disabled until rows are selected (DqZaken, DqWerkbord). Maps to CnIndexPage `bulkBandAlways`." },
|
|
3743
|
+
"quickFilterPlacement": { "type": "string", "enum": ["", "toolbar"], "default": "", "description": "type='index': where the quick-filter chips sit. `toolbar` keeps them on one row with the Filter button and the view switch beside a side panel, the chips that do not fit behind the overflow chip (DqTeamwachtrij). Maps to CnIndexPage `quickFilterPlacement`." },
|
|
3744
|
+
"rowClass": {
|
|
3745
|
+
"type": "array",
|
|
3746
|
+
"description": "type='index': per-row CSS class rules, compiled into the table's rowClass function as on the object-table widget. Each rule { when: { field, op?, value }, class } adds `class` to a row whose `field` (a dot-path) compares to `value` with `op` (default eq; the visibleWhen operators and tokens such as `@today`, with two dates compared as moments), e.g. an overdue task `{ when: { field: \"dueAt\", op: \"lt\", value: \"@today\" }, class: \"cn-row--overdue\" }`. Maps to CnIndexPage `rowClass`.",
|
|
3747
|
+
"items": {
|
|
3748
|
+
"type": "object",
|
|
3749
|
+
"additionalProperties": false,
|
|
3750
|
+
"required": [
|
|
3751
|
+
"when",
|
|
3752
|
+
"class"
|
|
3753
|
+
],
|
|
3754
|
+
"properties": {
|
|
3755
|
+
"when": {
|
|
3756
|
+
"type": "object",
|
|
3757
|
+
"additionalProperties": false,
|
|
3758
|
+
"required": [
|
|
3759
|
+
"field",
|
|
3760
|
+
"value"
|
|
3761
|
+
],
|
|
3762
|
+
"description": "The predicate evaluated against the row (shared visibleWhen grammar, LOCAL form).",
|
|
3763
|
+
"properties": {
|
|
3764
|
+
"field": {
|
|
3765
|
+
"type": "string",
|
|
3766
|
+
"description": "Dot-path into the row."
|
|
3767
|
+
},
|
|
3768
|
+
"op": {
|
|
3769
|
+
"type": "string",
|
|
3770
|
+
"enum": [
|
|
3771
|
+
"eq",
|
|
3772
|
+
"neq",
|
|
3773
|
+
"gt",
|
|
3774
|
+
"gte",
|
|
3775
|
+
"lt",
|
|
3776
|
+
"lte"
|
|
3777
|
+
],
|
|
3778
|
+
"default": "eq",
|
|
3779
|
+
"description": "Comparison operator (default eq)."
|
|
3780
|
+
},
|
|
3781
|
+
"value": {
|
|
3782
|
+
"description": "Literal right-hand side of the comparison."
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
},
|
|
3786
|
+
"class": {
|
|
3787
|
+
"type": "string",
|
|
3788
|
+
"description": "CSS class added to the row when `when` holds."
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
},
|
|
3793
|
+
"sourceConfig": { "type": "object", "additionalProperties": true, "description": "type='index' with `entitySource`: what the named source loads with. `endpoint`: `url` (an app path, required) and `rowsPath` (default `results`); its other keys go along as query parameters. `search`: `kinds` (`{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }` or `{ id, label, files: true }`), `term` and `termQuery` (default `q`). `users`: `defaultGroup`, `functionField`, `rolesField`. `flows` / `tasks`: `app`, `scope`. Maps to CnIndexPage `sourceConfig`.", "properties": { "url": { "type": "string", "minLength": 1 }, "rowsPath": { "type": "string", "minLength": 1 }, "kinds": { "type": "array", "items": { "type": "object", "required": ["id"], "additionalProperties": true, "properties": { "id": { "type": "string", "minLength": 1 }, "label": { "type": "string" }, "register": { "type": "string" }, "schema": { "type": "string" }, "files": { "type": "boolean" } } } }, "term": { "type": "string" }, "termQuery": { "type": "string", "minLength": 1 }, "defaultGroup": { "type": "string", "minLength": 1 }, "limit": { "type": "integer", "minimum": 1, "description": "A paging source: rows per page (20 by default); the pager moves through the pages." } } },
|
|
3742
3794
|
"app": {
|
|
3743
3795
|
"type": "string",
|
|
3744
3796
|
"description": "Scopes a named `entitySource` to one app (e.g. entitySource='flows' with app='dossiq' lists that app's flows)."
|
|
@@ -379,25 +379,45 @@ const CONNECTION_STATUS_LABELS = {
|
|
|
379
379
|
error: 'Error',
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
/**
|
|
383
|
+
* The short labels `formatterOptions.short` draws (board DqInstellingen
|
|
384
|
+
* "Aan / Uit / Fout"): a connection that works in some form is on, one that
|
|
385
|
+
* does not run is off, and a failing one is an error.
|
|
386
|
+
*/
|
|
387
|
+
const CONNECTION_STATUS_SHORT_LABELS = {
|
|
388
|
+
configured: 'On',
|
|
389
|
+
limited: 'On',
|
|
390
|
+
simulated: 'On',
|
|
391
|
+
unconfigured: 'Off',
|
|
392
|
+
disabled: 'Off',
|
|
393
|
+
unavailable: 'Off',
|
|
394
|
+
error: 'Error',
|
|
395
|
+
}
|
|
396
|
+
|
|
382
397
|
/**
|
|
383
398
|
* Connection status formatter (`connectionStatus` registry key) for the
|
|
384
399
|
* fleet connection registry. Renders one of the seven stored status values
|
|
385
400
|
* as its translated label through the library's own slug (`nextcloud-vue`).
|
|
401
|
+
* With `formatterOptions.short` it renders the board's three short labels
|
|
402
|
+
* instead: On (configured, limited, simulated), Off (not configured,
|
|
403
|
+
* switched off, not available) and Error.
|
|
386
404
|
*
|
|
387
405
|
* An unknown value passes through unchanged, so a status added later still
|
|
388
406
|
* shows as its raw value instead of an empty cell. Null and undefined render
|
|
389
407
|
* as `''`. Never throws.
|
|
390
408
|
*
|
|
391
409
|
* @param {unknown} value The stored status value.
|
|
410
|
+
* @param {object} [row] The row (unused).
|
|
411
|
+
* @param {object} [property] The column's property (unused).
|
|
412
|
+
* @param {{short?: boolean}} [options] The column's `formatterOptions`.
|
|
392
413
|
* @return {string} The translated label, or the value itself when it is not one of the seven.
|
|
393
414
|
*/
|
|
394
|
-
export function formatConnectionStatus(value) {
|
|
415
|
+
export function formatConnectionStatus(value, row, property, options) {
|
|
395
416
|
if (value === null || value === undefined) {
|
|
396
417
|
return ''
|
|
397
418
|
}
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
: null
|
|
419
|
+
const labels = options && options.short === true ? CONNECTION_STATUS_SHORT_LABELS : CONNECTION_STATUS_LABELS
|
|
420
|
+
const label = Object.hasOwn(labels, value) ? labels[value] : null
|
|
401
421
|
return label ? t('nextcloud-vue', label) : String(value)
|
|
402
422
|
}
|
|
403
423
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
import { compareVisibleWhen, readVisibleWhenPath } from './visibleWhen.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compile a row-class declaration into the `(row) => string` CnDataTable
|
|
9
|
+
* takes. A function passes straight through. An array of rules
|
|
10
|
+
* `[{ when: { field, op?, value }, class }]` adds each rule's `class` to a row
|
|
11
|
+
* whose `field` (a dot-path) compares to `value` with `op` (`eq` by default;
|
|
12
|
+
* the shared `visibleWhen` operators, `@today` and the other tokens included),
|
|
13
|
+
* every matching class space-joined: an overdue row can be marked from the
|
|
14
|
+
* manifest (`{ when: { field: "dueAt", op: "lt", value: "@today" }, class:
|
|
15
|
+
* "cn-row--overdue" }`). Anything else, or an empty list, is null.
|
|
16
|
+
*
|
|
17
|
+
* @param {Function|Array<{when: {field: string, op?: string, value: unknown}, class: string}>|null} rowClass The declaration.
|
|
18
|
+
* @return {?(row: object) => string} The row-class function, or null.
|
|
19
|
+
*/
|
|
20
|
+
export function compileRowClass(rowClass) {
|
|
21
|
+
if (typeof rowClass === 'function') {
|
|
22
|
+
return rowClass
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(rowClass) || rowClass.length === 0) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
const rules = rowClass.filter((rule) => rule && typeof rule.class === 'string' && rule.class !== '')
|
|
28
|
+
return (row) => {
|
|
29
|
+
const classes = []
|
|
30
|
+
for (const rule of rules) {
|
|
31
|
+
const when = rule.when || {}
|
|
32
|
+
const actual = readVisibleWhenPath(row, when.field)
|
|
33
|
+
if (compareVisibleWhen(actual, when.op || 'eq', when.value)) {
|
|
34
|
+
classes.push(rule.class)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return classes.join(' ')
|
|
38
|
+
}
|
|
39
|
+
}
|