apostrophe 3.21.0 → 3.21.1-alpha.2022060701
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/CHANGELOG.md +26 -0
- package/modules/@apostrophecms/asset/index.js +28 -5
- package/modules/@apostrophecms/asset/lib/webpack/utils.js +108 -21
- package/modules/@apostrophecms/i18n/i18n/en.json +2 -1
- package/modules/@apostrophecms/login/index.js +3 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +51 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +11 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +14 -2
- package/package.json +3 -2
- package/test/assets.js +157 -12
- package/test/modules/default-page/ui/public/index.css +1 -1
- package/test/modules/default-page/ui/src/index.scss +1 -1
- package/test/package.json +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## UNRELEASED
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Possibility to pass options to webpack extensions from any module.
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
|
|
11
|
+
* Fix a Webpack cache issue leading to modules symlinked in `node_modules` not being rebuilt.
|
|
12
|
+
* Fixes login maximum attempts error message that wasn't showing the plural when lockoutMinutes is more than 1.
|
|
13
|
+
* Fixes the text color of the current array item's slat label in the array editor modal
|
|
14
|
+
* Fixes the maximum width of an array item's slat label so as to not obscure the Remove button in narrow viewports
|
|
15
|
+
|
|
16
|
+
### Changes
|
|
17
|
+
|
|
18
|
+
* If an array field's titleField option is set to a select field, use the selected option's label as the slat label rather it's value.
|
|
19
|
+
* Disable the slat controls of the attachment component while uploading.
|
|
20
|
+
* Fixes bug when re-attaching the same file won't trigger an upload.
|
|
21
|
+
* AposSlat now fully respects the disabled state.
|
|
22
|
+
|
|
23
|
+
## 3.21.1 (2022-06-04)
|
|
24
|
+
|
|
25
|
+
### Fixes
|
|
26
|
+
|
|
27
|
+
* Work around backwards compatibility break in `sass` module by pinning to `sass` `1.50.x` while we investigate. If you saw the error `RangeError: Invalid value: Not in inclusive range 0..145: -1` you can now fix that by upgrading with `npm update`. If it does not immediately clear up the issue in development, try `node app @apostrophecms/asset:clear-cache`.
|
|
28
|
+
|
|
3
29
|
## 3.21.0 (2022-05-25)
|
|
4
30
|
|
|
5
31
|
### Adds
|
|
@@ -49,13 +49,16 @@ module.exports = {
|
|
|
49
49
|
self.configureBuilds();
|
|
50
50
|
self.initUploadfs();
|
|
51
51
|
|
|
52
|
-
const {
|
|
52
|
+
const {
|
|
53
|
+
extensions = {}, extensionOptions = {}, verifiedBundles = {}
|
|
54
|
+
} = await getWebpackExtensions({
|
|
53
55
|
getMetadata: self.apos.synth.getMetadata,
|
|
54
56
|
modulesToInstantiate: self.apos.modulesToBeInstantiated()
|
|
55
57
|
});
|
|
56
58
|
|
|
57
59
|
self.extraBundles = fillExtraBundles(verifiedBundles);
|
|
58
60
|
self.webpackExtensions = extensions;
|
|
61
|
+
self.webpackExtensionOptions = extensionOptions;
|
|
59
62
|
self.verifiedBundles = verifiedBundles;
|
|
60
63
|
self.buildWatcherEnable = process.env.APOS_ASSET_WATCH !== '0' && self.options.watch !== false;
|
|
61
64
|
self.buildWatcherDebounceMs = parseInt(self.options.watchDebounceMs || 1000, 10);
|
|
@@ -125,6 +128,7 @@ module.exports = {
|
|
|
125
128
|
const buildDir = `${self.apos.rootDir}/apos-build/${namespace}`;
|
|
126
129
|
const bundleDir = `${self.apos.rootDir}/public/apos-frontend/${namespace}`;
|
|
127
130
|
const modulesToInstantiate = self.apos.modulesToBeInstantiated();
|
|
131
|
+
const symLinkModules = await findNodeModulesSymlinks(self.apos.npmRootDir);
|
|
128
132
|
// Make it clear if builds should detect changes
|
|
129
133
|
const detectChanges = typeof argv.changes === 'string';
|
|
130
134
|
// Remove invalid changes. `argv.changes` is a comma separated list of relative
|
|
@@ -353,9 +357,16 @@ module.exports = {
|
|
|
353
357
|
? webpackMerge(webpackInstanceConfig, ...Object.values(self.webpackExtensions))
|
|
354
358
|
: webpackInstanceConfig;
|
|
355
359
|
|
|
356
|
-
// Inject the cache location at the end - we need the merged
|
|
357
|
-
const cacheMeta = await computeCacheMeta(name, webpackInstanceConfigMerged);
|
|
360
|
+
// Inject the cache location at the end - we need the merged config
|
|
361
|
+
const cacheMeta = await computeCacheMeta(name, webpackInstanceConfigMerged, symLinkModules);
|
|
358
362
|
webpackInstanceConfigMerged.cache.cacheLocation = cacheMeta.location;
|
|
363
|
+
// Exclude symlinked modules from the cache managedPaths, no other way for now
|
|
364
|
+
// https://github.com/webpack/webpack/issues/12112
|
|
365
|
+
if (cacheMeta.managedPathsRegex) {
|
|
366
|
+
webpackInstanceConfigMerged.snapshot = {
|
|
367
|
+
managedPaths: [ cacheMeta.managedPathsRegex ]
|
|
368
|
+
};
|
|
369
|
+
}
|
|
359
370
|
|
|
360
371
|
const result = await webpack(webpackInstanceConfigMerged);
|
|
361
372
|
await writeCacheMeta(name, cacheMeta);
|
|
@@ -716,7 +727,7 @@ module.exports = {
|
|
|
716
727
|
// but it can be overridden by an APOS_ASSET_CACHE environment.
|
|
717
728
|
// In order to compute an accurate hash, this helper needs
|
|
718
729
|
// the final, merged webpack configuration.
|
|
719
|
-
async function computeCacheMeta(name, webpackConfig) {
|
|
730
|
+
async function computeCacheMeta(name, webpackConfig, symLinkModules) {
|
|
720
731
|
const cacheBase = self.getCacheBasePath();
|
|
721
732
|
|
|
722
733
|
if (!packageLockContentCached) {
|
|
@@ -759,10 +770,22 @@ module.exports = {
|
|
|
759
770
|
);
|
|
760
771
|
const location = path.resolve(cacheBase, hash);
|
|
761
772
|
|
|
773
|
+
// Retrieve symlinkModules and convert them to managedPaths regex rule
|
|
774
|
+
let managedPathsRegex;
|
|
775
|
+
if (symLinkModules.length > 0) {
|
|
776
|
+
const regex = symLinkModules
|
|
777
|
+
.map(m => self.apos.util.regExpQuote(m))
|
|
778
|
+
.join('|');
|
|
779
|
+
managedPathsRegex = new RegExp(
|
|
780
|
+
'^(.+?[\\/]node_modules)[\\/]((?!' + regex + ')).*[\\/]*'
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
|
|
762
784
|
return {
|
|
763
785
|
base: cacheBase,
|
|
764
786
|
hash,
|
|
765
|
-
location
|
|
787
|
+
location,
|
|
788
|
+
managedPathsRegex
|
|
766
789
|
};
|
|
767
790
|
}
|
|
768
791
|
|
|
@@ -3,7 +3,8 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
module.exports = {
|
|
5
5
|
checkModulesWebpackConfig(modules, t) {
|
|
6
|
-
const allowedProperties = [ 'extensions', 'bundles' ];
|
|
6
|
+
const allowedProperties = [ 'extensions', 'extensionOptions', 'bundles' ];
|
|
7
|
+
|
|
7
8
|
for (const mod of Object.values(modules)) {
|
|
8
9
|
const webpackConfig = mod.__meta.webpack[mod.__meta.name];
|
|
9
10
|
|
|
@@ -18,7 +19,8 @@ module.exports = {
|
|
|
18
19
|
Object.keys(webpackConfig).some((prop) => !allowedProperties.includes(prop))
|
|
19
20
|
) {
|
|
20
21
|
const error = t('apostrophe:assetWebpackConfigWarning', {
|
|
21
|
-
module: mod.__meta.name
|
|
22
|
+
module: mod.__meta.name,
|
|
23
|
+
properties: allowedProperties.join(', ')
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
throw new Error(error);
|
|
@@ -51,7 +53,9 @@ module.exports = {
|
|
|
51
53
|
const modulesMeta = modulesToInstantiate
|
|
52
54
|
.map((name) => getMetadata(name));
|
|
53
55
|
|
|
54
|
-
const {
|
|
56
|
+
const {
|
|
57
|
+
extensions, extensionOptions, foundBundles
|
|
58
|
+
} = getModulesWebpackConfigs(
|
|
55
59
|
modulesMeta
|
|
56
60
|
);
|
|
57
61
|
|
|
@@ -59,6 +63,7 @@ module.exports = {
|
|
|
59
63
|
|
|
60
64
|
return {
|
|
61
65
|
extensions,
|
|
66
|
+
extensionOptions,
|
|
62
67
|
verifiedBundles
|
|
63
68
|
};
|
|
64
69
|
},
|
|
@@ -173,42 +178,58 @@ async function findSymlinks(where, sub = '') {
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
function getModulesWebpackConfigs (modulesMeta) {
|
|
176
|
-
const {
|
|
181
|
+
const {
|
|
182
|
+
extensions, extensionOptions, bundles
|
|
183
|
+
} = modulesMeta.reduce((modulesAcc, meta) => {
|
|
177
184
|
const { webpack, __meta } = meta;
|
|
178
185
|
|
|
179
186
|
const configs = formatConfigs(__meta.chain, webpack);
|
|
180
187
|
|
|
181
188
|
if (!configs.length) {
|
|
182
|
-
return
|
|
189
|
+
return modulesAcc;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
|
-
const
|
|
186
|
-
return {
|
|
192
|
+
const reduce = (list, prop) => {
|
|
193
|
+
return list.reduce((acc, cur) => ({
|
|
187
194
|
...acc,
|
|
188
|
-
...
|
|
189
|
-
};
|
|
190
|
-
}
|
|
195
|
+
...cur[prop] || {}
|
|
196
|
+
}), {});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const extensionOptions = configs.reduce((acc, { extensionOptions = {} }) => {
|
|
200
|
+
return [
|
|
201
|
+
...acc,
|
|
202
|
+
extensionOptions
|
|
203
|
+
];
|
|
204
|
+
}, []);
|
|
191
205
|
|
|
192
206
|
return {
|
|
193
207
|
extensions: {
|
|
194
|
-
...
|
|
195
|
-
...
|
|
196
|
-
...acc,
|
|
197
|
-
...config.extensions
|
|
198
|
-
}), {})
|
|
208
|
+
...modulesAcc.extensions,
|
|
209
|
+
...reduce(configs, 'extensions')
|
|
199
210
|
},
|
|
211
|
+
extensionOptions: [
|
|
212
|
+
...modulesAcc.extensionOptions,
|
|
213
|
+
...extensionOptions
|
|
214
|
+
],
|
|
200
215
|
bundles: {
|
|
201
|
-
...
|
|
202
|
-
...
|
|
216
|
+
...modulesAcc.bundles,
|
|
217
|
+
...reduce(configs, 'bundles')
|
|
203
218
|
}
|
|
204
219
|
};
|
|
205
220
|
}, {
|
|
206
221
|
extensions: {},
|
|
222
|
+
extensionOptions: [],
|
|
207
223
|
bundles: {}
|
|
208
224
|
});
|
|
209
225
|
|
|
226
|
+
const formattedOptions = formatExtensionsOptions(extensionOptions);
|
|
227
|
+
|
|
228
|
+
const { exts, options } = fillExtensionsOptions(extensions, formattedOptions);
|
|
229
|
+
|
|
210
230
|
return {
|
|
211
|
-
extensions,
|
|
231
|
+
extensions: exts,
|
|
232
|
+
extensionOptions: options,
|
|
212
233
|
foundBundles: flattenBundles(bundles)
|
|
213
234
|
};
|
|
214
235
|
};
|
|
@@ -223,8 +244,8 @@ async function verifyBundlesEntryPoints (bundles) {
|
|
|
223
244
|
|
|
224
245
|
return {
|
|
225
246
|
bundleName,
|
|
226
|
-
...jsFileExists && { jsPath
|
|
227
|
-
...scssFileExists && { scssPath
|
|
247
|
+
...jsFileExists && { jsPath },
|
|
248
|
+
...scssFileExists && { scssPath }
|
|
228
249
|
};
|
|
229
250
|
});
|
|
230
251
|
|
|
@@ -263,10 +284,13 @@ function formatConfigs (chain, webpackConfigs) {
|
|
|
263
284
|
return null;
|
|
264
285
|
}
|
|
265
286
|
|
|
266
|
-
const {
|
|
287
|
+
const {
|
|
288
|
+
bundles = {}, extensions = {}, extensionOptions = {}
|
|
289
|
+
} = config;
|
|
267
290
|
|
|
268
291
|
return {
|
|
269
292
|
extensions,
|
|
293
|
+
extensionOptions,
|
|
270
294
|
bundles: {
|
|
271
295
|
[name]: {
|
|
272
296
|
bundleNames: Object.keys(bundles),
|
|
@@ -289,3 +313,66 @@ function flattenBundles (bundles) {
|
|
|
289
313
|
];
|
|
290
314
|
}, []);
|
|
291
315
|
}
|
|
316
|
+
|
|
317
|
+
function fillExtensionsOptions (extensions, options) {
|
|
318
|
+
const isObject = (val) => val &&
|
|
319
|
+
typeof val === 'object' && !Array.isArray(val);
|
|
320
|
+
|
|
321
|
+
return Object.entries(extensions).reduce((acc, [ name, config ]) => {
|
|
322
|
+
if (isObject(config)) {
|
|
323
|
+
return {
|
|
324
|
+
...acc,
|
|
325
|
+
exts: {
|
|
326
|
+
...acc.exts,
|
|
327
|
+
[name]: config
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (typeof config !== 'function') {
|
|
333
|
+
return acc;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const computedOptions = computeOptions(options[name] || [], isObject);
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
exts: {
|
|
340
|
+
...acc.exts,
|
|
341
|
+
[name]: config(computedOptions)
|
|
342
|
+
},
|
|
343
|
+
options: {
|
|
344
|
+
...acc.options,
|
|
345
|
+
[name]: computedOptions
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}, {
|
|
349
|
+
exts: {},
|
|
350
|
+
options: {}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
function computeOptions (options, isObject) {
|
|
354
|
+
return options.reduce((acc, option) => {
|
|
355
|
+
if (!isObject(option) && typeof option !== 'function') {
|
|
356
|
+
return acc;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
...acc,
|
|
361
|
+
...isObject(option) ? option : option(acc)
|
|
362
|
+
};
|
|
363
|
+
}, {});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function formatExtensionsOptions (options) {
|
|
368
|
+
return options.reduce(
|
|
369
|
+
(acc, current) => {
|
|
370
|
+
return {
|
|
371
|
+
...acc,
|
|
372
|
+
...Object.fromEntries(Object.entries(current)
|
|
373
|
+
.map(([ ext, option ]) => [ ext, [ option, ...(acc[ext] || []) ] ]))
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
{}
|
|
377
|
+
);
|
|
378
|
+
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"aspectRatioWarning": "The aspect ratio cannot be changed for this widget",
|
|
34
34
|
"assetTypeBuildComplete": "👍 {{ label }} is complete!",
|
|
35
35
|
"assetTypeBuilding": "🧑💻 Building the {{ label }}...",
|
|
36
|
-
"assetWebpackConfigWarning": "⚠️ In the module {{ module }}, your webpack config is incorrect. It must be an object and should contain only
|
|
36
|
+
"assetWebpackConfigWarning": "⚠️ In the module {{ module }}, your webpack config is incorrect. It must be an object and should contain only the properties: {{ properties }}.",
|
|
37
37
|
"assetWebpackBundlesWarning": "⚠️ In the module {{ module }} your webpack config is incorrect. Each bundle can only have one property 'templates' that must be an array of strings.",
|
|
38
38
|
"assetWebpackCacheCleared": "Build cache cleared.",
|
|
39
39
|
"assetBuildWatchStarted": "Watching for UI changes...",
|
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
"errorCount": "{{ count }} error remaining",
|
|
122
122
|
"errorCount_plural": "{{ count }} errors remaining",
|
|
123
123
|
"errorCreatingNewContent": "Error while creating new, empty content.",
|
|
124
|
+
"errorFetchingTitleFieldChoicesByMethod": "An error occurred while fetching the choices for the titleField {{ name }}",
|
|
124
125
|
"errorWhileRestoring": "An error occurred while restoring the previously published version.",
|
|
125
126
|
"errorWhileUnpublishing": "An error occurred while unpublishing the document.",
|
|
126
127
|
"errorWhileArchiving": "An error occurred while moving the document to the archive.",
|
|
@@ -202,7 +202,9 @@ module.exports = {
|
|
|
202
202
|
.checkLoginAttempts(user.username, loginNamespace);
|
|
203
203
|
|
|
204
204
|
if (reached) {
|
|
205
|
-
throw self.apos.error('invalid', req.t('apostrophe:loginMaxAttemptsReached'
|
|
205
|
+
throw self.apos.error('invalid', req.t('apostrophe:loginMaxAttemptsReached', {
|
|
206
|
+
count: self.options.throttle.lockoutMinutes
|
|
207
|
+
}));
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
try {
|
|
@@ -121,6 +121,7 @@ export default {
|
|
|
121
121
|
type: 'overlay',
|
|
122
122
|
showModal: false
|
|
123
123
|
},
|
|
124
|
+
titleFieldChoices: null,
|
|
124
125
|
// If we don't clone, then we're making
|
|
125
126
|
// permanent modifications whether the user
|
|
126
127
|
// clicks save or not
|
|
@@ -210,6 +211,8 @@ export default {
|
|
|
210
211
|
await this.nextTick();
|
|
211
212
|
aposSchema.scrollFieldIntoView(name);
|
|
212
213
|
}
|
|
214
|
+
this.titleFieldChoices = await this.getTitleFieldChoices();
|
|
215
|
+
|
|
213
216
|
},
|
|
214
217
|
methods: {
|
|
215
218
|
async select(_id) {
|
|
@@ -354,7 +357,19 @@ export default {
|
|
|
354
357
|
label(item) {
|
|
355
358
|
let candidate;
|
|
356
359
|
if (this.field.titleField) {
|
|
360
|
+
|
|
361
|
+
// Initial field value
|
|
357
362
|
candidate = get(item, this.field.titleField);
|
|
363
|
+
|
|
364
|
+
// If the titleField references a select input, use the
|
|
365
|
+
// select label as the slat label, rather than the value.
|
|
366
|
+
if (this.titleFieldChoices) {
|
|
367
|
+
const choice = this.titleFieldChoices.find(choice => choice.value === candidate);
|
|
368
|
+
if (choice && choice.label) {
|
|
369
|
+
candidate = choice.label;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
358
373
|
} else if (this.schema.find(field => field.name === 'title') && (item.title !== undefined)) {
|
|
359
374
|
candidate = item.title;
|
|
360
375
|
}
|
|
@@ -374,6 +389,42 @@ export default {
|
|
|
374
389
|
title: this.label(item)
|
|
375
390
|
}));
|
|
376
391
|
return result;
|
|
392
|
+
},
|
|
393
|
+
async getTitleFieldChoices() {
|
|
394
|
+
// If the titleField references a select input, get it's choices
|
|
395
|
+
// to use as labels for the slat UI
|
|
396
|
+
|
|
397
|
+
let choices = null;
|
|
398
|
+
const titleField = this.schema.find(field => field.name === this.field.titleField);
|
|
399
|
+
|
|
400
|
+
// The titleField is a select
|
|
401
|
+
if (titleField?.choices) {
|
|
402
|
+
|
|
403
|
+
// Choices are provided by a method
|
|
404
|
+
if (typeof titleField.choices === 'string') {
|
|
405
|
+
const action = `${this.moduleOptions.action}/choices`;
|
|
406
|
+
try {
|
|
407
|
+
const result = await apos.http.get(
|
|
408
|
+
action,
|
|
409
|
+
{
|
|
410
|
+
qs: {
|
|
411
|
+
fieldId: titleField._id
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
if (result && result.choices) {
|
|
416
|
+
choices = result.choices;
|
|
417
|
+
}
|
|
418
|
+
} catch (e) {
|
|
419
|
+
console.error(this.$t('apostrophe:errorFetchingTitleFieldChoicesByMethod', { name: titleField.name }));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Choices are a normal, hardcoded array
|
|
423
|
+
} else if (Array.isArray(titleField.choices)) {
|
|
424
|
+
choices = titleField.choices;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return choices;
|
|
377
428
|
}
|
|
378
429
|
}
|
|
379
430
|
};
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
</template>
|
|
25
25
|
</p>
|
|
26
26
|
<input
|
|
27
|
+
ref="uploadField"
|
|
27
28
|
type="file"
|
|
28
29
|
class="apos-sr-only"
|
|
29
30
|
:disabled="disabled || fileOrAttachment"
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
<AposSlatList
|
|
36
37
|
:value="[fileOrAttachment]"
|
|
37
38
|
@input="update"
|
|
38
|
-
:disabled="
|
|
39
|
+
:disabled="attachmentDisabled"
|
|
39
40
|
/>
|
|
40
41
|
</div>
|
|
41
42
|
</div>
|
|
@@ -77,10 +78,13 @@ export default {
|
|
|
77
78
|
};
|
|
78
79
|
},
|
|
79
80
|
computed: {
|
|
80
|
-
fileOrAttachment
|
|
81
|
+
fileOrAttachment() {
|
|
81
82
|
return this.selectedFile || this.attachment;
|
|
82
83
|
},
|
|
83
|
-
|
|
84
|
+
attachmentDisabled() {
|
|
85
|
+
return this.uploading || this.readOnly || this.disabled;
|
|
86
|
+
},
|
|
87
|
+
messages() {
|
|
84
88
|
const msgs = {
|
|
85
89
|
primary: 'Drop a file here or',
|
|
86
90
|
highlighted: 'click to open the file explorer'
|
|
@@ -100,6 +104,7 @@ export default {
|
|
|
100
104
|
async uploadFile ({ target, dataTransfer }) {
|
|
101
105
|
this.dragging = false;
|
|
102
106
|
const [ file ] = target.files ? target.files : (dataTransfer.files || []);
|
|
107
|
+
this.resetField();
|
|
103
108
|
|
|
104
109
|
const extension = file.name.split('.').pop();
|
|
105
110
|
const allowedFile = await this.checkFileGroup(`.${extension}`);
|
|
@@ -117,6 +122,9 @@ export default {
|
|
|
117
122
|
|
|
118
123
|
this.$emit('upload-file', file);
|
|
119
124
|
},
|
|
125
|
+
resetField() {
|
|
126
|
+
this.$refs.uploadField.value = null;
|
|
127
|
+
},
|
|
120
128
|
dragHandler (event) {
|
|
121
129
|
event.preventDefault();
|
|
122
130
|
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
:class="{
|
|
9
9
|
'apos-is-engaged': engaged,
|
|
10
10
|
'apos-is-only-child': slatCount === 1,
|
|
11
|
-
'apos-is-selected': selected
|
|
11
|
+
'apos-is-selected': selected,
|
|
12
|
+
'apos-is-disabled': disabled,
|
|
12
13
|
}"
|
|
13
14
|
@keydown.prevent.space="toggleEngage"
|
|
14
15
|
@keydown.prevent.enter="toggleEngage"
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
@item-clicked="$emit('item-clicked', item)"
|
|
34
35
|
menu-placement="bottom-start"
|
|
35
36
|
menu-offset="40, 10"
|
|
37
|
+
disabled="disabled"
|
|
36
38
|
/>
|
|
37
39
|
<AposButton
|
|
38
40
|
class="apos-slat__editor-btn"
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
:icon-only="true"
|
|
47
49
|
:modifiers="['inline']"
|
|
48
50
|
@click="$emit('item-clicked', item)"
|
|
51
|
+
disabled="disabled"
|
|
49
52
|
/>
|
|
50
53
|
<a
|
|
51
54
|
class="apos-slat__control apos-slat__control--view"
|
|
@@ -222,6 +225,12 @@ export default {
|
|
|
222
225
|
color: var(--a-text-primary);
|
|
223
226
|
@include apos-transition();
|
|
224
227
|
|
|
228
|
+
&.apos-is-disabled {
|
|
229
|
+
.apos-slat__control--view {
|
|
230
|
+
pointer-events: none;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
225
234
|
&:hover {
|
|
226
235
|
cursor: grab;
|
|
227
236
|
background-color: var(--a-base-7);
|
|
@@ -262,6 +271,9 @@ export default {
|
|
|
262
271
|
&:hover {
|
|
263
272
|
background-color: var(--a-primary-dark-10);
|
|
264
273
|
}
|
|
274
|
+
.apos-slat__label {
|
|
275
|
+
color: var(--a-white);
|
|
276
|
+
}
|
|
265
277
|
}
|
|
266
278
|
|
|
267
279
|
.apos-slat-list__item--disabled {
|
|
@@ -274,6 +286,7 @@ export default {
|
|
|
274
286
|
.apos-slat__main {
|
|
275
287
|
display: flex;
|
|
276
288
|
align-items: center;
|
|
289
|
+
max-width: 75%;
|
|
277
290
|
& ::v-deep .trigger {
|
|
278
291
|
/* This gets inline positioned and has doesn't provide an extra class to beef up, sorry */
|
|
279
292
|
/* stylelint-disable-next-line declaration-no-important */
|
|
@@ -285,7 +298,6 @@ export default {
|
|
|
285
298
|
@include type-small;
|
|
286
299
|
overflow: hidden;
|
|
287
300
|
margin-left: 5px;
|
|
288
|
-
max-width: 220px;
|
|
289
301
|
white-space: nowrap;
|
|
290
302
|
text-overflow: ellipsis;
|
|
291
303
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "3.21.
|
|
3
|
+
"version": "3.21.1-alpha.2022060701",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"resolve": "^1.19.0",
|
|
104
104
|
"resolve-from": "^5.0.0",
|
|
105
105
|
"sanitize-html": "^2.0.0",
|
|
106
|
-
"sass": "
|
|
106
|
+
"sass": "1.52.1",
|
|
107
107
|
"sass-loader": "^10.1.1",
|
|
108
108
|
"server-destroy": "^1.0.1",
|
|
109
109
|
"sluggo": "^0.3.0",
|
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
"@babel/eslint-parser": "^7.17.0",
|
|
133
133
|
"eslint": "^7.25.0",
|
|
134
134
|
"eslint-config-apostrophe": "^3.4.0",
|
|
135
|
+
"eslint-plugin-n": "^15.2.1",
|
|
135
136
|
"eslint-plugin-node": "^11.1.0",
|
|
136
137
|
"eslint-plugin-vue": "^7.9.0",
|
|
137
138
|
"mocha": "^9.1.2",
|
package/test/assets.js
CHANGED
|
@@ -145,7 +145,9 @@ describe('Assets', function() {
|
|
|
145
145
|
modules
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
const {
|
|
148
|
+
const {
|
|
149
|
+
extensions, verifiedBundles
|
|
150
|
+
} = await getWebpackExtensions({
|
|
149
151
|
name: 'src',
|
|
150
152
|
getMetadata: apos.synth.getMetadata,
|
|
151
153
|
modulesToInstantiate: apos.modulesToBeInstantiated()
|
|
@@ -465,6 +467,7 @@ describe('Assets', function() {
|
|
|
465
467
|
});
|
|
466
468
|
|
|
467
469
|
const { meta, folders } = getCacheMeta();
|
|
470
|
+
|
|
468
471
|
assert.equal(folders.length, 11);
|
|
469
472
|
assert.equal(Object.keys(meta).length, 11);
|
|
470
473
|
assert(!meta['default:apos_3']);
|
|
@@ -594,7 +597,7 @@ describe('Assets', function() {
|
|
|
594
597
|
const assetPathAposJs = path.join(rootPath, 'test/public/apos-frontend/default/apos-module-bundle.js');
|
|
595
598
|
const assetPathAposCss = path.join(rootPath, 'test/public/apos-frontend/default/apos-bundle.css');
|
|
596
599
|
const assetContentJs = 'export default () => {};\n';
|
|
597
|
-
const assetContentScss = '.default-page {color:red}
|
|
600
|
+
const assetContentScss = '.default-page {color:red;}\n';
|
|
598
601
|
// Resurrect the default assets content if test has failed
|
|
599
602
|
fs.writeFileSync(assetPathJs, assetContentJs, 'utf8');
|
|
600
603
|
fs.writeFileSync(assetPathScss, assetContentScss, 'utf8');
|
|
@@ -633,7 +636,7 @@ describe('Assets', function() {
|
|
|
633
636
|
);
|
|
634
637
|
fs.writeFileSync(
|
|
635
638
|
assetPathScss,
|
|
636
|
-
'.default-page-watcher-test-src{color:red}
|
|
639
|
+
'.default-page-watcher-test-src{color:red;}\n',
|
|
637
640
|
'utf8'
|
|
638
641
|
);
|
|
639
642
|
|
|
@@ -719,7 +722,7 @@ describe('Assets', function() {
|
|
|
719
722
|
const assetPathAposJs = path.join(rootPath, 'test/public/apos-frontend/default/apos-module-bundle.js');
|
|
720
723
|
const assetPathAposCss = path.join(rootPath, 'test/public/apos-frontend/default/apos-bundle.css');
|
|
721
724
|
const assetContentJs = 'export default () => {};\n';
|
|
722
|
-
const assetContentScss = '.default-page {color:red}
|
|
725
|
+
const assetContentScss = '.default-page {color:red;}\n';
|
|
723
726
|
// Resurrect the default assets content if test has failed
|
|
724
727
|
fs.writeFileSync(assetPathJs, assetContentJs, 'utf8');
|
|
725
728
|
fs.writeFileSync(assetPathCss, assetContentScss, 'utf8');
|
|
@@ -758,7 +761,7 @@ describe('Assets', function() {
|
|
|
758
761
|
);
|
|
759
762
|
fs.writeFileSync(
|
|
760
763
|
assetPathCss,
|
|
761
|
-
'.default-page-watcher-test-public{color:red}
|
|
764
|
+
'.default-page-watcher-test-public{color:red;}\n',
|
|
762
765
|
'utf8'
|
|
763
766
|
);
|
|
764
767
|
|
|
@@ -934,7 +937,7 @@ describe('Assets', function() {
|
|
|
934
937
|
const assetPathScss = path.join(rootPath, 'test/modules/default-page/ui/src/index.scss');
|
|
935
938
|
const assetPathPublicCss = path.join(rootPath, 'test/public/apos-frontend/default/public-bundle.css');
|
|
936
939
|
const assetPathAposCss = path.join(rootPath, 'test/public/apos-frontend/default/apos-bundle.css');
|
|
937
|
-
const assetContentScss = '.default-page {color:red}
|
|
940
|
+
const assetContentScss = '.default-page {color:red;}\n';
|
|
938
941
|
// Resurrect the default assets content if test has failed
|
|
939
942
|
fs.writeFileSync(assetPathScss, assetContentScss, 'utf8');
|
|
940
943
|
|
|
@@ -987,7 +990,7 @@ describe('Assets', function() {
|
|
|
987
990
|
// * modify assets and recover
|
|
988
991
|
fs.writeFileSync(
|
|
989
992
|
assetPathScss,
|
|
990
|
-
'.default-page-watcher-test-recover{color:red}
|
|
993
|
+
'.default-page-watcher-test-recover{color:red;}\n',
|
|
991
994
|
'utf8'
|
|
992
995
|
);
|
|
993
996
|
|
|
@@ -1060,7 +1063,7 @@ describe('Assets', function() {
|
|
|
1060
1063
|
const assetPathAposJs = path.join(rootPath, 'test/public/apos-frontend/default/apos-module-bundle.js');
|
|
1061
1064
|
const assetPathAposCss = path.join(rootPath, 'test/public/apos-frontend/default/apos-bundle.css');
|
|
1062
1065
|
const assetContentJs = 'export default () => {};\n';
|
|
1063
|
-
const assetContentScss = '.default-page {color:red}
|
|
1066
|
+
const assetContentScss = '.default-page {color:red;}\n';
|
|
1064
1067
|
// Resurrect the default assets content if test has failed
|
|
1065
1068
|
fs.writeFileSync(assetPathJs, assetContentJs, 'utf8');
|
|
1066
1069
|
fs.writeFileSync(assetPathScss, assetContentScss, 'utf8');
|
|
@@ -1099,7 +1102,7 @@ describe('Assets', function() {
|
|
|
1099
1102
|
);
|
|
1100
1103
|
fs.writeFileSync(
|
|
1101
1104
|
assetPathScss,
|
|
1102
|
-
'.default-page-watcher-test-src{color:red}
|
|
1105
|
+
'.default-page-watcher-test-src{color:red;}\n',
|
|
1103
1106
|
'utf8'
|
|
1104
1107
|
);
|
|
1105
1108
|
|
|
@@ -1215,6 +1218,7 @@ describe('Assets', function() {
|
|
|
1215
1218
|
);
|
|
1216
1219
|
await Promise.delay(300);
|
|
1217
1220
|
}
|
|
1221
|
+
|
|
1218
1222
|
await retryAssertTrue(
|
|
1219
1223
|
async () => (await fs.readFile(assetPathPublic, 'utf8')).match(/bundle-page-watcher-test-3/),
|
|
1220
1224
|
'Unable to verify public asset rebuilding by the watcher',
|
|
@@ -1245,8 +1249,8 @@ describe('Assets', function() {
|
|
|
1245
1249
|
10000
|
|
1246
1250
|
);
|
|
1247
1251
|
await retryAssertTrue(
|
|
1248
|
-
() => timesRebuilt ===
|
|
1249
|
-
`Expected to rebuild
|
|
1252
|
+
() => timesRebuilt === 3,
|
|
1253
|
+
`Expected to rebuild 3 times, got ${timesRebuilt}`,
|
|
1250
1254
|
100,
|
|
1251
1255
|
5000
|
|
1252
1256
|
);
|
|
@@ -1257,7 +1261,6 @@ describe('Assets', function() {
|
|
|
1257
1261
|
});
|
|
1258
1262
|
|
|
1259
1263
|
it('should be able to setup the debounce time', async function() {
|
|
1260
|
-
await t.destroy(apos);
|
|
1261
1264
|
|
|
1262
1265
|
apos = await t.create({
|
|
1263
1266
|
root: module,
|
|
@@ -1346,6 +1349,69 @@ describe('Assets', function() {
|
|
|
1346
1349
|
});
|
|
1347
1350
|
assert(!apos.asset.buildWatcher);
|
|
1348
1351
|
process.env.NODE_ENV = 'development';
|
|
1352
|
+
|
|
1353
|
+
await t.destroy(apos);
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
it('should pass the right options to webpack extensions from all modules', async function() {
|
|
1357
|
+
const { extConfig1, extConfig2 } = getWebpackConfigsForExtensionOptions();
|
|
1358
|
+
|
|
1359
|
+
apos = await t.create({
|
|
1360
|
+
root: module,
|
|
1361
|
+
modules: {
|
|
1362
|
+
'test-widget': {
|
|
1363
|
+
extend: '@apostrophecms/widget-type',
|
|
1364
|
+
webpack: extConfig1
|
|
1365
|
+
},
|
|
1366
|
+
test: {
|
|
1367
|
+
extend: '@apostrophecms/piece-type',
|
|
1368
|
+
webpack: extConfig2
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
const {
|
|
1374
|
+
extensions, extensionOptions
|
|
1375
|
+
} = await getWebpackExtensions({
|
|
1376
|
+
name: 'src',
|
|
1377
|
+
getMetadata: apos.synth.getMetadata,
|
|
1378
|
+
modulesToInstantiate: apos.modulesToBeInstantiated()
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
assertWebpackExtensionOptions(extensions, extensionOptions);
|
|
1382
|
+
|
|
1383
|
+
await t.destroy(apos);
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
it('should allow two modules extending each others to pass options to the same webpack extension', async function() {
|
|
1387
|
+
const { extConfig1, extConfig2 } = getWebpackConfigsForExtensionOptions();
|
|
1388
|
+
|
|
1389
|
+
apos = await t.create({
|
|
1390
|
+
root: module,
|
|
1391
|
+
modules: {
|
|
1392
|
+
'test-widget': {
|
|
1393
|
+
extend: '@apostrophecms/widget-type',
|
|
1394
|
+
instantiate: false,
|
|
1395
|
+
webpack: extConfig1
|
|
1396
|
+
},
|
|
1397
|
+
'test-widget-special': {
|
|
1398
|
+
extend: 'test-widget',
|
|
1399
|
+
webpack: extConfig2
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
assert(!apos.modules['test-widget']);
|
|
1405
|
+
|
|
1406
|
+
const {
|
|
1407
|
+
extensions, extensionOptions
|
|
1408
|
+
} = await getWebpackExtensions({
|
|
1409
|
+
name: 'src',
|
|
1410
|
+
getMetadata: apos.synth.getMetadata,
|
|
1411
|
+
modulesToInstantiate: apos.modulesToBeInstantiated()
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
assertWebpackExtensionOptions(extensions, extensionOptions);
|
|
1349
1415
|
});
|
|
1350
1416
|
});
|
|
1351
1417
|
|
|
@@ -1441,3 +1507,82 @@ function loadUtils () {
|
|
|
1441
1507
|
retryAssertTrue
|
|
1442
1508
|
};
|
|
1443
1509
|
}
|
|
1510
|
+
|
|
1511
|
+
function assertWebpackExtensionOptions(extensions, extensionOptions) {
|
|
1512
|
+
assert(extensions.ext1.mode === 'production');
|
|
1513
|
+
assert(extensions.ext1.resolve.alias.testAlias === 'test-path');
|
|
1514
|
+
assert(extensions.ext1.resolve.alias.ext1 === 'ext1-path');
|
|
1515
|
+
|
|
1516
|
+
assert(extensions.ext2.resolve.alias.ext2 === 'ext2-path');
|
|
1517
|
+
assert(extensions.ext2.resolve.alias.newAlias1 === 'new-path1');
|
|
1518
|
+
assert(extensions.ext2.resolve.alias.newAlias2 === 'new-path2');
|
|
1519
|
+
|
|
1520
|
+
assert(extensionOptions.ext1.mode === 'production');
|
|
1521
|
+
assert(extensionOptions.ext1.alias.testAlias === 'test-path');
|
|
1522
|
+
|
|
1523
|
+
assert(extensionOptions.ext2.alias.newAlias1 === 'new-path1');
|
|
1524
|
+
assert(extensionOptions.ext2.alias.newAlias2 === 'new-path2');
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
function getWebpackConfigsForExtensionOptions () {
|
|
1528
|
+
return {
|
|
1529
|
+
extConfig1: {
|
|
1530
|
+
extensions: {
|
|
1531
|
+
ext1 ({ mode, alias = {} }) {
|
|
1532
|
+
return {
|
|
1533
|
+
mode,
|
|
1534
|
+
resolve: {
|
|
1535
|
+
alias: {
|
|
1536
|
+
ext1: 'ext1-path',
|
|
1537
|
+
...alias
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
},
|
|
1542
|
+
ext2 ({ alias = {} }) {
|
|
1543
|
+
return {
|
|
1544
|
+
resolve: {
|
|
1545
|
+
alias: {
|
|
1546
|
+
ext2: 'ext2-path',
|
|
1547
|
+
...alias
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
extensionOptions: {
|
|
1554
|
+
ext1: {
|
|
1555
|
+
mode: 'production'
|
|
1556
|
+
},
|
|
1557
|
+
ext2 (options) {
|
|
1558
|
+
return {
|
|
1559
|
+
alias: {
|
|
1560
|
+
newAlias1: 'new-path1',
|
|
1561
|
+
...options.alias || {}
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
},
|
|
1567
|
+
extConfig2: {
|
|
1568
|
+
extensionOptions: {
|
|
1569
|
+
ext1(options) {
|
|
1570
|
+
return {
|
|
1571
|
+
alias: {
|
|
1572
|
+
...options.alias || {},
|
|
1573
|
+
testAlias: 'test-path'
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
},
|
|
1577
|
+
ext2 (options) {
|
|
1578
|
+
return {
|
|
1579
|
+
alias: {
|
|
1580
|
+
newAlias2: 'new-path2',
|
|
1581
|
+
...options.alias || {}
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.default-page {color:red}
|
|
1
|
+
.default-page {color:red;}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.default-page {color:red}
|
|
1
|
+
.default-page {color:red;}
|