apostrophe 3.48.0 → 3.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -2
- package/index.js +20 -2
- package/lib/locales.js +1 -1
- package/lib/moog-require.js +3 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +12 -2
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +2 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +7 -24
- package/modules/@apostrophecms/asset/index.js +27 -2
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +23 -2
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +26 -2
- package/modules/@apostrophecms/doc/index.js +149 -0
- package/modules/@apostrophecms/doc-type/index.js +9 -1
- package/modules/@apostrophecms/global/index.js +4 -15
- package/modules/@apostrophecms/i18n/i18n/en.json +3 -2
- package/modules/@apostrophecms/i18n/index.js +76 -61
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +14 -1
- package/modules/@apostrophecms/login/ui/apos/components/AposForgotPasswordForm.vue +3 -60
- package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +3 -231
- package/modules/@apostrophecms/login/ui/apos/components/AposResetPasswordForm.vue +3 -96
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +2 -99
- package/modules/@apostrophecms/login/ui/apos/logic/AposForgotPasswordForm.js +68 -0
- package/modules/@apostrophecms/login/ui/apos/logic/AposLoginForm.js +239 -0
- package/modules/@apostrophecms/login/ui/apos/logic/AposResetPasswordForm.js +105 -0
- package/modules/@apostrophecms/login/ui/apos/logic/TheAposLogin.js +107 -0
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +9 -3
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalToolbar.vue +1 -0
- package/modules/@apostrophecms/page/index.js +63 -1
- package/modules/@apostrophecms/piece-type/index.js +57 -9
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +11 -8
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +226 -72
- package/modules/@apostrophecms/schema/index.js +0 -1
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +35 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +21 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +12 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposCombo.vue +178 -20
- package/modules/@apostrophecms/ui/ui/apos/components/AposFilterMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposPager.vue +4 -6
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_theme_mixins.scss +1 -0
- package/modules/@apostrophecms/util/index.js +5 -6
- package/modules/@apostrophecms/util/ui/src/http.js +6 -3
- package/package.json +20 -3
- package/test/change-doc-ids.js +134 -0
- package/test/i18n.js +310 -0
- package/test/static-i18n.js +0 -105
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.49.0 (2023-06-08)
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
* Updates area UX to not display Add Content controls when a widget is focused.
|
|
7
|
+
* Updates area UX to unfocus widget on esc key.
|
|
8
|
+
* Updates widget UI to use dashed outlines instead of borders to indicate bounds.
|
|
9
|
+
* Updates UI for Insert Menu.
|
|
10
|
+
* Updates Insert Menu UX to allow mid-node insertion.
|
|
11
|
+
* Rich Text Widget's Insert components are now expected to emit `done` and `cancel` for proper RT cleanup. `close` still supported for BC, acts as `done`.
|
|
12
|
+
* Migrated the business logic of the login-related Vue components to external mixins, so that the templates and styles can be overridden by
|
|
13
|
+
copying the component `.vue` file to project level without copying all of the business logic. If you have already copied the components to style them,
|
|
14
|
+
we encourage you to consider replacing your `script` tag with the new version, which just imports the mixin, so that fixes we make there will be
|
|
15
|
+
available in your project.
|
|
16
|
+
|
|
17
|
+
### Adds
|
|
18
|
+
* Adds keyboard accessibility to Insert menu.
|
|
19
|
+
* Adds regex pattern feature for string fields.
|
|
20
|
+
* Adds `pnpm` support. Introduces new optional Apostrophe root configuration `pnpm` to force opt-in/out when auto detection fails. See the [documentation](https://v3.docs.apostrophecms.org/guide/using-pnpm.html) for more details.
|
|
21
|
+
* Adds a warning if database queries involving relationships
|
|
22
|
+
are made before the last `apostrophe:modulesRegistered` handler has fired.
|
|
23
|
+
If you need to call Apostrophe's `find()` methods at startup,
|
|
24
|
+
it is best to wait for the `@apostrophecms/doc:beforeReplicate` event.
|
|
25
|
+
* Allow `@` when a piece is a template and `/@` for page templates (doc-template-library module).
|
|
26
|
+
* Adds a `prefix` option to the http frontend util module.
|
|
27
|
+
If explicitly set to `false`, prevents the prefix from being automatically added to the URL,
|
|
28
|
+
when making calls with already-prefixed URLs for instance.
|
|
29
|
+
* Adds the `redirectToFirstLocale` option to the `i18n` module to prevent users from reaching a version of their site that would not match any locale when requesting the site without a locale prefix in the URL.
|
|
30
|
+
* If just one instance of a piece type should always exist (per locale if localized), the
|
|
31
|
+
`singletonAuto` option may now be set to `true` or to an object with a `slug` option in
|
|
32
|
+
order to guarantee it. This implicitly sets `singleton: true` as well. This is now used
|
|
33
|
+
internally by `@apostrophecms/global` as well as the optional `@apostrophecms-pro/palette` module.
|
|
34
|
+
|
|
35
|
+
### Fixes
|
|
36
|
+
* Fix 404 error when viewing/editing a doc which draft has a different version of the slug than the published one.
|
|
37
|
+
* Fixed a bug where multiple home pages can potentially be inserted into the database if the
|
|
38
|
+
default locale is renamed. Introduced the `async apos.doc.bestAposDocId(criteria)` method to
|
|
39
|
+
help identify the right `aposDocId` when inserting a document that might exist in
|
|
40
|
+
other locales.
|
|
41
|
+
* Fixed a bug where singletons like the global doc might not be inserted at all if they
|
|
42
|
+
exist under the former name of the default locale and there are no other locales.
|
|
43
|
+
|
|
3
44
|
## 3.48.0 (2023-05-26)
|
|
4
45
|
|
|
5
46
|
### Adds
|
|
@@ -105,10 +146,10 @@ shouldn't close the link dialog etc.
|
|
|
105
146
|
|
|
106
147
|
### Fixes
|
|
107
148
|
|
|
108
|
-
* Fix various issues on conditional fields that were occurring when adding new widgets with default values or selecting a falsy value in a field that has a conditional field relying on it.
|
|
149
|
+
* Fix various issues on conditional fields that were occurring when adding new widgets with default values or selecting a falsy value in a field that has a conditional field relying on it.
|
|
109
150
|
Populate new or existing doc instances with default values and add an empty `null` choice to select fields that do not have a default value (required or not) and to the ones configured with dynamic choices.
|
|
110
151
|
* Rich text widgets save more reliably when many actions are taken quickly just before save.
|
|
111
|
-
* Fix an issue in the `oembed` field where the value was kept in memory after cancelling the widget editor, which resulted in saving the value if the widget was nested and the parent widget was saved.
|
|
152
|
+
* Fix an issue in the `oembed` field where the value was kept in memory after cancelling the widget editor, which resulted in saving the value if the widget was nested and the parent widget was saved.
|
|
112
153
|
Also improve the `oembed` field UX by setting the input as `readonly` rather than `disabled` when fetching the video metadata, in order to avoid losing its focus when typing.
|
|
113
154
|
|
|
114
155
|
## 3.44.0 (2023-04-13)
|
package/index.js
CHANGED
|
@@ -55,6 +55,13 @@ let defaults = require('./defaults.js');
|
|
|
55
55
|
// If set, Apostrophe will invoke it (await) before invoking process.exit.
|
|
56
56
|
// `beforeExit` may be an async function, will be awaited, and takes no arguments.
|
|
57
57
|
//
|
|
58
|
+
// `pnpm`
|
|
59
|
+
// A boolean to force on or off the pnpm related build routines. If not set,
|
|
60
|
+
// an automated check will be performed to determine if pnpm is in use. We offer
|
|
61
|
+
// an option, because automated check is not 100% reliable. Monorepo tools are
|
|
62
|
+
// often hiding package management specifics (lock files, node_module structure, etc.)
|
|
63
|
+
// in a centralized store.
|
|
64
|
+
//
|
|
58
65
|
// ## Awaiting the Apostrophe function
|
|
59
66
|
//
|
|
60
67
|
// The apos function is async, but in typical cases you do not
|
|
@@ -222,6 +229,11 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
222
229
|
self.root = options.root || getRoot();
|
|
223
230
|
self.rootDir = options.rootDir || path.dirname(self.root.filename);
|
|
224
231
|
self.npmRootDir = options.npmRootDir || self.rootDir;
|
|
232
|
+
self.selfDir = __dirname;
|
|
233
|
+
// Signals to various (build related) places that we are running a pnpm installation.
|
|
234
|
+
// The relevant option, if set, has a higher precedence over the automated check.
|
|
235
|
+
self.isPnpm = options.pnpm ??
|
|
236
|
+
fs.existsSync(path.join(self.npmRootDir, 'pnpm-lock.yaml'));
|
|
225
237
|
|
|
226
238
|
testModule();
|
|
227
239
|
|
|
@@ -290,7 +302,13 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
290
302
|
self.apos.schema.registerAllSchemas();
|
|
291
303
|
await self.apos.lock.withLock('@apostrophecms/migration:migrate', async () => {
|
|
292
304
|
await self.apos.migration.migrate(); // emits before and after events, inside the lock
|
|
293
|
-
|
|
305
|
+
// Inserts the global doc in the default locale if it does not exist; same for other
|
|
306
|
+
// singleton piece types registered by other modules
|
|
307
|
+
for (const module of Object.values(self.modules)) {
|
|
308
|
+
if (self.instanceOf(module, '@apostrophecms/piece-type') && module.options.singletonAuto) {
|
|
309
|
+
await module.insertIfMissing();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
294
312
|
await self.apos.page.implementParkAllInDefaultLocale();
|
|
295
313
|
await self.apos.doc.replicate(); // emits beforeReplicate and afterReplicate events
|
|
296
314
|
// Replicate will have created the parked pages across locales if needed, but we may
|
|
@@ -632,7 +650,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
632
650
|
`
|
|
633
651
|
);
|
|
634
652
|
} else {
|
|
635
|
-
warn('orphan-modules', `You have a ${self.localModules}/${name} folder, but that module is not activated in app.js
|
|
653
|
+
warn('orphan-modules', `You have a ${self.localModules}/${name} folder, but that module is not activated in app.js\nand it is not a base class of any other active module. Right now that code doesn't do anything.`);
|
|
636
654
|
}
|
|
637
655
|
}
|
|
638
656
|
function warn(name, message) {
|
package/lib/locales.js
CHANGED
|
@@ -32,7 +32,7 @@ module.exports = {
|
|
|
32
32
|
) {
|
|
33
33
|
throw new Error(stripIndent`
|
|
34
34
|
If some of your locales have hostnames, then they all must have
|
|
35
|
-
hostnames,
|
|
35
|
+
hostnames, and your top-level baseUrl option must be set.
|
|
36
36
|
|
|
37
37
|
In development, you can set baseUrl to http://localhost:3000
|
|
38
38
|
for testing purposes. In production it should always be set
|
package/lib/moog-require.js
CHANGED
|
@@ -95,6 +95,7 @@ module.exports = function(options) {
|
|
|
95
95
|
npmDefinition = importFresh(npmPath);
|
|
96
96
|
npmDefinition.__meta = {
|
|
97
97
|
npm: true,
|
|
98
|
+
bundled: _.has(self.bundled, type),
|
|
98
99
|
dirname: path.dirname(npmPath),
|
|
99
100
|
filename: npmPath,
|
|
100
101
|
name: type
|
|
@@ -165,6 +166,8 @@ module.exports = function(options) {
|
|
|
165
166
|
// multiple references to my-foo which is ambiguous
|
|
166
167
|
result.__meta.name = self.originalToMy(originalType);
|
|
167
168
|
}
|
|
169
|
+
// Mark "my" modules as such
|
|
170
|
+
result.__meta.my = self.isMy(result.__meta.name);
|
|
168
171
|
return result;
|
|
169
172
|
};
|
|
170
173
|
|
|
@@ -533,14 +533,24 @@ export default {
|
|
|
533
533
|
aposEdit: '1'
|
|
534
534
|
} : {})
|
|
535
535
|
};
|
|
536
|
-
|
|
536
|
+
|
|
537
|
+
const { action } = window.apos.modules[this.context.type];
|
|
538
|
+
const doc = await apos.http.get(`${action}/${this.context.aposDocId}`, {
|
|
539
|
+
qs: {
|
|
540
|
+
aposMode: this.draftMode,
|
|
541
|
+
project: { _url: 1 }
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const url = apos.http.addQueryToUrl(doc._url, qs);
|
|
537
546
|
const content = await apos.http.get(url, {
|
|
538
547
|
qs,
|
|
539
548
|
headers: {
|
|
540
549
|
'Cache-Control': 'no-cache'
|
|
541
550
|
},
|
|
542
551
|
draft: true,
|
|
543
|
-
busy: true
|
|
552
|
+
busy: true,
|
|
553
|
+
prefix: false
|
|
544
554
|
});
|
|
545
555
|
|
|
546
556
|
refreshable.innerHTML = content;
|
|
@@ -249,6 +249,8 @@ export default {
|
|
|
249
249
|
},
|
|
250
250
|
updateWidgetFocused(widgetId) {
|
|
251
251
|
this.focusedWidget = widgetId;
|
|
252
|
+
// Attached to window so that modals can see the area is active
|
|
253
|
+
window.apos.focusedWidget = widgetId;
|
|
252
254
|
},
|
|
253
255
|
async up(i) {
|
|
254
256
|
if (this.docId === window.apos.adminBar.contextId) {
|
|
@@ -530,33 +530,16 @@ export default {
|
|
|
530
530
|
.apos-area-widget-inner {
|
|
531
531
|
position: relative;
|
|
532
532
|
min-height: 50px;
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
left: 0;
|
|
537
|
-
width: 100%;
|
|
538
|
-
height: 1px;
|
|
539
|
-
border-top: 1px dashed var(--a-primary);
|
|
540
|
-
opacity: 0;
|
|
541
|
-
transition: opacity 0.2s ease;
|
|
542
|
-
pointer-events: none;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
&:before {
|
|
546
|
-
top: 0;
|
|
547
|
-
}
|
|
548
|
-
&:after {
|
|
549
|
-
bottom: 0;
|
|
550
|
-
}
|
|
533
|
+
border-radius: var(--a-border-radius);
|
|
534
|
+
outline: 1px solid transparent;
|
|
535
|
+
transition: outline 0.2s ease;
|
|
551
536
|
&.apos-is-highlighted {
|
|
552
|
-
|
|
553
|
-
opacity: 0.4;
|
|
554
|
-
}
|
|
537
|
+
outline: 1px dashed var(--a-primary-transparent-50);
|
|
555
538
|
}
|
|
556
539
|
&.apos-is-focused {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
540
|
+
outline: 1px dashed var(--a-primary);
|
|
541
|
+
&::v-deep .apos-rich-text-editor__editor.apos-is-visually-empty {
|
|
542
|
+
box-shadow: none;
|
|
560
543
|
}
|
|
561
544
|
}
|
|
562
545
|
|
|
@@ -260,11 +260,12 @@ module.exports = {
|
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
async function moduleOverrides(modulesDir, source) {
|
|
263
|
+
async function moduleOverrides(modulesDir, source, pnpmPaths) {
|
|
264
264
|
await fs.remove(modulesDir);
|
|
265
265
|
await fs.mkdirp(modulesDir);
|
|
266
266
|
let names = {};
|
|
267
267
|
const directories = {};
|
|
268
|
+
const pnpmOnly = {};
|
|
268
269
|
// Most other modules are not actually instantiated yet, but
|
|
269
270
|
// we can access their metadata, which is sufficient
|
|
270
271
|
for (const name of modulesToInstantiate) {
|
|
@@ -273,6 +274,9 @@ module.exports = {
|
|
|
273
274
|
for (const entry of metadata.__meta.chain) {
|
|
274
275
|
const effectiveName = entry.name.replace(/^my-/, '');
|
|
275
276
|
names[effectiveName] = true;
|
|
277
|
+
if (entry.npm && !entry.bundled && !entry.my) {
|
|
278
|
+
pnpmOnly[entry.dirname] = true;
|
|
279
|
+
}
|
|
276
280
|
ancestorDirectories.push(entry.dirname);
|
|
277
281
|
directories[effectiveName] = directories[effectiveName] || [];
|
|
278
282
|
for (const dir of ancestorDirectories) {
|
|
@@ -288,6 +292,21 @@ module.exports = {
|
|
|
288
292
|
for (const dir of directories[name]) {
|
|
289
293
|
const srcDir = `${dir}/${source}`;
|
|
290
294
|
if (fs.existsSync(srcDir)) {
|
|
295
|
+
if (
|
|
296
|
+
// is pnpm installation
|
|
297
|
+
self.apos.isPnpm &&
|
|
298
|
+
// is npm module and not bundled
|
|
299
|
+
pnpmOnly[dir] &&
|
|
300
|
+
// isn't apos core module
|
|
301
|
+
!dir.startsWith(path.join(self.apos.npmRootDir, 'node_modules/apostrophe/'))
|
|
302
|
+
) {
|
|
303
|
+
// Ignore further attempts to register this path (performance)
|
|
304
|
+
pnpmOnly[dir] = false;
|
|
305
|
+
// resolve symlinked pnpm path
|
|
306
|
+
const resolved = fs.realpathSync(dir);
|
|
307
|
+
// go up to the pnpm node_modules directory
|
|
308
|
+
pnpmPaths.add(resolved.split(name)[0]);
|
|
309
|
+
}
|
|
291
310
|
await fs.copy(srcDir, moduleDir);
|
|
292
311
|
}
|
|
293
312
|
}
|
|
@@ -302,7 +321,9 @@ module.exports = {
|
|
|
302
321
|
}));
|
|
303
322
|
const modulesDir = `${buildDir}/${name}/modules`;
|
|
304
323
|
const source = options.source || name;
|
|
305
|
-
|
|
324
|
+
// Gather pnpm modules that are used in the build to be added as resolve paths
|
|
325
|
+
const pnpmModules = new Set();
|
|
326
|
+
await moduleOverrides(modulesDir, `ui/${source}`, pnpmModules);
|
|
306
327
|
|
|
307
328
|
let iconImports, componentImports, tiptapExtensionImports, appImports, indexJsImports, indexSassImports;
|
|
308
329
|
if (options.apos) {
|
|
@@ -382,6 +403,7 @@ module.exports = {
|
|
|
382
403
|
outputPath: bundleDir,
|
|
383
404
|
outputFilename,
|
|
384
405
|
bundles: webpackExtraBundles,
|
|
406
|
+
pnpmModulesResolvePaths: pnpmModules,
|
|
385
407
|
// Added on the fly by the
|
|
386
408
|
// @apostrophecms/asset-es5 module,
|
|
387
409
|
// if it is present
|
|
@@ -774,10 +796,13 @@ module.exports = {
|
|
|
774
796
|
async function findPackageLock() {
|
|
775
797
|
const packageLockPath = path.join(self.apos.npmRootDir, 'package-lock.json');
|
|
776
798
|
const yarnPath = path.join(self.apos.npmRootDir, 'yarn.lock');
|
|
799
|
+
const pnpmPath = path.join(self.apos.npmRootDir, 'pnpm-lock.yaml');
|
|
777
800
|
if (await fs.pathExists(packageLockPath)) {
|
|
778
801
|
return packageLockPath;
|
|
779
802
|
} else if (await fs.pathExists(yarnPath)) {
|
|
780
803
|
return yarnPath;
|
|
804
|
+
} else if (await fs.pathExists(pnpmPath)) {
|
|
805
|
+
return pnpmPath;
|
|
781
806
|
} else {
|
|
782
807
|
return false;
|
|
783
808
|
}
|
|
@@ -11,7 +11,12 @@ if (process.env.APOS_BUNDLE_ANALYZER) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
module.exports = ({
|
|
14
|
-
importFile,
|
|
14
|
+
importFile,
|
|
15
|
+
modulesDir,
|
|
16
|
+
outputPath,
|
|
17
|
+
outputFilename,
|
|
18
|
+
// it's a Set, not an array
|
|
19
|
+
pnpmModulesResolvePaths
|
|
15
20
|
}, apos) => {
|
|
16
21
|
const tasks = [ scss, vue, js ].map(task =>
|
|
17
22
|
task(
|
|
@@ -23,6 +28,7 @@ module.exports = ({
|
|
|
23
28
|
)
|
|
24
29
|
);
|
|
25
30
|
|
|
31
|
+
const pnpmModulePath = apos.isPnpm ? [ path.join(apos.selfDir, '../') ] : [];
|
|
26
32
|
const config = {
|
|
27
33
|
entry: importFile,
|
|
28
34
|
// Ensure that the correct version of vue-loader is found
|
|
@@ -47,7 +53,16 @@ module.exports = ({
|
|
|
47
53
|
// at a later date if needed
|
|
48
54
|
resolveLoader: {
|
|
49
55
|
extensions: [ '*', '.js', '.vue', '.json' ],
|
|
50
|
-
modules: [
|
|
56
|
+
modules: [
|
|
57
|
+
// 1. Allow webpack to find loaders from core dependencies (pnpm), empty if not pnpm
|
|
58
|
+
...pnpmModulePath,
|
|
59
|
+
// 2. Allow webpack to find loaders from dependencies of any project level packages (pnpm),
|
|
60
|
+
// empty if not pnpm
|
|
61
|
+
...[ ...pnpmModulesResolvePaths ],
|
|
62
|
+
// 3. npm related paths
|
|
63
|
+
'node_modules/apostrophe/node_modules',
|
|
64
|
+
'node_modules'
|
|
65
|
+
]
|
|
51
66
|
},
|
|
52
67
|
resolve: {
|
|
53
68
|
extensions: [ '*', '.js', '.vue', '.json' ],
|
|
@@ -58,6 +73,12 @@ module.exports = ({
|
|
|
58
73
|
},
|
|
59
74
|
modules: [
|
|
60
75
|
'node_modules',
|
|
76
|
+
// 1. Allow webpack to find imports from core dependencies (pnpm), empty if not pnpm
|
|
77
|
+
...pnpmModulePath,
|
|
78
|
+
// 2. Allow webpack to find imports from dependencies of any project level packages (pnpm),
|
|
79
|
+
// empty if not pnpm
|
|
80
|
+
...[ ...pnpmModulesResolvePaths ],
|
|
81
|
+
// 3. npm related paths
|
|
61
82
|
`${apos.npmRootDir}/node_modules/apostrophe/node_modules`,
|
|
62
83
|
`${apos.npmRootDir}/node_modules`
|
|
63
84
|
],
|
|
@@ -10,7 +10,15 @@ if (process.env.APOS_BUNDLE_ANALYZER) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
module.exports = ({
|
|
13
|
-
importFile,
|
|
13
|
+
importFile,
|
|
14
|
+
modulesDir,
|
|
15
|
+
outputPath,
|
|
16
|
+
outputFilename,
|
|
17
|
+
// it's a Set, not an array
|
|
18
|
+
pnpmModulesResolvePaths,
|
|
19
|
+
bundles = {},
|
|
20
|
+
es5,
|
|
21
|
+
es5TaskFn
|
|
14
22
|
}, apos) => {
|
|
15
23
|
const mainBundleName = outputFilename.replace('.js', '');
|
|
16
24
|
const taskFns = [ scssTask, ...(es5 ? [ es5TaskFn ] : []) ];
|
|
@@ -27,6 +35,7 @@ module.exports = ({
|
|
|
27
35
|
);
|
|
28
36
|
|
|
29
37
|
const moduleName = es5 ? 'nomodule' : 'module';
|
|
38
|
+
const pnpmModulePath = apos.isPnpm ? [ path.join(apos.selfDir, '../') ] : [];
|
|
30
39
|
const config = {
|
|
31
40
|
entry: {
|
|
32
41
|
[mainBundleName]: importFile,
|
|
@@ -57,7 +66,16 @@ module.exports = ({
|
|
|
57
66
|
extensions: [ '*', '.js' ],
|
|
58
67
|
// Make sure css-loader and postcss-loader can always be found, even
|
|
59
68
|
// if npm didn't hoist them
|
|
60
|
-
modules: [
|
|
69
|
+
modules: [
|
|
70
|
+
'node_modules',
|
|
71
|
+
// 1. Allow webpack to find loaders from dependencies of any project level packages (pnpm),
|
|
72
|
+
// empty if not pnpm
|
|
73
|
+
...[ ...pnpmModulesResolvePaths ],
|
|
74
|
+
// 2. Allow webpack to find loaders from core dependencies (pnpm), empty if not pnpm
|
|
75
|
+
...pnpmModulePath,
|
|
76
|
+
// 3. npm related paths
|
|
77
|
+
'node_modules/apostrophe/node_modules'
|
|
78
|
+
]
|
|
61
79
|
},
|
|
62
80
|
resolve: {
|
|
63
81
|
extensions: [ '*', '.js' ],
|
|
@@ -67,6 +85,12 @@ module.exports = ({
|
|
|
67
85
|
},
|
|
68
86
|
modules: [
|
|
69
87
|
'node_modules',
|
|
88
|
+
// 1. Allow webpack to find imports from dependencies of any project level packages (pnpm),
|
|
89
|
+
// empty if not pnpm
|
|
90
|
+
...[ ...pnpmModulesResolvePaths ],
|
|
91
|
+
// 2. Allow webpack to find imports from core dependencies (pnpm), empty if not pnpm
|
|
92
|
+
...pnpmModulePath,
|
|
93
|
+
// 3. npm related paths
|
|
70
94
|
`${apos.npmRootDir}/node_modules`,
|
|
71
95
|
// Make sure core-js and regenerator-runtime can always be found, even
|
|
72
96
|
// if npm didn't hoist them
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const cuid = require('cuid');
|
|
3
3
|
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
|
|
4
|
+
const { klona } = require('klona');
|
|
4
5
|
|
|
5
6
|
// This module is responsible for managing all of the documents (apostrophe "docs")
|
|
6
7
|
// in the `aposDocs` mongodb collection.
|
|
@@ -296,6 +297,148 @@ module.exports = {
|
|
|
296
297
|
},
|
|
297
298
|
methods(self) {
|
|
298
299
|
return {
|
|
300
|
+
// `pairs` is an array of arrays, each containing an old _id
|
|
301
|
+
// and a new _id that should replace it.
|
|
302
|
+
//
|
|
303
|
+
// `aposDocId` is implicitly updated, `path` is updated if a page,
|
|
304
|
+
// and all references found in relationships are updated via reverse
|
|
305
|
+
// relationship id lookups, after which attachment references are updated.
|
|
306
|
+
// This is a slow operation, which is why this method should be called only
|
|
307
|
+
// by migrations and tasks that remedy an unexpected situation. _id is
|
|
308
|
+
// meant to be an immutable property, this method is a workaround
|
|
309
|
+
// for situations like a renamed locale or a replication bug fix.
|
|
310
|
+
//
|
|
311
|
+
// If `keep` is set to `'old'` the old document's content wins
|
|
312
|
+
// in the event of a conflict. If `keep` is set to `'new'` the
|
|
313
|
+
// new document's content wins in the event of a conflict.
|
|
314
|
+
// If `keep` is not set, a `conflict` error is thrown in the
|
|
315
|
+
// event of a conflict.
|
|
316
|
+
|
|
317
|
+
async changeDocIds(pairs, { keep } = {}) {
|
|
318
|
+
let renamed = 0;
|
|
319
|
+
let kept = 0;
|
|
320
|
+
// Get page paths up front so we can avoid multiple queries when working on path changes
|
|
321
|
+
const pages = await self.apos.doc.db.find({
|
|
322
|
+
path: { $exists: 1 },
|
|
323
|
+
slug: /^\//
|
|
324
|
+
}).project({
|
|
325
|
+
path: 1,
|
|
326
|
+
aposLastTargetId: 1
|
|
327
|
+
}).toArray();
|
|
328
|
+
for (const pair of pairs) {
|
|
329
|
+
const [ from, to ] = pair;
|
|
330
|
+
const existing = await self.apos.doc.db.findOne({ _id: from });
|
|
331
|
+
if (!existing) {
|
|
332
|
+
throw self.apos.error('notfound');
|
|
333
|
+
}
|
|
334
|
+
const replacement = klona(existing);
|
|
335
|
+
await self.apos.doc.db.removeOne({ _id: from });
|
|
336
|
+
const oldAposDocId = existing.aposDocId;
|
|
337
|
+
replacement._id = to;
|
|
338
|
+
const parts = to.split(':');
|
|
339
|
+
replacement.aposDocId = parts[0];
|
|
340
|
+
// Watch out for nonlocalized types, don't set aposLocale for them
|
|
341
|
+
if (parts.length > 1) {
|
|
342
|
+
replacement.aposLocale = parts.slice(1).join(':');
|
|
343
|
+
}
|
|
344
|
+
const isPage = self.apos.page.isPage(existing);
|
|
345
|
+
if (isPage) {
|
|
346
|
+
replacement.path = existing.path.replace(existing.aposDocId, replacement.aposDocId);
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
await self.apos.doc.db.insertOne(replacement);
|
|
350
|
+
renamed++;
|
|
351
|
+
} catch (e) {
|
|
352
|
+
// First reinsert old doc to prevent content loss on new doc insert failure
|
|
353
|
+
await self.apos.doc.db.insertOne(existing);
|
|
354
|
+
if (!self.apos.doc.isUniqueError(e)) {
|
|
355
|
+
// We cannot fix this error
|
|
356
|
+
throw e;
|
|
357
|
+
}
|
|
358
|
+
const existingReplacement = await self.apos.doc.db.findOne({ _id: replacement._id });
|
|
359
|
+
if (!existingReplacement) {
|
|
360
|
+
// We don't know the cause of this error
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
363
|
+
if (keep === 'new') {
|
|
364
|
+
// New content already exists in new locale, delete old locale
|
|
365
|
+
// and keep new
|
|
366
|
+
await self.apos.doc.db.removeOne({ _id: existing._id });
|
|
367
|
+
kept++;
|
|
368
|
+
} else if (keep === 'old') {
|
|
369
|
+
// We want to keep the old content, but with the new
|
|
370
|
+
// identifiers. Once again we need to remove the old doc first
|
|
371
|
+
// to cut down on conflicts
|
|
372
|
+
try {
|
|
373
|
+
await self.apos.doc.db.deleteOne({ _id: existing._id });
|
|
374
|
+
await self.apos.doc.db.deleteOne({ _id: replacement._id });
|
|
375
|
+
await self.apos.doc.db.insertOne(replacement);
|
|
376
|
+
renamed++;
|
|
377
|
+
} catch (e) {
|
|
378
|
+
// Reinsert old doc to prevent content loss on new doc insert failure
|
|
379
|
+
await self.apos.doc.db.insertOne(existing);
|
|
380
|
+
throw e;
|
|
381
|
+
}
|
|
382
|
+
kept++;
|
|
383
|
+
} else {
|
|
384
|
+
throw self.apos.error('conflict');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (isPage) {
|
|
388
|
+
for (const page of pages) {
|
|
389
|
+
if (page.aposLastTargetId === from) {
|
|
390
|
+
await self.apos.doc.db.updateOne({
|
|
391
|
+
_id: page._id
|
|
392
|
+
}, {
|
|
393
|
+
$set: {
|
|
394
|
+
aposLastTargetId: to
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (page.path.includes(oldAposDocId)) {
|
|
399
|
+
await self.apos.doc.db.updateOne({
|
|
400
|
+
_id: page._id
|
|
401
|
+
}, {
|
|
402
|
+
$set: {
|
|
403
|
+
path: page.path.replace(oldAposDocId, replacement.aposDocId)
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (existing.relatedReverseIds?.length) {
|
|
410
|
+
const relatedDocs = await self.apos.doc.db.find({
|
|
411
|
+
aposDocId: { $in: existing.relatedReverseIds }
|
|
412
|
+
}).toArray();
|
|
413
|
+
for (const doc of relatedDocs) {
|
|
414
|
+
replaceId(doc, oldAposDocId, replacement.aposDocId);
|
|
415
|
+
await self.apos.doc.db.replaceOne({
|
|
416
|
+
_id: doc._id
|
|
417
|
+
}, doc);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
await self.apos.attachment.recomputeAllDocReferences();
|
|
422
|
+
return {
|
|
423
|
+
renamed,
|
|
424
|
+
kept
|
|
425
|
+
};
|
|
426
|
+
function replaceId(obj, oldId, newId) {
|
|
427
|
+
if (obj == null) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if ((typeof obj) !== 'object') {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
for (const key of Object.keys(obj)) {
|
|
434
|
+
if (obj[key] === oldId) {
|
|
435
|
+
obj[key] = newId;
|
|
436
|
+
} else {
|
|
437
|
+
replaceId(obj[key], oldId, newId);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
299
442
|
async enableCollection() {
|
|
300
443
|
self.db = await self.apos.db.collection('aposDocs');
|
|
301
444
|
},
|
|
@@ -1142,6 +1285,7 @@ module.exports = {
|
|
|
1142
1285
|
type: module.name
|
|
1143
1286
|
});
|
|
1144
1287
|
}
|
|
1288
|
+
self.replicateReached = true;
|
|
1145
1289
|
// Include the criteria array in the event so that more entries can be pushed to it
|
|
1146
1290
|
await self.emit('beforeReplicate', criteria);
|
|
1147
1291
|
// We can skip the core work of this method if there is only one locale,
|
|
@@ -1303,6 +1447,11 @@ module.exports = {
|
|
|
1303
1447
|
});
|
|
1304
1448
|
},
|
|
1305
1449
|
|
|
1450
|
+
async bestAposDocId(criteria) {
|
|
1451
|
+
const existing = await self.apos.doc.db.findOne(criteria, { projection: { aposDocId: 1 } });
|
|
1452
|
+
return existing?.aposDocId || self.apos.util.generateId();
|
|
1453
|
+
},
|
|
1454
|
+
|
|
1306
1455
|
...require('./lib/legacy-migrations')(self)
|
|
1307
1456
|
};
|
|
1308
1457
|
}
|
|
@@ -193,6 +193,12 @@ module.exports = {
|
|
|
193
193
|
if (!self.options.name) {
|
|
194
194
|
self.options.name = self.__meta.name;
|
|
195
195
|
}
|
|
196
|
+
if (self.options.singletonAuto) {
|
|
197
|
+
self.options.singleton = true;
|
|
198
|
+
}
|
|
199
|
+
if (self.options.replicate === undefined) {
|
|
200
|
+
self.options.replicate = self.options.localized && self.options.singletonAuto;
|
|
201
|
+
}
|
|
196
202
|
self.name = self.options.name;
|
|
197
203
|
// Each doc-type has an array of fields which will be updated
|
|
198
204
|
// if the document is moved to the archive. In most cases 'slug'
|
|
@@ -1473,7 +1479,9 @@ module.exports = {
|
|
|
1473
1479
|
browserOptions.schema = self.allowedSchema(req);
|
|
1474
1480
|
browserOptions.localized = self.isLocalized();
|
|
1475
1481
|
browserOptions.autopublish = self.options.autopublish;
|
|
1476
|
-
browserOptions.previewDraft = self.isLocalized() &&
|
|
1482
|
+
browserOptions.previewDraft = self.isLocalized() &&
|
|
1483
|
+
!browserOptions.autopublish &&
|
|
1484
|
+
self.options.previewDraft;
|
|
1477
1485
|
|
|
1478
1486
|
return browserOptions;
|
|
1479
1487
|
}
|
|
@@ -42,7 +42,9 @@ module.exports = {
|
|
|
42
42
|
// Intentionally the same
|
|
43
43
|
pluralLabel: 'apostrophe:globalDocLabel',
|
|
44
44
|
searchable: false,
|
|
45
|
-
|
|
45
|
+
singletonAuto: {
|
|
46
|
+
slug: 'global'
|
|
47
|
+
},
|
|
46
48
|
showPermissions: true,
|
|
47
49
|
replicate: true
|
|
48
50
|
},
|
|
@@ -77,23 +79,10 @@ module.exports = {
|
|
|
77
79
|
},
|
|
78
80
|
methods(self) {
|
|
79
81
|
return {
|
|
80
|
-
async insertIfMissing() {
|
|
81
|
-
// Insert at startup
|
|
82
|
-
const req = self.apos.task.getReq();
|
|
83
|
-
const existing = await self.apos.doc.db.findOne({ slug: self.slug });
|
|
84
|
-
if (!existing) {
|
|
85
|
-
const _new = self.newInstance();
|
|
86
|
-
Object.assign(_new, {
|
|
87
|
-
slug: self.slug,
|
|
88
|
-
type: self.name
|
|
89
|
-
});
|
|
90
|
-
await self.insert(req, _new);
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
82
|
// Fetch and return the `global` doc object. You probably don't need to call this,
|
|
94
83
|
// because middleware has already populated `req.data.global` for you.
|
|
95
84
|
async findGlobal(req) {
|
|
96
|
-
return self.find(req, {
|
|
85
|
+
return self.find(req, { type: self.name }).permission(false).toObject();
|
|
97
86
|
},
|
|
98
87
|
// Fetch the global doc and add it to `req.data` as `req.data.global`, if it
|
|
99
88
|
// is not already present. If it is already present, skip the
|
|
@@ -376,13 +376,13 @@
|
|
|
376
376
|
"richTextH4": "Heading 4 (H4)",
|
|
377
377
|
"richTextHighlight": "Mark",
|
|
378
378
|
"richTextHorizontalRule": "Horizontal Rule",
|
|
379
|
-
"richTextInsertMenuHeading": "Insert...",
|
|
379
|
+
"richTextInsertMenuHeading": "Insert element...",
|
|
380
380
|
"richTextItalic": "Italic",
|
|
381
381
|
"richTextLink": "Link",
|
|
382
382
|
"richTextOrderedList": "Ordered List",
|
|
383
383
|
"richTextParagraph": "Paragraph (P)",
|
|
384
384
|
"richTextPlaceholder": "Start Typing Here...",
|
|
385
|
-
"richTextPlaceholderWithInsertMenu": "Start
|
|
385
|
+
"richTextPlaceholderWithInsertMenu": "Start typing or press '/' for commands...",
|
|
386
386
|
"richTextRedo": "Redo",
|
|
387
387
|
"richTextStrikethrough": "Strike",
|
|
388
388
|
"richTextStyleConfigWarning": "Misconfigured rich text style: label: {{ label }}, {{ tag }}",
|
|
@@ -399,6 +399,7 @@
|
|
|
399
399
|
"saveDraftDescription": "Save as a draft to publish later.",
|
|
400
400
|
"saveType": "Save {{ type }}",
|
|
401
401
|
"savingDocument": "Saving document...",
|
|
402
|
+
"search": "Search",
|
|
402
403
|
"searchDocType": "Search {{ type }}",
|
|
403
404
|
"searchLabel": "Search Page",
|
|
404
405
|
"searchLocales": "Search Locales",
|