apostrophe 4.20.0 → 4.21.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/CHANGELOG.md +40 -1
- package/README.md +112 -30
- package/index.js +10 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +1 -0
- package/modules/@apostrophecms/area/index.js +8 -4
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue +31 -4
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +40 -59
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +97 -10
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +6 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +7 -3
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +14 -9
- package/modules/@apostrophecms/area/ui/apos/lib/clone-widget.js +39 -0
- package/modules/@apostrophecms/asset/lib/build/external-module-api.js +5 -3
- package/modules/@apostrophecms/asset/lib/build/task.js +5 -3
- package/modules/@apostrophecms/asset/lib/globalIcons.js +1 -0
- package/modules/@apostrophecms/attachment/index.js +2 -1
- package/modules/@apostrophecms/doc/ui/apos/apps/AposDoc.js +7 -2
- package/modules/@apostrophecms/doc-type/index.js +3 -3
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +11 -1
- package/modules/@apostrophecms/i18n/i18n/de.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/fr.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/it.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +7 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +44 -22
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +7 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +0 -7
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploaderUi.vue +386 -0
- package/modules/@apostrophecms/image-widget/index.js +18 -3
- package/modules/@apostrophecms/image-widget/ui/apos/components/AposImageWidget.vue +217 -0
- package/modules/@apostrophecms/login/index.js +36 -26
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -51
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposUtilityOperations.vue +2 -2
- package/modules/@apostrophecms/piece-type/ui/apos/lib/postprocessRelationships.js +68 -0
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +3 -6
- package/modules/@apostrophecms/schema/ui/apos/lib/detectChange.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +32 -16
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +4 -1
- package/modules/@apostrophecms/submitted-draft/ui/apos/components/AposSubmittedDraftIcon.vue +7 -0
- package/modules/@apostrophecms/ui/ui/apos/apps/AposTransformers.js +12 -0
- package/modules/@apostrophecms/ui/ui/apos/composables/AposTheme.js +8 -7
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposThemeMixin.js +3 -4
- package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +6 -0
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -9
- package/modules/@apostrophecms/widget-type/ui/apos/composables/AposWidget.js +97 -0
- package/modules/@apostrophecms/widget-type/ui/apos/composables/AposWidgetProps.js +28 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +22 -86
- package/package.json +2 -2
- package/test/add-missing-schema-fields.js +15 -9
- package/test/login.js +73 -22
- package/test/widgets.js +0 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.21.1 (2025-09-26)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* The `exit` option to the main `apostrophe()` function now supports the new string value `exit: 'throw'`. If this value is specified and the apostrophe startup procedure fails with an error, the actual error is re-thrown for the benefit of the caller.
|
|
8
|
+
* For backwards compatibility, the existing `exit: false` option to the main `apostrophe()` function is still supported, but now logs the error that took place before returning `undefined` as before. This is more useful than the previous behavior, but `exit: 'throw'` is the more logical choice if you need to avoid a process exit.
|
|
9
|
+
* The default behavior is still to log the error and exit the process, which isthe only sensible move in most single-site projects.
|
|
10
|
+
|
|
11
|
+
## 4.21.0 (2025-09-03)
|
|
12
|
+
|
|
13
|
+
### Adds
|
|
14
|
+
|
|
15
|
+
* Modules can now call `apos.area.addCreateWidgetOperation` to register a custom operation that invokes a modal and inserts the widget returned by that modal. These operations are offered as choices in all "add widget" menus, both regular and expanded.
|
|
16
|
+
* `AposDocEditor` now accepts a `values` prop, which can be used to pass an object of initial values for some or all fields. Use of this prop is optional. It is not supported when editing existing documents.
|
|
17
|
+
* `apos.doc.edit` now accepts an optional `values` object as the final parameter, containing initial values for some or all fields. This is supported only when editing existing documents.
|
|
18
|
+
* When specifying a modal name to be executed, developers may now register "transformers" to be invoked first, using pipe syntax. For example, the modal name `aposSectionTemplateLibraryWidgetToDoc|AposDocEditor` will invoke the transformer `aposSectionTemplateLibraryWidgetToDoc` with the original props, and pass the returned result to `AposDocEditor`. Note that transformers are awaited. Transformers are registered in frontend admin UI code by passing a name and a function to `apos.ui.addTransformer`.
|
|
19
|
+
* Adds quick image upload UI to `@apostrophecms/image-widget`.
|
|
20
|
+
* Makes autocropping work when uploading or selecting images from the new quick image upload UI.
|
|
21
|
+
|
|
22
|
+
### Fixes
|
|
23
|
+
|
|
24
|
+
* The `?render-areas=1` API feature now correctly disregards areas in separate documents loaded via relationship fields. Formerly their presence resulted in an error, not a rendering.
|
|
25
|
+
* Make conditional fields work in Image Editor.
|
|
26
|
+
* Importing a custom icon from an npm module using a `~` path per the admin UI now works per the documentation, as long as the Vue component used for the icon is structured like those found in `@apostrophecms/vue-material-design-icons`.
|
|
27
|
+
* The `button: true` flag works again for piece module utility operations. Previously the button appeared but did not trigger the desired operation.
|
|
28
|
+
* Fix the fact that area options `minSize` and `aspectRatio` weren't passed to the image cropper when coming directly from the area and the widget controls (without passing through the widget editor).
|
|
29
|
+
* Fixes the widget data being cloned to be saved before the `postprocess` method being called, which leads to a loss of data in `AposWidgetEditor` (like the autocrop data).
|
|
30
|
+
* In editors like `AposWidgetEditor` relationships are now post processed after they are updated in `AposInputRelationship` only for the relationship that has been updated.
|
|
31
|
+
It allows live preview to work well with it, it also avoids complexity and fixes updated data not being properly synced between the editor and the `AposSchema`.
|
|
32
|
+
* Deeply nested widgets can now be edited properly via the editor dialog box. This longstanding issue did not affect on-page editing.
|
|
33
|
+
|
|
34
|
+
### Changes
|
|
35
|
+
|
|
36
|
+
* Rolled back a change in 4.16.0 that strictly enforced `required` and `min` for relationship fields. Because the related document can be archived or deleted at any time, it is misleading to offer such enforcement. Also, it greatly complicates adding these constraints to existing schemas, resulting in surprising and unwanted behaviors. Therefore it is better for these constraints to be soft constraints on the front end. `max` is still a hard constraint.
|
|
37
|
+
* The `@apostrophecms/login/whoami` route now accepts both `POST` (recommended) and `GET` requests. Previously, it only supported `GET`. This maintains backwards compatibility while aligning with the documentation’s recommendation to use `POST`.
|
|
38
|
+
|
|
3
39
|
## 4.20.0 (2025-08-06)
|
|
4
40
|
|
|
5
41
|
### Adds
|
|
@@ -9,8 +45,11 @@
|
|
|
9
45
|
|
|
10
46
|
### Changes
|
|
11
47
|
|
|
48
|
+
* A `clone-widget.js` file has been factored out, providing a universal way to return a clone of an existing widget which is distinct from the original.
|
|
49
|
+
* Adds any alt text found in an attribute to the media library attachment during import of rich text inline images by API
|
|
50
|
+
* Adds `prependNodes` and `appendNodes` methods to every module. These methods allow you to inject HTML to every page using a `node` declaration.
|
|
12
51
|
* Changes handling of `order` and `groups` in the `admin-bar` module to respect, rather that reverse, the order of items
|
|
13
|
-
* Interacting with the text inside a rich text widget will hide the widget controls to prevent
|
|
52
|
+
* Interacting with the text inside a rich text widget will hide the widget controls to prevent awkward text selection.
|
|
14
53
|
|
|
15
54
|
### Fixes
|
|
16
55
|
|
package/README.md
CHANGED
|
@@ -1,60 +1,142 @@
|
|
|
1
|
-
|
|
2
|
-
[](https://chat.apostrophecms.org)
|
|
3
|
-
|
|
4
|
-
<p align="center">
|
|
1
|
+
<div align="center">
|
|
5
2
|
<a href="https://github.com/apostrophecms/apostrophe">
|
|
6
3
|
<img src="logo.svg" alt="ApostropheCMS logo" width="80" height="80">
|
|
7
4
|
</a>
|
|
8
5
|
|
|
9
|
-
<
|
|
6
|
+
<h1>ApostropheCMS</h1>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
<a aria-label="Join the community on Discord" href="http://chat.apostrophecms.org">
|
|
10
|
+
<img alt="" src="https://img.shields.io/discord/517772094482677790?color=5865f2&label=Join%20the%20Discord&logo=discord&logoColor=fff&labelColor=000&style=for-the-badge&logoWidth=20" />
|
|
11
|
+
</a>
|
|
12
|
+
<a aria-label="License" href="https://github.com/apostrophecms/apostrophe/blob/main/LICENSE.md">
|
|
13
|
+
<img alt="" src="https://img.shields.io/static/v1?style=for-the-badge&labelColor=000000&label=License&message=MIT&color=3DA639" />
|
|
14
|
+
</a>
|
|
15
|
+
</p>
|
|
10
16
|
|
|
11
|
-
<p
|
|
12
|
-
|
|
17
|
+
<p>
|
|
18
|
+
<strong>Full-stack CMS for developers and content teams</strong><br />
|
|
19
|
+
Build websites with in-context editing and headless flexibility using Node.js and MongoDB.
|
|
13
20
|
<br />
|
|
14
|
-
<a href="https://
|
|
21
|
+
<a href="https://docs.apostrophecms.org/"><strong>Documentation »</strong></a>
|
|
15
22
|
<br />
|
|
16
23
|
<br />
|
|
17
24
|
<a href="http://demo.apostrophecms.com">Demo</a>
|
|
18
25
|
·
|
|
19
|
-
<a href="https://
|
|
26
|
+
<a href="https://productlane.com/edit-roadmap">Roadmap</a>
|
|
20
27
|
·
|
|
21
28
|
<a href="https://github.com/apostrophecms/apostrophe/issues/new?assignees=&labels=bug,3.0&template=bug_report.md&title=">Report Bug</a>
|
|
22
29
|
</p>
|
|
23
|
-
</
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
## About
|
|
33
|
+
|
|
34
|
+
ApostropheCMS is a full-stack content management system built with Node.js and MongoDB. Content creators can edit directly on live pages without switching between admin interfaces, while developers can build with modern JavaScript or use it headlessly with any frontend framework.
|
|
35
|
+
|
|
36
|
+
### Key Features
|
|
37
|
+
|
|
38
|
+
- **🎯 In-Context Editing** - Content creators edit directly on the live page, seeing changes instantly
|
|
39
|
+
- **⚡ Headless-Ready** - Use any frontend framework while keeping the powerful admin experience
|
|
40
|
+
- **🛠️ Developer-First** - Built with Node.js and MongoDB for full-stack JavaScript development
|
|
41
|
+
- **📈 Scales Beautifully** - From small sites to enterprise applications handling millions of pages
|
|
42
|
+
- **🔐 Enterprise Features** - Advanced permissions, workflow management, automated translations, and more
|
|
43
|
+
|
|
44
|
+
## System Requirements
|
|
45
|
+
|
|
46
|
+
| Requirement | Version | Installation Notes |
|
|
47
|
+
|-------------|---------|-------------------|
|
|
48
|
+
| **Node.js** | 20.x+ | Use [NVM](https://github.com/nvm-sh/nvm) for version management |
|
|
49
|
+
| **MongoDB** | 6.0+ | [MongoDB Atlas](https://www.mongodb.com/atlas) (cloud) or local install |
|
|
50
|
+
| **npm** | 10.x+ | Included with Node.js |
|
|
51
|
+
|
|
52
|
+
See our [setup guides](https://docs.apostrophecms.org/guide/development-setup.html) for installation instructions.
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
Get ApostropheCMS running locally in minutes:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Option 1: Install CLI globally (recommended for multiple projects)
|
|
60
|
+
npm install -g @apostrophecms/cli
|
|
61
|
+
apos create my-website
|
|
62
|
+
cd my-website
|
|
63
|
+
npm run dev
|
|
64
|
+
|
|
65
|
+
# Option 2: Use npx for one-time project creation
|
|
66
|
+
npx @apostrophecms/cli create my-website
|
|
67
|
+
cd my-website
|
|
68
|
+
npm run dev
|
|
69
|
+
```
|
|
24
70
|
|
|
25
|
-
|
|
71
|
+
Your new ApostropheCMS site will be available at `http://localhost:3000` with a powerful admin interface at `/login`.
|
|
26
72
|
|
|
27
|
-
|
|
73
|
+
### Prefer to Go Headless?
|
|
28
74
|
|
|
29
|
-
|
|
75
|
+
**Get started with Astro integration** - the easiest way to build headless sites while keeping visual editing:
|
|
30
76
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* [Nunjucks](https://mozilla.github.io/nunjucks/)
|
|
77
|
+
- **[Apollo Starter Kit (Astro)](https://apostrophecms.com/starter-kits/apollo-starter-kit-for-astro-cms)** - Production-ready foundation with beautiful design system and rich content features
|
|
78
|
+
- **[Essentials Starter Kit (Astro)](git clone https://github.com/apostrophecms/starter-kit-astro-essentials)** - Minimal, clean foundation for building custom designs from scratch
|
|
34
79
|
|
|
35
|
-
|
|
80
|
+
Both starter kits provide headless CMS power with in-context editing, letting content creators edit directly on the live site while you build with modern frontend tools. Our Astro integration handles all the content fetching automatically—no REST API calls to write.
|
|
36
81
|
|
|
37
|
-
|
|
82
|
+
**Desire a different frontend framework?** Use our REST APIs with React, Vue, Svelte, or any other framework:
|
|
38
83
|
|
|
39
|
-
|
|
84
|
+
- **[REST API Documentation](https://docs.apostrophecms.org/reference/api/pieces.html)** - Complete API reference
|
|
85
|
+
- **[Headless CMS Guide](https://docs.apostrophecms.org/guide/headless-cms.html)** - Integration walkthrough for any framework
|
|
40
86
|
|
|
41
|
-
|
|
87
|
+
### Hosting & Deployment
|
|
42
88
|
|
|
43
|
-
|
|
44
|
-
| ------------- | ------------- | -----
|
|
45
|
-
| Node.js | 12.x | Or better
|
|
46
|
-
| npm | 6.x | Or better
|
|
47
|
-
| MongoDB | 3.6 | Or better
|
|
48
|
-
| Imagemagick | Any | Faster image uploads, GIF support (optional)
|
|
89
|
+
Choose [ApostropheCMS hosting](https://apostrophecms.com/hosting) for turnkey solutions with optimized performance and dedicated support, or deploy to [any platform where Node.js runs](https://docs.apostrophecms.org/guide/hosting.html).
|
|
49
90
|
|
|
50
|
-
##
|
|
91
|
+
## Built With Modern Tech
|
|
51
92
|
|
|
52
|
-
[
|
|
93
|
+
- **[Node.js](https://nodejs.org/)** - JavaScript runtime for server-side development
|
|
94
|
+
- **[MongoDB](https://www.mongodb.com/)** - Flexible document database for content storage
|
|
95
|
+
- **ESM Modules** - Native ES6 module support for modern JavaScript
|
|
96
|
+
- **Vite** - Lightning-fast build tool and development server
|
|
97
|
+
- **Modern JavaScript** - ES6+, async/await, and contemporary development patterns
|
|
98
|
+
|
|
99
|
+
## Community & Support
|
|
100
|
+
|
|
101
|
+
**Join other developers and content creators using ApostropheCMS:**
|
|
102
|
+
|
|
103
|
+
- **[Discord](https://discord.com/invite/XkbRNq7)** - Get help, share projects, and connect with other users
|
|
104
|
+
- **[GitHub Discussions](https://github.com/apostrophecms/apostrophe/discussions)** - Feature requests, technical discussions, and community support
|
|
105
|
+
- **[Documentation](https://docs.apostrophecms.org/)** - Comprehensive guides, tutorials, and API references
|
|
53
106
|
|
|
54
107
|
## Contributing
|
|
55
108
|
|
|
56
|
-
We
|
|
109
|
+
We welcome contributions from the community! Whether you're fixing bugs, adding features, or improving documentation, your help makes ApostropheCMS better for everyone.
|
|
110
|
+
|
|
111
|
+
- **[Contribution Guide](https://github.com/apostrophecms/apostrophe/blob/main/CONTRIBUTING.md)** - How to contribute code, documentation, and feedback
|
|
112
|
+
- **[Good First Issues](https://github.com/apostrophecms/apostrophe/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)** - Perfect starting points for new contributors
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Pro Features
|
|
116
|
+
|
|
117
|
+
**For teams and organizations requiring additional features:**
|
|
118
|
+
|
|
119
|
+
- **🔐 Advanced User Management** - Granular permissions, user groups, and access controls
|
|
120
|
+
- **🌍 Automated Translation** - AI-powered translation with DeepL, Google Translate, and Azure
|
|
121
|
+
- **📊 Analytics & SEO** - Built-in SEO optimization and content analytics
|
|
122
|
+
- **⚡ Performance Optimization** - Advanced caching, CDN integration, and performance monitoring
|
|
123
|
+
- **🏢 Multisite Management** - Manage multiple sites from a single dashboard with shared resources
|
|
124
|
+
- **💼 Professional Support** - Dedicated support, training, and consultation services
|
|
125
|
+
|
|
126
|
+
[Explore all the pro extensions](https://apostrophecms.com/extensions?autocomplete=&license=assembly&license=pro) and [sign up](https://app.apostrophecms.com/login) for a Pro license in our self-service Apostrophe Workspaces, or [contact us](https://apostrophecms.com/contact-us) to learn about licensing and support options.
|
|
57
127
|
|
|
58
128
|
## License
|
|
59
129
|
|
|
60
|
-
ApostropheCMS is
|
|
130
|
+
ApostropheCMS is open source software licensed under the [MIT License](https://github.com/apostrophecms/apostrophe/blob/main/LICENSE.md). This means you're free to use, modify, and distribute it for both personal and commercial projects.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
<div align="center">
|
|
135
|
+
<p>
|
|
136
|
+
<strong>Ready to build something amazing?</strong><br>
|
|
137
|
+
<a href="https://docs.apostrophecms.org/">Get started with our documentation</a> or <a href="https://apostrophecms.com/contact-us">talk to our team</a>
|
|
138
|
+
</p>
|
|
139
|
+
<p>
|
|
140
|
+
<em>Built with ❤️ by the <a href="https://apostrophecms.com">ApostropheCMS team</a></em>
|
|
141
|
+
</p>
|
|
142
|
+
</div>
|
package/index.js
CHANGED
|
@@ -350,7 +350,16 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
350
350
|
|
|
351
351
|
return self;
|
|
352
352
|
} catch (e) {
|
|
353
|
-
if (options.exit
|
|
353
|
+
if (options.exit === false) {
|
|
354
|
+
console.error('apostrophe: error occurred during startup, continuing:');
|
|
355
|
+
console.error(e);
|
|
356
|
+
// returns undefined, for legacy reasons
|
|
357
|
+
} else if (options.exit === 'throw') {
|
|
358
|
+
// A more sensible approach for those who want to do something
|
|
359
|
+
// if initialization fails
|
|
360
|
+
throw e;
|
|
361
|
+
} else {
|
|
362
|
+
// Longstanding default behavior
|
|
354
363
|
console.error(e);
|
|
355
364
|
await self._exit(1, e);
|
|
356
365
|
}
|
|
@@ -88,6 +88,7 @@ module.exports = {
|
|
|
88
88
|
|
|
89
89
|
self.enableBrowserData();
|
|
90
90
|
self.addDeduplicateWidgetIdsMigration();
|
|
91
|
+
self.createWidgetOperations = [];
|
|
91
92
|
},
|
|
92
93
|
apiRoutes(self) {
|
|
93
94
|
return {
|
|
@@ -382,8 +383,8 @@ module.exports = {
|
|
|
382
383
|
return;
|
|
383
384
|
}
|
|
384
385
|
// We're only rendering areas on the document, not ancestor or
|
|
385
|
-
// child page documents.
|
|
386
|
-
const regex = /^_
|
|
386
|
+
// child page or related documents.
|
|
387
|
+
const regex = /^_|\._/;
|
|
387
388
|
if (dotPath.match(regex)) {
|
|
388
389
|
return;
|
|
389
390
|
}
|
|
@@ -786,7 +787,6 @@ module.exports = {
|
|
|
786
787
|
manager.options.initialModal !== false;
|
|
787
788
|
contextualWidgetDefaultData[name] = manager.options.defaultData || {};
|
|
788
789
|
});
|
|
789
|
-
|
|
790
790
|
return {
|
|
791
791
|
components: {
|
|
792
792
|
editor: 'AposAreaEditor',
|
|
@@ -799,7 +799,8 @@ module.exports = {
|
|
|
799
799
|
widgetPreview,
|
|
800
800
|
contextualWidgetDefaultData,
|
|
801
801
|
widgetManagers,
|
|
802
|
-
action: self.action
|
|
802
|
+
action: self.action,
|
|
803
|
+
createWidgetOperations: self.createWidgetOperations
|
|
803
804
|
};
|
|
804
805
|
},
|
|
805
806
|
async addDeduplicateWidgetIdsMigration() {
|
|
@@ -825,6 +826,9 @@ module.exports = {
|
|
|
825
826
|
}
|
|
826
827
|
});
|
|
827
828
|
});
|
|
829
|
+
},
|
|
830
|
+
addCreateWidgetOperation(operation) {
|
|
831
|
+
self.createWidgetOperations.push(operation);
|
|
828
832
|
}
|
|
829
833
|
};
|
|
830
834
|
},
|
|
@@ -69,7 +69,7 @@ module.exports = function(self) {
|
|
|
69
69
|
items: []
|
|
70
70
|
};
|
|
71
71
|
doc[name] = area;
|
|
72
|
-
const docId = doc._docId || doc._id;
|
|
72
|
+
const docId = doc._docId || ((doc.metaType === 'doc') ? doc._id : null);
|
|
73
73
|
if (docId) {
|
|
74
74
|
let mainDoc = await self.apos.doc.db.findOne({ _id: docId });
|
|
75
75
|
if (!mainDoc) {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
<AposAreaMenuItem
|
|
73
73
|
:item="child"
|
|
74
74
|
:tabbable="itemIndex === active"
|
|
75
|
-
@click="
|
|
75
|
+
@click="action(child)"
|
|
76
76
|
@up="switchItem(`child-${itemIndex}-${childIndex - 1}`, -1)"
|
|
77
77
|
@down="switchItem(`child-${itemIndex}-${childIndex + 1}`, 1)"
|
|
78
78
|
/>
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<AposAreaMenuItem
|
|
84
84
|
v-else
|
|
85
85
|
:item="item"
|
|
86
|
-
@click="
|
|
86
|
+
@click="action(item)"
|
|
87
87
|
@up="switchItem(`item-${itemIndex - 1}`, -1)"
|
|
88
88
|
@down="switchItem(`item-${itemIndex + 1}`, 1)"
|
|
89
89
|
/>
|
|
@@ -130,6 +130,10 @@ export default {
|
|
|
130
130
|
return {};
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
|
+
fieldId: {
|
|
134
|
+
type: String,
|
|
135
|
+
required: true
|
|
136
|
+
},
|
|
133
137
|
menuId: {
|
|
134
138
|
type: String,
|
|
135
139
|
default() {
|
|
@@ -196,8 +200,14 @@ export default {
|
|
|
196
200
|
// If the menu is not open, we don't need to compute it right now
|
|
197
201
|
return [];
|
|
198
202
|
}
|
|
199
|
-
const clipboard = apos.area.widgetClipboard.get();
|
|
200
203
|
const menu = [ ...this.contextMenuOptions.menu ];
|
|
204
|
+
for (const createWidgetOperation of this.moduleOptions.createWidgetOperations) {
|
|
205
|
+
menu.unshift({
|
|
206
|
+
type: 'operation',
|
|
207
|
+
...createWidgetOperation
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const clipboard = apos.area.widgetClipboard.get();
|
|
201
211
|
if (clipboard) {
|
|
202
212
|
const widget = clipboard;
|
|
203
213
|
const matchingChoice = menu.find(option => option.name === widget.type);
|
|
@@ -226,7 +236,24 @@ export default {
|
|
|
226
236
|
this.inContext = !apos.util.closest(this.$el, '[data-apos-schema-area]');
|
|
227
237
|
},
|
|
228
238
|
methods: {
|
|
229
|
-
async
|
|
239
|
+
async action(item) {
|
|
240
|
+
if (item.type === 'operation') {
|
|
241
|
+
const props = {
|
|
242
|
+
...item.props,
|
|
243
|
+
options: this.options,
|
|
244
|
+
fieldId: this.fieldId
|
|
245
|
+
};
|
|
246
|
+
this.$refs.contextMenu.hide();
|
|
247
|
+
const widget = await apos.modal.execute(item.modal, props);
|
|
248
|
+
if (widget) {
|
|
249
|
+
// Insert the widget at the appropriate insertion point, like we normally would
|
|
250
|
+
this.$emit('add', {
|
|
251
|
+
widget,
|
|
252
|
+
index: this.index
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
230
257
|
// Potential TODO: If we find ourselves manually flipping these bits in
|
|
231
258
|
// other AposContextMenu overrides we should consider refactoring
|
|
232
259
|
// contextmenus to be able to self close when any click takes place within
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
:empty="true"
|
|
33
33
|
:index="0"
|
|
34
34
|
:options="options"
|
|
35
|
+
:field-id="fieldId"
|
|
35
36
|
:max-reached="maxReached"
|
|
36
37
|
:disabled="field && field.readOnly"
|
|
37
38
|
:widget-options="options.widgets"
|
|
@@ -80,9 +81,9 @@
|
|
|
80
81
|
|
|
81
82
|
<script>
|
|
82
83
|
import { createId } from '@paralleldrive/cuid2';
|
|
83
|
-
import { klona } from 'klona';
|
|
84
84
|
import AposThemeMixin from 'Modules/@apostrophecms/ui/mixins/AposThemeMixin';
|
|
85
85
|
import newInstance from 'apostrophe/modules/@apostrophecms/schema/lib/newInstance.js';
|
|
86
|
+
import cloneWidget from 'Modules/@apostrophecms/area/lib/clone-widget.js';
|
|
86
87
|
|
|
87
88
|
export default {
|
|
88
89
|
name: 'AposAreaEditor',
|
|
@@ -546,12 +547,7 @@ export default {
|
|
|
546
547
|
}
|
|
547
548
|
},
|
|
548
549
|
clone(index) {
|
|
549
|
-
const widget =
|
|
550
|
-
delete widget._id;
|
|
551
|
-
this.regenerateIds(
|
|
552
|
-
apos.modules[apos.area.widgetManagers[widget.type]].schema,
|
|
553
|
-
widget
|
|
554
|
-
);
|
|
550
|
+
const widget = cloneWidget(this.next[index]);
|
|
555
551
|
this.insert({
|
|
556
552
|
widget,
|
|
557
553
|
index: index + 1
|
|
@@ -572,30 +568,6 @@ export default {
|
|
|
572
568
|
}
|
|
573
569
|
}
|
|
574
570
|
},
|
|
575
|
-
// Regenerate all array item, area, object and widget ids so they are considered
|
|
576
|
-
// new. Useful when copying a widget with nested content.
|
|
577
|
-
regenerateIds(schema, object) {
|
|
578
|
-
object._id = createId();
|
|
579
|
-
for (const field of schema) {
|
|
580
|
-
if (field.type === 'array') {
|
|
581
|
-
for (const item of (object[field.name] || [])) {
|
|
582
|
-
this.regenerateIds(field.schema, item);
|
|
583
|
-
}
|
|
584
|
-
} else if (field.type === 'object') {
|
|
585
|
-
this.regenerateIds(field.schema, object[field.name] || {});
|
|
586
|
-
} else if (field.type === 'area') {
|
|
587
|
-
if (object[field.name]) {
|
|
588
|
-
object[field.name]._id = createId();
|
|
589
|
-
for (const item of (object[field.name].items || [])) {
|
|
590
|
-
const schema = apos.modules[apos.area.widgetManagers[item.type]].schema;
|
|
591
|
-
this.regenerateIds(schema, item);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
// We don't want to regenerate attachment ids. They correspond to
|
|
596
|
-
// actual files, and the reference count will update automatically
|
|
597
|
-
}
|
|
598
|
-
},
|
|
599
571
|
async update(updated, { autosave = true, reverting = false } = {}) {
|
|
600
572
|
if (!reverting) {
|
|
601
573
|
updated.aposPlaceholder = false;
|
|
@@ -616,22 +588,30 @@ export default {
|
|
|
616
588
|
});
|
|
617
589
|
this.edited[updated._id] = true;
|
|
618
590
|
},
|
|
619
|
-
// Add a widget into an area.
|
|
591
|
+
// Add a widget into an area. index is required, along
|
|
592
|
+
// with one and only one of name, widget or clipboard.
|
|
593
|
+
// If widget is passed it is inserted directly. If
|
|
594
|
+
// clipboard is passed it is cloned and inserted.
|
|
620
595
|
async add({
|
|
621
596
|
index,
|
|
622
597
|
name,
|
|
598
|
+
widget,
|
|
623
599
|
clipboard
|
|
624
600
|
}) {
|
|
625
601
|
if (clipboard) {
|
|
626
|
-
|
|
627
|
-
apos.modules[apos.area.widgetManagers[clipboard.type]].schema,
|
|
628
|
-
clipboard
|
|
629
|
-
);
|
|
602
|
+
clipboard = cloneWidget(clipboard);
|
|
630
603
|
return this.insert({
|
|
631
604
|
widget: clipboard,
|
|
632
605
|
index
|
|
633
606
|
});
|
|
634
|
-
}
|
|
607
|
+
}
|
|
608
|
+
if (widget) {
|
|
609
|
+
return this.insert({
|
|
610
|
+
widget,
|
|
611
|
+
index
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
if (this.widgetIsContextual(name)) {
|
|
635
615
|
return this.insert({
|
|
636
616
|
widget: {
|
|
637
617
|
type: name,
|
|
@@ -640,35 +620,36 @@ export default {
|
|
|
640
620
|
},
|
|
641
621
|
index
|
|
642
622
|
});
|
|
643
|
-
}
|
|
644
|
-
|
|
623
|
+
}
|
|
624
|
+
if (!this.widgetHasInitialModal(name)) {
|
|
625
|
+
const newWidget = this.newWidget(name);
|
|
645
626
|
return this.insert({
|
|
646
627
|
widget: {
|
|
647
|
-
...
|
|
628
|
+
...newWidget,
|
|
648
629
|
aposPlaceholder: this.widgetHasPlaceholder(name)
|
|
649
630
|
},
|
|
650
631
|
index
|
|
651
632
|
});
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const componentName = this.widgetEditorComponent(name);
|
|
636
|
+
apos.area.activeEditor = this;
|
|
637
|
+
const preview = this.widgetPreview(name, index, true);
|
|
638
|
+
const newWidget = await apos.modal.execute(componentName, {
|
|
639
|
+
modelValue: null,
|
|
640
|
+
options: this.widgetOptionsByType(name),
|
|
641
|
+
type: name,
|
|
642
|
+
docId: this.docId,
|
|
643
|
+
areaFieldId: this.fieldId,
|
|
644
|
+
parentFollowingValues: this.followingValues,
|
|
645
|
+
preview
|
|
646
|
+
});
|
|
647
|
+
apos.area.activeEditor = null;
|
|
648
|
+
if (newWidget) {
|
|
649
|
+
return this.insert({
|
|
650
|
+
widget: newWidget,
|
|
651
|
+
index
|
|
664
652
|
});
|
|
665
|
-
apos.area.activeEditor = null;
|
|
666
|
-
if (widget) {
|
|
667
|
-
return this.insert({
|
|
668
|
-
widget,
|
|
669
|
-
index
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
653
|
}
|
|
673
654
|
},
|
|
674
655
|
widgetOptionsByType(name) {
|