@conduction/nextcloud-vue 2.129.0 → 2.130.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 +23 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +87 -4
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +29 -3
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +68 -14
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue.js +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +16 -5
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesCard/CnFilesCard.vue.js +1 -1
- package/dist/esm/components/CnFilesCard/CnFilesCard.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesCard/CnFilesCard.vue3.js +11 -2
- package/dist/esm/components/CnFilesCard/CnFilesCard.vue3.js.map +1 -1
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -0
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +32 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +62 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +515 -356
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +6 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +17 -9
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +323 -165
- package/dist/esm/utils/boardReferenceColumns.js +38 -1
- package/dist/esm/utils/boardReferenceColumns.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +989 -479
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +323 -165
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +67 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +59 -8
- package/src/components/CnEventsCard/CnEventsCard.vue +16 -5
- package/src/components/CnFilesCard/CnFilesCard.vue +7 -0
- package/src/components/CnIcon/semanticIcons.js +2 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +34 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +186 -8
- package/src/css/look-board-detail.css +27 -2
- package/src/css/look-board-index.css +50 -2
- package/src/css/look-board.css +52 -2
- package/src/integrations/builtin/calendar/CnCalendarCard.vue +12 -3
- package/src/integrations/builtin/calendar/__tests__/CnCalendarCardHeaderActions.spec.js +12 -1
- package/src/schemas/app-manifest-v2.schema.json +27 -3
- package/src/utils/boardReferenceColumns.js +37 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -64,6 +64,9 @@
|
|
|
64
64
|
{{ col.label }}
|
|
65
65
|
</template>
|
|
66
66
|
</th>
|
|
67
|
+
<th v-if="rowActions.length > 0" scope="col" class="cn-record-list__cell--actions">
|
|
68
|
+
{{ rowActionsHeader }}
|
|
69
|
+
</th>
|
|
67
70
|
</tr>
|
|
68
71
|
</thead>
|
|
69
72
|
<tbody>
|
|
@@ -78,12 +81,34 @@
|
|
|
78
81
|
:label="cell.text"
|
|
79
82
|
:variant="cell.tone"
|
|
80
83
|
size="small" />
|
|
81
|
-
<router-link v-else-if="cellIndex === 0 && item.to" :to="item.to">
|
|
84
|
+
<router-link v-else-if="cellIndex === 0 && item.to" :to="item.to" class="cn-record-list__cell-text">
|
|
82
85
|
{{ cell.text }}
|
|
83
86
|
</router-link>
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
<span v-else class="cn-record-list__cell-text">{{ cell.text }}</span>
|
|
88
|
+
<span v-if="cell.sub" class="cn-record-list__cell-sub">{{ cell.sub }}</span>
|
|
89
|
+
</td>
|
|
90
|
+
<td v-if="rowActions.length > 0" class="cn-record-list__cell--actions">
|
|
91
|
+
<NcActions
|
|
92
|
+
v-if="item.rowActions.length > 0"
|
|
93
|
+
:aria-label="rowActionsLabel"
|
|
94
|
+
data-testid="cn-record-list-row-actions">
|
|
95
|
+
<template v-for="entry in item.rowActions" :key="`row-action-${entry.index}`">
|
|
96
|
+
<NcActionLink
|
|
97
|
+
v-if="entry.href"
|
|
98
|
+
:href="entry.href"
|
|
99
|
+
:target="entry.external ? '_blank' : undefined"
|
|
100
|
+
:data-testid="`cn-record-list-row-action-${entry.index}`">
|
|
101
|
+
{{ entry.action.label }}
|
|
102
|
+
</NcActionLink>
|
|
103
|
+
<NcActionButton
|
|
104
|
+
v-else
|
|
105
|
+
:closeAfterClick="true"
|
|
106
|
+
:data-testid="`cn-record-list-row-action-${entry.index}`"
|
|
107
|
+
@click="runRowActionEntry(entry.action, item.row)">
|
|
108
|
+
{{ entry.action.label }}
|
|
109
|
+
</NcActionButton>
|
|
110
|
+
</template>
|
|
111
|
+
</NcActions>
|
|
87
112
|
</td>
|
|
88
113
|
</tr>
|
|
89
114
|
</tbody>
|
|
@@ -99,14 +124,38 @@
|
|
|
99
124
|
<!-- `groupBy`: a heading per group, each group its own list, named by
|
|
100
125
|
its heading. Without it this is one list, as before. -->
|
|
101
126
|
<template v-for="group in itemGroups" v-else :key="group.key">
|
|
127
|
+
<div
|
|
128
|
+
v-if="group.label && footerGroup !== '' && group.value === footerGroup"
|
|
129
|
+
class="cn-record-list__group-head"
|
|
130
|
+
data-testid="cn-record-list-group-head">
|
|
131
|
+
<h3
|
|
132
|
+
:id="group.headingId"
|
|
133
|
+
class="cn-record-list__group-heading"
|
|
134
|
+
data-testid="cn-record-list-group-heading">
|
|
135
|
+
{{ group.label }}
|
|
136
|
+
</h3>
|
|
137
|
+
<NcButton
|
|
138
|
+
variant="secondary"
|
|
139
|
+
:class="{ 'cn-record-list__action--tonal': footerTonal }"
|
|
140
|
+
:disabled="footerDisabled"
|
|
141
|
+
:aria-describedby="footerDisabled && footerAction.reason ? footerReasonId : null"
|
|
142
|
+
data-testid="cn-record-list-footer-action"
|
|
143
|
+
@click="runFooterAction">
|
|
144
|
+
<template v-if="footerAction.icon" #icon>
|
|
145
|
+
<CnIcon :name="footerAction.icon" :size="16" />
|
|
146
|
+
</template>
|
|
147
|
+
{{ footerAction.label }}
|
|
148
|
+
</NcButton>
|
|
149
|
+
</div>
|
|
102
150
|
<h3
|
|
103
|
-
v-if="group.label"
|
|
151
|
+
v-else-if="group.label"
|
|
104
152
|
:id="group.headingId"
|
|
105
153
|
class="cn-record-list__group-heading"
|
|
106
154
|
data-testid="cn-record-list-group-heading">
|
|
107
155
|
{{ group.label }}
|
|
108
156
|
</h3>
|
|
109
157
|
<ul
|
|
158
|
+
v-if="group.items.length > 0"
|
|
110
159
|
class="cn-record-list__list"
|
|
111
160
|
:aria-labelledby="group.label ? group.headingId : null"
|
|
112
161
|
:data-testid="isGrouped ? 'cn-record-list-group' : null">
|
|
@@ -360,11 +409,39 @@
|
|
|
360
409
|
says why (`disabledReason`: "Kan zodra de beslissing is
|
|
361
410
|
verzonden"). -->
|
|
362
411
|
<div
|
|
363
|
-
v-if="
|
|
412
|
+
v-if="footerGroupHead && rows.length === 0 && !error && !loading"
|
|
413
|
+
class="cn-record-list__group-head"
|
|
414
|
+
data-testid="cn-record-list-group-head">
|
|
415
|
+
<h3 class="cn-record-list__group-heading" data-testid="cn-record-list-group-heading">
|
|
416
|
+
{{ footerGroupHead }}
|
|
417
|
+
</h3>
|
|
418
|
+
<NcButton
|
|
419
|
+
variant="secondary"
|
|
420
|
+
:class="{ 'cn-record-list__action--tonal': footerTonal }"
|
|
421
|
+
:disabled="footerDisabled"
|
|
422
|
+
:aria-describedby="footerDisabled && footerAction.reason ? footerReasonId : null"
|
|
423
|
+
data-testid="cn-record-list-footer-action"
|
|
424
|
+
@click="runFooterAction">
|
|
425
|
+
<template v-if="footerAction.icon" #icon>
|
|
426
|
+
<CnIcon :name="footerAction.icon" :size="16" />
|
|
427
|
+
</template>
|
|
428
|
+
{{ footerAction.label }}
|
|
429
|
+
</NcButton>
|
|
430
|
+
</div>
|
|
431
|
+
<p
|
|
432
|
+
v-if="footerGroup !== '' && footerDisabled && footerAction && footerAction.reason && !error"
|
|
433
|
+
:id="footerReasonId"
|
|
434
|
+
class="cn-record-list__footer-reason"
|
|
435
|
+
data-testid="cn-record-list-footer-reason">
|
|
436
|
+
{{ footerAction.reason }}
|
|
437
|
+
</p>
|
|
438
|
+
<div
|
|
439
|
+
v-if="footerAction && footerGroup === '' && !error"
|
|
364
440
|
class="cn-record-list__footer"
|
|
365
441
|
data-testid="cn-record-list-footer">
|
|
366
442
|
<NcButton
|
|
367
443
|
variant="secondary"
|
|
444
|
+
:class="{ 'cn-record-list__action--tonal': footerTonal }"
|
|
368
445
|
:disabled="footerDisabled"
|
|
369
446
|
:aria-describedby="footerDisabled && footerAction.reason ? footerReasonId : null"
|
|
370
447
|
data-testid="cn-record-list-footer-action"
|
|
@@ -661,6 +738,41 @@ export default {
|
|
|
661
738
|
}
|
|
662
739
|
},
|
|
663
740
|
|
|
741
|
+
/**
|
|
742
|
+
* The group value `footerAction.group` puts the action in, with
|
|
743
|
+
* `groupBy`; '' when the action stands under the list.
|
|
744
|
+
*
|
|
745
|
+
* @spec openspec/changes/record-list-group-action/specs/record-list/spec.md#requirement-a-record-lists-action-can-end-a-group-heading-as-a-tonal-pill
|
|
746
|
+
* @return {string}
|
|
747
|
+
*/
|
|
748
|
+
footerGroup() {
|
|
749
|
+
const group = this.content?.footerAction?.group
|
|
750
|
+
if (!this.footerAction || !this.isGrouped || group === null || group === undefined || group === '') {
|
|
751
|
+
return ''
|
|
752
|
+
}
|
|
753
|
+
return String(group)
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* The heading of the action's group, for the list without rows.
|
|
758
|
+
*
|
|
759
|
+
* @return {string}
|
|
760
|
+
*/
|
|
761
|
+
footerGroupHead() {
|
|
762
|
+
if (this.footerGroup === '') {
|
|
763
|
+
return ''
|
|
764
|
+
}
|
|
765
|
+
const c = this.content
|
|
766
|
+
const labels = c.valueLabels && typeof c.valueLabels[c.groupBy] === 'object' ? c.valueLabels[c.groupBy] : {}
|
|
767
|
+
const named = labels[this.footerGroup] || this.footerGroup
|
|
768
|
+
return this.translate(named)
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
/** @return {boolean} Whether the action is drawn as a tonal pill (`footerAction.variant: "tonal"`). */
|
|
772
|
+
footerTonal() {
|
|
773
|
+
return this.content?.footerAction?.variant === 'tonal'
|
|
774
|
+
},
|
|
775
|
+
|
|
664
776
|
/**
|
|
665
777
|
* What `footerAction.disabledWhen` depends on, so it is evaluated again
|
|
666
778
|
* when the condition, the record or the workspace changes.
|
|
@@ -842,7 +954,7 @@ export default {
|
|
|
842
954
|
* or not at all. Without `groupBy` all rows are one unheaded group.
|
|
843
955
|
*
|
|
844
956
|
* @spec openspec/changes/record-list-group-headings/specs/record-list/spec.md#requirement-a-record-list-can-group-its-rows-under-headings
|
|
845
|
-
* @return {Array<{key: string, label: string, headingId: string, items: Array<object>}>} The groups.
|
|
957
|
+
* @return {Array<{key: string, value: string, label: string, headingId: string, items: Array<object>}>} The groups.
|
|
846
958
|
*/
|
|
847
959
|
itemGroups() {
|
|
848
960
|
if (!this.isGrouped) {
|
|
@@ -869,10 +981,15 @@ export default {
|
|
|
869
981
|
if (buckets.has('')) {
|
|
870
982
|
values.push('')
|
|
871
983
|
}
|
|
984
|
+
if (this.footerGroup !== '' && !buckets.has(this.footerGroup)) {
|
|
985
|
+
buckets.set(this.footerGroup, [])
|
|
986
|
+
values.splice(buckets.has('') ? values.length - 1 : values.length, 0, this.footerGroup)
|
|
987
|
+
}
|
|
872
988
|
return values.map((value, index) => {
|
|
873
989
|
const named = value === '' ? c.groupEmptyLabel : (labels[value] || value)
|
|
874
990
|
return {
|
|
875
991
|
key: `group-${value}`,
|
|
992
|
+
value,
|
|
876
993
|
label: typeof named === 'string' && named !== '' ? this.translate(named) : '',
|
|
877
994
|
headingId: `${this.groupIdBase}-${index}`,
|
|
878
995
|
items: buckets.get(value),
|
|
@@ -904,6 +1021,8 @@ export default {
|
|
|
904
1021
|
const indentPath = this.content?.indentPath
|
|
905
1022
|
const rows = this.items.map((item) => ({
|
|
906
1023
|
key: item.key,
|
|
1024
|
+
row: item.row,
|
|
1025
|
+
rowActions: this.rowActions.length > 0 ? this.rowActionsOf(item.row) : [],
|
|
907
1026
|
to: item.attrs.to || null,
|
|
908
1027
|
depth: indentPath ? Math.max(0, Math.floor(Number(readPath(item.row, indentPath)) || 0)) : 0,
|
|
909
1028
|
cells: this.tableColumns.map((col) => {
|
|
@@ -913,6 +1032,7 @@ export default {
|
|
|
913
1032
|
return {
|
|
914
1033
|
key: col.key,
|
|
915
1034
|
text,
|
|
1035
|
+
sub: typeof col.sub === 'string' ? this.fill(col.sub, item.row) : '',
|
|
916
1036
|
align: col.align,
|
|
917
1037
|
alert: col.alertAbove !== undefined && Number.isFinite(limit) && text !== '' && Number.isFinite(value) && value > limit,
|
|
918
1038
|
tone: this.cellToneOf(col, item.row, text),
|
|
@@ -1110,6 +1230,17 @@ export default {
|
|
|
1110
1230
|
}))
|
|
1111
1231
|
},
|
|
1112
1232
|
|
|
1233
|
+
/**
|
|
1234
|
+
* The heading of the `table` presentation's actions column.
|
|
1235
|
+
*
|
|
1236
|
+
* @return {string}
|
|
1237
|
+
* @spec openspec/changes/record-list-table-row-actions/specs/record-list-table/spec.md#requirement-a-table-row-can-carry-a-second-line-and-an-actions-menu
|
|
1238
|
+
*/
|
|
1239
|
+
rowActionsHeader() {
|
|
1240
|
+
const own = this.content?.rowActionsHeader
|
|
1241
|
+
return typeof own === 'string' && own !== '' ? this.translate(own) : t('nextcloud-vue', 'Actions')
|
|
1242
|
+
},
|
|
1243
|
+
|
|
1113
1244
|
/** @return {string} The accessible name of a row's actions menu. */
|
|
1114
1245
|
rowActionsLabel() {
|
|
1115
1246
|
const own = this.content?.rowActionsLabel
|
|
@@ -1318,7 +1449,7 @@ export default {
|
|
|
1318
1449
|
if (!declared || this.footerDisabled) {
|
|
1319
1450
|
return undefined
|
|
1320
1451
|
}
|
|
1321
|
-
const { disabledWhen, disabledReason, ...action } = declared
|
|
1452
|
+
const { disabledWhen, disabledReason, group, variant, ...action } = declared
|
|
1322
1453
|
if (typeof this.cnDispatchAction === 'function') {
|
|
1323
1454
|
return this.cnDispatchAction(action)
|
|
1324
1455
|
}
|
|
@@ -1531,16 +1662,22 @@ export default {
|
|
|
1531
1662
|
* Run a `rowAction` that is not a plain link through the page's
|
|
1532
1663
|
* dispatcher: a `handler` gets the row as its last argument, an
|
|
1533
1664
|
* `open-modal` gets it as the `row` prop, an `object-op` acts on it.
|
|
1665
|
+
* With `endpoint` it calls that app URL for the row, as a `rowActions`
|
|
1666
|
+
* endpoint entry does.
|
|
1534
1667
|
*
|
|
1535
1668
|
* @param {object} row The row.
|
|
1536
1669
|
* @return {unknown} What the dispatcher returns.
|
|
1537
1670
|
* @spec openspec/changes/screens-record-list-row-action/specs/record-list/spec.md
|
|
1671
|
+
* @spec openspec/changes/record-list-row-action-endpoint/specs/record-list/spec.md#requirement-a-rows-one-action-can-call-an-app-endpoint-for-that-row
|
|
1538
1672
|
*/
|
|
1539
1673
|
runRowAction(row) {
|
|
1540
1674
|
const action = this.content?.rowAction
|
|
1541
1675
|
if (!action || typeof action !== 'object') {
|
|
1542
1676
|
return undefined
|
|
1543
1677
|
}
|
|
1678
|
+
if (typeof action.endpoint === 'string' && action.endpoint !== '') {
|
|
1679
|
+
return this.runRowActionEntry(action, row)
|
|
1680
|
+
}
|
|
1544
1681
|
const type = action.type || 'handler'
|
|
1545
1682
|
let wrapped = action
|
|
1546
1683
|
if (type === 'handler') {
|
|
@@ -2342,6 +2479,34 @@ export default {
|
|
|
2342
2479
|
line-height: 1.4;
|
|
2343
2480
|
}
|
|
2344
2481
|
|
|
2482
|
+
.cn-record-list__group-head {
|
|
2483
|
+
display: flex;
|
|
2484
|
+
align-items: center;
|
|
2485
|
+
justify-content: space-between;
|
|
2486
|
+
gap: 8px;
|
|
2487
|
+
margin-top: 6px;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
.cn-record-list__group-head .cn-record-list__group-heading {
|
|
2491
|
+
margin: 0;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
.cn-record-list__action--tonal.button-vue {
|
|
2495
|
+
min-height: 34px;
|
|
2496
|
+
height: 34px;
|
|
2497
|
+
padding: 0 12px;
|
|
2498
|
+
border: 0;
|
|
2499
|
+
border-radius: 17px;
|
|
2500
|
+
background-color: var(--color-primary-element-light);
|
|
2501
|
+
color: var(--color-primary-element-light-text);
|
|
2502
|
+
font-size: 13px;
|
|
2503
|
+
font-weight: 600;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
.cn-record-list__action--tonal.button-vue:hover:not(:disabled) {
|
|
2507
|
+
background-color: var(--color-primary-element-light-hover);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2345
2510
|
.cn-record-list__footer {
|
|
2346
2511
|
display: flex;
|
|
2347
2512
|
flex-direction: column;
|
|
@@ -2539,6 +2704,19 @@ export default {
|
|
|
2539
2704
|
border-bottom: 1px solid var(--color-border);
|
|
2540
2705
|
}
|
|
2541
2706
|
|
|
2707
|
+
.cn-record-list__cell-sub {
|
|
2708
|
+
display: block;
|
|
2709
|
+
color: var(--color-text-maxcontrast);
|
|
2710
|
+
font-size: 13px;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
.cn-record-list__table .cn-record-list__cell--actions {
|
|
2714
|
+
width: 1%;
|
|
2715
|
+
padding-inline-start: 12px;
|
|
2716
|
+
text-align: end;
|
|
2717
|
+
white-space: nowrap;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2542
2720
|
.cn-record-list__sort {
|
|
2543
2721
|
display: inline-flex;
|
|
2544
2722
|
gap: 4px;
|
|
@@ -562,14 +562,17 @@
|
|
|
562
562
|
font-size: 13px;
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
|
|
565
|
+
/* A message's channel pill: 20px in the soft ink (DqZaakContact). It names
|
|
566
|
+
the thread to outrank the component's scoped muted ink. */
|
|
567
|
+
.cn-look-board .cn-conversation-thread .cn-conversation-thread__channel {
|
|
566
568
|
padding: 1px 8px;
|
|
567
569
|
border: 1px solid var(--color-border);
|
|
568
570
|
border-radius: 12px;
|
|
569
571
|
background: var(--color-main-background);
|
|
570
|
-
color: var(--
|
|
572
|
+
color: var(--cn-board-text-soft);
|
|
571
573
|
font-size: 12px;
|
|
572
574
|
font-weight: 600;
|
|
575
|
+
line-height: 16px;
|
|
573
576
|
}
|
|
574
577
|
|
|
575
578
|
/* The case Status card as the board draws it: no grey box, the status's
|
|
@@ -1324,3 +1327,25 @@
|
|
|
1324
1327
|
.cn-look-board .cn-facts-widget__action:not(.cn-look-nextcloud *) {
|
|
1325
1328
|
font-weight: 600;
|
|
1326
1329
|
}
|
|
1330
|
+
|
|
1331
|
+
/* ---- A card's Add ------------------------------------------------------ */
|
|
1332
|
+
/* The board draws a list card's Add as the neutral secondary at the 40px
|
|
1333
|
+
control height, right-aligned in the title row (DqZaakGerelateerd
|
|
1334
|
+
"Deelzaak aanmaken", DqZaakTaken "Toevoegen"). CnDetailWidgetHost renders
|
|
1335
|
+
that button in the board look, so the list's footer Add bar, which ADR-062
|
|
1336
|
+
draws in the Nextcloud look, is hidden in the card. The size outranks a
|
|
1337
|
+
card title row's 34px buttons.
|
|
1338
|
+
@spec openspec/changes/board-object-list-add-button/specs/board-object-list-add-button/spec.md#requirement-the-board-look-draws-a-cards-add-as-a-title-row-button */
|
|
1339
|
+
.cn-look-board .cn-detail-page__catalog-card:not(.cn-look-nextcloud *) .cn-object-list-widget__add {
|
|
1340
|
+
display: none;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/* An empty list card shows its text under the title-row Add, without the
|
|
1344
|
+
empty state's own Add. A filter's "clear" action stays. */
|
|
1345
|
+
.cn-look-board .cn-detail-page__catalog-card:not(.cn-look-nextcloud *) .cn-widget-empty-state__action:has(> .cn-object-list-widget__empty-add) {
|
|
1346
|
+
display: none;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.cn-look-board .cn-widget-wrapper__actions .button-vue.cn-detail-page__catalog-add:has(> .button-vue__wrapper > .button-vue__text:not(:empty)):where(:not(.cn-look-nextcloud *)) {
|
|
1350
|
+
--button-size: var(--cn-board-control-height);
|
|
1351
|
+
}
|
|
@@ -208,6 +208,16 @@
|
|
|
208
208
|
cursor: pointer;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/* Inside a record card the chip row is 36px, 12px in (DqZaakContact's channel
|
|
212
|
+
chips, as DqZaakHistorie's). */
|
|
213
|
+
.cn-look-board .cn-conversation-thread .cn-quick-filter-bar .cn-quick-filter-bar__tab {
|
|
214
|
+
--nldesign-component-button-border-radius: 18px;
|
|
215
|
+
height: 36px;
|
|
216
|
+
min-height: 36px;
|
|
217
|
+
padding: 0 12px;
|
|
218
|
+
border-radius: 18px;
|
|
219
|
+
}
|
|
220
|
+
|
|
211
221
|
.cn-look-board .cn-saved-views__chips .cn-saved-views__chip:hover,
|
|
212
222
|
.cn-look-board .cn-quick-filter-bar .cn-quick-filter-bar__tab:hover {
|
|
213
223
|
background: var(--color-background-hover);
|
|
@@ -805,8 +815,13 @@
|
|
|
805
815
|
padding: 0;
|
|
806
816
|
}
|
|
807
817
|
|
|
808
|
-
/* The title column: a 15px weight 600 link, the secondary line under it.
|
|
809
|
-
|
|
818
|
+
/* The title column: a 15px weight 600 link, the secondary line under it. A
|
|
819
|
+
framed widget table in a card or a settings section shows its first column
|
|
820
|
+
as plain text (DqInstellingen, DqTermijnen, DqDoorlooptijd); a borderless
|
|
821
|
+
one, the dashboard's compact case list, keeps the link (DqDashboard). The
|
|
822
|
+
`:where()` keeps the rule's weight.
|
|
823
|
+
@spec openspec/changes/board-widget-table-plain-title/specs/board-widget-table-plain-title/spec.md */
|
|
824
|
+
.cn-look-board .cn-table-container--board .cn-data-table td.cn-table-col--title:not(.cn-widget-object-table :where(.cn-table-container:not(.cn-table-container--borderless)) *) {
|
|
810
825
|
color: var(--color-main-text);
|
|
811
826
|
font-size: 15px;
|
|
812
827
|
font-weight: 600;
|
|
@@ -1769,3 +1784,36 @@
|
|
|
1769
1784
|
.cn-look-board .cn-saved-views-panel__row .action-item__menutoggle:not(.cn-look-nextcloud *) {
|
|
1770
1785
|
border-radius: 17px;
|
|
1771
1786
|
}
|
|
1787
|
+
|
|
1788
|
+
/* The board view's selects take the board control border (DqWerkbord). The
|
|
1789
|
+
theme forces a select's border colour from its form token, so the token
|
|
1790
|
+
gets the colour too. */
|
|
1791
|
+
.cn-look-board .cn-index-page__board-select:not(.cn-look-nextcloud *) {
|
|
1792
|
+
--nldesign-component-form-select-border-color: var(--cn-board-control-border);
|
|
1793
|
+
border-color: var(--cn-board-control-border);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
/* ---- Page tabs (CnIndexPage `pageTabs`, DqContracten) ------------------ */
|
|
1797
|
+
/* The sibling-page tabs are links; the board draws them as folder tabs in
|
|
1798
|
+
the main ink at 14px, the open one at 700 on a white bottom edge, with no
|
|
1799
|
+
line under the strip. The theme paints every link, so the ink also goes
|
|
1800
|
+
to its link tokens and the underline needs !important.
|
|
1801
|
+
@spec openspec/changes/board-index-page-tabs/specs/board-index-page-tabs/spec.md#requirement-board-index-page-tabs-are-folder-tabs-in-the-main-ink */
|
|
1802
|
+
.cn-look-board .cn-index-page__page-tabs:not(.cn-look-nextcloud *) {
|
|
1803
|
+
border-bottom: 0;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
.cn-look-board .cn-index-page__page-tabs:not(.cn-look-nextcloud *) .cn-index-page__page-tab {
|
|
1807
|
+
--nldesign-color-on-surface: var(--color-main-text);
|
|
1808
|
+
--nldesign-component-link-color: var(--color-main-text);
|
|
1809
|
+
--nldesign-component-link-hover-color: var(--color-main-text);
|
|
1810
|
+
color: var(--color-main-text);
|
|
1811
|
+
font-size: 14px;
|
|
1812
|
+
line-height: normal;
|
|
1813
|
+
text-decoration: none !important;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
.cn-look-board .cn-index-page__page-tabs:not(.cn-look-nextcloud *) .cn-index-page__page-tab--active {
|
|
1817
|
+
border-bottom: 1px solid var(--color-main-background);
|
|
1818
|
+
font-weight: 700;
|
|
1819
|
+
}
|
package/src/css/look-board.css
CHANGED
|
@@ -670,6 +670,13 @@
|
|
|
670
670
|
--button-size: var(--cn-board-control-height);
|
|
671
671
|
}
|
|
672
672
|
|
|
673
|
+
/* The contact thread's labelled header action (DqZaakContact "Contact
|
|
674
|
+
vastleggen") stands at the same 40px.
|
|
675
|
+
@spec openspec/changes/board-thread-header-button-height/specs/board-thread-header-button-height/spec.md#requirement-a-thread-headers-labelled-button-is-40px */
|
|
676
|
+
.cn-look-board .cn-conversation-thread__header-actions .button-vue:not(.action-item__menutoggle):has(> .button-vue__wrapper > .button-vue__text:not(:empty)):where(:not(.cn-look-nextcloud *)) {
|
|
677
|
+
--button-size: var(--cn-board-control-height);
|
|
678
|
+
}
|
|
679
|
+
|
|
673
680
|
/* ---- A workspace filter's table export ---------------------------------- */
|
|
674
681
|
/* "CSV exporteren" beside "Rapport laden" is the neutral secondary at the
|
|
675
682
|
40px control height, 14px in (DqTermijnen).
|
|
@@ -2350,6 +2357,48 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
|
|
|
2350
2357
|
color: var(--color-text-maxcontrast) !important;
|
|
2351
2358
|
}
|
|
2352
2359
|
|
|
2360
|
+
/* A text card sits at the card inset with its text 8px under the title
|
|
2361
|
+
(DqDagAfsluiten "Hoe dit werkt"), its blocks at the widget's size and a
|
|
2362
|
+
1.5 line height. CnTextWidget pads itself 16px inline and the theme sets
|
|
2363
|
+
every p at 15px, hence the !important.
|
|
2364
|
+
@spec openspec/changes/board-dashboard-text-card/specs/board-dashboard-text-card/spec.md#requirement-a-board-dashboard-text-card-sits-at-the-card-inset-under-its-title */
|
|
2365
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-text-widget {
|
|
2366
|
+
margin-top: -2px;
|
|
2367
|
+
padding: 0 var(--cn-board-dashboard-card-inset) 20px !important;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-text-widget__content > * {
|
|
2371
|
+
font-size: inherit !important;
|
|
2372
|
+
line-height: 1.5 !important;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-text-widget__content > :first-child {
|
|
2376
|
+
margin-top: 0;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-text-widget__content > :last-child {
|
|
2380
|
+
margin-bottom: 0;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/* A stat grid card draws plain figures at the card inset (DqDagAfsluiten
|
|
2384
|
+
"Uw dag"): 14px under the title, 16px apart, each a 13px label over a 24px
|
|
2385
|
+
figure, 2px apart, without the stat tile's padding or edge. The cell's
|
|
2386
|
+
variables are set on the cell, over a board tile's own.
|
|
2387
|
+
@spec openspec/changes/board-stat-grid-flat/specs/board-stat-grid-flat/spec.md#requirement-a-board-stat-grid-draws-flat-figures-at-the-card-inset */
|
|
2388
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-stat-grid {
|
|
2389
|
+
gap: 16px;
|
|
2390
|
+
margin: 4px 0 0;
|
|
2391
|
+
padding: 0 var(--cn-board-dashboard-card-inset) 20px;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-stat-grid .cn-kpi-card.cn-kpi-card--stacked {
|
|
2395
|
+
--cn-kpi-stacked-padding: 0;
|
|
2396
|
+
--cn-kpi-stacked-gap: 2px;
|
|
2397
|
+
--cn-kpi-stacked-title-size: 13px;
|
|
2398
|
+
--cn-kpi-stacked-value-size: 24px;
|
|
2399
|
+
border-width: 0;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2353
2402
|
/* An activity item pads itself 4px, so the body takes 4px less. */
|
|
2354
2403
|
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-nc-widget-widget__body {
|
|
2355
2404
|
padding: 0 calc(var(--cn-board-dashboard-card-inset) - 4px) 16px;
|
|
@@ -2646,9 +2695,10 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
|
|
|
2646
2695
|
/* Any labelled button in the card's body is 40px, a text-only one 16px in at
|
|
2647
2696
|
both sides (DqBeheerinstellingen "Opslaan", "Verbinding testen";
|
|
2648
2697
|
PtBeheerinstellingen "Adres opslaan"), not only the card's own save row.
|
|
2649
|
-
The title row's actions
|
|
2698
|
+
The title row's actions and an image field's button keep 34px
|
|
2699
|
+
(DqVthInstellingen "Regel toevoegen", DqInstellingenAlgemeen "Ander logo").
|
|
2650
2700
|
@spec openspec/changes/board-settings-section-buttons/specs/board-settings-section-buttons/spec.md#requirement-a-labelled-button-on-a-board-settings-card-is-40px */
|
|
2651
|
-
.cn-look-board .settings-section:not(.cn-look-nextcloud *) .button-vue.button-vue:has(> .button-vue__wrapper > .button-vue__text:not(:empty)):not(.cn-settings-section__actions *) {
|
|
2701
|
+
.cn-look-board .settings-section:not(.cn-look-nextcloud *) .button-vue.button-vue:has(> .button-vue__wrapper > .button-vue__text:not(:empty)):not(.cn-settings-section__actions *):not(.cn-settings-field__image-row *) {
|
|
2652
2702
|
--button-size: var(--cn-field-height, 40px);
|
|
2653
2703
|
}
|
|
2654
2704
|
|
|
@@ -34,11 +34,15 @@
|
|
|
34
34
|
<NcButton
|
|
35
35
|
v-for="action in shownHeaderActions"
|
|
36
36
|
:key="action"
|
|
37
|
-
:variant="action === 'create' ? 'primary' : 'secondary'"
|
|
37
|
+
:variant="action === 'create' && createVariant !== 'secondary' ? 'primary' : 'secondary'"
|
|
38
38
|
:data-testid="`cn-calendar-card-${action}`"
|
|
39
39
|
@click="action === 'create' ? (showCreate = true) : (showPicker = true)">
|
|
40
40
|
<template #icon>
|
|
41
|
-
<
|
|
41
|
+
<CnIcon v-if="action === 'create' && createIcon"
|
|
42
|
+
:name="createIcon"
|
|
43
|
+
:size="20"
|
|
44
|
+
data-testid="cn-calendar-card-create-icon" />
|
|
45
|
+
<Plus v-else-if="action === 'create'" :size="20" />
|
|
42
46
|
<LinkVariant v-else :size="20" />
|
|
43
47
|
</template>
|
|
44
48
|
{{ action === 'create' ? createLabel : linkLabel }}
|
|
@@ -139,6 +143,7 @@ import Plus from 'vue-material-design-icons/Plus.vue'
|
|
|
139
143
|
import CnCalendarEventCreate from '../../../components/CnCalendarEventCreate/CnCalendarEventCreate.vue'
|
|
140
144
|
import CnCalendarEventPicker from '../../../components/CnCalendarEventPicker/CnCalendarEventPicker.vue'
|
|
141
145
|
import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
|
|
146
|
+
import CnIcon from '../../../components/CnIcon/CnIcon.vue'
|
|
142
147
|
import { buildHeaders, prefixUrl } from '../../../utils/index.js'
|
|
143
148
|
import { userLocale } from '../../../utils/userLocale.js'
|
|
144
149
|
import { linkCalendarEvent } from './calendarEventLink.js'
|
|
@@ -151,7 +156,7 @@ import { linkCalendarEvent } from './calendarEventLink.js'
|
|
|
151
156
|
export default {
|
|
152
157
|
name: 'CnCalendarCard',
|
|
153
158
|
|
|
154
|
-
components: { CnCalendarEventCreate, CnCalendarEventPicker, CnDetailCard, LinkVariant, NcButton, NcLoadingIcon, Plus, CalendarClock, CalendarCheck },
|
|
159
|
+
components: { CnCalendarEventCreate, CnCalendarEventPicker, CnDetailCard, CnIcon, LinkVariant, NcButton, NcLoadingIcon, Plus, CalendarClock, CalendarCheck },
|
|
155
160
|
|
|
156
161
|
props: {
|
|
157
162
|
/** OpenRegister register id (slug or uuid). Optional for user-dashboard. */
|
|
@@ -200,6 +205,10 @@ export default {
|
|
|
200
205
|
headerActions: { type: Array, default: () => [] },
|
|
201
206
|
/** Label of the `create` button. */
|
|
202
207
|
createLabel: { type: String, default: () => t('nextcloud-vue', 'Make an appointment') },
|
|
208
|
+
/** `secondary` draws the `create` button outlined instead of primary. */
|
|
209
|
+
createVariant: { type: String, default: 'primary' },
|
|
210
|
+
/** Icon name of the `create` button; a plus without it. */
|
|
211
|
+
createIcon: { type: String, default: '' },
|
|
203
212
|
/** Label of the `link` button. */
|
|
204
213
|
linkLabel: { type: String, default: () => t('nextcloud-vue', 'Link an existing one') },
|
|
205
214
|
},
|
|
@@ -17,7 +17,8 @@ const CnCalendarCard = require('../CnCalendarCard.vue').default
|
|
|
17
17
|
|
|
18
18
|
const STUBS = {
|
|
19
19
|
NcLoadingIcon: { template: '<div class="cn-loading" />' },
|
|
20
|
-
NcButton: { template: '<button v-bind="$attrs"><slot /></button>' },
|
|
20
|
+
NcButton: { template: '<button v-bind="$attrs"><slot /><slot name="icon" /></button>' },
|
|
21
|
+
CnIcon: { props: ['name'], template: '<i :data-name="name" />' },
|
|
21
22
|
CnCalendarEventCreate: { name: 'CnCalendarEventCreate', template: '<div class="create-modal" />' },
|
|
22
23
|
CnCalendarEventPicker: { name: 'CnCalendarEventPicker', template: '<div class="picker-modal" />' },
|
|
23
24
|
}
|
|
@@ -95,4 +96,14 @@ describe('CnCalendarCard header actions', () => {
|
|
|
95
96
|
await flush()
|
|
96
97
|
expect(wrapper.find('[role="alert"]').text()).toContain('Not your calendar')
|
|
97
98
|
})
|
|
99
|
+
|
|
100
|
+
it('draws the create button secondary when asked', async () => {
|
|
101
|
+
const primary = mountCard({ headerActions: ['create'] })
|
|
102
|
+
await flush()
|
|
103
|
+
expect(primary.find('[data-testid="cn-calendar-card-create"]').attributes('variant')).toBe('primary')
|
|
104
|
+
const quiet = mountCard({ headerActions: ['create'], createVariant: 'secondary', createIcon: 'CalendarPlusOutline' })
|
|
105
|
+
await flush()
|
|
106
|
+
expect(quiet.find('[data-testid="cn-calendar-card-create"]').attributes('variant')).toBe('secondary')
|
|
107
|
+
expect(quiet.find('[data-testid="cn-calendar-card-create-icon"]').attributes('data-name')).toBe('CalendarPlusOutline')
|
|
108
|
+
})
|
|
98
109
|
})
|
|
@@ -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.150.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -1346,7 +1346,8 @@
|
|
|
1346
1346
|
},
|
|
1347
1347
|
"readOnly": { "type": "boolean", "default": false, "description": "No drag and no Move to while the columns come from this field." },
|
|
1348
1348
|
"moveHandler": { "type": "string", "minLength": 1, "description": "A handler in the app's registry that makes the move: it gets `{ card, toKey, field, value, target }` (value: the referenced id to write; target: its row) and returns a promise; a rejection shows its message as the refusal. Absent, the field is written with a PATCH." },
|
|
1349
|
-
"moveTargetsHandler": { "type": "string", "minLength": 1, "description": "A handler in the app's registry that answers where one card may go: it gets `{ card, field }` and returns the values (referenced ids) it can reach. Columns out of reach fade during a drag, read \"not allowed\" in the card menu, and refuse a drop." }
|
|
1349
|
+
"moveTargetsHandler": { "type": "string", "minLength": 1, "description": "A handler in the app's registry that answers where one card may go: it gets `{ card, field }` and returns the values (referenced ids) it can reach. Columns out of reach fade during a drag, read \"not allowed\" in the card menu, and refuse a drop." },
|
|
1350
|
+
"states": { "$ref": "#/$defs/boardDeclaredStates" }
|
|
1350
1351
|
}
|
|
1351
1352
|
}
|
|
1352
1353
|
},
|
|
@@ -1394,7 +1395,30 @@
|
|
|
1394
1395
|
"type": "boolean",
|
|
1395
1396
|
"default": false,
|
|
1396
1397
|
"description": "screens-board-reference-columns (2.97.0): wrap the columns onto more rows, as many to a row as fit at 240px, instead of one row that scrolls sideways. For a board with more stages than fit."
|
|
1397
|
-
}
|
|
1398
|
+
},
|
|
1399
|
+
"states": { "$ref": "#/$defs/boardDeclaredStates" }
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
"boardDeclaredStates": {
|
|
1403
|
+
"type": "array",
|
|
1404
|
+
"minItems": 1,
|
|
1405
|
+
"description": "board-declared-states (2.150.0): the columns the board draws for a field, in order, instead of the field's enum or lifecycle states. Each entry is a value or `{ key, label?, color?, hidden?, closed? }`: `hidden` draws no column (its cards go to Elsewhere), `closed` starts the column folded, `label` is an i18n key. On `board` it applies to the `statusField`; on a `columnFields` entry to that field. Ignored on a `source` or `kind: values` column.",
|
|
1406
|
+
"items": {
|
|
1407
|
+
"oneOf": [
|
|
1408
|
+
{ "type": "string", "minLength": 1 },
|
|
1409
|
+
{
|
|
1410
|
+
"type": "object",
|
|
1411
|
+
"additionalProperties": false,
|
|
1412
|
+
"required": ["key"],
|
|
1413
|
+
"properties": {
|
|
1414
|
+
"key": { "type": "string", "minLength": 1 },
|
|
1415
|
+
"label": { "type": "string" },
|
|
1416
|
+
"color": { "type": "string" },
|
|
1417
|
+
"hidden": { "type": "boolean" },
|
|
1418
|
+
"closed": { "type": "boolean" }
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
]
|
|
1398
1422
|
}
|
|
1399
1423
|
},
|
|
1400
1424
|
"headerPill": {
|