apostrophe 3.5.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +96 -3
  4. package/README.md +1 -1
  5. package/index.js +108 -3
  6. package/lib/moog-require.js +23 -0
  7. package/lib/moog.js +1 -0
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
  9. package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
  10. package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
  11. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +2 -2
  12. package/modules/@apostrophecms/asset/index.js +77 -13
  13. package/modules/@apostrophecms/attachment/index.js +1 -0
  14. package/modules/@apostrophecms/db/index.js +5 -6
  15. package/modules/@apostrophecms/doc/index.js +2 -0
  16. package/modules/@apostrophecms/doc-type/index.js +39 -16
  17. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  18. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
  19. package/modules/@apostrophecms/i18n/i18n/en.json +19 -6
  20. package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
  21. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
  22. package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
  23. package/modules/@apostrophecms/i18n/index.js +10 -1
  24. package/modules/@apostrophecms/image/index.js +2 -1
  25. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  26. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -1
  27. package/modules/@apostrophecms/image-widget/index.js +2 -1
  28. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  29. package/modules/@apostrophecms/job/index.js +164 -212
  30. package/modules/@apostrophecms/login/index.js +1 -16
  31. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +5 -0
  32. package/modules/@apostrophecms/migration/index.js +1 -1
  33. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  34. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
  35. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
  36. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  37. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
  38. package/modules/@apostrophecms/notification/index.js +116 -8
  39. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  40. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  41. package/modules/@apostrophecms/page/index.js +37 -30
  42. package/modules/@apostrophecms/permission/index.js +1 -1
  43. package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
  44. package/modules/@apostrophecms/piece-type/index.js +178 -61
  45. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
  46. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
  47. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  48. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
  49. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +1 -3
  50. package/modules/@apostrophecms/schema/index.js +97 -20
  51. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
  52. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
  53. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  54. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
  55. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
  56. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
  57. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
  58. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
  59. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
  60. package/modules/@apostrophecms/task/index.js +2 -2
  61. package/modules/@apostrophecms/template/index.js +63 -36
  62. package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
  63. package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
  64. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -2
  65. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
  66. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  67. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
  68. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
  69. package/modules/@apostrophecms/util/index.js +2 -2
  70. package/modules/@apostrophecms/util/ui/src/http.js +12 -8
  71. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  72. package/modules/@apostrophecms/widget-type/index.js +1 -1
  73. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
  74. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  75. package/package.json +3 -3
  76. package/test/extra_node_modules/improve-global/index.js +7 -0
  77. package/test/extra_node_modules/improve-piece-type/index.js +7 -0
  78. package/test/improve-overrides.js +30 -0
  79. package/test/job.js +224 -0
  80. package/test/modules/@apostrophecms/global/index.js +8 -0
  81. package/test/modules/fragment-all/views/aux-test.html +7 -0
  82. package/test/modules/fragment-all/views/fragment.html +5 -0
  83. package/test/package.json +5 -4
  84. package/test/pieces.js +34 -0
  85. package/test/reverse-relationship.js +170 -0
  86. package/test/templates.js +7 -1
  87. package/test-lib/test.js +23 -12
  88. package/test-lib/util.js +33 -0
@@ -219,8 +219,8 @@ module.exports = {
219
219
  };
220
220
  addCloneMethod(req);
221
221
  req.res.__ = req.__;
222
- const { role, ..._properties } = options || {};
223
- Object.assign(req, _properties);
222
+ const { _role, ...properties } = options || {};
223
+ Object.assign(req, properties);
224
224
  self.apos.i18n.setPrefixUrls(req);
225
225
  return req;
226
226
 
@@ -61,11 +61,14 @@ module.exports = {
61
61
  prefix: self.apos.prefix
62
62
  };
63
63
 
64
+ self.envs = {};
65
+
64
66
  self.filters = {};
65
67
 
66
68
  self.nunjucks = self.options.language || require('nunjucks');
67
69
 
68
70
  self.insertions = {};
71
+
69
72
  },
