@conduction/nextcloud-vue 2.74.0 → 2.76.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/CnAppNav/CnAppNav.vue2.js +19 -14
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +2 -2
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +77 -26
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +22 -14
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +23 -4
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js +9 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js.map +1 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js +107 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js +79 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js.map +1 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +6 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -2
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCredentials/CnCredentials.vue2.js +11 -10
- package/dist/esm/components/CnCredentials/CnCredentials.vue2.js.map +1 -1
- package/dist/esm/components/CnCredentials/CnCredentials.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +8 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js +4 -1
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js.map +1 -1
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +14 -7
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +32 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +6 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +144 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +14 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +90 -14
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +15 -3
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +26 -0
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +26 -9
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +19 -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/CnQuickFilterBar/CnQuickFilterBar.vue2.js +18 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +2 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +153 -38
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +74 -56
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +16 -0
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +2 -2
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/composables/useObjectLock.js +41 -10
- package/dist/esm/composables/useObjectLock.js.map +1 -1
- package/dist/esm/directives/cnSelectAria.js +55 -0
- package/dist/esm/directives/cnSelectAria.js.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +7 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +8 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +154 -2
- package/dist/esm/store/useObjectStore.js +37 -9
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/cnFetch.js +17 -2
- package/dist/esm/utils/cnFetch.js.map +1 -1
- package/dist/esm/utils/objectSignals.js +44 -0
- package/dist/esm/utils/objectSignals.js.map +1 -0
- package/dist/esm/utils/resolveBrand.js +38 -0
- package/dist/esm/utils/resolveBrand.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +9640 -8649
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +154 -2
- package/l10n/en.json +7 -1
- package/l10n/nl.json +8 -2
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +21 -16
- package/src/components/CnAppRoot/CnAppRoot.vue +53 -0
- package/src/components/CnBannerWidget/CnBannerWidget.vue +23 -4
- package/src/components/CnBrandBar/CnBrandBar.vue +230 -0
- package/src/components/CnCardGrid/CnCardGrid.md +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +7 -0
- package/src/components/CnCredentials/CnCredentials.vue +17 -16
- package/src/components/CnDetailPage/CnDetailPage.vue +8 -5
- package/src/components/CnEnvironmentBanner/CnEnvironmentBanner.vue +4 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +10 -0
- package/src/components/CnFormPage/CnFormPage.vue +33 -0
- package/src/components/CnIndexPage/CnIndexPage.md +15 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +157 -18
- package/src/components/CnIndexPage/selfModeActions.js +89 -13
- package/src/components/CnIndexPage/useSelfFetchList.js +15 -3
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +89 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +19 -3
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +20 -4
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +181 -38
- package/src/components/CnStatsBlock/CnStatsBlock.vue +18 -2
- package/src/composables/__tests__/useObjectLock.endpoints.spec.js +83 -0
- package/src/composables/useObjectLock.js +41 -10
- package/src/css/form-field.css +1 -1
- package/src/css/look-board-index.css +35 -1
- package/src/directives/cnSelectAria.js +54 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +6 -1
- package/src/store/useObjectStore.js +37 -9
- package/src/utils/cnFetch.js +16 -1
- package/src/utils/index.js +1 -0
- package/src/utils/objectSignals.js +41 -0
- package/src/utils/resolveBrand.js +37 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -343,7 +343,10 @@
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
.cn-look-board .cn-index-page__main.cn-index-page__main--table {
|
|
346
|
-
|
|
346
|
+
/* The body is a row: the card takes the width left beside a sidebar (the
|
|
347
|
+
board's table card spans the content), and keeps the height of its rows. */
|
|
348
|
+
flex: 1 1 auto;
|
|
349
|
+
align-self: flex-start;
|
|
347
350
|
border: 1px solid var(--color-border);
|
|
348
351
|
border-radius: var(--cn-board-card-radius, 12px);
|
|
349
352
|
background: var(--color-main-background);
|
|
@@ -533,6 +536,37 @@
|
|
|
533
536
|
background: var(--color-primary-element-light);
|
|
534
537
|
}
|
|
535
538
|
|
|
539
|
+
/* The status accent outranks the board's border, also on hover and selection. */
|
|
540
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent {
|
|
541
|
+
border-inline-start-width: 4px;
|
|
542
|
+
padding-inline-start: 13px;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-success,
|
|
546
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-success:hover {
|
|
547
|
+
border-inline-start-color: var(--color-element-success);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-warning,
|
|
551
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-warning:hover {
|
|
552
|
+
border-inline-start-color: var(--color-element-warning);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-error,
|
|
556
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-error:hover {
|
|
557
|
+
border-inline-start-color: var(--color-element-error);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-info,
|
|
561
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-info:hover {
|
|
562
|
+
border-inline-start-color: var(--color-element-info);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-primary,
|
|
566
|
+
.cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-primary:hover {
|
|
567
|
+
border-inline-start-color: var(--color-primary-element);
|
|
568
|
+
}
|
|
569
|
+
|
|
536
570
|
.cn-look-board .cn-object-card--board .cn-object-card__content {
|
|
537
571
|
display: flex;
|
|
538
572
|
flex: 1 1 auto;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* `v-cn-select-aria`: puts `aria-invalid`, `aria-describedby` and
|
|
6
|
+
* `aria-required` on the search input of an NcSelect.
|
|
7
|
+
*
|
|
8
|
+
* NcSelect forwards no attributes to its input: an attribute bound on it lands
|
|
9
|
+
* on the wrapping `.v-select` div, where a screen reader never reads it. The
|
|
10
|
+
* input is the combobox, so the attributes belong there. This directive sets
|
|
11
|
+
* them on that input after every render, and removes the ones that go away
|
|
12
|
+
* (a field that becomes valid loses `aria-invalid`).
|
|
13
|
+
*
|
|
14
|
+
* Usage: `<NcSelect v-cn-select-aria="{ 'aria-invalid': 'true', 'aria-describedby': 'err-id' }" />`
|
|
15
|
+
* A `null` or `undefined` value does nothing, so a caller can bind it
|
|
16
|
+
* unconditionally.
|
|
17
|
+
*
|
|
18
|
+
* @spec openspec/changes/screens-form-parity/tasks.md
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const ARIA_ATTRIBUTES = ['aria-invalid', 'aria-describedby', 'aria-required']
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply the bound attributes to the combobox input inside the element.
|
|
25
|
+
*
|
|
26
|
+
* @param {HTMLElement} el The element the directive sits on (the select root).
|
|
27
|
+
* @param {{value: object|null|undefined}} binding The directive binding.
|
|
28
|
+
* @return {void}
|
|
29
|
+
*/
|
|
30
|
+
function apply(el, binding) {
|
|
31
|
+
const attributes = binding.value
|
|
32
|
+
if (!attributes || typeof el.querySelector !== 'function') {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
const input = el.querySelector('input.vs__search') || el.querySelector('input')
|
|
36
|
+
if (!input) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
for (const name of ARIA_ATTRIBUTES) {
|
|
40
|
+
const value = attributes[name]
|
|
41
|
+
if (value === undefined || value === null || value === false) {
|
|
42
|
+
input.removeAttribute(name)
|
|
43
|
+
} else {
|
|
44
|
+
input.setAttribute(name, String(value))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const cnSelectAria = {
|
|
50
|
+
mounted: apply,
|
|
51
|
+
updated: apply,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default cnSelectAria
|
package/src/index.js
CHANGED
|
@@ -542,6 +542,7 @@ export { cnFetch, cnFetchJson, CnHttpError, configureCnFetch } from './utils/cnF
|
|
|
542
542
|
// CnLockIndicator, CnObjectCard and CnDataTable, and available to apps that
|
|
543
543
|
// need the same answer without rendering a padlock.
|
|
544
544
|
export { isLockedByCurrentUser, isObjectLocked, lockHolder, readLockPayload, resolveObjectLock } from './utils/objectLock.js'
|
|
545
|
+
export { dispatchObjectsChanged } from './utils/objectSignals.js'
|
|
545
546
|
export { columnsFromSchema, fieldsFromSchema, filtersFromSchema, findRepeatingWrite, formatValue, journeyItemTargets, validateValue } from './utils/index.js'
|
|
546
547
|
export { DYNAMIC_KEY_PREFIX, EXTENDS_FORM_KEY, PREFILL_KEY, splitDynamicFormData, usesArrayValues, valueArrayFor, valueRecordsFor } from './utils/index.js'
|
|
547
548
|
// The OpenRegister schema API contract — shared so Buildiq and OpenRegister cannot
|
|
@@ -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.73.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -326,6 +326,11 @@
|
|
|
326
326
|
"alt": {
|
|
327
327
|
"type": "string",
|
|
328
328
|
"description": "i18n key / alternative text for the logo. Used only when no `name` is set."
|
|
329
|
+
},
|
|
330
|
+
"placement": {
|
|
331
|
+
"type": "string",
|
|
332
|
+
"enum": ["header", "nav"],
|
|
333
|
+
"description": "Where the brand block is drawn (screens-brand-block-top-bar). Under `look: \"board\"` the default is `header`: a 237px block at the start of Nextcloud's own header (#header), before the app menu, drawn by CnAppRoot; an app without CnAppNav has it too. Nextcloud offers no extension point there, so when `#header` is absent the block falls back to the navigation. `nav` keeps the block at the top of the app navigation. Ignored without the board look, where the navigation draws the block."
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
336
|
},
|
|
@@ -240,9 +240,10 @@ const baseActions = {
|
|
|
240
240
|
* @param {object} [slugs] Optional slug hints for live-updates transport
|
|
241
241
|
* @param {string|null} [slugs.registerSlug] Canonical register slug (e.g. 'zaken')
|
|
242
242
|
* @param {string|null} [slugs.schemaSlug] Canonical schema slug (e.g. 'meldingen')
|
|
243
|
+
* @param {string} [slugs.collectionUrl] Endpoint `fetchCollection` lists from instead of `/register/schema`; every other call keeps the pair URL
|
|
243
244
|
*/
|
|
244
245
|
registerObjectType(slug, schemaId, registerId, slugs = {}) {
|
|
245
|
-
const { registerSlug = null, schemaSlug = null } = slugs
|
|
246
|
+
const { registerSlug = null, schemaSlug = null, collectionUrl = null } = slugs
|
|
246
247
|
// Replace entire objects so Vue 2 reactivity detects the change
|
|
247
248
|
// (Vue 2 cannot track new properties added to existing reactive objects)
|
|
248
249
|
this.objectTypeRegistry = {
|
|
@@ -252,6 +253,7 @@ const baseActions = {
|
|
|
252
253
|
register: registerId,
|
|
253
254
|
registerSlug,
|
|
254
255
|
schemaSlug,
|
|
256
|
+
...(collectionUrl ? { collectionUrl } : {}),
|
|
255
257
|
},
|
|
256
258
|
}
|
|
257
259
|
this.collections = { ...this.collections, [slug]: [] }
|
|
@@ -496,6 +498,31 @@ const baseActions = {
|
|
|
496
498
|
return url
|
|
497
499
|
},
|
|
498
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Build the list URL for a type registered with a `collectionUrl`. `_order`
|
|
503
|
+
* goes in the bracket form (`_order[key]=dir`) every PHP endpoint parses.
|
|
504
|
+
*
|
|
505
|
+
* @param {object} config The type config
|
|
506
|
+
* @param {object} [params] Query parameters
|
|
507
|
+
* @return {string} Full URL including query string
|
|
508
|
+
*/
|
|
509
|
+
_buildCollectionUrl(config, params = {}) {
|
|
510
|
+
const { _order: order, ...rest } = params
|
|
511
|
+
const merged = { ...rest }
|
|
512
|
+
if (order && typeof order === 'object') {
|
|
513
|
+
for (const [key, dir] of Object.entries(order)) {
|
|
514
|
+
merged[`_order[${key}]`] = dir
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const lang = this._resolveLanguage()
|
|
518
|
+
if (lang) {
|
|
519
|
+
merged._lang = lang
|
|
520
|
+
}
|
|
521
|
+
const url = prefixUrl(config.collectionUrl)
|
|
522
|
+
const query = buildQueryString(merged)
|
|
523
|
+
return query && url.includes('?') ? url + '&' + query.slice(1) : url + query
|
|
524
|
+
},
|
|
525
|
+
|
|
499
526
|
/**
|
|
500
527
|
* Fetch for a type, retrying ONCE with its `schemaFallback` when the
|
|
501
528
|
* schema path segment answers 404. A type without a fallback (every type
|
|
@@ -685,14 +712,15 @@ const baseActions = {
|
|
|
685
712
|
}
|
|
686
713
|
}
|
|
687
714
|
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
715
|
+
const config = this._getTypeConfig(type)
|
|
716
|
+
const init = { method: 'GET', headers: this._buildHeaders() }
|
|
717
|
+
const response = config.collectionUrl
|
|
718
|
+
? await trackedFetch(this._buildCollectionUrl(config, fetchParams), init)
|
|
719
|
+
: await this._fetchWithSchemaFallback(
|
|
720
|
+
type,
|
|
721
|
+
(schema) => this._buildUrlWithParams(type, fetchParams, null, schema),
|
|
722
|
+
init,
|
|
723
|
+
)
|
|
696
724
|
|
|
697
725
|
if (!response.ok) {
|
|
698
726
|
const failure = await parseResponseError(response, type)
|
package/src/utils/cnFetch.js
CHANGED
|
@@ -59,11 +59,26 @@ export function configureCnFetch(config = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
transport = {
|
|
61
61
|
host,
|
|
62
|
-
baseUrl: typeof config.baseUrl === 'string' ? config.baseUrl
|
|
62
|
+
baseUrl: typeof config.baseUrl === 'string' ? stripTrailingSlashes(config.baseUrl) : '',
|
|
63
63
|
credential: config.credential || null,
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Drop trailing `/` characters in one linear pass (a `/\/+$/` pattern
|
|
69
|
+
* backtracks quadratically on a long run of slashes).
|
|
70
|
+
*
|
|
71
|
+
* @param {string} value The string to trim.
|
|
72
|
+
* @return {string} The string without trailing slashes.
|
|
73
|
+
*/
|
|
74
|
+
export function stripTrailingSlashes(value) {
|
|
75
|
+
let end = value.length
|
|
76
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
77
|
+
end--
|
|
78
|
+
}
|
|
79
|
+
return value.slice(0, end)
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
/**
|
|
68
83
|
* The bearer credential, resolved.
|
|
69
84
|
*
|
package/src/utils/index.js
CHANGED
|
@@ -19,3 +19,4 @@ export { validateFieldValue } from './formValidation.js'
|
|
|
19
19
|
export { computeObjectDiff } from './computeObjectDiff.js'
|
|
20
20
|
export { foldAuditTrailEntries } from './auditTrailDiff.js'
|
|
21
21
|
export { isLockedByCurrentUser, isObjectLocked, lockHolder, readLockPayload, resolveObjectLock } from './objectLock.js'
|
|
22
|
+
export { dispatchObjectsChanged } from './objectSignals.js'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* Signal that objects changed outside the surface showing them, such as files
|
|
6
|
+
* uploaded or an object saved from an app's own modal. CnIndexPage listens and
|
|
7
|
+
* refreshes its list when the register and schema match.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/objectSignals
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const OBJECTS_CHANGED_EVENT = 'cn:objects-changed'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Announce that objects of a register and schema changed.
|
|
16
|
+
*
|
|
17
|
+
* Pass the same register and schema identifiers (slug or id) the listing page
|
|
18
|
+
* uses. Leaving one out matches every page on that part.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} [payload] What changed.
|
|
21
|
+
* @param {string|number} [payload.register] The register slug or id.
|
|
22
|
+
* @param {string|number} [payload.schema] The schema slug or id.
|
|
23
|
+
* @param {string} [payload.id] The changed object's id, when there is one.
|
|
24
|
+
* @return {boolean} True when the event was dispatched.
|
|
25
|
+
*/
|
|
26
|
+
export function dispatchObjectsChanged({ register, schema, id } = {}) {
|
|
27
|
+
if (typeof window === 'undefined') {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
const detail = {
|
|
31
|
+
register: register === undefined || register === null || register === '' ? null : String(register),
|
|
32
|
+
schema: schema === undefined || schema === null || schema === '' ? null : String(schema),
|
|
33
|
+
id: id ? String(id) : null,
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
window.dispatchEvent(new CustomEvent(OBJECTS_CHANGED_EVENT, { detail }))
|
|
37
|
+
return true
|
|
38
|
+
} catch {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* Reads a declared brand (`nav.brand`, or the `brand` prop of CnAppNav and
|
|
6
|
+
* CnAppRoot) into the one shape the brand block and the brand bar draw.
|
|
7
|
+
*
|
|
8
|
+
* @spec openspec/changes/screens-brand-block-top-bar/tasks.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a declared brand.
|
|
13
|
+
*
|
|
14
|
+
* @param {object|null|undefined} declared The declared brand.
|
|
15
|
+
* @param {(text: string) => string} translate Label lookup for name, caption and alt.
|
|
16
|
+
* @return {{logo: string, emblem: (string|boolean), name: string, caption: string, alt: string, placement: string}|null}
|
|
17
|
+
* The brand, or null when nothing is declared that can be drawn.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveBrand(declared, translate) {
|
|
20
|
+
if (!declared || typeof declared !== 'object') {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
const text = (value) => (typeof value === 'string' && value !== '' ? translate(value) : '')
|
|
24
|
+
const brand = {
|
|
25
|
+
logo: typeof declared.logo === 'string' ? declared.logo : '',
|
|
26
|
+
// A URL, or `true` for the theme's emblem (--nldesign-emblem-url).
|
|
27
|
+
emblem: declared.emblem === true ? true : (typeof declared.emblem === 'string' ? declared.emblem : ''),
|
|
28
|
+
name: text(declared.name),
|
|
29
|
+
caption: text(declared.caption),
|
|
30
|
+
alt: text(declared.alt),
|
|
31
|
+
// '' means "not declared": the caller decides the default from the look.
|
|
32
|
+
placement: declared.placement === 'nav' || declared.placement === 'header' ? declared.placement : '',
|
|
33
|
+
}
|
|
34
|
+
return (brand.logo || brand.emblem || brand.name || brand.caption) ? brand : null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default resolveBrand
|