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
@@ -6,44 +6,15 @@
6
6
  >
7
7
  <template #body>
8
8
  <div class="apos-attachment">
9
- <label
10
- class="apos-input-wrapper apos-attachment-dropzone"
11
- :class="{
12
- 'apos-attachment-dropzone--dragover': dragging,
13
- 'apos-is-disabled': disabled || limitReached
14
- }"
15
- @drop.prevent="uploadMedia"
16
- @dragover="dragHandler"
17
- @dragleave="dragging = false"
18
- >
19
- <p class="apos-attachment-instructions">
20
- <template v-if="dragging">
21
- <cloud-upload-icon :size="38" />
22
- </template>
23
- <AposSpinner v-else-if="uploading" />
24
- <template v-else>
25
- <paperclip-icon :size="14" class="apos-attachment-icon" />
26
- {{ messages.primary }}&nbsp;
27
- <span class="apos-attachment-highlight" v-if="messages.highlighted">
28
- {{ messages.highlighted }}
29
- </span>
30
- </template>
31
- </p>
32
- <input
33
- type="file"
34
- class="apos-sr-only"
35
- :disabled="disabled || limitReached"
36
- @input="uploadMedia"
37
- :accept="field.accept"
38
- >
39
- </label>
40
- <div v-if="next && next._id" class="apos-attachment-files">
41
- <AposSlatList
42
- :value="next ? [ next ] : []"
43
- @input="updated"
44
- :disabled="field.readOnly"
45
- />
46
- </div>
9
+ <AposFile
10
+ :allowed-extensions="field.accept"
11
+ :uploading="uploading"
12
+ :disabled="disabled"
13
+ :attachment="next"
14
+ :def="field.def"
15
+ @upload-file="uploadMedia"
16
+ @update="updated"
17
+ />
47
18
  </div>
48
19
  </template>
49
20
  </AposInputWrapper>
@@ -51,13 +22,9 @@
51
22
 
52
23
  <script>
53
24
  import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin.js';
54
- import AposSlatList from 'Modules/@apostrophecms/ui/components/AposSlatList';
55
25
 
