@conduction/nextcloud-vue 2.137.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/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +6 -4
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue.js +1 -1
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js +6 -0
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js +24 -7
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +2 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.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 +43 -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 +133 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +25 -17
- 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/CnMailTab/dashboardRegistration.js +4 -0
- package/dist/esm/components/CnMailTab/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue.js +1 -1
- package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.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/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +2 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +6 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +3 -3
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +16 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +11 -5
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue.js +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js +1 -0
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +78 -12
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/l10n/en.json.js +12 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +12 -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 +685 -512
- package/dist/esm/utils/resolveFilterTokens.js +9 -0
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +1 -1
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +580 -236
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +685 -512
- package/l10n/en.json +12 -1
- package/l10n/nl.json +12 -1
- package/package.json +1 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +6 -4
- package/src/components/CnChartWidget/CnChartWidget.vue +4 -0
- package/src/components/CnDetailCard/CnDetailCard.md +1 -0
- package/src/components/CnDetailCard/CnDetailCard.vue +25 -1
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +135 -7
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +10 -2
- package/src/components/CnMailTab/dashboardRegistration.js +4 -0
- package/src/components/CnNotificationMatrix/CnNotificationMatrix.vue +4 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +3 -0
- package/src/components/CnOrderedListField/CnOrderedListField.vue +11 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +22 -3
- package/src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue +1 -0
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +3 -0
- package/src/composables/indexSources.js +78 -12
- package/src/css/dialog.css +26 -0
- package/src/css/index-page.css +5 -0
- package/src/css/look-board-detail.css +36 -0
- package/src/css/look-board-index.css +21 -0
- package/src/css/look-board.css +47 -0
- package/src/utils/resolveFilterTokens.js +9 -0
- package/src/utils/sentinelTokens.js +1 -1
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
7
|
<div class="cn-ordered-list-field" data-testid="cn-ordered-list-field">
|
|
8
|
-
<p
|
|
8
|
+
<p
|
|
9
|
+
v-if="label"
|
|
10
|
+
:id="labelId"
|
|
11
|
+
class="cn-ordered-list-field__label"
|
|
12
|
+
:class="{ 'hidden-visually': hideLabel }">
|
|
9
13
|
{{ label }}
|
|
10
14
|
</p>
|
|
11
15
|
<ol
|
|
@@ -192,6 +196,12 @@ export default {
|
|
|
192
196
|
default: '',
|
|
193
197
|
},
|
|
194
198
|
|
|
199
|
+
/** Keep the label for assistive technology only, when a heading above already says it. */
|
|
200
|
+
hideLabel: {
|
|
201
|
+
type: Boolean,
|
|
202
|
+
default: false,
|
|
203
|
+
},
|
|
204
|
+
|
|
195
205
|
/** The picker's label. */
|
|
196
206
|
addLabel: {
|
|
197
207
|
type: String,
|
|
@@ -20,9 +20,11 @@
|
|
|
20
20
|
<div v-else-if="loading && rows.length === 0" class="cn-record-list__loading">
|
|
21
21
|
<NcLoadingIcon :size="20" :name="loadingLabel" />
|
|
22
22
|
</div>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<template v-else-if="rows.length === 0">
|
|
24
|
+
<p v-if="emptyLabel !== ''" class="cn-record-list__empty" data-testid="cn-record-list-empty">
|
|
25
|
+
{{ emptyLabel }}
|
|
26
|
+
</p>
|
|
27
|
+
</template>
|
|
26
28
|
<!-- notice: the rows as sentences in one toned box ("2 waarschuwingen: ..."). -->
|
|
27
29
|
<div
|
|
28
30
|
v-else-if="presentation === 'notice'"
|
|
@@ -251,6 +253,7 @@
|
|
|
251
253
|
<component
|
|
252
254
|
:is="item.action.tag"
|
|
253
255
|
class="cn-record-list__action"
|
|
256
|
+
:class="{ 'cn-record-list__action--outlined': rowActionOutlined }"
|
|
254
257
|
v-bind="item.action.attrs"
|
|
255
258
|
data-testid="cn-record-list-action"
|
|
256
259
|
v-on="item.action.tag === 'button' ? { click: () => runRowAction(item.row) } : {}">
|
|
@@ -1469,6 +1472,18 @@ export default {
|
|
|
1469
1472
|
* @return {string}
|
|
1470
1473
|
* @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
|
|
1471
1474
|
*/
|
|
1475
|
+
/**
|
|
1476
|
+
* Whether the row action is drawn as an outlined button sized to its
|
|
1477
|
+
* label (`rowAction.variant: "outlined"`, DqMijnTeam "Oppakken") instead
|
|
1478
|
+
* of the 150px pill (DcMijnActies "Stem uitbrengen").
|
|
1479
|
+
*
|
|
1480
|
+
* @return {boolean} True for the outlined variant.
|
|
1481
|
+
* @spec openspec/changes/board-row-action-outlined/specs/board-row-action-outlined/spec.md#requirement-a-dashboard-rows-action-is-an-outlined-button
|
|
1482
|
+
*/
|
|
1483
|
+
rowActionOutlined() {
|
|
1484
|
+
return this.content?.rowAction?.variant === 'outlined'
|
|
1485
|
+
},
|
|
1486
|
+
|
|
1472
1487
|
rowActionsHeader() {
|
|
1473
1488
|
const own = this.content?.rowActionsHeader
|
|
1474
1489
|
return typeof own === 'string' && own !== '' ? this.translate(own) : t('nextcloud-vue', 'Actions')
|
|
@@ -1511,6 +1526,10 @@ export default {
|
|
|
1511
1526
|
if (this.conditionalEmptyText !== '') {
|
|
1512
1527
|
return this.conditionalEmptyText
|
|
1513
1528
|
}
|
|
1529
|
+
// `emptyText: ""` says nothing on an empty list.
|
|
1530
|
+
if (this.content?.emptyText === '') {
|
|
1531
|
+
return ''
|
|
1532
|
+
}
|
|
1514
1533
|
return this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'Nothing here yet')
|
|
1515
1534
|
},
|
|
1516
1535
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
:modelValue="values"
|
|
13
13
|
:options="options"
|
|
14
14
|
:label="t('nextcloud-vue', 'Views in my menu')"
|
|
15
|
+
:hideLabel="true"
|
|
15
16
|
:addLabel="t('nextcloud-vue', 'Add view')"
|
|
16
17
|
:addPlaceholder="t('nextcloud-vue', 'Pick a saved view')"
|
|
17
18
|
:addHint="t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')"
|
|
@@ -400,7 +400,10 @@ export default {
|
|
|
400
400
|
background: var(--color-main-background);
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
/* A section's heading and description start at the text's edge, as the
|
|
404
|
+
board draws them (NcAppSettingsSection indents them 12px). */
|
|
403
405
|
.cn-user-settings-dialog__sections :deep(.app-settings-section) {
|
|
406
|
+
--app-settings-section-text-offset: 0;
|
|
404
407
|
margin: 0;
|
|
405
408
|
padding: 0;
|
|
406
409
|
border: 0;
|
|
@@ -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?, 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
|
|
@@ -1102,6 +1105,10 @@ function fillRowTemplate(template, row) {
|
|
|
1102
1105
|
* coloured by `sourceConfig.statusColorMap`. `linkedTemplate` (a field
|
|
1103
1106
|
* template, "{case.title}") or `linkedField` (a path) fills the "Linked"
|
|
1104
1107
|
* column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
|
|
1108
|
+
* `subtitleTemplate` ("Case {identifier}", translated) fills the line under a
|
|
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.
|
|
1105
1112
|
* The page's title and count line can name the term as `{term}`, and the
|
|
1106
1113
|
* count is the active kind's total. Kind, link and status are filterable.
|
|
1107
1114
|
*
|
|
@@ -1111,6 +1118,23 @@ function fillRowTemplate(template, row) {
|
|
|
1111
1118
|
* @return {object} The source adapter.
|
|
1112
1119
|
* @spec openspec/changes/index-search-source/specs/index-page/spec.md#requirement-an-index-page-can-search-several-schemas-and-files
|
|
1113
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
|
+
|
|
1114
1138
|
function searchSource(config = {}, context = {}) {
|
|
1115
1139
|
const translate = typeof context.translate === 'function' ? context.translate : (text) => text
|
|
1116
1140
|
const kinds = (Array.isArray(config && config.kinds) ? config.kinds : [])
|
|
@@ -1199,12 +1223,27 @@ function searchSource(config = {}, context = {}) {
|
|
|
1199
1223
|
return ''
|
|
1200
1224
|
}
|
|
1201
1225
|
|
|
1226
|
+
const subtitleOf = (kind, object) => {
|
|
1227
|
+
if (kind.subtitleTemplate) {
|
|
1228
|
+
return fillTemplate(translate(kind.subtitleTemplate), object)
|
|
1229
|
+
}
|
|
1230
|
+
return kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : ''
|
|
1231
|
+
}
|
|
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
|
+
|
|
1202
1240
|
const searchObjects = async (axios, kind, term) => {
|
|
1203
1241
|
const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema })
|
|
1204
1242
|
const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } })
|
|
1205
1243
|
const body = (res && res.data) || {}
|
|
1206
1244
|
const results = Array.isArray(body.results) ? body.results : (Array.isArray(body) ? body : [])
|
|
1207
1245
|
const labels = kind.statusResolve && kind.statusField ? await statusLabels(kind) : null
|
|
1246
|
+
const userNames = kind.linkedUser === true ? await linkedUserNames(kind, results) : null
|
|
1208
1247
|
return {
|
|
1209
1248
|
total: typeof body.total === 'number' ? body.total : results.length,
|
|
1210
1249
|
rows: results.map((object) => {
|
|
@@ -1219,9 +1258,9 @@ function searchSource(config = {}, context = {}) {
|
|
|
1219
1258
|
kindLabel: kind.label,
|
|
1220
1259
|
kindText: kindTextOf(kind, object),
|
|
1221
1260
|
title: String(readPath(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
|
|
1222
|
-
subtitle: kind
|
|
1261
|
+
subtitle: subtitleOf(kind, object),
|
|
1223
1262
|
status,
|
|
1224
|
-
linked: linkedOf(kind, object),
|
|
1263
|
+
linked: userNames ? (userNames.get(linkedOf(kind, object)) || linkedOf(kind, object)) : linkedOf(kind, object),
|
|
1225
1264
|
href: kind.href ? generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
|
|
1226
1265
|
object,
|
|
1227
1266
|
}
|
|
@@ -1329,7 +1368,7 @@ function searchSource(config = {}, context = {}) {
|
|
|
1329
1368
|
*/
|
|
1330
1369
|
loading: () => state.loading,
|
|
1331
1370
|
|
|
1332
|
-
columns: [
|
|
1371
|
+
columns: withColumnWidths([
|
|
1333
1372
|
{ key: 'title', label: t('nextcloud-vue', 'Result'), secondary: 'subtitle', sortable: true },
|
|
1334
1373
|
// `kindAsText`: what one row is, as plain text ("Zaak", "Lantaarnpaal",
|
|
1335
1374
|
// DqZoekresultaten), instead of the plural kind as a pill.
|
|
@@ -1344,7 +1383,7 @@ function searchSource(config = {}, context = {}) {
|
|
|
1344
1383
|
widgetProps: { colorMap: (config && config.statusColorMap) || {} },
|
|
1345
1384
|
sortable: true,
|
|
1346
1385
|
},
|
|
1347
|
-
],
|
|
1386
|
+
], config && config.columnWidths),
|
|
1348
1387
|
|
|
1349
1388
|
/**
|
|
1350
1389
|
* Every column header carries a filter button (DqZoekresultaten); the
|
|
@@ -1406,14 +1445,24 @@ function searchSource(config = {}, context = {}) {
|
|
|
1406
1445
|
* the pager asks for.
|
|
1407
1446
|
*
|
|
1408
1447
|
* @param {Array<object>} members The members, by name.
|
|
1409
|
-
* @param {object} config `search`, `sortKey`, `sortOrder`, `limit`, `offset`.
|
|
1448
|
+
* @param {object} config `search`, `nameLike`, `rolesLike`, `emailLike`, `today`, `sortKey`, `sortOrder`, `limit`, `offset`.
|
|
1410
1449
|
* @return {{rows: Array<object>, total: number}} The page and how many match.
|
|
1411
1450
|
*/
|
|
1412
1451
|
function membersPage(members, config = {}) {
|
|
1452
|
+
const has = (member, fields, text) => fields.some((field) => String(member[field] || '').toLowerCase().includes(text))
|
|
1413
1453
|
const term = String(config.search || '').trim().toLowerCase()
|
|
1414
|
-
let rows = term === ''
|
|
1415
|
-
|
|
1416
|
-
|
|
1454
|
+
let rows = term === '' ? members.slice() : members.filter((member) => has(member, ['displayname', 'function', 'roles', 'email'], term))
|
|
1455
|
+
// The table heads' filters: text contains, and the Today values picked.
|
|
1456
|
+
for (const [param, fields] of [['nameLike', ['displayname', 'function']], ['rolesLike', ['roles']], ['emailLike', ['email']]]) {
|
|
1457
|
+
const text = String(config[param] || '').trim().toLowerCase()
|
|
1458
|
+
if (text !== '') {
|
|
1459
|
+
rows = rows.filter((member) => has(member, fields, text))
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
const today = (Array.isArray(config.today) ? config.today : (config.today ? [config.today] : [])).map(String)
|
|
1463
|
+
if (today.length > 0) {
|
|
1464
|
+
rows = rows.filter((member) => today.includes(member.today))
|
|
1465
|
+
}
|
|
1417
1466
|
if (config.sortKey) {
|
|
1418
1467
|
const sign = config.sortOrder === 'desc' ? -1 : 1
|
|
1419
1468
|
rows = rows.sort((a, b) => sign * String(a[config.sortKey] ?? '').localeCompare(String(b[config.sortKey] ?? '')))
|
|
@@ -1475,10 +1524,11 @@ function usersSource() {
|
|
|
1475
1524
|
*/
|
|
1476
1525
|
load: async (config) => {
|
|
1477
1526
|
const group = String((config && (config.group || config.defaultGroup)) || '')
|
|
1478
|
-
const { search, sortKey, sortOrder, limit, offset, ...request } = config || {}
|
|
1527
|
+
const { search, sortKey, sortOrder, limit, offset, nameLike, rolesLike, emailLike, today, ...request } = config || {}
|
|
1479
1528
|
const key = JSON.stringify({ ...request, group })
|
|
1480
|
-
|
|
1481
|
-
|
|
1529
|
+
const narrows = [offset, search, sortKey, nameLike, rolesLike, emailLike, today].some((value) => value !== undefined)
|
|
1530
|
+
// A new page, term, order or head filter of the members already read: no request.
|
|
1531
|
+
if (key === membersKey && narrows) {
|
|
1482
1532
|
const page = membersPage(readMembers, config)
|
|
1483
1533
|
state.rows = page.rows
|
|
1484
1534
|
state.total = page.total
|
|
@@ -1574,8 +1624,24 @@ function usersSource() {
|
|
|
1574
1624
|
|
|
1575
1625
|
searchFields: {
|
|
1576
1626
|
group: { param: 'group', single: true },
|
|
1627
|
+
'displayname[like]': { param: 'nameLike', single: true },
|
|
1628
|
+
'roles[like]': { param: 'rolesLike', single: true },
|
|
1629
|
+
'email[like]': { param: 'emailLike', single: true },
|
|
1630
|
+
today: { param: 'today' },
|
|
1577
1631
|
},
|
|
1578
1632
|
|
|
1633
|
+
/** A filter button on every head (DqAfdelingen); the members read are narrowed here. */
|
|
1634
|
+
headerFilters: true,
|
|
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
|
+
|
|
1579
1645
|
showAdd: false,
|
|
1580
1646
|
}
|
|
1581
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;
|
|
@@ -212,6 +230,14 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
|
|
|
212
230
|
padding: 22px 24px 20px;
|
|
213
231
|
}
|
|
214
232
|
|
|
233
|
+
/* NcDialog's scoped `.dialog__content[data-v-…]` sets padding-inline-end
|
|
234
|
+
12px with the same specificity as the rule above, so wherever its chunk
|
|
235
|
+
loads later (an admin page's setup wizard) the body ran 12px wider than
|
|
236
|
+
the board (DqInstellen 684 for 672). This one outranks it. */
|
|
237
|
+
.cn-dialog-board .dialog .dialog__content {
|
|
238
|
+
padding-inline-end: 24px;
|
|
239
|
+
}
|
|
240
|
+
|
|
215
241
|
.cn-dialog-board .dialog__content > .cn-dialog-header {
|
|
216
242
|
margin-bottom: -2px;
|
|
217
243
|
}
|
package/src/css/index-page.css
CHANGED
|
@@ -221,6 +221,11 @@
|
|
|
221
221
|
font-size: 14px;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
.cn-index-page__map-missing-note {
|
|
225
|
+
color: var(--color-main-text);
|
|
226
|
+
line-height: 1.45;
|
|
227
|
+
}
|
|
228
|
+
|
|
224
229
|
/* The selected folder's heading above the list. */
|
|
225
230
|
/* A sort option whose endpoint failed: the note above the fallen-back list. */
|
|
226
231
|
.cn-index-page__sort-source-note {
|
|
@@ -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,23 @@
|
|
|
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
|
+
|
|
1285
|
+
/* A long task name wraps under itself, as the board draws it, rather than
|
|
1286
|
+
widening the table past the card. */
|
|
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) {
|
|
1288
|
+
white-space: normal;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/* A row's button and its task menu stand on one line. */
|
|
1292
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper__content > .cn-tasks-widget .cn-tasks-widget__table .cn-tasks-widget__cell-actions > * {
|
|
1293
|
+
display: inline-flex;
|
|
1294
|
+
vertical-align: middle;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1273
1297
|
/* ---- An object list table edge to edge (`content.tableBleed`) ------------ */
|
|
1274
1298
|
/* The case-tab boards draw a list's table edge to edge in its card
|
|
1275
1299
|
(DqZaakContact "Contactmomenten op deze zaak", DqZaakGerelateerd): it runs
|
|
@@ -1557,3 +1581,15 @@
|
|
|
1557
1581
|
font-size: 14px;
|
|
1558
1582
|
line-height: 1.2;
|
|
1559
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.
|
|
@@ -2335,6 +2356,32 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
|
|
|
2335
2356
|
font-weight: 600;
|
|
2336
2357
|
}
|
|
2337
2358
|
|
|
2359
|
+
/* A row whose one action is outlined (`rowAction.variant: "outlined"`,
|
|
2360
|
+
DqMijnTeam "Oppakken"): its title link reads as the row's title in the main
|
|
2361
|
+
ink (the theme's link rule reads --nldesign-color-on-surface), and the
|
|
2362
|
+
action is an outlined button sized to its label. Without the variant the
|
|
2363
|
+
action keeps its 150px pill (DcMijnActies).
|
|
2364
|
+
@spec openspec/changes/board-row-action-outlined/specs/board-row-action-outlined/spec.md#requirement-a-dashboard-rows-action-is-an-outlined-button */
|
|
2365
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--action:has(.cn-record-list__action--outlined) .cn-record-list__title--link {
|
|
2366
|
+
--nldesign-color-on-surface: var(--color-main-text);
|
|
2367
|
+
color: var(--color-main-text);
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__action--outlined {
|
|
2371
|
+
--nldesign-color-on-surface: var(--color-main-text);
|
|
2372
|
+
flex: none;
|
|
2373
|
+
padding: 0 12px;
|
|
2374
|
+
border: 1px solid var(--cn-board-control-border);
|
|
2375
|
+
border-radius: 8px;
|
|
2376
|
+
background: var(--color-main-background);
|
|
2377
|
+
color: var(--color-main-text);
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__action--outlined:hover,
|
|
2381
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__action--outlined:focus-visible {
|
|
2382
|
+
background: var(--color-background-hover);
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2338
2385
|
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__meta {
|
|
2339
2386
|
color: var(--color-text-maxcontrast);
|
|
2340
2387
|
font-size: 13px;
|
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
* - `@object.<field>` → a field off the current detail-page object (needs `ctx`)
|
|
44
44
|
* - `@workspace.<key>` → a value off the page-level workspace context (needs `ctx.workspace`)
|
|
45
45
|
* - `@config.<key>` → a value off the page-level app config (needs `ctx.config`)
|
|
46
|
+
* - `@context.<name>.<path>` → a value off a record the detail page reads beside
|
|
47
|
+
* the loaded one (needs `ctx.context`); left as written
|
|
48
|
+
* while that record is not read
|
|
46
49
|
*
|
|
47
50
|
* `@objectId` / `@object.<field>` are OBJECT-CONTEXT tokens: they resolve only
|
|
48
51
|
* when a `ctx` `{ objectId, object }` is supplied (a detail page provides it via
|
|
@@ -75,6 +78,7 @@ import { getCurrentUser } from '@nextcloud/auth'
|
|
|
75
78
|
// userGroupsPeek, not widgetVisibility: that one imports @nextcloud/axios, and
|
|
76
79
|
// this resolver runs on public pages and in node (see userGroupsPeek.js).
|
|
77
80
|
import { peekMyTeam } from './myTeam.js'
|
|
81
|
+
import { readPath } from './readPath.js'
|
|
78
82
|
import { TODAY_DELTA_RE } from './sentinelTokens.js'
|
|
79
83
|
import { peekCurrentUserGroups } from './userGroupsPeek.js'
|
|
80
84
|
|
|
@@ -195,6 +199,11 @@ export function resolveFilterValue(v, ctx) {
|
|
|
195
199
|
}
|
|
196
200
|
return v
|
|
197
201
|
}
|
|
202
|
+
if (v.startsWith('@context.')) {
|
|
203
|
+
// @spec openspec/changes/files-second-line-fields/specs/files-second-line-fields/spec.md#requirement-widget-props-can-read-the-detail-pages-context-records
|
|
204
|
+
const found = ctx && ctx.context ? readPath(ctx.context, v.slice('@context.'.length)) : undefined
|
|
205
|
+
return found === undefined ? v : found
|
|
206
|
+
}
|
|
198
207
|
if (v.startsWith('@workspace.')) {
|
|
199
208
|
// A trailing `?` marks the token OPTIONAL: when unset the caller drops the
|
|
200
209
|
// filter key (show all) instead of waiting (see hasUnresolvedTokens /
|
|
@@ -188,7 +188,7 @@ export const SENTINEL_VOCABULARY = Object.freeze({
|
|
|
188
188
|
members: ['@subject.<field>'],
|
|
189
189
|
},
|
|
190
190
|
context: {
|
|
191
|
-
resolver: 'CnDetailPage visibilityContext (detailWidgetVisibility), read by a grid widget\'s or a tab\'s visibleWhen',
|
|
191
|
+
resolver: 'CnDetailPage visibilityContext (detailWidgetVisibility), read by a grid widget\'s or a tab\'s visibleWhen, and by a widget\'s props (CnDetailWidgetHost)',
|
|
192
192
|
description: 'A record the detail page reads beside the loaded one, named in `config.visibilityContext` (a case\'s case type). A `visibleWhen` field reads it as `@context.<name>.<field>`, dot paths allowed (`@context.caseType.category`).',
|
|
193
193
|
members: ['@context.<name>.<field>'],
|
|
194
194
|
},
|