apostrophe 3.6.0 → 3.9.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.
Files changed (71) hide show
  1. package/.eslintrc +4 -0
  2. package/.github/workflows/main.yml +45 -0
  3. package/CHANGELOG.md +92 -3
  4. package/README.md +2 -3
  5. package/index.js +104 -3
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +5 -1
  7. package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +15 -10
  8. package/modules/@apostrophecms/asset/index.js +105 -15
  9. package/modules/@apostrophecms/attachment/index.js +1 -4
  10. package/modules/@apostrophecms/db/index.js +5 -6
  11. package/modules/@apostrophecms/doc/index.js +2 -0
  12. package/modules/@apostrophecms/doc-type/index.js +39 -16
  13. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  14. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +0 -1
  15. package/modules/@apostrophecms/i18n/i18n/en.json +23 -4
  16. package/modules/@apostrophecms/i18n/i18n/es.json +1 -2
  17. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
  18. package/modules/@apostrophecms/i18n/i18n/sk.json +3 -4
  19. package/modules/@apostrophecms/i18n/index.js +36 -6
  20. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  21. package/modules/@apostrophecms/image-widget/index.js +2 -1
  22. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  23. package/modules/@apostrophecms/job/index.js +165 -220
  24. package/modules/@apostrophecms/login/index.js +0 -15
  25. package/modules/@apostrophecms/migration/index.js +1 -1
  26. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  27. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
  28. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  29. package/modules/@apostrophecms/module/index.js +1 -4
  30. package/modules/@apostrophecms/notification/index.js +116 -8
  31. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  32. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  33. package/modules/@apostrophecms/page/index.js +84 -52
  34. package/modules/@apostrophecms/page-type/index.js +5 -1
  35. package/modules/@apostrophecms/piece-type/index.js +183 -61
  36. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +180 -50
  37. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
  38. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +141 -0
  39. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
  40. package/modules/@apostrophecms/schema/index.js +81 -25
  41. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +9 -3
  42. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  43. package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +11 -4
  44. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +2 -2
  45. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
  46. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
  47. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
  48. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +0 -2
  49. package/modules/@apostrophecms/schema/ui/apos/components/AposLogo.vue +1 -1
  50. package/modules/@apostrophecms/schema/ui/apos/components/AposLogoIcon.vue +1 -1
  51. package/modules/@apostrophecms/schema/ui/apos/components/AposLogoPadless.vue +1 -1
  52. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +0 -1
  53. package/modules/@apostrophecms/search/index.js +53 -33
  54. package/modules/@apostrophecms/task/index.js +7 -3
  55. package/modules/@apostrophecms/template/index.js +7 -11
  56. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -0
  57. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
  58. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  59. package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +9 -3
  60. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
  61. package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +3 -2
  62. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
  63. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  64. package/modules/@apostrophecms/widget-type/index.js +1 -1
  65. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +5 -19
  66. package/package.json +2 -2
  67. package/test/job.js +224 -0
  68. package/test/pieces.js +34 -0
  69. package/test-lib/util.js +32 -0
  70. package/.circleci/config.yml +0 -94
  71. package/.scratch.md +0 -2
@@ -1,4 +1,4 @@
1
- // This module establishes `apos.db`, the mongodb driver connection object.
1
+ // This module establishes `apos.db`, the MongoDB database object.
2
2
  //
3
3
  // ## Options
4
4
  //
@@ -20,8 +20,7 @@
20
20
  //
21
21
  // ### `client`
22
22
  //
23
- // An existing MongoDB connection (MongoClient) object. If present, a new
24
- // connection instance is created that reuses the same sockets,
23
+ // An existing MongoDB connection (MongoClient) object. If present, it is used
25
24
  // and `uri`, `host`, `connect`, etc. are ignored.
26
25
  //
27
26
  // ### `versionCheck`
@@ -40,7 +39,7 @@
40
39
  // allow other modules to drop related non-MongoDB resources at the
41
40
  // same time, if desired.
42
41
  //
