@conduction/nextcloud-vue 2.76.0 → 2.78.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/CnAiCompanion/CnAiCompanion.vue2.js +23 -0
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +18 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +49 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +79 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +229 -203
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +37 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +2 -2
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -4
- 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 +48 -6
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue.js +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js +45 -2
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +16 -28
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +4 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +104 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +13 -9
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +36 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +35 -23
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js +6 -2
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +11 -1
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -0
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useObjectPresence.js +15 -4
- package/dist/esm/composables/useObjectPresence.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +32 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +32 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +500 -75
- package/dist/esm/store/plugins/interactions.js +20 -10
- package/dist/esm/store/plugins/interactions.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +17 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/badgeVariants.js +32 -0
- package/dist/esm/utils/badgeVariants.js.map +1 -0
- package/dist/esm/utils/builtInFormatters.js +15 -7
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/groupAutocomplete.js +63 -1
- package/dist/esm/utils/groupAutocomplete.js.map +1 -1
- package/dist/esm/utils/lensAvailability.js +90 -0
- package/dist/esm/utils/lensAvailability.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +13 -6
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/recordInteractions.js +31 -13
- package/dist/esm/utils/recordInteractions.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +46 -3
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/schema.js +1 -1
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +4 -4
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +46 -1
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +4389 -2890
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +500 -75
- package/l10n/en.json +32 -1
- package/l10n/nl.json +32 -1
- package/package.json +1 -1
- package/src/components/CnAiCompanion/CnAiCompanion.vue +23 -0
- package/src/components/CnAppNav/CnAppNav.vue +44 -5
- package/src/components/CnBoardView/CnBoardView.vue +2 -1
- package/src/components/CnCellRenderer/CnCellRenderer.md +1 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +106 -9
- package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +39 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +29 -4
- package/src/components/CnDataTable/CnDataTable.md +9 -0
- package/src/components/CnDataTable/CnDataTable.vue +87 -22
- package/src/components/CnDateRangePicker/CnDateRangePicker.vue +44 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +10 -27
- package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
- package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
- package/src/components/CnFormDialog/CnFormDialog.vue +7 -1
- package/src/components/CnFormPage/CnFormPage.vue +4 -1
- package/src/components/CnIndexPage/CnIndexPage.md +36 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +112 -19
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
- package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +52 -2
- package/src/components/CnStatusBadge/CnStatusBadge.vue +6 -2
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/composables/__tests__/useObjectPresence.spec.js +24 -0
- package/src/composables/useFormDraft.js +11 -1
- package/src/composables/useListView.js +4 -0
- package/src/composables/useObjectPresence.js +15 -4
- package/src/css/badge.css +31 -0
- package/src/css/kpi-card.css +51 -0
- package/src/css/look-board-detail.css +40 -2
- package/src/css/look-board-index.css +36 -0
- package/src/css/look-board.css +8 -0
- package/src/css/table.css +82 -17
- package/src/index.js +2 -1
- package/src/schemas/app-manifest-v2.schema.json +15 -11
- package/src/store/plugins/interactions.js +20 -10
- package/src/store/useObjectStore.js +17 -0
- package/src/utils/badgeVariants.js +29 -0
- package/src/utils/builtInFormatters.js +15 -7
- package/src/utils/groupAutocomplete.js +75 -0
- package/src/utils/index.js +1 -1
- package/src/utils/lensAvailability.js +86 -0
- package/src/utils/personalLenses.js +13 -6
- package/src/utils/recordInteractions.js +31 -13
- package/src/utils/resolveFilterTokens.js +45 -2
- package/src/utils/schema.js +1 -1
- package/src/utils/sentinelTokens.js +4 -4
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/widgetVisibility.js +45 -0
|
@@ -40,6 +40,7 @@ jest.mock('@nextcloud/router', () => ({
|
|
|
40
40
|
jest.mock('@nextcloud/auth', () => ({
|
|
41
41
|
__esModule: true,
|
|
42
42
|
getCurrentUser: () => ({ uid: 'anna' }),
|
|
43
|
+
getRequestToken: () => 'token-123',
|
|
43
44
|
}))
|
|
44
45
|
|
|
45
46
|
/** The subscription the composable made, so a test can push into it. */
|
|
@@ -246,6 +247,29 @@ describe('useObjectPresence', () => {
|
|
|
246
247
|
scope.stop()
|
|
247
248
|
})
|
|
248
249
|
|
|
250
|
+
it('🔴 a closing tab departs with a beacon that carries the request token', async () => {
|
|
251
|
+
// A beacon cannot set headers, and the departure route keeps the CSRF
|
|
252
|
+
// check, so the token has to travel in the form body. The old beacon
|
|
253
|
+
// sent no body at all and could never pass that check.
|
|
254
|
+
const sendBeacon = jest.fn(() => true)
|
|
255
|
+
const original = navigator.sendBeacon
|
|
256
|
+
Object.defineProperty(navigator, 'sendBeacon', { value: sendBeacon, configurable: true, writable: true })
|
|
257
|
+
try {
|
|
258
|
+
await withPresence(async () => {
|
|
259
|
+
await nextTick()
|
|
260
|
+
await Promise.resolve()
|
|
261
|
+
window.dispatchEvent(new Event('beforeunload'))
|
|
262
|
+
})
|
|
263
|
+
} finally {
|
|
264
|
+
Object.defineProperty(navigator, 'sendBeacon', { value: original, configurable: true, writable: true })
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const call = sendBeacon.mock.calls.find(([url]) => url === '/apps/openregister/api/objects/dossiq/case/case-1/presence?_method=DELETE')
|
|
268
|
+
expect(call).toBeDefined()
|
|
269
|
+
expect(call[1]).toBeInstanceOf(FormData)
|
|
270
|
+
expect(call[1].get('requesttoken')).toBe('token-123')
|
|
271
|
+
})
|
|
272
|
+
|
|
249
273
|
it('does nothing at all when disabled', async () => {
|
|
250
274
|
const scope = effectScope()
|
|
251
275
|
scope.run(() => {
|
|
@@ -127,12 +127,22 @@ export function readDraft(key, { objectUpdated = '', now = Date.now() } = {}) {
|
|
|
127
127
|
* What the draft indicator says: "Saving" while a local write is pending,
|
|
128
128
|
* "Saved just now" / "Saved 3 minutes ago" once it landed, nothing before that.
|
|
129
129
|
*
|
|
130
|
+
* After a failed save (`options.saveFailed`) it never says Saving or Saved:
|
|
131
|
+
* next to a server error those words read as if the record had been saved.
|
|
132
|
+
* It then says "Draft kept on this device" when a local copy is stored, and
|
|
133
|
+
* nothing otherwise.
|
|
134
|
+
*
|
|
130
135
|
* @param {string} state `idle`, `saving` or `saved`.
|
|
131
136
|
* @param {number} [savedAt] When the last write landed, in epoch ms.
|
|
132
137
|
* @param {number} [now] The current epoch ms, for testing.
|
|
138
|
+
* @param {{saveFailed?: boolean}} [options] `saveFailed`: the form shows a failed save.
|
|
133
139
|
* @return {string} The text, or '' when it says nothing.
|
|
140
|
+
* @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-the-draft-indicator-never-reads-as-a-server-save-after-a-failed-save
|
|
134
141
|
*/
|
|
135
|
-
export function draftIndicatorText(state, savedAt = 0, now = Date.now()) {
|
|
142
|
+
export function draftIndicatorText(state, savedAt = 0, now = Date.now(), options = {}) {
|
|
143
|
+
if (options && options.saveFailed === true) {
|
|
144
|
+
return state === 'saved' ? t('nextcloud-vue', 'Draft kept on this device') : ''
|
|
145
|
+
}
|
|
136
146
|
if (state === 'saving') {
|
|
137
147
|
return t('nextcloud-vue', 'Saving')
|
|
138
148
|
}
|
|
@@ -98,6 +98,9 @@ export function useListView(objectTypeOrOptions, options) {
|
|
|
98
98
|
// field (a folder pane grouping by it, say) reads them here rather than
|
|
99
99
|
// deriving them from `objects`, which is only the current page.
|
|
100
100
|
const facets = computed(() => objectStore.facets[getType()] || {})
|
|
101
|
+
// Personal-lens reports of the latest response (`{ recent: { available, reason } }`).
|
|
102
|
+
// @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-a-list-response-carries-the-report-of-the-lenses-it-was-asked-for
|
|
103
|
+
const lenses = computed(() => (objectStore.lenses && objectStore.lenses[getType()]) || {})
|
|
101
104
|
// Outcome of the latest `refresh()`: the store's error for it, or null once
|
|
102
105
|
// it succeeded or a later fetch stored new rows. The store keeps the previous
|
|
103
106
|
// rows on a failed fetch, so this is how a consumer tells stale rows from
|
|
@@ -381,6 +384,7 @@ export function useListView(objectTypeOrOptions, options) {
|
|
|
381
384
|
loading,
|
|
382
385
|
pagination,
|
|
383
386
|
facets,
|
|
387
|
+
lenses,
|
|
384
388
|
error,
|
|
385
389
|
// Local state
|
|
386
390
|
searchTerm,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
// Copyright (C) 2026 Conduction B.V.
|
|
3
3
|
|
|
4
|
-
import { getCurrentUser } from '@nextcloud/auth'
|
|
4
|
+
import { getCurrentUser, getRequestToken } from '@nextcloud/auth'
|
|
5
5
|
import axios from '@nextcloud/axios'
|
|
6
6
|
import { generateUrl } from '@nextcloud/router'
|
|
7
7
|
import { computed, getCurrentScope, onScopeDispose, ref, unref, watch } from 'vue'
|
|
@@ -201,7 +201,15 @@ export function useObjectPresence(register, schema, objectUuid, options = {}) {
|
|
|
201
201
|
* the browser and survives the navigation, which is the only mechanism
|
|
202
202
|
* that does.
|
|
203
203
|
*
|
|
204
|
+
* 🔴 THE TOKEN RIDES IN THE BODY. A beacon cannot set headers, so the
|
|
205
|
+
* `requesttoken` header axios adds is missing, and the departure route
|
|
206
|
+
* keeps Nextcloud's CSRF check. Nextcloud also reads the token from a
|
|
207
|
+
* POST field, so it goes in a form body. Without it the beacon answers
|
|
208
|
+
* 412; before OpenRegister registered POST on `/presence` it answered 405
|
|
209
|
+
* (seen on cloud.conduction.nl on every dossiq case page).
|
|
210
|
+
*
|
|
204
211
|
* @return {void}
|
|
212
|
+
* @spec openspec/changes/no-calls-to-missing-apps-and-presence-beacon/specs/object-presence/spec.md#requirement-a-closing-tab-departs-through-a-route-that-exists
|
|
205
213
|
*/
|
|
206
214
|
function beaconDepart() {
|
|
207
215
|
const url = presenceUrl()
|
|
@@ -209,9 +217,12 @@ export function useObjectPresence(register, schema, objectUuid, options = {}) {
|
|
|
209
217
|
return
|
|
210
218
|
}
|
|
211
219
|
try {
|
|
212
|
-
// No DELETE from a beacon
|
|
213
|
-
//
|
|
214
|
-
|
|
220
|
+
// No DELETE from a beacon, it is always a POST, so the server's
|
|
221
|
+
// POST departure route is used, marked `_method=DELETE`. Both reach
|
|
222
|
+
// one implementation.
|
|
223
|
+
const body = new FormData()
|
|
224
|
+
body.append('requesttoken', getRequestToken() || '')
|
|
225
|
+
navigator.sendBeacon?.(url.replace(/\/presence$/, '/presence?_method=DELETE'), body)
|
|
215
226
|
} catch {
|
|
216
227
|
// Best effort.
|
|
217
228
|
}
|
package/src/css/badge.css
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
@conduction/nextcloud-vue — Status Badge
|
|
3
3
|
======================================== */
|
|
4
4
|
|
|
5
|
+
/* The two board tones without a Nextcloud colour role (screens-cell-pill-parity).
|
|
6
|
+
A theme may redefine them; the six role variants below use Nextcloud's own. */
|
|
7
|
+
:root {
|
|
8
|
+
--cn-status-purple-bg: #e8e6f6;
|
|
9
|
+
--cn-status-purple-text: #4a3f8f;
|
|
10
|
+
--cn-status-teal-bg: #e3f1f3;
|
|
11
|
+
--cn-status-teal-text: #1d5e66;
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
.cn-status-badge {
|
|
6
15
|
display: inline-flex;
|
|
7
16
|
align-items: center;
|
|
@@ -51,6 +60,18 @@
|
|
|
51
60
|
color: var(--color-info-text, var(--color-info, #0082c9));
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
/* Work in progress ("In behandeling"), a board tone. */
|
|
64
|
+
.cn-status-badge--purple {
|
|
65
|
+
background-color: var(--cn-status-purple-bg);
|
|
66
|
+
color: var(--cn-status-purple-text);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Handed on ("Omgezet naar een zaak"), a board tone. */
|
|
70
|
+
.cn-status-badge--teal {
|
|
71
|
+
background-color: var(--cn-status-teal-bg);
|
|
72
|
+
color: var(--cn-status-teal-text);
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
/* Solid variants — solid background with white text for use on colored backgrounds */
|
|
55
76
|
.cn-status-badge--solid.cn-status-badge--default {
|
|
56
77
|
background-color: var(--color-background-dark);
|
|
@@ -81,3 +102,13 @@
|
|
|
81
102
|
background-color: var(--color-info, #0082c9);
|
|
82
103
|
color: white;
|
|
83
104
|
}
|
|
105
|
+
|
|
106
|
+
.cn-status-badge--solid.cn-status-badge--purple {
|
|
107
|
+
background-color: var(--cn-status-purple-text);
|
|
108
|
+
color: white;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.cn-status-badge--solid.cn-status-badge--teal {
|
|
112
|
+
background-color: var(--cn-status-teal-text);
|
|
113
|
+
color: white;
|
|
114
|
+
}
|
package/src/css/kpi-card.css
CHANGED
|
@@ -463,6 +463,57 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
463
463
|
gap: 6px;
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
+
/* A linked board tile is one link: the label is not underlined link text, in
|
|
467
|
+
any layout (screens-stat-tile-parity). The NL Design theme underlines every
|
|
468
|
+
`a` with `!important`, so the opt-out needs it too. */
|
|
469
|
+
a.cn-kpi-card--board,
|
|
470
|
+
a.cn-kpi-card--board:hover,
|
|
471
|
+
a.cn-kpi-card--board:focus-visible,
|
|
472
|
+
a.cn-kpi-card--board * {
|
|
473
|
+
text-decoration: none !important;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* `content.iconPlacement: "end"` (DqDashboard): label left, the icon in a
|
|
477
|
+
32px circle right, tinted by the tile's state. */
|
|
478
|
+
.cn-kpi-card--board .cn-kpi-card__title--badge-end {
|
|
479
|
+
justify-content: space-between;
|
|
480
|
+
gap: 8px;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.cn-kpi-card__badge {
|
|
484
|
+
display: inline-flex;
|
|
485
|
+
flex: 0 0 auto;
|
|
486
|
+
align-items: center;
|
|
487
|
+
justify-content: center;
|
|
488
|
+
width: 32px;
|
|
489
|
+
height: 32px;
|
|
490
|
+
margin-inline-start: auto;
|
|
491
|
+
border-radius: 50%;
|
|
492
|
+
color: var(--color-primary-element-light-text, var(--color-primary-element));
|
|
493
|
+
background: var(--color-primary-element-light, color-mix(in srgb, var(--color-primary-element) 12%, transparent));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.cn-kpi-card__badge svg {
|
|
497
|
+
width: 18px;
|
|
498
|
+
height: 18px;
|
|
499
|
+
fill: currentcolor;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.cn-kpi-card__badge--success {
|
|
503
|
+
color: var(--color-text-success, var(--color-success-text));
|
|
504
|
+
background: color-mix(in srgb, var(--color-text-success, var(--color-success-text)) 12%, var(--color-main-background));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.cn-kpi-card__badge--warning {
|
|
508
|
+
color: var(--color-element-warning, var(--color-warning-text));
|
|
509
|
+
background: color-mix(in srgb, var(--color-element-warning, var(--color-warning-text)) 12%, var(--color-main-background));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.cn-kpi-card__badge--error {
|
|
513
|
+
color: var(--color-text-error, var(--color-error-text));
|
|
514
|
+
background: color-mix(in srgb, var(--color-text-error, var(--color-error-text)) 10%, var(--color-main-background));
|
|
515
|
+
}
|
|
516
|
+
|
|
466
517
|
.cn-kpi-card__glyph {
|
|
467
518
|
display: inline-flex;
|
|
468
519
|
flex: 0 0 auto;
|
|
@@ -114,12 +114,26 @@
|
|
|
114
114
|
|
|
115
115
|
/* ---- Folder tabs ------------------------------------------------------- */
|
|
116
116
|
|
|
117
|
+
/* The strip draws no line of its own (design-system pull request 147). The card under it
|
|
118
|
+
carries its full border, top included, so the top-right corner is the
|
|
119
|
+
card's own rounded corner instead of a strip rule running straight past it.
|
|
120
|
+
The open tab overlaps that top border by 1px (the nav's -1px margin) and
|
|
121
|
+
paints over it, so it flows into the card as a folder tab. */
|
|
117
122
|
.cn-look-board .cn-tabs--board .cn-tabs__bar {
|
|
118
|
-
border-bottom:
|
|
123
|
+
border-bottom: none;
|
|
119
124
|
gap: 4px;
|
|
125
|
+
position: relative;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.cn-look-board .cn-tabs.cn-tabs--board .cn-tabs__bar::after {
|
|
129
|
+
content: none;
|
|
120
130
|
}
|
|
121
131
|
|
|
132
|
+
/* Tabs sit on the card's top edge; only the open tab reaches 1px further
|
|
133
|
+
down (min-height 35px below), over the card's top border. Top-aligned so
|
|
134
|
+
the inactive tabs do not stretch to the open tab's height. */
|
|
122
135
|
.cn-look-board .cn-tabs--board .cn-tabs__nav {
|
|
136
|
+
align-items: flex-start;
|
|
123
137
|
gap: 4px;
|
|
124
138
|
}
|
|
125
139
|
|
|
@@ -140,7 +154,11 @@
|
|
|
140
154
|
border-bottom: 1px solid var(--color-main-background);
|
|
141
155
|
border-top: 1px solid var(--color-border-dark);
|
|
142
156
|
font-weight: 700;
|
|
157
|
+
min-height: 35px;
|
|
158
|
+
padding-bottom: 9px;
|
|
143
159
|
padding-top: 8px;
|
|
160
|
+
position: relative;
|
|
161
|
+
z-index: 1;
|
|
144
162
|
}
|
|
145
163
|
|
|
146
164
|
/* The count: a grey, borderless 20px badge 8px after the label. */
|
|
@@ -177,10 +195,30 @@
|
|
|
177
195
|
.cn-look-board .cn-tabs--board .cn-tabs__content > .cn-tab > :first-child .cn-widget-wrapper,
|
|
178
196
|
.cn-look-board .cn-tabs--board .cn-tabs__content > .cn-tab > .cn-widget-wrapper,
|
|
179
197
|
.cn-look-board .cn-tabs--board .cn-tabs__content > .cn-tab > .cn-detail-card {
|
|
180
|
-
border-top:
|
|
198
|
+
border-top: 1px solid var(--color-border);
|
|
181
199
|
border-radius: 0 12px 12px 12px;
|
|
182
200
|
}
|
|
183
201
|
|
|
202
|
+
/* A CnTabsWidget panel is the card itself: the same full border, so it joins
|
|
203
|
+
the open tab the same way. The top-left stays square under the first tab. */
|
|
204
|
+
.cn-look-board .cn-tabs--board.cn-tabs-widget__tabs .cn-tabs__content {
|
|
205
|
+
border: 1px solid var(--color-border);
|
|
206
|
+
border-radius: 0 12px 12px 12px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* A panel of CnTabsWidget is the content block itself: the open tab flows
|
|
210
|
+
straight into it. The widget in the panel renders chromeless, but the board
|
|
211
|
+
card rule below (and the first-card rule above) used to give its wrapper a
|
|
212
|
+
border and a 12px radius again, so a second card sat inside the panel. Its
|
|
213
|
+
left edge ran down from the open tab and its rounded top-right drew a line
|
|
214
|
+
across the top right of the content block, where DqZaak shows one sheet.
|
|
215
|
+
Same specificity as the first-card rule and later, so it wins on order. */
|
|
216
|
+
.cn-look-board .cn-tabs--board.cn-tabs-widget__tabs .cn-tabs__content .cn-widget-wrapper.cn-widget-wrapper--chromeless {
|
|
217
|
+
background: transparent;
|
|
218
|
+
border: 0;
|
|
219
|
+
border-radius: 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
184
222
|
/* ---- Body cards and side cards ----------------------------------------- */
|
|
185
223
|
|
|
186
224
|
.cn-look-board .cn-detail-page .cn-widget-wrapper,
|
|
@@ -45,6 +45,42 @@
|
|
|
45
45
|
margin-inline-start: 0;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/* The header buttons keep the board's button under a theme that styles every
|
|
49
|
+
secondary or primary button with `!important` (the nldesign theme: a
|
|
50
|
+
primary border, 8px 16px padding): Download and the Actions menu are 40px,
|
|
51
|
+
a grey border on the main background (PqTickets, DqZaken), the primary
|
|
52
|
+
button is the primary fill without a border. The buildiq square is not a
|
|
53
|
+
direct child button and keeps its own rule. */
|
|
54
|
+
.cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--secondary,
|
|
55
|
+
.cn-look-board .cn-index-page .cn-index-page__header-buttons > .action-item .button-vue {
|
|
56
|
+
height: var(--cn-board-control-height) !important;
|
|
57
|
+
min-height: var(--cn-board-control-height) !important;
|
|
58
|
+
padding: 0 14px !important;
|
|
59
|
+
border: 1px solid var(--color-border-dark) !important;
|
|
60
|
+
border-radius: var(--cn-board-control-radius) !important;
|
|
61
|
+
background: var(--color-main-background) !important;
|
|
62
|
+
color: var(--color-main-text) !important;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--secondary:hover,
|
|
66
|
+
.cn-look-board .cn-index-page .cn-index-page__header-buttons > .action-item .button-vue:hover {
|
|
67
|
+
background: var(--color-background-hover) !important;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--primary {
|
|
71
|
+
height: var(--cn-board-control-height) !important;
|
|
72
|
+
min-height: var(--cn-board-control-height) !important;
|
|
73
|
+
padding: 0 16px !important;
|
|
74
|
+
border: 0 !important;
|
|
75
|
+
border-radius: var(--cn-board-control-radius) !important;
|
|
76
|
+
background: var(--color-primary-element) !important;
|
|
77
|
+
color: var(--color-primary-element-text) !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--primary:hover {
|
|
81
|
+
background: var(--color-primary-element-hover) !important;
|
|
82
|
+
}
|
|
83
|
+
|
|
48
84
|
/* ----------------------------------------------------------------- toolbar */
|
|
49
85
|
|
|
50
86
|
/* No band, no padding, no radius. The DOM is already in reading order (row 1,
|
package/src/css/look-board.css
CHANGED
|
@@ -435,3 +435,11 @@
|
|
|
435
435
|
color: var(--color-main-text);
|
|
436
436
|
opacity: 1;
|
|
437
437
|
}
|
|
438
|
+
|
|
439
|
+
/* ---- Status pills (screens-cell-pill-parity) ---------------------------- */
|
|
440
|
+
/* The board draws a primary pill in the deep primary ink on the light primary
|
|
441
|
+
ground ("Verzoek", "Nieuw"); the default look keeps the brighter
|
|
442
|
+
primary-element text. The other tones already match the board. */
|
|
443
|
+
.cn-look-board .cn-status-badge--primary:not(.cn-status-badge--solid):not(.cn-look-nextcloud *) {
|
|
444
|
+
color: var(--color-primary-element-light-text);
|
|
445
|
+
}
|
package/src/css/table.css
CHANGED
|
@@ -284,9 +284,64 @@
|
|
|
284
284
|
background-color: var(--color-background-hover);
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
287
|
+
/* The sort control: a real button that looks like the header text, the label
|
|
288
|
+
followed by a 14px chevron 4px after it (the "Wettelijke termijn" header on
|
|
289
|
+
the DqWooVerzoeken board). Selector scoped under the table so it outranks
|
|
290
|
+
Nextcloud's own `button` rule, which would otherwise give it a border, a
|
|
291
|
+
fill, a 34px min-height and a margin. */
|
|
292
|
+
.cn-data-table th .cn-table-header__sort {
|
|
293
|
+
display: inline-flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
gap: 4px;
|
|
296
|
+
max-width: 100%;
|
|
297
|
+
min-width: 0;
|
|
298
|
+
min-height: 0;
|
|
299
|
+
margin: 0;
|
|
300
|
+
padding: 0;
|
|
301
|
+
border: 0;
|
|
302
|
+
border-radius: var(--border-radius, 4px);
|
|
303
|
+
background: transparent;
|
|
304
|
+
color: inherit;
|
|
305
|
+
font: inherit;
|
|
306
|
+
font-weight: inherit;
|
|
307
|
+
text-align: start;
|
|
308
|
+
vertical-align: middle;
|
|
309
|
+
cursor: pointer;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.cn-data-table th .cn-table-header__sort:hover,
|
|
313
|
+
.cn-data-table th .cn-table-header__sort:focus {
|
|
314
|
+
background: transparent;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.cn-data-table th .cn-table-header__sort:focus-visible {
|
|
318
|
+
outline: 2px solid var(--color-main-text);
|
|
319
|
+
outline-offset: 2px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.cn-data-table th .cn-table-header__sort > :first-child {
|
|
323
|
+
min-width: 0;
|
|
324
|
+
overflow: hidden;
|
|
325
|
+
text-overflow: ellipsis;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* The chevron. Unsorted columns show a pale down chevron, so every sortable
|
|
329
|
+
column says it sorts; the sorted column shows the direction at full text
|
|
330
|
+
colour. The direction is also in the header's `aria-sort`. */
|
|
331
|
+
.cn-table-header__chevron {
|
|
332
|
+
display: inline-flex;
|
|
333
|
+
flex: none;
|
|
334
|
+
color: var(--color-main-text);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.cn-table-header__chevron--idle {
|
|
338
|
+
color: var(--color-text-maxcontrast);
|
|
339
|
+
opacity: 0.5;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.cn-data-table th.cn-table-header--sortable:hover .cn-table-header__chevron--idle,
|
|
343
|
+
.cn-data-table th .cn-table-header__sort:focus-visible .cn-table-header__chevron--idle {
|
|
344
|
+
opacity: 1;
|
|
290
345
|
}
|
|
291
346
|
|
|
292
347
|
/* A column carrying a `description` gets a dotted underline — the long-standing
|
|
@@ -302,15 +357,12 @@
|
|
|
302
357
|
cursor: help;
|
|
303
358
|
}
|
|
304
359
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
16px icon; the filled icon plus the primary colour marks an active filter,
|
|
312
|
-
so the state never rests on colour alone. */
|
|
313
|
-
.cn-table-header__filter {
|
|
360
|
+
/* Header filter button (CnDataTable `filterable`): a 14px funnel directly
|
|
361
|
+
after the sort chevron, in a 24px hit area. Muted with no filter set; with
|
|
362
|
+
a filter set it takes the dark primary and a dot, so the state never rests
|
|
363
|
+
on colour alone. */
|
|
364
|
+
.cn-data-table th .cn-table-header__filter {
|
|
365
|
+
position: relative;
|
|
314
366
|
display: inline-flex;
|
|
315
367
|
align-items: center;
|
|
316
368
|
justify-content: center;
|
|
@@ -318,7 +370,7 @@
|
|
|
318
370
|
min-width: 24px;
|
|
319
371
|
height: 24px;
|
|
320
372
|
min-height: 24px;
|
|
321
|
-
margin: 0 0 0
|
|
373
|
+
margin: 0 0 0 2px;
|
|
322
374
|
padding: 0;
|
|
323
375
|
border: none;
|
|
324
376
|
border-radius: var(--border-radius, 4px);
|
|
@@ -328,18 +380,31 @@
|
|
|
328
380
|
cursor: pointer;
|
|
329
381
|
}
|
|
330
382
|
|
|
331
|
-
.cn-table-header__filter:hover {
|
|
383
|
+
.cn-data-table th .cn-table-header__filter:hover {
|
|
332
384
|
background-color: var(--color-background-hover);
|
|
333
385
|
color: var(--color-main-text);
|
|
334
386
|
}
|
|
335
387
|
|
|
336
|
-
.cn-table-header__filter:focus-visible {
|
|
388
|
+
.cn-data-table th .cn-table-header__filter:focus-visible {
|
|
337
389
|
outline: 2px solid var(--color-main-text);
|
|
338
390
|
outline-offset: 0;
|
|
339
391
|
}
|
|
340
392
|
|
|
341
|
-
.cn-table-header__filter--active
|
|
342
|
-
|
|
393
|
+
.cn-data-table th .cn-table-header__filter--active,
|
|
394
|
+
.cn-data-table th .cn-table-header__filter--active:hover {
|
|
395
|
+
color: var(--color-primary-element-light-text, var(--color-primary-element));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* The active-filter dot, on the funnel's top right. */
|
|
399
|
+
.cn-table-header__filter-dot {
|
|
400
|
+
position: absolute;
|
|
401
|
+
inset-block-start: 3px;
|
|
402
|
+
inset-inline-end: 3px;
|
|
403
|
+
width: 6px;
|
|
404
|
+
height: 6px;
|
|
405
|
+
border-radius: 50%;
|
|
406
|
+
background-color: var(--color-primary-element);
|
|
407
|
+
pointer-events: none;
|
|
343
408
|
}
|
|
344
409
|
|
|
345
410
|
/* Active header filters above the table, one removable chip each. */
|
package/src/index.js
CHANGED
|
@@ -543,6 +543,7 @@ export { cnFetch, cnFetchJson, CnHttpError, configureCnFetch } from './utils/cnF
|
|
|
543
543
|
// need the same answer without rendering a padlock.
|
|
544
544
|
export { isLockedByCurrentUser, isObjectLocked, lockHolder, readLockPayload, resolveObjectLock } from './utils/objectLock.js'
|
|
545
545
|
export { dispatchObjectsChanged } from './utils/objectSignals.js'
|
|
546
|
+
export { lensUnavailableText, readLensReports } from './utils/lensAvailability.js'
|
|
546
547
|
export { columnsFromSchema, fieldsFromSchema, filtersFromSchema, findRepeatingWrite, formatValue, journeyItemTargets, validateValue } from './utils/index.js'
|
|
547
548
|
export { DYNAMIC_KEY_PREFIX, EXTENDS_FORM_KEY, PREFILL_KEY, splitDynamicFormData, usesArrayValues, valueArrayFor, valueRecordsFor } from './utils/index.js'
|
|
548
549
|
// The OpenRegister schema API contract — shared so Buildiq and OpenRegister cannot
|
|
@@ -569,7 +570,7 @@ export {
|
|
|
569
570
|
SENTINEL_RESOLVERS,
|
|
570
571
|
warnIfDeprecated,
|
|
571
572
|
} from './utils/resolveManifestTokens.js'
|
|
572
|
-
export { filterWidgetsByVisibility, getCurrentUserGroups, getCurrentUserId, isWidgetVisible, resetVisibilityCache } from './utils/index.js'
|
|
573
|
+
export { filterWidgetsByVisibility, getCurrentUserGroups, getCurrentUserId, isWidgetVisible, peekCurrentUserGroups, resetVisibilityCache } from './utils/index.js'
|
|
573
574
|
export { safeHref, safeImageSrc, safeSvgPath } from './utils/index.js'
|
|
574
575
|
export { followLinkClick, isModifiedClick, isNewTabHandled, isRowMiddleClick, openRowTarget, resolveHref } from './utils/index.js'
|
|
575
576
|
export { resolveImageUrl } from './utils/index.js'
|
|
@@ -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.76.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -760,8 +760,8 @@
|
|
|
760
760
|
},
|
|
761
761
|
"sentinelFilterToken": {
|
|
762
762
|
"type": "string",
|
|
763
|
-
"pattern": "^@(?:me(?:\\.(?:displayName|email))?|now|today|monthStart|quarterStart|yearStart)$|^@today[+-][0-9]+d$",
|
|
764
|
-
"description": "Filter-value context: relative fetch-time tokens resolved by resolveFilterTokens (@me, @me.displayName, @me.email, @now, @today, @today±Nd, @monthStart, @quarterStart, @yearStart). Mirrors SENTINEL_TOKEN_PATTERNS.filter in src/utils/sentinelTokens.js (byte-equality enforced by a unit test)."
|
|
763
|
+
"pattern": "^@(?:me(?:\\.(?:displayName|email))?|myGroups|now|today|monthStart|quarterStart|yearStart)$|^@today[+-][0-9]+d$",
|
|
764
|
+
"description": "Filter-value context: relative fetch-time tokens resolved by resolveFilterTokens (@me, @me.displayName, @me.email, @myGroups (the current user's group ids, an IN list), @now, @today, @today±Nd, @monthStart, @quarterStart, @yearStart). Mirrors SENTINEL_TOKEN_PATTERNS.filter in src/utils/sentinelTokens.js (byte-equality enforced by a unit test)."
|
|
765
765
|
},
|
|
766
766
|
"sentinelConfigToken": {
|
|
767
767
|
"type": "string",
|
|
@@ -1094,7 +1094,7 @@
|
|
|
1094
1094
|
"pillColors": {
|
|
1095
1095
|
"type": "object",
|
|
1096
1096
|
"description": "Pill value to badge variant. A value without an entry takes the default variant.",
|
|
1097
|
-
"additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info"] }
|
|
1097
|
+
"additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }
|
|
1098
1098
|
},
|
|
1099
1099
|
"due": { "type": "string", "description": "The date field of the footer's due text, marked by `dueRule`." },
|
|
1100
1100
|
"owner": { "type": "string", "description": "The field of the owner shown as an avatar (initials) at the footer's end." }
|
|
@@ -1143,7 +1143,7 @@
|
|
|
1143
1143
|
"colorMap": {
|
|
1144
1144
|
"type": "object",
|
|
1145
1145
|
"description": "Raw value to badge variant. Keys are matched ignoring case.",
|
|
1146
|
-
"additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info"] }
|
|
1146
|
+
"additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }
|
|
1147
1147
|
},
|
|
1148
1148
|
"labels": {
|
|
1149
1149
|
"type": "object",
|
|
@@ -1152,7 +1152,7 @@
|
|
|
1152
1152
|
},
|
|
1153
1153
|
"variant": {
|
|
1154
1154
|
"type": "string",
|
|
1155
|
-
"enum": ["default", "primary", "success", "warning", "error", "info"],
|
|
1155
|
+
"enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"],
|
|
1156
1156
|
"default": "default",
|
|
1157
1157
|
"description": "Badge variant for a value `colorMap` does not name."
|
|
1158
1158
|
}
|
|
@@ -3304,19 +3304,23 @@
|
|
|
3304
3304
|
"properties": {
|
|
3305
3305
|
"favourite": {
|
|
3306
3306
|
"type": "boolean",
|
|
3307
|
-
"description": "
|
|
3307
|
+
"description": "Deprecated and ignored: there is no star any more, a favourite is a follow with notifications off. Kept so existing manifests stay valid; use follow."
|
|
3308
3308
|
},
|
|
3309
3309
|
"follow": {
|
|
3310
3310
|
"type": "boolean",
|
|
3311
|
-
"description": "Detail pages: the Follow
|
|
3311
|
+
"description": "Detail pages: the one Follow control beside the title, with a notifications on/off switch while the user follows and the follower count for a user who may update the record. Omitted means automatic: it shows when the object carries @self.watching. false removes it, and the object read then no longer asks for @self.can."
|
|
3312
|
+
},
|
|
3313
|
+
"showFollowColumn": {
|
|
3314
|
+
"type": "boolean",
|
|
3315
|
+
"description": "Index pages: add a first column with a follow toggle per row, bound to each row's @self.watching. Clicking it does not open the row. Default false."
|
|
3312
3316
|
},
|
|
3313
3317
|
"showFavouriteColumn": {
|
|
3314
3318
|
"type": "boolean",
|
|
3315
|
-
"description": "
|
|
3319
|
+
"description": "Deprecated alias of showFollowColumn: the star column became the follow column. Default false."
|
|
3316
3320
|
},
|
|
3317
3321
|
"personalLenses": {
|
|
3318
3322
|
"type": "array",
|
|
3319
|
-
"description": "Index pages: personal lenses added to the quick filters, in order.
|
|
3323
|
+
"description": "Index pages: personal lenses added to the quick filters, in order. watching lists the records the user follows (labelled Following), recent the records the user opened last (it owns the order, so column sorting is off), unread the records changed since the user last looked. They combine with every other filter. favourite is a deprecated alias of watching; asking for both gives one tab. Default none.",
|
|
3320
3324
|
"items": { "enum": ["favourite", "recent", "watching", "unread"] },
|
|
3321
3325
|
"uniqueItems": true
|
|
3322
3326
|
},
|
|
@@ -3582,7 +3586,7 @@
|
|
|
3582
3586
|
},
|
|
3583
3587
|
"columns": {
|
|
3584
3588
|
"type": "array",
|
|
3585
|
-
"description": "Table columns (for type='index' and type='logs'). Items may be strings (legacy shorthand) or column objects ({ key, label, sortable?, width?, formatter?, formatterOptions?, widget?, widgetProps?, format?, aggregate? }). `formatter` resolves against cnFormatters (built-ins incl. currency / conditionalPhrase — configured via `formatterOptions`); `widget` resolves against cnCellWidgets (built-ins incl. badge / fkResolve / link / avatar / date).",
|
|
3589
|
+
"description": "Table columns (for type='index' and type='logs'). Items may be strings (legacy shorthand) or column objects ({ key, label, sortable?, width?, formatter?, formatterOptions?, widget?, widgetProps?, format?, aggregate?, colorMap? }). `colorMap` maps an enum column's raw values to a pill tone (default, primary, success, warning, error, info, purple, teal) and wins over the schema property's own `colorMap` / `x-color-map`. `formatter` resolves against cnFormatters (built-ins incl. currency / conditionalPhrase — configured via `formatterOptions`); `widget` resolves against cnCellWidgets (built-ins incl. badge / fkResolve / link / avatar / date).",
|
|
3586
3590
|
"items": {
|
|
3587
3591
|
"oneOf": [
|
|
3588
3592
|
{ "type": "string" },
|