@conduction/nextcloud-vue 2.56.0-beta.12 → 2.56.0-beta.13
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 +99 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +23 -7
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +125 -4
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +439 -381
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +142 -26
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +195 -124
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
- 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/CnHeaderWidget/CnHeaderWidget.vue2.js +84 -11
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +3 -2
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +22 -3
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +10 -3
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +400 -0
- package/dist/esm/mixins/pageViews.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +182 -0
- package/dist/esm/store/useObjectStore.js +118 -33
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +25 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +27 -3
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/schema.js +40 -2
- package/dist/esm/utils/schema.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/nextcloud-vue.cjs.js +2001 -684
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +182 -0
- package/l10n/en.json +3 -0
- package/l10n/nl.json +3 -0
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +148 -7
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +194 -10
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
- package/src/components/CnDetailPage/CnDetailPage.md +5 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +208 -11
- package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +100 -13
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +9 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +33 -5
- package/src/css/detail-page.css +27 -0
- package/src/css/kpi-card.css +47 -0
- package/src/mixins/pageViews.js +396 -0
- package/src/schemas/app-manifest-v2.schema.json +51 -2
- package/src/store/useObjectStore.js +118 -33
- package/src/utils/actionsDispatcher.js +25 -2
- package/src/utils/columnFilters.js +29 -2
- package/src/utils/schema.js +40 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -264,6 +264,27 @@ const baseActions = {
|
|
|
264
264
|
this.facets = { ...this.facets, [slug]: {} }
|
|
265
265
|
},
|
|
266
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Give a registered type a second schema path segment to try once when
|
|
269
|
+
* its own answers 404 (see `_fetchWithSchemaFallback`). Set by
|
|
270
|
+
* `resolveObjectOpType` for a schema title it kebab-cased, with the
|
|
271
|
+
* title as written. A type that is not registered is left alone.
|
|
272
|
+
*
|
|
273
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-schema-title-falls-back-to-its-written-form
|
|
274
|
+
* @param {string} slug The type slug
|
|
275
|
+
* @param {string} schema The fallback schema path segment
|
|
276
|
+
*/
|
|
277
|
+
setSchemaFallback(slug, schema) {
|
|
278
|
+
const config = this.objectTypeRegistry[slug]
|
|
279
|
+
if (!config || typeof schema !== 'string' || schema === '') {
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
this.objectTypeRegistry = {
|
|
283
|
+
...this.objectTypeRegistry,
|
|
284
|
+
[slug]: { ...config, schemaFallback: schema },
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
267
288
|
/**
|
|
268
289
|
* Unregister an object type and clean up all its state.
|
|
269
290
|
*
|
|
@@ -433,11 +454,12 @@ const baseActions = {
|
|
|
433
454
|
*
|
|
434
455
|
* @param {string} type The type slug
|
|
435
456
|
* @param {string|null} [id] Optional object ID
|
|
457
|
+
* @param {string|null} [schema] Schema path segment to use instead of the type's own
|
|
436
458
|
* @return {string} Full API URL path
|
|
437
459
|
*/
|
|
438
|
-
_buildUrl(type, id = null) {
|
|
460
|
+
_buildUrl(type, id = null, schema = null) {
|
|
439
461
|
const config = this._getTypeConfig(type)
|
|
440
|
-
let url = `${this._options.baseUrl}/${config.register}/${config.schema}`
|
|
462
|
+
let url = `${this._options.baseUrl}/${config.register}/${schema ?? config.schema}`
|
|
441
463
|
if (id) {
|
|
442
464
|
url += `/${id}`
|
|
443
465
|
}
|
|
@@ -458,11 +480,12 @@ const baseActions = {
|
|
|
458
480
|
* @param {string} type The type slug
|
|
459
481
|
* @param {object} [params] Extra query parameters
|
|
460
482
|
* @param {string|null} [id] Optional object ID
|
|
483
|
+
* @param {string|null} [schema] Schema path segment to use instead of the type's own
|
|
461
484
|
* @return {string} Full API URL path including query string
|
|
462
485
|
*/
|
|
463
|
-
_buildUrlWithParams(type, params = {}, id = null) {
|
|
486
|
+
_buildUrlWithParams(type, params = {}, id = null, schema = null) {
|
|
464
487
|
const config = this._getTypeConfig(type)
|
|
465
|
-
let url = `${this._options.baseUrl}/${config.register}/${config.schema}`
|
|
488
|
+
let url = `${this._options.baseUrl}/${config.register}/${schema ?? config.schema}`
|
|
466
489
|
if (id) {
|
|
467
490
|
url += `/${id}`
|
|
468
491
|
}
|
|
@@ -472,6 +495,54 @@ const baseActions = {
|
|
|
472
495
|
return url
|
|
473
496
|
},
|
|
474
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Fetch for a type, retrying ONCE with its `schemaFallback` when the
|
|
500
|
+
* schema path segment answers 404. A type without a fallback (every type
|
|
501
|
+
* not registered by `resolveObjectOpType` for a kebab-cased title) makes
|
|
502
|
+
* exactly the one request it always made. When the retry succeeds the
|
|
503
|
+
* fallback becomes the type's schema, so later calls go straight to it;
|
|
504
|
+
* when it does not, the first answer is returned and nothing changes.
|
|
505
|
+
*
|
|
506
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-schema-title-falls-back-to-its-written-form
|
|
507
|
+
* @param {string} type The type slug
|
|
508
|
+
* @param {function((string|null)): string} makeUrl Builds the URL; called with
|
|
509
|
+
* null for the type's own schema, or with the fallback segment.
|
|
510
|
+
* @param {object} init The fetch options.
|
|
511
|
+
* @return {Promise<Response>} The response to use.
|
|
512
|
+
*/
|
|
513
|
+
async _fetchWithSchemaFallback(type, makeUrl, init) {
|
|
514
|
+
// `makeUrl` wraps `_buildUrl` / `_buildUrlWithParams`, so `url` is built
|
|
515
|
+
// from the prefixed `_options.baseUrl` like every other request here.
|
|
516
|
+
let url = makeUrl(null)
|
|
517
|
+
const response = await fetch(url, init)
|
|
518
|
+
if (!response || response.status !== 404) {
|
|
519
|
+
return response
|
|
520
|
+
}
|
|
521
|
+
const config = this.objectTypeRegistry[type]
|
|
522
|
+
const fallback = config && config.schemaFallback
|
|
523
|
+
if (!fallback || String(fallback) === String(config.schema)) {
|
|
524
|
+
return response
|
|
525
|
+
}
|
|
526
|
+
let retry
|
|
527
|
+
try {
|
|
528
|
+
url = makeUrl(fallback)
|
|
529
|
+
retry = await fetch(url, init)
|
|
530
|
+
} catch {
|
|
531
|
+
return response
|
|
532
|
+
}
|
|
533
|
+
if (!retry || !retry.ok) {
|
|
534
|
+
discardResponseBody(retry)
|
|
535
|
+
return response
|
|
536
|
+
}
|
|
537
|
+
discardResponseBody(response)
|
|
538
|
+
const { schemaFallback: _, ...rest } = config
|
|
539
|
+
this.objectTypeRegistry = {
|
|
540
|
+
...this.objectTypeRegistry,
|
|
541
|
+
[type]: { ...rest, schema: fallback },
|
|
542
|
+
}
|
|
543
|
+
return retry
|
|
544
|
+
},
|
|
545
|
+
|
|
475
546
|
/**
|
|
476
547
|
* Clear the error state for a type.
|
|
477
548
|
*
|
|
@@ -604,12 +675,14 @@ const baseActions = {
|
|
|
604
675
|
}
|
|
605
676
|
}
|
|
606
677
|
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
678
|
+
const response = await this._fetchWithSchemaFallback(
|
|
679
|
+
type,
|
|
680
|
+
(schema) => this._buildUrlWithParams(type, fetchParams, null, schema),
|
|
681
|
+
{
|
|
682
|
+
method: 'GET',
|
|
683
|
+
headers: this._buildHeaders(),
|
|
684
|
+
},
|
|
685
|
+
)
|
|
613
686
|
|
|
614
687
|
if (!response.ok) {
|
|
615
688
|
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
@@ -675,11 +748,14 @@ const baseActions = {
|
|
|
675
748
|
*/
|
|
676
749
|
async fetchCollectionForOptions(type, params = {}) {
|
|
677
750
|
try {
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
751
|
+
const response = await this._fetchWithSchemaFallback(
|
|
752
|
+
type,
|
|
753
|
+
(schema) => this._buildUrlWithParams(type, params, null, schema),
|
|
754
|
+
{
|
|
755
|
+
method: 'GET',
|
|
756
|
+
headers: this._buildHeaders(),
|
|
757
|
+
},
|
|
758
|
+
)
|
|
683
759
|
|
|
684
760
|
if (!response.ok) {
|
|
685
761
|
discardResponseBody(response)
|
|
@@ -734,10 +810,14 @@ const baseActions = {
|
|
|
734
810
|
this.errors = { ...this.errors, [type]: null }
|
|
735
811
|
|
|
736
812
|
try {
|
|
737
|
-
const response = await
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
813
|
+
const response = await this._fetchWithSchemaFallback(
|
|
814
|
+
type,
|
|
815
|
+
(schema) => (schema === null ? url : this._buildUrl(type, id, schema)),
|
|
816
|
+
{
|
|
817
|
+
method: 'GET',
|
|
818
|
+
headers: this._buildHeaders(),
|
|
819
|
+
},
|
|
820
|
+
)
|
|
741
821
|
|
|
742
822
|
if (!response.ok) {
|
|
743
823
|
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
@@ -805,16 +885,19 @@ const baseActions = {
|
|
|
805
885
|
|
|
806
886
|
try {
|
|
807
887
|
const isUpdate = !!objectData.id
|
|
808
|
-
const url = isUpdate
|
|
809
|
-
? this._buildUrl(type, objectData.id)
|
|
810
|
-
: this._buildUrl(type)
|
|
811
888
|
const method = isUpdate ? 'PUT' : 'POST'
|
|
812
889
|
|
|
813
|
-
const response = await
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
890
|
+
const response = await this._fetchWithSchemaFallback(
|
|
891
|
+
type,
|
|
892
|
+
(schema) => (isUpdate
|
|
893
|
+
? this._buildUrl(type, objectData.id, schema)
|
|
894
|
+
: this._buildUrl(type, null, schema)),
|
|
895
|
+
{
|
|
896
|
+
method,
|
|
897
|
+
headers: this._buildHeaders(),
|
|
898
|
+
body: JSON.stringify(objectData),
|
|
899
|
+
},
|
|
900
|
+
)
|
|
818
901
|
|
|
819
902
|
if (!response.ok) {
|
|
820
903
|
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
@@ -866,12 +949,14 @@ const baseActions = {
|
|
|
866
949
|
this.errors = { ...this.errors, [type]: null }
|
|
867
950
|
|
|
868
951
|
try {
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
952
|
+
const response = await this._fetchWithSchemaFallback(
|
|
953
|
+
type,
|
|
954
|
+
(schema) => this._buildUrl(type, id, schema),
|
|
955
|
+
{
|
|
956
|
+
method: 'DELETE',
|
|
957
|
+
headers: this._buildHeaders(),
|
|
958
|
+
},
|
|
959
|
+
)
|
|
875
960
|
|
|
876
961
|
if (!response.ok) {
|
|
877
962
|
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
@@ -100,13 +100,33 @@ export function isExternalActionTarget(target) {
|
|
|
100
100
|
* would otherwise register — and fetch — a 404 (defect 7, learniq round
|
|
101
101
|
* 1). Idempotent for a caller that already passes a correct slug.
|
|
102
102
|
*
|
|
103
|
+
* A caller that already holds the schema SLUG (not a `$ref` title) passes
|
|
104
|
+
* `{ exactSchema: true }`: the slug is then used exactly as given. Kebab-casing
|
|
105
|
+
* a real camelCase slug (`productCategory` -> `product-category`) asks the
|
|
106
|
+
* objects API for a schema that does not exist and 404s (pipelinq review,
|
|
107
|
+
* round two).
|
|
108
|
+
*
|
|
109
|
+
* @spec openspec/changes/review-round-two/specs/schema-utilities/spec.md
|
|
110
|
+
*
|
|
111
|
+
* A title is not always registered under its kebab form: learniq holds
|
|
112
|
+
* `LearniqSettings` and `SubjectTeacherAssignment` as written. So a type
|
|
113
|
+
* registered here for a value the kebab rule CHANGED also carries the value
|
|
114
|
+
* as written as its `schemaFallback`, and the store retries with it once
|
|
115
|
+
* when the kebab form answers 404 (keeping it from then on). The kebab form
|
|
116
|
+
* is still tried first, so a schema registered under it costs nothing.
|
|
117
|
+
*
|
|
118
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-schema-title-falls-back-to-its-written-form
|
|
103
119
|
* @param {object} store The object store instance (useObjectStore shape).
|
|
104
120
|
* @param {{register: (string|number), schema: (string|number)}} source The widget source.
|
|
121
|
+
* @param {{exactSchema: (boolean|undefined)}} [options] `exactSchema: true` skips the title-to-slug step.
|
|
105
122
|
* @return {string} The type slug to use for store CRUD calls.
|
|
106
123
|
*/
|
|
107
|
-
export function resolveObjectOpType(store, source) {
|
|
124
|
+
export function resolveObjectOpType(store, source, options = {}) {
|
|
108
125
|
const register = String(source.register)
|
|
109
|
-
const schema = String(schemaRefSlug(source.schema))
|
|
126
|
+
const schema = options.exactSchema === true ? String(source.schema) : String(schemaRefSlug(source.schema))
|
|
127
|
+
const written = typeof source.schema === 'string'
|
|
128
|
+
? source.schema.substring(source.schema.lastIndexOf('/') + 1)
|
|
129
|
+
: ''
|
|
110
130
|
const registry = store.objectTypeRegistry || {}
|
|
111
131
|
for (const [slug, config] of Object.entries(registry)) {
|
|
112
132
|
if (!config) {
|
|
@@ -120,6 +140,9 @@ export function resolveObjectOpType(store, source) {
|
|
|
120
140
|
const slug = `${register}/${schema}`
|
|
121
141
|
if (!registry[slug]) {
|
|
122
142
|
store.registerObjectType(slug, schema, register)
|
|
143
|
+
if (written !== '' && written !== schema && typeof store.setSchemaFallback === 'function') {
|
|
144
|
+
store.setSchemaFallback(slug, written)
|
|
145
|
+
}
|
|
123
146
|
}
|
|
124
147
|
return slug
|
|
125
148
|
}
|
|
@@ -15,13 +15,19 @@
|
|
|
15
15
|
* Parameter shapes, as OpenRegister reads them:
|
|
16
16
|
* - equality and "any of": `key=value`, `key[]=a&key[]=b`
|
|
17
17
|
* - ranges: `key[gte]=from`, `key[lte]=to`
|
|
18
|
+
* - contains (text): `key[like]=term`, case-insensitive (openregister#4430);
|
|
19
|
+
* the server escapes `%`, `_` and `\`, so the raw term goes out as typed
|
|
18
20
|
*
|
|
19
21
|
* @module utils/columnFilters
|
|
20
22
|
* @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md
|
|
23
|
+
* @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
import { schemaRefSlug } from './schemaRefSlug.js'
|
|
24
27
|
|
|
28
|
+
/** OpenRegister's case-insensitive contains operator, used by the text filter. */
|
|
29
|
+
export const CONTAINS_OPERATOR = 'like'
|
|
30
|
+
|
|
25
31
|
/** Column keys that mark a value computed on the client, never stored. */
|
|
26
32
|
const COMPUTED_MARKERS = ['aggregate', 'compute', 'computed', 'virtual']
|
|
27
33
|
|
|
@@ -160,7 +166,7 @@ function referenceTarget(column, prop) {
|
|
|
160
166
|
*
|
|
161
167
|
* Kinds: `enum` (checkbox list), `boolean` (yes, no, any), `reference`
|
|
162
168
|
* (searchable list of the referenced objects), `number` and `date` (from and
|
|
163
|
-
* to), and `string` (
|
|
169
|
+
* to), and `string` (contains, through OpenRegister's `[like]`). `filterable: false` on the column turns it
|
|
164
170
|
* off. A column with no schema property behind it only filters when it
|
|
165
171
|
* carries an enum hint (`enum`, or a badge widget's colour map), because the
|
|
166
172
|
* server cannot filter on a value it does not store.
|
|
@@ -234,9 +240,25 @@ export function columnFilterParamKeys(def) {
|
|
|
234
240
|
if (def.kind === 'number' || def.kind === 'date') {
|
|
235
241
|
return [`${def.key}[gte]`, `${def.key}[lte]`]
|
|
236
242
|
}
|
|
243
|
+
if (def.kind === 'string') {
|
|
244
|
+
return [containsKey(def)]
|
|
245
|
+
}
|
|
237
246
|
return [def.key]
|
|
238
247
|
}
|
|
239
248
|
|
|
249
|
+
/**
|
|
250
|
+
* The parameter a text filter writes: `{key}[like]`. An exact `key=value`
|
|
251
|
+
* from the facet sidebar or a fixed filter is a different key, so the header
|
|
252
|
+
* neither shows nor clears it.
|
|
253
|
+
*
|
|
254
|
+
* @param {object} def A filter definition of kind `string`.
|
|
255
|
+
* @return {string} The parameter key.
|
|
256
|
+
* @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains
|
|
257
|
+
*/
|
|
258
|
+
function containsKey(def) {
|
|
259
|
+
return `${def.key}[${CONTAINS_OPERATOR}]`
|
|
260
|
+
}
|
|
261
|
+
|
|
240
262
|
/**
|
|
241
263
|
* A column filter's current state, read from the active-filter map.
|
|
242
264
|
*
|
|
@@ -262,6 +284,9 @@ export function columnFilterState(def, activeFilters) {
|
|
|
262
284
|
to: def.kind === 'date' ? to.slice(0, 10) : to,
|
|
263
285
|
}
|
|
264
286
|
}
|
|
287
|
+
if (def.kind === 'string') {
|
|
288
|
+
return { value: asList(filters[containsKey(def)])[0] || '' }
|
|
289
|
+
}
|
|
265
290
|
return { value: asList(filters[def.key])[0] || '' }
|
|
266
291
|
}
|
|
267
292
|
|
|
@@ -309,8 +334,10 @@ export function columnFilterParams(def, state) {
|
|
|
309
334
|
if (def.kind === 'boolean') {
|
|
310
335
|
return { [def.key]: (s.value === 'true' || s.value === 'false') ? [s.value] : [] }
|
|
311
336
|
}
|
|
337
|
+
// Text: the raw term under `[like]`. No wildcards: OpenRegister wraps
|
|
338
|
+
// the term itself and escapes `%`, `_` and `\` in it.
|
|
312
339
|
const value = s.value === undefined || s.value === null ? '' : String(s.value).trim()
|
|
313
|
-
return { [def
|
|
340
|
+
return { [containsKey(def)]: value === '' ? [] : [value] }
|
|
314
341
|
}
|
|
315
342
|
|
|
316
343
|
/**
|
package/src/utils/schema.js
CHANGED
|
@@ -367,7 +367,7 @@ function isUserProp(prop) {
|
|
|
367
367
|
if (!prop || typeof prop !== 'object') {
|
|
368
368
|
return false
|
|
369
369
|
}
|
|
370
|
-
if (prop.referenceType === 'nextcloud-user') {
|
|
370
|
+
if (prop.referenceType === 'nextcloud-user' || prop.widget === 'user') {
|
|
371
371
|
return true
|
|
372
372
|
}
|
|
373
373
|
const format = prop.format || ''
|
|
@@ -389,7 +389,44 @@ function isGroupProp(prop) {
|
|
|
389
389
|
if (!prop || typeof prop !== 'object') {
|
|
390
390
|
return false
|
|
391
391
|
}
|
|
392
|
-
return prop.referenceType === 'nextcloud-group' || prop.format === 'nc-group'
|
|
392
|
+
return prop.referenceType === 'nextcloud-group' || prop.format === 'nc-group' || prop.widget === 'group'
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Make a manifest field override that names a picker widget behave exactly
|
|
397
|
+
* like the schema format that triggers it.
|
|
398
|
+
*
|
|
399
|
+
* OpenRegister refuses formats it does not know (`nc-group`, `language`,
|
|
400
|
+
* `timezone`) at import, so an app cannot always put them in its schema. It
|
|
401
|
+
* declares `fieldOverrides.<key>.widget: 'group' | 'language' | 'timezone'`
|
|
402
|
+
* (or `'user'`) instead. The widget alone is not enough for the user and
|
|
403
|
+
* group pickers, which CnFormDialog recognises by `userPicker` /
|
|
404
|
+
* `groupPicker`, so set those here. An override may also carry its own
|
|
405
|
+
* `x-default` (`current-language`, `current-timezone`), which becomes the
|
|
406
|
+
* field's `defaultToken` just as the schema key does.
|
|
407
|
+
*
|
|
408
|
+
* Mutates `field` in place.
|
|
409
|
+
*
|
|
410
|
+
* @spec openspec/changes/review-round-two/specs/schema-utilities/spec.md
|
|
411
|
+
* @param {object} field The field descriptor, overrides already merged in.
|
|
412
|
+
* @param {object} override The raw override for this key.
|
|
413
|
+
* @return {void}
|
|
414
|
+
*/
|
|
415
|
+
function applyPickerOverride(field, override) {
|
|
416
|
+
if (!override || typeof override !== 'object') {
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
const widget = override.widget
|
|
420
|
+
if (widget === 'group' || widget === 'group-multiselect') {
|
|
421
|
+
field.groupPicker = { multiple: widget === 'group-multiselect' }
|
|
422
|
+
field.userPicker = null
|
|
423
|
+
} else if (widget === 'user' || widget === 'user-multiselect') {
|
|
424
|
+
field.userPicker = { multiple: widget === 'user-multiselect' }
|
|
425
|
+
field.groupPicker = null
|
|
426
|
+
}
|
|
427
|
+
if (typeof override['x-default'] === 'string' && override['x-default'] !== '') {
|
|
428
|
+
field.defaultToken = override['x-default']
|
|
429
|
+
}
|
|
393
430
|
}
|
|
394
431
|
|
|
395
432
|
/**
|
|
@@ -844,6 +881,7 @@ export function fieldsFromSchema(schema, options = {}) {
|
|
|
844
881
|
// Apply per-field overrides
|
|
845
882
|
if (overrides[key]) {
|
|
846
883
|
Object.assign(field, overrides[key])
|
|
884
|
+
applyPickerOverride(field, overrides[key])
|
|
847
885
|
}
|
|
848
886
|
|
|
849
887
|
return field
|