43
- // Note that `apos.db` is the mongodb database object, not this module.
42
+ // Note that `apos.db` is the MongoDB database object, not this module.
44
43
  // You shouldn't need to talk to this module after startup, but you can
45
44
  // access it as `apos.modules['@apostrophecms/db']` if you wish. You can
46
45
  // also access `apos.dbClient` if you need the MongoClient object.
@@ -81,8 +80,8 @@ module.exports = {
81
80
  },
82
81
  methods(self) {
83
82
  return {
84
- // Open the database connection. Always use MongoClient with its
85
- // sensible defaults. Build a URI if we need to, so we can call it
83
+ // Open the database connection. Always uses MongoClient with its
84
+ // sensible defaults. Builds a URI if necessary, so we can call it
86
85
  // in a consistent way.
87
86
  //
88
87
  // One default we override: if the connection is lost, we keep
@@ -439,6 +439,7 @@ module.exports = {
439
439
  await self.insertBody(req, doc, options);
440
440
  await m.emit('afterInsert', req, doc, options);
441
441
  await m.emit('afterSave', req, doc, options);
442
+ // TODO: Remove `afterLoad` in next major version. Deprecated.
442
443
  await m.emit('afterLoad', req, [ doc ]);
443
444
  return doc;
444
445
  },
@@ -474,6 +475,7 @@ module.exports = {
474
475
  await self.updateBody(req, doc, options);
475
476
  await m.emit('afterUpdate', req, doc, options);
476
477
  await m.emit('afterSave', req, doc, options);
478
+ // TODO: Remove `afterLoad` in next major version. Deprecated.
477
479
  await m.emit('afterLoad', req, [ doc ]);
478
480
  return doc;
479
481
  },
@@ -389,7 +389,7 @@ module.exports = {
389
389
  self.schema = self.apos.schema.compose({
390
390
  addFields: self.apos.schema.fieldsToArray(`Module ${self.__meta.name}`, self.fields),
391
391
  arrangeFields: self.apos.schema.groupsToArray(self.fieldsGroups)
392
- });
392
+ }, self);
393
393
  if (self.options.slugPrefix) {
394
394
  if (self.options.slugPrefix === 'deduplicate-') {
395
395
  const req = self.apos.task.getReq();
@@ -545,8 +545,11 @@ module.exports = {
545
545
 
546
546
  await self.apos.schema.convert(req, schema, input, doc);
547
547
 
548
- doc.copyOfId = copyOf && copyOf._id;
549
548
  if (copyOf) {
549
+ if (copyOf._id) {
550
+ doc.copyOfId = copyOf._id;
551
+ }
552
+
550
553
  self.apos.schema.regenerateIds(req, fullSchema, doc);
551
554
  }
552
555
  },
@@ -554,17 +557,9 @@ module.exports = {
554
557
  // taking into account issues like relationship fields keeping their data in
555
558
  // a separate ids property, etc.
556
559
  fieldsPresent(input) {
557
- const schema = self.schema;
558
- const output = [];
559
- for (const field of schema) {
560
- if (field.type.name.substring(0, 5) === '_relationship') {
561
- if (_.has(input, field.idsStorage)) {
562
- output.push(field.name);
563
- }
564
- } else {
565
- output.push(field.name);
566
- }
567
- }
560
+ return self.schema
561
+ .filter((field) => _.has(input, field.name))
562
+ .map((field) => field.name);
568
563
  },
569
564
  // Returns a query that finds docs the current user can edit. Unlike
570
565
  // find(), this query defaults to including docs in the archive. Subclasses
@@ -738,7 +733,7 @@ module.exports = {
738
733
  },
739
734
  // Localize (export) the given draft to another locale, creating the document in the
740
735
  // other locale if necessary. By default, if the document already exists in the
741
- // other locale, it is not ovewritten. Use the `update: true` option to change that.
736
+ // other locale, it is not overwritten. Use the `update: true` option to change that.
742
737
  // You can localize starting from either draft or published content. Either way what
743
738
  // gets created or updated in the other locale is a draft.
744
739
  async localize(req, draft, toLocale, options = { update: false }) {
@@ -1217,6 +1212,21 @@ module.exports = {
1217
1212
  // cursors.
1218
1213
 
1219
1214
  project: {
1215
+ launder (p) {
1216
+ // check that project is an object
1217
+ if (!p || typeof p !== 'object' || Array.isArray(p)) {
1218
+ return {};
1219
+ }
1220
+
1221
+ const projection = Object.entries(p).reduce((acc, [ key, val ]) => {
1222
+ return {
1223
+ ...acc,
1224
+ [key]: self.apos.launder.boolean(val)
1225
+ };
1226
+ }, {});
1227
+
1228
+ return projection;
1229
+ },
1220
1230
  finalize() {
1221
1231
  let projection = query.get('project') || {};
1222
1232
  // Keys beginning with `_` are computed values
@@ -1259,6 +1269,14 @@ module.exports = {
1259
1269
  if (query.get('search')) {
1260
1270
  // MongoDB mandates this if we want to sort on search result quality
1261
1271
  projection.textScore = { $meta: 'textScore' };
1272
+ } else if (projection.textScore) {
1273
+ // Gracefully elide the textScore projection when it is not useful and
1274
+ // would cause an error anyway.
1275
+ //
1276
+ // This allows the reuse of the `project()` value passed to one query
1277
+ // in a second query without worrying about whether the second query
1278
+ // contains a search or not
1279
+ delete projection.textScore;
1262
1280
  }
1263
1281
  query.set('project', projection);
1264
1282
  }
@@ -1442,9 +1460,14 @@ module.exports = {
1442
1460
  attachments: {
1443
1461
  def: false,
1444
1462
  after(results) {
1445
- for (const doc of results) {
1446
- self.apos.attachment.all(doc, { annotate: true });
1463
+ const attachments = query.get('attachments');
1464
+
1465
+ if (attachments) {
1466
+ self.apos.attachment.all(results, { annotate: true });
1447
1467
  }
1468
+ },
1469
+ launder(b) {
1470
+ return self.apos.launder.boolean(b);
1448
1471
  }
1449
1472
  },
1450
1473
 
@@ -286,9 +286,21 @@ export default {
286
286
  apos.bus.$off('content-changed', this.onContentChanged);
287
287
  },
288
288
  methods: {
289
- onContentChanged(e) {
289
+ async onContentChanged(e) {
290
290
  if (e.doc && (e.doc._id === this.context._id)) {
291
291
  this.context = e.doc;
292
+ } else if (e.docIds && e.docIds.includes(this.context._id)) {
293
+ try {
294
+ this.context = await apos.http.get(`${this.moduleOptions.action}/${this.context._id}`, {
295
+ busy: true
296
+ });
297
+ } catch (error) {
298
+ // If not found it is likely that there was an archiving or restoring
299
+ // batch operation.
300
+ if (error.name !== 'notfound') {
301
+ console.error(error);
302
+ }
303
+ }
292
304
  }
293
305
  },
294
306
  menuHandler(action) {
@@ -162,7 +162,6 @@ export default {
162
162
  return `${this.moduleAction}/${this.docId}`;
163
163
  },
164
164
  tooltip() {
165
- // TODO I18N
166
165
  let msg;
167
166
  if (this.errorCount) {
168
167
  msg = {
@@ -3,6 +3,7 @@
3
3
  "addItem": "Add Item",
4
4
  "addWidgetType": "Add {{ label }}",
5
5
  "admin": "Admin",
6
+ "affirmativeLabel": "Yes, continue.",
6
7
  "altText": "Alt Text",
7
8
  "altTextHelp": "Image description used for accessibility",
8
9
  "any": "Any",
@@ -17,6 +18,8 @@
17
18
  "archiveTypeAffirmativeLabel": "Yes, archive {{ type }}",
18
19
  "archiveTypeNote": "You are currently viewing the {{ type }} you want to archive. When it is archived you will be returned to the home page.",
19
20
  "archived": "Archived",
21
+ "archivingBatchConfirmation": "Are you sure you want to archive {{ count }} {{ type }}?",
22
+ "archivingBatchConfirmationButton": "Yes, archive content.",
20
23
  "archivingDraftChildCount": "{{ count }} of those descendants have never been published.",
21
24
  "archivingPageHasChild": "That page has one descendant.",
22
25
  "archivingPageHasChild_plural": "That page has {{ count }} descendants.",
@@ -121,6 +124,7 @@
121
124
  "errorPageMessage": "An error has occurred",
122
125
  "errorPageStatusCode": "500",
123
126
  "errorPageTitle": "An error has occurred",
127
+ "errorBatchOperationNoti": "Batch operation {{ operation }} failed.",
124
128
  "everythingElse": "Everything Else",
125
129
  "exit": "Exit",
126
130
  "fetchPublishedVersionFailed": "An error occurred fetching the published version of the document.",
@@ -173,6 +177,8 @@
173
177
  "manageDocType": "Manage {{ type }}",
174
178
  "manageDraftSubmissions": "Manage Draft Submissions",
175
179
  "managePages": "Manage Pages",
180
+ "maxLabel": "Max:",
181
+ "maxUi": "Max: {{ number }}",
176
182
  "mediaCreatedDate": "Uploaded: {{ createdDate }}",
177
183
  "mediaDimensions": "Dimensions: {{ width }} 𝗑 {{ height }}",
178
184
  "mediaFileSize": "File Size: {{ fileSize }}",
@@ -180,6 +186,8 @@
180
186
  "mediaMB": "{{ size }}MB",
181
187
  "mediaUploadViaDrop": "Drop ’em when you’re ready",
182
188
  "mediaUploadViaExplorer": "Or click to open the file explorer",
189
+ "minLabel": "Min:",
190
+ "minUi": "Min: {{ number }}",
183
191
  "modify": "Modify",
184
192
  "modifyOrDelete": "Modify / Delete",
185
193
  "moreOptions": "More Options",
@@ -204,11 +212,13 @@
204
212
  "notFoundPageStatusCode": "404",
205
213
  "notFoundPageTitle": "404 - Page not found",
206
214
  "notInLocale": "The current page doesn’t exist in {{ label }}. Localize the version from {{ currentLocale }}?",
215
+ "notificationClearEventError": "There was an error clearing a registered notification event.",
207
216
  "noTypeFound": "No {{ type }} Found",
208
217
  "parentNotLocalized": "Localize the parent page first",
209
218
  "notYetPublished": "This document hasn't been published yet.",
210
219
  "nudgeDown": "Nudge Down",
211
220
  "nudgeUp": "Nudge Up",
221
+ "numberAdded": "{{ count }} Added",
212
222
  "office": "Office",
213
223
  "openGlobal": "Open Global Site Settings",
214
224
  "page": "Page",
@@ -244,6 +254,8 @@
244
254
  "richTextUndo": "Undo",
245
255
  "richTextStyleConfigWarning": "Misconfigured rich text style: label: {{ label }}, {{ tag }}",
246
256
  "password": "Password",
257
+ "passwordErrorMin": "Minimum of {{ min }} characters",
258
+ "passwordErrorMax": "Maximum of {{ max }} characters",
247
259
  "passwordResetRequest": "Your request to reset your password on {{ site }}",
248
260
  "pasteWidget": "Paste {{ widget }}",
249
261
  "pending": "Pending",
@@ -255,12 +267,12 @@
255
267
  "provideButtonLabel": "Provide a Button Label",
256
268
  "previousPage": "Previous Page",
257
269
  "public": "Public",
258
- "modernBuild": "public-facing modern JavaScript and Sass",
259
- "ie11Build": "public-facing modern JavaScript and Sass (IE11 build)",
270
+ "modernBuild": "Public-facing modern JavaScript and Sass",
271
+ "ie11Build": "Public-facing modern JavaScript and Sass (IE11 build)",
260
272
  "publish": "Publish",
261
273
  "publishBeforeUsingTooltip": "Publish this content before using it in a relationship",
262
274
  "published": "Published",
263
- "rawCssAndJs": "raw CSS and JS",
275
+ "rawCssAndJs": "Raw CSS and JS",
264
276
  "rawHtml": "Raw HTML",
265
277
  "rawHtmlCode": "Raw HTML (Code)",
266
278
  "rawHtmlCodeHelp": "Be careful when embedding third-party code, as it can break the website editing functionality. If a page becomes unusable, add \"?safe_mode=1\" to the URL to make it work temporarily without the problem code being rendered.",
@@ -279,6 +291,8 @@
279
291
  "relatedDocsOnly": "Related documents only",
280
292
  "relatedDocsDefinition": "Related documents are documents referenced by this document. This typically includes images, content defined by relationships, etc.",
281
293
  "restore": "Restore",
294
+ "restoreBatchConfirmation": "Are you sure you want to restore {{ count }} {{ type }}?",
295
+ "restoreBatchConfirmationButton": "Yes, restore content.",
282
296
  "resolveErrorsBeforeSaving": "Resolve errors before saving.",
283
297
  "resolveErrorsFirst": "Resolve errors first.",
284
298
  "restoreOnlyThisPage": "Restore only this page",
@@ -307,6 +321,12 @@
307
321
  "select": "Select",
308
322
  "selectedMenuItem": "✓ {{ label }}",
309
323
  "selectAll": "Select All",
324
+ "selectBoxMessage": "{{ num }} {{ label }} selected.",
325
+ "selectBoxMessagePage": "{{ num }} {{ label }} on this page selected.",
326
+ "selectBoxMessageAllButton": "Select all {{ num }} {{ label }}.",
327
+ "selectBoxMessageButton": "Select {{ num }} {{ label }}.",
328
+ "selectBoxMessageSelected": "{{ num }} {{ label }} selected.",
329
+ "selectBoxMessageAllSelected": "All {{ num }} {{ label }} selected.",
310
330
  "deselectAll": "Deselect All",
311
331
  "selectContent": "Select Content",
312
332
  "selectContentToLocalize": "What content do you want to localize?",
@@ -375,7 +395,6 @@
375
395
  "videoUrlHelp": "Enter the URL for a media source you wish to embed (e.g., YouTube, Vimeo, or other hosted video URL).",
376
396
  "view": "View",
377
397
  "visibility": "Visibility",
378
- "visibilityLabel": "Who can view this?",
379
398
  "willMoveImageToArchive": "This will move the image to the archive.",
380
399
  "yes": "Yes",
381
400
  "yesLocalizeAndSwitchLocales": "Yes, localize this page and switch locales",
@@ -204,7 +204,7 @@
204
204
  "notFoundPageStatusCode": "404",
205
205
  "notFoundPageTitle": "404 - Página no encontrada",
206
206
  "notInLocale": "La página actual no existe en {{ label }}. ¿Traducir la versión desde la configuración regional {{ currentLocale }}?",
207
- "noTypeFound": "Ningún {{ type }} Ecnontrado",
207
+ "noTypeFound": "Ningún {{ type }} Encontrado",
208
208
  "parentNotLocalized": "Primero traduzca la configuración regional de la página principal",
209
209
  "notYetPublished": "Este documento aún no ha sido publicado.",
210
210
  "nudgeDown": "Mover Hacia Arriba",
@@ -375,7 +375,6 @@
375
375
  "videoUrlHelp": "Ingresa la dirección URL del video que quiere insertar (e.j., YouTube, Vimeo, o otra dirección URL de video).",
376
376
  "view": "Mirar",
377
377
  "visibility": "Visibilidad",
378
- "visibilityLabel": "¿Quién puede mirar esto?",
379
378
  "willMoveImageToArchive": "Esto moverá la imagen al archivo de documentos.",
380
379
  "yes": "Sí",
381
380
  "yesLocalizeAndSwitchLocales": "Sí, traducir esta página y cambiar de configuración regional",
@@ -372,7 +372,6 @@
372
372
  "videoUrlHelp": "Insira o URL de uma fonte de mídia que deseja incorporar (por exemplo, YouTube, Vimeo ou outro URL de vídeo).",
373
373
  "view": "Visualizar",
374
374
  "visibility": "Visibilidade",
375
- "visibilityLabel": "Quem pode ver isto?",
376
375
  "willMoveImageToArchive": "Isso moverá a imagem para o arquivo.",
377
376
  "yes": "Sim",
378
377
  "yesLocalizeAndSwitchLocales": "Sim, localize esta página e mude de local",
@@ -253,12 +253,12 @@
253
253
  "provideButtonLabel": "Poskytnite štítok s tlačidlom",
254
254
  "previousPage": "Predchádzajúca strana",
255
255
  "public": "Verejné",
256
- "modernBuild": "verejnosti orientovaný moderný JavaScript a Sass",
257
- "ie11Build": "verejný moderný JavaScript a Sass (zostava IE11)",
256
+ "modernBuild": "Verejnosti orientovaný moderný JavaScript a Sass",
257
+ "ie11Build": "Verejný moderný JavaScript a Sass (zostava IE11)",
258
258
  "publish": "Publikovať",
259
259
  "publishBeforeUsingTooltip": "Pred použitím v kontexte zverejnite tento obsah",
260
260
  "published": "Publikovaný",
261
- "rawCssAndJs": "zdrojový CSS a JS",
261
+ "rawCssAndJs": "Zdrojový CSS a JS",
262
262
  "rawHtml": "Zdrojový HTML",
263
263
  "rawHtmlCode": "Zdrojový HTML (kód)",
264
264
  "rawHtmlCodeHelp": "Pri vkladaní kódu tretej strany buďte opatrní, pretože môže narušiť funkciu úprav webových stránok. Ak sa stránka stane nepoužiteľnou, pridajte \"?safe_mode=1\" na adresu URL, aby dočasne fungovala bez toho, aby sa zobrazil problémový kód.",
@@ -373,7 +373,6 @@
373
373
  "videoUrlHelp": "Zadajte webovú adresu zdroja média, ktorý chcete vložiť (napr. YouTube, Vimeo alebo inej adresy URL hosteného videa).",
374
374
  "view": "Pozrieť",
375
375
  "visibility": "Viditeľnosť",
376
- "visibilityLabel": "Kto to môže vidieť?",
377
376
  "willMoveImageToArchive": "Tým sa obrázok presunie do archívu.",
378
377
  "yes": "Áno",
379
378
  "yesLocalizeAndSwitchLocales": "Áno, preložte túto stránku a prepnite jazykovú mutáciu",
@@ -13,11 +13,27 @@ const ExpressSessionCookie = require('express-session/session/cookie');
13
13
 
14
14
  const apostropheI18nDebugPlugin = {
15
15
  type: 'postProcessor',
16
- name: 'apostrophei18nDebugPlugin',
16
+ name: 'apostropheI18nDebugPlugin',
17
17
  process(value, key, options, translator) {
18
- // For ease of tracking down which phrases were
19
- // actually passed through i18next
20
- return `🌍 ${value}`;
18
+ // The key is passed as an array (theoretically to include multiple keys).
19
+ // We confirm that and grab the primary one for comparison.
20
+ const l10nKey = Array.isArray(key) ? key[0] : key;
21
+
22
+ if (value === l10nKey) {
23
+ if (l10nKey.match(/^\S+:/)) {
24
+ // The l10n key does not have a value assigned (or the key is
25
+ // actually the same as the phrase). The key seems to have a
26
+ // namespace, so might be from the Apostrophe UI.
27
+ return `❌ ${value}`;
28
+ } else {
29
+ // The l10n key does not have a value assigned (or the key is
30
+ // actually the same as the phrase). It is in the default namespace.
31
+ return `🕳 ${value}`;
32
+ }
33
+ } else {
34
+ // The phrase is fully localized.
35
+ return `🌍 ${value}`;
36
+ }
21
37
  }
22
38
  };
23
39
 
@@ -51,9 +67,14 @@ module.exports = {
51
67
  throw self.apos.error('invalid', `Locale prefixes must not contain more than one forward slash ("/").\nUse hyphens as separators. Check locale "${key}".`);
52
68
  }
53
69
  }
70
+ const fallbackLng = [ self.defaultLocale ];
71
+ // In case the default locale also has inadequate admin UI phrases
72
+ if (fallbackLng[0] !== 'en') {
73
+ fallbackLng.push('en');
74
+ }
54
75
  // Make sure we have our own instance to avoid conflicts with other apos objects
55
76
  self.i18next = i18next.createInstance({
56
- fallbackLng: self.defaultLocale,
77
+ fallbackLng,
57
78
  // Required to prevent the debugger from complaining
58
79
  languages: Object.keys(self.locales),
59
80
  // Added later, but required here
@@ -68,7 +89,12 @@ module.exports = {
68
89
  if (self.show) {
69
90
  self.i18next.use(apostropheI18nDebugPlugin);
70
91
  }
71
- await self.i18next.init();
92
+
93
+ const i18nextOptions = self.show ? {
94
+ postProcess: 'apostropheI18nDebugPlugin'
95
+ } : {};
96
+
97
+ await self.i18next.init(i18nextOptions);
72
98
  self.addInitialResources();
73
99
  self.enableBrowserData();
74
100
  },
@@ -459,6 +485,10 @@ module.exports = {
459
485
  if (req.locale !== self.defaultLocale) {
460
486
  i18n[self.defaultLocale] = self.getBrowserBundles(self.defaultLocale);
461
487
  }
488
+ // In case the default locale also has inadequate admin UI phrases
489
+ if (!i18n.en) {
490
+ i18n.en = self.getBrowserBundles('en');
491
+ }
462
492
  const result = {
463
493
  i18n,
464
494
  locale: req.locale,
@@ -45,10 +45,13 @@
45
45
  <template #bodyHeader>
46
46
  <AposDocsManagerToolbar
47
47
  :selected-state="selectAllState"
48
- :total-pages="totalPages" :current-page="currentPage"
49
- :filters="toolbarFilters" :labels="moduleLabels"
48
+ :total-pages="totalPages"
49
+ :current-page="currentPage"
50
+ :filters="toolbarFilters"
51
+ :labels="moduleLabels"
50
52
  :disable="relationshipErrors === 'min'"
51
- :options="{ hideSelectAll: !relationshipField }"
53
+ :displayed-items="items.length"
54
+ :checked-count="checked.length"
52
55
  @page-change="updatePage"
53
56
  @select-click="selectClick"
54
57
  @search="search"
@@ -3,7 +3,8 @@ module.exports = {
3
3
  options: {
4
4
  label: 'apostrophe:image',
5
5
  className: false,
6
- icon: 'image-icon'
6
+ icon: 'image-icon',
7
+ dimensionAttrs: false
7
8
  },
8
9
  fields: {
9
10
  add: {
@@ -4,15 +4,25 @@
4
4
  {% set className = data.manager.options.className %}
5
5
  {% endif %}
6
6
 
7
+ {% if data.options.dimensionAttrs %}
8
+ {% set dimensionAttrs = data.options.dimensionAttrs %}
9
+ {% elif data.manager.options.dimensionAttrs %}
10
+ {% set dimensionAttrs = data.manager.options.dimensionAttrs %}
11
+ {% endif %}
12
+
7
13
  {% set attachment = apos.image.first(data.widget._image) %}
8
14
 
9
15
  {% if attachment %}
10
- <img{% if className %} class="{{ className }}"{% endif %}
16
+ <img {% if className %} class="{{ className }}"{% endif %}
11
17
  srcset="{{ apos.image.srcset(attachment) }}"
12
18
  src="{{ apos.attachment.url(attachment, { size: data.options.size or 'full' }) }}"
13
19
  alt="{{ attachment._alt or '' }}"
20
+ {% if dimensionAttrs %}
21
+ {% if attachment.width %} width="{{ attachment.width }}" {% endif %}
22
+ {% if attachment.height %} height="{{ attachment.height }}" {% endif %}
23
+ {% endif %}
14
24
  {% if data.contextOptions and data.contextOptions.sizes %}
15
25
  sizes="{{ data.contextOptions.sizes }}"
16
26
  {% endif %}
17
27
  />
18
- {% endif %}
28
+ {% endif %}