apostrophe 4.28.1 → 4.29.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 (89) hide show
  1. package/CHANGELOG.md +29 -4
  2. package/README.md +2 -2
  3. package/defaults.js +1 -0
  4. package/lib/safe-json-script.js +27 -0
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -0
  7. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +3 -5
  8. package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +13 -1
  9. package/modules/@apostrophecms/asset/lib/globalIcons.js +3 -0
  10. package/modules/@apostrophecms/attachment/index.js +43 -1
  11. package/modules/@apostrophecms/color-field/index.js +7 -1
  12. package/modules/@apostrophecms/doc/index.js +11 -1
  13. package/modules/@apostrophecms/doc-type/index.js +165 -32
  14. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -1
  15. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +104 -59
  16. package/modules/@apostrophecms/file/index.js +109 -8
  17. package/modules/@apostrophecms/i18n/i18n/de.json +0 -2
  18. package/modules/@apostrophecms/i18n/i18n/en.json +40 -1
  19. package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
  20. package/modules/@apostrophecms/i18n/i18n/fr.json +0 -1
  21. package/modules/@apostrophecms/i18n/i18n/it.json +0 -1
  22. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
  23. package/modules/@apostrophecms/i18n/i18n/sk.json +0 -1
  24. package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nBatchReporting.js +18 -1
  25. package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nLocalizeActions.js +50 -0
  26. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +56 -13
  27. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +8 -2
  28. package/modules/@apostrophecms/layout-column-widget/index.js +156 -163
  29. package/modules/@apostrophecms/layout-widget/index.js +7 -2
  30. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposAreaLayoutEditor.vue +6 -11
  31. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridColumn.vue +3 -5
  32. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridLayout.vue +4 -4
  33. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridManager.vue +0 -16
  34. package/modules/@apostrophecms/layout-widget/ui/apos/lib/grid-state.mjs +7 -27
  35. package/modules/@apostrophecms/layout-widget/views/column.html +7 -9
  36. package/modules/@apostrophecms/login/index.js +39 -40
  37. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +17 -2
  38. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +3 -2
  39. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +1 -0
  40. package/modules/@apostrophecms/page/index.js +2 -0
  41. package/modules/@apostrophecms/piece-type/index.js +3 -1
  42. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
  43. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +5 -0
  44. package/modules/@apostrophecms/recently-edited/index.js +831 -0
  45. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposCellTitle.vue +54 -0
  46. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedCombo.vue +454 -0
  47. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilterTag.vue +75 -0
  48. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilters.vue +287 -0
  49. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedIcon.vue +16 -0
  50. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedManager.vue +346 -0
  51. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedBatch.js +193 -0
  52. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedData.js +276 -0
  53. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFetch.js +199 -0
  54. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFilters.js +100 -0
  55. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +8 -4
  56. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +1 -1
  57. package/modules/@apostrophecms/styles/index.js +10 -0
  58. package/modules/@apostrophecms/styles/lib/apiRoutes.js +6 -0
  59. package/modules/@apostrophecms/styles/lib/handlers.js +5 -0
  60. package/modules/@apostrophecms/styles/lib/methods.js +9 -3
  61. package/modules/@apostrophecms/styles/lib/presets.js +119 -0
  62. package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +3 -8
  63. package/modules/@apostrophecms/styles/ui/apos/composables/AposStyles.js +1 -3
  64. package/modules/@apostrophecms/styles/ui/apos/render-factory.js +29 -0
  65. package/modules/@apostrophecms/styles/ui/apos/universal/backgroundHelpers.mjs +140 -0
  66. package/modules/@apostrophecms/styles/ui/apos/universal/customRules.mjs +105 -0
  67. package/modules/@apostrophecms/styles/ui/apos/universal/render.mjs +195 -15
  68. package/modules/@apostrophecms/template/index.js +22 -6
  69. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +2 -0
  70. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +18 -4
  71. package/modules/@apostrophecms/ui/ui/apos/composables/useInfiniteScroll.js +91 -0
  72. package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
  73. package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +5 -2
  74. package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
  75. package/modules/@apostrophecms/url/index.js +38 -4
  76. package/modules/@apostrophecms/widget-type/index.js +22 -6
  77. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +8 -4
  78. package/package.json +17 -17
  79. package/test/add-missing-schema-fields-project/node_modules/.package-lock.json +2 -2
  80. package/test/layout-widget-migration.js +719 -0
  81. package/test/login-requirements.js +1 -1
  82. package/test/pieces-public-api.js +80 -0
  83. package/test/pieces.js +25 -0
  84. package/test/recently-edited.js +2311 -0
  85. package/test/schemas.js +39 -3
  86. package/test/static-build.js +642 -0
  87. package/test/styles.js +2569 -0
  88. package/.claude/settings.local.json +0 -15
  89. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposLayoutColControlDialog.vue +0 -171