70
73
  handlers(self) {
71
74
  return {
@@ -248,6 +251,34 @@ module.exports = {
248
251
 
249
252
  let result;
250
253
 
254
+ const args = self.getRenderArgs(req, data, module);
255
+
256
+ const env = self.getEnv(req, module);
257
+
258
+ if (type === 'file') {
259
+ let finalName = s;
260
+ if (!finalName.match(/\.\w+$/)) {
261
+ finalName += '.html';
262
+ }
263
+ result = await Promise.promisify(function (finalName, args, callback) {
264
+ return env.getTemplate(finalName).render(args, callback);
265
+ })(finalName, args);
266
+ } else if (type === 'string') {
267
+ result = await Promise.promisify(function (s, args, callback) {
268
+ return env.renderString(s, args, callback);
269
+ })(s, args);
270
+ } else {
271
+ throw new Error('renderBody does not support the type ' + type);
272
+ }
273
+ return result;
274
+ },
275
+
276
+ // Implementation detail of `renderBody` responsible for
277
+ // creating the input object passed to Nunjucks for rendering,
278
+ // with `data` merged into the `.data` property,
279
+ // `apos` available separately, `__req` available separately, etc.
280
+
281
+ getRenderArgs(req, data, module) {
251
282
  const merged = {};
252
283
 
253
284
  if (data) {
@@ -278,8 +309,6 @@ module.exports = {
278
309
 
279
310
  args.data.locale = args.data.locale || req.locale;
280
311
 
281
- const env = self.getEnv(req, module);
282
-
283
312
  args.apos = self.templateApos;
284
313
  args.__t = req.t;
285
314
  args.__ = key => {
@@ -289,39 +318,39 @@ module.exports = {
289
318
  `);
290
319
  return key;
291
320
  };
292
- if (type === 'file') {
293
- let finalName = s;
294
- if (!finalName.match(/\.\w+$/)) {
295
- finalName += '.html';
321
+ args.__req = req;
322
+ args.getOption = (key, def) => {
323
+ const colonAt = key.indexOf(':');
324
+ let optionModule = self.apos.modules[module.__meta.name];
325
+ if (colonAt !== -1) {
326
+ const name = key.substring(0, colonAt);
327
+ key = key.substring(colonAt + 1);
328
+ optionModule = self.apos.modules[name];
296
329
  }
297
- result = await Promise.promisify(function (finalName, args, callback) {
298
- return env.getTemplate(finalName).render(args, callback);
299
- })(finalName, args);
300
- } else if (type === 'string') {
301
- result = await Promise.promisify(function (s, args, callback) {
302
- return env.renderString(s, args, callback);
303
- })(s, args);
304
- } else {
305
- throw new Error('renderBody does not support the type ' + type);
306
- }
307
- return result;
330
+ return optionModule.getOption(req, key, def);
331
+ };
332
+ return args;
308
333
  },
309
334
 
310
335
  // Fetch a nunjucks environment in which `include`, `extends`, etc. search
311
336
  // the views directories of the specified module and its ancestors.
312
337
  // Typically you will call `self.render` or `self.partial` on your module
313
338
  // object rather than calling this directly.
339
+ //
340
+ // `req` is effectively here for bc purposes only. This method
341
+ // does NOT always pass `req` to `newEnv` for every new release, as
342
+ // `req` is separately supplied to each request to fix a memory leak
343
+ // that occurs when Nunjucks environments are created for every request.
314
344
 
315
345
  getEnv(req, module) {
316
346
  const name = module.__meta.name;
317
-
318
- req.envs = req.envs || {};
319
- // Cache for performance
320
- if (_.has(req.envs, name)) {
321
- return req.envs[name];
347
+ if (!_.has(self.envs, name)) {
348
+ // Pass the original req for bc purposes only,
349
+ // note that due to the reuse of envs there is
350
+ // no guarantee newEnv will be called for every req
351
+ self.envs[name] = self.newEnv(req, name, self.getViewFolders(module));
322
352
  }
323
- req.envs[name] = self.newEnv(req, name, self.getViewFolders(module));
324
- return req.envs[name];
353
+ return self.envs[name];
325
354
  },
326
355
 
327
356
  getViewFolders(module) {
@@ -343,7 +372,14 @@ module.exports = {
343
372
  // specified directories are searched for includes,
344
373
  // etc. Don't call this directly, use:
345
374
  //
346
- // apos.template.getEnv(module)
375
+ // apos.template.getEnv(req, module)
376
+ //
377
+ // `req` is effectively here for bc purposes only. Apostrophe
378
+ // does NOT always pass `req` to `newEnv` for every new release, as
379
+ // `req` is separately supplied to each request to fix a memory leak
380
+ // that occurs when Nunjucks environments are created for every request.
381
+ // If you must access `req` in a custom Nunjucks tag use
382
+ // `context.ctx.__req`, NOT `env.opts.req` which is no longer provided.
347
383
 
348
384
  newEnv(req, moduleName, dirs) {
349
385
 
@@ -351,22 +387,11 @@ module.exports = {
351
387
 
352
388
  const env = new self.nunjucks.Environment(loader, {
353
389
  autoescape: true,
354
- req,
355
390
  module: self.apos.modules[moduleName]
356
391
  });
357
392
 
358
393
  env.addGlobal('apos', self.templateApos);
359
394
  env.addGlobal('module', self.templateApos.modules[moduleName]);
360
- env.addGlobal('getOption', function(key, def) {
361
- const colonAt = key.indexOf(':');
362
- let optionModule = self.apos.modules[moduleName];
363
- if (colonAt !== -1) {
364
- const name = key.substring(0, colonAt);
365
- key = key.substring(colonAt + 1);
366
- optionModule = self.apos.modules[name];
367
- }
368
- return optionModule.getOption(req, key, def);
369
- });
370
395
 
371
396
  self.addStandardFilters(env);
372
397
 
@@ -627,6 +652,8 @@ module.exports = {
627
652
  locale: req.locale,
628
653
  csrfCookieName: self.apos.csrfCookieName,
629
654
  tabId: self.apos.util.generateId(),
655
+ uploadsUrl: self.apos.attachment.uploadfs.getUrl(),
656
+ assetBaseUrl: self.apos.asset.getAssetBaseUrl(),
630
657
  scene
631
658
  };
632
659
  if (req.user) {
@@ -23,7 +23,7 @@ module.exports = function(self) {
23
23
  },
24
24
  // Do the actual work
25
25
  async run(context, name, data) {
26
- const req = context.env.opts.req;
26
+ const req = context.ctx.__req;
27
27
  if (!data) {
28
28
  data = {};
29
29
  }
@@ -98,9 +98,8 @@ module.exports = (self) => {
98
98
  const source = info.body();
99
99
  const input = createRenderInput(info);
100
100
 
101
- const req = context.env.opts.req;
101
+ const req = context.ctx.__req;
102
102
  const env = self.getEnv(req, context.env.opts.module);
103
- input.apos = self.templateApos;
104
103
 
105
104
  // attach the render caller as a function
106
105
  // it's just a string, but we keep
@@ -108,6 +107,11 @@ module.exports = (self) => {
108
107
  input.rendercaller = rendercaller;
109
108
 
110
109
  const result = await require('util').promisify((s, args, callback) => {
110
+ args = {
111
+ ...self.getRenderArgs(req, {}, context.env.opts.module),
112
+ // Parameters to fragments are top level, they are not in `data`
113
+ ...args
114
+ };
111
115
  return env.renderString(s, args, callback);
112
116
  })(source, input);
113
117
  return result;
@@ -15,6 +15,7 @@
15
15
  :type="buttonType"
16
16
  :role="role"
17
17
  :id="attrs.id ? attrs.id : id"
18
+ :style="{color: textColor}"
18
19
  v-bind="attrs"
19
20
  >
20
21
  <transition name="fade">
@@ -71,6 +72,10 @@ export default {
71
72
  type: String,
72
73
  default: null
73
74
  },
75
+ textColor: {
76
+ type: String,
77
+ default: null
78
+ },
74
79
  href: {
75
80
  type: [ String, Boolean ],
76
81
  default: false
@@ -402,8 +407,6 @@ export default {
402
407
  box-sizing: border-box;
403
408
  display: block;
404
409
  width: 100%;
405
- height: 47px;
406
- max-width: 400px;
407
410
  }
408
411
 
409
412
  .apos-button--icon-right {
@@ -64,7 +64,7 @@ export default {
64
64
  .apos-table__cell-field--context-menu__content {
65
65
  @include apos-transition();
66
66
  display: inline-block;
67
- opacity: 0;
67
+ opacity: 0.3;
68
68
  &.apos-is-visible {
69
69
  opacity: 1;
70
70
  }
@@ -0,0 +1,205 @@
1
+ <template>
2
+ <div>
3
+ <label
4
+ class="apos-input-wrapper apos-file-dropzone"
5
+ :class="{
6
+ 'apos-file-dropzone--dragover': dragging,
7
+ 'apos-is-disabled': disabled || fileOrAttachment
8
+ }"
9
+ @drop.prevent="uploadFile"
10
+ @dragover="dragHandler"
11
+ @dragleave="dragging = false"
12
+ >
13
+ <p class="apos-file-instructions">
14
+ <template v-if="dragging">
15
+ <cloud-upload-icon :size="38" />
16
+ </template>
17
+ <AposSpinner v-else-if="uploading" />
18
+ <template v-else>
19
+ <paperclip-icon :size="14" class="apos-file-icon" />
20
+ {{ messages.primary }}&nbsp;
21
+ <span class="apos-file-highlight" v-if="messages.highlighted">
22
+ {{ messages.highlighted }}
23
+ </span>
24
+ </template>
25
+ </p>
26
+ <input
27
+ type="file"
28
+ class="apos-sr-only"
29
+ :disabled="disabled || fileOrAttachment"
30
+ @input="uploadFile"
31
+ :accept="allowedExtensions"
32
+ >
33
+ </label>
34
+ <div v-if="fileOrAttachment" class="apos-file-files">
35
+ <AposSlatList
36
+ :value="[fileOrAttachment]"
37
+ @input="update"
38
+ :disabled="readOnly"
39
+ />
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script>
45
+ export default {
46
+ props: {
47
+ uploading: {
48
+ type: Boolean,
49
+ default: false
50
+ },
51
+ disabled: {
52
+ type: Boolean,
53
+ default: false
54
+ },
55
+ attachment: {
56
+ type: Object,
57
+ default: null
58
+ },
59
+ allowedExtensions: {
60
+ type: String,
61
+ default: '*'
62
+ },
63
+ readOnly: {
64
+ type: Boolean,
65
+ default: false
66
+ },
67
+ def: {
68
+ type: String,
69
+ default: null
70
+ }
71
+ },
72
+ emits: [ 'upload-file', 'update' ],
73
+ data () {
74
+ return {
75
+ selectedFile: null,
76
+ dragging: false
77
+ };
78
+ },
79
+ computed: {
80
+ fileOrAttachment () {
81
+ return this.selectedFile || this.attachment;
82
+ },
83
+ messages () {
84
+ const msgs = {
85
+ primary: 'Drop a file here or',
86
+ highlighted: 'click to open the file explorer'
87
+ };
88
+ if (this.disabled) {
89
+ msgs.primary = 'Field is disabled';
90
+ msgs.highlighted = '';
91
+ }
92
+ if (this.fileOrAttachment) {
93
+ msgs.primary = 'Attachment limit reached';
94
+ msgs.highlighted = '';
95
+ }
96
+ return msgs;
97
+ }
98
+ },
99
+ methods: {
100
+ async uploadFile ({ target, dataTransfer }) {
101
+ this.dragging = false;
102
+ const [ file ] = target.files ? target.files : (dataTransfer.files || []);
103
+
104
+ const extension = file.name.split('.').pop();
105
+ const allowedFile = await this.checkFileGroup(`.${extension}`);
106
+
107
+ if (!allowedFile) {
108
+ return;
109
+ }
110
+
111
+ this.selectedFile = {
112
+ _id: file.name,
113
+ title: file.name,
114
+ extension,
115
+ _url: URL.createObjectURL(file)
116
+ };
117
+
118
+ this.$emit('upload-file', file);
119
+ },
120
+ dragHandler (event) {
121
+ event.preventDefault();
122
+
123
+ if (!this.disabled && !this.dragging) {
124
+ this.dragging = true;
125
+ }
126
+ },
127
+ update(items) {
128
+ if (this.selectedFile && this.selectedFile._url) {
129
+ URL.revokeObjectURL(this.selectedFile._url);
130
+ }
131
+
132
+ this.selectedFile = null;
133
+ this.$emit('update', items);
134
+ },
135
+ async checkFileGroup(fileExt) {
136
+ const allowedExt = this.allowedExtensions.split(',');
137
+ const allowed = allowedExt.includes(fileExt);
138
+
139
+ if (!allowed) {
140
+ await apos.notify('apostrophe:fileTypeNotAccepted', {
141
+ type: 'warning',
142
+ icon: 'alert-circle-icon',
143
+ interpolate: {
144
+ extensions: this.allowedExtensions
145
+ }
146
+ });
147
+ }
148
+
149
+ return allowed;
150
+ }
151
+ }
152
+ };
153
+ </script>
154
+ <style scoped lang='scss'>
155
+ .apos-file-dropzone {
156
+ @include apos-button-reset();
157
+ @include type-base;
158
+ display: block;
159
+ margin: 10px 0;
160
+ padding: 20px;
161
+ border: 2px dashed var(--a-base-8);
162
+ border-radius: var(--a-border-radius);
163
+ transition: all 0.2s ease;
164
+ &:hover {
165
+ border-color: var(--a-primary);
166
+ background-color: var(--a-base-10);
167
+ }
168
+ &:active,
169
+ &:focus {
170
+ border: 2px solid var(--a-primary);
171
+ }
172
+ &.apos-is-disabled {
173
+ color: var(--a-base-4);
174
+ background-color: var(--a-base-7);
175
+ border-color: var(--a-base-4);
176
+
177
+ &:hover {
178
+ cursor: not-allowed;
179
+ }
180
+ }
181
+ }
182
+
183
+ .apos-file-dropzone--dragover {
184
+ border: 2px dashed var(--a-primary);
185
+ background-color: var(--a-base-10);
186
+ }
187
+
188
+ .apos-file-instructions {
189
+ display: flex;
190
+ flex-wrap: wrap;
191
+ align-items: center;
192
+ justify-content: center;
193
+ pointer-events: none;
194
+ // v-html goofiness
195
+ & ::v-deep .apos-file-highlight {
196
+ color: var(--a-primary);
197
+ font-weight: var(--a-weight-bold);
198
+ }
199
+ }
200
+
201
+ .apos-file-icon {
202
+ transform: rotate(45deg);
203
+ margin-right: 5px;
204
+ }
205
+ </style>
@@ -10,11 +10,20 @@ export default {
10
10
  install(Vue, options) {
11
11
  const i18n = options.i18n;
12
12
 
13
+ const fallbackLng = [ i18n.defaultLocale ];
14
+ // In case the default locale also has inadequate admin UI phrases
15
+ if (fallbackLng[0] !== 'en') {
16
+ fallbackLng.push('en');
17
+ }
18
+
13
19
  i18next.init({
14
20
  lng: i18n.locale,
15
- fallbackLng: i18n.defaultLocale,
21
+ fallbackLng,
16
22
  resources: {},
17
- debug: i18n.debug
23
+ debug: i18n.debug,
24
+ interpolation: {
25
+ escapeValue: false
26
+ }
18
27
  });
19
28
 
20
29
  for (const [ ns, phrases ] of Object.entries(i18n.i18n[i18n.locale])) {
@@ -25,6 +34,11 @@ export default {
25
34
  i18next.addResourceBundle(i18n.defaultLocale, ns, phrases, true, true);
26
35
  }
27
36
  }
37
+ if ((i18n.locale !== 'en') && (i18n.defaultLocale !== 'en')) {
38
+ for (const [ ns, phrases ] of Object.entries(i18n.i18n.en)) {
39
+ i18next.addResourceBundle('en', ns, phrases, true, true);
40
+ }
41
+ }
28
42
 
29
43
  // Like standard i18next $t, but also with support
30
44
  // for just one object argument with at least a `key`
@@ -5,7 +5,7 @@
5
5
 
6
6
  .apos-table__header {
7
7
  margin-bottom: $spacing-base;
8
- padding: 12.5px 4.5px;
8
+ padding: 12.5px 15px;
9
9
  border-bottom: 1px solid var(--a-base-8);
10
10
  color: var(--a-base-3);
11
11
  text-align: left;
@@ -52,12 +52,13 @@ span.apos-table__header-label:hover {
52
52
  @include apos-transition(all, 0.05s);
53
53
  }
54
54
  .apos-table__cell {
55
- padding: 5px;
55
+ padding: 5px 15px;
56
56
  border-bottom: 1px solid var(--a-base-10);
57
57
  }
58
58
 
59
59
  .apos-table__cell--context-menu {
60
- width: 40px;
60
+ padding-right: 0;
61
+ padding-left: 0;
61
62
  }
62
63
 
63
64
  .apos-table__cell-field--context-menu {
@@ -414,7 +414,7 @@ module.exports = {
414
414
  return self.insensitiveSortCompare(a[property], b[property]);
415
415
  });
416
416
  },
417
- // Copmpare two strings in a case-insensitive way, returning -1, 0 or 1, suitable for use with sort().
417
+ // Compare two strings in a case-insensitive way, returning -1, 0 or 1, suitable for use with sort().
418
418
  // If the two strings represent numbers, compare them as numbers for a natural sort order
419
419
  // when comparing strings like '4' and '10'.
420
420
  insensitiveSortCompare(a, b) {
@@ -858,7 +858,7 @@ module.exports = {
858
858
  return _.startCase(o);
859
859
  },
860
860
 
861
- // check if something is a function (as opposd to property)
861
+ // check if something is a function (as opposed to property)
862
862
  isFunction: function(o) {
863
863
  return (typeof o === 'function');
864
864
  },
@@ -150,10 +150,12 @@ export default () => {
150
150
  if (options.busy) {
151
151
  if (!busyActive[busyName]) {
152
152
  busyActive[busyName] = 0;
153
- apos.bus.$emit('busy', {
154
- active: true,
155
- name: busyName
156
- });
153
+ if (apos.bus) {
154
+ apos.bus.$emit('busy', {
155
+ active: true,
156
+ name: busyName
157
+ });
158
+ }
157
159
  }
158
160
  // keep track of nested calls
159
161
  busyActive[busyName]++;
@@ -240,10 +242,12 @@ export default () => {
240
242
  busyActive[busyName]--;
241
243
  if (!busyActive[busyName]) {
242
244
  // if no nested calls, disable the "busy" state
243
- apos.bus.$emit('busy', {
244
- active: false,
245
- name: busyName
246
- });
245
+ if (apos.bus) {
246
+ apos.bus.$emit('busy', {
247
+ active: false,
248
+ name: busyName
249
+ });
250
+ }
247
251
  }
248
252
  }
249
253
  });
@@ -257,6 +257,21 @@ export default () => {
257
257
  return path + '.' + file.extension;
258
258
  };
259
259
 
260
+ // Given an asset path such as `/modules/modulename/images/file.png`, this
261
+ // method will return a URL for it. This is used when frontend JavaScript
262
+ // code needs to access static assets shipped in the `public` subdirectory of
263
+ // individual modules. Currently `path` must begin with `/modules/` followed
264
+ // by a module name; other namespaces may exist later. The remainder of the
265
+ // path, such as `/images/file.png` in the above example, must currespond
266
+ // to a file that exists in the `public` subdirectory of the named module.
267
+ //
268
+ // Asset paths of this type are also automatically supported by CSS and
269
+ // SCSS files in the project when using `url()`.
270
+
271
+ apos.util.assetUrl = function(path) {
272
+ return apos.assetBaseUrl + path;
273
+ };
274
+
260
275
  // Returns true if the uri references the same site (same host and port) as the
261
276
  // current page. Cross-browser implementation, valid at least back to IE11.
262
277
  // Regarding port numbers, this will match as long as the URIs are consistent
@@ -156,7 +156,7 @@ module.exports = {
156
156
  self.schema = self.apos.schema.compose({
157
157
  addFields: self.apos.schema.fieldsToArray(`Module ${self.__meta.name}`, self.fields),
158
158
  arrangeFields: self.apos.schema.groupsToArray(self.fieldsGroups)
159
- });
159
+ }, self);
160
160
  const forbiddenFields = [
161
161
  '_id',
162
162
  'type'
@@ -19,6 +19,7 @@
19
19
  :schema="schema"
20
20
  :value="docFields"
21
21
  @input="updateDocFields"
22
+ @validate="triggerValidate"
22
23
  :following-values="followingValues()"
23
24
  :conditional-fields="conditionalFields()"
24
25
  ref="schema"
@@ -23,7 +23,8 @@ export default {
23
23
  data() {
24
24
  return {
25
25
  rendered: '...',
26
- playerOpts: null
26
+ playerOpts: null,
27
+ playerEl: null
27
28
  };
28
29
  },
29
30
  mounted() {
@@ -40,6 +41,7 @@ export default {
40
41
  },
41
42
  methods: {
42
43
  async renderContent() {
44
+ const self = this;
43
45
  const parameters = {
44
46
  _docId: this.docId,
45
47
  widget: this.value,
@@ -61,6 +63,7 @@ export default {
61
63
  // AposAreas manager can spot any new area divs.
62
64
  // This will also run the player
63
65
  setTimeout(function() {
66
+ self.setPlayerEl();
64
67
  apos.bus.$emit('widget-rendered');
65
68
  }, 0);
66
69
  } catch (e) {
@@ -68,13 +71,18 @@ export default {
68
71
  console.error('Unable to render widget. Possibly the schema has been changed and the existing widget does not pass validation.', e);
69
72
  }
70
73
  },
71
- runPlayer() {
72
- if (!this.playerOpts) {
73
- return;
74
+ setPlayerEl() {
75
+ if (this.playerOpts) {
76
+ const el = this.$el.querySelector(this.playerOpts.selector);
77
+ if (el && this.playerOpts.player) {
78
+ this.playerEl = el;
79
+ }
74
80
  }
75
- const el = this.$el.querySelector(this.playerOpts.selector);
76
- if (el && this.playerOpts.player) {
77
- this.playerOpts.player(el);
81
+ },
82
+ runPlayer() {
83
+ if (this.playerEl && !this.playerEl.aposWidgetPlayed) {
84
+ this.playerOpts.player(this.playerEl);
85
+ this.playerEl.aposWidgetPlayed = true;
78
86
  }
79
87
  },
80
88
  clicked(e) {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "3.5.0",
3
+ "version": "3.8.1",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "pretest": "npm run lint && npm audit",
7
+ "pretest": "npm run lint",
8
8
  "test": "nyc --reporter=html mocha -t 10000",
9
9
  "lint": "eslint . && node scripts/lint-i18n"
10
10
  },
@@ -127,7 +127,7 @@
127
127
  "eslint-loader": "^4.0.2",
128
128
  "eslint-plugin-node": "^11.1.0",
129
129
  "eslint-plugin-vue": "^7.9.0",
130
- "mocha": "^7.1.2",
130
+ "mocha": "^9.1.2",
131
131
  "nyc": "^15.1.0",
132
132
  "replace-in-file": "^6.1.0",
133
133
  "vue-eslint-parser": "^7.1.1",