apostrophe 3.7.0 → 3.8.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 (39) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +34 -3
  4. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +5 -1
  5. package/modules/@apostrophecms/asset/index.js +77 -13
  6. package/modules/@apostrophecms/attachment/index.js +1 -0
  7. package/modules/@apostrophecms/db/index.js +5 -6
  8. package/modules/@apostrophecms/doc-type/index.js +23 -3
  9. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  10. package/modules/@apostrophecms/i18n/i18n/en.json +15 -4
  11. package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
  12. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
  13. package/modules/@apostrophecms/i18n/i18n/sk.json +3 -4
  14. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  15. package/modules/@apostrophecms/image-widget/index.js +2 -1
  16. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  17. package/modules/@apostrophecms/job/index.js +164 -212
  18. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  19. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
  20. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  21. package/modules/@apostrophecms/notification/index.js +116 -8
  22. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  23. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  24. package/modules/@apostrophecms/page/index.js +37 -30
  25. package/modules/@apostrophecms/piece-type/index.js +178 -61
  26. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -50
  27. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +0 -2
  28. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  29. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  30. package/modules/@apostrophecms/task/index.js +2 -2
  31. package/modules/@apostrophecms/template/index.js +2 -0
  32. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -0
  33. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  34. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  35. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  36. package/package.json +2 -2
  37. package/test/job.js +224 -0
  38. package/test/pieces.js +17 -0
  39. package/test-lib/util.js +32 -0
package/.eslintrc CHANGED
@@ -10,6 +10,10 @@
10
10
  "no-var": "error",
11
11
  "no-console": 0,
12
12
  "multiline-ternary": "off",
13
+ "no-unused-vars": [
14
+ "error",
15
+ { "varsIgnorePattern": "^_.", "args": "none" }
16
+ ],
13
17
  "vue/no-deprecated-destroyed-lifecycle": 0,
14
18
  "vue/v-on-event-hyphenation": 1,
15
19
  "vue/custom-event-name-casing": ["warn", "kebab-case"],
