@oat-sa/tao-core-ui 3.23.0 → 3.24.1
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 +1230 -97
- package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
- package/dist/resourcemgr/fileBrowser.js +815 -83
- package/dist/resourcemgr/filePreview.js +82 -7
- package/dist/resourcemgr/fileSelector.js +377 -92
- package/dist/resourcemgr.js +314 -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 +1230 -97
- package/src/resourcemgr/css/resourcemgr.css.map +1 -1
- package/src/resourcemgr/fileBrowser.js +872 -92
- package/src/resourcemgr/filePreview.js +89 -8
- package/src/resourcemgr/fileSelector.js +356 -46
- package/src/resourcemgr/scss/resourcemgr.scss +1450 -89
- 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 +125 -54
- package/src/resourcemgr/tpl/rootFolder.tpl +3 -1
- package/src/resourcemgr.js +294 -47
- package/src/searchModal/advancedSearch.js +242 -31
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This program is free software; you can redistribute it and/or
|
|
3
|
+
* modify it under the terms of the GNU General Public License
|
|
4
|
+
* as published by the Free Software Foundation; under version 2
|
|
5
|
+
* of the License (non-upgradable).
|
|
6
|
+
*
|
|
7
|
+
* This program is distributed in the hope that it will be useful,
|
|
8
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
* GNU General Public License for more details.
|
|
11
|
+
*
|
|
12
|
+
* You should have received a copy of the GNU General Public License
|
|
13
|
+
* along with this program; if not, write to the Free Software
|
|
14
|
+
* Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
|
|
15
|
+
*
|
|
16
|
+
* Copyright (c) 2018-2026 (original work) Open Assessment Technologies SA;
|
|
17
|
+
*/
|
|
1
18
|
import $ from 'jquery';
|
|
2
19
|
import _ from 'lodash';
|
|
3
20
|
import bytes from 'util/bytes';
|
|
@@ -5,6 +22,7 @@ import context from 'context';
|
|
|
5
22
|
import 'ui/previewer';
|
|
6
23
|
|
|
7
24
|
const ns = 'resourcemgr';
|
|
25
|
+
const EMPTY_META = '-';
|
|
8
26
|
|
|
9
27
|
export default function(options) {
|
|
10
28
|
const $container = options.$target;
|
|
@@ -43,16 +61,79 @@ export default function(options) {
|
|
|
43
61
|
$container.trigger(`select.${ns}`, [[data]]);
|
|
44
62
|
});
|
|
45
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @param {*} value
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function metaOrDash(value) {
|
|
69
|
+
if (value === null || typeof value === 'undefined' || value === '') {
|
|
70
|
+
return EMPTY_META;
|
|
71
|
+
}
|
|
72
|
+
return String(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Row markup uses data-download for permission flag (boolean), not the URL.
|
|
77
|
+
* @param {*} file
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
function downloadHref(file) {
|
|
81
|
+
if (typeof file.downloadUrl === 'string' && file.downloadUrl) {
|
|
82
|
+
return file.downloadUrl;
|
|
83
|
+
}
|
|
84
|
+
if (typeof file.url === 'string' && file.url) {
|
|
85
|
+
const query = file.url.split('#')[0].split('?')[1] || '';
|
|
86
|
+
const hasSvgzSupport = query.split('&').some(param => param.split('=')[0] === 'svgzsupport');
|
|
87
|
+
return hasSvgzSupport ? file.url : `${file.url}&svgzsupport=true`;
|
|
88
|
+
}
|
|
89
|
+
if (typeof file.download === 'string' && file.download) {
|
|
90
|
+
return file.download;
|
|
91
|
+
}
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {*} file
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
function downloadFilename(file) {
|
|
100
|
+
if (typeof file.display === 'string' && file.display) {
|
|
101
|
+
return file.display;
|
|
102
|
+
}
|
|
103
|
+
const path = file.file || '';
|
|
104
|
+
const idx = path.lastIndexOf('/');
|
|
105
|
+
return idx >= 0 ? path.slice(idx + 1) : path;
|
|
106
|
+
}
|
|
107
|
+
|
|
46
108
|
function startPreview(file, preview, download, select) {
|
|
47
109
|
if (preview) {
|
|
48
110
|
$previewer.previewer(file);
|
|
49
|
-
|
|
50
|
-
|
|
111
|
+
if (file.type || file.mime) {
|
|
112
|
+
$propType.text(
|
|
113
|
+
file.type && file.mime ? `${file.type} (${file.mime})` : metaOrDash(file.type || file.mime)
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
$propType.text(EMPTY_META);
|
|
117
|
+
}
|
|
118
|
+
$propSize.text(
|
|
119
|
+
file.size === null || typeof file.size === 'undefined' || file.size === ''
|
|
120
|
+
? EMPTY_META
|
|
121
|
+
: bytes.hrSize(file.size)
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
$propType.text(EMPTY_META);
|
|
125
|
+
$propSize.text(EMPTY_META);
|
|
51
126
|
}
|
|
52
|
-
if(download) {
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
$link.
|
|
127
|
+
if (download) {
|
|
128
|
+
const href = downloadHref(file);
|
|
129
|
+
if (href) {
|
|
130
|
+
$link.attr('href', href).attr('download', downloadFilename(file));
|
|
131
|
+
if ($link.hasClass('hidden')) {
|
|
132
|
+
$link.removeClass('hidden');
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
$link.attr('href', '#').removeAttr('download');
|
|
136
|
+
$link.addClass('hidden');
|
|
56
137
|
}
|
|
57
138
|
} else {
|
|
58
139
|
$link.attr('href', '#').attr('download', '#');
|
|
@@ -65,8 +146,8 @@ export default function(options) {
|
|
|
65
146
|
|
|
66
147
|
function stopPreview() {
|
|
67
148
|
$previewer.previewer('update', { url: false });
|
|
68
|
-
$propType.
|
|
69
|
-
$propSize.
|
|
149
|
+
$propType.text(EMPTY_META);
|
|
150
|
+
$propSize.text(EMPTY_META);
|
|
70
151
|
$('a', $propUrl).addClass('hidden');
|
|
71
152
|
$selectButton.attr('disabled', 'disabled');
|
|
72
153
|
}
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* You should have received a copy of the GNU General Public License
|
|
13
13
|
* along with this program; if not, write to the Free Software
|
|
14
|
-
* Foundation, Inc.,
|
|
14
|
+
* Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
|
|
15
15
|
*
|
|
16
|
-
* Copyright (c) 2015-
|
|
16
|
+
* Copyright (c) 2015-2026 (original work) Open Assessment Technologies SA;
|
|
17
17
|
*
|
|
18
18
|
*/
|
|
19
19
|
|
|
@@ -32,9 +32,11 @@ import context from 'context';
|
|
|
32
32
|
import 'ui/uploader';
|
|
33
33
|
import updatePermissions from './util/updatePermissions';
|
|
34
34
|
import loggerFactory from 'core/logger';
|
|
35
|
+
import { DEFAULT_SORT, sortAssetItems } from 'ui/resourcemgr/assetSearchContract';
|
|
35
36
|
|
|
36
37
|
const ns = 'resourcemgr';
|
|
37
38
|
const logger = loggerFactory(`ui/${ns}`);
|
|
39
|
+
const ROW_SELECTOR = '.files-list tr';
|
|
38
40
|
|
|
39
41
|
function shortenPath(path) {
|
|
40
42
|
let tokens = path.replace(/\/$/, '').split('/');
|
|
@@ -47,6 +49,70 @@ function shortenPath(path) {
|
|
|
47
49
|
return title.join('/');
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a catalog folder path for the Location column.
|
|
54
|
+
* Strips URI schemes and media-root labels (Assets/Media); keeps directory path only.
|
|
55
|
+
* @param {String} location
|
|
56
|
+
* @returns {String}
|
|
57
|
+
*/
|
|
58
|
+
function formatLocationDisplay(location) {
|
|
59
|
+
if (location === null || typeof location === 'undefined' || location === '') {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
let path = String(location).trim().replace(/\\/g, '/');
|
|
63
|
+
if (!path) {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Drop URI schemes (taomedia://…, https://…, asset://…)
|
|
68
|
+
path = path.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '');
|
|
69
|
+
// Residual host/path from http(s) after scheme strip
|
|
70
|
+
if (path.indexOf('/') > 0 && /^[^/]+\.[^/]+\//.test(path)) {
|
|
71
|
+
path = path.slice(path.indexOf('/'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const segments = path.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
|
|
75
|
+
if (!segments.length) {
|
|
76
|
+
return '/';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Directory path only: drop trailing filename-like segment
|
|
80
|
+
const last = segments[segments.length - 1];
|
|
81
|
+
if (segments.length > 1 && /\.[a-z0-9]{1,8}$/i.test(last)) {
|
|
82
|
+
segments.pop();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Strip media-tree root label prefix (Assets, Media, …)
|
|
86
|
+
const rootLabels = ['assets', 'media', 'mediamanager'];
|
|
87
|
+
if (segments.length && rootLabels.indexOf(segments[0].toLowerCase()) !== -1) {
|
|
88
|
+
segments.shift();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return segments.length ? `/${segments.join('/')}` : '/';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Format updatedAt as a fixed UTC date-time (YYYY-MM-DD HH:mm).
|
|
96
|
+
* @param {String} value
|
|
97
|
+
* @returns {String}
|
|
98
|
+
*/
|
|
99
|
+
function formatUpdatedAtUtc(value) {
|
|
100
|
+
if (!value) {
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
const date = new Date(value);
|
|
104
|
+
if (Number.isNaN(date.getTime()) || date.getTime() <= 0) {
|
|
105
|
+
return '—';
|
|
106
|
+
}
|
|
107
|
+
const pad = function (n) {
|
|
108
|
+
return String(n).padStart(2, '0');
|
|
109
|
+
};
|
|
110
|
+
return (
|
|
111
|
+
`${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ` +
|
|
112
|
+
`${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
50
116
|
function isTextLarger($element, text) {
|
|
51
117
|
let $dummy = $element
|
|
52
118
|
.clone()
|
|
@@ -70,12 +136,23 @@ export default function (options) {
|
|
|
70
136
|
let disableUpload = options.disableUpload || false;
|
|
71
137
|
let $container = options.$target;
|
|
72
138
|
let $fileSelector = $('.file-selector', $container);
|
|
73
|
-
let $
|
|
139
|
+
let $filesWrapper = $('.files-wrapper', $fileSelector);
|
|
140
|
+
let $filesListLoading = $('.files-list-loading', $filesWrapper);
|
|
141
|
+
let $filesListLoadingStatus = $('.files-list-loading-status', $filesListLoading);
|
|
142
|
+
let $fileContainer = $('.files-list', $fileSelector);
|
|
74
143
|
let $placeholder = $('.empty', $fileSelector);
|
|
75
144
|
let $uploader = $('.file-upload-container', $fileSelector);
|
|
76
145
|
let parentSelector = `#${$container.attr('id')} .file-selector`;
|
|
77
146
|
let $pathTitle = $fileSelector.find('h1 > .title');
|
|
78
|
-
let $browserTitle = $('.file-browser > h1', $container);
|
|
147
|
+
let $browserTitle = $('.file-browser > h1.resources-title', $container);
|
|
148
|
+
if (!$browserTitle.length) {
|
|
149
|
+
$browserTitle = $('.file-browser > h1', $container);
|
|
150
|
+
}
|
|
151
|
+
let searchMode = false;
|
|
152
|
+
let initialSelectionApplied = false;
|
|
153
|
+
let pendingContextSelection = null;
|
|
154
|
+
let sort = Object.assign({}, DEFAULT_SORT);
|
|
155
|
+
let lastFiles = [];
|
|
79
156
|
|
|
80
157
|
//set up the uploader
|
|
81
158
|
if (disableUpload) {
|
|
@@ -84,16 +161,35 @@ export default function (options) {
|
|
|
84
161
|
} else {
|
|
85
162
|
setUpUploader(root);
|
|
86
163
|
}
|
|
164
|
+
|
|
165
|
+
$container.on(`searchmode.${ns}`, function (e, enabled) {
|
|
166
|
+
searchMode = !!enabled;
|
|
167
|
+
$fileSelector.toggleClass('search-mode', searchMode);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
function applyFilesListLoading(loading) {
|
|
171
|
+
$filesWrapper.toggleClass('is-files-loading', !!loading);
|
|
172
|
+
$filesWrapper.attr('aria-busy', loading ? 'true' : 'false');
|
|
173
|
+
$filesListLoading.toggleClass('hidden', !loading).attr('aria-hidden', loading ? 'false' : 'true');
|
|
174
|
+
$filesListLoadingStatus.attr('aria-busy', loading ? 'true' : 'false');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
$container.on(`filesloading.${ns}`, function (e, loading) {
|
|
178
|
+
if (searchMode) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
applyFilesListLoading(loading);
|
|
182
|
+
});
|
|
183
|
+
|
|
87
184
|
//update current folder
|
|
88
185
|
$container.on(`folderselect.${ns}`, function (e, fullPath, data, activePath, content) {
|
|
89
186
|
let files;
|
|
90
187
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
188
|
+
if (searchMode) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
applyFilesListLoading(false);
|
|
97
193
|
|
|
98
194
|
//update title
|
|
99
195
|
if ($container[0].querySelector('.upload')) {
|
|
@@ -108,47 +204,245 @@ export default function (options) {
|
|
|
108
204
|
|
|
109
205
|
//update content here
|
|
110
206
|
if (_.isArray(data)) {
|
|
111
|
-
files = _.filter(data, function (item) {
|
|
207
|
+
files = _.filter(data.map(ensurePermissions), function (item) {
|
|
112
208
|
return !!item.uri;
|
|
113
209
|
}).map(function (file) {
|
|
114
|
-
file
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
210
|
+
return prepareFileForDisplay(file, fullPath, activePath);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
updateFiles(files);
|
|
214
|
+
if (pendingContextSelection) {
|
|
215
|
+
const pending = pendingContextSelection;
|
|
216
|
+
initialSelectionApplied = false;
|
|
217
|
+
applyInitialSelection(pending, false);
|
|
218
|
+
if (initialSelectionApplied) {
|
|
219
|
+
pendingContextSelection = null;
|
|
119
220
|
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
applyInitialSelection();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
120
226
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
227
|
+
// Render scoped search results from the service as-is (no client MIME/auth filtering).
|
|
228
|
+
$container.on(`searchresults.${ns}`, function (e, result) {
|
|
229
|
+
const items = (result && result.items) || [];
|
|
230
|
+
const files = items
|
|
231
|
+
.filter(function (item) {
|
|
232
|
+
return item && !!item.uri;
|
|
233
|
+
})
|
|
234
|
+
.map(function (file) {
|
|
235
|
+
return prepareFileForDisplay(ensureSearchPermissions(file), result.path || '', result.path);
|
|
126
236
|
});
|
|
127
237
|
|
|
128
|
-
|
|
238
|
+
$pathTitle.text(__('Search results'));
|
|
239
|
+
updateFiles(files, {
|
|
240
|
+
emptyMessage: result && result.emptyMessage
|
|
241
|
+
});
|
|
129
242
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
243
|
+
if (!(result && result.error)) {
|
|
244
|
+
applyInitialSelection(result && result.initialSelection);
|
|
133
245
|
}
|
|
134
246
|
});
|
|
135
247
|
|
|
248
|
+
// Reopen resolve (AC3): defer preselect until folderselect loads the resolved parent folder.
|
|
249
|
+
$container.on(`applycontext.${ns}`, function (e, ctx) {
|
|
250
|
+
initialSelectionApplied = false;
|
|
251
|
+
options.initialSelection = ctx && ctx.selection ? ctx.selection : null;
|
|
252
|
+
options.currentAssetItem = ctx && ctx.currentAssetItem ? ctx.currentAssetItem : null;
|
|
253
|
+
pendingContextSelection = options.initialSelection || null;
|
|
254
|
+
$fileContainer.find('tr.active').removeClass('active');
|
|
255
|
+
});
|
|
256
|
+
|
|
136
257
|
function injectTranscriptionMetadata(transcriptionUrl, metadataUri, resourceUri) {
|
|
137
258
|
return `${transcriptionUrl}?metadataUri=${encodeURIComponent(metadataUri)}&resourceUri=${
|
|
138
259
|
resourceUri.replace('taomedia://mediamanager/', '')
|
|
139
260
|
}`;
|
|
140
261
|
}
|
|
141
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Normalize ACL flags unless they are already a boolean permission map.
|
|
265
|
+
* @param {Object} item
|
|
266
|
+
* @returns {Object}
|
|
267
|
+
*/
|
|
268
|
+
function ensurePermissions(item) {
|
|
269
|
+
if (item.permissions && !Array.isArray(item.permissions) && typeof item.permissions === 'object') {
|
|
270
|
+
return item;
|
|
271
|
+
}
|
|
272
|
+
return updatePermissions(item);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Search results often omit ACL (indexed gateway). Missing/empty ACL must stay pickable
|
|
277
|
+
* (read/preview/download) without inventing write/delete/upload. Explicit ACL arrays and
|
|
278
|
+
* boolean maps are preserved / mapped via updatePermissions.
|
|
279
|
+
* @param {Object} item
|
|
280
|
+
* @returns {Object}
|
|
281
|
+
*/
|
|
282
|
+
function ensureSearchPermissions(item) {
|
|
283
|
+
if (!item) {
|
|
284
|
+
return item;
|
|
285
|
+
}
|
|
286
|
+
if (item.permissions && !Array.isArray(item.permissions) && typeof item.permissions === 'object') {
|
|
287
|
+
return item;
|
|
288
|
+
}
|
|
289
|
+
if (!item.permissions || (Array.isArray(item.permissions) && item.permissions.length === 0)) {
|
|
290
|
+
item.permissions = {
|
|
291
|
+
read: true,
|
|
292
|
+
write: false,
|
|
293
|
+
preview: true,
|
|
294
|
+
download: true,
|
|
295
|
+
upload: false,
|
|
296
|
+
delete: false
|
|
297
|
+
};
|
|
298
|
+
return item;
|
|
299
|
+
}
|
|
300
|
+
return updatePermissions(item);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Map a service file record to the selector display model.
|
|
305
|
+
* @param {Object} file
|
|
306
|
+
* @param {String} fullPath
|
|
307
|
+
* @param {String} [folderPath]
|
|
308
|
+
* @returns {Object}
|
|
309
|
+
*/
|
|
310
|
+
function prepareFileForDisplay(file, fullPath, folderPath) {
|
|
311
|
+
file.type = mimeType.getFileType(file);
|
|
312
|
+
if (typeof file.identifier === 'undefined') {
|
|
313
|
+
file.display = `${fullPath}/${file.name}`.replace('//', '/');
|
|
314
|
+
} else {
|
|
315
|
+
file.display = file.identifier + file.name;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
file.viewUrl = `${options.downloadUrl}?${$.param(options.params)}&${
|
|
319
|
+
options.pathParam || 'path'
|
|
320
|
+
}=${encodeURIComponent(file.uri)}`;
|
|
321
|
+
file.downloadUrl = `${file.viewUrl}&svgzsupport=true`;
|
|
322
|
+
if (!file.location) {
|
|
323
|
+
file.location = folderPath || fullPath || '';
|
|
324
|
+
}
|
|
325
|
+
if (!file.updatedAt && file.updated_at) {
|
|
326
|
+
file.updatedAt = file.updated_at;
|
|
327
|
+
}
|
|
328
|
+
file.locationDisplay = formatLocationDisplay(file.location);
|
|
329
|
+
file.updatedAtDisplay = formatUpdatedAtUtc(file.updatedAt);
|
|
330
|
+
return file;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Sync sort classes and aria-sort on table headers.
|
|
335
|
+
*/
|
|
336
|
+
function applySortHeader() {
|
|
337
|
+
$fileSelector.find('.files thead th[data-sort-by]').each(function () {
|
|
338
|
+
const $th = $(this);
|
|
339
|
+
const field = $th.attr('data-sort-by');
|
|
340
|
+
$th.removeClass('sorted sorted_asc sorted_desc');
|
|
341
|
+
if (field === sort.field) {
|
|
342
|
+
$th.addClass('sorted').addClass(sort.direction === 'desc' ? 'sorted_desc' : 'sorted_asc');
|
|
343
|
+
$th.attr('aria-sort', sort.direction === 'desc' ? 'descending' : 'ascending');
|
|
344
|
+
} else {
|
|
345
|
+
$th.attr('aria-sort', 'none');
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Apply a column click to sort state and notify listeners.
|
|
352
|
+
* Skip optimistic re-sort of lastFiles (current page only) — that flashed the
|
|
353
|
+
* wrong order before browse/search refetch; updateFiles still sorts the response.
|
|
354
|
+
* @param {String} field
|
|
355
|
+
*/
|
|
356
|
+
function changeSort(field) {
|
|
357
|
+
if (!field) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (field === sort.field) {
|
|
361
|
+
sort = {
|
|
362
|
+
field,
|
|
363
|
+
direction: sort.direction === 'asc' ? 'desc' : 'asc'
|
|
364
|
+
};
|
|
365
|
+
} else {
|
|
366
|
+
sort = {
|
|
367
|
+
field,
|
|
368
|
+
direction: 'asc'
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
applySortHeader();
|
|
372
|
+
$container.trigger(`sortchange.${ns}`, [Object.assign({}, sort)]);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Preselect initialSelection once when the matching row is present.
|
|
377
|
+
* When resolve returned a currentAssetItem missing from the page, inject it.
|
|
378
|
+
* @param {String} [selection]
|
|
379
|
+
* @param {Boolean} [allowInject=true] - inject currentAssetItem when missing (skip during pending reopen)
|
|
380
|
+
*/
|
|
381
|
+
function applyInitialSelection(selection, allowInject) {
|
|
382
|
+
const target = selection || options.initialSelection;
|
|
383
|
+
if (!target || initialSelectionApplied) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const mayInject = allowInject !== false;
|
|
387
|
+
let $item = $fileContainer.find('tr').filter(function () {
|
|
388
|
+
return $(this).attr('data-file') === String(target);
|
|
389
|
+
});
|
|
390
|
+
if (
|
|
391
|
+
!$item.length
|
|
392
|
+
&& mayInject
|
|
393
|
+
&& options.currentAssetItem
|
|
394
|
+
&& String(options.currentAssetItem.uri) === String(target)
|
|
395
|
+
) {
|
|
396
|
+
const injected = prepareFileForDisplay(
|
|
397
|
+
ensurePermissions(Object.assign({}, options.currentAssetItem)),
|
|
398
|
+
options.currentAssetItem.location || '',
|
|
399
|
+
options.currentAssetItem.location || ''
|
|
400
|
+
);
|
|
401
|
+
updateFiles([injected].concat(lastFiles || []));
|
|
402
|
+
$item = $fileContainer.find('tr').filter(function () {
|
|
403
|
+
return $(this).attr('data-file') === String(target);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if ($item.length) {
|
|
407
|
+
initialSelectionApplied = true;
|
|
408
|
+
$item.first().trigger('click');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
applySortHeader();
|
|
413
|
+
|
|
414
|
+
$(parentSelector)
|
|
415
|
+
.off('click.resourcemgr', '.files thead th[data-sort-by]')
|
|
416
|
+
.on('click.resourcemgr', '.files thead th[data-sort-by]', function (e) {
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
e.stopImmediatePropagation();
|
|
419
|
+
changeSort($(this).attr('data-sort-by'));
|
|
420
|
+
})
|
|
421
|
+
.off('keydown.resourcemgr', '.files thead th[data-sort-by]')
|
|
422
|
+
.on('keydown.resourcemgr', '.files thead th[data-sort-by]', function (e) {
|
|
423
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
424
|
+
e.preventDefault();
|
|
425
|
+
e.stopImmediatePropagation();
|
|
426
|
+
changeSort($(this).attr('data-sort-by'));
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
142
430
|
//listen for file activation
|
|
143
431
|
$(parentSelector)
|
|
144
|
-
.off('click',
|
|
145
|
-
.on('click',
|
|
432
|
+
.off('click', ROW_SELECTOR)
|
|
433
|
+
.on('click', ROW_SELECTOR, function (e) {
|
|
434
|
+
if ($(e.target).closest('.row-actions').length) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if ($(e.target).closest('a.select, a.download, a.delete').length) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
146
440
|
const clickedItem = e.target;
|
|
147
441
|
if (clickedItem.hasAttribute('data-delete') || $(clickedItem).hasClass('icon-bin')) {
|
|
148
442
|
return;
|
|
149
443
|
}
|
|
150
444
|
let $selected = $(this);
|
|
151
|
-
let $files = $(
|
|
445
|
+
let $files = $(ROW_SELECTOR, $fileSelector);
|
|
152
446
|
let data = _.clone($selected.data());
|
|
153
447
|
if (
|
|
154
448
|
options.resourceMetadataUrl
|
|
@@ -168,10 +462,10 @@ export default function (options) {
|
|
|
168
462
|
|
|
169
463
|
//select a file
|
|
170
464
|
$(parentSelector)
|
|
171
|
-
.off('click',
|
|
172
|
-
.on('click',
|
|
465
|
+
.off('click', `${ROW_SELECTOR} a.select`)
|
|
466
|
+
.on('click', `${ROW_SELECTOR} a.select`, function (e) {
|
|
173
467
|
e.preventDefault();
|
|
174
|
-
let data = _.pick($(this).
|
|
468
|
+
let data = _.pick($(this).closest('tr').data(), ['file', 'type', 'mime', 'size', 'alt']);
|
|
175
469
|
if (context.mediaSources && context.mediaSources.length === 0 && data.file.indexOf('local/') > -1) {
|
|
176
470
|
data.file = data.file.substring(6);
|
|
177
471
|
}
|
|
@@ -180,8 +474,8 @@ export default function (options) {
|
|
|
180
474
|
|
|
181
475
|
//delete a file
|
|
182
476
|
$(parentSelector)
|
|
183
|
-
.off('click',
|
|
184
|
-
.on('click',
|
|
477
|
+
.off('click', `${ROW_SELECTOR} a.delete`)
|
|
478
|
+
.on('click', `${ROW_SELECTOR} a.delete`, function (e) {
|
|
185
479
|
// This function replaces ui/deleter and must follow the same logic.
|
|
186
480
|
// The main difference is that it insert a confirmation dialog before deleting the file.
|
|
187
481
|
e.preventDefault();
|
|
@@ -190,7 +484,7 @@ export default function (options) {
|
|
|
190
484
|
return;
|
|
191
485
|
}
|
|
192
486
|
|
|
193
|
-
const $target = $elt.closest('
|
|
487
|
+
const $target = $elt.closest('tr');
|
|
194
488
|
const path = $target.data('file');
|
|
195
489
|
const hooks = [];
|
|
196
490
|
|
|
@@ -219,7 +513,7 @@ export default function (options) {
|
|
|
219
513
|
params = {};
|
|
220
514
|
if (e.namespace === 'deleter' && $target.length) {
|
|
221
515
|
path = $target.data('file');
|
|
222
|
-
params[options.pathParam] = path;
|
|
516
|
+
params[options.pathParam || 'path'] = path;
|
|
223
517
|
$.getJSON(options.deleteUrl, _.merge(params, options.params), function (response) {
|
|
224
518
|
if (response.deleted) {
|
|
225
519
|
$container.trigger(`filedelete.${ns}`, [path]);
|
|
@@ -237,7 +531,9 @@ export default function (options) {
|
|
|
237
531
|
$container.trigger(`filenew.${ns}`, [result, currentPath]);
|
|
238
532
|
});
|
|
239
533
|
$uploader.on('fail.uploader', function (e, file, err) {
|
|
240
|
-
errors.push(
|
|
534
|
+
errors.push(
|
|
535
|
+
__('Unable to upload file %s : %s', _.escape(file && file.name), _.escape(err && err.message))
|
|
536
|
+
);
|
|
241
537
|
});
|
|
242
538
|
|
|
243
539
|
$uploader.on('end.uploader', function () {
|
|
@@ -253,11 +549,11 @@ export default function (options) {
|
|
|
253
549
|
$uploader.uploader({
|
|
254
550
|
upload: true,
|
|
255
551
|
multiple: true,
|
|
256
|
-
uploadUrl: `${options.uploadUrl}?${$.param(options.params)}&${options.pathParam}=${currentPath}`,
|
|
552
|
+
uploadUrl: `${options.uploadUrl}?${$.param(options.params)}&${options.pathParam || 'path'}=${currentPath}`,
|
|
257
553
|
fileSelect: function (files, done) {
|
|
258
554
|
let givenLength = files.length;
|
|
259
555
|
let fileNames = [];
|
|
260
|
-
$fileContainer.find('
|
|
556
|
+
$fileContainer.find('.desc').each(function () {
|
|
261
557
|
fileNames.push($(this).text().toLowerCase());
|
|
262
558
|
});
|
|
263
559
|
|
|
@@ -297,7 +593,7 @@ export default function (options) {
|
|
|
297
593
|
let pathParam = `${currentPath}/${file.name}`;
|
|
298
594
|
pathParam.replace('//', '/');
|
|
299
595
|
$.getJSON(
|
|
300
|
-
`${options.fileExistsUrl}?${$.param(options.params)}&${options.pathParam}=${pathParam}`,
|
|
596
|
+
`${options.fileExistsUrl}?${$.param(options.params)}&${options.pathParam || 'path'}=${pathParam}`,
|
|
301
597
|
function (response) {
|
|
302
598
|
if (response && response.exists === true) {
|
|
303
599
|
//eslint-disable-next-line no-alert
|
|
@@ -324,7 +620,7 @@ export default function (options) {
|
|
|
324
620
|
currentPath = uri;
|
|
325
621
|
$uploader.uploader('options', {
|
|
326
622
|
uploadUrl: `${options.uploadUrl}?${$.param(options.params)}&${
|
|
327
|
-
options.pathParam
|
|
623
|
+
options.pathParam || 'path'
|
|
328
624
|
}=${currentPath}&relPath=${currentPath}`
|
|
329
625
|
});
|
|
330
626
|
});
|
|
@@ -332,14 +628,18 @@ export default function (options) {
|
|
|
332
628
|
function setUploadMode(enableUploadMode) {
|
|
333
629
|
if (!enableUploadMode) {
|
|
334
630
|
isUploadMode = false;
|
|
631
|
+
$fileSelector.removeClass('upload-mode');
|
|
335
632
|
$uploader.hide();
|
|
633
|
+
$filesWrapper.show();
|
|
336
634
|
$fileContainer.show();
|
|
337
635
|
// Note: show() would display as inline, not inline-block!
|
|
338
636
|
$switcher.filter('.upload').css({ display: 'inline-block' });
|
|
339
637
|
$switcher.filter('.listing').hide();
|
|
340
|
-
$browserTitle.text(__('
|
|
638
|
+
$browserTitle.text(__('Resources'));
|
|
341
639
|
} else {
|
|
342
640
|
isUploadMode = true;
|
|
641
|
+
$fileSelector.addClass('upload-mode');
|
|
642
|
+
$filesWrapper.hide();
|
|
343
643
|
$fileContainer.hide();
|
|
344
644
|
$placeholder.hide();
|
|
345
645
|
$uploader.show();
|
|
@@ -363,17 +663,27 @@ export default function (options) {
|
|
|
363
663
|
setUploadMode(isUploadMode);
|
|
364
664
|
}
|
|
365
665
|
|
|
366
|
-
function updateFiles(
|
|
666
|
+
function updateFiles(files, updateOptions) {
|
|
667
|
+
const displayOptions = updateOptions || {};
|
|
668
|
+
const emptyLabel = displayOptions.emptyMessage || __('No files');
|
|
669
|
+
lastFiles = Array.isArray(files) ? files.slice() : [];
|
|
670
|
+
const sorted = sortAssetItems(lastFiles, sort);
|
|
367
671
|
$fileContainer.empty();
|
|
368
|
-
if (
|
|
672
|
+
if (sorted.length) {
|
|
369
673
|
$placeholder.hide();
|
|
674
|
+
$filesWrapper.show();
|
|
370
675
|
$fileContainer.append(
|
|
371
676
|
fileSelectTpl({
|
|
372
|
-
files:
|
|
677
|
+
files: sorted
|
|
373
678
|
})
|
|
374
679
|
);
|
|
375
|
-
} else if ($
|
|
376
|
-
$
|
|
680
|
+
} else if ($fileSelector.find('.asset-search-error:not([hidden])').length === 0) {
|
|
681
|
+
$filesWrapper.hide();
|
|
682
|
+
// Stylesheet defaults `.empty` to display:none; force visible for empty states.
|
|
683
|
+
$placeholder.text(emptyLabel).css('display', 'block');
|
|
684
|
+
} else {
|
|
685
|
+
$filesWrapper.hide();
|
|
686
|
+
$placeholder.hide();
|
|
377
687
|
}
|
|
378
688
|
}
|
|
379
689
|
}
|