apostrophe 3.38.1 → 3.39.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 +25 -0
- package/modules/@apostrophecms/area/index.js +13 -8
- package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
- package/modules/@apostrophecms/attachment/index.js +5 -0
- package/modules/@apostrophecms/attachment/lib/tasks/download-all.js +77 -0
- package/modules/@apostrophecms/attachment/lib/tasks/rescale.js +3 -2
- package/modules/@apostrophecms/command-menu/index.js +46 -0
- package/modules/@apostrophecms/i18n/i18n/en.json +19 -0
- package/modules/@apostrophecms/i18n/index.js +25 -14
- package/modules/@apostrophecms/image/index.js +36 -0
- package/modules/@apostrophecms/image-widget/views/widget.html +6 -12
- package/modules/@apostrophecms/rich-text-widget/index.js +195 -58
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +30 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapAnchor.vue +1 -8
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapButton.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapImage.vue +261 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +1 -8
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapTable.vue +173 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Div.js +43 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Image.js +106 -0
- package/package.json +7 -1
- package/test/command-menu.js +70 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.39.1 (2023-02-02)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* Rescaling cropped images with the `@apostrophecms/attachment:rescale` task now works correctly. Thanks to [Waldemar Pankratz](https://github.com/waldemar-p) for this contribution.
|
|
8
|
+
|
|
9
|
+
## 3.39.0 (2023-02-01)
|
|
10
|
+
|
|
11
|
+
### Adds
|
|
12
|
+
|
|
13
|
+
* Basic support for editing tables by adding `table` to the rich text toolbar. Enabling `table` allows you to create tables, including `td` and `th` tags, with the ability to merge and split cells. For now the table editing UI is basic, all of the functionality is there but we plan to add more conveniences for easy table editing soon. See the "Table" dropdown for actions that are permitted based on the current selection.
|
|
14
|
+
* `superscript` and `subscript` may now be added to the rich text widget's `toolbar` option.
|
|
15
|
+
* Early beta-quality support for adding inline images to rich text, by adding `image` to the rich text toolbar. This feature works reliably, however the UI is not mature yet. In particular you must search for images by typing part of the title. We will support a proper "browse" experience here soon. For good results you should also configure the `imageStyles` option. You will also want to style the `figure` tags produced. See the documentation for more information.
|
|
16
|
+
* Support for `div` tags in the rich text toolbar, if you choose to include them in `styles`. This is often necessary for A2 content migration and can potentially be useful in new work when combined with a `class` if there is no suitable semantic block tag.
|
|
17
|
+
* The new `@apostrophecms/attachment:download-all --to=folder` command line task is useful to download all of your attachments from an uploadfs backend other than local storage, especially if you do not have a more powerful "sync" utility for that particular storage backend.
|
|
18
|
+
* A new `loadingType` option can now be set for `image-widget` when configuring an `area` field. This sets the `loading` attribute of the `img` tag, which can be used to enable lazy loading in most browsers. Thanks to [Waldemar Pankratz](https://github.com/waldemar-p) for this contribution.
|
|
19
|
+
* Two new module-level options have been added to the `image-widget` module: `loadingType` and `size`. These act as fallbacks for the same options at the area level. Thanks to [Waldemar Pankratz](https://github.com/waldemar-p) for this contribution.
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
|
|
23
|
+
* Adding missing require (`bluebird`) and fallback (`file.crops || []`) to `@apostrophecms/attachment:rescale`-task
|
|
24
|
+
|
|
3
25
|
## 3.38.1 (2023-01-23)
|
|
4
26
|
|
|
5
27
|
### Fixes
|
|
@@ -29,9 +51,12 @@ node app @apostrophecms/i18n:rename-locale --old=de-DE --new=de-de --keep=de-de
|
|
|
29
51
|
* Emit a `beforeSaveSafe` event from the `@apostrophecms:user` module, with `req`, `safeUser` and `user` as arguments, in order to give the possibility to override properties of the `safeUser` object which contains password hashes and other information too sensitive to be stored in the aposDocs collection.
|
|
30
52
|
* Automatically convert failed uppercase URLs to their lowercase version - can be disabled with `redirectFailedUpperCaseUrls: false` in `@apostrophecms/page/index.js` options. This only comes into play if a 404 is about to happen.
|
|
31
53
|
* Automatically convert country codes in locales like `xx-yy` to `xx-YY` before passing them to `i18next`, which is strict about uppercase country codes.
|
|
54
|
+
* Keyboard shortcuts conflicts are detected and logged on to the terminal.
|
|
32
55
|
|
|
33
56
|
### Fixes
|
|
34
57
|
|
|
58
|
+
* Invalid locales passed to the i18n locale switching middleware are politely mapped to 400 errors.
|
|
59
|
+
* Any other exceptions thrown in the i18n locale switching middleware can no longer crash the process.
|
|
35
60
|
* Documents kept as the `previous` version for undo purposes were not properly marked as such, breaking the public language switcher in some cases. This was fixed and a migration was added for existing data.
|
|
36
61
|
* Uploading an image in an apostrophe area with `minSize` requirements will not trigger an unexpected error anymore. If the image is too small, a notification will be displayed with the minimum size requirements. The `Edit Image` modal will now display the minimum size requirements, if any, above the `Browse Images` field.
|
|
37
62
|
* Some browsers saw the empty `POST` response for new notifications as invalid XML. It will now return an empty JSON object with the `Content-Type` set to `application/json`.
|
|
@@ -324,15 +324,20 @@ module.exports = {
|
|
|
324
324
|
// to update a widget on the page after it is saved, or for
|
|
325
325
|
// preview when editing.
|
|
326
326
|
async renderWidget(req, type, data, options) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
327
|
+
try {
|
|
328
|
+
const manager = self.getWidgetManager(type);
|
|
329
|
+
if (!manager) {
|
|
330
|
+
// No manager available - possibly a stale widget in the database
|
|
331
|
+
// of a type no longer in the project
|
|
332
|
+
self.warnMissingWidgetType(type);
|
|
333
|
+
return '';
|
|
334
|
+
}
|
|
335
|
+
data.type = type;
|
|
336
|
+
return manager.output(req, data, options);
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.error(e);
|
|
339
|
+
throw e;
|
|
333
340
|
}
|
|
334
|
-
data.type = type;
|
|
335
|
-
return manager.output(req, data, options);
|
|
336
341
|
},
|
|
337
342
|
// Update or create an area at the specified
|
|
338
343
|
// dot path in the document with the specified
|
|
@@ -56,6 +56,8 @@ module.exports = {
|
|
|
56
56
|
'format-list-numbered-icon': 'FormatListNumbered',
|
|
57
57
|
'format-quote-close-icon': 'FormatQuoteClose',
|
|
58
58
|
'format-strikethrough-variant-icon': 'FormatStrikethroughVariant',
|
|
59
|
+
'format-superscript-icon': 'FormatSuperscript',
|
|
60
|
+
'format-subscript-icon': 'FormatSubscript',
|
|
59
61
|
'format-underline-icon': 'FormatUnderline',
|
|
60
62
|
'help-circle-icon': 'HelpCircle',
|
|
61
63
|
'image-edit-outline': 'ImageEditOutline',
|
|
@@ -112,6 +112,7 @@ module.exports = {
|
|
|
112
112
|
self.sizeAvailableInArchive = self.options.sizeAvailableInArchive || 'one-sixth';
|
|
113
113
|
|
|
114
114
|
self.rescaleTask = require('./lib/tasks/rescale.js')(self);
|
|
115
|
+
self.downloadAllTask = require('./lib/tasks/download-all.js')(self);
|
|
115
116
|
self.addFieldType();
|
|
116
117
|
self.enableBrowserData();
|
|
117
118
|
|
|
@@ -128,6 +129,10 @@ module.exports = {
|
|
|
128
129
|
usage: 'Usage: node app @apostrophecms/attachment:rescale\n\nRegenerate all sizes of all image attachments. Useful after a new size\nis added to the configuration. Takes a long time!',
|
|
129
130
|
task: self.rescaleTask
|
|
130
131
|
},
|
|
132
|
+
'download-all': {
|
|
133
|
+
usage: 'Usage: node app @apostrophecms/attachment:download-all --to=public/uploads/attachments [--resume] [--parallel=3]\n\nDownload all attachments to a local folder, usually to sync\nfrom a non-local uploadfs backend. Takes a long time!',
|
|
134
|
+
task: self.downloadAllTask
|
|
135
|
+
},
|
|
131
136
|
'migrate-to-disabled-file-key': {
|
|
132
137
|
usage: 'Usage: node app @apostrophecms/attachment:migrate-to-disabled-file-key\n\nThis task should be run after adding the disabledFileKey option to uploadfs\nfor the first time. It should only be relevant for storage backends where\nthat option is not mandatory, i.e. only local storage as of this writing.',
|
|
133
138
|
task: self.migrateToDisabledFileKeyTask
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Direct use of console is appropriate in tasks. -Tom
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const sep = require('path').sep;
|
|
6
|
+
|
|
7
|
+
// Download everything Apostrophe believes to be in uploadfs.
|
|
8
|
+
// Useful when uploadfs is not using a storage backend we
|
|
9
|
+
// can conveniently sync from in some other way
|
|
10
|
+
|
|
11
|
+
module.exports = function(self) {
|
|
12
|
+
return async function(argv) {
|
|
13
|
+
const copyOut = require('util').promisify(self.uploadfs.copyOut);
|
|
14
|
+
if (!argv.to) {
|
|
15
|
+
throw 'You must specify a --to=directory argument';
|
|
16
|
+
}
|
|
17
|
+
console.log(`Downloading all attachments to ${argv.to} (this takes time)`);
|
|
18
|
+
const files = fs.readdirSync(argv.to);
|
|
19
|
+
if ((files.length > 0) && (!argv.resume)) {
|
|
20
|
+
throw `The directory ${argv.to} is not empty and --resume not specified, exiting`;
|
|
21
|
+
}
|
|
22
|
+
if (!argv.to.endsWith(sep)) {
|
|
23
|
+
argv.to += sep;
|
|
24
|
+
}
|
|
25
|
+
const total = await self.db.count();
|
|
26
|
+
let n = 0;
|
|
27
|
+
const parallel = argv.parallel ? parseInt(argv.parallel) : 1;
|
|
28
|
+
await self.each({}, parallel, async function(file) {
|
|
29
|
+
const isImage = [ 'jpg', 'png', 'gif', 'webp' ].includes(file.extension);
|
|
30
|
+
const originalFile = filename(file);
|
|
31
|
+
n++;
|
|
32
|
+
console.log(n + ' of ' + total + ': ' + originalFile);
|
|
33
|
+
const files = [
|
|
34
|
+
originalFile
|
|
35
|
+
];
|
|
36
|
+
if (isImage) {
|
|
37
|
+
files.push(...self.imageSizes.map(size => {
|
|
38
|
+
return filename(file, size);
|
|
39
|
+
}));
|
|
40
|
+
for (const crop of (file.crops || [])) {
|
|
41
|
+
files.push(filename(file, false, crop));
|
|
42
|
+
files.push(...self.imageSizes.map(size => filename(file, size, crop)));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const to = argv.to + file;
|
|
47
|
+
const tmp = to + '.tmp';
|
|
48
|
+
if (fs.existsSync(to)) {
|
|
49
|
+
console.log(`${to} already exists, skipping`);
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
console.log(`about to copy out: ${file}`);
|
|
53
|
+
await copyOut(`/attachments/${file}`, tmp);
|
|
54
|
+
fs.renameSync(tmp, argv.to + file);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (e.code === 'ENOSPC') {
|
|
57
|
+
throw e;
|
|
58
|
+
} else {
|
|
59
|
+
console.log(`${e.code}: ${file} was probably never uploaded to uploadfs, skipping`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function filename(file, size, crop) {
|
|
65
|
+
let name = file._id + '-' + file.name;
|
|
66
|
+
if (crop) {
|
|
67
|
+
name += '.' + crop.left + '.' + crop.top + '.' + crop.width + '.' + crop.height;
|
|
68
|
+
}
|
|
69
|
+
if (size) {
|
|
70
|
+
name += '.' + size.name;
|
|
71
|
+
}
|
|
72
|
+
name += '.' + file.extension;
|
|
73
|
+
return name;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const _ = require('lodash');
|
|
5
5
|
const fs = require('fs');
|
|
6
|
+
const Promise = require('bluebird');
|
|
6
7
|
|
|
7
8
|
// Regenerate all scaled images. Useful after changing the configured sizes
|
|
8
9
|
|
|
@@ -61,12 +62,12 @@ module.exports = function(self) {
|
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
|
-
for (const crop of file.crops) {
|
|
65
|
+
for (const crop of file.crops || []) {
|
|
65
66
|
console.log('RECROPPING');
|
|
66
67
|
const originalFile = '/attachments/' + file._id + '-' + file.name + '.' + crop.left + '.' + crop.top + '.' + crop.width + '.' + crop.height + '.' + file.extension;
|
|
67
68
|
console.log('Cropping ' + tempFile + ' to ' + originalFile);
|
|
68
69
|
try {
|
|
69
|
-
Promise.promisify(self.uploadfs.copyImageIn)(tempFile, originalFile, {
|
|
70
|
+
await Promise.promisify(self.uploadfs.copyImageIn)(tempFile, originalFile, {
|
|
70
71
|
crop: crop,
|
|
71
72
|
sizes: self.imageSizes
|
|
72
73
|
});
|
|
@@ -363,12 +363,58 @@ module.exports = {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
const { groups, modals } = self.getVisible(req);
|
|
366
|
+
self.notifyConflicts(req, modals);
|
|
366
367
|
|
|
367
368
|
return {
|
|
368
369
|
components: { the: self.options.components.the || 'TheAposCommandMenu' },
|
|
369
370
|
groups,
|
|
370
371
|
modals
|
|
371
372
|
};
|
|
373
|
+
},
|
|
374
|
+
notifyConflicts(req, modals = self.modals) {
|
|
375
|
+
const shortcuts = {
|
|
376
|
+
modal: {},
|
|
377
|
+
list: {},
|
|
378
|
+
conflict: {}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
Object.entries(modals)
|
|
382
|
+
.forEach(([ modal, groups ]) => Object.values(groups)
|
|
383
|
+
.forEach(group => Object.entries(group.commands)
|
|
384
|
+
.forEach(([ name, field ]) => {
|
|
385
|
+
self.detectShortcutConflict({
|
|
386
|
+
shortcuts,
|
|
387
|
+
shortcut: field.shortcut.toUpperCase(),
|
|
388
|
+
modal: modal === 'default' ? 'admin-bar' : modal,
|
|
389
|
+
moduleName: name
|
|
390
|
+
});
|
|
391
|
+
})
|
|
392
|
+
)
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
self.apos.util.warnDev(
|
|
396
|
+
req.t('apostrophe:shortcutConflictNotification'),
|
|
397
|
+
shortcuts.conflict
|
|
398
|
+
);
|
|
399
|
+
},
|
|
400
|
+
detectShortcutConflict({
|
|
401
|
+
shortcuts, shortcut, modal, moduleName
|
|
402
|
+
}) {
|
|
403
|
+
shortcuts.modal[modal] = shortcuts.modal[modal] || [];
|
|
404
|
+
shortcuts.list[modal] = shortcuts.list[modal] || {};
|
|
405
|
+
shortcuts.list[modal][shortcut] = shortcuts.list[modal][shortcut] || [];
|
|
406
|
+
|
|
407
|
+
const existingShortcut = shortcuts.modal[modal].includes(shortcut);
|
|
408
|
+
|
|
409
|
+
if (existingShortcut) {
|
|
410
|
+
shortcuts.conflict[modal] = shortcuts.conflict[modal] || {};
|
|
411
|
+
shortcuts.conflict[modal][shortcut] = shortcuts.list[modal][shortcut];
|
|
412
|
+
} else {
|
|
413
|
+
shortcuts.modal[modal].push(shortcut);
|
|
414
|
+
}
|
|
415
|
+
shortcuts.list[modal][shortcut].push(moduleName);
|
|
416
|
+
|
|
417
|
+
return shortcuts;
|
|
372
418
|
}
|
|
373
419
|
};
|
|
374
420
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
+
"addColumnBefore": "Add Column Before",
|
|
3
|
+
"addColumnAfter": "Add Column After",
|
|
2
4
|
"addContent": "Add Content",
|
|
3
5
|
"addItem": "Add Item",
|
|
6
|
+
"addRowBefore": "Add Row Before",
|
|
7
|
+
"addRowAfter": "Add Row After",
|
|
4
8
|
"addWidgetType": "Add {{ label }}",
|
|
5
9
|
"admin": "Admin",
|
|
6
10
|
"affirmativeLabel": "Yes, continue.",
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
"browseDocType": "Browse {{ type }}",
|
|
46
50
|
"cancel": "Cancel",
|
|
47
51
|
"cannotMoveArchive": "You cannot move the Archive",
|
|
52
|
+
"caption": "Caption",
|
|
48
53
|
"changed": "Changed",
|
|
49
54
|
"changesAwaitingApproval": "Changes to this document are awaiting approval by an admin or editor.",
|
|
50
55
|
"changesDiscarded": "Changes discarded",
|
|
@@ -93,9 +98,12 @@
|
|
|
93
98
|
"dayjsMediaCreatedDateFormat": "MMM Do, YYYY",
|
|
94
99
|
"deduplicateSlugReserved": "The deduplicate- slug is reserved.",
|
|
95
100
|
"delete": "Delete",
|
|
101
|
+
"deleteColumn": "Delete Column",
|
|
96
102
|
"deleteDraft": "Delete Draft",
|
|
97
103
|
"deleteDraftAffirmativeLabel": "Yes, delete document",
|
|
98
104
|
"deleteDraftDescription": "Since {{ title }} has never been published, this will completely delete the document.",
|
|
105
|
+
"deleteRow": "Delete Row",
|
|
106
|
+
"deleteTable": "Delete Table",
|
|
99
107
|
"description": "Description",
|
|
100
108
|
"disabled": "Disabled",
|
|
101
109
|
"discardChanges": "Discard Changes",
|
|
@@ -188,6 +196,7 @@
|
|
|
188
196
|
"insertAndRedirect": "{{ saveLabel }} {{ typeLabel }} and be redirected to the {{ typeLabel }}.",
|
|
189
197
|
"insertAndReturn": "{{ saveLabel }} and return to the {{ typeLabel }} listing.",
|
|
190
198
|
"insertAndNew": "{{ saveLabel }} {{ typeLabel }} and create a new one.",
|
|
199
|
+
"insertTable": "Insert Table",
|
|
191
200
|
"lastEdited": "Last Edited",
|
|
192
201
|
"leavePageDescription": "The content you're trying to edit belongs to another document and must be edited there.\nChanges made to {{ oldTitle }} are saved automatically.",
|
|
193
202
|
"leavePageHeading": "Leave {{ oldTitle }} to edit {{ newTitle }}?",
|
|
@@ -241,6 +250,7 @@
|
|
|
241
250
|
"mediaMB": "{{ size }}MB",
|
|
242
251
|
"mediaUploadViaDrop": "Drop ’em when you’re ready",
|
|
243
252
|
"mediaUploadViaExplorer": "Or click to open the file explorer",
|
|
253
|
+
"mergeCells": "Merge Cells",
|
|
244
254
|
"minLabel": "Min:",
|
|
245
255
|
"minUi": "Min: {{ number }}",
|
|
246
256
|
"modify": "Modify",
|
|
@@ -403,6 +413,14 @@
|
|
|
403
413
|
"shareDraftEnable": "Enable draft sharing",
|
|
404
414
|
"shareDraftHeader": "Share this page",
|
|
405
415
|
"shareDraftError": "This document cannot be shared at this time",
|
|
416
|
+
"shortcutConflictNotification": "Shortcut conflicts detected:",
|
|
417
|
+
"subscript": "Subscript",
|
|
418
|
+
"superscript": "Superscript",
|
|
419
|
+
"splitCell": "Split Cell",
|
|
420
|
+
"style": "Style",
|
|
421
|
+
"toggleHeaderCell": "Toggle Header (Cell)",
|
|
422
|
+
"toggleHeaderColumn": "Toggle Header (Column)",
|
|
423
|
+
"toggleHeaderRow": "Toggle Header (Row)",
|
|
406
424
|
"visibilityHelp": "Select whether this content is public or private",
|
|
407
425
|
"slug": "Slug",
|
|
408
426
|
"slugInUse": "Slug already in use",
|
|
@@ -414,6 +432,7 @@
|
|
|
414
432
|
"submitUpdate": "Submit Update",
|
|
415
433
|
"suggestionsHeader": "Try one of these suggestions:",
|
|
416
434
|
"switchLocalesAndLocalizePage": "Switch locales and localize page to {{ label }}?",
|
|
435
|
+
"table": "Table",
|
|
417
436
|
"tags": "Tags",
|
|
418
437
|
"tagYourImages": "Tag your images to make searching and filtering the media manager easier",
|
|
419
438
|
"takeActionAndCreateNew": "{{ saveLabel }} and Create New",
|
|
@@ -287,6 +287,9 @@ module.exports = {
|
|
|
287
287
|
post: {
|
|
288
288
|
async locale(req) {
|
|
289
289
|
const sanitizedLocale = self.sanitizeLocaleName(req.body.locale);
|
|
290
|
+
if (!sanitizedLocale) {
|
|
291
|
+
throw self.apos.error('invalid', 'invalid locale');
|
|
292
|
+
}
|
|
290
293
|
// Clipboards transferring between locales needs to jump
|
|
291
294
|
// from LocalStorage to the cross-domain session cache
|
|
292
295
|
let clipboard = req.body.clipboard;
|
|
@@ -626,21 +629,29 @@ module.exports = {
|
|
|
626
629
|
// if possible, to the corresponding version in toLocale.
|
|
627
630
|
toLocaleRouteFactory(module) {
|
|
628
631
|
return async (req, res) => {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
632
|
+
try {
|
|
633
|
+
const _id = module.inferIdLocaleAndMode(req, req.params._id);
|
|
634
|
+
const toLocale = self.sanitizeLocaleName(req.params.toLocale);
|
|
635
|
+
if (!toLocale) {
|
|
636
|
+
return res.status(400).send('invalid locale name');
|
|
637
|
+
}
|
|
638
|
+
const localeReq = req.clone({
|
|
639
|
+
locale: toLocale
|
|
640
|
+
});
|
|
641
|
+
const corresponding = await module.find(localeReq, {
|
|
642
|
+
_id: `${_id.split(':')[0]}:${localeReq.locale}:${localeReq.mode}`
|
|
643
|
+
}).toObject();
|
|
644
|
+
if (!corresponding) {
|
|
645
|
+
return res.status(404).send('not found');
|
|
646
|
+
}
|
|
647
|
+
if (!corresponding._url) {
|
|
648
|
+
return res.status(400).send('invalid (has no URL)');
|
|
649
|
+
}
|
|
650
|
+
return res.redirect(corresponding._url);
|
|
651
|
+
} catch (e) {
|
|
652
|
+
self.apos.util.error(e);
|
|
653
|
+
return res.status(500).send('error');
|
|
642
654
|
}
|
|
643
|
-
return res.redirect(corresponding._url);
|
|
644
655
|
};
|
|
645
656
|
},
|
|
646
657
|
// Exclude private locales when logged out
|
|
@@ -290,6 +290,34 @@ module.exports = {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
}),
|
|
293
|
+
routes(self, options) {
|
|
294
|
+
return {
|
|
295
|
+
get: {
|
|
296
|
+
// Convenience route to get the URL of the image
|
|
297
|
+
// knowing only the image id. Useful in the rich text editor.
|
|
298
|
+
// Not performant for frontend use
|
|
299
|
+
':imageId/src': async (req, res) => {
|
|
300
|
+
const size = req.query.size || self.getLargestSize();
|
|
301
|
+
try {
|
|
302
|
+
const image = await self.find(req, {
|
|
303
|
+
aposDocId: req.params.imageId
|
|
304
|
+
}).toObject();
|
|
305
|
+
if (!image) {
|
|
306
|
+
return res.status(404).send('notfound');
|
|
307
|
+
}
|
|
308
|
+
const url = image.attachment && image.attachment._urls && image.attachment._urls[size];
|
|
309
|
+
if (url) {
|
|
310
|
+
return res.redirect(image.attachment._urls[size]);
|
|
311
|
+
}
|
|
312
|
+
return res.status(404).send('notfound');
|
|
313
|
+
} catch (e) {
|
|
314
|
+
self.apos.util.error(e);
|
|
315
|
+
return res.status(500).send('error');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
},
|
|
293
321
|
methods(self) {
|
|
294
322
|
return {
|
|
295
323
|
// This method is available as a template helper: apos.image.first
|
|
@@ -417,6 +445,14 @@ module.exports = {
|
|
|
417
445
|
self.getComponentName('managerModal', 'AposMediaManager'),
|
|
418
446
|
{ moduleName: self.__meta.name }
|
|
419
447
|
);
|
|
448
|
+
},
|
|
449
|
+
getLargestSize() {
|
|
450
|
+
return self.apos.attachment.imageSizes.reduce((a, size) => {
|
|
451
|
+
return size.width > a.width ? size : a;
|
|
452
|
+
}, {
|
|
453
|
+
name: 'dummy',
|
|
454
|
+
width: 0
|
|
455
|
+
}).name;
|
|
420
456
|
}
|
|
421
457
|
};
|
|
422
458
|
},
|
|
@@ -5,24 +5,18 @@
|
|
|
5
5
|
class="image-widget-placeholder"
|
|
6
6
|
/>
|
|
7
7
|
{% else %}
|
|
8
|
-
{%
|
|
9
|
-
|
|
10
|
-
{%
|
|
11
|
-
|
|
12
|
-
{% endif %}
|
|
13
|
-
|
|
14
|
-
{% if data.options.dimensionAttrs %}
|
|
15
|
-
{% set dimensionAttrs = data.options.dimensionAttrs %}
|
|
16
|
-
{% elif data.manager.options.dimensionAttrs %}
|
|
17
|
-
{% set dimensionAttrs = data.manager.options.dimensionAttrs %}
|
|
18
|
-
{% endif %}
|
|
8
|
+
{% set className = data.options.className or data.manager.options.className %}
|
|
9
|
+
{% set dimensionAttrs = data.options.dimensionAttrs or data.manager.options.dimensionAttrs %}
|
|
10
|
+
{% set loadingType = data.options.loadingType or data.manager.options.loadingType %}
|
|
11
|
+
{% set size = data.options.size or data.manager.options.size or 'full' %}
|
|
19
12
|
|
|
20
13
|
{% set attachment = apos.image.first(data.widget._image) %}
|
|
21
14
|
|
|
22
15
|
{% if attachment %}
|
|
23
16
|
<img {% if className %} class="{{ className }}"{% endif %}
|
|
17
|
+
{% if loadingType %} loading="{{ loadingType }}"{% endif %}
|
|
24
18
|
srcset="{{ apos.image.srcset(attachment) }}"
|
|
25
|
-
src="{{ apos.attachment.url(attachment, { size:
|
|
19
|
+
src="{{ apos.attachment.url(attachment, { size: size }) }}"
|
|
26
20
|
alt="{{ attachment._alt or '' }}"
|
|
27
21
|
{% if dimensionAttrs %}
|
|
28
22
|
{% if attachment.width %} width="{{ apos.attachment.getWidth(attachment) }}" {% endif %}
|