@conduction/nextcloud-vue 2.70.0 → 2.72.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/CnAppRoot/CnAppRoot.vue2.js +151 -13
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +38 -42
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js +2 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js +7 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js +10 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js +150 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js +54 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +22 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/composables/useWalkthrough.js +11 -3
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js +7 -10
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js +23 -13
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js +8 -6
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue3.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 +15 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +15 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +117 -0
- package/dist/esm/utils/columnFilters.js +54 -4
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/selectOptions.js +57 -0
- package/dist/esm/utils/selectOptions.js.map +1 -0
- package/dist/esm/utils/userAutocomplete.js +69 -3
- package/dist/esm/utils/userAutocomplete.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 +6560 -5880
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +117 -0
- package/l10n/en.json +15 -1
- package/l10n/nl.json +15 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
- package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -18
- package/src/components/CnFlowDetail/CnRunDetailSidebar.vue +6 -0
- package/src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue +250 -0
- package/src/components/CnFlowStepOutcomes/index.js +8 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +23 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
- package/src/components/index.js +1 -0
- package/src/composables/useWalkthrough.js +11 -3
- package/src/css/dashboard.css +44 -0
- package/src/dialogs/CnFlowNodeEditModal.vue +41 -20
- package/src/dialogs/CnRunNodeDialog.vue +8 -6
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +2 -1
- package/src/utils/columnFilters.js +54 -4
- package/src/utils/selectOptions.js +54 -0
- package/src/utils/userAutocomplete.js +68 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* The choices a node's `select` field offers, in the `{id, label}` shape
|
|
6
|
+
* NcSelect takes.
|
|
7
|
+
*
|
|
8
|
+
* A field gets its choices one of two ways. `optionsFrom` names a url the
|
|
9
|
+
* owning app answers. `options` lists them in the form itself, for a short
|
|
10
|
+
* fixed vocabulary that has no endpoint of its own. Both dialogs that render a
|
|
11
|
+
* node's configForm read the same shapes through this module, so the two
|
|
12
|
+
* cannot come to accept different ones.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalise rows into `{id, label}` options.
|
|
17
|
+
*
|
|
18
|
+
* Accepts plain strings and numbers, `{id, label}`, `{value, label}`, and
|
|
19
|
+
* OpenRegister objects (uuid from `@self`, label from name or title). A row
|
|
20
|
+
* without an id is dropped rather than shown as a blank option.
|
|
21
|
+
*
|
|
22
|
+
* @param {Array<unknown>} rows The raw rows.
|
|
23
|
+
* @param {(label: string) => string} [translate] Applied to each declared label.
|
|
24
|
+
* @return {Array<{id: string|number, label: string}>} The options.
|
|
25
|
+
*/
|
|
26
|
+
function normaliseSelectOptions(rows, translate = (s) => s) {
|
|
27
|
+
if (!Array.isArray(rows)) {
|
|
28
|
+
return []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return rows.map((row) => {
|
|
32
|
+
if (typeof row === 'string' || typeof row === 'number') {
|
|
33
|
+
return { id: row, label: String(row) }
|
|
34
|
+
}
|
|
35
|
+
if (!row || typeof row !== 'object') {
|
|
36
|
+
return { id: undefined, label: '' }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid;
|
|
40
|
+
const declared = row.label || row.name || row.title;
|
|
41
|
+
|
|
42
|
+
return { id, label: declared ? String(translate(declared)) : String(id) }
|
|
43
|
+
}).filter((option) => option.id !== undefined && option.id !== null && option.id !== '')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a field declares its choices in the form itself.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} field The field declaration.
|
|
50
|
+
* @return {boolean} True for a `select` with an `options` list.
|
|
51
|
+
*/
|
|
52
|
+
function hasStaticOptions(field) {
|
|
53
|
+
return field?.type === 'select' && Array.isArray(field?.options)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { hasStaticOptions, normaliseSelectOptions };
|
|
57
|
+
//# sourceMappingURL=selectOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectOptions.js","sources":["../../../src/utils/selectOptions.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The choices a node's `select` field offers, in the `{id, label}` shape\n * NcSelect takes.\n *\n * A field gets its choices one of two ways. `optionsFrom` names a url the\n * owning app answers. `options` lists them in the form itself, for a short\n * fixed vocabulary that has no endpoint of its own. Both dialogs that render a\n * node's configForm read the same shapes through this module, so the two\n * cannot come to accept different ones.\n */\n\n/**\n * Normalise rows into `{id, label}` options.\n *\n * Accepts plain strings and numbers, `{id, label}`, `{value, label}`, and\n * OpenRegister objects (uuid from `@self`, label from name or title). A row\n * without an id is dropped rather than shown as a blank option.\n *\n * @param {Array<unknown>} rows The raw rows.\n * @param {(label: string) => string} [translate] Applied to each declared label.\n * @return {Array<{id: string|number, label: string}>} The options.\n */\nexport function normaliseSelectOptions(rows, translate = (s) => s) {\n\tif (!Array.isArray(rows)) {\n\t\treturn []\n\t}\n\n\treturn rows.map((row) => {\n\t\tif (typeof row === 'string' || typeof row === 'number') {\n\t\t\treturn { id: row, label: String(row) }\n\t\t}\n\t\tif (!row || typeof row !== 'object') {\n\t\t\treturn { id: undefined, label: '' }\n\t\t}\n\n\t\tconst id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid\n\t\tconst declared = row.label || row.name || row.title\n\n\t\treturn { id, label: declared ? String(translate(declared)) : String(id) }\n\t}).filter((option) => option.id !== undefined && option.id !== null && option.id !== '')\n}\n\n/**\n * Whether a field declares its choices in the form itself.\n *\n * @param {object} field The field declaration.\n * @return {boolean} True for a `select` with an `options` list.\n */\nexport function hasStaticOptions(field) {\n\treturn field?.type === 'select' && Array.isArray(field?.options)\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE;AACnE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AAC3B,EAAE,OAAO,EAAE;AACX,EAAE;AACF;AACA,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK;AAC1B,EAAE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AAC1D,GAAG,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE;AACzC,GAAG;AACH,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AACvC,GAAG,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;AACtC,GAAG;AACH;AACA,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,IAAI,GAAG,CAAC,KAAI;AAClE,EAAE,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,MAAK;AACrD;AACA,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,EAAE;AAC3E,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;AACzF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,KAAK,EAAE;AACxC,CAAC,OAAO,KAAK,EAAE,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;AACjE;;;;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getCurrentUser } from '@nextcloud/auth';
|
|
1
2
|
import axios__default from '@nextcloud/axios';
|
|
2
3
|
import { generateOcsUrl } from '@nextcloud/router';
|
|
3
4
|
|
|
@@ -16,6 +17,46 @@ import { generateOcsUrl } from '@nextcloud/router';
|
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The signed-in user as a picker option, or null when nobody is signed in.
|
|
22
|
+
*
|
|
23
|
+
* Core autocomplete never returns the person who searches (it is built for
|
|
24
|
+
* sharing, and you do not share with yourself), so a picker backed by it
|
|
25
|
+
* alone cannot assign a task to yourself.
|
|
26
|
+
*
|
|
27
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md
|
|
28
|
+
* @return {{id: string, label: string, displayName: string, subline: string}|null} The option.
|
|
29
|
+
*/
|
|
30
|
+
function currentUserOption() {
|
|
31
|
+
let user;
|
|
32
|
+
try {
|
|
33
|
+
user = getCurrentUser();
|
|
34
|
+
} catch {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
if (!user || !user.uid) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
const name = user.displayName || String(user.uid);
|
|
41
|
+
return { id: String(user.uid), label: name, displayName: name, subline: '' }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether the current-user option matches a search term (uid or display
|
|
46
|
+
* name contains the term, ignoring case). An empty term matches.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} option The current-user option.
|
|
49
|
+
* @param {string} query The search term.
|
|
50
|
+
* @return {boolean} True when it matches.
|
|
51
|
+
*/
|
|
52
|
+
function matchesQuery(option, query) {
|
|
53
|
+
const q = String(query || '').trim().toLowerCase();
|
|
54
|
+
if (q === '') {
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
return option.id.toLowerCase().includes(q) || option.label.toLowerCase().includes(q)
|
|
58
|
+
}
|
|
59
|
+
|
|
19
60
|
/**
|
|
20
61
|
* Map one autocomplete suggestion to a picker option, keeping only user
|
|
21
62
|
* results. The OCS payload uses `id` for the UID, `label` for the display
|
|
@@ -60,10 +101,11 @@ function toUserOption(suggestion) {
|
|
|
60
101
|
* @param {string} [query] The search term (empty loads an initial page).
|
|
61
102
|
* @param {object} [options] Tuning options.
|
|
62
103
|
* @param {number} [options.limit] Max results (default 25).
|
|
104
|
+
* @param {boolean} [options.includeCurrentUser] Add the signed-in user when they match (default true; a mention list passes false).
|
|
63
105
|
* @return {Promise<Array<{id: string, label: string, subline: string}>>} Picker options.
|
|
64
106
|
*/
|
|
65
107
|
async function searchNextcloudUsers(query = '', options = {}) {
|
|
66
|
-
const { limit = 25 } = options;
|
|
108
|
+
const { limit = 25, includeCurrentUser = true } = options;
|
|
67
109
|
try {
|
|
68
110
|
const url = generateOcsUrl('core/autocomplete/get');
|
|
69
111
|
const response = await axios__default.get(url, {
|
|
@@ -87,14 +129,32 @@ async function searchNextcloudUsers(query = '', options = {}) {
|
|
|
87
129
|
const list = Array.isArray(data)
|
|
88
130
|
? data
|
|
89
131
|
: (Array.isArray(response && response.data) ? response.data : []);
|
|
90
|
-
|
|
132
|
+
const found = list
|
|
91
133
|
.map(toUserOption)
|
|
92
|
-
.filter((opt) => opt !== null)
|
|
134
|
+
.filter((opt) => opt !== null);
|
|
135
|
+
return includeCurrentUser ? withCurrentUser(found, query) : found
|
|
93
136
|
} catch {
|
|
94
137
|
return []
|
|
95
138
|
}
|
|
96
139
|
}
|
|
97
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Put the signed-in user first in a result list when they match the search
|
|
143
|
+
* and the server left them out.
|
|
144
|
+
*
|
|
145
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md
|
|
146
|
+
* @param {Array<object>} options The server's options.
|
|
147
|
+
* @param {string} query The search term.
|
|
148
|
+
* @return {Array<object>} The options, the current user included when they match.
|
|
149
|
+
*/
|
|
150
|
+
function withCurrentUser(options, query) {
|
|
151
|
+
const me = currentUserOption();
|
|
152
|
+
if (!me || !matchesQuery(me, query) || options.some((opt) => opt.id === me.id)) {
|
|
153
|
+
return options
|
|
154
|
+
}
|
|
155
|
+
return [me, ...options]
|
|
156
|
+
}
|
|
157
|
+
|
|
98
158
|
/**
|
|
99
159
|
* Resolve a single user UID to a `{ id, label }` option for edit-mode display.
|
|
100
160
|
* Tries the autocomplete endpoint with the UID as the query and matches the
|
|
@@ -109,6 +169,12 @@ async function resolveNextcloudUser(uid) {
|
|
|
109
169
|
if (uid === undefined || uid === null || uid === '') {
|
|
110
170
|
return fallback
|
|
111
171
|
}
|
|
172
|
+
// The server never returns the signed-in user, so their own uid would
|
|
173
|
+
// otherwise fall back to the bare uid.
|
|
174
|
+
const me = currentUserOption();
|
|
175
|
+
if (me && me.id === String(uid)) {
|
|
176
|
+
return me
|
|
177
|
+
}
|
|
112
178
|
const results = await searchNextcloudUsers(String(uid));
|
|
113
179
|
const match = results.find((opt) => opt.id === String(uid));
|
|
114
180
|
return match || fallback
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"userAutocomplete.js","sources":["../../../src/utils/userAutocomplete.js"],"sourcesContent":["/**\n * Nextcloud user autocomplete helpers.\n *\n * Resolves real Nextcloud users for the schema-driven form's user-picker\n * (a property marked `referenceType: 'nextcloud-user'` or `format: 'user'`).\n * Backed by the core autocomplete OCS endpoint, which is available to every\n * authenticated user (unlike the admin-only `cloud/users` provisioning API).\n *\n * Options are mapped to `{ id: <uid>, label: <display name> }` so the picker\n * stores the stable UID string while showing the human name.\n *\n * @module utils/userAutocomplete\n */\n\nimport axios from '@nextcloud/axios'\nimport { generateOcsUrl } from '@nextcloud/router'\n\n/**\n * Map one autocomplete suggestion to a picker option, keeping only user\n * results. The OCS payload uses `id` for the UID, `label` for the display\n * name and `source` (`users`) / `shareType` (`0`) to mark a user entry.\n *\n * @param {object} suggestion A single autocomplete suggestion object.\n * @return {{id: string, label: string, displayName: string, subline: string}|null} Option or null when it isn't a user.\n */\nfunction toUserOption(suggestion) {\n\tif (!suggestion || typeof suggestion !== 'object') {\n\t\treturn null\n\t}\n\tconst isUser = suggestion.source === 'users'\n\t\t|| suggestion.shareType === 0\n\t\t|| (suggestion.value && suggestion.value.shareType === 0)\n\tif (!isUser) {\n\t\treturn null\n\t}\n\tconst uid = suggestion.id\n\t\t|| (suggestion.value && suggestion.value.shareWith)\n\tif (uid === undefined || uid === null || uid === '') {\n\t\treturn null\n\t}\n\tconst name = suggestion.label || String(uid)\n\treturn {\n\t\tid: String(uid),\n\t\tlabel: name,\n\t\t// NcSelectUsers reads `displayName` specifically (label: \"displayName\"\n\t\t// on the underlying NcSelect, and NcListItemIcon's `name` prop).\n\t\tdisplayName: name,\n\t\tsubline: suggestion.subline || '',\n\t}\n}\n\n/**\n * Search Nextcloud users by name/UID via the core autocomplete OCS endpoint.\n *\n * Fails soft: any network/HTTP error resolves to an empty list (the caller —\n * CnFormDialog — degrades to a plain text input on repeated failure) and the\n * error is swallowed so the console isn't spammed during typing.\n *\n * @param {string} [query] The search term (empty loads an initial page).\n * @param {object} [options] Tuning options.\n * @param {number} [options.limit] Max results (default 25).\n * @return {Promise<Array<{id: string, label: string, subline: string}>>} Picker options.\n */\nexport async function searchNextcloudUsers(query = '', options = {}) {\n\tconst { limit = 25 } = options\n\ttry {\n\t\tconst url = generateOcsUrl('core/autocomplete/get')\n\t\tconst response = await axios.get(url, {\n\t\t\theaders: {\n\t\t\t\t'OCS-APIRequest': 'true',\n\t\t\t\tAccept: 'application/json',\n\t\t\t},\n\t\t\tparams: {\n\t\t\t\tsearch: query || '',\n\t\t\t\titemType: ' ',\n\t\t\t\titemId: ' ',\n\t\t\t\t'shareTypes[]': 0,\n\t\t\t\tlimit,\n\t\t\t},\n\t\t})\n\t\t// OCS envelope: { ocs: { data: [ ... ] } }; tolerate a bare array too.\n\t\tconst data = response\n\t\t\t&& response.data\n\t\t\t&& response.data.ocs\n\t\t\t&& response.data.ocs.data\n\t\tconst list = Array.isArray(data)\n\t\t\t? data\n\t\t\t: (Array.isArray(response && response.data) ? response.data : [])\n\t\treturn list\n\t\t\t.map(toUserOption)\n\t\t\t.filter((opt) => opt !== null)\n\t} catch {\n\t\treturn []\n\t}\n}\n\n/**\n * Resolve a single user UID to a `{ id, label }` option for edit-mode display.\n * Tries the autocomplete endpoint with the UID as the query and matches the\n * exact UID; falls back to `{ id: uid, label: uid }` so the select always\n * shows at least the stored UID even when the name can't be resolved.\n *\n * @param {string} uid The stored user UID.\n * @return {Promise<{id: string, label: string}>} The resolved option.\n */\nexport async function resolveNextcloudUser(uid) {\n\tconst fallback = { id: String(uid), label: String(uid) }\n\tif (uid === undefined || uid === null || uid === '') {\n\t\treturn fallback\n\t}\n\tconst results = await searchNextcloudUsers(String(uid))\n\tconst match = results.find((opt) => opt.id === String(uid))\n\treturn match || fallback\n}\n"],"names":["axios"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"userAutocomplete.js","sources":["../../../src/utils/userAutocomplete.js"],"sourcesContent":["/**\n * Nextcloud user autocomplete helpers.\n *\n * Resolves real Nextcloud users for the schema-driven form's user-picker\n * (a property marked `referenceType: 'nextcloud-user'` or `format: 'user'`).\n * Backed by the core autocomplete OCS endpoint, which is available to every\n * authenticated user (unlike the admin-only `cloud/users` provisioning API).\n *\n * Options are mapped to `{ id: <uid>, label: <display name> }` so the picker\n * stores the stable UID string while showing the human name.\n *\n * @module utils/userAutocomplete\n */\n\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { generateOcsUrl } from '@nextcloud/router'\n\n/**\n * The signed-in user as a picker option, or null when nobody is signed in.\n *\n * Core autocomplete never returns the person who searches (it is built for\n * sharing, and you do not share with yourself), so a picker backed by it\n * alone cannot assign a task to yourself.\n *\n * @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md\n * @return {{id: string, label: string, displayName: string, subline: string}|null} The option.\n */\nfunction currentUserOption() {\n\tlet user\n\ttry {\n\t\tuser = getCurrentUser()\n\t} catch {\n\t\treturn null\n\t}\n\tif (!user || !user.uid) {\n\t\treturn null\n\t}\n\tconst name = user.displayName || String(user.uid)\n\treturn { id: String(user.uid), label: name, displayName: name, subline: '' }\n}\n\n/**\n * Whether the current-user option matches a search term (uid or display\n * name contains the term, ignoring case). An empty term matches.\n *\n * @param {object} option The current-user option.\n * @param {string} query The search term.\n * @return {boolean} True when it matches.\n */\nfunction matchesQuery(option, query) {\n\tconst q = String(query || '').trim().toLowerCase()\n\tif (q === '') {\n\t\treturn true\n\t}\n\treturn option.id.toLowerCase().includes(q) || option.label.toLowerCase().includes(q)\n}\n\n/**\n * Map one autocomplete suggestion to a picker option, keeping only user\n * results. The OCS payload uses `id` for the UID, `label` for the display\n * name and `source` (`users`) / `shareType` (`0`) to mark a user entry.\n *\n * @param {object} suggestion A single autocomplete suggestion object.\n * @return {{id: string, label: string, displayName: string, subline: string}|null} Option or null when it isn't a user.\n */\nfunction toUserOption(suggestion) {\n\tif (!suggestion || typeof suggestion !== 'object') {\n\t\treturn null\n\t}\n\tconst isUser = suggestion.source === 'users'\n\t\t|| suggestion.shareType === 0\n\t\t|| (suggestion.value && suggestion.value.shareType === 0)\n\tif (!isUser) {\n\t\treturn null\n\t}\n\tconst uid = suggestion.id\n\t\t|| (suggestion.value && suggestion.value.shareWith)\n\tif (uid === undefined || uid === null || uid === '') {\n\t\treturn null\n\t}\n\tconst name = suggestion.label || String(uid)\n\treturn {\n\t\tid: String(uid),\n\t\tlabel: name,\n\t\t// NcSelectUsers reads `displayName` specifically (label: \"displayName\"\n\t\t// on the underlying NcSelect, and NcListItemIcon's `name` prop).\n\t\tdisplayName: name,\n\t\tsubline: suggestion.subline || '',\n\t}\n}\n\n/**\n * Search Nextcloud users by name/UID via the core autocomplete OCS endpoint.\n *\n * Fails soft: any network/HTTP error resolves to an empty list (the caller —\n * CnFormDialog — degrades to a plain text input on repeated failure) and the\n * error is swallowed so the console isn't spammed during typing.\n *\n * @param {string} [query] The search term (empty loads an initial page).\n * @param {object} [options] Tuning options.\n * @param {number} [options.limit] Max results (default 25).\n * @param {boolean} [options.includeCurrentUser] Add the signed-in user when they match (default true; a mention list passes false).\n * @return {Promise<Array<{id: string, label: string, subline: string}>>} Picker options.\n */\nexport async function searchNextcloudUsers(query = '', options = {}) {\n\tconst { limit = 25, includeCurrentUser = true } = options\n\ttry {\n\t\tconst url = generateOcsUrl('core/autocomplete/get')\n\t\tconst response = await axios.get(url, {\n\t\t\theaders: {\n\t\t\t\t'OCS-APIRequest': 'true',\n\t\t\t\tAccept: 'application/json',\n\t\t\t},\n\t\t\tparams: {\n\t\t\t\tsearch: query || '',\n\t\t\t\titemType: ' ',\n\t\t\t\titemId: ' ',\n\t\t\t\t'shareTypes[]': 0,\n\t\t\t\tlimit,\n\t\t\t},\n\t\t})\n\t\t// OCS envelope: { ocs: { data: [ ... ] } }; tolerate a bare array too.\n\t\tconst data = response\n\t\t\t&& response.data\n\t\t\t&& response.data.ocs\n\t\t\t&& response.data.ocs.data\n\t\tconst list = Array.isArray(data)\n\t\t\t? data\n\t\t\t: (Array.isArray(response && response.data) ? response.data : [])\n\t\tconst found = list\n\t\t\t.map(toUserOption)\n\t\t\t.filter((opt) => opt !== null)\n\t\treturn includeCurrentUser ? withCurrentUser(found, query) : found\n\t} catch {\n\t\treturn []\n\t}\n}\n\n/**\n * Put the signed-in user first in a result list when they match the search\n * and the server left them out.\n *\n * @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md\n * @param {Array<object>} options The server's options.\n * @param {string} query The search term.\n * @return {Array<object>} The options, the current user included when they match.\n */\nfunction withCurrentUser(options, query) {\n\tconst me = currentUserOption()\n\tif (!me || !matchesQuery(me, query) || options.some((opt) => opt.id === me.id)) {\n\t\treturn options\n\t}\n\treturn [me, ...options]\n}\n\n/**\n * Resolve a single user UID to a `{ id, label }` option for edit-mode display.\n * Tries the autocomplete endpoint with the UID as the query and matches the\n * exact UID; falls back to `{ id: uid, label: uid }` so the select always\n * shows at least the stored UID even when the name can't be resolved.\n *\n * @param {string} uid The stored user UID.\n * @return {Promise<{id: string, label: string}>} The resolved option.\n */\nexport async function resolveNextcloudUser(uid) {\n\tconst fallback = { id: String(uid), label: String(uid) }\n\tif (uid === undefined || uid === null || uid === '') {\n\t\treturn fallback\n\t}\n\t// The server never returns the signed-in user, so their own uid would\n\t// otherwise fall back to the bare uid.\n\tconst me = currentUserOption()\n\tif (me && me.id === String(uid)) {\n\t\treturn me\n\t}\n\tconst results = await searchNextcloudUsers(String(uid))\n\tconst match = results.find((opt) => opt.id === String(uid))\n\treturn match || fallback\n}\n"],"names":["axios"],"mappings":"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"}
|