apostrophe 3.17.0 → 3.18.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/.editorconfig +3 -0
- package/.eslintrc +4 -3
- package/.github/workflows/main.yml +2 -2
- package/.stylelintrc +12 -2
- package/CHANGELOG.md +34 -2
- package/defaults.js +2 -2
- package/index.js +124 -33
- package/lib/escape-host.js +8 -0
- package/lib/mongodb-connect.js +55 -0
- package/lib/opentelemetry.js +144 -0
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +2 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +20 -8
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +10 -0
- package/modules/@apostrophecms/asset/lib/globalIcons.js +1 -0
- package/modules/@apostrophecms/attachment/index.js +81 -29
- package/modules/@apostrophecms/db/index.js +7 -10
- package/modules/@apostrophecms/doc/index.js +138 -23
- package/modules/@apostrophecms/doc-type/index.js +162 -63
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +39 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +11 -1
- package/modules/@apostrophecms/email/index.js +1 -1
- package/modules/@apostrophecms/express/index.js +2 -2
- package/modules/@apostrophecms/http/index.js +2 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +10 -0
- package/modules/@apostrophecms/i18n/i18n/es.json +7 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +7 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +7 -0
- package/modules/@apostrophecms/image/index.js +182 -1
- package/modules/@apostrophecms/image/ui/apos/apps/AposImageRelationshipQueryFilter.js +13 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +460 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +510 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +5 -1
- package/modules/@apostrophecms/image/ui/apos/lib/aspectRatios.js +26 -0
- package/modules/@apostrophecms/image-widget/views/widget.html +5 -2
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +45 -1
- package/modules/@apostrophecms/module/index.js +98 -17
- package/modules/@apostrophecms/module/lib/events.js +46 -11
- package/modules/@apostrophecms/page/index.js +55 -22
- package/modules/@apostrophecms/piece-page-type/index.js +1 -0
- package/modules/@apostrophecms/piece-type/index.js +13 -4
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +2 -2
- package/modules/@apostrophecms/rich-text-widget/index.js +1 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +4 -0
- package/modules/@apostrophecms/schema/index.js +79 -73
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +10 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +22 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +72 -36
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +7 -26
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +8 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +45 -15
- package/modules/@apostrophecms/task/index.js +106 -52
- package/modules/@apostrophecms/template/index.js +111 -76
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +42 -22
- package/modules/@apostrophecms/ui/ui/apos/components/AposSelect.vue +61 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +46 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +10 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +2 -22
- package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
- package/modules/@apostrophecms/widget-type/index.js +2 -23
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidget.vue +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +20 -1
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +0 -9
- package/package.json +16 -12
- package/scripts/lint-i18n.js +2 -2
- package/test/assets.js +2 -1
- package/test/attachments.js +119 -26
- package/test/bundle.js +1 -1
- package/test/content-i18n.js +6 -6
- package/test/docs.js +244 -4
- package/test/draft-published.js +41 -41
- package/test/express.js +1 -1
- package/test/http.js +2 -2
- package/test/images.js +94 -4
- package/test/job.js +1 -1
- package/test/locks.js +1 -1
- package/test/middleware-and-route-order.js +3 -3
- package/test/pages-public-api.js +48 -4
- package/test/pages-rest.js +20 -20
- package/test/pages.js +377 -11
- package/test/parked-pages.js +1 -1
- package/test/permissions.js +10 -10
- package/test/pieces-public-api.js +130 -6
- package/test/pieces.js +247 -60
- package/test/recursionGuard.js +6 -6
- package/test/restApiRoutes.js +6 -6
- package/test/schemaBuilders.js +7 -7
- package/test/schemas.js +59 -59
- package/test/search.js +3 -3
- package/test/soft-redirects.js +13 -13
- package/test/static-i18n.js +1 -1
- package/test/templates.js +10 -10
- package/test/urls.js +2 -2
- package/test/users.js +21 -21
- package/test/utils.js +13 -13
- package/test/widgets.js +2 -2
- package/test-lib/util.js +2 -5
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidget.vue +0 -26
|
@@ -15,62 +15,78 @@
|
|
|
15
15
|
|
|
16
16
|
const _ = require('lodash');
|
|
17
17
|
const { stripIndent } = require('common-tags');
|
|
18
|
+
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
|
|
18
19
|
|
|
19
20
|
module.exports = {
|
|
20
21
|
options: { alias: 'task' },
|
|
21
22
|
handlers(self) {
|
|
22
23
|
return {
|
|
23
24
|
'apostrophe:run': {
|
|
24
|
-
async runTask(isTask) {
|
|
25
|
+
async runTask(isTask, after) {
|
|
25
26
|
|
|
26
27
|
if (!isTask) {
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
let task;
|
|
31
31
|
const cmd = self.apos.argv._[0];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const telemetry = self.apos.telemetry;
|
|
33
|
+
const spanName = `task:${cmd}`;
|
|
34
|
+
await telemetry.startActiveSpan(spanName, async (span) => {
|
|
35
|
+
span.setAttribute(SemanticAttributes.CODE_FUNCTION, 'runTask');
|
|
36
|
+
span.setAttribute(SemanticAttributes.CODE_NAMESPACE, '@apostrophecms/task');
|
|
37
|
+
span.setAttribute(telemetry.Attributes.ARGV, telemetry.stringify(self.apos.argv));
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
|
|
39
|
+
let task;
|
|
40
|
+
if (!cmd) {
|
|
41
|
+
const err = 'There is no command line argument to serve as a task name, should never happen';
|
|
42
|
+
console.error(err);
|
|
43
|
+
return self.exit(after, 1, span, err);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
self.usage();
|
|
46
|
+
if (cmd === 'help') {
|
|
47
|
+
span.setAttribute(telemetry.Attributes.TARGET_FUNCTION, 'help');
|
|
48
|
+
// list all tasks
|
|
49
|
+
if (self.apos.argv._.length === 1) {
|
|
50
|
+
return self.usage(after, span);
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
|
|
53
|
+
// help with specific task
|
|
54
|
+
if (self.apos.argv._.length === 2) {
|
|
55
|
+
span.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, self.apos.argv._[1]);
|
|
56
|
+
task = self.find(self.apos.argv._[1]);
|
|
57
|
+
if (!task) {
|
|
58
|
+
console.error('There is no such task.');
|
|
59
|
+
return self.usage(after, span, 'There is no such task.');
|
|
60
|
+
}
|
|
61
|
+
if (task.usage) {
|
|
62
|
+
console.log(`\nTips for the ${task.fullName} task:\n`);
|
|
63
|
+
console.log(task.usage);
|
|
64
|
+
} else {
|
|
65
|
+
console.log('That is a valid task, but it does not have a help message.');
|
|
66
|
+
}
|
|
67
|
+
return self.exit(after, 0, span);
|
|
55
68
|
}
|
|
56
|
-
process.exit(0);
|
|
57
69
|
}
|
|
58
|
-
}
|
|
59
70
|
|
|
60
|
-
|
|
71
|
+
task = self.find(cmd);
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
if (!task) {
|
|
74
|
+
console.error('\nThere is no such task.');
|
|
75
|
+
return self.usage(after, span, `There is no such task ${cmd}`);
|
|
76
|
+
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
const [ moduleName, taskName ] = task.fullName.split(':');
|
|
79
|
+
span.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, moduleName);
|
|
80
|
+
span.setAttribute(telemetry.Attributes.TARGET_FUNCTION, taskName);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await task.task(self.apos.argv);
|
|
84
|
+
return self.exit(after, 0, span);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(e);
|
|
87
|
+
return self.exit(after, 1, span, e);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
92
|
};
|
|
@@ -116,21 +132,39 @@ module.exports = {
|
|
|
116
132
|
// task developer might assume they can exit the process directly.
|
|
117
133
|
|
|
118
134
|
async invoke(name, args, options) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
const telemetry = self.apos.telemetry;
|
|
136
|
+
const spanName = `task:${self.__meta.name}:${name}`;
|
|
137
|
+
await telemetry.startActiveSpan(spanName, async (span) => {
|
|
138
|
+
span.setAttribute(SemanticAttributes.CODE_FUNCTION, 'invoke');
|
|
139
|
+
span.setAttribute(SemanticAttributes.CODE_NAMESPACE, '@apostrophecms/task');
|
|
140
|
+
try {
|
|
141
|
+
const aposArgv = self.apos.argv;
|
|
142
|
+
if (Array.isArray(args)) {
|
|
143
|
+
args.splice(0, 0, name);
|
|
144
|
+
} else {
|
|
145
|
+
options = args;
|
|
146
|
+
args = [ name ];
|
|
147
|
+
}
|
|
148
|
+
const task = self.find(name);
|
|
149
|
+
const [ moduleName, taskName ] = task.fullName.split(':');
|
|
150
|
+
span.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, moduleName);
|
|
151
|
+
span.setAttribute(telemetry.Attributes.TARGET_FUNCTION, taskName);
|
|
152
|
+
const argv = {
|
|
153
|
+
_: args,
|
|
154
|
+
...options || {}
|
|
155
|
+
};
|
|
156
|
+
span.setAttribute(telemetry.Attributes.ARGV, telemetry.stringify(argv));
|
|
157
|
+
self.apos.argv = argv;
|
|
158
|
+
await task.task(argv);
|
|
159
|
+
self.apos.argv = aposArgv;
|
|
160
|
+
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
161
|
+
} catch (err) {
|
|
162
|
+
telemetry.handleError(span, err);
|
|
163
|
+
throw err;
|
|
164
|
+
} finally {
|
|
165
|
+
span.end();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
134
168
|
},
|
|
135
169
|
|
|
136
170
|
// Identifies the task corresponding to the given command line argument.
|
|
@@ -152,8 +186,9 @@ module.exports = {
|
|
|
152
186
|
|
|
153
187
|
// Displays a usage message, including a list of available tasks,
|
|
154
188
|
// and exits the entire program with a nonzero status code.
|
|
189
|
+
// Forward signal, span and error to the exit handler.
|
|
155
190
|
|
|
156
|
-
usage() {
|
|
191
|
+
usage(after, span, err) {
|
|
157
192
|
// Direct use of console makes sense in tasks. -Tom
|
|
158
193
|
console.error('\nThe following tasks are available:\n');
|
|
159
194
|
for (const [ moduleName, module ] of Object.entries(self.apos.modules)) {
|
|
@@ -165,7 +200,26 @@ module.exports = {
|
|
|
165
200
|
console.error('node app help groupname:taskname\n');
|
|
166
201
|
console.error('To get help with a specific task.\n');
|
|
167
202
|
console.error('To launch the site, run with no arguments.');
|
|
168
|
-
|
|
203
|
+
return self.exit(after, 1, span, err);
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
// Register error (if any) and close the current telemetry span;
|
|
207
|
+
// send a signal back to the bootstrap to exit the process with a given code.
|
|
208
|
+
|
|
209
|
+
exit(after, code, span, err) {
|
|
210
|
+
after.exit = code;
|
|
211
|
+
if (!span) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (err) {
|
|
216
|
+
self.apos.telemetry.handleError(span, err);
|
|
217
|
+
} else if (code) {
|
|
218
|
+
span.setStatus({ code: self.apos.telemetry.api.SpanStatusCode.ERROR });
|
|
219
|
+
} else {
|
|
220
|
+
span.setStatus({ code: self.apos.telemetry.api.SpanStatusCode.OK });
|
|
221
|
+
}
|
|
222
|
+
span.end();
|
|
169
223
|
},
|
|
170
224
|
|
|
171
225
|
// Return a `req` object suitable for command line tasks
|
|
@@ -31,6 +31,7 @@ const qs = require('qs');
|
|
|
31
31
|
const Promise = require('bluebird');
|
|
32
32
|
const path = require('path');
|
|
33
33
|
const { stripIndent } = require('common-tags');
|
|
34
|
+
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
|
|
34
35
|
|
|
35
36
|
module.exports = {
|
|
36
37
|
options: { alias: 'template' },
|
|
@@ -631,91 +632,125 @@ module.exports = {
|
|
|
631
632
|
// async function.
|
|
632
633
|
|
|
633
634
|
async renderPageForModule(req, template, data, module) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
635
|
+
const telemetry = self.apos.telemetry;
|
|
636
|
+
const spanName = `render:${self.__meta.name}:renderPageForModule`;
|
|
637
|
+
return telemetry.startActiveSpan(spanName, async (span) => {
|
|
638
|
+
span.setAttributes({
|
|
639
|
+
[SemanticAttributes.CODE_FUNCTION]: 'renderPageForModule',
|
|
640
|
+
[SemanticAttributes.CODE_NAMESPACE]: self.__meta.name
|
|
641
|
+
});
|
|
640
642
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
643
|
+
let scene = req.user ? 'apos' : 'public';
|
|
644
|
+
if (req.scene) {
|
|
645
|
+
scene = req.scene;
|
|
646
|
+
} else {
|
|
647
|
+
req.scene = scene;
|
|
648
|
+
}
|
|
649
|
+
span.setAttribute(telemetry.Attributes.SCENE, scene);
|
|
650
|
+
span.setAttribute(telemetry.Attributes.TEMPLATE, template);
|
|
651
|
+
|
|
652
|
+
const aposBodyData = {
|
|
653
|
+
modules: {},
|
|
654
|
+
prefix: req.prefix,
|
|
655
|
+
sitePrefix: self.apos.prefix,
|
|
656
|
+
shortName: self.apos.shortName,
|
|
657
|
+
locale: req.locale,
|
|
658
|
+
csrfCookieName: self.apos.csrfCookieName,
|
|
659
|
+
tabId: self.apos.util.generateId(),
|
|
660
|
+
uploadsUrl: self.apos.attachment.uploadfs.getUrl(),
|
|
661
|
+
assetBaseUrl: self.apos.asset.getAssetBaseUrl(),
|
|
662
|
+
scene
|
|
663
|
+
};
|
|
664
|
+
if (req.user) {
|
|
665
|
+
aposBodyData.user = {
|
|
666
|
+
title: req.user.title,
|
|
667
|
+
_id: req.user._id,
|
|
668
|
+
username: req.user.username
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
await self.emit('addBodyData', req, aposBodyData);
|
|
672
|
+
self.addBodyDataAttribute(req, { apos: JSON.stringify(aposBodyData) });
|
|
673
|
+
|
|
674
|
+
// Always the last call; signifies we're done initializing the
|
|
675
|
+
// page as far as the core is concerned; a lovely time for other
|
|
676
|
+
// modules and project-level javascript to do their own
|
|
677
|
+
// enhancements.
|
|
678
|
+
//
|
|
679
|
+
// This method emits a 'ready' event, and also
|
|
680
|
+
// emits an 'enhance' event with the entire $body
|
|
681
|
+
// as its argument.
|
|
682
|
+
//
|
|
683
|
+
// Waits for DOMready to give other
|
|
684
|
+
// things maximum opportunity to happen.
|
|
685
|
+
|
|
686
|
+
const decorate = req.query.aposRefresh !== '1';
|
|
687
|
+
|
|
688
|
+
// data.url will be the original requested page URL, for use in building
|
|
689
|
+
// relative links, adding or removing query parameters, etc. If this is a
|
|
690
|
+
// refresh request, we remove that so that frontend templates don't build
|
|
691
|
+
// URLs that also refresh
|
|
692
|
+
|
|
693
|
+
const args = {
|
|
694
|
+
outerLayout: decorate ? '@apostrophecms/template:outerLayout.html' : '@apostrophecms/template:refreshLayout.html',
|
|
695
|
+
permissions: req.user && (req.user._permissions || {}),
|
|
696
|
+
scene,
|
|
697
|
+
refreshing: !decorate,
|
|
698
|
+
// Make the query available to templates for easy access to
|
|
699
|
+
// filter settings etc.
|
|
700
|
+
query: req.query,
|
|
701
|
+
url: unrefreshed(req.url)
|
|
658
702
|
};
|
|
659
|
-
}
|
|
660
|
-
await self.emit('addBodyData', req, aposBodyData);
|
|
661
|
-
self.addBodyDataAttribute(req, { apos: JSON.stringify(aposBodyData) });
|
|
662
|
-
|
|
663
|
-
// Always the last call; signifies we're done initializing the
|
|
664
|
-
// page as far as the core is concerned; a lovely time for other
|
|
665
|
-
// modules and project-level javascript to do their own
|
|
666
|
-
// enhancements.
|
|
667
|
-
//
|
|
668
|
-
// This method emits a 'ready' event, and also
|
|
669
|
-
// emits an 'enhance' event with the entire $body
|
|
670
|
-
// as its argument.
|
|
671
|
-
//
|
|
672
|
-
// Waits for DOMready to give other
|
|
673
|
-
// things maximum opportunity to happen.
|
|
674
|
-
|
|
675
|
-
const decorate = req.query.aposRefresh !== '1';
|
|
676
|
-
|
|
677
|
-
// data.url will be the original requested page URL, for use in building
|
|
678
|
-
// relative links, adding or removing query parameters, etc. If this is a
|
|
679
|
-
// refresh request, we remove that so that frontend templates don't build
|
|
680
|
-
// URLs that also refresh
|
|
681
|
-
|
|
682
|
-
const args = {
|
|
683
|
-
outerLayout: decorate ? '@apostrophecms/template:outerLayout.html' : '@apostrophecms/template:refreshLayout.html',
|
|
684
|
-
permissions: req.user && (req.user._permissions || {}),
|
|
685
|
-
scene,
|
|
686
|
-
refreshing: !decorate,
|
|
687
|
-
// Make the query available to templates for easy access to
|
|
688
|
-
// filter settings etc.
|
|
689
|
-
query: req.query,
|
|
690
|
-
url: unrefreshed(req.url)
|
|
691
|
-
};
|
|
692
703
|
|
|
693
|
-
|
|
704
|
+
_.extend(args, data);
|
|
694
705
|
|
|
695
|
-
|
|
706
|
+
if (req.aposError) {
|
|
696
707
|
// A 500-worthy error occurred already, i.e. in `pageBeforeSend`
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const content = await module.render(req, template, args);
|
|
708
|
+
telemetry.handleError(span, req.aposError);
|
|
709
|
+
span.end();
|
|
710
|
+
return error(req.aposError);
|
|
711
|
+
}
|
|
702
712
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
713
|
+
try {
|
|
714
|
+
const spanRenderName = `render:${module.__meta.name}:render`;
|
|
715
|
+
const content = await telemetry.startActiveSpan(spanRenderName, async (spanRender) => {
|
|
716
|
+
spanRender.setAttribute(SemanticAttributes.CODE_FUNCTION, 'render');
|
|
717
|
+
spanRender.setAttribute(SemanticAttributes.CODE_NAMESPACE, module.__meta.name);
|
|
718
|
+
spanRender.setAttribute(telemetry.Attributes.SCENE, scene);
|
|
719
|
+
spanRender.setAttribute(telemetry.Attributes.TEMPLATE, template);
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
const content = await module.render(req, template, args);
|
|
723
|
+
spanRender.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
724
|
+
return content;
|
|
725
|
+
} catch (err) {
|
|
726
|
+
telemetry.handleError(spanRender, err);
|
|
727
|
+
throw err;
|
|
728
|
+
} finally {
|
|
729
|
+
spanRender.end();
|
|
730
|
+
}
|
|
731
|
+
}, span);
|
|
732
|
+
|
|
733
|
+
const filledContent = self.insertBundlesMarkup({
|
|
734
|
+
page: req.data.bestPage,
|
|
735
|
+
scene,
|
|
736
|
+
template,
|
|
737
|
+
content,
|
|
738
|
+
scriptsPlaceholder: req.scriptsPlaceholder,
|
|
739
|
+
stylesheetsPlaceholder: req.stylesheetsPlaceholder,
|
|
740
|
+
widgetsBundles: req.widgetsBundles
|
|
741
|
+
});
|
|
712
742
|
|
|
713
|
-
|
|
714
|
-
|
|
743
|
+
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
744
|
+
return filledContent;
|
|
745
|
+
} catch (e) {
|
|
715
746
|
// The page template threw an exception. Log where it
|
|
716
747
|
// occurred for easier debugging
|
|
717
|
-
|
|
718
|
-
|
|
748
|
+
telemetry.handleError(span, e);
|
|
749
|
+
return error(e);
|
|
750
|
+
} finally {
|
|
751
|
+
span.end();
|
|
752
|
+
}
|
|
753
|
+
});
|
|
719
754
|
|
|
720
755
|
function error(e) {
|
|
721
756
|
self.logError(req, e);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Implements {% component 'moduleName:componentName' with dataObject %}
|
|
2
2
|
|
|
3
|
+
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
|
|
4
|
+
|
|
3
5
|
module.exports = function(self) {
|
|
4
6
|
return {
|
|
5
7
|
// We need a custom parser because of the "with" syntax
|
|
@@ -27,29 +29,47 @@ module.exports = function(self) {
|
|
|
27
29
|
if (!data) {
|
|
28
30
|
data = {};
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
32
|
+
|
|
33
|
+
const telemetry = self.apos.telemetry;
|
|
34
|
+
const spanName = `component:${name}`;
|
|
35
|
+
return telemetry.startActiveSpan(spanName, async (span) => {
|
|
36
|
+
span.setAttribute(telemetry.Attributes.TEMPLATE, name);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const parsed = name.match(/^([^:]+):(.+)$/);
|
|
40
|
+
if (!parsed) {
|
|
41
|
+
throw new Error('When using {% component %} the component name must be a string like: module-name:componentName');
|
|
42
|
+
}
|
|
43
|
+
// eslint-disable-next-line no-unused-vars
|
|
44
|
+
const [ dummy, moduleName, componentName ] = parsed;
|
|
45
|
+
span.setAttribute(SemanticAttributes.CODE_FUNCTION, componentName);
|
|
46
|
+
span.setAttribute(SemanticAttributes.CODE_NAMESPACE, moduleName);
|
|
47
|
+
const module = self.apos.modules[moduleName];
|
|
48
|
+
if (!module) {
|
|
49
|
+
throw new Error(`{% component %} was invoked with the name of a module that does not exist. Hint:\nit must be a module that is actually live in your project, not a base class\nlike @apostrophecms/piece-type.\nModule name: ${moduleName} Component name: ${componentName}`);
|
|
50
|
+
}
|
|
51
|
+
if (!(module.components && module.components[componentName])) {
|
|
52
|
+
throw new Error(`{% component %} was invoked with the name of a component that does not exist.\nModule name: ${moduleName} Component name: ${componentName}`);
|
|
53
|
+
}
|
|
54
|
+
const result = await self.apos.util.recursionGuard(req, `component:${moduleName}:${componentName}`, async () => {
|
|
55
|
+
const input = await module.components[componentName](req, data);
|
|
56
|
+
return module.render(req, componentName, input);
|
|
57
|
+
});
|
|
58
|
+
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
59
|
+
|
|
60
|
+
if (result === undefined) {
|
|
61
|
+
// Recursion guard stopped it, nunjucks expects a string
|
|
62
|
+
return '';
|
|
63
|
+
} else {
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
telemetry.handleError(span, err);
|
|
68
|
+
throw err;
|
|
69
|
+
} finally {
|
|
70
|
+
span.end();
|
|
71
|
+
}
|
|
46
72
|
});
|
|
47
|
-
if (result === undefined) {
|
|
48
|
-
// Recursion guard stopped it, nunjucks expects a string
|
|
49
|
-
return '';
|
|
50
|
-
} else {
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
73
|
}
|
|
54
74
|
};
|
|
55
75
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="apos-input-wrapper">
|
|
3
|
+
<select
|
|
4
|
+
class="apos-input apos-input--select"
|
|
5
|
+
:disabled="disabled"
|
|
6
|
+
@change="change($event.target.value)"
|
|
7
|
+
>
|
|
8
|
+
<option
|
|
9
|
+
v-for="choice in choices"
|
|
10
|
+
:key="JSON.stringify(choice.value)"
|
|
11
|
+
:value="JSON.stringify(choice.value)"
|
|
12
|
+
:selected="choice.value === selected"
|
|
13
|
+
>
|
|
14
|
+
{{ $t(choice.label) }}
|
|
15
|
+
</option>
|
|
16
|
+
</select>
|
|
17
|
+
<AposIndicator
|
|
18
|
+
icon="menu-down-icon"
|
|
19
|
+
class="apos-input-icon"
|
|
20
|
+
:icon-size="20"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
<script>
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
name: 'AposSelect',
|
|
28
|
+
props: {
|
|
29
|
+
icon: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: 'menu-down-icon'
|
|
32
|
+
},
|
|
33
|
+
choices: {
|
|
34
|
+
type: Array,
|
|
35
|
+
default() {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
selected: {
|
|
40
|
+
type: [ String, Number ],
|
|
41
|
+
default: ''
|
|
42
|
+
},
|
|
43
|
+
disabled: {
|
|
44
|
+
type: Boolean,
|
|
45
|
+
default: false
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
emits: [ 'change' ],
|
|
49
|
+
methods: {
|
|
50
|
+
change(value) {
|
|
51
|
+
this.$emit('change', value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style lang="scss" scoped>
|
|
58
|
+
.apos-input-icon {
|
|
59
|
+
@include apos-transition();
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
@@ -27,13 +27,26 @@
|
|
|
27
27
|
:size="13"
|
|
28
28
|
/>
|
|
29
29
|
<AposContextMenu
|
|
30
|
-
v-if="
|
|
30
|
+
v-if="hasRelationshipEditor && more.menu.length"
|
|
31
31
|
:button="more.button"
|
|
32
32
|
:menu="more.menu"
|
|
33
33
|
@item-clicked="$emit('item-clicked', item)"
|
|
34
34
|
menu-placement="bottom-start"
|
|
35
35
|
menu-offset="40, 10"
|
|
36
36
|
/>
|
|
37
|
+
<AposButton
|
|
38
|
+
class="apos-slat__editor-btn"
|
|
39
|
+
v-if="editorIcon && hasRelationshipEditor"
|
|
40
|
+
role="button"
|
|
41
|
+
:tooltip="{
|
|
42
|
+
content: editorLabel,
|
|
43
|
+
placement: 'bottom'
|
|
44
|
+
}"
|
|
45
|
+
:icon="editorIcon"
|
|
46
|
+
:icon-only="true"
|
|
47
|
+
:modifiers="['inline']"
|
|
48
|
+
@click="$emit('item-clicked', item)"
|
|
49
|
+
/>
|
|
37
50
|
<a
|
|
38
51
|
class="apos-slat__control apos-slat__control--view"
|
|
39
52
|
v-if="item._url || item._urls"
|
|
@@ -42,9 +55,16 @@
|
|
|
42
55
|
>
|
|
43
56
|
<eye-icon :size="14" class="apos-slat__control--view-icon" />
|
|
44
57
|
</a>
|
|
45
|
-
<div
|
|
58
|
+
<div
|
|
59
|
+
v-if="item.attachment &&
|
|
60
|
+
item.attachment.group === 'images' &&
|
|
61
|
+
item.attachment._urls"
|
|
62
|
+
class="apos-slat__media-preview"
|
|
63
|
+
>
|
|
46
64
|
<img
|
|
47
|
-
:src="item.attachment._urls
|
|
65
|
+
:src="item.attachment._urls.uncropped
|
|
66
|
+
? item.attachment._urls.uncropped['one-sixth']
|
|
67
|
+
: item.attachment._urls['one-sixth']"
|
|
48
68
|
:alt="item.description || item.title"
|
|
49
69
|
class="apos-slat__media"
|
|
50
70
|
>
|
|
@@ -114,6 +134,14 @@ export default {
|
|
|
114
134
|
hasRelationshipSchema: {
|
|
115
135
|
type: Boolean,
|
|
116
136
|
default: false
|
|
137
|
+
},
|
|
138
|
+
editorLabel: {
|
|
139
|
+
type: String,
|
|
140
|
+
default: null
|
|
141
|
+
},
|
|
142
|
+
editorIcon: {
|
|
143
|
+
type: String,
|
|
144
|
+
default: null
|
|
117
145
|
}
|
|
118
146
|
},
|
|
119
147
|
emits: [ 'engage', 'disengage', 'move', 'remove', 'item-clicked', 'select' ],
|
|
@@ -128,10 +156,10 @@ export default {
|
|
|
128
156
|
type: 'inline'
|
|
129
157
|
},
|
|
130
158
|
menu: [
|
|
131
|
-
{
|
|
132
|
-
label: '
|
|
159
|
+
...!this.editorIcon ? [ {
|
|
160
|
+
label: 'apostrophe:editRelationship',
|
|
133
161
|
action: 'edit-relationship'
|
|
134
|
-
}
|
|
162
|
+
} ] : []
|
|
135
163
|
]
|
|
136
164
|
}
|
|
137
165
|
};
|
|
@@ -144,6 +172,12 @@ export default {
|
|
|
144
172
|
} else {
|
|
145
173
|
return `${(size / 1000000).toFixed(1)}MB`;
|
|
146
174
|
}
|
|
175
|
+
},
|
|
176
|
+
hasRelationshipEditor() {
|
|
177
|
+
if (this.item.attachment && this.item.attachment.group === 'images') {
|
|
178
|
+
return this.hasRelationshipSchema && this.item.attachment._isCroppable;
|
|
179
|
+
}
|
|
180
|
+
return this.hasRelationshipSchema;
|
|
147
181
|
}
|
|
148
182
|
},
|
|
149
183
|
methods: {
|
|
@@ -162,11 +196,8 @@ export default {
|
|
|
162
196
|
},
|
|
163
197
|
move(dir) {
|
|
164
198
|
if (this.engaged) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} else {
|
|
168
|
-
this.$emit('move', this.item._id, -1);
|
|
169
|
-
}
|
|
199
|
+
const direction = dir > 0 ? 1 : -1;
|
|
200
|
+
this.$emit('move', this.item._id, direction);
|
|
170
201
|
}
|
|
171
202
|
},
|
|
172
203
|
remove(focusNext) {
|
|
@@ -259,6 +290,10 @@ export default {
|
|
|
259
290
|
text-overflow: ellipsis;
|
|
260
291
|
}
|
|
261
292
|
|
|
293
|
+
.apos-slat__editor-btn {
|
|
294
|
+
margin-right: 5px;
|
|
295
|
+
}
|
|
296
|
+
|
|
262
297
|
.apos-slat__control {
|
|
263
298
|
display: flex;
|
|
264
299
|
align-content: center;
|