@conduction/nextcloud-vue 2.140.0 → 2.142.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/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +24 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +3 -3
- 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 +12 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +57 -40
- 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 +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +34 -3
- 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 -3
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +21 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +14 -0
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
- 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 +3 -1
- 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/CnPartyPicker/CnPartyPicker.vue.js +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +31 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js +52 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js +119 -65
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +86 -4
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +67 -5
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +11 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +12 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1116 -686
- package/dist/esm/utils/buildViewTree.js +23 -8
- package/dist/esm/utils/buildViewTree.js.map +1 -1
- package/dist/esm/utils/groupSublines.js +120 -0
- package/dist/esm/utils/groupSublines.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +2023 -1239
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1116 -686
- package/l10n/en.json +11 -1
- package/l10n/nl.json +12 -2
- package/package.json +1 -1
- package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +26 -4
- package/src/components/CnChartWidget/CnChartWidget.md +1 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +49 -4
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +28 -4
- package/src/components/CnFactsWidget/CnFactsWidget.vue +34 -3
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +44 -4
- package/src/components/CnIndexPage/CnIndexPage.vue +22 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +14 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +4 -1
- package/src/components/CnPartyPicker/CnPartyPicker.vue +31 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +33 -9
- package/src/css/dialog.css +13 -1
- package/src/css/look-board-detail.css +107 -9
- package/src/css/look-board-index.css +143 -7
- package/src/css/look-board.css +109 -0
- package/src/css/table.css +12 -0
- package/src/integrations/builtin/contacts/CnContactsTab.vue +101 -26
- package/src/integrations/builtin/contacts/__tests__/CnContactsTabBoardRows.spec.js +67 -0
- package/src/integrations/builtin/deck/CnDeckCard.vue +121 -2
- package/src/integrations/builtin/deck/__tests__/CnDeckCardHeaderActions.spec.js +68 -0
- package/src/utils/buildViewTree.js +23 -8
- package/src/utils/groupSublines.js +125 -0
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
</p>
|
|
104
104
|
</div>
|
|
105
105
|
|
|
106
|
-
<div v-if="hasActiveFilters" class="cn-index-sidebar__section">
|
|
106
|
+
<div v-if="hasActiveFilters && !isBoardLook" class="cn-index-sidebar__section">
|
|
107
107
|
<NcButton variant="tertiary" @click="onClearAll">
|
|
108
108
|
<template #icon>
|
|
109
109
|
<FilterRemoveOutline :size="20" />
|
|
@@ -113,7 +113,20 @@
|
|
|
113
113
|
</div>
|
|
114
114
|
|
|
115
115
|
<div v-if="schemaFilters.length > 0" class="cn-index-sidebar__section">
|
|
116
|
-
|
|
116
|
+
<!-- The board look: "Alles wissen" beside the heading (DqZakenSorteren). -->
|
|
117
|
+
<div v-if="isBoardLook" class="cn-index-sidebar__filters-head" data-testid="cn-index-sidebar-filters-head">
|
|
118
|
+
<h3>{{ filtersLabel }}</h3>
|
|
119
|
+
<button
|
|
120
|
+
v-if="hasActiveFilters"
|
|
121
|
+
type="button"
|
|
122
|
+
class="cn-index-sidebar__filters-clear"
|
|
123
|
+
@click="onClearAll">
|
|
124
|
+
{{ clearAllLabel }}
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
<h3 v-else>
|
|
128
|
+
{{ filtersLabel }}
|
|
129
|
+
</h3>
|
|
117
130
|
<div
|
|
118
131
|
v-for="filter in schemaFilters"
|
|
119
132
|
:key="filter.key"
|
|
@@ -155,6 +168,27 @@
|
|
|
155
168
|
:label="filter.label"
|
|
156
169
|
:placeholder="filter.label"
|
|
157
170
|
@update:modelValue="onTextChange(filter.key, $event)" />
|
|
171
|
+
<!-- The board look: a short field as checkbox rows with counts. -->
|
|
172
|
+
<fieldset
|
|
173
|
+
v-else-if="checklistOptions(filter)"
|
|
174
|
+
class="cn-index-sidebar__checklist"
|
|
175
|
+
:data-testid="`cn-index-sidebar-checklist-${filter.key}`">
|
|
176
|
+
<legend class="hidden-visually">
|
|
177
|
+
{{ filter.label }}
|
|
178
|
+
</legend>
|
|
179
|
+
<label
|
|
180
|
+
v-for="option in checklistOptions(filter)"
|
|
181
|
+
:key="`${filter.key}-${option.id}`"
|
|
182
|
+
class="cn-index-sidebar__checklist-row"
|
|
183
|
+
data-testid="cn-index-sidebar-checklist-row">
|
|
184
|
+
<input
|
|
185
|
+
type="checkbox"
|
|
186
|
+
:checked="isChecked(filter, option.id)"
|
|
187
|
+
@change="onChecklistToggle(filter, option.id, $event.target.checked)">
|
|
188
|
+
<span class="cn-index-sidebar__checklist-label">{{ option.label }}</span>
|
|
189
|
+
<span v-if="option.count !== null" class="cn-index-sidebar__checklist-count">{{ option.count }}</span>
|
|
190
|
+
</label>
|
|
191
|
+
</fieldset>
|
|
158
192
|
<NcSelect
|
|
159
193
|
v-else
|
|
160
194
|
class="cn-index-sidebar__select"
|
|
@@ -369,7 +403,7 @@ import CnSavedViewsPanel from '../CnSavedViewsPanel/CnSavedViewsPanel.vue'
|
|
|
369
403
|
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
370
404
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
371
405
|
import { METADATA_COLUMNS } from '../../constants/metadata.js'
|
|
372
|
-
import { facetOptions } from '../../utils/facets.js'
|
|
406
|
+
import { facetOptions, MISSING_VALUE } from '../../utils/facets.js'
|
|
373
407
|
import { labelLang } from '../../utils/manifestTranslate.js'
|
|
374
408
|
import { columnsFromSchema, filtersFromSchema } from '../../utils/schema.js'
|
|
375
409
|
/**
|
|
@@ -389,6 +423,9 @@ import { labelSidePanelClose } from '../../utils/sidePanelCloseLabel.js'
|
|
|
389
423
|
import { reverseSortKeys, sameSortKeys } from '../../utils/sortOrder.js'
|
|
390
424
|
import { CnDateRangePicker, DEFAULT_DATE_RANGE_PRESETS } from '../CnDateRangePicker/index.js'
|
|
391
425
|
import { CnIcon } from '../CnIcon/index.js'
|
|
426
|
+
/** The most values a filter shows as checkbox rows in the board look; a longer field keeps its picker. */
|
|
427
|
+
const CHECKLIST_MAX = 12
|
|
428
|
+
|
|
392
429
|
export default {
|
|
393
430
|
name: 'CnIndexSidebar',
|
|
394
431
|
|
|
@@ -1411,6 +1448,67 @@ export default {
|
|
|
1411
1448
|
return values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })
|
|
1412
1449
|
},
|
|
1413
1450
|
|
|
1451
|
+
/**
|
|
1452
|
+
* A field's values as checkbox rows, in the board look and for a short
|
|
1453
|
+
* list only: label and count apart, from the facet when there is one,
|
|
1454
|
+
* else the field's own options. Null where the picker stays (the
|
|
1455
|
+
* Nextcloud look, a single choice, a reference, more than
|
|
1456
|
+
* CHECKLIST_MAX values).
|
|
1457
|
+
*
|
|
1458
|
+
* @spec openspec/changes/board-sidebar-filters-as-checkbox-lists/specs/index-sidebar/spec.md#requirement-the-board-look-draws-a-short-filter-as-a-checkbox-list-with-counts
|
|
1459
|
+
* @param {object} filter The filter definition.
|
|
1460
|
+
* @return {Array<{id: (string|number|boolean), label: string, count: (number|null)}>|null} The rows, or null.
|
|
1461
|
+
*/
|
|
1462
|
+
checklistOptions(filter) {
|
|
1463
|
+
if (!this.isBoardLook || filter.multiple === false || filter.type === 'reference') {
|
|
1464
|
+
return null
|
|
1465
|
+
}
|
|
1466
|
+
const facet = this.facetData[filter.key]
|
|
1467
|
+
let rows
|
|
1468
|
+
if (facet && Array.isArray(facet.values) && facet.values.length > 0) {
|
|
1469
|
+
rows = facet.values.map((bucket) => ({
|
|
1470
|
+
id: bucket.value,
|
|
1471
|
+
label: String(bucket.label !== undefined && bucket.label !== null ? bucket.label : bucket.value),
|
|
1472
|
+
count: typeof bucket.count === 'number' ? bucket.count : null,
|
|
1473
|
+
}))
|
|
1474
|
+
if (typeof facet.missing === 'number' && facet.missing > 0) {
|
|
1475
|
+
rows.push({ id: MISSING_VALUE, label: t('nextcloud-vue', 'No value'), count: facet.missing })
|
|
1476
|
+
}
|
|
1477
|
+
} else {
|
|
1478
|
+
rows = (filter.options || []).map((option) => ({ id: option.id, label: String(option.label), count: null }))
|
|
1479
|
+
}
|
|
1480
|
+
return rows.length > 0 && rows.length <= CHECKLIST_MAX ? rows : null
|
|
1481
|
+
},
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Whether a value of a field is among its active filter values.
|
|
1485
|
+
*
|
|
1486
|
+
* @param {object} filter The filter definition.
|
|
1487
|
+
* @param {string|number|boolean} id The value.
|
|
1488
|
+
* @return {boolean}
|
|
1489
|
+
*/
|
|
1490
|
+
isChecked(filter, id) {
|
|
1491
|
+
const value = this.activeFilters[filter.key]
|
|
1492
|
+
const values = Array.isArray(value) ? value : (value === undefined || value === null || value === '' ? [] : [value])
|
|
1493
|
+
return values.map(String).includes(String(id))
|
|
1494
|
+
},
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* A checkbox row was toggled: the field's values with it added or taken out.
|
|
1498
|
+
*
|
|
1499
|
+
* @spec openspec/changes/board-sidebar-filters-as-checkbox-lists/specs/index-sidebar/spec.md#requirement-the-board-look-draws-a-short-filter-as-a-checkbox-list-with-counts
|
|
1500
|
+
* @param {object} filter The filter definition.
|
|
1501
|
+
* @param {string|number|boolean} id The value.
|
|
1502
|
+
* @param {boolean} checked Whether it is now ticked.
|
|
1503
|
+
* @return {void}
|
|
1504
|
+
*/
|
|
1505
|
+
onChecklistToggle(filter, id, checked) {
|
|
1506
|
+
const value = this.activeFilters[filter.key]
|
|
1507
|
+
const current = Array.isArray(value) ? [...value] : (value === undefined || value === null || value === '' ? [] : [value])
|
|
1508
|
+
const rest = current.filter((v) => String(v) !== String(id))
|
|
1509
|
+
this.$emit('filter-change', { key: filter.key, values: checked ? [...rest, id] : rest })
|
|
1510
|
+
},
|
|
1511
|
+
|
|
1414
1512
|
/**
|
|
1415
1513
|
* Handle filter select change
|
|
1416
1514
|
*
|
|
@@ -226,6 +226,25 @@ function controlIcon(path) {
|
|
|
226
226
|
* @slot popup Per-marker popup body override. Scoped props: `{ feature, properties }`. When omitted, the widget renders the value of `markers.popupField` (or no popup at all when unset).
|
|
227
227
|
* @slot fallback Replaces the default "map unavailable" message when Leaflet fails to load (test environments, CSP-blocked builds, offline dev). No scoped props.
|
|
228
228
|
*/
|
|
229
|
+
/**
|
|
230
|
+
* The count line a header spec gives (`{ one, other }` or one string, with
|
|
231
|
+
* `{total}`), once the marker source answered with markers.
|
|
232
|
+
*
|
|
233
|
+
* @param {object|string|undefined} spec The declared line.
|
|
234
|
+
* @param {object} vm The map: `markersLoaded`, `markersFailed`, `markerCount`, `cnTranslate`.
|
|
235
|
+
* @return {string} The line, or ''.
|
|
236
|
+
*/
|
|
237
|
+
function countLine(spec, vm) {
|
|
238
|
+
if (!spec || !vm.markersLoaded || vm.markersFailed || vm.markerCount === 0) {
|
|
239
|
+
return ''
|
|
240
|
+
}
|
|
241
|
+
const text = typeof spec === 'string' ? spec : (vm.markerCount === 1 ? spec.one : spec.other)
|
|
242
|
+
if (typeof text !== 'string' || text === '') {
|
|
243
|
+
return ''
|
|
244
|
+
}
|
|
245
|
+
return vm.cnTranslate(text).replace(/\{total\}/g, String(vm.markerCount))
|
|
246
|
+
}
|
|
247
|
+
|
|
229
248
|
export default {
|
|
230
249
|
name: 'CnMapWidget',
|
|
231
250
|
|
|
@@ -577,15 +596,19 @@ export default {
|
|
|
577
596
|
* @spec openspec/changes/map-widget-header-count/specs/map-widget-header-count/spec.md#requirement-a-map-card-can-say-how-many-places-it-shows
|
|
578
597
|
*/
|
|
579
598
|
headerSubtitleText() {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
599
|
+
return countLine(this.content?.headerSubtitle, this)
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* The same count as a note at the right of the card's title row
|
|
604
|
+
* (`content.headerNote`, the same shape as `headerSubtitle`), as the
|
|
605
|
+
* case boards draw "Locaties ... 1 locatie".
|
|
606
|
+
*
|
|
607
|
+
* @return {string}
|
|
608
|
+
* @spec openspec/changes/map-count-note/specs/map-widget/spec.md
|
|
609
|
+
*/
|
|
610
|
+
headerNoteText() {
|
|
611
|
+
return countLine(this.content?.headerNote, this)
|
|
589
612
|
},
|
|
590
613
|
|
|
591
614
|
markerStatusText() {
|
|
@@ -672,6 +695,21 @@ export default {
|
|
|
672
695
|
}
|
|
673
696
|
},
|
|
674
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Report the count note to the card around the map.
|
|
700
|
+
*
|
|
701
|
+
* @param {string} note The note, or ''.
|
|
702
|
+
*/
|
|
703
|
+
headerNoteText(note) {
|
|
704
|
+
if (this.content?.headerNote) {
|
|
705
|
+
/**
|
|
706
|
+
* @event header The card header's note at the right of the title.
|
|
707
|
+
* @type {{note: string}}
|
|
708
|
+
*/
|
|
709
|
+
this.$emit('header', { note })
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
|
|
675
713
|
// Keyed, never `deep: true` — see `layersKey`.
|
|
676
714
|
layersKey() {
|
|
677
715
|
if (this.map) {
|
|
@@ -669,14 +669,14 @@ export default {
|
|
|
669
669
|
},
|
|
670
670
|
|
|
671
671
|
/**
|
|
672
|
-
* How the table draws its title column (`content.rowTitle`: link, plain
|
|
673
|
-
* or
|
|
672
|
+
* How the table draws its title column (`content.rowTitle`: link, plain,
|
|
673
|
+
* text or strong), link when unset or unknown.
|
|
674
674
|
*
|
|
675
675
|
* @return {string}
|
|
676
676
|
* @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-tables-title-column-can-read-as-plain-text
|
|
677
677
|
*/
|
|
678
678
|
rowTitle() {
|
|
679
|
-
return ['plain', 'text'].includes(this.content.rowTitle) ? this.content.rowTitle : 'link'
|
|
679
|
+
return ['plain', 'text', 'strong'].includes(this.content.rowTitle) ? this.content.rowTitle : 'link'
|
|
680
680
|
},
|
|
681
681
|
|
|
682
682
|
/**
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
:dropOverlay="dropOverlay"
|
|
17
17
|
:dropLabel="dropLabel"
|
|
18
18
|
:dropDetail="dropDetail"
|
|
19
|
+
:dropDetailValues="dropDetailValues"
|
|
19
20
|
:dropHint="dropHint"
|
|
20
21
|
:rowActions="rowActions"
|
|
21
22
|
:newActions="newActions"
|
|
@@ -360,7 +361,9 @@ export default {
|
|
|
360
361
|
/** Forwarded to CnFilesBrowser: the drop state's line ("Laat los om toe te voegen"); the library's own without it. */
|
|
361
362
|
dropLabel: { type: String, default: undefined },
|
|
362
363
|
/** Forwarded to CnFilesBrowser: the drop state's second line, `{count}` the files dragged; it also makes the overlay fill the panel. */
|
|
363
|
-
dropDetail: { type: String, default: '' },
|
|
364
|
+
dropDetail: { type: [String, Array], default: '' },
|
|
365
|
+
/** Forwarded to CnFilesBrowser: named values filled into `dropDetail`. */
|
|
366
|
+
dropDetailValues: { type: Object, default: null },
|
|
364
367
|
/** Forwarded to CnFilesBrowser: "Or drag files onto this list." under the list. */
|
|
365
368
|
dropHint: { type: Boolean, default: false },
|
|
366
369
|
/**
|
|
@@ -52,7 +52,9 @@ const SEARCH_DELAY = 250
|
|
|
52
52
|
* Organisatie · KvK …"). Built on CnSearchSelect.
|
|
53
53
|
*
|
|
54
54
|
* `sources`: each `{ id, register, schema, label?, title?, secondary?,
|
|
55
|
-
* limit?, icon? }` searches OpenRegister with `_search`; `
|
|
55
|
+
* limit?, icon?, lookup? }` searches OpenRegister with `_search`; `lookup`
|
|
56
|
+
* (`{ url, when }`) sends a term matching `when` to the app's exact lookup
|
|
57
|
+
* instead (a POST of `{ term }` answering `{ results }`); `title` and
|
|
56
58
|
* `secondary` are templates filled from the row (default `{name}` / none),
|
|
57
59
|
* `label` names the kind ("Resident") and starts the second line; `label`
|
|
58
60
|
* and `secondary` go through the host translate, so a manifest can write
|
|
@@ -284,6 +286,10 @@ export default {
|
|
|
284
286
|
const { data } = await axios.post(generateUrl('/contactsmenu/contacts'), { filter: term })
|
|
285
287
|
return (data && Array.isArray(data.contacts) ? data.contacts : []).map((c) => this.contactOptionOf(source, c))
|
|
286
288
|
}
|
|
289
|
+
if (this.isExactLookup(source, term) === true) {
|
|
290
|
+
const { data } = await axios.post(generateUrl(source.lookup.url), { term })
|
|
291
|
+
return (data && Array.isArray(data.results) ? data.results : []).map((row) => this.optionOf(source, row))
|
|
292
|
+
}
|
|
287
293
|
if (!source.register || !source.schema) {
|
|
288
294
|
return []
|
|
289
295
|
}
|
|
@@ -311,6 +317,30 @@ export default {
|
|
|
311
317
|
this.searching = false
|
|
312
318
|
},
|
|
313
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Whether a typed term goes to the source's exact lookup instead of the
|
|
322
|
+
* free-text search: `source.lookup` is `{ url, when }`, `when` a regular
|
|
323
|
+
* expression the whole term must match ("^[0-9]{9}$" for a BSN). The
|
|
324
|
+
* lookup is a POST, so the term never sits in a URL or a server log line,
|
|
325
|
+
* and the app behind it may check and record who looked whom up.
|
|
326
|
+
*
|
|
327
|
+
* @param {object} source The source.
|
|
328
|
+
* @param {string} term The typed term.
|
|
329
|
+
* @return {boolean}
|
|
330
|
+
* @spec openspec/changes/party-picker-exact-lookup/specs/party-picker/spec.md#requirement-a-party-source-can-look-a-term-up-exactly
|
|
331
|
+
*/
|
|
332
|
+
isExactLookup(source, term) {
|
|
333
|
+
const lookup = source && source.lookup
|
|
334
|
+
if (!lookup || typeof lookup.url !== 'string' || lookup.url === '' || typeof lookup.when !== 'string') {
|
|
335
|
+
return false
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
return new RegExp(lookup.when).test(term)
|
|
339
|
+
} catch {
|
|
340
|
+
return false
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
|
|
314
344
|
/**
|
|
315
345
|
* Name a value the list does not hold: the first OpenRegister source
|
|
316
346
|
* that has an object with that id.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<template>
|
|
7
7
|
<div
|
|
8
8
|
class="cn-record-list"
|
|
9
|
-
:class="[`cn-record-list--${presentation}`, { 'cn-record-list--compact': content.density === 'compact', 'cn-record-list--tiles': presentation === 'rows' && content.tiles === true, 'cn-record-list--rule-above': content.ruleAbove === true, 'cn-record-list--data-table': presentation === 'table' && content.tableLook === 'data' }]"
|
|
9
|
+
:class="[`cn-record-list--${presentation}`, { 'cn-record-list--compact': content.density === 'compact', 'cn-record-list--stacked': content.density === 'stacked', 'cn-record-list--tiles': presentation === 'rows' && content.tiles === true, 'cn-record-list--rule-above': content.ruleAbove === true, 'cn-record-list--data-table': presentation === 'table' && content.tableLook === 'data' }]"
|
|
10
10
|
data-testid="cn-record-list">
|
|
11
11
|
<p v-if="intro && !absent" class="cn-record-list__intro" data-testid="cn-record-list-intro">
|
|
12
12
|
{{ intro }}
|
|
@@ -713,6 +713,8 @@ export default {
|
|
|
713
713
|
* without a value.
|
|
714
714
|
* `density: "compact"` draws the rows 10px above and below, at least
|
|
715
715
|
* 56px tall, with the title at 500 (DqProcesanalyse).
|
|
716
|
+
* `density: "stacked"` stands the rows 10px apart with no rule and no
|
|
717
|
+
* row padding (DqDagAfsluiten "Morgen eerst").
|
|
716
718
|
* In `rows`, `text` is a line under the meta line ("12 dagen stil"),
|
|
717
719
|
* and `chevron: false` leaves the chevron off a linked row
|
|
718
720
|
* (DqDashboard).
|
|
@@ -3103,6 +3105,23 @@ export default {
|
|
|
3103
3105
|
font-weight: 500;
|
|
3104
3106
|
}
|
|
3105
3107
|
|
|
3108
|
+
/* `density: "stacked"`: the rows stand 10px apart with no rule and no row
|
|
3109
|
+
padding, a short side list (DqDagAfsluiten "Morgen eerst"). */
|
|
3110
|
+
.cn-record-list--stacked .cn-record-list__list {
|
|
3111
|
+
display: flex;
|
|
3112
|
+
flex-direction: column;
|
|
3113
|
+
gap: 10px;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
.cn-record-list--stacked .cn-record-list__row {
|
|
3117
|
+
min-height: 0;
|
|
3118
|
+
padding: 0;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
.cn-record-list--stacked .cn-record-list__item + .cn-record-list__item {
|
|
3122
|
+
border-top: 0;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3106
3125
|
.cn-record-list__meta {
|
|
3107
3126
|
color: var(--color-text-maxcontrast);
|
|
3108
3127
|
font-size: 13px;
|
|
@@ -37,15 +37,20 @@
|
|
|
37
37
|
data-testid="cn-record-list-due">{{ item.due.text }}</span>
|
|
38
38
|
</div>
|
|
39
39
|
<div class="cn-record-notes__fields">
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
<!-- The label stands above the field, as the leaf's own beside it
|
|
41
|
+
(humaniq "Uren"), so both fields share one line. -->
|
|
42
|
+
<div class="cn-record-notes__field">
|
|
43
|
+
<label :for="fieldId(item.key)" class="cn-record-notes__field-label">{{ fieldLabel }}</label>
|
|
44
|
+
<NcTextField
|
|
45
|
+
:id="fieldId(item.key)"
|
|
46
|
+
:modelValue="drafts[item.key] || ''"
|
|
47
|
+
:label="fieldLabel"
|
|
48
|
+
labelOutside
|
|
49
|
+
:placeholder="placeholder"
|
|
50
|
+
:disabled="busy"
|
|
51
|
+
:data-testid="`cn-record-list-note-${item.key}`"
|
|
52
|
+
@update:modelValue="setDraft(item.key, $event)" />
|
|
53
|
+
</div>
|
|
49
54
|
<div v-if="leaf && leafProvider" class="cn-record-notes__leaf" :data-testid="`cn-record-list-leaf-${item.key}`">
|
|
50
55
|
<CnLeafMountHost
|
|
51
56
|
v-if="leafIsMount"
|
|
@@ -301,6 +306,16 @@ export default {
|
|
|
301
306
|
this.drafts = { ...this.drafts, [key]: String(value ?? '') }
|
|
302
307
|
},
|
|
303
308
|
|
|
309
|
+
/**
|
|
310
|
+
* The id of a row's note field, for its label.
|
|
311
|
+
*
|
|
312
|
+
* @param {string} key The row key.
|
|
313
|
+
* @return {string} The id.
|
|
314
|
+
*/
|
|
315
|
+
fieldId(key) {
|
|
316
|
+
return `cn-record-notes-${this.$.uid}-${String(key).replace(/[^A-Za-z0-9_-]/g, '-')}`
|
|
317
|
+
},
|
|
318
|
+
|
|
304
319
|
/**
|
|
305
320
|
* @param {string} key The row key.
|
|
306
321
|
* @return {boolean} Whether the row has a note typed.
|
|
@@ -472,7 +487,16 @@ export default {
|
|
|
472
487
|
}
|
|
473
488
|
|
|
474
489
|
.cn-record-notes__field {
|
|
490
|
+
display: flex;
|
|
475
491
|
flex: 1 1 260px;
|
|
492
|
+
flex-direction: column;
|
|
493
|
+
gap: 6px;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.cn-record-notes__field-label {
|
|
497
|
+
color: var(--color-main-text);
|
|
498
|
+
font-size: 14px;
|
|
499
|
+
font-weight: 600;
|
|
476
500
|
}
|
|
477
501
|
|
|
478
502
|
.cn-record-notes__leaf {
|
package/src/css/dialog.css
CHANGED
|
@@ -238,8 +238,11 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
|
|
|
238
238
|
padding-inline-end: 24px;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/* The body starts 22px under the header, as most dialog boards draw it
|
|
242
|
+
(header padding-bottom 4px + form padding-top 18px; DqBerichtVersturen,
|
|
243
|
+
DqOverdragen, DqNieuweZaak, DqBesluit): the 18px gap plus 4px. */
|
|
241
244
|
.cn-dialog-board .dialog__content > .cn-dialog-header {
|
|
242
|
-
margin-bottom:
|
|
245
|
+
margin-bottom: 4px;
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
/* Footer: 16px 24px, hairline on top, right aligned, 40px buttons, 10px gap. */
|
|
@@ -344,6 +347,15 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
|
|
|
344
347
|
margin-block-start: 26px;
|
|
345
348
|
}
|
|
346
349
|
|
|
350
|
+
/* A select in a form dialog sits under its label and over its help line
|
|
351
|
+
as a text field does (DqNieuweZaak "Zaaktype", "Intakekanaal"): the
|
|
352
|
+
form's label stands outside the select, so NcSelect's own 6px/4px margins
|
|
353
|
+
only pushed the control 6px below the text fields beside it and its help
|
|
354
|
+
line 4px further down. */
|
|
355
|
+
.cn-dialog-board .dialog__content .cn-form-dialog__select-wrapper > .v-select.select:not(:has(.input-field__label)) {
|
|
356
|
+
margin-block: 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
347
359
|
/* A multiple select is the board's 40px field too (DqFilterDelen "Delen
|
|
348
360
|
met"). The edge goes on the toggle, which holds the chips and the search,
|
|
349
361
|
so the chosen people and groups sit inside the field; the search inside it
|
|
@@ -721,6 +721,13 @@
|
|
|
721
721
|
padding-top: calc(14px - var(--cn-board-first-line-leading));
|
|
722
722
|
}
|
|
723
723
|
|
|
724
|
+
/* A table (an object list) starts 12px below the title's box: its head has
|
|
725
|
+
a fill and no leading, so the glyph-to-glyph gap above left it 2px short
|
|
726
|
+
(DqZaaktypeStatussen "Statussen", "Resultaten"). */
|
|
727
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper__content:has(> .cn-object-list-widget):not(.cn-detail-page__side *) {
|
|
728
|
+
padding-top: 12px;
|
|
729
|
+
}
|
|
730
|
+
|
|
724
731
|
/* Running text (DqZaak "Het verzoek") sits 12px below the title's box and
|
|
725
732
|
keeps its own half-leading, at 1.6 as drawn.
|
|
726
733
|
@spec openspec/changes/board-card-title-gap-variants/specs/board-card-title-gap-variants/spec.md#requirement-a-board-card-starts-its-body-as-drawn-for-its-kind-of-body */
|
|
@@ -1527,12 +1534,12 @@
|
|
|
1527
1534
|
}
|
|
1528
1535
|
|
|
1529
1536
|
/* ---- A card's Add ------------------------------------------------------ */
|
|
1530
|
-
/* The board draws a list card's Add as the neutral secondary
|
|
1531
|
-
|
|
1532
|
-
"
|
|
1537
|
+
/* The board draws a list card's Add as the neutral secondary right-aligned
|
|
1538
|
+
in the title row, at the title row's 34px (DqZaaktypeStatussen "Status
|
|
1539
|
+
toevoegen", DqZaaktype "Taak toevoegen", DqCatalogus "Objecttype
|
|
1540
|
+
toevoegen": 13 of the 15 boards that draw one). CnDetailWidgetHost renders
|
|
1533
1541
|
that button in the board look, so the list's footer Add bar, which ADR-062
|
|
1534
|
-
draws in the Nextcloud look, is hidden in the card.
|
|
1535
|
-
card title row's 34px buttons.
|
|
1542
|
+
draws in the Nextcloud look, is hidden in the card.
|
|
1536
1543
|
@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 */
|
|
1537
1544
|
.cn-look-board .cn-detail-page__catalog-card:not(.cn-look-nextcloud *) .cn-object-list-widget__add {
|
|
1538
1545
|
display: none;
|
|
@@ -1544,10 +1551,6 @@
|
|
|
1544
1551
|
display: none;
|
|
1545
1552
|
}
|
|
1546
1553
|
|
|
1547
|
-
.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 *)) {
|
|
1548
|
-
--button-size: var(--cn-board-control-height);
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
1554
|
/* A map in a board card has the board's control corners (DqZaak, DqZaakMeer
|
|
1552
1555
|
"Locaties"); overflow keeps the tiles inside them. */
|
|
1553
1556
|
.cn-look-board .cn-map-widget:not(.cn-map-widget--fullscreen) {
|
|
@@ -1582,6 +1585,49 @@
|
|
|
1582
1585
|
line-height: 1.2;
|
|
1583
1586
|
}
|
|
1584
1587
|
|
|
1588
|
+
/* The contacts tab's rows as DqZaakBetrokkenen "Partijen" draws them: the
|
|
1589
|
+
role heading 15px bold ink in sentence case, each person on a row 12px
|
|
1590
|
+
high-padded under a hairline, the details and the period 13px muted.
|
|
1591
|
+
@spec openspec/changes/board-contacts-rows/specs/contacts/spec.md */
|
|
1592
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__group-title {
|
|
1593
|
+
margin: 6px 0 0;
|
|
1594
|
+
font-size: 15px;
|
|
1595
|
+
font-weight: 700;
|
|
1596
|
+
letter-spacing: 0;
|
|
1597
|
+
text-transform: none;
|
|
1598
|
+
color: var(--color-main-text);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__item {
|
|
1602
|
+
align-items: center;
|
|
1603
|
+
padding: 12px 0;
|
|
1604
|
+
border-top: 1px solid var(--cn-board-hairline);
|
|
1605
|
+
border-radius: 0;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__item:first-child {
|
|
1609
|
+
margin-top: 8px;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__name {
|
|
1613
|
+
font-size: 15px;
|
|
1614
|
+
font-weight: 600;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__meta,
|
|
1618
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__period {
|
|
1619
|
+
font-size: 13px;
|
|
1620
|
+
color: var(--color-text-maxcontrast);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__period {
|
|
1624
|
+
flex: none;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
.cn-look-board .cn-contacts-tab .cn-contacts-tab__group {
|
|
1628
|
+
margin-bottom: 8px;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1585
1631
|
/* The contacts tab inside a detail card (DqZaakBetrokkenen "Partijen") takes
|
|
1586
1632
|
the card's padding instead of its own, and its buttons start at the card's
|
|
1587
1633
|
edge under the subtitle.
|
|
@@ -1593,3 +1639,55 @@
|
|
|
1593
1639
|
.cn-look-board .cn-detail-page__leaf-card .cn-contacts-tab .cn-contacts-tab__header {
|
|
1594
1640
|
justify-content: flex-start;
|
|
1595
1641
|
}
|
|
1642
|
+
|
|
1643
|
+
/* A body card's subtitle as the Dq boards draw it: 14px muted at line
|
|
1644
|
+
height 1.2, 3px under the title, and the content 12px under it.
|
|
1645
|
+
@spec openspec/changes/board-card-subtitle-gaps/specs/board-card/spec.md */
|
|
1646
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper:not(.cn-detail-page__side *) .cn-widget-wrapper__title-stack:has(> .cn-widget-wrapper__subtitle) {
|
|
1647
|
+
gap: 3px;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper:not(.cn-detail-page__side *) .cn-widget-wrapper__subtitle {
|
|
1651
|
+
font-size: 14px;
|
|
1652
|
+
line-height: 1.2;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper:not(.cn-detail-page__side *):has(> .cn-widget-wrapper__header .cn-widget-wrapper__subtitle) > .cn-widget-wrapper__content {
|
|
1656
|
+
padding-top: 12px;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/* A checklist (DqTaak): each item an 18-20px box at the card's own inset, the
|
|
1660
|
+
label 10px after it in weight 600, 12px between items, the list 2px below
|
|
1661
|
+
the title block. Nextcloud's checkbox pads its box 7px in and makes every row
|
|
1662
|
+
34px tall, which put the list 7px right and 2px apart too far per item.
|
|
1663
|
+
@spec openspec/changes/board-checklist-rows/specs/checklist-widget/spec.md#requirement-a-board-checklist-sits-at-the-card-inset */
|
|
1664
|
+
.cn-look-board .cn-checklist-widget__items {
|
|
1665
|
+
display: flex;
|
|
1666
|
+
flex-direction: column;
|
|
1667
|
+
gap: 12px;
|
|
1668
|
+
margin-top: 2px;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
.cn-look-board .cn-checklist-widget__item .checkbox-content {
|
|
1672
|
+
padding: 0;
|
|
1673
|
+
gap: 10px;
|
|
1674
|
+
min-height: 20px;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
.cn-look-board .cn-checklist-widget__item .checkbox-content.checkbox-content .checkbox-content__icon {
|
|
1678
|
+
margin: 0;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.cn-look-board .cn-checklist-widget__item .checkbox-radio-switch__input {
|
|
1682
|
+
min-height: 20px;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.cn-look-board .cn-checklist-widget__item .checkbox-content__wrapper,
|
|
1686
|
+
.cn-look-board .cn-checklist-widget__item .checkbox-content__text {
|
|
1687
|
+
line-height: 18px;
|
|
1688
|
+
min-height: 0;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
.cn-look-board .cn-checklist-widget__item .checkbox-content__text {
|
|
1692
|
+
font-weight: 600;
|
|
1693
|
+
}
|