@oat-sa/tao-core-ui 3.23.0 → 3.24.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/figure/FigureStateActive.js +64 -32
- package/dist/image/ImgStateActive/initHelper.js +64 -32
- package/dist/image/ImgStateActive.js +64 -32
- package/dist/previewer.js +1 -1
- package/dist/resourcemgr/assetSearch.js +793 -0
- package/dist/resourcemgr/assetSearchContract.js +477 -0
- package/dist/resourcemgr/css/resourcemgr.css +1137 -97
- package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
- package/dist/resourcemgr/fileBrowser.js +456 -79
- package/dist/resourcemgr/filePreview.js +40 -4
- package/dist/resourcemgr/fileSelector.js +362 -92
- package/dist/resourcemgr.js +309 -57
- package/dist/searchModal/advancedSearch.js +233 -31
- package/package.json +15 -6
- package/src/image/ImgStateActive/initHelper.js +69 -31
- package/src/previewer.js +1 -1
- package/src/resourcemgr/assetSearch.js +847 -0
- package/src/resourcemgr/assetSearchContract.js +489 -0
- package/src/resourcemgr/css/resourcemgr.css +1137 -97
- package/src/resourcemgr/css/resourcemgr.css.map +1 -1
- package/src/resourcemgr/fileBrowser.js +470 -91
- package/src/resourcemgr/filePreview.js +46 -4
- package/src/resourcemgr/fileSelector.js +338 -46
- package/src/resourcemgr/scss/resourcemgr.scss +1351 -88
- package/src/resourcemgr/tpl/advanced-search.tpl +10 -0
- package/src/resourcemgr/tpl/fileSelect.tpl +25 -28
- package/src/resourcemgr/tpl/folder.tpl +4 -1
- package/src/resourcemgr/tpl/layout.tpl +113 -54
- package/src/resourcemgr/tpl/rootFolder.tpl +3 -1
- package/src/resourcemgr.js +289 -47
- package/src/searchModal/advancedSearch.js +242 -31
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
define(['exports'], function (exports) { 'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This program is free software; you can redistribute it and/or
|
|
5
|
+
* modify it under the terms of the GNU General Public License
|
|
6
|
+
* as published by the Free Software Foundation; under version 2
|
|
7
|
+
* of the License (non-upgradable).
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with this program; if not, write to the Free Software
|
|
16
|
+
* Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
|
|
17
|
+
*
|
|
18
|
+
* Copyright (c) 2026 (original work) Open Assessment Technologies SA;
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* FE contract for scoped asset search used by Resource Manager (PoC).
|
|
23
|
+
* Align request/response fields with the backend search ticket.
|
|
24
|
+
*
|
|
25
|
+
* @exports ui/resourcemgr/assetSearchContract
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Supported sort fields for the picker search UI.
|
|
30
|
+
* @type {{LABEL: string, LOCATION: string, UPDATED_AT: string}}
|
|
31
|
+
*/
|
|
32
|
+
const SORT_FIELDS = {
|
|
33
|
+
LABEL: 'label',
|
|
34
|
+
LOCATION: 'location',
|
|
35
|
+
UPDATED_AT: 'updatedAt'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default sort: Label ascending (case-insensitive on the service side).
|
|
40
|
+
* @type {{field: string, direction: string}}
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_SORT = {
|
|
43
|
+
field: SORT_FIELDS.LABEL,
|
|
44
|
+
direction: 'asc'
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default page size for search results.
|
|
49
|
+
* @type {number}
|
|
50
|
+
*/
|
|
51
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Minimum universal query length before Search is enabled (matches indexed prefix rules).
|
|
55
|
+
* @type {number}
|
|
56
|
+
*/
|
|
57
|
+
const MIN_TEXT_QUERY_LENGTH = 3;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether a rendered metadata criterion has a usable value.
|
|
61
|
+
* @param {Object} criterion
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
function isMetadataCriterionComplete(criterion) {
|
|
65
|
+
if (!criterion || !criterion.rendered) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if (criterion.type === 'text') {
|
|
69
|
+
return String(criterion.value || '').trim() !== '';
|
|
70
|
+
}
|
|
71
|
+
if (criterion.type === 'list') {
|
|
72
|
+
if (!Array.isArray(criterion.value)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return criterion.value.some(function (entry) {
|
|
76
|
+
return entry !== '' && entry !== null && typeof entry !== 'undefined';
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* True when any rendered metadata card is missing a value.
|
|
84
|
+
* @param {Object} [criteriaState]
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
function hasIncompleteRenderedMetadata(criteriaState) {
|
|
88
|
+
if (!criteriaState || typeof criteriaState !== 'object') {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return Object.keys(criteriaState).some(function (key) {
|
|
92
|
+
const criterion = criteriaState[key];
|
|
93
|
+
return criterion && criterion.rendered && !isMetadataCriterionComplete(criterion);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether Search should be enabled for the current pending input.
|
|
99
|
+
* Unlock when universal query (≥ min length) **or** complete metadata is present (OR).
|
|
100
|
+
* Any rendered filter card without a value blocks until fixed or removed.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} textQuery - universal search field (trimmed by caller)
|
|
103
|
+
* @param {Object} [criteriaState] - advancedSearch.getState()
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
function canSubmitScopedAssetSearch(textQuery, criteriaState) {
|
|
107
|
+
const text = String(textQuery || '').trim();
|
|
108
|
+
if (hasIncompleteRenderedMetadata(criteriaState)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const metadata = buildMetadataFromCriteriaState(criteriaState);
|
|
112
|
+
const hasValidMetadata = Object.keys(metadata).length > 0;
|
|
113
|
+
const hasValidQuery = text.length >= MIN_TEXT_QUERY_LENGTH;
|
|
114
|
+
return hasValidQuery || hasValidMetadata;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build query params for a scoped asset-search request.
|
|
119
|
+
*
|
|
120
|
+
* Request minimum:
|
|
121
|
+
* - path/scope (folder subtree)
|
|
122
|
+
* - query text (universal search only; metadata is separate)
|
|
123
|
+
* - optional metadata map (propertyUri → value)
|
|
124
|
+
* - sort field + direction
|
|
125
|
+
* - page / pageSize
|
|
126
|
+
* - existing picker params (uri, lang, filters) passed through as-is
|
|
127
|
+
*
|
|
128
|
+
* Metadata filters are sent as nested `metadata[propertyUri]=value` (jQuery
|
|
129
|
+
* serializes a plain object). Independently supplied properties combine with AND
|
|
130
|
+
* on the API. Universal `query` and metadata also combine with AND.
|
|
131
|
+
*
|
|
132
|
+
* @param {Object} options
|
|
133
|
+
* @param {string} options.path - folder scope
|
|
134
|
+
* @param {string} options.query - universal search text only
|
|
135
|
+
* @param {Object.<string, string>} [options.metadata] - property URI → value
|
|
136
|
+
* @param {{field: string, direction: string}} options.sort
|
|
137
|
+
* @param {number} options.page - 1-based page
|
|
138
|
+
* @param {number} [options.pageSize]
|
|
139
|
+
* @param {string} [options.pathParam='path']
|
|
140
|
+
* @param {Object} [options.params] - existing RM params (uri, lang, filters, …)
|
|
141
|
+
* @returns {Object}
|
|
142
|
+
*/
|
|
143
|
+
function buildSearchRequestParams(options) {
|
|
144
|
+
const pathParam = options.pathParam || 'path';
|
|
145
|
+
const pageSize = options.pageSize || DEFAULT_PAGE_SIZE;
|
|
146
|
+
const sort = options.sort || DEFAULT_SORT;
|
|
147
|
+
const params = Object.assign({}, options.params || {});
|
|
148
|
+
params[pathParam] = options.path;
|
|
149
|
+
params.query = options.query || '';
|
|
150
|
+
params.sortBy = sort.field;
|
|
151
|
+
params.sortDir = sort.direction;
|
|
152
|
+
params.page = options.page || 1;
|
|
153
|
+
params.pageSize = pageSize;
|
|
154
|
+
const metadata = normalizeMetadataMap(options.metadata);
|
|
155
|
+
if (Object.keys(metadata).length) {
|
|
156
|
+
params.metadata = metadata;
|
|
157
|
+
}
|
|
158
|
+
return params;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build a flat metadata map from advancedSearch.getState().
|
|
163
|
+
* Text criteria use trimmed string values; list criteria use the first
|
|
164
|
+
* non-empty selected value only.
|
|
165
|
+
*
|
|
166
|
+
* Follow-up: multi-value list criteria and LOGIC_AND/OR/NOT require BE contract
|
|
167
|
+
* changes (flat metadata[propertyUri]=value supports AND-only today). See HKD-1996.
|
|
168
|
+
*
|
|
169
|
+
* @param {Object} [criteriaState]
|
|
170
|
+
* @returns {Object.<string, string>}
|
|
171
|
+
*/
|
|
172
|
+
function buildMetadataFromCriteriaState(criteriaState) {
|
|
173
|
+
const metadata = {};
|
|
174
|
+
if (!criteriaState || typeof criteriaState !== 'object') {
|
|
175
|
+
return metadata;
|
|
176
|
+
}
|
|
177
|
+
Object.keys(criteriaState).forEach(function (key) {
|
|
178
|
+
const criterion = criteriaState[key];
|
|
179
|
+
if (!criterion || !criterion.rendered || !criterion.propertyUri) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (criterion.type === 'text') {
|
|
183
|
+
const value = String(criterion.value || '').trim();
|
|
184
|
+
if (value) {
|
|
185
|
+
metadata[criterion.propertyUri] = value;
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (criterion.type === 'list' && Array.isArray(criterion.value)) {
|
|
190
|
+
const first = criterion.value.find(function (entry) {
|
|
191
|
+
return entry !== '' && entry !== null && typeof entry !== 'undefined';
|
|
192
|
+
});
|
|
193
|
+
if (typeof first !== 'undefined' && first !== null && first !== '') {
|
|
194
|
+
metadata[criterion.propertyUri] = String(first);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return metadata;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {Object} [metadata]
|
|
203
|
+
* @returns {Object.<string, string>}
|
|
204
|
+
*/
|
|
205
|
+
function normalizeMetadataMap(metadata) {
|
|
206
|
+
const normalized = {};
|
|
207
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
208
|
+
return normalized;
|
|
209
|
+
}
|
|
210
|
+
Object.keys(metadata).forEach(function (uri) {
|
|
211
|
+
const value = metadata[uri];
|
|
212
|
+
if (typeof value === 'string' && value !== '') {
|
|
213
|
+
normalized[uri] = value;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return normalized;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Normalize a search service payload into a stable picker shape.
|
|
221
|
+
*
|
|
222
|
+
* Response minimum for PoC rows:
|
|
223
|
+
* - identity/uri, label/name, mime/type, location, updatedAt, permissions
|
|
224
|
+
* - total + pageSize for pagination
|
|
225
|
+
* Empty success (total 0 / empty items) is distinct from request failure.
|
|
226
|
+
*
|
|
227
|
+
* Accepts either a wrapped `{ data: … }` response or a flat body, and either
|
|
228
|
+
* `items` or browse-compatible `children`.
|
|
229
|
+
*
|
|
230
|
+
* @param {Object} payload
|
|
231
|
+
* @returns {{items: Array, total: number, page: number, pageSize: number}}
|
|
232
|
+
*/
|
|
233
|
+
function normalizeSearchResponse(payload) {
|
|
234
|
+
const body = unwrapPayload(payload);
|
|
235
|
+
const rawItems = body.items || body.children || [];
|
|
236
|
+
const items = (Array.isArray(rawItems) ? rawItems : []).map(normalizeSearchItem);
|
|
237
|
+
const pageSize = Number(body.pageSize || body.childrenLimit || DEFAULT_PAGE_SIZE);
|
|
238
|
+
const page = Number(body.page || 1);
|
|
239
|
+
const rawTotal = Number(body.total);
|
|
240
|
+
const total = typeof body.total === 'undefined' || body.total === null || !Number.isFinite(rawTotal) ? items.length : rawTotal;
|
|
241
|
+
return {
|
|
242
|
+
items,
|
|
243
|
+
total,
|
|
244
|
+
page: Number.isFinite(page) && page > 0 ? page : 1,
|
|
245
|
+
pageSize: Number.isFinite(pageSize) && pageSize > 0 ? pageSize : DEFAULT_PAGE_SIZE
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Browse (`ItemContent/files`) responses use `children` and ignore `query`.
|
|
251
|
+
* Real search responses expose `items`.
|
|
252
|
+
*
|
|
253
|
+
* @param {Object} payload
|
|
254
|
+
* @returns {boolean}
|
|
255
|
+
*/
|
|
256
|
+
function isBrowseShapedSearchPayload(payload) {
|
|
257
|
+
const body = unwrapPayload(payload);
|
|
258
|
+
return Array.isArray(body.children) && !Array.isArray(body.items);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Local filter/sort/page for browse-shaped payloads (`children`, no `items`).
|
|
263
|
+
* Search endpoints that return `items` are left untouched.
|
|
264
|
+
* MIME eligibility stays server-side (AC: no client-side MIME decisions).
|
|
265
|
+
*
|
|
266
|
+
* @param {Object} normalized - output of normalizeSearchResponse
|
|
267
|
+
* @param {Object} options
|
|
268
|
+
* @param {string} options.query
|
|
269
|
+
* @param {{field: string, direction: string}} [options.sort]
|
|
270
|
+
* @param {number} [options.page]
|
|
271
|
+
* @param {number} [options.pageSize]
|
|
272
|
+
* @returns {{items: Array, total: number, page: number, pageSize: number}}
|
|
273
|
+
*/
|
|
274
|
+
function applyLocalSearchFallback(normalized, options) {
|
|
275
|
+
const metadata = normalizeMetadataMap(options && options.metadata);
|
|
276
|
+
if (Object.keys(metadata).length) {
|
|
277
|
+
const pageSize = Number(options && options.pageSize) > 0 ? Number(options.pageSize) : normalized.pageSize || DEFAULT_PAGE_SIZE;
|
|
278
|
+
return {
|
|
279
|
+
items: [],
|
|
280
|
+
total: 0,
|
|
281
|
+
page: 1,
|
|
282
|
+
pageSize,
|
|
283
|
+
metadataUnsupported: true
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const query = String(options && options.query || '').trim().toLowerCase();
|
|
287
|
+
const sort = options && options.sort || DEFAULT_SORT;
|
|
288
|
+
const pageSize = Number(options && options.pageSize) > 0 ? Number(options.pageSize) : normalized.pageSize || DEFAULT_PAGE_SIZE;
|
|
289
|
+
const page = Number(options && options.page) > 0 ? Number(options.page) : 1;
|
|
290
|
+
let items = (normalized.items || []).filter(isSearchableAsset);
|
|
291
|
+
if (query) {
|
|
292
|
+
const queryTokens = tokenizeSearchText(query);
|
|
293
|
+
if (queryTokens.length === 0) {
|
|
294
|
+
// Delimiter-only query: no searchable tokens → empty (matches BE).
|
|
295
|
+
items = [];
|
|
296
|
+
} else {
|
|
297
|
+
items = items.filter(function (item) {
|
|
298
|
+
return matchesQueryTokens(getSearchableText(item), queryTokens);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
items = sortAssetItems(items, sort);
|
|
303
|
+
const total = items.length;
|
|
304
|
+
const safePageSize = pageSize > 0 ? pageSize : DEFAULT_PAGE_SIZE;
|
|
305
|
+
const maxPage = Math.max(1, Math.ceil(total / safePageSize) || 1);
|
|
306
|
+
const safePage = Math.min(page, maxPage);
|
|
307
|
+
const start = (safePage - 1) * safePageSize;
|
|
308
|
+
return {
|
|
309
|
+
items: items.slice(start, start + safePageSize),
|
|
310
|
+
total,
|
|
311
|
+
page: safePage,
|
|
312
|
+
pageSize: safePageSize
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param {Object} payload
|
|
318
|
+
* @returns {Object}
|
|
319
|
+
*/
|
|
320
|
+
function unwrapPayload(payload) {
|
|
321
|
+
return payload && payload.data && !Array.isArray(payload.data) ? payload.data : payload || {};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* @param {Object} item
|
|
326
|
+
* @returns {Object}
|
|
327
|
+
*/
|
|
328
|
+
function normalizeSearchItem(item) {
|
|
329
|
+
const normalized = Object.assign({}, item);
|
|
330
|
+
if (!normalized.name && normalized.label) {
|
|
331
|
+
normalized.name = normalized.label;
|
|
332
|
+
}
|
|
333
|
+
if (!normalized.label && normalized.name) {
|
|
334
|
+
normalized.label = normalized.name;
|
|
335
|
+
}
|
|
336
|
+
if (!normalized.uri && normalized.id) {
|
|
337
|
+
normalized.uri = normalized.id;
|
|
338
|
+
}
|
|
339
|
+
if (!normalized.updatedAt && normalized.updated_at) {
|
|
340
|
+
normalized.updatedAt = normalized.updated_at;
|
|
341
|
+
}
|
|
342
|
+
if (!normalized.location && normalized.path) {
|
|
343
|
+
normalized.location = normalized.path;
|
|
344
|
+
}
|
|
345
|
+
return normalized;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @param {Object} item
|
|
350
|
+
* @returns {boolean}
|
|
351
|
+
*/
|
|
352
|
+
function isSearchableAsset(item) {
|
|
353
|
+
if (!item) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
if (item.mime || item.type) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
// Directories from browse have nested children / no mime.
|
|
360
|
+
if (Array.isArray(item.children) || item.path && !item.uri && !item.file) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
return Boolean(item.uri || item.file || item.name || item.label);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* @param {Object} item
|
|
368
|
+
* @returns {string}
|
|
369
|
+
*/
|
|
370
|
+
function getSearchableText(item) {
|
|
371
|
+
return [item.label, item.name, item.location, item.path, item.uri, item.file].filter(Boolean).join(' ').toLowerCase();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Split search text into lowercase alphanumeric tokens (aligned with BE
|
|
376
|
+
* AssetSearchBuilder::tokenize).
|
|
377
|
+
*
|
|
378
|
+
* @param {string} value
|
|
379
|
+
* @returns {string[]}
|
|
380
|
+
*/
|
|
381
|
+
function tokenizeSearchText(value) {
|
|
382
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
383
|
+
if (!normalized) {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
return normalized.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Every query token must be a prefix of at least one haystack token (AND).
|
|
391
|
+
*
|
|
392
|
+
* @param {string} haystackText
|
|
393
|
+
* @param {string[]} queryTokens
|
|
394
|
+
* @returns {boolean}
|
|
395
|
+
*/
|
|
396
|
+
function matchesQueryTokens(haystackText, queryTokens) {
|
|
397
|
+
const haystackTokens = tokenizeSearchText(haystackText);
|
|
398
|
+
if (haystackTokens.length === 0) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return queryTokens.every(function (token) {
|
|
402
|
+
return haystackTokens.some(function (candidate) {
|
|
403
|
+
return candidate.indexOf(token) === 0;
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Sort asset rows by the picker contract (label / location / updatedAt).
|
|
410
|
+
* Equal keys fall back to label so the order is stable when a column is tied.
|
|
411
|
+
*
|
|
412
|
+
* @param {Array} items
|
|
413
|
+
* @param {{field: string, direction: string}} [sort]
|
|
414
|
+
* @returns {Array}
|
|
415
|
+
*/
|
|
416
|
+
function sortAssetItems(items, sort) {
|
|
417
|
+
const resolved = Object.assign({}, DEFAULT_SORT, sort || {});
|
|
418
|
+
return (items || []).slice().sort(function (a, b) {
|
|
419
|
+
const primary = compareSortValues(getSortValue(a, resolved.field), getSortValue(b, resolved.field), resolved.direction);
|
|
420
|
+
if (primary !== 0) {
|
|
421
|
+
return primary;
|
|
422
|
+
}
|
|
423
|
+
if (resolved.field === SORT_FIELDS.LABEL) {
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
return compareSortValues(getSortValue(a, SORT_FIELDS.LABEL), getSortValue(b, SORT_FIELDS.LABEL), 'asc');
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @param {string} left
|
|
432
|
+
* @param {string} right
|
|
433
|
+
* @param {string} direction
|
|
434
|
+
* @returns {number}
|
|
435
|
+
*/
|
|
436
|
+
function compareSortValues(left, right, direction) {
|
|
437
|
+
if (left < right) {
|
|
438
|
+
return direction === 'desc' ? 1 : -1;
|
|
439
|
+
}
|
|
440
|
+
if (left > right) {
|
|
441
|
+
return direction === 'desc' ? -1 : 1;
|
|
442
|
+
}
|
|
443
|
+
return 0;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* @param {Object} item
|
|
448
|
+
* @param {string} field
|
|
449
|
+
* @returns {string}
|
|
450
|
+
*/
|
|
451
|
+
function getSortValue(item, field) {
|
|
452
|
+
if (field === SORT_FIELDS.LOCATION) {
|
|
453
|
+
return String(item.location || item.path || '').toLowerCase();
|
|
454
|
+
}
|
|
455
|
+
if (field === SORT_FIELDS.UPDATED_AT) {
|
|
456
|
+
return String(item.updatedAt || item.updated_at || '');
|
|
457
|
+
}
|
|
458
|
+
return String(item.label || item.name || '').toLowerCase();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
exports.DEFAULT_PAGE_SIZE = DEFAULT_PAGE_SIZE;
|
|
462
|
+
exports.DEFAULT_SORT = DEFAULT_SORT;
|
|
463
|
+
exports.MIN_TEXT_QUERY_LENGTH = MIN_TEXT_QUERY_LENGTH;
|
|
464
|
+
exports.SORT_FIELDS = SORT_FIELDS;
|
|
465
|
+
exports.applyLocalSearchFallback = applyLocalSearchFallback;
|
|
466
|
+
exports.buildMetadataFromCriteriaState = buildMetadataFromCriteriaState;
|
|
467
|
+
exports.buildSearchRequestParams = buildSearchRequestParams;
|
|
468
|
+
exports.canSubmitScopedAssetSearch = canSubmitScopedAssetSearch;
|
|
469
|
+
exports.hasIncompleteRenderedMetadata = hasIncompleteRenderedMetadata;
|
|
470
|
+
exports.isBrowseShapedSearchPayload = isBrowseShapedSearchPayload;
|
|
471
|
+
exports.isMetadataCriterionComplete = isMetadataCriterionComplete;
|
|
472
|
+
exports.normalizeSearchResponse = normalizeSearchResponse;
|
|
473
|
+
exports.sortAssetItems = sortAssetItems;
|
|
474
|
+
|
|
475
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
476
|
+
|
|
477
|
+
});
|