Skip to content

Commit bb5e8da

Browse files
Merge the "features to support sections in core" branch (#5032)
* PRO-7339 PRO-7337 Supporting features for sections (#5023) * features to support sections * wip * working * get the field id through to the sections module * fix bugs, run quiet * modal excludes action * wip * made the expanded menus work * changelog * actual eslint fixes, also tell eslint to chill * button style --------- Co-authored-by: Stuart Romanek <stuart@apostrophecms.com> * stylelint mad about buttons (#5033) * more merge stuff (#5037) * more merge stuff * linty stuff --------- Co-authored-by: Stuart Romanek <stuart@apostrophecms.com>
1 parent 0f7b06f commit bb5e8da

14 files changed

Lines changed: 275 additions & 79 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
### Adds
66

7+
* 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.
8+
* `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.
9+
* `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.
10+
* 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`.
711
* Adds quick image upload UI to `@apostrophecms/image-widget`.
812

913
## 4.20.0 (2025-08-06)
@@ -15,8 +19,11 @@
1519

1620
### Changes
1721

22+
* 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.
23+
* Adds any alt text found in an attribute to the media library attachment during import of rich text inline images by API
24+
* Adds `prependNodes` and `appendNodes` methods to every module. These methods allow you to inject HTML to every page using a `node` declaration.
1825
* Changes handling of `order` and `groups` in the `admin-bar` module to respect, rather that reverse, the order of items
19-
* Interacting with the text inside a rich text widget will hide the widget controls to prevent awkawrd text selection.
26+
* Interacting with the text inside a rich text widget will hide the widget controls to prevent awkward text selection.
2027

2128
### Fixes
2229

modules/@apostrophecms/area/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ module.exports = {
8888

8989
self.enableBrowserData();
9090
self.addDeduplicateWidgetIdsMigration();
91+
self.createWidgetOperations = [];
9192
},
9293
apiRoutes(self) {
9394
return {
@@ -786,7 +787,6 @@ module.exports = {
786787
manager.options.initialModal !== false;
787788
contextualWidgetDefaultData[name] = manager.options.defaultData || {};
788789
});
789-
790790
return {
791791
components: {
792792
editor: 'AposAreaEditor',
@@ -799,7 +799,8 @@ module.exports = {
799799
widgetPreview,
800800
contextualWidgetDefaultData,
801801
widgetManagers,
802-
action: self.action
802+
action: self.action,
803+
createWidgetOperations: self.createWidgetOperations
803804
};
804805
},
805806
async addDeduplicateWidgetIdsMigration() {
@@ -825,6 +826,9 @@ module.exports = {
825826
}
826827
});
827828
});
829+
},
830+
addCreateWidgetOperation(operation) {
831+
self.createWidgetOperations.push(operation);
828832
}
829833
};
830834
},

modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
<AposAreaMenuItem
7373
:item="child"
7474
:tabbable="itemIndex === active"
75-
@click="add(child)"
75+
@click="action(child)"
7676
@up="switchItem(`child-${itemIndex}-${childIndex - 1}`, -1)"
7777
@down="switchItem(`child-${itemIndex}-${childIndex + 1}`, 1)"
7878
/>
@@ -83,7 +83,7 @@
8383
<AposAreaMenuItem
8484
v-else
8585
:item="item"
86-
@click="add(item)"
86+
@click="action(item)"
8787
@up="switchItem(`item-${itemIndex - 1}`, -1)"
8888
@down="switchItem(`item-${itemIndex + 1}`, 1)"
8989
/>
@@ -130,6 +130,10 @@ export default {
130130
return {};
131131
}
132132
},
133+
fieldId: {
134+
type: String,
135+
required: true
136+
},
133137
menuId: {
134138
type: String,
135139
default() {
@@ -196,8 +200,14 @@ export default {
196200
// If the menu is not open, we don't need to compute it right now
197201
return [];
198202
}
199-
const clipboard = apos.area.widgetClipboard.get();
200203
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();
201211
if (clipboard) {
202212
const widget = clipboard;
203213
const matchingChoice = menu.find(option => option.name === widget.type);
@@ -226,7 +236,24 @@ export default {
226236
this.inContext = !apos.util.closest(this.$el, '[data-apos-schema-area]');
227237
},
228238
methods: {
229-
async add(item) {
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+
}
230257
// Potential TODO: If we find ourselves manually flipping these bits in
231258
// other AposContextMenu overrides we should consider refactoring
232259
// contextmenus to be able to self close when any click takes place within

modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
:empty="true"
3333
:index="0"
3434
:options="options"
35+
:field-id="fieldId"
3536
:max-reached="maxReached"
3637
:disabled="field && field.readOnly"
3738
:widget-options="options.widgets"
@@ -80,9 +81,9 @@
8081

8182
<script>
8283
import { createId } from '@paralleldrive/cuid2';
83-
import { klona } from 'klona';
8484
import AposThemeMixin from 'Modules/@apostrophecms/ui/mixins/AposThemeMixin';
8585
import newInstance from 'apostrophe/modules/@apostrophecms/schema/lib/newInstance.js';
86+
import cloneWidget from 'Modules/@apostrophecms/area/lib/clone-widget.js';
8687
8788
export default {
8889
name: 'AposAreaEditor',
@@ -546,12 +547,7 @@ export default {
546547
}
547548
},
548549
clone(index) {
549-
const widget = klona(this.next[index]);
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]);
555551
this.insert({
556552
widget,
557553
index: index + 1
@@ -572,30 +568,6 @@ export default {
572568
}
573569
}
574570
},
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-
},
599571
async update(updated, { autosave = true, reverting = false } = {}) {
600572
if (!reverting) {
601573
updated.aposPlaceholder = false;
@@ -616,22 +588,29 @@ export default {
616588
});
617589
this.edited[updated._id] = true;
618590
},
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.
620595
async add({
621596
index,
622597
name,
598+
widget,
623599
clipboard
624600
}) {
625601
if (clipboard) {
626-
this.regenerateIds(
627-
apos.modules[apos.area.widgetManagers[clipboard.type]].schema,
628-
clipboard
629-
);
602+
clipboard = cloneWidget(clipboard);
630603
return this.insert({
631604
widget: clipboard,
632605
index
633606
});
634607
}
608+
if (widget) {
609+
return this.insert({
610+
widget,
611+
index
612+
});
613+
}
635614
if (this.widgetIsContextual(name)) {
636615
return this.insert({
637616
widget: {
@@ -643,10 +622,10 @@ export default {
643622
});
644623
}
645624
if (!this.widgetHasInitialModal(name)) {
646-
const widget = this.newWidget(name);
625+
const newWidget = this.newWidget(name);
647626
return this.insert({
648627
widget: {
649-
...widget,
628+
...newWidget,
650629
aposPlaceholder: this.widgetHasPlaceholder(name)
651630
},
652631
index
@@ -656,7 +635,7 @@ export default {
656635
const componentName = this.widgetEditorComponent(name);
657636
apos.area.activeEditor = this;
658637
const preview = this.widgetPreview(name, index, true);
659-
const widget = await apos.modal.execute(componentName, {
638+
const newWidget = await apos.modal.execute(componentName, {
660639
modelValue: null,
661640
options: this.widgetOptionsByType(name),
662641
type: name,
@@ -666,9 +645,9 @@ export default {
666645
preview
667646
});
668647
apos.area.activeEditor = null;
669-
if (widget) {
648+
if (newWidget) {
670649
return this.insert({
671-
widget,
650+
widget: newWidget,
672651
index
673652
});
674653
}

0 commit comments

Comments
 (0)