package/.scratch.md ADDED
@@ -0,0 +1,2 @@
1
+ - `/deep/ .apos-button` to `:deep(.apos-button)`
2
+ - Remove v-popover, v-tooltip, VueClickOutsideElement
package/CHANGELOG.md CHANGED
@@ -1,11 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.8.0 - 2021-11-15
4
+
5
+ ### Adds
6
+
7
+ * Checkboxes for pieces are back, a main checkbox allows to select all page items. When all pieces on a page are checked, a banner where the user can select all pieces appears. A launder for mongo projections has been added.
8
+ * Registered `batchOperations` on a piece-type will now become buttons in the manager batch operations "more menu" (styled as a kebab icon). Batch operations should include a label, `messages` object, and `modalOptions` for the confirmation modal.
9
+ * `batchOperations` can be grouped into a single button with a menu using the `group` cascade subproperty.
10
+ * `batchOperations` can be conditional with an `if` conditional object. This allows developers to pass a single value or an array of values.
11
+ * Piece types can have `utilityOperations` configured as a top-level cascade property. These operations are made available in the piece manager as new buttons.
12
+ * Notifications may now include an `event` property, which the AposNotification component will emit on mount. The `event` property should be set to an object with `name` (the event name) and optionally `data` (data included with the event emission).
13
+ * Adds support for using the attachments query builder in REST API calls via the query string.
14
+ * Adds contextual menu for pieces, any module extending the piece-type one can add actions in this contextual menu.
15
+ * When clicking on a batch operation, it opens a confirmation modal using modal options from the batch operation, it also works for operations in grouped ones. operations name property has been renamed in action to work with AposContextMenu component.
16
+ * Beginning with this release, a module-specific static asset in your project such as `modules/mymodulename/public/images/bg.png` can always be referenced in your `.scss` and `.css` files as `/modules/mymodulename/images/bg.png`, even if assets are actually being deployed to S3, CDNs, etc. Note that `public` and `ui/public` module subdirectories have separate functions. See the documentation for more information.
17
+ * Adds AposFile.vue component to abstract file dropzone UI, uses it in AposInputAttachment, and uses it in the confirmation modal for pieces import.
18
+ * Optionally add `dimensionAttrs` option to image widget, which sets width & height attributes to optimize for Cumulative Layout Shift. Thank you to [Qiao Lin](https://github.com/qclin) for the contribution.
19
+
20
+ ### Fixes
21
+
22
+ * The `apos.util.attachmentUrl` method now works correctly. To facilitate that, `apos.uploadsUrl` is now populated browser-side at all times as the frontend logic originally expected. For backwards compatibility `apos.attachment.uploadsUrl` is still populated when logged in.
23
+ * Widget players are now prevented from being played twice by the implementing vue component.
24
+
25
+ ### Changes
26
+ * Removes Apostrophe 2 documentation and UI configuration from the `@apostrophecms/job` module. These options were not yet in use for A3.
27
+ * Renames methods and removes unsupported routes in the `@apostrophecms/job` module that were not yet in use. This was not done lightly, but specifically because of the minimal likelihood that they were in use in project code given the lack of UI support.
28
+ * The deprecated `cancel` route was removed and will likely be replaced at a later date.
29
+ * `run` was renamed `runBatch` as its purpose is specifically to run processes on a "batch selected" array of pieces or pages.
30
+ * `runNonBatch` was renamed to `run` as it is the more generic job-running method. It is likely that `runBatch` will eventually be refactored to use this method.
31
+ * The `good` and `bad` methods are renamed `success` and `failure`, respectively. The expected methods used in the `run` method were similarly renamed. They still increment job document properties called `good` and `bad`.
32
+ * Comments out the unused `batchSimpleRoute` methods in the page and piece-type modules to avoid usage before they are fully implemented.
33
+ * Optionally add `dimensionAttrs` option to image widget, which sets width & height attributes to optimize for Cumulative Layout Shift.
34
+ * Temporarily removes `npm audit` from our automated tests because of a sub-dependency of uploadfs that doesn't actually cause a security vulnerability for apostrophe.
35
+
3
36
  ## 3.7.0 - 2021-10-28
4
37
 
5
38
  ### Adds
6
39
 
7
40
  * Schema select field choices can now be populated by a server side function, like an API call. Set the `choices` property to a method name of the calling module. That function should take a single argument of `req`, and return an array of objects with `label` and `value` properties. The function can be async and will be awaited.
8
-
9
41
  * Apostrophe now has built-in support for the Node.js cluster module. If the `APOS_CLUSTER_PROCESSES` environment variable is set to a number, that number of child processes are forked, sharing the same listening port. If the variable is set to `0`, one process is forked for each CPU core, with a minimum of `2` to provide availability during restarts. If the variable is set to a negative number, that number is added to the number of CPU cores, e.g. `-1` is a good way to reserve one core for MongoDB if it is running on the same server. This is for production use only (`NODE_ENV=production`). If a child process fails it is restarted automatically.
10
42
 
11
43
  ### Fixes
@@ -187,8 +219,7 @@ No changes. Publishing to correctly mark the latest 3.x release as "latest" in n
187
219
  can include the widget on the clipboard, a special Clipboard widget will appear in area's Add UI. This works across pages as well.
188
220
 
189
221
  ### Changes
190
- * Apostrophe's Global's UI (the @apostrophecms/global singleton has moved from the admin bar's content controls to
191
- the admin utility tray under a cog icon.
222
+ * Apostrophe's Global's UI (the @apostrophecms/global singleton has moved from the admin bar's content controls to the admin utility tray under a cog icon.
192
223
  * The context bar's document Edit button, which was a cog icon, has been rolled into the doc's context menu.
193
224
 
194
225
  ## 3.1.3 - 2021-07-16
@@ -463,7 +463,11 @@ export default {
463
463
  }
464
464
  },
465
465
  async onContentChanged(e) {
466
- if (e.doc && (e.doc._id === this.context._id)) {
466
+
467
+ if (
468
+ (e.doc && (e.doc._id === this.context._id)) ||
469
+ (e.docIds && e.docIds.includes(this.context._id))
470
+ ) {
467
471
  if (e.action === 'delete') {
468
472
  if (!this.contextStack.length) {
469
473
  // With the current page gone, we need to move to safe ground
@@ -73,6 +73,12 @@ module.exports = {
73
73
  await fs.remove(buildDir);
74
74
  await fs.mkdirp(buildDir);
75
75
 
76
+ // Static asset files in `public` subdirs of each module are copied
77
+ // to the same relative path `/public/apos-frontend/namespace/modules/modulename`.
78
+ // Inherited files are also copied, with the deepest subclass overriding in the
79
+ // event of a conflict
80
+ await moduleOverrides(`${bundleDir}/modules`, 'public');
81
+
76
82
  for (const [ name, options ] of Object.entries(self.builds)) {
77
83
  // If the option is not present always rebuild everything
78
84
  let rebuild = argv && !argv['check-apos-build'];
@@ -122,12 +128,17 @@ module.exports = {
122
128
  // just `public` and `apos`) by examining those specified as
123
129
  // targets for the various builds
124
130
  const scenes = [ ...new Set(Object.values(self.builds).map(options => options.scenes).flat()) ];
125
- let bundles = [];
131
+ let deployFiles = [];
126
132
  for (const scene of scenes) {
127
- bundles = [ ...bundles, ...merge(scene) ];
133
+ deployFiles = [ ...deployFiles, ...merge(scene) ];
128
134
  }
129
-
130
- await deploy(bundles);
135
+ // enumerate public assets and include them in deployment if appropriate
136
+ const publicAssets = glob.sync('modules/**/*', {
137
+ cwd: bundleDir,
138
+ mark: true
139
+ }).filter(match => !match.endsWith('/'));
140
+ deployFiles = [ ...deployFiles, ...publicAssets ];
141
+ await deploy(deployFiles);
131
142
 
132
143
  if (process.env.APOS_BUNDLE_ANALYZER) {
133
144
  return new Promise((resolve, reject) => {
@@ -162,7 +173,7 @@ module.exports = {
162
173
  for (const name of names) {
163
174
  const moduleDir = `${modulesDir}/${name}`;
164
175
  for (const dir of directories[name]) {
165
- const srcDir = `${dir}/ui/${source}`;
176
+ const srcDir = `${dir}/${source}`;
166
177
  if (fs.existsSync(srcDir)) {
167
178
  await fs.copy(srcDir, moduleDir);
168
179
  }
@@ -176,7 +187,7 @@ module.exports = {
176
187
  }));
177
188
  const modulesDir = `${buildDir}/${name}/modules`;
178
189
  const source = options.source || name;
179
- await moduleOverrides(modulesDir, source);
190
+ await moduleOverrides(modulesDir, `ui/${source}`);
180
191
 
181
192
  let iconImports, componentImports, tiptapExtensionImports, appImports, indexJsImports, indexSassImports;
182
193
  if (options.apos) {
@@ -233,7 +244,8 @@ module.exports = {
233
244
  // Remove previous build artifacts, as some pipelines won't build all artifacts
234
245
  // if there is no input, and we don't want stale output in the bundle
235
246
  fs.removeSync(`${bundleDir}/${outputFilename}`);
236
- fs.removeSync(`${bundleDir}/${outputFilename}`.replace(/\.js$/, '.css'));
247
+ const cssPath = `${bundleDir}/${outputFilename}`.replace(/\.js$/, '.css');
248
+ fs.removeSync(cssPath);
237
249
  await Promise.promisify(webpackModule)(require(`./lib/webpack/${name}/webpack.config`)(
238
250
  {
239
251
  importFile,
@@ -243,6 +255,11 @@ module.exports = {
243
255
  },
244
256
  self.apos
245
257
  ));
258
+ if (fs.existsSync(cssPath)) {
259
+ fs.writeFileSync(cssPath, self.filterCss(fs.readFileSync(cssPath, 'utf8'), {
260
+ modulesPrefix: `${self.getAssetBaseUrl()}/modules`
261
+ }));
262
+ }
246
263
  if (options.apos) {
247
264
  const now = Date.now().toString();
248
265
  fs.writeFileSync(`${bundleDir}/${name}-build-timestamp.txt`, now);
@@ -269,7 +286,9 @@ module.exports = {
269
286
  const publicImports = getImports(name, '*.css', { });
270
287
  fs.writeFileSync(`${bundleDir}/${name}-build.css`,
271
288
  publicImports.paths.map(path => {
272
- return fs.readFileSync(path, 'utf8');
289
+ return self.filterCss(fs.readFileSync(path, 'utf8'), {
290
+ modulesPrefix: `${self.getAssetBaseUrl()}/modules`
291
+ });
273
292
  }).join('\n')
274
293
  );
275
294
  }
@@ -358,7 +377,19 @@ module.exports = {
358
377
  return [ jsModules, jsNoModules, css ];
359
378
  }
360
379
 
361
- async function deploy(bundles) {
380
+ // If NODE_ENV is production, this function will copy the given
381
+ // array of asset files from `${bundleDir}/${file}` to
382
+ // the same relative location in the appropriate release subdirectory in
383
+ // `/public/apos-frontend/releases`, or in `/apos-frontend/releases` in
384
+ // uploadfs if `APOS_UPLOADFS_ASSETS` is present.
385
+ //
386
+ // If NODE_ENV is not production this function does nothing and
387
+ // the assets are served directly from `/public/apos-frontend/${file}`.
388
+ //
389
+ // The namespace (e.g. default) should be part of each filename given.
390
+ // A leading slash should NOT be passed.
391
+
392
+ async function deploy(files) {
362
393
  if (process.env.NODE_ENV !== 'production') {
363
394
  return;
364
395
  }
@@ -373,17 +404,23 @@ module.exports = {
373
404
  } else {
374
405
  // The right choice with Docker if uploadfs is just the local filesystem
375
406
  // mapped to a volume (a Docker build step can't access that)
376
- copyIn = fs.copyFile;
407
+ copyIn = fsCopyIn;
377
408
  releaseDir = `${self.apos.rootDir}/public/apos-frontend/releases/${releaseId}/${namespace}`;
378
409
  await fs.mkdirp(releaseDir);
379
410
  }
380
- for (const bundle of bundles) {
381
- const src = `${bundleDir}/${bundle}`;
382
- await copyIn(src, `${releaseDir}/${bundle}`);
411
+ for (const file of files) {
412
+ const src = `${bundleDir}/${file}`;
413
+ await copyIn(src, `${releaseDir}/${file}`);
383
414
  await fs.remove(src);
384
415
  }
385
416
  }
386
417
 
418
+ async function fsCopyIn(from, to) {
419
+ const base = path.dirname(to);
420
+ await fs.mkdirp(base);
421
+ return fs.copyFile(from, to);
422
+ }
423
+
387
424
  function getImports(folder, pattern, options) {
388
425
  let components = [];
389
426
  const seen = {};
@@ -650,6 +687,30 @@ module.exports = {
650
687
  if (!self.options.es5) {
651
688
  delete self.builds['src-es5'];
652
689
  }
690
+ },
691
+ // Filter the given css performing any necessary transformations,
692
+ // such as support for the /modules path regardless of where
693
+ // static assets are actually deployed
694
+ filterCss(css, { modulesPrefix }) {
695
+ return self.filterCssUrls(css, url => {
696
+ if (url.startsWith('/modules')) {
697
+ return url.replace('/modules', modulesPrefix);
698
+ }
699
+ return url;
700
+ });
701
+ },
702
+ // Run all URLs in CSS through a filter function
703
+ filterCssUrls(css, filter) {
704
+ css = css.replace(/url\(([^'"].*?)\)/g, function(s, url) {
705
+ return 'url(' + filter(url) + ')';
706
+ });
707
+ css = css.replace(/url\("([^"]+?)"\)/g, function(s, url) {
708
+ return 'url("' + filter(url) + '")';
709
+ });
710
+ css = css.replace(/url\('([^']+?)'\)/g, function(s, url) {
711
+ return 'url(\'' + filter(url) + '\')';
712
+ });
713
+ return css;
653
714
  }
654
715
  };
655
716
  },
@@ -667,6 +728,9 @@ module.exports = {
667
728
  }
668
729
  const script = fs.readFileSync(path.join(__dirname, '/lib/refresh-on-restart.js'), 'utf8');
669
730
  return self.apos.template.safe(`<script data-apos-refresh-on-restart="${self.action}/restart-id">\n${script}</script>`);
731
+ },
732
+ url(path) {
733
+ return `${self.getAssetBaseUrl()}${path}`;
670
734
  }
671
735
  };
672
736
  },
@@ -1038,6 +1038,7 @@ module.exports = {
1038
1038
  action: self.action,
1039
1039
  fileGroups: self.fileGroups,
1040
1040
  name: self.name,
1041
+ // for bc
1041
1042
  uploadsUrl: self.uploadfs.getUrl(),
1042
1043
  croppable: self.croppable,
1043
1044
  sized: self.sized
@@ -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
@@ -738,7 +738,7 @@ module.exports = {
738
738
  },
739
739
  // Localize (export) the given draft to another locale, creating the document in the
740
740
  // 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.
741
+ // other locale, it is not overwritten. Use the `update: true` option to change that.
742
742
  // You can localize starting from either draft or published content. Either way what
743
743
  // gets created or updated in the other locale is a draft.
744
744
  async localize(req, draft, toLocale, options = { update: false }) {
@@ -1217,6 +1217,21 @@ module.exports = {
1217
1217
  // cursors.
1218
1218
 
1219
1219
  project: {
1220
+ launder (p) {
1221
+ // check that project is an object
1222
+ if (!p || typeof p !== 'object' || Array.isArray(p)) {
1223
+ return {};
1224
+ }
1225
+
1226
+ const projection = Object.entries(p).reduce((acc, [ key, val ]) => {
1227
+ return {
1228
+ ...acc,
1229
+ [key]: self.apos.launder.boolean(val)
1230
+ };
1231
+ }, {});
1232
+
1233
+ return projection;
1234
+ },
1220
1235
  finalize() {
1221
1236
  let projection = query.get('project') || {};
1222
1237
  // Keys beginning with `_` are computed values
@@ -1442,9 +1457,14 @@ module.exports = {
1442
1457
  attachments: {
1443
1458
  def: false,
1444
1459
  after(results) {
1445
- for (const doc of results) {
1446
- self.apos.attachment.all(doc, { annotate: true });
1460
+ const attachments = query.get('attachments');
1461
+
1462
+ if (attachments) {
1463
+ self.apos.attachment.all(results, { annotate: true });
1447
1464
  }
1465
+ },
1466
+ launder(b) {
1467
+ return self.apos.launder.boolean(b);
1448
1468
  }
1449
1469
  },
1450
1470
 
@@ -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) {
@@ -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.",
@@ -204,6 +207,7 @@
204
207
  "notFoundPageStatusCode": "404",
205
208
  "notFoundPageTitle": "404 - Page not found",
206
209
  "notInLocale": "The current page doesn’t exist in {{ label }}. Localize the version from {{ currentLocale }}?",
210
+ "notificationClearEventError": "There was an error clearing a registered notification event.",
207
211
  "noTypeFound": "No {{ type }} Found",
208
212
  "parentNotLocalized": "Localize the parent page first",
209
213
  "notYetPublished": "This document hasn't been published yet.",
@@ -255,12 +259,12 @@
255
259
  "provideButtonLabel": "Provide a Button Label",
256
260
  "previousPage": "Previous Page",
257
261
  "public": "Public",
258
- "modernBuild": "public-facing modern JavaScript and Sass",
259
- "ie11Build": "public-facing modern JavaScript and Sass (IE11 build)",
262
+ "modernBuild": "Public-facing modern JavaScript and Sass",
263
+ "ie11Build": "Public-facing modern JavaScript and Sass (IE11 build)",
260
264
  "publish": "Publish",
261
265
  "publishBeforeUsingTooltip": "Publish this content before using it in a relationship",
262
266
  "published": "Published",
263
- "rawCssAndJs": "raw CSS and JS",
267
+ "rawCssAndJs": "Raw CSS and JS",
264
268
  "rawHtml": "Raw HTML",
265
269
  "rawHtmlCode": "Raw HTML (Code)",
266
270
  "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 +283,8 @@
279
283
  "relatedDocsOnly": "Related documents only",
280
284
  "relatedDocsDefinition": "Related documents are documents referenced by this document. This typically includes images, content defined by relationships, etc.",
281
285
  "restore": "Restore",
286
+ "restoreBatchConfirmation": "Are you sure you want to restore {{ count }} {{ type }}?",
287
+ "restoreBatchConfirmationButton": "Yes, restore content.",
282
288
  "resolveErrorsBeforeSaving": "Resolve errors before saving.",
283
289
  "resolveErrorsFirst": "Resolve errors first.",
284
290
  "restoreOnlyThisPage": "Restore only this page",
@@ -307,6 +313,12 @@
307
313
  "select": "Select",
308
314
  "selectedMenuItem": "✓ {{ label }}",
309
315
  "selectAll": "Select All",
316
+ "selectBoxMessage": "{{ num }} {{ label }} selected.",
317
+ "selectBoxMessagePage": "{{ num }} {{ label }} on this page selected.",
318
+ "selectBoxMessageAllButton": "Select all {{ num }} {{ label }}.",
319
+ "selectBoxMessageButton": "Select {{ num }} {{ label }}.",
320
+ "selectBoxMessageSelected": "{{ num }} {{ label }} selected.",
321
+ "selectBoxMessageAllSelected": "All {{ num }} {{ label }} selected.",
310
322
  "deselectAll": "Deselect All",
311
323
  "selectContent": "Select Content",
312
324
  "selectContentToLocalize": "What content do you want to localize?",
@@ -375,7 +387,6 @@
375
387
  "videoUrlHelp": "Enter the URL for a media source you wish to embed (e.g., YouTube, Vimeo, or other hosted video URL).",
376
388
  "view": "View",
377
389
  "visibility": "Visibility",
378
- "visibilityLabel": "Who can view this?",
379
390
  "willMoveImageToArchive": "This will move the image to the archive.",
380
391
  "yes": "Yes",
381
392
  "yesLocalizeAndSwitchLocales": "Yes, localize this page and switch locales",
@@ -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",
@@ -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 %}