@@ -92,6 +92,16 @@ export default {
92
92
  moduleLabels: {
93
93
  type: Object,
94
94
  default: null
95
+ },
96
+ showUnpublish: {
97
+ type: Boolean,
98
+ default() {
99
+ return true;
100
+ }
101
+ },
102
+ crossLocale: {
103
+ type: Boolean,
104
+ default: false
95
105
  }
96
106
  },
97
107
  emits: [ 'menu-open', 'menu-close', 'close' ],
@@ -105,6 +115,12 @@ export default {
105
115
  },
106
116
  computed: {
107
117
  menu() {
118
+ // TODO: Register core context actions (edit, preview,
119
+ // copy, localize, archive, etc.) as custom operations
120
+ // via `addContextOperation`. This would let us unify
121
+ // cross-locale gating through the `crossLocale`
122
+ // flag on each operation, eliminating the manual
123
+ // `!this.crossLocale` wrapping below.
108
124
  const menu = [
109
125
  // TODO
110
126
  // ...(this.isModifiedFromPublished ? [
@@ -128,64 +144,76 @@ export default {
128
144
  }
129
145
  ]
130
146
  : []),
131
- ...((this.showDismissSubmission && this.canDismissSubmission)
147
+ // In cross-locale mode, only Edit and Preview are safe.
148
+ // All other built-in ops are hidden.
149
+ ...(!this.crossLocale
132
150
  ? [
133
- {
134
- label: 'apostrophe:dismissSubmission',
135
- action: 'dismissSubmission'
136
- }
137
- ]
138
- : []),
139
- ...(this.showCopy && this.canCopy
140
- ? [
141
- {
142
- label: 'apostrophe:duplicate',
143
- action: 'copy'
144
- }
145
- ]
146
- : []),
147
- ...(this.canLocalize
148
- ? [
149
- {
150
- label: 'apostrophe:localize',
151
- action: 'localize'
152
- }
151
+ ...((this.showDismissSubmission && this.canDismissSubmission)
152
+ ? [
153
+ {
154
+ label: 'apostrophe:dismissSubmission',
155
+ action: 'dismissSubmission'
156
+ }
157
+ ]
158
+ : []),
159
+ ...(this.showCopy && this.canCopy
160
+ ? [
161
+ {
162
+ label: 'apostrophe:duplicate',
163
+ action: 'copy'
164
+ }
165
+ ]
166
+ : []),
167
+ ...(this.canLocalize
168
+ ? [
169
+ {
170
+ label: 'apostrophe:localize',
171
+ action: 'localize'
172
+ }
173
+ ]
174
+ : [])
153
175
  ]
154
176
  : []),
155
177
  ...this.customMenusByContext,
156
- ...((this.showDiscardDraft && this.canDiscardDraft)
178
+ ...(!this.crossLocale
157
179
  ? [
158
- {
159
- label: this.context.lastPublishedAt ? 'apostrophe:discardDraft' : 'apostrophe:deleteDraft',
160
- action: 'discardDraft',
161
- modifiers: [ 'danger' ]
162
- }
163
- ]
164
- : []),
165
- ...(this.showArchive && this.canArchive
166
- ? [
167
- {
168
- label: 'apostrophe:archive',
169
- action: 'archive',
170
- modifiers: [ 'danger' ]
171
- }
172
- ]
173
- : []),
174
- ...(this.canUnpublish
175
- ? [
176
- {
177
- label: 'apostrophe:unpublish',
178
- action: 'unpublish',
179
- modifiers: [ 'danger' ]
180
- }
181
- ]
182
- : []),
183
- ...(this.showRestore && this.canRestore
184
- ? [
185
- {
186
- label: 'apostrophe:restore',
187
- action: 'restore'
188
- }
180
+ ...((this.showDiscardDraft && this.canDiscardDraft)
181
+ ? [
182
+ {
183
+ label: this.context.lastPublishedAt
184
+ ? 'apostrophe:discardDraft'
185
+ : 'apostrophe:deleteDraft',
186
+ action: 'discardDraft',
187
+ modifiers: [ 'danger' ]
188
+ }
189
+ ]
190
+ : []),
191
+ ...(this.showArchive && this.canArchive
192
+ ? [
193
+ {
194
+ label: 'apostrophe:archive',
195
+ action: 'archive',
196
+ modifiers: [ 'danger' ]
197
+ }
198
+ ]
199
+ : []),
200
+ ...(this.showUnpublish && this.canUnpublish
201
+ ? [
202
+ {
203
+ label: 'apostrophe:unpublish',
204
+ action: 'unpublish',
205
+ modifiers: [ 'danger' ]
206
+ }
207
+ ]
208
+ : []),
209
+ ...(this.showRestore && this.canRestore
210
+ ? [
211
+ {
212
+ label: 'apostrophe:restore',
213
+ action: 'restore'
214
+ }
215
+ ]
216
+ : [])
189
217
  ]
190
218
  : [])
191
219
  ];
@@ -208,8 +236,14 @@ export default {
208
236
  },
209
237
  customOperationsByContext() {
210
238
  return this.customOperations.filter(({
211
- manuallyPublished, hasUrl, conditions, context, if: ifProps, moduleIf
239
+ manuallyPublished, hasUrl, conditions, context,
240
+ if: ifProps, moduleIf, crossLocale
212
241
  }) => {
242
+ // In cross-locale mode, only show operations explicitly marked as safe
243
+ if (this.crossLocale && !crossLocale) {
244
+ return false;
245
+ }
246
+
213
247
  if (typeof manuallyPublished === 'boolean' && manuallyPublished !== this.manuallyPublished) {
214
248
  return false;
215
249
  }
@@ -433,13 +467,19 @@ export default {
433
467
  this[item.action](this.context);
434
468
  },
435
469
  async edit(doc) {
470
+ const props = {
471
+ moduleName: this.moduleName,
472
+ docId: doc._id,
473
+ type: doc.type
474
+ };
475
+ // Cross-locale mode suported.
476
+ if (this.crossLocale && doc.aposLocale) {
477
+ props.locale = doc.aposLocale.split(':')[0];
478
+ }
436
479
  await apos.modal.execute(
437
480
  doc._aposEditorModal || this.moduleOptions.components.editorModal,
438
- {
439
- moduleName: this.moduleName,
440
- docId: doc._id,
441
- type: doc.type
442
- });
481
+ props
482
+ );
443
483
  },
444
484
  preview(doc) {
445
485
  window.open(doc._url, '_blank').focus();
@@ -490,6 +530,11 @@ export default {
490
530
  ...docProps(doc),
491
531
  ...operation.props
492
532
  };
533
+ // In cross-locale mode, pass the doc's locale so the modal
534
+ // opens in the correct locale context.
535
+ if (this.crossLocale && doc.aposLocale) {
536
+ props.locale = doc.aposLocale.split(':')[0];
537
+ }
493
538
  if (operation.type === 'event') {
494
539
  apos.bus.$emit(operation.action, props);
495
540
  return;
@@ -105,18 +105,112 @@ module.exports = {
105
105
  attachmentDocIds: []
106
106
  };
107
107
  },
108
- addUrls(req, files) {
108
+ // Returns the pretty URL base path (e.g. `/files`) when
109
+ // pretty URLs are enabled, or `null` otherwise.
110
+ // The returned path is prefix-qualified. If `options.relative`
111
+ // is true, the path is relative (no origin).
112
+ getPrettyUrlBase(req, { relative = false } = {}) {
113
+ if (!self.options.prettyUrls) {
114
+ return null;
115
+ }
116
+ const base = relative
117
+ ? self.apos.url.getBaseUrl(req, {
118
+ relative: true,
119
+ localePrefix: true
120
+ })
121
+ : req.prefix;
122
+ return `${base}${self.options.prettyUrlDir}`;
123
+ },
124
+ // Returns the pretty-URL path component for a file doc.
125
+ // E.g. `/my-document.pdf` — just the filename portion,
126
+ // without any base, prefix, or origin.
127
+ // Returns `null` when pretty URLs are disabled or the
128
+ // doc has no attachment. Works with any object that has
129
+ // `slug` and `attachment.extension` (including the
130
+ // lightweight projections used by `applyPrettyUrlPaths`).
131
+ getPrettyPath(doc) {
132
+ if (!self.options.prettyUrls || !doc.attachment) {
133
+ return null;
134
+ }
135
+ const slug = doc.slug.replace(self.options.slugPrefix || '', '');
136
+ return `/${slug}.${doc.attachment.extension}`;
137
+ },
138
+ // When `options.relative` is true, pretty URLs are built
139
+ // with only the prefix + prettyUrlDir (no origin).
140
+ addUrls(req, files, { relative = false } = {}) {
141
+ const prettyBase = self.getPrettyUrlBase(req, { relative });
109
142
  for (const file of files) {
110
- // Watch out for projections with no attachment property
111
- // (the slug-taken route does that)
112
- if (self.options.prettyUrls && file.attachment) {
113
- const { extension } = file.attachment;
114
- file._url = `${req.prefix}${self.options.prettyUrlDir}/${file.slug.replace(self.options.slugPrefix || '', '')}.${extension}`;
143
+ const prettyPath = self.getPrettyPath(file);
144
+ if (prettyBase && prettyPath) {
145
+ file._url = `${prettyBase}${prettyPath}`;
115
146
  file.attachment._prettyUrl = file._url;
116
- } else {
147
+ } else if (file.attachment) {
117
148
  file._url = self.apos.attachment.url(file.attachment);
118
149
  }
119
150
  }
151
+ },
152
+ // Mutates `attachmentMeta.results` in-place, replacing
153
+ // uploadfs paths with pretty URL paths for file pieces
154
+ // that have pretty URLs enabled. Adds a `base` property
155
+ // to each affected entry so the consumer can override the
156
+ // global `uploadsUrl` for those entries.
157
+ //
158
+ // Uses a lightweight direct DB query (no builders) to
159
+ // find file docs by attachment ID. Returns nothing —
160
+ // the mutation is performed on the input object.
161
+ //
162
+ // Does nothing when pretty URLs are disabled.
163
+ // For the shape of the `attachmentMeta` object, see the
164
+ // `@apostrophecms/url:getAllAttachmentMetadata` method documentation.
165
+ async applyPrettyUrlPaths(req, attachmentMeta) {
166
+ if (!self.options.prettyUrls || !attachmentMeta?.results?.length) {
167
+ return;
168
+ }
169
+
170
+ const base = self.getPrettyUrlBase(req, { relative: true });
171
+ const batchSize = 100;
172
+
173
+ for (let i = 0; i < attachmentMeta.results.length; i += batchSize) {
174
+ const batch = attachmentMeta.results.slice(i, i + batchSize);
175
+ const batchIds = batch.map(a => a._id);
176
+
177
+ // Lightweight direct query — no builders, no relationship
178
+ // resolution. Only needs slug and attachment ID/extension.
179
+ const fileDocs = await self.apos.doc.db.find({
180
+ type: self.options.name,
181
+ 'attachment._id': { $in: batchIds },
182
+ aposLocale: `${req.locale}:${req.mode}`,
183
+ archived: { $ne: true }
184
+ }, {
185
+ projection: {
186
+ slug: 1,
187
+ 'attachment._id': 1,
188
+ 'attachment.extension': 1
189
+ }
190
+ }).toArray();
191
+
192
+ if (!fileDocs.length) {
193
+ continue;
194
+ }
195
+
196
+ const byAttId = new Map();
197
+ for (const entry of batch) {
198
+ byAttId.set(entry._id, entry);
199
+ }
200
+
201
+ for (const doc of fileDocs) {
202
+ const entry = byAttId.get(doc.attachment._id);
203
+ if (!entry) {
204
+ continue;
205
+ }
206
+ const prettyPath = self.getPrettyPath(doc);
207
+ if (!prettyPath) {
208
+ continue;
209
+ }
210
+ entry.urls = [ { path: prettyPath } ];
211
+ entry.base = base;
212
+ }
213
+ }
120
214
  }
121
215
  };
122
216
  },
@@ -154,7 +248,14 @@ module.exports = {
154
248
  const uglyUrl = self.apos.attachment.url(file.attachment, {
155
249
  prettyUrl: false
156
250
  });
157
- return await streamProxy(req, uglyUrl, { error: self.apos.util.error });
251
+ // For relative URLs (local uploadfs, not CDN), resolve
252
+ // against the current server's origin so the proxy can
253
+ // make the self-request. During static builds
254
+ // `attachment.url()` may return only a path.
255
+ const proxyUrl = uglyUrl.startsWith('/')
256
+ ? `${req.protocol}://${req.get('host')}${uglyUrl}`
257
+ : uglyUrl;
258
+ return await streamProxy(req, proxyUrl, { error: self.apos.util.error });
158
259
  } catch (e) {
159
260
  self.apos.util.error('Error in pretty URL route:', e);
160
261
  return res.status(500).send('error');
@@ -71,7 +71,6 @@
71
71
  "batchRestoreProgress": "Wiederherstellung {{ type }}...",
72
72
  "batchTagProgress": "Taggen von {{ count }} {{ type }}...",
73
73
  "batchUntagProgress": "Entfernen der Markierung von {{ count }} {{ type }}...",
74
- "batchRestoreProgress": "Wiederherstellung {{ type }}...",
75
74
  "boxFieldAriaLabelAll": "Wert - alle Seiten",
76
75
  "boxFieldAriaLabelIndividual": "- {{ side }} Wert",
77
76
  "boxFieldEditAll": "Alle Werte bearbeiten",
@@ -325,7 +324,6 @@
325
324
  "localizeLocalized": "Lokalisierte",
326
325
  "localizeNewRelated": "Übersetze alle neuen zugehörige Dokumente",
327
326
  "localizeNotYetLocalized": "Noch nicht lokalisiert",
328
- "localized": "{{ type }} {{ title }} auf {{ locale }} übersetzt",
329
327
  "localizedBatch": "Verarbeitet {{ count }} {{ type }}",
330
328
  "localizedBatchFailed": "Lokalisierung von {{ type }} fehlgeschlagen",
331
329
  "localizedBatchWithFailures": "Verarbeitet {{ count }} {{ type }} ({{ bad }} von {{ total }} fehlgeschlagen)",
@@ -287,6 +287,7 @@
287
287
  "insertTable": "Insert Table",
288
288
  "invalid": "invalid",
289
289
  "lastEdited": "Last Edited",
290
+ "lastEditor": "Last Editor",
290
291
  "lastUpdatedBy": "Last saved on {{ updatedAt }} <br /> by {{ updatedBy }}",
291
292
  "layout": "Layout",
292
293
  "layoutAlign": "Vertical alignment",
@@ -318,6 +319,7 @@
318
319
  "listJoiner": ", ",
319
320
  "live": "Live",
320
321
  "loadDocFailed": "The requested document was not found.",
322
+ "loadingMore": "Loading more...",
321
323
  "locale": "Locale",
322
324
  "localeSwitcherDiscardChangesAffirmative": "Save as draft and switch",
323
325
  "localeSwitcherDiscardChangesNegative": "Discard changes and switch",
@@ -326,10 +328,12 @@
326
328
  "localize": "Localize...",
327
329
  "localizeAllRelated": "Localize all related documents and overwrite existing documents",
328
330
  "localizeContent": "Localize Content",
331
+ "localizeEditDocument": "Edit document",
329
332
  "localizeLocalized": "Localized",
333
+ "localizeManageDocuments": "Manage documents",
330
334
  "localizeNewRelated": "Localize new related documents",
331
335
  "localizeNotYetLocalized": "Not Yet Localized",
332
- "localized": "Localized the {{ type }} {{ title }} to {{ locale }}",
336
+ "localizeOpenDocument": "View document",
333
337
  "localizedBatch": "Processed {{ count }} {{ type }}",
334
338
  "localizedBatchFailed": "Localizing {{ type }} failed",
335
339
  "localizedBatchWithFailures": "Processed {{ count }} {{ type }} ({{ bad }} of {{ total }} failed)",
@@ -444,6 +448,7 @@
444
448
  "pageNumber": "Page {{ number }}",
445
449
  "pageTitle": "Page Title",
446
450
  "pages": "Pages",
451
+ "pieces": "Pieces",
447
452
  "parentNotLocalized": "Localize the parent page first",
448
453
  "password": "Password",
449
454
  "passwordChangeHelp": "Modify your existing password",
@@ -476,6 +481,29 @@
476
481
  "rawHtml": "Raw HTML",
477
482
  "rawHtmlCode": "Raw HTML (Code)",
478
483
  "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.",
484
+ "recentlyEdited": "Recently edited",
485
+ "recentlyEditedAction": "Action",
486
+ "recentlyEditedResetFilters": "Reset filters",
487
+ "recentlyEditedActionCreated": "Created",
488
+ "recentlyEditedActionLocalized": "Localized",
489
+ "recentlyEditedActionPublished": "Published",
490
+ "recentlyEditedActionSubmitted": "Submitted",
491
+ "recentlyEditedCurrentUser": "Me ({{ user }})",
492
+ "recentlyEditedDocuments": "Recently edited documents",
493
+ "recentlyEditedEditedBy": "Edited by",
494
+ "recentlyEditedClearAllFilters": "Clear all filters",
495
+ "recentlyEditedClearSearch": "Clear search",
496
+ "recentlyEditedEmpty": "No recently edited documents found in the past {{ count }} day",
497
+ "recentlyEditedEmpty_plural": "No recently edited documents found in the past {{ count }} days",
498
+ "recentlyEditedEmptyFiltered": "No documents match your current filters",
499
+ "recentlyEditedEmptyFilteredHint": "Try adjusting or clearing your filters to see more results",
500
+ "recentlyEditedEmptySearched": "No documents match your search",
501
+ "recentlyEditedEmptySearchedHint": "Try different search terms or adjust your filters",
502
+ "recentlyEditedFilters": "Filters",
503
+ "recentlyEditedSearchDocuments": "Search documents",
504
+ "recentlyEditedAddFilter": "Add filter value",
505
+ "recentlyEditedStatus": "Status",
506
+ "recentlyEditedStatusSubmitted": "Submitted",
479
507
  "redo": "Redo",
480
508
  "redoFailed": "The operation could not be redone.",
481
509
  "redoTooltip": "Redo Change",
@@ -600,14 +628,25 @@
600
628
  "splitCell": "Split Cell",
601
629
  "style": "Style",
602
630
  "styleAlignment": "Alignment",
631
+ "styleBackground": "Background",
632
+ "styleBackgroundColor": "Color",
633
+ "styleBackgroundGradient": "Gradient",
634
+ "styleBackgroundImage": "Image",
635
+ "styleBackgroundOverlay": "Overlay",
636
+ "styleBackgroundType": "Background Type",
603
637
  "styleBorder": "Border",
604
638
  "styleBorderWidth": "Border Width",
605
639
  "styleCenter": "Center",
606
640
  "styleColor": "Border Color",
607
641
  "styleDashed": "Dashed",
608
642
  "styleDotted": "Dotted",
643
+ "styleGradientAngle": "Angle",
644
+ "styleGradientEnd": "End Color",
645
+ "styleGradientStart": "Start Color",
609
646
  "styleLeft": "Left",
610
647
  "styleMargin": "Margin",
648
+ "styleOverlayColor": "Overlay Color",
649
+ "styleOverlayOpacity": "Overlay Opacity",
611
650
  "stylePadding": "Padding",
612
651
  "styleRadius": "Radius",
613
652
  "styleRight": "Right",
@@ -324,7 +324,6 @@
324
324
  "localizeLocalized": "Localizado",
325
325
  "localizeNewRelated": "Traducir nuevos documentos relacionados",
326
326
  "localizeNotYetLocalized": "No localizado aún",
327
- "localized": "El {{ type }} {{ title }} ha sido traducido a la configuración regional {{ locale }}",
328
327
  "localizedBatch": "Procesado {{ count }} {{ type }}",
329
328
  "localizedBatchFailed": "Error al localizar {{ type }}",
330
329
  "localizedBatchWithFailures": "Procesado {{ count }} {{ type }} ({{ bad }} de {{ total }} fallaron)",
@@ -324,7 +324,6 @@
324
324
  "localizeLocalized": "Localisé",
325
325
  "localizeNewRelated": "Changer la langue des nouveaux documents associés",
326
326
  "localizeNotYetLocalized": "Pas encore localisé",
327
- "localized": "{{ type }} {{ title }} changé en {{ locale }}",
328
327
  "localizedBatch": "Traité {{ count }} {{ type }}",
329
328
  "localizedBatchFailed": "La localisation de {{ type }} a échoué.",
330
329
  "localizedBatchWithFailures": "Traité {{ count }} {{ type }} ({{ bad }} sur {{ total }} ont échoué).",
@@ -324,7 +324,6 @@
324
324
  "localizeLocalized": "Localizzato",
325
325
  "localizeNewRelated": "Localizza nuovi documenti correlati",
326
326
  "localizeNotYetLocalized": "Non ancora localizzato",
327
- "localized": "Localizzato il {{ type }} {{ title }} in {{ locale }}",
328
327
  "localizedBatch": "Elaborato {{ count }} {{ type }}",
329
328
  "localizedBatchFailed": "Localizzazione di {{ type }} fallita",
330
329
  "localizedBatchWithFailures": "Elaborato {{ count }} {{ type }} ({{ bad }} di {{ total }} fallito)",
@@ -324,7 +324,6 @@
324
324
  "localizeLocalized": "Localizado",
325
325
  "localizeNewRelated": "Localizar novos documentos relacionados",
326
326
  "localizeNotYetLocalized": "Ainda Não Localizado",
327
- "localized": "Localizar o(a) {{ type }} {{ title }} para {{ locale }}",
328
327
  "localizedBatch": "Processado {{ count }} {{ type }}",
329
328
  "localizedBatchFailed": "Falha ao localizar {{ type }}",
330
329
  "localizedBatchWithFailures": "Processado {{ count }} {{ type }} ({{ bad }} de {{ total }} falharam)",
@@ -324,7 +324,6 @@
324
324
  "localizeLocalized": "Lokalizované",
325
325
  "localizeNewRelated": "Preložte nové súvisiace dokumenty",
326
326
  "localizeNotYetLocalized": "Ešte nie je lokalizované",
327
- "localized": "Preložené {{ type }} {{ title }} do {{ locale }}",
328
327
  "localizedBatch": "Spracované {{ count }} {{ type }}",
329
328
  "localizedBatchFailed": "Lokalizácia {{ type }} zlyhala",
330
329
  "localizedBatchWithFailures": "Spracované {{ count }} {{ type }} ({{ bad }} z {{ total }} zlyhalo)",
@@ -16,11 +16,28 @@ export default () => {
16
16
 
17
17
  if (!skipCount) {
18
18
  if (count) {
19
+ const targetLocales = [ ...new Set(
20
+ log.filter(isSuccess).map(entry => entry.locale)
21
+ ) ];
22
+ const managerOpen = apos.modal.get()
23
+ .some(modal => modal.componentName === 'AposRecentlyEditedManager');
19
24
  await apos.notify('apostrophe:localizingNotificationSuccess', {
20
25
  type: 'success',
21
26
  icon: 'translate-icon',
22
27
  interpolate: { count },
23
- dismiss: true
28
+ dismiss: managerOpen,
29
+ ...!managerOpen && {
30
+ buttons: [
31
+ {
32
+ type: 'event',
33
+ label: 'apostrophe:localizeManageDocuments',
34
+ name: 'localize-manage-documents',
35
+ data: {
36
+ locales: targetLocales
37
+ }
38
+ }
39
+ ]
40
+ }
24
41
  });
25
42
  }
26
43
  return;
@@ -0,0 +1,50 @@
1
+ export default () => {
2
+ let once = false;
3
+
4
+ apos.util.onReady(() => {
5
+ if (once) {
6
+ return;
7
+ }
8
+ once = true;
9
+
10
+ apos.bus.$on('localize-open-document', openDocument);
11
+ apos.bus.$on('localize-manage-documents', manageDocuments);
12
+ });
13
+
14
+ function openDocument(data) {
15
+ if (!data) {
16
+ return;
17
+ }
18
+ if (data._url) {
19
+ window.open(data._url, '_blank');
20
+ return;
21
+ }
22
+ const docModuleName = data.slug?.startsWith('/')
23
+ ? '@apostrophecms/page'
24
+ : data.type;
25
+ if (!apos.modules[docModuleName]) {
26
+ return;
27
+ }
28
+ const editorComponent = apos.modules[docModuleName]?.components?.editorModal || 'AposDocEditor';
29
+ apos.modal.execute(editorComponent, {
30
+ moduleName: docModuleName,
31
+ docId: data._id,
32
+ locale: data.locale
33
+ });
34
+ }
35
+
36
+ function manageDocuments(data) {
37
+ if (!data) {
38
+ return;
39
+ }
40
+ apos.bus.$emit('admin-menu-click', {
41
+ itemName: '@apostrophecms/recently-edited:manager',
42
+ props: {
43
+ initialFilters: {
44
+ _action: [ 'localized' ],
45
+ _locale: data.locales || []
46
+ }
47
+ }
48
+ });
49
+ }
50
+ };