apostrophe 4.23.1-alpha.1 → 4.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/.github/workflows/main.yml +4 -4
- package/CHANGELOG.md +23 -2
- package/defaults.js +2 -0
- package/index.js +33 -24
- package/lib/glob.js +4 -2
- package/lib/moog.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbSwitch.vue +1 -1
- package/modules/@apostrophecms/area/ui/apos/logic/AposAreaEditor.js +16 -2
- package/modules/@apostrophecms/asset/index.js +4 -3
- package/modules/@apostrophecms/asset/lib/build/external-module-api.js +8 -5
- package/modules/@apostrophecms/asset/lib/build/internals.js +4 -3
- package/modules/@apostrophecms/asset/lib/build/task.js +4 -2
- package/modules/@apostrophecms/asset/lib/path.js +24 -0
- package/modules/@apostrophecms/asset/lib/webpack/utils.js +1 -1
- package/modules/@apostrophecms/box-field/index.js +157 -0
- package/modules/@apostrophecms/box-field/ui/apos/components/AposInputBox.vue +323 -0
- package/modules/@apostrophecms/box-field/ui/apos/logic/AposInputBox.js +196 -0
- package/modules/@apostrophecms/doc/index.js +134 -7
- package/modules/@apostrophecms/doc/ui/apos/mixins/AposFieldMetaUtilsMixin.js +1 -1
- package/modules/@apostrophecms/doc-type/index.js +1 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +28 -7
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocLocalePicker.vue +45 -17
- package/modules/@apostrophecms/file-widget/index.js +18 -0
- package/modules/@apostrophecms/file-widget/views/widget.html +16 -0
- package/modules/@apostrophecms/i18n/i18n/de.json +29 -2
- package/modules/@apostrophecms/i18n/i18n/en.json +28 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +28 -2
- package/modules/@apostrophecms/i18n/i18n/fr.json +28 -2
- package/modules/@apostrophecms/i18n/i18n/it.json +28 -2
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +28 -2
- package/modules/@apostrophecms/i18n/i18n/sk.json +28 -2
- package/modules/@apostrophecms/i18n/index.js +67 -2
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +75 -50
- package/modules/@apostrophecms/job/index.js +61 -35
- package/modules/@apostrophecms/layout-column-widget/index.js +2 -1
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposAreaLayoutEditor.vue +29 -2
- package/modules/@apostrophecms/migration/index.js +89 -38
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +6 -4
- package/modules/@apostrophecms/notification/index.js +25 -5
- package/modules/@apostrophecms/page/index.js +29 -8
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +1 -1
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +3 -3
- package/modules/@apostrophecms/page-type/index.js +2 -4
- package/modules/@apostrophecms/piece-type/index.js +11 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +5 -5
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +75 -58
- package/modules/@apostrophecms/schema/index.js +1 -1
- package/modules/@apostrophecms/schema/lib/newInstance.js +91 -12
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +138 -37
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +7 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +4 -1
- package/modules/@apostrophecms/soft-redirect/index.js +6 -1
- package/modules/@apostrophecms/task/index.js +1 -1
- package/modules/@apostrophecms/template/index.js +8 -4
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +38 -0
- package/modules/@apostrophecms/ui/ui/apos/stores/notification.js +5 -2
- package/modules/@apostrophecms/util/index.js +13 -2
- package/modules/@apostrophecms/widget-type/ui/apos/composables/AposWidget.js +8 -1
- package/package.json +3 -3
- package/scripts/i18n-ignore.js +9 -0
- package/scripts/lint-i18n.js +10 -4
- package/test/add-missing-schema-fields-project/app.js +4 -0
- package/test/add-missing-schema-fields-project/config.js +13 -0
- package/test/add-missing-schema-fields-project/modules/product/index.js +77 -0
- package/test/add-missing-schema-fields-project/package.json +16 -0
- package/test/add-missing-schema-fields-project/test.js +122 -0
- package/test/add-missing-schema-fields.js +6 -4
- package/test/areas.js +117 -0
- package/test/attachments.js +13 -1
- package/test/bundle.js +1 -1
- package/test/docs.js +662 -2
- package/test/i18n.js +274 -2
- package/test/job.js +329 -131
- package/test/pages.js +208 -83
- package/test/utils.js +85 -6
- package/test-lib/test.js +2 -2
|
@@ -18,21 +18,21 @@ jobs:
|
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
strategy:
|
|
20
20
|
matrix:
|
|
21
|
-
node-version: [20, 22]
|
|
21
|
+
node-version: [20, 22, 24]
|
|
22
22
|
mongodb-version: [6.0, 7.0, 8.0]
|
|
23
23
|
|
|
24
24
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
25
25
|
steps:
|
|
26
26
|
- name: Git checkout
|
|
27
|
-
uses: actions/checkout@
|
|
27
|
+
uses: actions/checkout@v5
|
|
28
28
|
|
|
29
29
|
- name: Use Node.js ${{ matrix.node-version }}
|
|
30
|
-
uses: actions/setup-node@
|
|
30
|
+
uses: actions/setup-node@v6
|
|
31
31
|
with:
|
|
32
32
|
node-version: ${{ matrix.node-version }}
|
|
33
33
|
|
|
34
34
|
- name: Start MongoDB
|
|
35
|
-
uses: supercharge/mongodb-github-action@1.
|
|
35
|
+
uses: supercharge/mongodb-github-action@1.12.0
|
|
36
36
|
with:
|
|
37
37
|
mongodb-version: ${{ matrix.mongodb-version }}
|
|
38
38
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 4.
|
|
3
|
+
## 4.24.1 (2025-12-04)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* Fixes soft-redirect module to decode the URL path before matching historic URLs, ensuring proper matching of accents, Cyrillic, and other non-ASCII characters in old URLs. While this was not a new issue, the fix is of new importance now that a migration path to eliminate accent marks in existing slugs has been introduced in version 4.24.0.
|
|
8
|
+
|
|
9
|
+
## 4.24.0 (2025-11-25)
|
|
4
10
|
|
|
5
11
|
### Adds
|
|
6
12
|
|
|
13
|
+
* Adds `stripUrlAccents` option in `@apostrophecms/i18n` module to globally control whether accents are stripped from URLs. When set to `true`, all URLs (slugs) will have accents from Latin characters removed on document creation and updates. No existing documents are modified automatically; this only affects new or updated documents. A new task `node app @apostrophecms/i18n:strip-slug-accents` is provided to update existing document slugs in the database when needed.
|
|
14
|
+
* Add `@apostrophecms/migration:add-missing-schema-fields` task. This task does not run database migrations.
|
|
7
15
|
* Translation strings added for the layout- and layout-column-widgets.
|
|
16
|
+
* Adds `@apostrophecms/doc:get-apos-doc-id` and `@apostrophecms/doc:set-apos-doc-id` tasks.
|
|
17
|
+
* New `box` schema field type
|
|
18
|
+
* When switching locale from the doc editor, ask if the user wants to localize the current document in the target locale or want to start a blank document.
|
|
19
|
+
* Adds batch failure notifications.
|
|
20
|
+
* Introduced a new `longPolling: false` option for the `@apostrophecms/notification` module. This eliminates long-pending requests when logged in, but also slows down the delivery of notifications. The behavior can be tuned further via the `pollingInterval` option, which defaults to `5000` milliseconds.
|
|
21
|
+
* Add support for `def` in area fields - array of widget names to use as defaults when the area is created.
|
|
8
22
|
|
|
9
23
|
### Changes
|
|
10
24
|
|
|
25
|
+
* `@apostrophecms/migration:requirements` handler now runs the migration requirements like `insertIfMissing`, `implementParkAllInDefaultLocale`, `replicate` and `implementParkAllInOtherLocales`.
|
|
11
26
|
* Bump nodemailer to v7.x.
|
|
27
|
+
* Improves client error log when unable to render a widget.
|
|
28
|
+
* Rich text `styles` are once again available to insert menu items, such as our optional `@apostrophecms/ai-helper` module.
|
|
12
29
|
|
|
13
30
|
### Fixes
|
|
14
31
|
|
|
15
32
|
* Specify the content type when calling back to Astro with JSON to render an area. This is required starting in Astro 4.9.0 and up, otherwise the request is blocked by CSRF protection.
|
|
16
|
-
* Improved support for Node.js on "plain vanilla" Windows, without WSL.
|
|
33
|
+
* Improved support for Node.js on "plain vanilla" Windows, e.g. without WSL. We suggest working with NVM for Windows and Git Bash.
|
|
34
|
+
* Fixes `AposBreadcrumbSwitch` tooltip prop that is supposed to be an object, not a string. Object returned from the shared method `getOperationTooltip`.
|
|
35
|
+
* Empty text nodes are output properly without a warning.
|
|
36
|
+
* Uses `modalData.locale` in `AposI18nLocalize` component. Fixes watcher on `relatedDocTypes` not being properly triggered (uses data and methods for more control instead).
|
|
37
|
+
* Layout area fix when no columns are present.
|
|
17
38
|
|
|
18
39
|
## 4.23.0 (2025-10-30)
|
|
19
40
|
|
package/defaults.js
CHANGED
|
@@ -46,8 +46,10 @@ module.exports = {
|
|
|
46
46
|
'@apostrophecms/rich-text-widget': {},
|
|
47
47
|
'@apostrophecms/html-widget': {},
|
|
48
48
|
'@apostrophecms/color-field': {},
|
|
49
|
+
'@apostrophecms/box-field': {},
|
|
49
50
|
'@apostrophecms/oembed-field': {},
|
|
50
51
|
'@apostrophecms/video-widget': {},
|
|
52
|
+
'@apostrophecms/file-widget': {},
|
|
51
53
|
'@apostrophecms/ui': {},
|
|
52
54
|
'@apostrophecms/user': {},
|
|
53
55
|
'@apostrophecms/settings': {},
|
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const npmResolve = require('resolve');
|
|
|
13
13
|
const glob = require('./lib/glob.js');
|
|
14
14
|
const moogRequire = require('./lib/moog-require');
|
|
15
15
|
const importFresh = require('./lib/import-fresh');
|
|
16
|
+
const { pathToFileURL } = require('node:url');
|
|
16
17
|
let defaults = require('./defaults.js');
|
|
17
18
|
|
|
18
19
|
// ## Top-level options
|
|
@@ -322,21 +323,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
322
323
|
await self.emit('modulesRegistered'); // formerly modulesReady
|
|
323
324
|
self.apos.schema.validateAllSchemas();
|
|
324
325
|
self.apos.schema.registerAllSchemas();
|
|
325
|
-
await self.apos.
|
|
326
|
-
await self.apos.migration.migrate(self.argv);
|
|
327
|
-
// Inserts the global doc in the default locale if it does not exist;
|
|
328
|
-
// same for other singleton piece types registered by other modules
|
|
329
|
-
for (const apostropheModule of Object.values(self.modules)) {
|
|
330
|
-
if (self.instanceOf(apostropheModule, '@apostrophecms/piece-type') && apostropheModule.options.singletonAuto) {
|
|
331
|
-
await apostropheModule.insertIfMissing();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
await self.apos.page.implementParkAllInDefaultLocale();
|
|
335
|
-
await self.apos.doc.replicate(); // emits beforeReplicate and afterReplicate events
|
|
336
|
-
// Replicate will have created the parked pages across locales if needed,
|
|
337
|
-
// but we may still need to reset parked properties
|
|
338
|
-
await self.apos.page.implementParkAllInOtherLocales();
|
|
339
|
-
});
|
|
326
|
+
await self.apos.migration.migrate(self.argv);
|
|
340
327
|
await self.emit('ready'); // formerly afterInit
|
|
341
328
|
|
|
342
329
|
if (self.taskRan) {
|
|
@@ -376,7 +363,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
376
363
|
const reallyLocalPath = self.rootDir + localPath;
|
|
377
364
|
|
|
378
365
|
if (fs.existsSync(reallyLocalPath)) {
|
|
379
|
-
local = await self.root.import(reallyLocalPath);
|
|
366
|
+
local = await self.root.import(pathToFileURL(reallyLocalPath));
|
|
380
367
|
}
|
|
381
368
|
|
|
382
369
|
// Otherwise making a second apos instance
|
|
@@ -408,7 +395,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
408
395
|
const configs = glob(self.localModules + '/**/modules.js', { follow: true });
|
|
409
396
|
for (const config of configs) {
|
|
410
397
|
try {
|
|
411
|
-
_.merge(self.options.modules, await self.root.import(config));
|
|
398
|
+
_.merge(self.options.modules, await self.root.import(pathToFileURL(config)));
|
|
412
399
|
} catch (e) {
|
|
413
400
|
console.error(stripIndent`
|
|
414
401
|
When nestedModuleSubdirs is active, any modules.js file beneath:
|
|
@@ -526,7 +513,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
526
513
|
checkTestModule();
|
|
527
514
|
// Allow tests to be in test/ or in tests/
|
|
528
515
|
const testDir = path.dirname(m.filename);
|
|
529
|
-
const moduleDir = testDir.replace(/\/tests?$/, '');
|
|
516
|
+
const moduleDir = testDir.replace(/\/tests?$/, '').replace(/\\tests?$/, '');
|
|
530
517
|
if (testDir === moduleDir) {
|
|
531
518
|
throw new Error('Test file must be in test/ or tests/ subdirectory of module');
|
|
532
519
|
}
|
|
@@ -540,7 +527,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
540
527
|
|
|
541
528
|
if (!fs.existsSync(testDir + '/node_modules')) {
|
|
542
529
|
fs.mkdirSync(testDir + '/node_modules' + pkgNamespace, { recursive: true });
|
|
543
|
-
fs.symlinkSync(moduleDir, testDir + '/node_modules/' + pkgName, '
|
|
530
|
+
fs.symlinkSync(moduleDir, testDir + '/node_modules/' + pkgName, 'junction');
|
|
544
531
|
}
|
|
545
532
|
// Makes sure we encounter mocha along the way
|
|
546
533
|
// and throws an exception if we don't
|
|
@@ -734,7 +721,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
734
721
|
if (fs.existsSync(path.resolve(self.localModules, name, 'modules.js'))) {
|
|
735
722
|
return;
|
|
736
723
|
}
|
|
737
|
-
const submodule = await self.root.import(path.resolve(self.localModules, name, 'index.js'));
|
|
724
|
+
const submodule = await self.root.import(pathToFileURL(path.resolve(self.localModules, name, 'index.js')));
|
|
738
725
|
if (
|
|
739
726
|
submodule &&
|
|
740
727
|
submodule.options &&
|
|
@@ -914,8 +901,19 @@ function getRoot(options) {
|
|
|
914
901
|
if (root?.filename && root?.require) {
|
|
915
902
|
return {
|
|
916
903
|
filename: root.filename,
|
|
917
|
-
|
|
918
|
-
|
|
904
|
+
async import(id) {
|
|
905
|
+
// Must accept URL objects
|
|
906
|
+
id = id.toString();
|
|
907
|
+
// To accurately simulate ES import, we need to
|
|
908
|
+
// accept file:// URLs like it can
|
|
909
|
+
if (id.startsWith('file:')) {
|
|
910
|
+
id = url.fileURLToPath(id);
|
|
911
|
+
}
|
|
912
|
+
return root.require(id);
|
|
913
|
+
},
|
|
914
|
+
require(id) {
|
|
915
|
+
return root.require(id);
|
|
916
|
+
}
|
|
919
917
|
};
|
|
920
918
|
}
|
|
921
919
|
|
|
@@ -957,8 +955,19 @@ function getRoot(options) {
|
|
|
957
955
|
const legacyRoot = getLegacyRoot();
|
|
958
956
|
return {
|
|
959
957
|
filename: legacyRoot.filename,
|
|
960
|
-
|
|
961
|
-
|
|
958
|
+
async import(id) {
|
|
959
|
+
// Must accept URL objects
|
|
960
|
+
id = id.toString();
|
|
961
|
+
// To accuratesly simulate ES import, we need to
|
|
962
|
+
// accept file:// URLs like it can
|
|
963
|
+
if (id.startsWith('file:')) {
|
|
964
|
+
id = url.fileURLToPath(id);
|
|
965
|
+
}
|
|
966
|
+
return legacyRoot.require(id);
|
|
967
|
+
},
|
|
968
|
+
require(id) {
|
|
969
|
+
return legacyRoot.require(id);
|
|
970
|
+
}
|
|
962
971
|
};
|
|
963
972
|
};
|
|
964
973
|
|
package/lib/glob.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const { globSync } = require('glob');
|
|
2
2
|
|
|
3
3
|
// synchronous glob 10 but with the sorting semantics of glob 8,
|
|
4
|
-
// to ease backwards compatibility in Apostrophe startup logic
|
|
4
|
+
// to ease backwards compatibility in Apostrophe startup logic.
|
|
5
|
+
// Also replaces \ with / for consistency across platforms
|
|
5
6
|
|
|
6
7
|
module.exports = (pattern, options) => {
|
|
7
|
-
|
|
8
|
+
pattern = pattern.replaceAll('\\', '//');
|
|
9
|
+
const result = globSync(pattern, options).map(path => path.replaceAll('\\', '/'));
|
|
8
10
|
if (!options.nosort) {
|
|
9
11
|
result.sort((a, b) => a.localeCompare(b, 'en'));
|
|
10
12
|
}
|
package/lib/moog.js
CHANGED
|
@@ -189,7 +189,7 @@ module.exports = function(options) {
|
|
|
189
189
|
}
|
|
190
190
|
// You can have access to options within a function, if you choose to
|
|
191
191
|
// provide one
|
|
192
|
-
const properties = ((typeof step[cascade]) === 'function') ? step[cascade](that, options) : step[cascade];
|
|
192
|
+
const properties = ((typeof step[cascade]) === 'function') ? await step[cascade](that, options) : step[cascade];
|
|
193
193
|
if (properties) {
|
|
194
194
|
const valid = [ 'add', 'remove', 'order', 'group' ];
|
|
195
195
|
if (properties.add) {
|
|
@@ -626,10 +626,24 @@ export default {
|
|
|
626
626
|
},
|
|
627
627
|
// Return a new widget object in which defaults are fully populated,
|
|
628
628
|
// especially valid sub-area objects, so that nested edits work on the page
|
|
629
|
-
|
|
629
|
+
// Optional schemaOverride array can be provided to override any fields from
|
|
630
|
+
// the base schema. Useful for e.g. per-instance defaults.
|
|
631
|
+
newWidget(type, schemaOverride) {
|
|
630
632
|
const schema = apos.modules[apos.area.widgetManagers[type]].schema;
|
|
633
|
+
const finalSchema = Array.isArray(schemaOverride)
|
|
634
|
+
? schema.map(field => {
|
|
635
|
+
const _field = schemaOverride.find(f => f.name === field.name);
|
|
636
|
+
if (_field) {
|
|
637
|
+
return {
|
|
638
|
+
...field,
|
|
639
|
+
..._field
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return field;
|
|
643
|
+
})
|
|
644
|
+
: schema;
|
|
631
645
|
const widget = {
|
|
632
|
-
...newInstance(
|
|
646
|
+
...newInstance(finalSchema),
|
|
633
647
|
type
|
|
634
648
|
};
|
|
635
649
|
return widget;
|
|
@@ -6,7 +6,7 @@ const { stripIndent } = require('common-tags');
|
|
|
6
6
|
const { createId } = require('@paralleldrive/cuid2');
|
|
7
7
|
const chokidar = require('chokidar');
|
|
8
8
|
const _ = require('lodash');
|
|
9
|
-
const { glob } = require('
|
|
9
|
+
const { glob } = require('./lib/path');
|
|
10
10
|
const globalIcons = require('./lib/globalIcons');
|
|
11
11
|
const {
|
|
12
12
|
checkModulesWebpackConfig,
|
|
@@ -31,7 +31,7 @@ module.exports = {
|
|
|
31
31
|
// If false no UI assets sources will be watched in development.
|
|
32
32
|
// This option has no effect in production (watch disabled).
|
|
33
33
|
watch: true,
|
|
34
|
-
//
|
|
34
|
+
// Milliseconds to wait between asset sources changes before
|
|
35
35
|
// performing a build.
|
|
36
36
|
watchDebounceMs: 1000,
|
|
37
37
|
// Object containing instructions for remapping existing bundles.
|
|
@@ -939,7 +939,8 @@ module.exports = {
|
|
|
939
939
|
const pulledChanges = [];
|
|
940
940
|
let change = changes.pop();
|
|
941
941
|
while (change) {
|
|
942
|
-
|
|
942
|
+
// Fix windows paths
|
|
943
|
+
pulledChanges.push(change.replace(/\\/g, '/'));
|
|
943
944
|
change = changes.pop();
|
|
944
945
|
}
|
|
945
946
|
// No changes - should never happen.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('node:path');
|
|
3
|
-
const { glob } = require('
|
|
3
|
+
const { glob } = require('../../lib/path');
|
|
4
4
|
const { stripIndent } = require('common-tags');
|
|
5
5
|
|
|
6
6
|
// High and Low level public API for external modules.
|
|
@@ -196,7 +196,7 @@ function invoke() {
|
|
|
196
196
|
}` + '\n'
|
|
197
197
|
: '';
|
|
198
198
|
|
|
199
|
-
// Remove the
|
|
199
|
+
// Remove the indentation per line.
|
|
200
200
|
// It may look weird, but the result is nice and formatted import file.
|
|
201
201
|
output += (js && js.invokeCode.trim().split('\n').map(l => l.trim()).join('\n') + '\n') || '';
|
|
202
202
|
|
|
@@ -653,7 +653,9 @@ function invoke() {
|
|
|
653
653
|
}
|
|
654
654
|
}
|
|
655
655
|
}
|
|
656
|
-
|
|
656
|
+
// You would think we should run pathToFileURL over realPath,
|
|
657
|
+
// but that actually breaks both Windows and Linux with Vite. -Tom
|
|
658
|
+
const importPath = JSON.stringify(realPath);
|
|
657
659
|
const name = self.getComponentNameByPath(
|
|
658
660
|
component,
|
|
659
661
|
{ enumerate: options.enumerateImports === true ? i : false }
|
|
@@ -661,8 +663,8 @@ function invoke() {
|
|
|
661
663
|
const jsName = JSON.stringify(name);
|
|
662
664
|
const importName = `${name}${options.importSuffix || ''}`;
|
|
663
665
|
const importCode = options.importName === false
|
|
664
|
-
? `import ${
|
|
665
|
-
: `import ${importName} from ${
|
|
666
|
+
? `import ${importPath};\n`
|
|
667
|
+
: `import ${importName} from ${importPath};\n`;
|
|
666
668
|
|
|
667
669
|
output.importCode += `${importCode}`;
|
|
668
670
|
|
|
@@ -720,6 +722,7 @@ function invoke() {
|
|
|
720
722
|
let importName = importFrom;
|
|
721
723
|
if (!importIndex.includes(importFrom)) {
|
|
722
724
|
if (importFrom.substring(0, 1) === '~') {
|
|
725
|
+
// An npm module name
|
|
723
726
|
importName = self.apos.util.slugify(importFrom).replaceAll('-', '');
|
|
724
727
|
output.importCode += `import ${importName}Icon from '${importFrom.substring(1)}';\n`;
|
|
725
728
|
} else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const util = require('node:util');
|
|
4
|
-
const { glob } = require('
|
|
4
|
+
const { glob } = require('../../lib/path');
|
|
5
5
|
const { getBuildExtensions, fillExtraBundles } = require('./utils');
|
|
6
6
|
|
|
7
7
|
// Internal build interface.
|
|
@@ -127,11 +127,12 @@ module.exports = (self) => {
|
|
|
127
127
|
// Get the component name from a file path. The `enumerate` option allows
|
|
128
128
|
// to append a number to the component name.
|
|
129
129
|
getComponentNameByPath(componentPath, { enumerate } = {}) {
|
|
130
|
-
|
|
130
|
+
const result = path
|
|
131
131
|
.basename(componentPath)
|
|
132
132
|
.replace(/-/g, '_')
|
|
133
133
|
.replace(/\s+/g, '')
|
|
134
134
|
.replace(/\.\w+/, '') + (typeof enumerate === 'number' ? `_${enumerate}` : '');
|
|
135
|
+
return result;
|
|
135
136
|
},
|
|
136
137
|
|
|
137
138
|
// Return the reported by the external module during build dev server URL.
|
|
@@ -201,7 +202,7 @@ module.exports = (self) => {
|
|
|
201
202
|
// return a list of all bundle files.
|
|
202
203
|
//
|
|
203
204
|
// The `bundles` (Set) property added to the entrypoints configuration
|
|
204
|
-
//
|
|
205
|
+
// contains the bundle files used later when injecting the scripts and
|
|
205
206
|
// stylesheets in the browser. The `metadata` is the return value of the
|
|
206
207
|
// external build module build method (see `self.build()` and
|
|
207
208
|
// `configureBuildModule()`).
|
|
@@ -5,6 +5,7 @@ const fs = require('fs-extra');
|
|
|
5
5
|
const { stripIndent } = require('common-tags');
|
|
6
6
|
const webpackModule = require('webpack');
|
|
7
7
|
const { mergeWithCustomize: webpackMerge } = require('webpack-merge');
|
|
8
|
+
const { pathToFileURL } = require('node:url');
|
|
8
9
|
const {
|
|
9
10
|
getBundlesNames,
|
|
10
11
|
writeBundlesImportFiles,
|
|
@@ -675,12 +676,13 @@ module.exports = (self) => ({
|
|
|
675
676
|
`);
|
|
676
677
|
}
|
|
677
678
|
}
|
|
678
|
-
|
|
679
|
+
// We know component is a file path at this point
|
|
680
|
+
const importUrl = JSON.stringify(pathToFileURL(component));
|
|
679
681
|
const name = getComponentName(component, options, i);
|
|
680
682
|
const jsName = JSON.stringify(name);
|
|
681
683
|
const importName = `${name}${options.importSuffix || ''}`;
|
|
682
684
|
const importCode = `
|
|
683
|
-
import ${importName} from ${
|
|
685
|
+
import ${importName} from ${importUrl};
|
|
684
686
|
`;
|
|
685
687
|
|
|
686
688
|
output.paths.push(component);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { glob: globOriginal } = require('glob');
|
|
2
|
+
const {
|
|
3
|
+
resolve: resolveOriginal,
|
|
4
|
+
dirname: dirnameOriginal
|
|
5
|
+
} = require('path');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
glob,
|
|
9
|
+
resolve,
|
|
10
|
+
dirname
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function glob(path, options) {
|
|
14
|
+
const results = await globOriginal(path, options);
|
|
15
|
+
return results.map(result => result.replaceAll('\\', '/'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolve(...args) {
|
|
19
|
+
return resolveOriginal(...args).replaceAll('\\', '/');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function dirname(path) {
|
|
23
|
+
return dirnameOriginal(path).replaceAll('\\', '/');
|
|
24
|
+
}
|
|
@@ -284,7 +284,7 @@ async function verifyBundlesEntryPoints (bundles) {
|
|
|
284
284
|
// already verified it's unique for the given module
|
|
285
285
|
if (!remapping.source) {
|
|
286
286
|
// Bundle name for "main" "doesn't matter - it will be ignored and
|
|
287
|
-
// never built, we want to achieve a free from
|
|
287
|
+
// never built, we want to achieve a free from collision name. What
|
|
288
288
|
// matters is main = true. Target is 'main' by convention:
|
|
289
289
|
// `main.bundleName`
|
|
290
290
|
bundleName = `${remapping.target}.${bundleName}`;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
options: {
|
|
3
|
+
name: 'box',
|
|
4
|
+
alias: 'boxField'
|
|
5
|
+
},
|
|
6
|
+
init(self) {
|
|
7
|
+
self.name = self.options.name;
|
|
8
|
+
self.addFieldType();
|
|
9
|
+
self.enableBrowserData();
|
|
10
|
+
},
|
|
11
|
+
methods(self) {
|
|
12
|
+
return {
|
|
13
|
+
addFieldType() {
|
|
14
|
+
self.apos.schema.addFieldType({
|
|
15
|
+
name: 'box',
|
|
16
|
+
convert(req, field, data, destination) {
|
|
17
|
+
const defProps = [ 'top', 'right', 'bottom', 'left' ];
|
|
18
|
+
const temp = {};
|
|
19
|
+
const min = self.apos.launder.integer(field.min);
|
|
20
|
+
let max = null;
|
|
21
|
+
|
|
22
|
+
if ('max' in field) {
|
|
23
|
+
max = self.apos.launder.integer(field.max);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// All values to numbers or null
|
|
27
|
+
defProps.forEach(side => {
|
|
28
|
+
const int = parseInt(data[field.name][side]);
|
|
29
|
+
if (int || int === 0) {
|
|
30
|
+
temp[side] = int;
|
|
31
|
+
} else {
|
|
32
|
+
temp[side] = null;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// One non-null value if required
|
|
37
|
+
if (field.required) {
|
|
38
|
+
const unique = [ ...new Set(Object.values(temp)) ];
|
|
39
|
+
if (unique.length === 1 && unique[0] === null) {
|
|
40
|
+
throw self.apos.error('required');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Minimum values in range
|
|
45
|
+
for (const key of defProps) {
|
|
46
|
+
if (temp[key] && temp < min) {
|
|
47
|
+
throw self.apos.error(`${key} is below the min ${field.min}, is ${temp[key]}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Maximum values in range
|
|
52
|
+
if (max) {
|
|
53
|
+
for (const key of defProps) {
|
|
54
|
+
if (temp[key] && temp[key] > field.max) {
|
|
55
|
+
throw self.apos.error(`${key} is greater than the max ${field.max}, is ${temp[key]}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Copy values to destination
|
|
61
|
+
destination[field.name] = temp;
|
|
62
|
+
},
|
|
63
|
+
validate(field, options, warn, fail) {
|
|
64
|
+
const defProps = [ 'top', 'right', 'bottom', 'left' ];
|
|
65
|
+
let defMin = 0;
|
|
66
|
+
|
|
67
|
+
if (field.max && typeof field.max !== 'number') {
|
|
68
|
+
fail('Property "max" must be a number');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (field.step && typeof field.step !== 'number') {
|
|
72
|
+
fail('Property "step" must be a number');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (field.def) {
|
|
76
|
+
const fieldDefProps = Object.keys(field.def);
|
|
77
|
+
if (
|
|
78
|
+
!(fieldDefProps.length === defProps.length &&
|
|
79
|
+
fieldDefProps.every(k => defProps.includes(k)))
|
|
80
|
+
) {
|
|
81
|
+
fail('Def must be an object with only "top", "right", "bottom", and "left" keys');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const key in field.def) {
|
|
85
|
+
if (!Number.isFinite(field.def[key])) {
|
|
86
|
+
fail(`Default property "${key}" must be a number`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (field.min) {
|
|
92
|
+
if (typeof field.min !== 'number') {
|
|
93
|
+
fail('Property "min" must be a number');
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
if (field.def) {
|
|
97
|
+
// if def has a negative value, it needs to be the min
|
|
98
|
+
const fieldDefMin = Math.min(...Object.values(field.def));
|
|
99
|
+
if (defMin > fieldDefMin) {
|
|
100
|
+
defMin = fieldDefMin;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
field.min = defMin;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
},
|
|
107
|
+
def: {
|
|
108
|
+
top: null,
|
|
109
|
+
right: null,
|
|
110
|
+
bottom: null,
|
|
111
|
+
left: null
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
getBrowserData(req) {
|
|
116
|
+
return {
|
|
117
|
+
name: self.name,
|
|
118
|
+
action: self.action
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
helpers() {
|
|
124
|
+
return {
|
|
125
|
+
toCss(value, property, unit = 'px') {
|
|
126
|
+
const {
|
|
127
|
+
top, right, bottom, left
|
|
128
|
+
} = value;
|
|
129
|
+
const vals = [ top, right, bottom, left ];
|
|
130
|
+
|
|
131
|
+
if (vals.every(v => v == null)) {
|
|
132
|
+
return '';
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (vals.every(v => v === top && v != null)) {
|
|
136
|
+
return `${property}: ${top}${unit};`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const sides = {
|
|
140
|
+
top,
|
|
141
|
+
right,
|
|
142
|
+
bottom,
|
|
143
|
+
left
|
|
144
|
+
};
|
|
145
|
+
const parts = [];
|
|
146
|
+
|
|
147
|
+
for (const [ side, val ] of Object.entries(sides)) {
|
|
148
|
+
if (val != null) {
|
|
149
|
+
parts.push(`${property}-${side}: ${val}${unit};`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return parts.join(' ');
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
};
|