@conduction/nextcloud-vue 2.77.0 → 2.79.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/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- 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/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.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 +117 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +122 -97
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- 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 +168 -5
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +84 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
- 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/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
- 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/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +8 -2
- 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 +31 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.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/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.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/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.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/useObjectPresence.js +15 -4
- package/dist/esm/composables/useObjectPresence.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +68 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +113 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +526 -16
- package/dist/esm/utils/badgeVariants.js +32 -0
- package/dist/esm/utils/badgeVariants.js.map +1 -0
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2569 -811
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +526 -16
- package/l10n/en.json +68 -1
- package/l10n/nl.json +113 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
- package/src/components/CnAiCompanion/CnAiCompanion.vue +23 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnBoardView/CnBoardView.vue +2 -1
- package/src/components/CnCellRenderer/CnCellRenderer.md +1 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +169 -10
- package/src/components/CnChartWidget/CnChartWidget.vue +39 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +248 -8
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +88 -4
- package/src/components/CnDateRangePicker/CnDateRangePicker.vue +44 -1
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +8 -2
- package/src/components/CnFormPage/CnFormPage.vue +4 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +33 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnStatWidget/CnStatWidget.vue +52 -2
- package/src/components/CnStatusBadge/CnStatusBadge.vue +6 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/index.js +1 -1
- package/src/composables/__tests__/useObjectPresence.spec.js +24 -0
- package/src/composables/useAppManifest.js +8 -3
- package/src/composables/useFormDraft.js +11 -1
- 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 +10 -0
- package/src/css/look-board-index.css +128 -14
- package/src/css/look-board.css +85 -0
- package/src/css/table.css +37 -0
- package/src/index.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +50 -5
- package/src/utils/badgeVariants.js +29 -0
- package/src/utils/boardDate.js +189 -0
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -254,6 +254,11 @@ export default {
|
|
|
254
254
|
cnLook: { default: 'nextcloud' },
|
|
255
255
|
/** The tab list's name under the board look, provided by CnDetailPage (`config.tabsLabel`). */
|
|
256
256
|
cnDetailTabsLabel: { default: null },
|
|
257
|
+
/**
|
|
258
|
+
* The host's translate (CnAppRoot provides `cnTranslate`), so a tab
|
|
259
|
+
* label written in the manifest reads in the user's language.
|
|
260
|
+
*/
|
|
261
|
+
cnTranslate: { default: () => (key) => key },
|
|
257
262
|
},
|
|
258
263
|
|
|
259
264
|
props: {
|
|
@@ -471,7 +476,7 @@ export default {
|
|
|
471
476
|
key: `${widgetId || 'tab'}-${index}`,
|
|
472
477
|
id: (tab && typeof tab === 'object' && tab.id) || widgetId || '',
|
|
473
478
|
widgetId,
|
|
474
|
-
label:
|
|
479
|
+
label: this.tabLabel(tab, widget, widgetId),
|
|
475
480
|
icon: (tab && tab.icon) || widget?.icon || '',
|
|
476
481
|
widget,
|
|
477
482
|
count,
|
|
@@ -652,6 +657,30 @@ export default {
|
|
|
652
657
|
},
|
|
653
658
|
|
|
654
659
|
methods: {
|
|
660
|
+
/**
|
|
661
|
+
* The label a tab shows: the authored `label`, else (board look) History
|
|
662
|
+
* for the activity widget, else the child widget's title, else its id.
|
|
663
|
+
* A manifest label or title goes through the host's translate; the
|
|
664
|
+
* library's own History is translated by the library catalogue.
|
|
665
|
+
*
|
|
666
|
+
* @param {object|string} tab The `content.tabs[]` entry.
|
|
667
|
+
* @param {object|null} widget The widget definition it names.
|
|
668
|
+
* @param {string} widgetId The widget id.
|
|
669
|
+
* @return {string} The label.
|
|
670
|
+
* @spec openspec/changes/screens-detail-labels-parity/specs/detail-labels-board-look/spec.md#requirement-detail-labels-read-in-the-users-language
|
|
671
|
+
*/
|
|
672
|
+
tabLabel(tab, widget, widgetId) {
|
|
673
|
+
const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
|
|
674
|
+
if (tab && typeof tab.label === 'string' && tab.label !== '') {
|
|
675
|
+
return translate(tab.label)
|
|
676
|
+
}
|
|
677
|
+
if (this.isBoard && isActivityWidget(widget)) {
|
|
678
|
+
return t('nextcloud-vue', 'History')
|
|
679
|
+
}
|
|
680
|
+
const title = widgetTitleOf(widget)
|
|
681
|
+
return title ? translate(title) : (widgetId || '')
|
|
682
|
+
},
|
|
683
|
+
|
|
655
684
|
/**
|
|
656
685
|
* Whether a tab shows: `visible`, `hidden`, or `pending` while its
|
|
657
686
|
* condition cannot be decided yet (object not loaded, count not back).
|
|
@@ -323,6 +323,19 @@ export default {
|
|
|
323
323
|
default: false,
|
|
324
324
|
},
|
|
325
325
|
|
|
326
|
+
/**
|
|
327
|
+
* Fit the table to the tile: the title column (a column's `grow: true`,
|
|
328
|
+
* else `title` or `name`, else the first) shrinks and ends in an
|
|
329
|
+
* ellipsis so the trailing columns stay visible. On by default; pass
|
|
330
|
+
* `false` for the table's own auto layout. Forwarded to CnDataTable.
|
|
331
|
+
*
|
|
332
|
+
* @spec openspec/changes/narrow-object-table-widgets/specs/narrow-object-table-widgets/spec.md#requirement-a-narrow-object-table-keeps-its-trailing-columns-in-view
|
|
333
|
+
*/
|
|
334
|
+
fitWidth: {
|
|
335
|
+
type: Boolean,
|
|
336
|
+
default: true,
|
|
337
|
+
},
|
|
338
|
+
|
|
326
339
|
/**
|
|
327
340
|
* Route NAME for row-click navigation. Mapped onto CnDataTable's
|
|
328
341
|
* `rowClickRoute` as `{ name: rowRoute, params: { id } }` with the
|
package/src/components/index.js
CHANGED
|
@@ -109,7 +109,7 @@ export { CnTreeView } from './CnTreeView/index.js'
|
|
|
109
109
|
export { CnUserActionMenu } from './CnUserActionMenu/index.js'
|
|
110
110
|
export { CnNotesCard } from './CnNotesCard/index.js'
|
|
111
111
|
export { CnTasksCard } from './CnTasksCard/index.js'
|
|
112
|
-
export { CnFilesBrowser } from './CnFilesBrowser/index.js'
|
|
112
|
+
export { CnFilesBrowser, createOpenRegisterSource } from './CnFilesBrowser/index.js'
|
|
113
113
|
export { CnFilesCard } from './CnFilesCard/index.js'
|
|
114
114
|
export { CnFileManager } from './CnFileManager/index.js'
|
|
115
115
|
export { CnRelatedFiles } from './CnRelatedFiles/index.js'
|
|
@@ -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(() => {
|
|
@@ -205,6 +205,7 @@ function loadInMemory(input) {
|
|
|
205
205
|
* @param {object} options Configuration options (endpoint / fetcher /
|
|
206
206
|
* getAppConfigValue overrides).
|
|
207
207
|
* @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}
|
|
208
|
+
* @spec openspec/changes/manifest-validator-self-contained/specs/manifest-validator-self-contained/spec.md#requirement-a-backend-manifest-delta-reaches-the-navigation
|
|
208
209
|
*/
|
|
209
210
|
function loadFromBackend(appId, bundledManifest, options) {
|
|
210
211
|
// Shallow by default (manifest-shallow-reactivity-by-default) — see
|
|
@@ -284,9 +285,13 @@ function loadFromBackend(appId, bundledManifest, options) {
|
|
|
284
285
|
// The shallowRef reassignment is what re-renders consumers; the new
|
|
285
286
|
// object stays shallow (not deep-observed) unless CnAppRoot upgrades it.
|
|
286
287
|
manifest.value = resolved
|
|
287
|
-
} catch {
|
|
288
|
-
//
|
|
289
|
-
//
|
|
288
|
+
} catch (error) {
|
|
289
|
+
// Any unexpected error leaves the bundled manifest in place, so
|
|
290
|
+
// apps keep working. Say so: a silent catch here once hid a
|
|
291
|
+
// validator that threw on every manifest, and the backend delta
|
|
292
|
+
// never reached the navigation.
|
|
293
|
+
// eslint-disable-next-line no-console
|
|
294
|
+
console.warn('[useAppManifest] Could not resolve the manifest; keeping the bundled manifest.', error)
|
|
290
295
|
} finally {
|
|
291
296
|
isLoading.value = false
|
|
292
297
|
}
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -112,6 +112,16 @@
|
|
|
112
112
|
gap: 20px;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/* GridStack insets every cell by its 12px margin, the outer edge included, so
|
|
116
|
+
the tab strip started 12px below the top of the body column and stopped 12px
|
|
117
|
+
short of its end, while the side column started at the top. Pulling the grid
|
|
118
|
+
out by that margin puts its first card on the column's edges: the side column
|
|
119
|
+
is level with the strip, and the strip ends where the body column ends. */
|
|
120
|
+
.cn-look-board.cn-detail-page--with-side > .cn-detail-page__body > .cn-detail-page__grid {
|
|
121
|
+
margin: -12px;
|
|
122
|
+
width: auto;
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
/* ---- Folder tabs ------------------------------------------------------- */
|
|
116
126
|
|
|
117
127
|
/* The strip draws no line of its own (design-system pull request 147). The card under it
|
|
@@ -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,
|
|
@@ -71,16 +107,27 @@
|
|
|
71
107
|
margin-inline-start: 0;
|
|
72
108
|
}
|
|
73
109
|
|
|
74
|
-
/* The first of Save view
|
|
75
|
-
the
|
|
76
|
-
|
|
110
|
+
/* The first of Save view and the Filter + view switch unit takes the free
|
|
111
|
+
space (the board's `flex: 1` spacer), so they sit at the end of row 1.
|
|
112
|
+
When the line is full the unit wraps as one to the START of the next line
|
|
113
|
+
while Save view stays at the end of row 1 (DqZaken); without Save view the
|
|
114
|
+
unit sits at the end of row 1 (PqTickets, PqLeads, PtPortals). */
|
|
115
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > :is(.cn-saved-views__save, .cn-actions-bar__view-controls) {
|
|
77
116
|
margin-inline-start: auto;
|
|
78
117
|
}
|
|
79
118
|
|
|
80
|
-
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views >
|
|
119
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-saved-views__save ~ .cn-actions-bar__view-controls {
|
|
81
120
|
margin-inline-start: 0;
|
|
82
121
|
}
|
|
83
122
|
|
|
123
|
+
.cn-look-board .cn-actions-bar--board .cn-actions-bar__view-controls {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex: 0 0 auto;
|
|
126
|
+
flex-wrap: nowrap;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: 8px;
|
|
129
|
+
}
|
|
130
|
+
|
|
84
131
|
/* Row 2: the search field and the active filters on a line of their own. */
|
|
85
132
|
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--search {
|
|
86
133
|
display: flex;
|
|
@@ -166,16 +213,39 @@
|
|
|
166
213
|
color: var(--color-primary-element-text);
|
|
167
214
|
}
|
|
168
215
|
|
|
169
|
-
/* Save view: 34px, radius 17, on the light primary
|
|
216
|
+
/* Save view: a ghost button, 34px, radius 17, no border, on the light primary
|
|
217
|
+
tint, a 14px icon 6px before the label, on one line. */
|
|
170
218
|
.cn-look-board .cn-saved-views__save .button-vue {
|
|
171
219
|
min-height: 34px;
|
|
172
220
|
height: 34px;
|
|
173
|
-
padding: 0
|
|
221
|
+
padding: 0 12px;
|
|
222
|
+
border: 0;
|
|
174
223
|
border-radius: 17px;
|
|
175
224
|
background: var(--color-primary-element-light);
|
|
176
225
|
color: var(--color-primary-element-light-text);
|
|
177
226
|
font-size: 13px;
|
|
178
227
|
font-weight: 600;
|
|
228
|
+
white-space: nowrap;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.cn-look-board .cn-saved-views__save .button-vue:hover {
|
|
232
|
+
background: var(--color-primary-element-light-hover);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__wrapper {
|
|
236
|
+
gap: 6px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__icon {
|
|
240
|
+
width: auto;
|
|
241
|
+
min-width: 0;
|
|
242
|
+
height: auto;
|
|
243
|
+
min-height: 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__text {
|
|
247
|
+
margin: 0;
|
|
248
|
+
padding: 0;
|
|
179
249
|
}
|
|
180
250
|
|
|
181
251
|
/* Filter: a labelled secondary button, 36px, radius 8, with the active count. */
|
|
@@ -407,6 +477,29 @@
|
|
|
407
477
|
text-decoration: none;
|
|
408
478
|
}
|
|
409
479
|
|
|
480
|
+
/* Only the title is underlined (DqZaken). An underline set on the cell
|
|
481
|
+
reaches every in-flow child whatever its own text-decoration, so the
|
|
482
|
+
secondary lines become inline blocks on a line of their own: an atomic
|
|
483
|
+
inline box is the one thing the underline does not reach. */
|
|
484
|
+
.cn-look-board .cn-table-container--board .cn-data-table td.cn-table-col--title .cn-table-cell__secondary {
|
|
485
|
+
display: inline-block;
|
|
486
|
+
width: 100%;
|
|
487
|
+
vertical-align: top;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* `rowTitle: "plain"` (PqTickets, OcPublicaties): the title is bold text in
|
|
491
|
+
the main text colour without an underline, the secondary line 13px muted,
|
|
492
|
+
2px under it. The row stays clickable through its stretched row link. */
|
|
493
|
+
.cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title {
|
|
494
|
+
font-weight: 700;
|
|
495
|
+
text-decoration: none;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title .cn-table-cell__secondary {
|
|
499
|
+
margin-top: 2px;
|
|
500
|
+
font-size: 13px;
|
|
501
|
+
}
|
|
502
|
+
|
|
410
503
|
/* A status pill: 3px 10px, radius 12, 13px weight 600. The colours stay the
|
|
411
504
|
status colour map's. */
|
|
412
505
|
.cn-look-board .cn-table-container--board .cn-data-table .cn-status-badge {
|
|
@@ -429,15 +522,36 @@
|
|
|
429
522
|
--default-clickable-area: 34px;
|
|
430
523
|
}
|
|
431
524
|
|
|
525
|
+
/* The row menu: a 34px square, three dots, a grey border on the main
|
|
526
|
+
background (DqZaken, PqTickets). A theme that styles every secondary button
|
|
527
|
+
with `!important` (the nldesign theme: a primary border, 8px 16px padding,
|
|
528
|
+
`min-width: max-content`) would otherwise turn it into a 64px wide button
|
|
529
|
+
with a blue border, so the board values are `!important` too. */
|
|
530
|
+
.cn-look-board .cn-row-actions--board .button-vue,
|
|
531
|
+
.cn-look-board .cn-row-actions--board .button-vue:hover,
|
|
532
|
+
.cn-look-board .cn-row-actions--board .button-vue:focus {
|
|
533
|
+
box-sizing: border-box !important;
|
|
534
|
+
width: 34px !important;
|
|
535
|
+
min-width: 34px !important;
|
|
536
|
+
height: 34px !important;
|
|
537
|
+
min-height: 34px !important;
|
|
538
|
+
padding: 0 !important;
|
|
539
|
+
border: 1px solid var(--color-border-dark) !important;
|
|
540
|
+
border-radius: 8px !important;
|
|
541
|
+
color: var(--cn-board-text-soft) !important;
|
|
542
|
+
}
|
|
543
|
+
|
|
432
544
|
.cn-look-board .cn-row-actions--board .button-vue {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
545
|
+
background: var(--color-main-background) !important;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.cn-look-board .cn-row-actions--board .button-vue:hover {
|
|
549
|
+
background: var(--color-background-hover) !important;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.cn-look-board .cn-row-actions--board .button-vue:focus-visible {
|
|
553
|
+
outline: 2px solid var(--color-main-text);
|
|
554
|
+
outline-offset: 2px;
|
|
441
555
|
}
|
|
442
556
|
|
|
443
557
|
/* ------------------------------------------------------------------ footer */
|
package/src/css/look-board.css
CHANGED
|
@@ -146,6 +146,14 @@
|
|
|
146
146
|
margin-inline-start: -264px;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/* The navigation draws the line between it and the content. NcContent draws a
|
|
150
|
+
second one on the content while the navigation is open, which doubled the
|
|
151
|
+
line and put the page 1px further from the navigation than the board's 28px.
|
|
152
|
+
Outranks NcContent's rule (six classes) with seven. */
|
|
153
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"]:not(.app-navigation--closed):not(.app-navigation--close) ~ .app-content.app-content {
|
|
154
|
+
border-inline-start: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
149
157
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__content {
|
|
150
158
|
box-sizing: border-box;
|
|
151
159
|
gap: 20px;
|
|
@@ -155,6 +163,31 @@
|
|
|
155
163
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list {
|
|
156
164
|
height: auto;
|
|
157
165
|
flex: 1 1 auto;
|
|
166
|
+
margin: 0;
|
|
167
|
+
gap: 2px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* The board's vertical rhythm (AppZijbalk): the primary action 20px from the
|
|
171
|
+
top, the list 20px under it, entries 2px apart. An empty search slot is a
|
|
172
|
+
flex child too and pushed everything 20px down, so it goes. */
|
|
173
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__search:empty {
|
|
174
|
+
display: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* A caption is a 12px label with 6px under it, 20px after the group above
|
|
178
|
+
and 2px before its first entry. NcAppNavigationCaption gives its name a
|
|
179
|
+
15px font on a 34px line, which made it 44px high. */
|
|
180
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list > .app-navigation-caption:not(:first-child) {
|
|
181
|
+
margin-top: 18px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-caption .app-navigation-caption__name {
|
|
185
|
+
margin: 0;
|
|
186
|
+
padding: 0;
|
|
187
|
+
font-size: inherit;
|
|
188
|
+
font-weight: inherit;
|
|
189
|
+
line-height: normal;
|
|
190
|
+
color: inherit;
|
|
158
191
|
}
|
|
159
192
|
|
|
160
193
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry,
|
|
@@ -239,6 +272,43 @@
|
|
|
239
272
|
font-size: 15px;
|
|
240
273
|
}
|
|
241
274
|
|
|
275
|
+
/* The footer of the board: Help and Advanced as two 40px rows at weight 400,
|
|
276
|
+
2px apart. The Help row kept the 42px of a list entry, and the Advanced
|
|
277
|
+
foldout drew a 34px button inside 4px of padding, 20px under Help. */
|
|
278
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list {
|
|
279
|
+
gap: 2px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list .app-navigation-entry,
|
|
283
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list .app-navigation-entry-link {
|
|
284
|
+
min-height: 40px;
|
|
285
|
+
font-weight: 400;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] {
|
|
289
|
+
padding: 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list + [data-testid="cn-nav-settings"] {
|
|
293
|
+
margin-top: -18px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] > :first-child {
|
|
297
|
+
margin: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] > :first-child .button-vue {
|
|
301
|
+
box-sizing: border-box;
|
|
302
|
+
width: 100%;
|
|
303
|
+
height: 40px;
|
|
304
|
+
min-height: 40px;
|
|
305
|
+
padding: 0 10px;
|
|
306
|
+
border-radius: 8px;
|
|
307
|
+
font-size: 15px;
|
|
308
|
+
font-weight: 400;
|
|
309
|
+
justify-content: flex-start;
|
|
310
|
+
}
|
|
311
|
+
|
|
242
312
|
/* The card above the footer, pushed down. */
|
|
243
313
|
.cn-look-board .cn-app-nav__card {
|
|
244
314
|
order: 0;
|
|
@@ -435,3 +505,18 @@
|
|
|
435
505
|
color: var(--color-main-text);
|
|
436
506
|
opacity: 1;
|
|
437
507
|
}
|
|
508
|
+
|
|
509
|
+
/* ---- Status pills (screens-cell-pill-parity) ---------------------------- */
|
|
510
|
+
/* The board draws a primary pill in the deep primary ink on the light primary
|
|
511
|
+
ground ("Verzoek", "Nieuw"); the default look keeps the brighter
|
|
512
|
+
primary-element text. The other tones already match the board. */
|
|
513
|
+
.cn-look-board .cn-status-badge--primary:not(.cn-status-badge--solid):not(.cn-look-nextcloud *) {
|
|
514
|
+
color: var(--color-primary-element-light-text);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* ---- Date and age cells (screens-cell-date-parity) ---------------------- */
|
|
518
|
+
/* The board sets every waiting time ("3 dagen", "4 uur") in weight 600; a
|
|
519
|
+
late one is red through its variant class. */
|
|
520
|
+
.cn-look-board .cn-cell-renderer__age:not(.cn-look-nextcloud *) {
|
|
521
|
+
font-weight: 600;
|
|
522
|
+
}
|