apostrophe 3.22.0 → 3.23.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 (29) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/modules/@apostrophecms/doc-type/index.js +101 -10
  3. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +11 -7
  4. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +8 -109
  5. package/modules/@apostrophecms/i18n/i18n/en.json +7 -1
  6. package/modules/@apostrophecms/i18n/i18n/es.json +7 -1
  7. package/modules/@apostrophecms/i18n/i18n/fr.json +7 -1
  8. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +7 -1
  9. package/modules/@apostrophecms/i18n/i18n/sk.json +7 -1
  10. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +3 -1
  11. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +37 -2
  12. package/modules/@apostrophecms/login/index.js +10 -0
  13. package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +323 -0
  14. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocErrorsMixin.js +79 -0
  15. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +2 -1
  16. package/modules/@apostrophecms/modal/ui/apos/mixins/AposModalTabsMixin.js +58 -0
  17. package/modules/@apostrophecms/module/index.js +11 -0
  18. package/modules/@apostrophecms/page/index.js +39 -5
  19. package/modules/@apostrophecms/permission/index.js +26 -1
  20. package/modules/@apostrophecms/piece-type/index.js +22 -0
  21. package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +53 -10
  22. package/modules/@apostrophecms/soft-redirect/index.js +10 -1
  23. package/modules/@apostrophecms/template/index.js +0 -1
  24. package/package.json +9 -9
  25. package/test/docs.js +4 -4
  26. package/test/modules/test-page/views/page.html +10 -5
  27. package/test/pages.js +146 -0
  28. package/test/pieces.js +150 -0
  29. package/test-lib/util.js +3 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.23.0 (2022-06-22)
