apostrophe 3.61.1 → 3.63.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 +49 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +6 -4
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +9 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +8 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +6 -3
- package/modules/@apostrophecms/doc/index.js +256 -7
- package/modules/@apostrophecms/doc/ui/apos/mixins/AposFieldMetaUtilsMixin.js +93 -0
- package/modules/@apostrophecms/doc-type/index.js +78 -12
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +9 -1
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +24 -6
- package/modules/@apostrophecms/i18n/i18n/en.json +1 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +8 -7
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +1 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +5 -2
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +1 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +4 -2
- package/modules/@apostrophecms/login/index.js +25 -19
- package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +11 -1
- package/modules/@apostrophecms/login/ui/apos/logic/AposLoginForm.js +46 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +8 -3
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +3 -0
- package/modules/@apostrophecms/page/index.js +118 -27
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +3 -1
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +7 -0
- package/modules/@apostrophecms/page-type/index.js +81 -4
- package/modules/@apostrophecms/permission/index.js +60 -31
- package/modules/@apostrophecms/piece-type/index.js +19 -41
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +9 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposUtilityOperations.vue +16 -1
- package/modules/@apostrophecms/rich-text-widget/index.js +141 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +8 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapImage.vue +7 -7
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +38 -79
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Link.js +11 -0
- package/modules/@apostrophecms/schema/index.js +9 -0
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +22 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +4 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +7 -8
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +2 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +76 -30
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +2 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +7 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +13 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +5 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +21 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +12 -8
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +35 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +6 -0
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +41 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposCheckbox.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +7 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +8 -0
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +10 -4
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +2 -2
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +7 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +6 -0
- package/package.json +2 -2
- package/test/areas.js +1 -1
- package/test/assets.js +2 -2
- package/test/attachments.js +2 -2
- package/test/base-module.js +2 -1
- package/test/base-url-env-var.js +2 -2
- package/test/change-doc-ids.js +33 -31
- package/test/command-menu.js +2 -2
- package/test/content-i18n.js +47 -46
- package/test/db.js +2 -2
- package/test/docs.js +301 -126
- package/test/draft-published.js +2 -2
- package/test/email.js +2 -1
- package/test/express.js +3 -2
- package/test/external-front.js +4 -4
- package/test/field-meta.js +363 -0
- package/test/global.js +2 -1
- package/test/http.js +4 -2
- package/test/images.js +87 -88
- package/test/job.js +34 -34
- package/test/locks.js +2 -2
- package/test/login-requirements.js +3 -2
- package/test/middleware-and-route-order.js +2 -2
- package/test/page-type.js +2 -1
- package/test/pages-autocomplete.js +192 -0
- package/test/pages-public-api.js +2 -2
- package/test/pages-rest.js +4 -4
- package/test/pages.js +389 -57
- package/test/parked-pages.js +47 -47
- package/test/permissions.js +76 -0
- package/test/pieces-page-type.js +2 -1
- package/test/pieces-public-api.js +38 -38
- package/test/pieces.js +4 -4
- package/test/published-pages.js +16 -16
- package/test/rich-text-widget.js +164 -0
- package/test/schema-queryBuilders.js +180 -0
- package/test/schemaBuilders.js +4 -4
- package/test/schemas.js +220 -221
- package/test/search.js +2 -2
- package/test/soft-redirects.js +2 -1
- package/test/templates.js +2 -2
- package/test/users.js +2 -1
|
@@ -128,7 +128,7 @@ module.exports = {
|
|
|
128
128
|
return {
|
|
129
129
|
add: {
|
|
130
130
|
new: {
|
|
131
|
-
|
|
131
|
+
canCreate: true,
|
|
132
132
|
relationship: true,
|
|
133
133
|
label: {
|
|
134
134
|
key: 'apostrophe:newDocType',
|
|
@@ -156,7 +156,7 @@ module.exports = {
|
|
|
156
156
|
description: 'apostrophe:publishingBatchConfirmation',
|
|
157
157
|
confirmationButton: 'apostrophe:publishingBatchConfirmationButton'
|
|
158
158
|
},
|
|
159
|
-
permission: '
|
|
159
|
+
permission: 'publish'
|
|
160
160
|
},
|
|
161
161
|
archive: {
|
|
162
162
|
label: 'apostrophe:archive',
|
|
@@ -173,7 +173,7 @@ module.exports = {
|
|
|
173
173
|
description: 'apostrophe:archivingBatchConfirmation',
|
|
174
174
|
confirmationButton: 'apostrophe:archivingBatchConfirmationButton'
|
|
175
175
|
},
|
|
176
|
-
permission: '
|
|
176
|
+
permission: 'delete'
|
|
177
177
|
},
|
|
178
178
|
restore: {
|
|
179
179
|
label: 'apostrophe:restore',
|
|
@@ -670,10 +670,18 @@ module.exports = {
|
|
|
670
670
|
},
|
|
671
671
|
// Similar to insertDraftOf, invoked on first publication.
|
|
672
672
|
insertPublishedOf(req, doc, published, options) {
|
|
673
|
+
// Check publish permission up front because we won't check it
|
|
674
|
+
// in insert
|
|
675
|
+
if (!self.apos.permission.can(req, 'publish', doc)) {
|
|
676
|
+
throw self.apos.error('forbidden');
|
|
677
|
+
}
|
|
673
678
|
return self.insert(
|
|
674
679
|
req.clone({ mode: 'published' }),
|
|
675
680
|
published,
|
|
676
|
-
|
|
681
|
+
{
|
|
682
|
+
...options,
|
|
683
|
+
permissions: false
|
|
684
|
+
}
|
|
677
685
|
);
|
|
678
686
|
},
|
|
679
687
|
// Returns one editable piece matching the criteria, throws `notfound`
|
|
@@ -703,39 +711,6 @@ module.exports = {
|
|
|
703
711
|
async delete(req, piece, options = {}) {
|
|
704
712
|
return self.apos.doc.delete(req, piece, options);
|
|
705
713
|
},
|
|
706
|
-
composeFilters() {
|
|
707
|
-
self.filters = Object.keys(self.filters).map((key) => ({
|
|
708
|
-
name: key,
|
|
709
|
-
...self.filters[key],
|
|
710
|
-
inputType: self.filters[key].inputType || 'select'
|
|
711
|
-
}));
|
|
712
|
-
// Add a null choice if not already added or set to `required`
|
|
713
|
-
self.filters.forEach((filter) => {
|
|
714
|
-
if (filter.choices) {
|
|
715
|
-
if (
|
|
716
|
-
!filter.required &&
|
|
717
|
-
filter.choices &&
|
|
718
|
-
!filter.choices.find((choice) => choice.value === null)
|
|
719
|
-
) {
|
|
720
|
-
filter.def = null;
|
|
721
|
-
filter.choices.push({
|
|
722
|
-
value: null,
|
|
723
|
-
label: 'apostrophe:none'
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
} else {
|
|
727
|
-
// Dynamic choices from the REST API, but
|
|
728
|
-
// we need a label for "no opinion"
|
|
729
|
-
filter.nullLabel = 'Choose One';
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
},
|
|
733
|
-
composeColumns() {
|
|
734
|
-
self.columns = Object.keys(self.columns).map((key) => ({
|
|
735
|
-
name: key,
|
|
736
|
-
...self.columns[key]
|
|
737
|
-
}));
|
|
738
|
-
},
|
|
739
714
|
// Enable inclusion of this type in sitewide search results
|
|
740
715
|
searchDetermineTypes(types) {
|
|
741
716
|
if (self.options.searchable !== false) {
|
|
@@ -850,7 +825,10 @@ module.exports = {
|
|
|
850
825
|
return self.findOneForEditing(
|
|
851
826
|
req,
|
|
852
827
|
{ _id: piece._id },
|
|
853
|
-
{
|
|
828
|
+
{
|
|
829
|
+
attachments: true,
|
|
830
|
+
permission: 'create'
|
|
831
|
+
}
|
|
854
832
|
);
|
|
855
833
|
},
|
|
856
834
|
|
|
@@ -1153,14 +1131,14 @@ module.exports = {
|
|
|
1153
1131
|
browserOptions.batchOperations = self.checkBatchOperationsPermissions(req);
|
|
1154
1132
|
browserOptions.utilityOperations = self.utilityOperations;
|
|
1155
1133
|
browserOptions.insertViaUpload = self.options.insertViaUpload;
|
|
1156
|
-
browserOptions.quickCreate = !self.options.singleton && self.options.quickCreate &&
|
|
1134
|
+
browserOptions.quickCreate = !self.options.singleton && self.options.quickCreate && browserOptions.canCreate;
|
|
1157
1135
|
browserOptions.singleton = self.options.singleton;
|
|
1158
1136
|
browserOptions.showCreate = !self.options.singleton && self.options.showCreate;
|
|
1159
1137
|
browserOptions.showDismissSubmission = self.options.showDismissSubmission;
|
|
1160
1138
|
browserOptions.showArchive = self.options.showArchive;
|
|
1161
1139
|
browserOptions.showDiscardDraft = self.options.showDiscardDraft;
|
|
1162
|
-
browserOptions.
|
|
1163
|
-
|
|
1140
|
+
browserOptions.canDeleteDraft = self.apos.permission.can(req, 'delete', self.name, 'draft');
|
|
1141
|
+
|
|
1164
1142
|
_.defaults(browserOptions, {
|
|
1165
1143
|
components: {}
|
|
1166
1144
|
});
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
@click="saveRelationship"
|
|
31
31
|
/>
|
|
32
32
|
<AposButton
|
|
33
|
-
v-else-if="moduleOptions.
|
|
33
|
+
v-else-if="moduleOptions.canCreate && moduleOptions.showCreate"
|
|
34
34
|
:label="{
|
|
35
35
|
key: 'apostrophe:newDocType',
|
|
36
36
|
type: $t(moduleOptions.label)
|
|
@@ -297,6 +297,14 @@ export default {
|
|
|
297
297
|
withPublished: 1
|
|
298
298
|
};
|
|
299
299
|
|
|
300
|
+
const type = this.relationshipField?.withType;
|
|
301
|
+
const isPage = apos.modules['@apostrophecms/page'].validPageTypes
|
|
302
|
+
.includes(type);
|
|
303
|
+
|
|
304
|
+
if (isPage) {
|
|
305
|
+
options.type = type;
|
|
306
|
+
}
|
|
307
|
+
|
|
300
308
|
// Avoid undefined properties.
|
|
301
309
|
const qs = Object.entries(options)
|
|
302
310
|
.reduce((acc, [ key, val ]) => ({
|
|
@@ -95,7 +95,13 @@ export default {
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
setUtilityOperations () {
|
|
98
|
-
const {
|
|
98
|
+
const {
|
|
99
|
+
utilityOperations,
|
|
100
|
+
canPublish,
|
|
101
|
+
canEdit,
|
|
102
|
+
canArchive,
|
|
103
|
+
canCreate
|
|
104
|
+
} = this.moduleOptions;
|
|
99
105
|
|
|
100
106
|
const operations = ((Array.isArray(utilityOperations) && utilityOperations) || []).filter(operation => {
|
|
101
107
|
let ok = true;
|
|
@@ -106,9 +112,18 @@ export default {
|
|
|
106
112
|
ok = !operation.relationship;
|
|
107
113
|
}
|
|
108
114
|
}
|
|
115
|
+
if (operation.canCreate) {
|
|
116
|
+
ok = ok && canCreate;
|
|
117
|
+
}
|
|
109
118
|
if (operation.canEdit) {
|
|
110
119
|
ok = ok && canEdit;
|
|
111
120
|
}
|
|
121
|
+
if (operation.canArchive) {
|
|
122
|
+
ok = ok && canArchive;
|
|
123
|
+
}
|
|
124
|
+
if (operation.canPublish) {
|
|
125
|
+
ok = ok && canPublish;
|
|
126
|
+
}
|
|
112
127
|
return ok;
|
|
113
128
|
});
|
|
114
129
|
|
|
@@ -6,6 +6,84 @@ const cheerio = require('cheerio');
|
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
8
8
|
extend: '@apostrophecms/widget-type',
|
|
9
|
+
cascades: [ 'linkFields' ],
|
|
10
|
+
linkFields(self, options) {
|
|
11
|
+
const linkWithType = (Array.isArray(options.linkWithType)
|
|
12
|
+
? options.linkWithType
|
|
13
|
+
: [ options.linkWithType ]);
|
|
14
|
+
|
|
15
|
+
// Labels are not available at the time the schema is built,
|
|
16
|
+
// they are added on modulesRegistered.
|
|
17
|
+
const linkWithTypeChoices = linkWithType
|
|
18
|
+
.map(type => ({
|
|
19
|
+
label: type,
|
|
20
|
+
value: type
|
|
21
|
+
}))
|
|
22
|
+
.concat([
|
|
23
|
+
{
|
|
24
|
+
label: 'apostrophe:url',
|
|
25
|
+
value: '_url'
|
|
26
|
+
}
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const linkWithTypeFields = linkWithType.reduce((fields, type) => {
|
|
30
|
+
const name = `_${type}`;
|
|
31
|
+
fields[name] = {
|
|
32
|
+
type: 'relationship',
|
|
33
|
+
label: type,
|
|
34
|
+
withType: type,
|
|
35
|
+
required: true,
|
|
36
|
+
max: 1,
|
|
37
|
+
browse: true,
|
|
38
|
+
if: {
|
|
39
|
+
linkTo: type
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return fields;
|
|
43
|
+
}, {});
|
|
44
|
+
linkWithTypeFields.updateTitle = {
|
|
45
|
+
label: 'apostrophe:updateTitle',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
def: true,
|
|
48
|
+
if: {
|
|
49
|
+
$or: linkWithType.map(type => ({
|
|
50
|
+
linkTo: type
|
|
51
|
+
}))
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
add: {
|
|
57
|
+
linkTo: {
|
|
58
|
+
label: 'apostrophe:linkTo',
|
|
59
|
+
type: 'select',
|
|
60
|
+
choices: linkWithTypeChoices,
|
|
61
|
+
required: true,
|
|
62
|
+
def: linkWithTypeChoices[0].value
|
|
63
|
+
},
|
|
64
|
+
...linkWithTypeFields,
|
|
65
|
+
href: {
|
|
66
|
+
label: 'apostrophe:url',
|
|
67
|
+
type: 'string',
|
|
68
|
+
required: true,
|
|
69
|
+
if: {
|
|
70
|
+
linkTo: '_url'
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
target: {
|
|
74
|
+
label: 'apostrophe:linkTarget',
|
|
75
|
+
type: 'checkboxes',
|
|
76
|
+
htmlAttribute: 'target',
|
|
77
|
+
choices: [
|
|
78
|
+
{
|
|
79
|
+
label: 'apostrophe:openLinkInNewTab',
|
|
80
|
+
value: '_blank'
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
},
|
|
9
87
|
options: {
|
|
10
88
|
icon: 'format-text-icon',
|
|
11
89
|
label: 'apostrophe:richText',
|
|
@@ -270,6 +348,18 @@ module.exports = {
|
|
|
270
348
|
'format-color-highlight-icon': 'FormatColorHighlight',
|
|
271
349
|
'table-icon': 'Table'
|
|
272
350
|
},
|
|
351
|
+
handlers(self) {
|
|
352
|
+
return {
|
|
353
|
+
'apostrophe:modulesRegistered': {
|
|
354
|
+
validateAndFixLinkWithTypes() {
|
|
355
|
+
self.validateAndFixLinkWithTypes();
|
|
356
|
+
},
|
|
357
|
+
composeLinkSchema() {
|
|
358
|
+
self.composeLinkSchema();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
},
|
|
273
363
|
methods(self) {
|
|
274
364
|
return {
|
|
275
365
|
// Return just the rich text of the widget, which may be undefined or null if it has not yet been edited
|
|
@@ -404,7 +494,9 @@ module.exports = {
|
|
|
404
494
|
'href',
|
|
405
495
|
'id',
|
|
406
496
|
'name',
|
|
407
|
-
|
|
497
|
+
...self.linkSchema
|
|
498
|
+
.filter(field => field.htmlAttribute)
|
|
499
|
+
.map(field => field.htmlAttribute)
|
|
408
500
|
]
|
|
409
501
|
},
|
|
410
502
|
alignLeft: {
|
|
@@ -675,6 +767,53 @@ module.exports = {
|
|
|
675
767
|
}
|
|
676
768
|
}
|
|
677
769
|
return content;
|
|
770
|
+
},
|
|
771
|
+
// Validate the types provided for links, update labels derived from
|
|
772
|
+
// corresponding modules, as they are not available at the time
|
|
773
|
+
// the schema is generated.
|
|
774
|
+
validateAndFixLinkWithTypes() {
|
|
775
|
+
const linkWithType = (Array.isArray(self.options.linkWithType)
|
|
776
|
+
? self.options.linkWithType
|
|
777
|
+
: [ self.options.linkWithType ]);
|
|
778
|
+
|
|
779
|
+
for (const type of linkWithType) {
|
|
780
|
+
if (!self.apos.modules[type]) {
|
|
781
|
+
throw new Error(
|
|
782
|
+
`The linkWithType option of rich text widget "${type}" must be a valid module type`
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
self.linkFields[`_${type}`].label = getLabel(type);
|
|
787
|
+
const choice = self.linkFields.linkTo.choices
|
|
788
|
+
.find(choice => choice.value === type);
|
|
789
|
+
|
|
790
|
+
choice.label = getLabel(type);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function getLabel(type) {
|
|
794
|
+
if ([ '@apostrophecms/any-page-type', '@apostrophecms/page' ].includes(type)) {
|
|
795
|
+
return 'apostrophe:page';
|
|
796
|
+
}
|
|
797
|
+
return self.apos.modules[type].options?.label ?? type;
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
// Compose and register the link schema.
|
|
801
|
+
composeLinkSchema() {
|
|
802
|
+
self.linkSchema = self.apos.schema.compose({
|
|
803
|
+
addFields: self.apos.schema.fieldsToArray(`Links ${self.__meta.name}`, self.linkFields),
|
|
804
|
+
arrangeFields: self.apos.schema.groupsToArray(self.linkFieldsGroups)
|
|
805
|
+
}, self);
|
|
806
|
+
|
|
807
|
+
self.apos.schema.validate(self.linkSchema, {
|
|
808
|
+
type: 'link',
|
|
809
|
+
subtype: self.__meta.name
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
// Don't allow htmlAttribute `href`, it's a special case.
|
|
813
|
+
const hrefField = self.linkSchema.find(field => field.htmlAttribute === 'href');
|
|
814
|
+
if (hrefField) {
|
|
815
|
+
throw new Error(`Field "${hrefField.name}" validation error: "htmlAttribute: href" is not allowed.`);
|
|
816
|
+
}
|
|
678
817
|
}
|
|
679
818
|
};
|
|
680
819
|
},
|
|
@@ -739,6 +878,7 @@ module.exports = {
|
|
|
739
878
|
// Not optional in presence of an insert menu, it's not acceptable UX without it
|
|
740
879
|
placeholderTextWithInsertMenu: self.options.placeholderTextWithInsertMenu,
|
|
741
880
|
linkWithType: Array.isArray(self.options.linkWithType) ? self.options.linkWithType : [ self.options.linkWithType ],
|
|
881
|
+
linkSchema: self.linkSchema,
|
|
742
882
|
imageStyles: self.options.imageStyles
|
|
743
883
|
};
|
|
744
884
|
return finalData;
|
package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue
CHANGED
|
@@ -161,6 +161,14 @@ export default {
|
|
|
161
161
|
return {};
|
|
162
162
|
}
|
|
163
163
|
},
|
|
164
|
+
// not used, but we need to keep it here to avoid
|
|
165
|
+
// an attribute [object Object]
|
|
166
|
+
meta: {
|
|
167
|
+
type: Object,
|
|
168
|
+
default() {
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
},
|
|
164
172
|
docId: {
|
|
165
173
|
type: String,
|
|
166
174
|
required: false,
|
|
@@ -42,13 +42,6 @@ export default {
|
|
|
42
42
|
active: false
|
|
43
43
|
};
|
|
44
44
|
},
|
|
45
|
-
watch: {
|
|
46
|
-
hasSelection(newVal, oldVal) {
|
|
47
|
-
if (!newVal) {
|
|
48
|
-
this.close();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
45
|
computed: {
|
|
53
46
|
attributes() {
|
|
54
47
|
return this.editor.getAttributes('image');
|
|
@@ -71,6 +64,13 @@ export default {
|
|
|
71
64
|
return text !== '' || type?.name === 'image';
|
|
72
65
|
}
|
|
73
66
|
},
|
|
67
|
+
watch: {
|
|
68
|
+
hasSelection(newVal, oldVal) {
|
|
69
|
+
if (!newVal) {
|
|
70
|
+
this.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
74
|
methods: {
|
|
75
75
|
click() {
|
|
76
76
|
if (this.hasSelection) {
|
|
@@ -86,11 +86,11 @@ export default {
|
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
data() {
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
return {
|
|
91
91
|
generation: 1,
|
|
92
92
|
href: null,
|
|
93
|
-
target: null,
|
|
93
|
+
// target: null,
|
|
94
94
|
active: false,
|
|
95
95
|
hasLinkOnOpen: false,
|
|
96
96
|
triggerValidation: false,
|
|
@@ -98,73 +98,7 @@ export default {
|
|
|
98
98
|
data: {}
|
|
99
99
|
},
|
|
100
100
|
formModifiers: [ 'small', 'margin-micro' ],
|
|
101
|
-
originalSchema:
|
|
102
|
-
{
|
|
103
|
-
name: 'linkTo',
|
|
104
|
-
label: this.$t('apostrophe:linkTo'),
|
|
105
|
-
type: 'select',
|
|
106
|
-
def: linkWithType[0],
|
|
107
|
-
required: true,
|
|
108
|
-
choices: [
|
|
109
|
-
...(linkWithType.map(type => {
|
|
110
|
-
return {
|
|
111
|
-
// Should already be localized server side
|
|
112
|
-
label: apos.modules[type].label,
|
|
113
|
-
value: type
|
|
114
|
-
};
|
|
115
|
-
})),
|
|
116
|
-
{
|
|
117
|
-
// TODO this needs i18n
|
|
118
|
-
label: this.$t('apostrophe:url'),
|
|
119
|
-
// Value that will never be a doc type
|
|
120
|
-
value: '_url'
|
|
121
|
-
}
|
|
122
|
-
]
|
|
123
|
-
},
|
|
124
|
-
...getOptions().linkWithType.map(type => ({
|
|
125
|
-
name: `_${type}`,
|
|
126
|
-
type: 'relationship',
|
|
127
|
-
label: apos.modules[type].label,
|
|
128
|
-
withType: type,
|
|
129
|
-
required: true,
|
|
130
|
-
max: 1,
|
|
131
|
-
browse: true,
|
|
132
|
-
if: {
|
|
133
|
-
linkTo: type
|
|
134
|
-
}
|
|
135
|
-
})),
|
|
136
|
-
{
|
|
137
|
-
name: 'updateTitle',
|
|
138
|
-
label: this.$t('apostrophe:updateTitle'),
|
|
139
|
-
type: 'boolean',
|
|
140
|
-
def: true,
|
|
141
|
-
if: {
|
|
142
|
-
$or: linkWithType.map(type => ({
|
|
143
|
-
linkTo: type
|
|
144
|
-
}))
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
name: 'href',
|
|
149
|
-
label: this.$t('apostrophe:url'),
|
|
150
|
-
type: 'string',
|
|
151
|
-
required: true,
|
|
152
|
-
if: {
|
|
153
|
-
linkTo: '_url'
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: 'target',
|
|
158
|
-
label: this.$t('apostrophe:linkTarget'),
|
|
159
|
-
type: 'checkboxes',
|
|
160
|
-
choices: [
|
|
161
|
-
{
|
|
162
|
-
label: this.$t('apostrophe:openLinkInNewTab'),
|
|
163
|
-
value: '_blank'
|
|
164
|
-
}
|
|
165
|
-
]
|
|
166
|
-
}
|
|
167
|
-
]
|
|
101
|
+
originalSchema: getOptions().linkSchema
|
|
168
102
|
};
|
|
169
103
|
},
|
|
170
104
|
computed: {
|
|
@@ -186,6 +120,9 @@ export default {
|
|
|
186
120
|
},
|
|
187
121
|
schema() {
|
|
188
122
|
return this.originalSchema;
|
|
123
|
+
},
|
|
124
|
+
schemaHtmlAttributes() {
|
|
125
|
+
return this.schema.filter(item => !!item.htmlAttribute);
|
|
189
126
|
}
|
|
190
127
|
},
|
|
191
128
|
watch: {
|
|
@@ -249,10 +186,22 @@ export default {
|
|
|
249
186
|
if (this.docFields.data.target && !this.docFields.data.href) {
|
|
250
187
|
delete this.docFields.data.target;
|
|
251
188
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
189
|
+
|
|
190
|
+
const attrs = this.schemaHtmlAttributes.reduce((acc, field) => {
|
|
191
|
+
const value = this.docFields.data[field.name];
|
|
192
|
+
if (field.type === 'checkboxes' && !value?.[0]) {
|
|
193
|
+
return acc;
|
|
194
|
+
}
|
|
195
|
+
if (field.type === 'boolean') {
|
|
196
|
+
acc[field.htmlAttribute] = value === true ? '' : null;
|
|
197
|
+
return acc;
|
|
198
|
+
}
|
|
199
|
+
acc[field.htmlAttribute] = Array.isArray(value) ? value[0] : value;
|
|
200
|
+
return acc;
|
|
201
|
+
}, {});
|
|
202
|
+
attrs.href = this.docFields.data.href;
|
|
203
|
+
this.editor.commands.setLink(attrs);
|
|
204
|
+
|
|
256
205
|
this.close();
|
|
257
206
|
});
|
|
258
207
|
},
|
|
@@ -272,13 +221,23 @@ export default {
|
|
|
272
221
|
},
|
|
273
222
|
async populateFields() {
|
|
274
223
|
try {
|
|
275
|
-
const attrs = this.attributes;
|
|
276
|
-
if (attrs.target) {
|
|
277
|
-
// checkboxes field expects an array
|
|
278
|
-
attrs.target = [ attrs.target ];
|
|
279
|
-
}
|
|
224
|
+
const attrs = { ...this.attributes };
|
|
280
225
|
this.docFields.data = {};
|
|
281
226
|
this.schema.forEach((item) => {
|
|
227
|
+
if (item.htmlAttribute && item.type === 'checkboxes') {
|
|
228
|
+
this.docFields.data[item.name] = attrs[item.htmlAttribute] ? [ attrs[item.htmlAttribute] ] : [];
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (item.htmlAttribute && item.type === 'boolean') {
|
|
232
|
+
this.docFields.data[item.name] = attrs[item.htmlAttribute] === null
|
|
233
|
+
? null
|
|
234
|
+
: (attrs[item.htmlAttribute] === '');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (item.htmlAttribute) {
|
|
238
|
+
this.docFields.data[item.name] = attrs[item.htmlAttribute] || '';
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
282
241
|
this.docFields.data[item.name] = attrs[item.name] || '';
|
|
283
242
|
});
|
|
284
243
|
const matches = this.docFields.data.href.match(/^#apostrophe-permalink-(.*)\?updateTitle=(\d)$/);
|
|
@@ -361,7 +320,7 @@ function getOptions() {
|
|
|
361
320
|
}
|
|
362
321
|
|
|
363
322
|
// special schema style for this use
|
|
364
|
-
.apos-link-control ::v-deep .apos-field--
|
|
323
|
+
.apos-link-control ::v-deep .apos-field--checkboxes {
|
|
365
324
|
.apos-field__label {
|
|
366
325
|
display: none;
|
|
367
326
|
}
|
|
@@ -8,6 +8,17 @@ export default (options) => {
|
|
|
8
8
|
linkOnPaste: true,
|
|
9
9
|
HTMLAttributes: {}
|
|
10
10
|
};
|
|
11
|
+
},
|
|
12
|
+
addAttributes() {
|
|
13
|
+
return {
|
|
14
|
+
...this.parent?.(),
|
|
15
|
+
...apos.modules['@apostrophecms/rich-text-widget'].linkSchema
|
|
16
|
+
.filter(field => !!field.htmlAttribute)
|
|
17
|
+
.reduce((obj, field) => {
|
|
18
|
+
obj[field.htmlAttribute] = { default: field.def ?? null };
|
|
19
|
+
return obj;
|
|
20
|
+
}, {})
|
|
21
|
+
};
|
|
11
22
|
}
|
|
12
23
|
});
|
|
13
24
|
};
|
|
@@ -28,6 +28,7 @@ module.exports = {
|
|
|
28
28
|
self.fieldsById = {};
|
|
29
29
|
self.arrayManagers = {};
|
|
30
30
|
self.objectManagers = {};
|
|
31
|
+
self.fieldMetadataComponents = [];
|
|
31
32
|
|
|
32
33
|
self.enableBrowserData();
|
|
33
34
|
|
|
@@ -1127,6 +1128,13 @@ module.exports = {
|
|
|
1127
1128
|
return self.fieldTypes[typeName];
|
|
1128
1129
|
},
|
|
1129
1130
|
|
|
1131
|
+
addFieldMetadataComponent(namespace, component) {
|
|
1132
|
+
self.fieldMetadataComponents.push({
|
|
1133
|
+
name: component,
|
|
1134
|
+
namespace
|
|
1135
|
+
});
|
|
1136
|
+
},
|
|
1137
|
+
|
|
1130
1138
|
// Given a schema and a query, add query builders to the query
|
|
1131
1139
|
// for each of the fields in the schema, based on their field type,
|
|
1132
1140
|
// if supported by the field type. If the field already has a
|
|
@@ -1762,6 +1770,7 @@ module.exports = {
|
|
|
1762
1770
|
}
|
|
1763
1771
|
browserOptions.action = self.action;
|
|
1764
1772
|
browserOptions.components = { fields: fields };
|
|
1773
|
+
browserOptions.fieldMetadataComponents = self.fieldMetadataComponents;
|
|
1765
1774
|
return browserOptions;
|
|
1766
1775
|
}
|
|
1767
1776
|
};
|
|
@@ -192,6 +192,9 @@ module.exports = (self) => {
|
|
|
192
192
|
query.and(criteria);
|
|
193
193
|
}
|
|
194
194
|
},
|
|
195
|
+
launder: function (s) {
|
|
196
|
+
return self.apos.launder.string(s);
|
|
197
|
+
},
|
|
195
198
|
choices: async function () {
|
|
196
199
|
return self.sortedDistinct(field.name, query);
|
|
197
200
|
}
|
|
@@ -461,6 +464,7 @@ module.exports = (self) => {
|
|
|
461
464
|
$gte: value[0],
|
|
462
465
|
$lte: value[1]
|
|
463
466
|
};
|
|
467
|
+
query.and(criteria);
|
|
464
468
|
} else {
|
|
465
469
|
criteria = {};
|
|
466
470
|
criteria[field.name] = self.apos.launder.integer(value);
|
|
@@ -471,8 +475,14 @@ module.exports = (self) => {
|
|
|
471
475
|
choices: async function () {
|
|
472
476
|
return self.sortedDistinct(field.name, query);
|
|
473
477
|
},
|
|
474
|
-
launder: function (
|
|
475
|
-
|
|
478
|
+
launder: function (value) {
|
|
479
|
+
const launderInteger = (v) => self.apos.launder.integer(v, null);
|
|
480
|
+
|
|
481
|
+
if (Array.isArray(value)) {
|
|
482
|
+
return value.map(launderInteger);
|
|
483
|
+
} else {
|
|
484
|
+
return launderInteger(value);
|
|
485
|
+
}
|
|
476
486
|
}
|
|
477
487
|
});
|
|
478
488
|
}
|
|
@@ -505,6 +515,7 @@ module.exports = (self) => {
|
|
|
505
515
|
$gte: value[0],
|
|
506
516
|
$lte: value[1]
|
|
507
517
|
};
|
|
518
|
+
query.and(criteria);
|
|
508
519
|
} else {
|
|
509
520
|
criteria = {};
|
|
510
521
|
criteria[field.name] = self.apos.launder.float(value);
|
|
@@ -514,6 +525,15 @@ module.exports = (self) => {
|
|
|
514
525
|
},
|
|
515
526
|
choices: async function () {
|
|
516
527
|
return self.sortedDistinct(field.name, query);
|
|
528
|
+
},
|
|
529
|
+
launder: function(value) {
|
|
530
|
+
const launderFloat = (v) => self.apos.launder.float(v, null);
|
|
531
|
+
|
|
532
|
+
if (Array.isArray(value)) {
|
|
533
|
+
return value.map(launderFloat);
|
|
534
|
+
} else {
|
|
535
|
+
return launderFloat(value);
|
|
536
|
+
}
|
|
517
537
|
}
|
|
518
538
|
});
|
|
519
539
|
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
:field="field"
|
|
4
4
|
:error="effectiveError" :uid="uid"
|
|
5
5
|
:display-options="displayOptions"
|
|
6
|
-
:modifiers="modifiers"
|
|
6
|
+
:modifiers="[...modifiers, 'full-width']"
|
|
7
|
+
:items="next.items"
|
|
8
|
+
:meta="areaMeta"
|
|
7
9
|
>
|
|
8
10
|
<template #body>
|
|
9
11
|
<!-- data-apos-schema-area lets all the child areas know that this area is in a schema (which is in a modal)
|
|
@@ -18,6 +20,7 @@
|
|
|
18
20
|
:is="editorComponent"
|
|
19
21
|
:options="field.options"
|
|
20
22
|
:items="next.items"
|
|
23
|
+
:meta="areaMeta"
|
|
21
24
|
:choices="choices"
|
|
22
25
|
:id="next._id"
|
|
23
26
|
:field-id="field._id"
|