56
26
  export default {
57
27
  name: 'AposInputAttachment',
58
- components: {
59
- AposSlatList
60
- },
61
28
  mixins: [ AposInputMixin ],
62
29
  emits: [ 'upload-started', 'upload-complete' ],
63
30
  data () {
@@ -67,46 +34,13 @@ export default {
67
34
  next: (this.value && (typeof this.value.data === 'object'))
68
35
  ? this.value.data : (this.field.def || null),
69
36
  disabled: false,
70
- dragging: false,
71
- uploading: false,
72
- allowedExtensions: [ '*' ]
37
+ uploading: false
73
38
  };
74
39
  },
75
- computed: {
76
- messages () {
77
- const msgs = {
78
- primary: 'Drop a file here or',
79
- highlighted: 'click to open the file explorer'
80
- };
81
- if (this.disabled) {
82
- msgs.primary = 'Field is disabled';
83
- msgs.highlighted = '';
84
- }
85
- if (this.limitReached) {
86
- msgs.primary = 'Attachment limit reached';
87
- msgs.highlighted = '';
88
- }
89
- return msgs;
90
- },
91
- limitReached () {
92
- return !!(this.value.data && this.value.data._id);
93
- }
94
- },
95
40
  async mounted () {
96
41
  this.disabled = this.field.readOnly;
97
-
98
- const groups = apos.modules['@apostrophecms/attachment'].fileGroups;
99
- const groupInfo = groups.find(group => {
100
- return group.name === this.field.fileGroup;
101
- });
102
- if (groupInfo && groupInfo.extensions) {
103
- this.allowedExtensions = groupInfo.extensions;
104
- }
105
42
  },
106
43
  methods: {
107
- watchNext () {
108
- this.validateAndEmit();
109
- },
110
44
  updated (items) {
111
45
  // NOTE: This is limited to a single item.
112
46
  this.next = items.length > 0 ? items[0] : null;
@@ -118,31 +52,12 @@ export default {
118
52
 
119
53
  return false;
120
54
  },
121
- async uploadMedia (event) {
55
+ async uploadMedia (file) {
122
56
  if (!this.disabled || !this.limitReached) {
123
57
  try {
124
- this.dragging = false;
125
58
  this.disabled = true;
126
59
  this.uploading = true;
127
60
 
128
- const file = event.target.files ? event.target.files[0] : event.dataTransfer.files[0];
129
-
130
- if (!this.checkFileGroup(file.name)) {
131
- const joined = this.allowedExtensions.join(this.$t('apostrophe:listJoiner'));
132
- await apos.notify('apostrophe:fileTypeNotAccepted', {
133
- type: 'warning',
134
- icon: 'alert-circle-icon',
135
- interpolate: {
136
- extensions: joined
137
- }
138
- });
139
-
140
- this.disabled = false;
141
- this.uploading = false;
142
-
143
- return;
144
- }
145
-
146
61
  await apos.notify('apostrophe:uploading', {
147
62
  dismiss: true,
148
63
  icon: 'cloud-upload-icon',
@@ -183,71 +98,7 @@ export default {
183
98
  this.uploading = false;
184
99
  }
185
100
  }
186
- },
187
- checkFileGroup(filename) {
188
- const fileExt = filename.split('.').pop();
189
- return this.allowedExtensions[0] === '*' ||
190
- this.allowedExtensions.includes(fileExt);
191
- },
192
- dragHandler (event) {
193
- event.preventDefault();
194
- if (!this.disabled && !this.dragging) {
195
- this.dragging = true;
196
- }
197
101
  }
198
102
  }
199
103
  };
200
104
  </script>
201
-
202
- <style lang="scss" scoped>
203
- .apos-attachment-dropzone {
204
- @include apos-button-reset();
205
- @include type-base;
206
- display: block;
207
- margin: 10px 0;
208
- padding: 20px;
209
- border: 2px dashed var(--a-base-8);
210
- border-radius: var(--a-border-radius);
211
- transition: all 0.2s ease;
212
- &:hover {
213
- border-color: var(--a-primary);
214
- background-color: var(--a-base-10);
215
- }
216
- &:active,
217
- &:focus {
218
- border: 2px solid var(--a-primary);
219
- }
220
- &.apos-is-disabled {
221
- color: var(--a-base-4);
222
- background-color: var(--a-base-7);
223
- border-color: var(--a-base-4);
224
-
225
- &:hover {
226
- cursor: not-allowed;
227
- }
228
- }
229
- }
230
-
231
- .apos-attachment-dropzone--dragover {
232
- border: 2px dashed var(--a-primary);
233
- background-color: var(--a-base-10);
234
- }
235
-
236
- .apos-attachment-instructions {
237
- display: flex;
238
- flex-wrap: wrap;
239
- align-items: center;
240
- justify-content: center;
241
- pointer-events: none;
242
- // v-html goofiness
243
- & ::v-deep .apos-attachment-highlight {
244
- color: var(--a-primary);
245
- font-weight: var(--a-weight-bold);
246
- }
247
- }
248
-
249
- .apos-attachment-icon {
250
- transform: rotate(45deg);
251
- margin-right: 5px;
252
- }
253
- </style>
@@ -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
 
@@ -652,6 +652,8 @@ module.exports = {
652
652
  locale: req.locale,
653
653
  csrfCookieName: self.apos.csrfCookieName,
654
654
  tabId: self.apos.util.generateId(),
655
+ uploadsUrl: self.apos.attachment.uploadfs.getUrl(),
656
+ assetBaseUrl: self.apos.asset.getAssetBaseUrl(),
655
657
  scene
656
658
  };
657
659
  if (req.user) {
@@ -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
@@ -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>
@@ -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
@@ -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.7.0",
3
+ "version": "3.8.0",
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
  },