4
+
5
+ ### Adds
6
+
7
+ * Shared Drafts: gives the possibility to share a link which can be used to preview the draft version of page, or a piece `show` page.
8
+ * Add `Localize` option to `@apostrophecms/image`. In Edit mode the context bar menu includes a "Localize" option to start cloning this image into other locales.
9
+
10
+ ### Fixes
11
+
12
+ * Update `sass` to [`1.52.3`+](https://github.com/sass/dart-sass/pull/1713) to prevent the error `RangeError: Invalid value: Not in inclusive range 0..145: -1`. You can now fix that by upgrading with `npm update`. If it does not immediately clear up the issue in development, try `node app @apostrophecms/asset:clear-cache`.
13
+ * Fix a potential issue when URLs have a query string, in the `'@apostrophecms/page:notFound'` handler of the `soft-redirect` module.
14
+
15
+ ## 3.22.1 (2022-06-17)
16
+
17
+ * Hotfix: temporarily pin versions of tiptap modules to work around packaging error that breaks import of the most recent releases. We will unpin as soon as this is fixed upstream. Fixes a bug where `npm update` would fail for A3 projects.
18
+
3
19
  ## 3.22.0 (2022-06-08)
4
20
 
5
21
  ### Adds
@@ -30,6 +46,7 @@
30
46
  * Trigger only the relevant build when in a watch mode (development). The build paths should not contain comma (`,`).
31
47
  * Adds an `unpublish` method, available for any doc-type.
32
48
  An _Unpublish_ option has also been added to the context menu of the modal when editing a piece or a page.
49
+ * Allows developers to group fields in relationships the same way it's done for normal schemas.
33
50
 
34
51
  ### Fixes
35
52
 
@@ -91,6 +91,7 @@ module.exports = {
91
91
  self.composeSchema();
92
92
  self.apos.doc.setManager(self.name, self);
93
93
  self.enableBrowserData();
94
+ self.addContextMenu();
94
95
  },
95
96
  handlers(self) {
96
97
  return {
@@ -281,6 +282,16 @@ module.exports = {
281
282
 
282
283
  methods(self) {
283
284
  return {
285
+ addContextMenu() {
286
+ self.apos.doc.addContextOperation(self.__meta.name, {
287
+ action: 'shareDraft',
288
+ context: 'update',
289
+ label: 'apostrophe:shareDraft',
290
+ modal: 'AposModalShareDraft',
291
+ manuallyPublished: true,
292
+ hasUrl: true
293
+ });
294
+ },
284
295
  getRelatedDocsIds(req, doc) {
285
296
  const relatedDocsIds = [];
286
297
  const handlers = {
@@ -1199,6 +1210,54 @@ module.exports = {
1199
1210
  delete $set[name];
1200
1211
  }
1201
1212
  }
1213
+ },
1214
+
1215
+ async share(req, doc) {
1216
+ if (doc._edit !== true) {
1217
+ throw self.apos.error('notfound');
1218
+ }
1219
+
1220
+ if (!doc._url) {
1221
+ return doc;
1222
+ }
1223
+
1224
+ const { aposShareKey: _aposShareKey, ...draft } = doc;
1225
+ const aposShareKey = doc.aposShareKey || self.apos.util.generateId();
1226
+
1227
+ await self.apos.doc.db.updateOne({
1228
+ _id: doc._id
1229
+ }, {
1230
+ $set: {
1231
+ aposShareKey
1232
+ }
1233
+ });
1234
+
1235
+ return {
1236
+ ...draft,
1237
+ aposShareKey
1238
+ };
1239
+ },
1240
+
1241
+ async unshare(req, doc) {
1242
+ if (doc._edit !== true) {
1243
+ throw self.apos.error('notfound');
1244
+ }
1245
+
1246
+ if (!doc._url) {
1247
+ return doc;
1248
+ }
1249
+
1250
+ const { aposShareKey: _aposShareKey, ...draft } = doc;
1251
+
1252
+ await self.apos.doc.db.updateOne({
1253
+ _id: doc._id
1254
+ }, {
1255
+ $unset: {
1256
+ aposShareKey: 1
1257
+ }
1258
+ });
1259
+
1260
+ return draft;
1202
1261
  }
1203
1262
  };
1204
1263
  },
@@ -1232,6 +1291,28 @@ module.exports = {
1232
1291
  queries(self, query) {
1233
1292
  return {
1234
1293
  builders: {
1294
+ transformDraftForSharing: {
1295
+ after(results) {
1296
+ if (!self.isShareDraftRequest(query.req)) {
1297
+ return;
1298
+ }
1299
+
1300
+ const { aposShareId, aposShareKey } = query.req.query;
1301
+
1302
+ // Change drafts values to make it pass for a published document
1303
+ results.forEach(transformDraftToPublished);
1304
+
1305
+ function transformDraftToPublished (result) {
1306
+ if (result._id === aposShareId && result.aposShareKey === aposShareKey) {
1307
+ const changeToPublished = string => string.replace(':draft', ':published');
1308
+
1309
+ result._id = changeToPublished(result._id);
1310
+ result.aposLocale = changeToPublished(result.aposLocale);
1311
+ result.aposMode = 'published';
1312
+ }
1313
+ }
1314
+ }
1315
+ },
1235
1316
  // `.criteria({...})` Sets the MongoDB criteria, discarding
1236
1317
  // criteria previously added using this
1237
1318
  // method or the `and` method. For this reason,
@@ -2061,16 +2142,26 @@ module.exports = {
2061
2142
  queryLocale = `${query.req.locale}:${query.req.mode}`;
2062
2143
  }
2063
2144
  if (queryLocale) {
2064
- query.and({
2065
- $or: [
2066
- {
2067
- aposLocale: queryLocale
2068
- },
2069
- {
2070
- aposLocale: null
2071
- }
2072
- ]
2073
- });
2145
+ const $or = [
2146
+ {
2147
+ aposLocale: queryLocale
2148
+ },
2149
+ {
2150
+ aposLocale: null
2151
+ }
2152
+ ];
2153
+
2154
+ if (self.isShareDraftRequest(query.req)) {
2155
+ const { aposShareId, aposShareKey } = query.req.query;
2156
+
2157
+ $or.push({
2158
+ _id: aposShareId,
2159
+ aposShareKey,
2160
+ aposLocale: queryLocale.replace(':published', ':draft')
2161
+ });
2162
+ }
2163
+
2164
+ query.and({ $or });
2074
2165
  }
2075
2166
  }
2076
2167
  }
@@ -125,7 +125,7 @@ export default {
125
125
  action: 'edit'
126
126
  }
127
127
  ] : []),
128
- ...((this.showPreview && this.context._url) ? [
128
+ ...((this.showPreview && this.hasUrl) ? [
129
129
  {
130
130
  label: 'apostrophe:preview',
131
131
  action: 'preview'
@@ -192,13 +192,14 @@ export default {
192
192
  },
193
193
  customOperationsByContext() {
194
194
  return this.customOperations.filter(op => {
195
- if (op.context === 'update' && this.isUpdateOperation) {
196
- if (typeof op.manuallyPublished === 'boolean') {
197
- return op.manuallyPublished === this.manuallyPublished;
198
- }
199
- return true;
195
+ if (typeof op.manuallyPublished === 'boolean' && op.manuallyPublished !== this.manuallyPublished) {
196
+ return false;
200
197
  }
201
- return false;
198
+ if (typeof op.hasUrl === 'boolean' && op.hasUrl !== this.hasUrl) {
199
+ return false;
200
+ }
201
+
202
+ return op.context === 'update' && this.isUpdateOperation;
202
203
  });
203
204
  },
204
205
  moduleName() {
@@ -214,6 +215,9 @@ export default {
214
215
  isUpdateOperation() {
215
216
  return !!this.context._id;
216
217
  },
218
+ hasUrl() {
219
+ return !!this.context._url;
220
+ },
217
221
  canPublish() {
218
222
  if (this.context._id) {
219
223
  return this.context._publish;
@@ -26,14 +26,14 @@
26
26
  type="primary" :label="saveLabel"
27
27
  :disabled="saveDisabled"
28
28
  @click="onRestore"
29
- :tooltip="tooltip"
29
+ :tooltip="errorTooltip"
30
30
  />
31
31
  <AposButtonSplit
32
32
  v-else-if="saveMenu"
33
33
  :menu="saveMenu"
34
34
  menu-label="Select Save Method"
35
35
  :disabled="saveDisabled"
36
- :tooltip="tooltip"
36
+ :tooltip="errorTooltip"
37
37
  :selected="savePreference"
38
38
  @click="saveHandler($event)"
39
39
  />
@@ -42,7 +42,7 @@
42
42
  <AposModalRail>
43
43
  <AposModalTabs
44
44
  :key="tabKey"
45
- v-if="tabs.length > 0"
45
+ v-if="tabs.length"
46
46
  :current="currentTab"
47
47
  :tabs="tabs"
48
48
  :errors="fieldErrors"
@@ -105,15 +105,15 @@
105
105
  </template>
106
106
 
107
107
  <script>
108
+ import { klona } from 'klona';
108
109
  import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
109
110
  import AposModalTabsMixin from 'Modules/@apostrophecms/modal/mixins/AposModalTabsMixin';
110
111
  import AposEditorMixin from 'Modules/@apostrophecms/modal/mixins/AposEditorMixin';
111
112
  import AposPublishMixin from 'Modules/@apostrophecms/ui/mixins/AposPublishMixin';
112
113
  import AposArchiveMixin from 'Modules/@apostrophecms/ui/mixins/AposArchiveMixin';
113
114
  import AposAdvisoryLockMixin from 'Modules/@apostrophecms/ui/mixins/AposAdvisoryLockMixin';
115
+ import AposDocErrorsMixin from 'Modules/@apostrophecms/modal/mixins/AposDocErrorsMixin';
114
116
  import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
115
- import { klona } from 'klona';
116
- import cuid from 'cuid';
117
117
 
118
118
  export default {
119
119
  name: 'AposDocEditor',
@@ -123,7 +123,8 @@ export default {
123
123
  AposEditorMixin,
124
124
  AposPublishMixin,
125
125
  AposAdvisoryLockMixin,
126
- AposArchiveMixin
126
+ AposArchiveMixin,
127
+ AposDocErrorsMixin
127
128
  ],
128
129
  provide () {
129
130
  return {
@@ -147,7 +148,6 @@ export default {
147
148
  emits: [ 'modal-result', 'safe-close' ],
148
149
  data() {
149
150
  return {
150
- tabKey: cuid(),
151
151
  docType: this.moduleName,
152
152
  docReady: false,
153
153
  fieldErrors: {},
@@ -162,7 +162,6 @@ export default {
162
162
  ref: null
163
163
  },
164
164
  published: null,
165
- errorCount: 0,
166
165
  restoreOnly: false,
167
166
  saveMenu: null,
168
167
  generation: 0
@@ -172,16 +171,6 @@ export default {
172
171
  getOnePath() {
173
172
  return `${this.moduleAction}/${this.docId}`;
174
173
  },
175
- tooltip() {
176
- let msg;
177
- if (this.errorCount) {
178
- msg = {
179
- key: 'apostrophe:errorCount',
180
- count: this.errorCount
181
- };
182
- }
183
- return msg;
184
- },
185
174
  followingUtils() {
186
175
  return this.followingValues('utility');
187
176
  },
@@ -238,33 +227,6 @@ export default {
238
227
  // `@apostrophecms/page` module action.
239
228
  return (window.apos.modules[this.moduleName] || {}).action;
240
229
  },
241
- groups() {
242
- const groupSet = {};
243
-
244
- this.schema.forEach(field => {
245
- if (!this.filterOutParkedFields([ field.name ]).length) {
246
- return;
247
- }
248
- if (field.group && !groupSet[field.group.name]) {
249
- groupSet[field.group.name] = {
250
- label: field.group.label,
251
- fields: [ field.name ],
252
- schema: [ field ]
253
- };
254
- } else if (field.group) {
255
- groupSet[field.group.name].fields.push(field.name);
256
- groupSet[field.group.name].schema.push(field);
257
- }
258
- });
259
- if (!groupSet.utility) {
260
- groupSet.utility = {
261
- label: 'apostrophe:utility',
262
- fields: [],
263
- schema: []
264
- };
265
- }
266
- return groupSet;
267
- },
268
230
  utilityFields() {
269
231
  let fields = [];
270
232
  if (this.groups.utility && this.groups.utility.fields) {
@@ -272,18 +234,6 @@ export default {
272
234
  }
273
235
  return this.filterOutParkedFields(fields);
274
236
  },
275
- tabs() {
276
- const tabs = [];
277
- for (const key in this.groups) {
278
- if (key !== 'utility') {
279
- tabs.push({
280
- name: key,
281
- label: this.groups[key].label
282
- });
283
- }
284
- };
285
- return tabs;
286
- },
287
237
  modalTitle() {
288
238
  if (this.docId) {
289
239
  return {
@@ -374,13 +324,7 @@ export default {
374
324
  original(newVal) {
375
325
  this.originalDoc.ref = newVal;
376
326
  this.saveMenu = this.computeSaveMenu();
377
- },
378
- tabs() {
379
- if ((!this.currentTab) || (!this.tabs.find(tab => tab.name === this.currentTab))) {
380
- this.currentTab = this.tabs[0] && this.tabs[0].name;
381
- }
382
327
  }
383
-
384
328
  },
385
329
  async mounted() {
386
330
  this.modal.active = true;
@@ -515,51 +459,6 @@ export default {
515
459
  }
516
460
  window.location = this.original._url;
517
461
  },
518
- updateFieldState(fieldState) {
519
- this.tabKey = cuid();
520
- for (const key in this.groups) {
521
- this.groups[key].fields.forEach(field => {
522
- if (fieldState[field]) {
523
- this.fieldErrors[key][field] = fieldState[field].error;
524
- }
525
- });
526
- }
527
- this.updateErrorCount();
528
- },
529
- updateErrorCount() {
530
- let count = 0;
531
- for (const key in this.fieldErrors) {
532
- for (const tabKey in this.fieldErrors[key]) {
533
- if (this.fieldErrors[key][tabKey]) {
534
- count++;
535
- }
536
- }
537
- }
538
- this.errorCount = count;
539
- },
540
- focusNextError() {
541
- let field;
542
- for (const key in this.fieldErrors) {
543
- for (const tabKey in this.fieldErrors[key]) {
544
- if (this.fieldErrors[key][tabKey] && !field) {
545
- field = this.schema.filter(item => {
546
- return item.name === tabKey;
547
- })[0];
548
-
549
- if (field.group.name !== 'utility') {
550
- this.switchPane(field.group.name);
551
- }
552
-
553
- this.getAposSchema(field).scrollFieldIntoView(field.name);
554
- }
555
- }
556
- }
557
- },
558
- prepErrors() {
559
- for (const name in this.groups) {
560
- this.fieldErrors[name] = {};
561
- }
562
- },
563
462
  // Implementing a method expected by the advisory lock mixin
564
463
  lockNotAvailable() {
565
464
  this.modal.showModal = false;
@@ -729,7 +628,7 @@ export default {
729
628
  });
730
629
  },
731
630
  updateDocFields(value) {
732
- this.updateFieldState(value.fieldState);
631
+ this.updateFieldErrors(value.fieldState);
733
632
  this.docFields.data = {
734
633
  ...this.docFields.data,
735
634
  ...value.data
@@ -355,6 +355,12 @@
355
355
  "selectManyLabel": "Select {{ typeLabel }}",
356
356
  "selectLocales": "Select Locales",
357
357
  "sentenceJoiner": " ",
358
+ "shareDraft": "Share Draft",
359
+ "shareDraftCopyLink": "Copy draft link",
360
+ "shareDraftDescription": "Enabling draft sharing will allow anyone with this link to view the current draft",
361
+ "shareDraftEnable": "Enable draft sharing",
362
+ "shareDraftHeader": "Share this page",
363
+ "shareDraftError": "This document cannot be shared at this time",
358
364
  "visibilityHelp": "Select whether this content is public or private",
359
365
  "slug": "Slug",
360
366
  "slugInUse": "Slug already in use",
@@ -368,7 +374,7 @@
368
374
  "switchLocalesAndLocalizePage": "Switch locales and localize page to {{ label }}?",
369
375
  "tags": "Tags",
370
376
  "tagYourImages": "Tag your images to make searching and filtering the media manager easier",
371
- "takeActionAndCreateNew": "{{ saveLabel }} and View",
377
+ "takeActionAndCreateNew": "{{ saveLabel }} and Create New",
372
378
  "takeActionAndView": "{{ saveLabel }} and View",
373
379
  "takeControlFromOther": "{{ who }} is editing that document. Do you want to take control?",
374
380
  "takeControlFromSelf": "You are editing that document in another tab or window. Do you want to take control in this tab?",
@@ -324,6 +324,12 @@
324
324
  "selectManyLabel": "Seleccionar {{ typeLabel }}",
325
325
  "selectLocales": "Seleccionar Configuración Regional",
326
326
  "sentenceJoiner": " ",
327
+ "shareDraft": "Compartir borrador",
328
+ "shareDraftCopyLink": "Copiar borrador de enlace",
329
+ "shareDraftDescription": "Habilitar el uso compartido de borradores permitirá que cualquier persona con este enlace vea el borrador actual",
330
+ "shareDraftEnable": "Habilitar borrador compartido",
331
+ "shareDraftHeader": "Comparte esta página",
332
+ "shareDraftError": "Este documento no se puede compartir en este momento",
327
333
  "visibilityHelp": "Seleccione si este contenido es público o privado",
328
334
  "slug": "Slug",
329
335
  "slugInUse": "Este slug ya está en uso",
@@ -337,7 +343,7 @@
337
343
  "switchLocalesAndLocalizePage": "¿Cambiar de configuracion regional y traducir la página a la configuracion regional {{ label }}?",
338
344
  "tags": "Etiquetas",
339
345
  "tagYourImages": "Etiquete sus imágenes para facilitar la busqueda y filtrado en el gestor multimedia",
340
- "takeActionAndCreateNew": "{{ saveLabel }} y Mirar",
346
+ "takeActionAndCreateNew": "{{ saveLabel }} y Crear Nuevo",
341
347
  "takeActionAndView": "{{ saveLabel }} y Mirar",
342
348
  "takeControlFromOther": "{{ who }} está editando ese documento. ¿Quiere tomar el control?",
343
349
  "takeControlFromSelf": "Está editando ese documento en otra pestaña o ventana. ¿Quiere tomar control en esta pestaña?",
@@ -339,6 +339,12 @@
339
339
  "selectManyLabel": "Sélectionner {{ typeLabel }}",
340
340
  "selectLocales": "Sélectionner les langues",
341
341
  "sentenceJoiner": " ",
342
+ "shareDraft": "Partager le brouillon",
343
+ "shareDraftCopyLink": "Copier le lien du brouillon",
344
+ "shareDraftDescription": "L'activation du partage de brouillon permettra à toute personne disposant de ce lien d'accéder au brouillon actuel",
345
+ "shareDraftEnable": "Activer le partage de brouillon",
346
+ "shareDraftHeader": "Partager cette page",
347
+ "shareDraftError": "Ce document ne peut pas être partagé actuellement",
342
348
  "visibilityHelp": "Choisissez si ce contenu est public ou privé",
343
349
  "slug": "Slug",
344
350
  "slugInUse": "Slug déjà utilisé",
@@ -352,7 +358,7 @@
352
358
  "switchLocalesAndLocalizePage": "Changer la langue et basculer la page vers {{ label }} ?",
353
359
  "tags": "Tags",
354
360
  "tagYourImages": "Taguez vos images pour faciliter la recherche et le filtrage du gestionnaire de médias",
355
- "takeActionAndCreateNew": "{{ saveLabel }} et voir",
361
+ "takeActionAndCreateNew": "{{ saveLabel }} et en créer de nouveau",
356
362
  "takeActionAndView": "{{ saveLabel }} et voir",
357
363
  "takeControlFromOther": "{{ who }} est en train de modifier ce document. Voulez-vous prendre le contrôle ?",
358
364
  "takeControlFromSelf": "Vous modifiez ce document dans un autre onglet ou une autre fenêtre. Voulez-vous prendre le contrôle dans cet onglet ?",
@@ -321,6 +321,12 @@
321
321
  "selectManyLabel": "Selecionar {{ typeLabel }}",
322
322
  "selectLocales": "Selecionar Localidades",
323
323
  "sentenceJoiner": " ",
324
+ "shareDraft": "Compartilhar rascunho",
325
+ "shareDraftCopyLink": "Copiar link de rascunho",
326
+ "shareDraftDescription": "Ativar o compartilhamento de rascunho permitirá que qualquer pessoa com este link visualize o rascunho atual",
327
+ "shareDraftEnable": "Ativar compartilhamento de rascunho",
328
+ "shareDraftHeader": "Compartilhe esta página",
329
+ "shareDraftError": "Este documento não pode ser compartilhado no momento",
324
330
  "visibilityHelp": "Selecione se este conteúdo é público ou privado",
325
331
  "slug": "Slug",
326
332
  "slugInUse": "Este slug já está em uso",
@@ -334,7 +340,7 @@
334
340
  "switchLocalesAndLocalizePage": "Mudar de localidade e localizar página para {{ label }}?",
335
341
  "tags": "Tags",
336
342
  "tagYourImages": "Adicione tags nas suas imagens para facilitar a pesquisa e a filtragem do gerenciador de mídia",
337
- "takeActionAndCreateNew": "{{ saveLabel }} e Vizualizar",
343
+ "takeActionAndCreateNew": "{{ saveLabel }} e Criar Novo",
338
344
  "takeActionAndView": "{{ saveLabel }} e Vizualizar",
339
345
  "takeControlFromOther": "{{ who }} está editando esse documento. Você quer assumir o controle?",
340
346
  "takeControlFromSelf": "Você está editando esse documento em outra guia ou janela. Você deseja assumir o controle nesta guia?",
@@ -344,6 +344,12 @@
344
344
  "selectManyLabel": "Vybrať {{ typeLabel }}",
345
345
  "selectLocales": "Vybrať jazykové mutácie",
346
346
  "sentenceJoiner": " ",
347
+ "shareDraft": "Zdieľať koncept",
348
+ "shareDraftCopyLink": "Kopírovať odkaz na koncept",
349
+ "shareDraftDescription": "Povolenie zdieľania konceptu umožní komukoľvek s týmto odkazom zobraziť aktuálny koncept",
350
+ "shareDraftEnable": "Povoliť zdieľanie konceptov",
351
+ "shareDraftHeader": "Zdieľajte túto stránku",
352
+ "shareDraftError": "Tento dokument momentálne nie je možné zdieľať",
347
353
  "visibilityHelp": "Vyberte, či je tento obsah verejný alebo súkromný",
348
354
  "slug": "Pochopiteľný text URL",
349
355
  "slugInUse": "Pochopiteľný text URL sa už používa",
@@ -357,7 +363,7 @@
357
363
  "switchLocalesAndLocalizePage": "Prepnúť jazyk a preložiť stránku na {{ label }}?",
358
364
  "tags": "Značky",
359
365
  "tagYourImages": "Označte svoje obrázky, aby ste uľahčili vyhľadávanie a filtrovanie médií",
360
- "takeActionAndCreateNew": "{{ saveLabel }} a zobraziť",
366
+ "takeActionAndCreateNew": "{{ saveLabel }} a vytvoriť nové",
361
367
  "takeActionAndView": "{{ saveLabel }} a zobraziť",
362
368
  "takeControlFromOther": "{{ who }} upravuje tento dokument. Chcete prevziať kontrolu?",
363
369
  "takeControlFromSelf": "Tento dokument upravujete na inej karte alebo v inom okne. Chcete na tejto karte prevziať kontrolu?",
@@ -90,7 +90,9 @@
90
90
  >
91
91
  <AposMediaManagerEditor
92
92
  v-show="editing"
93
- :media="editing" :selected="selected"
93
+ :media="editing"
94
+ :selected="selected"
95
+ :is-modified="isModified"
94
96
  :module-labels="moduleLabels"
95
97
  @back="updateEditing(null)" @saved="updateMedia"
96
98
  @modified="editorModified"
@@ -100,6 +100,7 @@
100
100
  <script>
101
101
  import AposEditorMixin from 'Modules/@apostrophecms/modal/mixins/AposEditorMixin';
102
102
  import AposAdvisoryLockMixin from 'Modules/@apostrophecms/ui/mixins/AposAdvisoryLockMixin';
103
+ import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
103
104
  import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
104
105
  import { klona } from 'klona';
105
106
  import dayjs from 'dayjs';
@@ -110,7 +111,7 @@ import cuid from 'cuid';
110
111
  dayjs.extend(advancedFormat);
111
112
 
112
113
  export default {
113
- mixins: [ AposEditorMixin, AposAdvisoryLockMixin ],
114
+ mixins: [ AposEditorMixin, AposAdvisoryLockMixin, AposModifiedMixin ],
114
115
  props: {
115
116
  media: {
116
117
  type: Object,
@@ -124,6 +125,10 @@ export default {
124
125
  return [];
125
126
  }
126
127
  },
128
+ isModified: {
129
+ type: Boolean,
130
+ default: false
131
+ },
127
132
  moduleLabels: {
128
133
  type: Object,
129
134
  default() {
@@ -152,11 +157,20 @@ export default {
152
157
  moduleOptions() {
153
158
  return window.apos.modules[this.activeMedia.type] || {};
154
159
  },
160
+ canLocalize() {
161
+ return (Object.keys(apos.i18n.locales).length > 1) && this.moduleOptions.localized && this.activeMedia._id;
162
+ },
155
163
  moreMenu() {
156
164
  const menu = [ {
157
165
  label: 'apostrophe:discardChanges',
158
166
  action: 'cancel'
159
167
  } ];
168
+ if (this.canLocalize) {
169
+ menu.push({
170
+ label: 'apostrophe:localize',
171
+ action: 'localize'
172
+ });
173
+ }
160
174
  if (this.activeMedia._id && !this.restoreOnly) {
161
175
  menu.push({
162
176
  label: 'apostrophe:archiveImage',
@@ -330,7 +344,7 @@ export default {
330
344
  this.$emit('back');
331
345
  },
332
346
  lockNotAvailable() {
333
- this.isModified = false;
347
+ this.$emit('modified', false);
334
348
  this.cancel();
335
349
  },
336
350
  updateActiveAttachment(attachment) {
@@ -338,6 +352,27 @@ export default {
338
352
  },
339
353
  viewMedia () {
340
354
  window.open(this.activeMedia.attachment._urls.original, '_blank');
355
+ },
356
+ async localize(media) {
357
+ // If there are changes warn the user before discarding them before
358
+ // the localize operation
359
+ if (this.isModified) {
360
+ if (!await this.confirmAndCancel()) {
361
+ return;
362
+ }
363
+
364
+ await this.cancel();
365
+ this.updateActiveDoc(this.activeMedia);
366
+ }
367
+ apos.bus.$emit('admin-menu-click', {
368
+ itemName: '@apostrophecms/i18n:localize',
369
+ props: {
370
+ doc: this.activeMedia
371
+ }
372
+ });
373
+ },
374
+ async close() {
375
+ await this.cancel();
341
376
  }
342
377
  }
343
378
  };
@@ -806,6 +806,16 @@ module.exports = {
806
806
  return (req, res, next) => req.user ? next() : passportSession(req, res, next);
807
807
  })()
808
808
  },
809
+ removeUserForDraftSharing: {
810
+ before: '@apostrophecms/i18n',
811
+ middleware(req, res, next) {
812
+ // Remove user to hide the admin UI, in order to simulate a logged-out page view
813
+ if (self.isShareDraftRequest(req)) {
814
+ delete req.user;
815
+ }
816
+ return next();
817
+ }
818
+ },
809
819
  honorLoginInvalidBefore: {
810
820
  before: '@apostrophecms/i18n',
811
821
  middleware(req, res, next) {