apostrophe 3.30.0 → 3.31.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.
- package/CHANGELOG.md +6 -0
- package/modules/@apostrophecms/area/index.js +6 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +18 -2
- package/modules/@apostrophecms/i18n/i18n/en.json +2 -0
- package/modules/@apostrophecms/i18n/i18n/es.json +2 -0
- package/modules/@apostrophecms/i18n/i18n/fr.json +2 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +2 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +2 -0
- package/modules/@apostrophecms/image-widget/index.js +4 -1
- package/modules/@apostrophecms/image-widget/public/placeholder.jpg +0 -0
- package/modules/@apostrophecms/image-widget/ui/src/index.scss +3 -0
- package/modules/@apostrophecms/image-widget/views/widget.html +35 -27
- package/modules/@apostrophecms/rich-text-widget/index.js +4 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +73 -11
- package/modules/@apostrophecms/video-widget/index.js +4 -1
- package/modules/@apostrophecms/video-widget/views/widget.html +24 -16
- package/modules/@apostrophecms/widget-type/index.js +27 -5
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidget.vue +5 -2
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +11 -0
- package/package.json +6 -5
- package/test/modules/placeholder-page/index.js +21 -0
- package/test/modules/placeholder-page/views/page.html +3 -0
- package/test/modules/placeholder-widget/index.js +36 -0
- package/test/modules/placeholder-widget/views/widget.html +5 -0
- package/test/widgets.js +453 -114
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.31.0 (2022-10-27)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Adds `placeholder: true` and `initialModal: false` features to improve the user experience of adding widgets to the page. Checkout the [Widget Placeholders documentation](https://v3.docs.apostrophecms.org/guide/areas-and-widgets.html#adding-placeholder-content-to-widgets) for more detail.
|
|
8
|
+
|
|
3
9
|
## 3.30.0 (2022-10-12)
|
|
4
10
|
|
|
5
11
|
### Adds
|
|
@@ -575,6 +575,8 @@ module.exports = {
|
|
|
575
575
|
const widgetEditors = {};
|
|
576
576
|
const widgetManagers = {};
|
|
577
577
|
const widgetIsContextual = {};
|
|
578
|
+
const widgetHasPlaceholder = {};
|
|
579
|
+
const widgetHasInitialModal = {};
|
|
578
580
|
const contextualWidgetDefaultData = {};
|
|
579
581
|
|
|
580
582
|
_.each(self.widgetManagers, function (manager, name) {
|
|
@@ -584,6 +586,8 @@ module.exports = {
|
|
|
584
586
|
widgetEditors[name] = (browserData && browserData.components && browserData.components.widgetEditor) || 'AposWidgetEditor';
|
|
585
587
|
widgetManagers[name] = manager.__meta.name;
|
|
586
588
|
widgetIsContextual[name] = manager.options.contextual;
|
|
589
|
+
widgetHasPlaceholder[name] = manager.options.placeholder;
|
|
590
|
+
widgetHasInitialModal[name] = !manager.options.placeholder && manager.options.initialModal !== false;
|
|
587
591
|
contextualWidgetDefaultData[name] = manager.options.defaultData;
|
|
588
592
|
});
|
|
589
593
|
|
|
@@ -594,6 +598,8 @@ module.exports = {
|
|
|
594
598
|
widgetEditors
|
|
595
599
|
},
|
|
596
600
|
widgetIsContextual,
|
|
601
|
+
widgetHasPlaceholder,
|
|
602
|
+
widgetHasInitialModal,
|
|
597
603
|
contextualWidgetDefaultData,
|
|
598
604
|
widgetManagers,
|
|
599
605
|
action: self.action
|
|
@@ -404,6 +404,8 @@ export default {
|
|
|
404
404
|
}
|
|
405
405
|
},
|
|
406
406
|
async update(widget) {
|
|
407
|
+
widget.aposPlaceholder = false;
|
|
408
|
+
|
|
407
409
|
if (this.docId === window.apos.adminBar.contextId) {
|
|
408
410
|
apos.bus.$emit('context-edited', {
|
|
409
411
|
[`@${widget._id}`]: widget
|
|
@@ -434,9 +436,17 @@ export default {
|
|
|
434
436
|
} else if (this.widgetIsContextual(name)) {
|
|
435
437
|
return this.insert({
|
|
436
438
|
widget: {
|
|
437
|
-
_id: cuid(),
|
|
438
439
|
type: name,
|
|
439
|
-
...this.contextualWidgetDefaultData(name)
|
|
440
|
+
...this.contextualWidgetDefaultData(name),
|
|
441
|
+
aposPlaceholder: this.widgetHasPlaceholder(name)
|
|
442
|
+
},
|
|
443
|
+
index
|
|
444
|
+
});
|
|
445
|
+
} else if (!this.widgetHasInitialModal(name)) {
|
|
446
|
+
return this.insert({
|
|
447
|
+
widget: {
|
|
448
|
+
type: name,
|
|
449
|
+
aposPlaceholder: this.widgetHasPlaceholder(name)
|
|
440
450
|
},
|
|
441
451
|
index
|
|
442
452
|
});
|
|
@@ -502,6 +512,12 @@ export default {
|
|
|
502
512
|
widgetIsContextual(type) {
|
|
503
513
|
return this.moduleOptions.widgetIsContextual[type];
|
|
504
514
|
},
|
|
515
|
+
widgetHasPlaceholder(type) {
|
|
516
|
+
return this.moduleOptions.widgetHasPlaceholder[type];
|
|
517
|
+
},
|
|
518
|
+
widgetHasInitialModal(type) {
|
|
519
|
+
return this.moduleOptions.widgetHasInitialModal[type];
|
|
520
|
+
},
|
|
505
521
|
widgetEditorComponent(type) {
|
|
506
522
|
return this.moduleOptions.components.widgetEditors[type];
|
|
507
523
|
},
|
|
@@ -161,6 +161,7 @@
|
|
|
161
161
|
"home": "Home",
|
|
162
162
|
"image": "Image",
|
|
163
163
|
"imageFile": "Image File",
|
|
164
|
+
"imagePlaceholder": "Image placeholder",
|
|
164
165
|
"imageTag": "Image Tag",
|
|
165
166
|
"imageTags": "Image Tags",
|
|
166
167
|
"images": "Images",
|
|
@@ -263,6 +264,7 @@
|
|
|
263
264
|
"richTextItalic": "Italic",
|
|
264
265
|
"richTextLink": "Link",
|
|
265
266
|
"richTextParagraph": "Paragraph (P)",
|
|
267
|
+
"richTextPlaceholder": "Start Typing Here...",
|
|
266
268
|
"richTextH2": "Heading 2 (H2)",
|
|
267
269
|
"richTextH3": "Heading 3 (H3)",
|
|
268
270
|
"richTextH4": "Heading 4 (H4)",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"home": "Inicio",
|
|
151
151
|
"image": "Imagen",
|
|
152
152
|
"imageFile": "Archivo de Imagen",
|
|
153
|
+
"imagePlaceholder": "Marcador de posición de imagen",
|
|
153
154
|
"imageTag": "Etiqueta de Imagen",
|
|
154
155
|
"imageTags": "Etiquetas de Imágenes",
|
|
155
156
|
"images": "Imágenes",
|
|
@@ -242,6 +243,7 @@
|
|
|
242
243
|
"richTextItalic": "Cursiva",
|
|
243
244
|
"richTextLink": "Liga",
|
|
244
245
|
"richTextParagraph": "Párrafo (P)",
|
|
246
|
+
"richTextPlaceholder": "Comience a escribir aquí...",
|
|
245
247
|
"richTextH2": "Título 2 (H2)",
|
|
246
248
|
"richTextH3": "Título 3 (H3)",
|
|
247
249
|
"richTextH4": "Título 4 (H4)",
|
|
@@ -148,6 +148,7 @@
|
|
|
148
148
|
"home": "Accueil",
|
|
149
149
|
"image": "Image",
|
|
150
150
|
"imageFile": "Fichier d'image",
|
|
151
|
+
"imagePlaceholder": "Espace réservé pour l'image",
|
|
151
152
|
"imageTag": "Tag d'image",
|
|
152
153
|
"imageTags": "Tags d'image",
|
|
153
154
|
"images": "Images",
|
|
@@ -249,6 +250,7 @@
|
|
|
249
250
|
"richTextItalic": "Italique",
|
|
250
251
|
"richTextLink": "Lien",
|
|
251
252
|
"richTextParagraph": "Paragraphe (P)",
|
|
253
|
+
"richTextPlaceholder": "Commencez à écrire ici...",
|
|
252
254
|
"richTextH2": "Titre de niveau 2 (H2)",
|
|
253
255
|
"richTextH3": "Titre de niveau 3 (H3)",
|
|
254
256
|
"richTextH4": "Titre de niveau 4 (H4)",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"home": "Home",
|
|
151
151
|
"image": "Imagem",
|
|
152
152
|
"imageFile": "Arquivo de Imagem",
|
|
153
|
+
"imagePlaceholder": "Espaço reservado para imagem",
|
|
153
154
|
"imageTag": "Tag de Imagem",
|
|
154
155
|
"imageTags": "Tags de Imagem",
|
|
155
156
|
"images": "Imagens",
|
|
@@ -240,6 +241,7 @@
|
|
|
240
241
|
"richTextItalic": "Itálico",
|
|
241
242
|
"richTextLink": "Link",
|
|
242
243
|
"richTextParagraph": "Parágrafo (P)",
|
|
244
|
+
"richTextPlaceholder": "Comece a digitar aqui...",
|
|
243
245
|
"richTextH2": "Título 2 (H2)",
|
|
244
246
|
"richTextH3": "Título 3 (H3)",
|
|
245
247
|
"richTextH4": "Título 4 (H4)",
|
|
@@ -154,6 +154,7 @@
|
|
|
154
154
|
"home": "Domovská stránka",
|
|
155
155
|
"image": "Obrázok",
|
|
156
156
|
"imageFile": "Súbor s obrázkom",
|
|
157
|
+
"imagePlaceholder": "Zástupný symbol obrázka",
|
|
157
158
|
"imageTag": "Značku obrázku",
|
|
158
159
|
"imageTags": "Značky obrázkov",
|
|
159
160
|
"images": "Obrázky",
|
|
@@ -252,6 +253,7 @@
|
|
|
252
253
|
"richTextItalic": "Kurzíva",
|
|
253
254
|
"richTextLink": "Link",
|
|
254
255
|
"richTextParagraph": "Odstavec (P)",
|
|
256
|
+
"richTextPlaceholder": "Začnite písať tu...",
|
|
255
257
|
"richTextH2": "Nadpis 2 (H2)",
|
|
256
258
|
"richTextH3": "Nadpis 3 (H3)",
|
|
257
259
|
"richTextH4": "Nadpis 4 (H4)",
|
|
@@ -4,7 +4,10 @@ module.exports = {
|
|
|
4
4
|
label: 'apostrophe:image',
|
|
5
5
|
className: false,
|
|
6
6
|
icon: 'image-icon',
|
|
7
|
-
dimensionAttrs: false
|
|
7
|
+
dimensionAttrs: false,
|
|
8
|
+
placeholder: true,
|
|
9
|
+
placeholderClass: false,
|
|
10
|
+
placeholderUrl: '/modules/@apostrophecms/image-widget/placeholder.jpg'
|
|
8
11
|
},
|
|
9
12
|
fields: {
|
|
10
13
|
add: {
|
|
Binary file
|
|
@@ -1,31 +1,39 @@
|
|
|
1
|
-
{% if data.options.
|
|
2
|
-
|
|
3
|
-
{
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
{% if data.widget.aposPlaceholder and data.manager.options.placeholderUrl %}
|
|
2
|
+
<img
|
|
3
|
+
src="{{ apos.asset.url(data.manager.options.placeholderUrl) }}"
|
|
4
|
+
alt="{{ __t('apostrophe:imagePlaceholder') }}"
|
|
5
|
+
class="image-widget-placeholder"
|
|
6
|
+
/>
|
|
7
|
+
{% else %}
|
|
8
|
+
{% if data.options.className %}
|
|
9
|
+
{% set className = data.options.className %}
|
|
10
|
+
{% elif data.manager.options.className %}
|
|
11
|
+
{% set className = data.manager.options.className %}
|
|
12
|
+
{% endif %}
|
|
6
13
|
|
|
7
|
-
{% if data.options.dimensionAttrs %}
|
|
8
|
-
|
|
9
|
-
{% elif data.manager.options.dimensionAttrs
|
|
10
|
-
|
|
11
|
-
{% endif %}
|
|
14
|
+
{% if data.options.dimensionAttrs %}
|
|
15
|
+
{% set dimensionAttrs = data.options.dimensionAttrs %}
|
|
16
|
+
{% elif data.manager.options.dimensionAttrs %}
|
|
17
|
+
{% set dimensionAttrs = data.manager.options.dimensionAttrs %}
|
|
18
|
+
{% endif %}
|
|
12
19
|
|
|
13
|
-
{% set attachment = apos.image.first(data.widget._image) %}
|
|
20
|
+
{% set attachment = apos.image.first(data.widget._image) %}
|
|
14
21
|
|
|
15
|
-
{% if attachment %}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
{% if attachment %}
|
|
23
|
+
<img {% if className %} class="{{ className }}"{% endif %}
|
|
24
|
+
srcset="{{ apos.image.srcset(attachment) }}"
|
|
25
|
+
src="{{ apos.attachment.url(attachment, { size: data.options.size or 'full' }) }}"
|
|
26
|
+
alt="{{ attachment._alt or '' }}"
|
|
27
|
+
{% if dimensionAttrs %}
|
|
28
|
+
{% if attachment.width %} width="{{ apos.attachment.getWidth(attachment) }}" {% endif %}
|
|
29
|
+
{% if attachment.height %} height="{{ apos.attachment.getHeight(attachment) }}" {% endif %}
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if data.contextOptions and data.contextOptions.sizes %}
|
|
32
|
+
sizes="{{ data.contextOptions.sizes }}"
|
|
33
|
+
{% endif %}
|
|
34
|
+
{% if apos.attachment.hasFocalPoint(attachment) %}
|
|
35
|
+
style="object-position: {{ apos.attachment.focalPointToObjectPosition(attachment) }}"
|
|
36
|
+
{%- endif -%}
|
|
37
|
+
/>
|
|
38
|
+
{% endif %}
|
|
31
39
|
{% endif %}
|
|
@@ -9,6 +9,8 @@ module.exports = {
|
|
|
9
9
|
icon: 'format-text-icon',
|
|
10
10
|
label: 'apostrophe:richText',
|
|
11
11
|
contextual: true,
|
|
12
|
+
placeholder: true,
|
|
13
|
+
placeholderText: 'apostrophe:richTextPlaceholder',
|
|
12
14
|
defaultData: { content: '' },
|
|
13
15
|
className: false,
|
|
14
16
|
minimumDefaultOptions: {
|
|
@@ -417,7 +419,8 @@ module.exports = {
|
|
|
417
419
|
tools: self.options.editorTools,
|
|
418
420
|
defaultOptions: self.options.defaultOptions,
|
|
419
421
|
tiptapTextCommands: self.options.tiptapTextCommands,
|
|
420
|
-
tiptapTypes: self.options.tiptapTypes
|
|
422
|
+
tiptapTypes: self.options.tiptapTypes,
|
|
423
|
+
placeholderText: self.options.placeholder && self.options.placeholderText
|
|
421
424
|
};
|
|
422
425
|
return finalData;
|
|
423
426
|
}
|
package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue
CHANGED
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
<div class="apos-rich-text-editor__editor" :class="editorModifiers">
|
|
29
29
|
<editor-content :editor="editor" :class="editorOptions.className" />
|
|
30
30
|
</div>
|
|
31
|
-
<div
|
|
31
|
+
<div
|
|
32
|
+
v-if="showPlaceholder !== null && (!placeholderText || !isFocused)"
|
|
33
|
+
class="apos-rich-text-editor__editor_after" :class="editorModifiers"
|
|
34
|
+
>
|
|
32
35
|
{{ $t('apostrophe:emptyRichTextWidget') }}
|
|
33
36
|
</div>
|
|
34
37
|
</div>
|
|
@@ -45,6 +48,8 @@ import TextAlign from '@tiptap/extension-text-align';
|
|
|
45
48
|
import Highlight from '@tiptap/extension-highlight';
|
|
46
49
|
import TextStyle from '@tiptap/extension-text-style';
|
|
47
50
|
import Underline from '@tiptap/extension-underline';
|
|
51
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
52
|
+
|
|
48
53
|
export default {
|
|
49
54
|
name: 'AposRichTextWidgetEditor',
|
|
50
55
|
components: {
|
|
@@ -88,7 +93,9 @@ export default {
|
|
|
88
93
|
},
|
|
89
94
|
hasErrors: false
|
|
90
95
|
},
|
|
91
|
-
pending: null
|
|
96
|
+
pending: null,
|
|
97
|
+
isFocused: null,
|
|
98
|
+
showPlaceholder: null
|
|
92
99
|
};
|
|
93
100
|
},
|
|
94
101
|
computed: {
|
|
@@ -158,6 +165,9 @@ export default {
|
|
|
158
165
|
tiptapTypes() {
|
|
159
166
|
return this.moduleOptions.tiptapTypes;
|
|
160
167
|
},
|
|
168
|
+
placeholderText() {
|
|
169
|
+
return this.moduleOptions.placeholderText;
|
|
170
|
+
},
|
|
161
171
|
aposTiptapExtensions() {
|
|
162
172
|
return (apos.tiptapExtensions || [])
|
|
163
173
|
.map(extension => extension({
|
|
@@ -176,19 +186,63 @@ export default {
|
|
|
176
186
|
}
|
|
177
187
|
},
|
|
178
188
|
mounted() {
|
|
189
|
+
const extensions = [
|
|
190
|
+
StarterKit,
|
|
191
|
+
TextAlign.configure({
|
|
192
|
+
types: [ 'heading', 'paragraph' ]
|
|
193
|
+
}),
|
|
194
|
+
Highlight,
|
|
195
|
+
TextStyle,
|
|
196
|
+
Underline,
|
|
197
|
+
|
|
198
|
+
// For this contextual widget, no need to check `widget.aposPlaceholder` value
|
|
199
|
+
// since `placeholderText` option is enough to decide whether to display it or not.
|
|
200
|
+
this.placeholderText && Placeholder.configure({
|
|
201
|
+
placeholder: () => {
|
|
202
|
+
// Avoid brief display of the placeholder when loading the page.
|
|
203
|
+
if (this.isFocused === null) {
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Display placeholder after loading the page.
|
|
208
|
+
if (this.showPlaceholder === null) {
|
|
209
|
+
return this.$t(this.placeholderText);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return this.showPlaceholder ? this.$t(this.placeholderText) : '';
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
]
|
|
216
|
+
.filter(Boolean)
|
|
217
|
+
.concat(this.aposTiptapExtensions);
|
|
218
|
+
|
|
179
219
|
this.editor = new Editor({
|
|
180
220
|
content: this.initialContent,
|
|
181
221
|
autofocus: this.autofocus,
|
|
182
222
|
onUpdate: this.editorUpdate,
|
|
183
|
-
extensions
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
223
|
+
extensions,
|
|
224
|
+
|
|
225
|
+
// The following events are triggered:
|
|
226
|
+
// - before the placeholder configuration function, when loading the page
|
|
227
|
+
// - after it, once the page is loaded and we interact with the editors
|
|
228
|
+
// To solve this issue, use another `this.showPlaceholder` variable
|
|
229
|
+
// and toggle it after the placeholder configuration function is called,
|
|
230
|
+
// thanks to nextTick.
|
|
231
|
+
// The proper thing would be to call nextTick inside the placeholder
|
|
232
|
+
// function so that it can rely on the focus state set by these event
|
|
233
|
+
// listeners, but the placeholder function is called synchronously...
|
|
234
|
+
onFocus: () => {
|
|
235
|
+
this.isFocused = true;
|
|
236
|
+
this.$nextTick(() => {
|
|
237
|
+
this.showPlaceholder = false;
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
onBlur: () => {
|
|
241
|
+
this.isFocused = false;
|
|
242
|
+
this.$nextTick(() => {
|
|
243
|
+
this.showPlaceholder = true;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
192
246
|
});
|
|
193
247
|
},
|
|
194
248
|
|
|
@@ -336,6 +390,14 @@ export default {
|
|
|
336
390
|
outline: none;
|
|
337
391
|
}
|
|
338
392
|
|
|
393
|
+
.apos-rich-text-editor__editor ::v-deep .ProseMirror p.is-empty:first-child::before {
|
|
394
|
+
content: attr(data-placeholder);
|
|
395
|
+
float: left;
|
|
396
|
+
pointer-events: none;
|
|
397
|
+
height: 0;
|
|
398
|
+
color: var(--a-base-4);
|
|
399
|
+
}
|
|
400
|
+
|
|
339
401
|
.apos-rich-text-editor__editor {
|
|
340
402
|
@include apos-transition();
|
|
341
403
|
position: relative;
|
|
@@ -14,7 +14,10 @@ module.exports = {
|
|
|
14
14
|
options: {
|
|
15
15
|
label: 'apostrophe:video',
|
|
16
16
|
className: false,
|
|
17
|
-
icon: 'play-box-icon'
|
|
17
|
+
icon: 'play-box-icon',
|
|
18
|
+
placeholder: true,
|
|
19
|
+
placeholderClass: false,
|
|
20
|
+
placeholderUrl: 'https://youtu.be/Q5UX9yexEyM'
|
|
18
21
|
},
|
|
19
22
|
fields: {
|
|
20
23
|
add: {
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
{% if data.options.
|
|
2
|
-
{% set className = data.options.className %}
|
|
3
|
-
{% elif data.manager.options.className %}
|
|
4
|
-
{% set className = data.manager.options.className %}
|
|
5
|
-
{% endif %}
|
|
6
|
-
|
|
7
|
-
{# oembed repopulates me #}
|
|
8
|
-
{% if data.widget.video %}
|
|
1
|
+
{% if data.widget.aposPlaceholder and data.manager.options.placeholderUrl %}
|
|
9
2
|
<div
|
|
10
|
-
{% if className %} class="{{ className }}"{% endif %}
|
|
11
3
|
data-apos-video-widget
|
|
12
|
-
data-apos-video-url={{ data.
|
|
4
|
+
data-apos-video-url="{{ data.manager.options.placeholderUrl }}"
|
|
13
5
|
>
|
|
14
|
-
{% if data.widget.video.thumbnail %}
|
|
15
|
-
<img src="{{ data.widget.video.thumbnail }}" alt="{{ data.widget.video.thumbnail }}"/>
|
|
16
|
-
{% endif %}
|
|
17
6
|
</div>
|
|
18
|
-
{%
|
|
19
|
-
|
|
20
|
-
{%
|
|
7
|
+
{% else %}
|
|
8
|
+
{% if data.options.className %}
|
|
9
|
+
{% set className = data.options.className %}
|
|
10
|
+
{% elif data.manager.options.className %}
|
|
11
|
+
{% set className = data.manager.options.className %}
|
|
12
|
+
{% endif %}
|
|
13
|
+
|
|
14
|
+
{# oembed repopulates me #}
|
|
15
|
+
{% if data.widget.video %}
|
|
16
|
+
<div
|
|
17
|
+
{% if className %} class="{{ className }}"{% endif %}
|
|
18
|
+
data-apos-video-widget
|
|
19
|
+
data-apos-video-url={{ data.widget.video.url }}
|
|
20
|
+
>
|
|
21
|
+
{% if data.widget.video.thumbnail %}
|
|
22
|
+
<img src="{{ data.widget.video.thumbnail }}" alt="{{ data.widget.video.thumbnail }}"/>
|
|
23
|
+
{% endif %}
|
|
24
|
+
</div>
|
|
25
|
+
{% elif data.user %}
|
|
26
|
+
<p {% if data.manager.options.className %} class="{{ data.manager.options.className }} {{ data.manager.options.className }}--error"{% endif %}>No video selected</p>
|
|
27
|
+
{% endif %}
|
|
28
|
+
{% endif %}
|
|
@@ -97,7 +97,10 @@ const _ = require('lodash');
|
|
|
97
97
|
module.exports = {
|
|
98
98
|
cascades: [ 'fields' ],
|
|
99
99
|
options: {
|
|
100
|
-
neverLoadSelf: true
|
|
100
|
+
neverLoadSelf: true,
|
|
101
|
+
initialModal: true,
|
|
102
|
+
placeholder: false,
|
|
103
|
+
placeholderClass: 'apos-placeholder'
|
|
101
104
|
},
|
|
102
105
|
init(self) {
|
|
103
106
|
|
|
@@ -161,8 +164,24 @@ module.exports = {
|
|
|
161
164
|
...self.getWidgetsBundles(`${widget.type}-widget`)
|
|
162
165
|
};
|
|
163
166
|
|
|
167
|
+
let effectiveWidget = widget;
|
|
168
|
+
|
|
169
|
+
if (widget.aposPlaceholder === true) {
|
|
170
|
+
// Do not render widget on preview mode:
|
|
171
|
+
if (req.query.aposEdit !== '1') {
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
effectiveWidget = { ...widget };
|
|
176
|
+
self.schema.forEach(field => {
|
|
177
|
+
if (field.placeholder !== undefined) {
|
|
178
|
+
effectiveWidget[field.name] = field.placeholder;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
164
183
|
return self.render(req, self.template, {
|
|
165
|
-
widget:
|
|
184
|
+
widget: effectiveWidget,
|
|
166
185
|
options: options,
|
|
167
186
|
manager: self,
|
|
168
187
|
contextOptions: _with
|
|
@@ -276,11 +295,14 @@ module.exports = {
|
|
|
276
295
|
// Make sure we get default values for contextual fields so
|
|
277
296
|
// `by` doesn't go missing for `@apostrophecms/image-widget`
|
|
278
297
|
const output = self.apos.schema.newInstance(self.schema);
|
|
279
|
-
const schema = self.allowedSchema(req);
|
|
280
298
|
output._id = self.apos.launder.id(input._id) || self.apos.util.generateId();
|
|
281
|
-
await self.apos.schema.convert(req, schema, input, output);
|
|
282
299
|
output.metaType = 'widget';
|
|
283
300
|
output.type = self.name;
|
|
301
|
+
output.aposPlaceholder = self.apos.launder.boolean(input.aposPlaceholder);
|
|
302
|
+
if (!output.aposPlaceholder) {
|
|
303
|
+
const schema = self.allowedSchema(req);
|
|
304
|
+
await self.apos.schema.convert(req, schema, input, output);
|
|
305
|
+
}
|
|
284
306
|
return output;
|
|
285
307
|
},
|
|
286
308
|
|
|
@@ -352,7 +374,7 @@ module.exports = {
|
|
|
352
374
|
action: self.action,
|
|
353
375
|
schema: schema,
|
|
354
376
|
contextual: self.options.contextual,
|
|
355
|
-
|
|
377
|
+
placeholderClass: self.options.placeholderClass,
|
|
356
378
|
className: self.options.className,
|
|
357
379
|
components: self.options.components
|
|
358
380
|
});
|
|
@@ -62,6 +62,17 @@ export default {
|
|
|
62
62
|
this.rendered = '<p>Unable to render this widget.</p>';
|
|
63
63
|
console.error('Unable to render widget. Possibly the schema has been changed and the existing widget does not pass validation.', e);
|
|
64
64
|
}
|
|
65
|
+
},
|
|
66
|
+
getClasses() {
|
|
67
|
+
const { placeholderClass } = this.moduleOptions;
|
|
68
|
+
|
|
69
|
+
if (!placeholderClass) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
[placeholderClass]: this.value.aposPlaceholder === true
|
|
75
|
+
};
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@opentelemetry/semantic-conventions": "^1.0.1",
|
|
36
36
|
"@tiptap/extension-highlight": "^2.0.0-beta.33",
|
|
37
37
|
"@tiptap/extension-link": "^2.0.0-beta.38",
|
|
38
|
+
"@tiptap/extension-placeholder": "^2.0.0-beta.196",
|
|
38
39
|
"@tiptap/extension-text-align": "^2.0.0-beta.29",
|
|
39
40
|
"@tiptap/extension-text-style": "^2.0.0-beta.23",
|
|
40
41
|
"@tiptap/extension-underline": "^2.0.0-beta.23",
|
|
@@ -124,16 +125,16 @@
|
|
|
124
125
|
"eslint-config-apostrophe": "^3.4.0",
|
|
125
126
|
"eslint-plugin-n": "^15.2.1",
|
|
126
127
|
"eslint-plugin-node": "^11.1.0",
|
|
128
|
+
"eslint-plugin-promise": "^5.1.0",
|
|
127
129
|
"eslint-plugin-vue": "^7.9.0",
|
|
128
130
|
"mocha": "^9.1.2",
|
|
129
131
|
"nyc": "^15.1.0",
|
|
130
132
|
"replace-in-file": "^6.1.0",
|
|
131
|
-
"vue-eslint-parser": "^7.1.1",
|
|
132
|
-
"webpack-bundle-analyzer": "^3.9.0",
|
|
133
|
-
"eslint-plugin-promise": "^5.1.0",
|
|
134
133
|
"stylelint": "^14.6.1",
|
|
135
134
|
"stylelint-declaration-strict-value": "^1.8.0",
|
|
136
|
-
"stylelint-order": "^5.0.0"
|
|
135
|
+
"stylelint-order": "^5.0.0",
|
|
136
|
+
"vue-eslint-parser": "^7.1.1",
|
|
137
|
+
"webpack-bundle-analyzer": "^3.9.0"
|
|
137
138
|
},
|
|
138
139
|
"browserslist": [
|
|
139
140
|
"ie >= 10"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
extend: '@apostrophecms/page-type',
|
|
3
|
+
options: {
|
|
4
|
+
label: 'Placeholder Test Page'
|
|
5
|
+
},
|
|
6
|
+
fields: {
|
|
7
|
+
add: {
|
|
8
|
+
main: {
|
|
9
|
+
type: 'area',
|
|
10
|
+
label: 'Main',
|
|
11
|
+
options: {
|
|
12
|
+
widgets: {
|
|
13
|
+
placeholder: {},
|
|
14
|
+
'@apostrophecms/image': {},
|
|
15
|
+
'@apostrophecms/video': {}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
extend: '@apostrophecms/widget-type',
|
|
3
|
+
options: {
|
|
4
|
+
label: 'Placeholder Test Widget',
|
|
5
|
+
placeholder: true
|
|
6
|
+
},
|
|
7
|
+
fields: {
|
|
8
|
+
add: {
|
|
9
|
+
string: {
|
|
10
|
+
type: 'string',
|
|
11
|
+
label: 'String',
|
|
12
|
+
placeholder: 'String PLACEHOLDER'
|
|
13
|
+
},
|
|
14
|
+
integer: {
|
|
15
|
+
type: 'integer',
|
|
16
|
+
label: 'Integer',
|
|
17
|
+
placeholder: 0
|
|
18
|
+
},
|
|
19
|
+
float: {
|
|
20
|
+
type: 'float',
|
|
21
|
+
label: 'Float',
|
|
22
|
+
placeholder: 0.1
|
|
23
|
+
},
|
|
24
|
+
date: {
|
|
25
|
+
type: 'date',
|
|
26
|
+
label: 'Date',
|
|
27
|
+
placeholder: 'YYYY-MM-DD'
|
|
28
|
+
},
|
|
29
|
+
time: {
|
|
30
|
+
type: 'time',
|
|
31
|
+
label: 'Time',
|
|
32
|
+
placeholder: 'HH:MM:SS'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
package/test/widgets.js
CHANGED
|
@@ -1,154 +1,493 @@
|
|
|
1
1
|
const t = require('../test-lib/test.js');
|
|
2
2
|
const assert = require('assert');
|
|
3
|
-
|
|
4
|
-
let apos;
|
|
3
|
+
const { JSDOM } = require('jsdom');
|
|
5
4
|
|
|
6
5
|
describe('Widgets', function() {
|
|
6
|
+
const getRenderArgs = (req, page) => ({
|
|
7
|
+
outerLayout: '@apostrophecms/template:outerLayout.html',
|
|
8
|
+
permissions: req.user && (req.user._permissions || {}),
|
|
9
|
+
scene: 'apos',
|
|
10
|
+
refreshing: false,
|
|
11
|
+
query: req.query,
|
|
12
|
+
url: req.url,
|
|
13
|
+
page
|
|
14
|
+
});
|
|
15
|
+
let apos;
|
|
16
|
+
let req;
|
|
17
|
+
let homePath;
|
|
7
18
|
|
|
8
19
|
this.timeout(t.timeout);
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
return t.destroy(apos);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should add test modules', async function() {
|
|
21
|
+
before(async function() {
|
|
15
22
|
apos = await t.create({
|
|
16
23
|
root: module,
|
|
17
24
|
modules: {
|
|
18
25
|
'args-bad-page': {},
|
|
19
26
|
'args-good-page': {},
|
|
20
|
-
'args-widget': {}
|
|
27
|
+
'args-widget': {},
|
|
28
|
+
'placeholder-page': {},
|
|
29
|
+
'placeholder-widget': {}
|
|
21
30
|
}
|
|
22
31
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
|
|
33
|
+
req = apos.task.getAnonReq({
|
|
34
|
+
query: {
|
|
35
|
+
aposEdit: '1'
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const home = await apos.page.find(req, { level: 0 }).toObject();
|
|
40
|
+
homePath = home._id.replace(':en:published', '');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
after(function() {
|
|
44
|
+
return t.destroy(apos);
|
|
26
45
|
});
|
|
27
46
|
|
|
28
|
-
|
|
47
|
+
describe('area tag', function() {
|
|
48
|
+
let testItems = [];
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
before(async function() {
|
|
51
|
+
testItems = [
|
|
52
|
+
{
|
|
53
|
+
_id: 'goodPageId:en:published',
|
|
54
|
+
aposLocale: 'en:published',
|
|
55
|
+
aposDocId: 'goodPageId',
|
|
56
|
+
type: 'args-good-page',
|
|
57
|
+
slug: '/good-page',
|
|
58
|
+
visibility: 'public',
|
|
59
|
+
path: `${homePath}/good-page`,
|
|
60
|
+
level: 1,
|
|
61
|
+
rank: 0,
|
|
62
|
+
metaType: 'doc',
|
|
63
|
+
main: {
|
|
64
|
+
_id: 'randomAreaId1',
|
|
65
|
+
items: [
|
|
66
|
+
{
|
|
67
|
+
_id: 'randomWidgetId1',
|
|
68
|
+
snippet: 'You can control what happens when the text reaches the edges of its content area using its attributes.',
|
|
69
|
+
metaType: 'widget',
|
|
70
|
+
type: 'args'
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
metaType: 'area'
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
_id: 'badPageId:en:published',
|
|
78
|
+
aposLocale: 'en:published',
|
|
79
|
+
aposDocId: 'badPageId',
|
|
80
|
+
type: 'args-bad-page',
|
|
81
|
+
slug: '/bad-page',
|
|
82
|
+
visibility: 'public',
|
|
83
|
+
path: `${homePath}/bad-page`,
|
|
84
|
+
level: 1,
|
|
85
|
+
rank: 0,
|
|
86
|
+
metaType: 'doc',
|
|
87
|
+
main: {
|
|
88
|
+
_id: 'randomAreaId2',
|
|
89
|
+
items: [
|
|
90
|
+
{
|
|
91
|
+
_id: 'randomWidgetId2',
|
|
92
|
+
snippet: 'You can control what happens when the text reaches the edges of its content area using its attributes.',
|
|
93
|
+
metaType: 'widget',
|
|
94
|
+
type: 'args'
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
metaType: 'area'
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
];
|
|
32
101
|
|
|
33
|
-
|
|
34
|
-
|
|
102
|
+
await apos.doc.db.insertMany(testItems.map(item => ({
|
|
103
|
+
...item,
|
|
104
|
+
aposLocale: item.aposLocale.replace(':published', ':draft'),
|
|
105
|
+
_id: item._id.replace(':published', ':draft')
|
|
106
|
+
})));
|
|
35
107
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
108
|
+
await apos.doc.db.insertMany(testItems);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
after(async function() {
|
|
112
|
+
await apos.doc.db.deleteMany({
|
|
113
|
+
aposDocId: {
|
|
114
|
+
$in: testItems.map(item => item.aposDocId)
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should be able to render page template with well constructed area tag', async function() {
|
|
120
|
+
const goodPageDoc = await apos.page.find(req, { slug: '/good-page' }).toObject();
|
|
121
|
+
|
|
122
|
+
const args = getRenderArgs(req, goodPageDoc);
|
|
123
|
+
const result = await apos.modules['args-good-page'].render(req, 'page', args);
|
|
124
|
+
|
|
125
|
+
assert(result.includes('<h2>Good args page</h2>'));
|
|
126
|
+
assert(result.includes('<p>You can control what happens when the text reaches the edges of its content area using its attributes.</p>'));
|
|
127
|
+
assert(result.includes('<li>color: 🟣</li>'));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should error while trying to render page template with poorly constructed area tag', async function() {
|
|
131
|
+
const badPageDoc = await apos.page.find(req, { slug: '/bad-page' }).toObject();
|
|
132
|
+
|
|
133
|
+
const args = getRenderArgs(req, badPageDoc);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
await apos.modules['args-bad-page'].render(req, 'page', args);
|
|
137
|
+
|
|
138
|
+
assert(false);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
assert(error.toString().includes('Too many arguments were passed'));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('placeholders', function() {
|
|
146
|
+
const insertPage = async (apos, homePath, widgets) => {
|
|
147
|
+
const page = {
|
|
148
|
+
_id: 'placeholder-page:en:published',
|
|
39
149
|
aposLocale: 'en:published',
|
|
40
|
-
aposDocId: '
|
|
41
|
-
type: '
|
|
42
|
-
slug: '/
|
|
150
|
+
aposDocId: 'placeholder-page',
|
|
151
|
+
type: 'placeholder-page',
|
|
152
|
+
slug: '/placeholder-page',
|
|
43
153
|
visibility: 'public',
|
|
44
|
-
path: `${
|
|
154
|
+
path: `${homePath}/placeholder-page`,
|
|
45
155
|
level: 1,
|
|
46
156
|
rank: 0,
|
|
157
|
+
metaType: 'doc',
|
|
47
158
|
main: {
|
|
48
|
-
_id: '
|
|
49
|
-
items:
|
|
50
|
-
{
|
|
51
|
-
_id: 'randomWidgetId1',
|
|
52
|
-
snippet: 'You can control what happens when the text reaches the edges of its content area using its attributes.',
|
|
53
|
-
metaType: 'widget',
|
|
54
|
-
type: 'args'
|
|
55
|
-
}
|
|
56
|
-
],
|
|
159
|
+
_id: 'area1',
|
|
160
|
+
items: widgets,
|
|
57
161
|
metaType: 'area'
|
|
58
162
|
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
await apos.doc.db.insertOne(page);
|
|
166
|
+
await apos.doc.db.insertOne({
|
|
167
|
+
...page,
|
|
168
|
+
aposLocale: page.aposLocale.replace(':published', ':draft'),
|
|
169
|
+
_id: page._id.replace(':published', ':draft')
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const deletePage = async (apos, page) => {
|
|
174
|
+
await apos.doc.db.deleteMany({
|
|
175
|
+
aposDocId: page.aposDocId
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
describe('custom widget', function() {
|
|
180
|
+
const widgetBaseData = {
|
|
181
|
+
metaType: 'widget',
|
|
182
|
+
type: 'placeholder'
|
|
183
|
+
};
|
|
184
|
+
const widgetData = {
|
|
185
|
+
...widgetBaseData,
|
|
186
|
+
string: 'Some string',
|
|
187
|
+
integer: 2,
|
|
188
|
+
float: 2.2,
|
|
189
|
+
date: '2022-09-21',
|
|
190
|
+
time: '15:39:12'
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
let page;
|
|
194
|
+
let result;
|
|
195
|
+
|
|
196
|
+
before(async function() {
|
|
197
|
+
const widgets = [
|
|
198
|
+
{
|
|
199
|
+
_id: 'widget1',
|
|
200
|
+
...widgetBaseData,
|
|
201
|
+
aposPlaceholder: true
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
_id: 'widget2',
|
|
205
|
+
...widgetBaseData,
|
|
206
|
+
aposPlaceholder: false
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
_id: 'widget3',
|
|
210
|
+
...widgetBaseData
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
_id: 'widget4',
|
|
214
|
+
...widgetData
|
|
215
|
+
}
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
await insertPage(apos, homePath, widgets);
|
|
219
|
+
page = await apos.page.find(req, { slug: '/placeholder-page' }).toObject();
|
|
220
|
+
|
|
221
|
+
const args = getRenderArgs(req, page);
|
|
222
|
+
result = await apos.modules['placeholder-page'].render(req, 'page', args);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
after(async function() {
|
|
226
|
+
await deletePage(apos, page);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should render the placeholders when widget\'s `aposPlaceholder` doc field is `true`', function() {
|
|
230
|
+
assert(result.includes('<li>widget1 - aposPlaceholder: true</li>'));
|
|
231
|
+
assert(result.includes('<li>widget1 - string: String PLACEHOLDER</li>'));
|
|
232
|
+
assert(result.includes('<li>widget1 - integer: 0</li>'));
|
|
233
|
+
assert(result.includes('<li>widget1 - float: 0.1</li>'));
|
|
234
|
+
assert(result.includes('<li>widget1 - date: YYYY-MM-DD</li>'));
|
|
235
|
+
assert(result.includes('<li>widget1 - time: HH:MM:SS</li>'));
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should not render the placeholders when widget\'s `aposPlaceholder` doc field is `false`', function() {
|
|
239
|
+
assert(result.includes('<li>widget2 - aposPlaceholder: false</li>'));
|
|
240
|
+
assert(!result.includes('<li>widget2 - string: String PLACEHOLDER</li>'));
|
|
241
|
+
assert(!result.includes('<li>widget2 - integer: 0</li>'));
|
|
242
|
+
assert(!result.includes('<li>widget2 - float: 0.1</li>'));
|
|
243
|
+
assert(!result.includes('<li>widget2 - date: YYYY-MM-DD</li>'));
|
|
244
|
+
assert(!result.includes('<li>widget2 - time: HH:MM:SS</li>'));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should not render the placeholders when widget\'s `aposPlaceholder` doc field is not defined', function() {
|
|
248
|
+
assert(!result.includes('<li>widget3 - string: String PLACEHOLDER</li>'));
|
|
249
|
+
assert(!result.includes('<li>widget3 - integer: 0</li>'));
|
|
250
|
+
assert(!result.includes('<li>widget3 - float: 0.1</li>'));
|
|
251
|
+
assert(!result.includes('<li>widget3 - date: YYYY-MM-DD</li>'));
|
|
252
|
+
assert(!result.includes('<li>widget3 - time: HH:MM:SS</li>'));
|
|
253
|
+
|
|
254
|
+
assert(result.includes('<li>widget4 - string: Some string</li>'));
|
|
255
|
+
assert(result.includes('<li>widget4 - integer: 2</li>'));
|
|
256
|
+
assert(result.includes('<li>widget4 - float: 2.2</li>'));
|
|
257
|
+
assert(result.includes('<li>widget4 - date: 2022-09-21</li>'));
|
|
258
|
+
assert(result.includes('<li>widget4 - time: 15:39:12</li>'));
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should not render the placeholders on preview mode', async function() {
|
|
262
|
+
// eslint-disable-next-line no-unused-vars
|
|
263
|
+
const { aposEdit, ...query } = req.query;
|
|
264
|
+
const _nonEditingReq = {
|
|
265
|
+
...req,
|
|
266
|
+
query
|
|
267
|
+
};
|
|
268
|
+
const args = getRenderArgs(_nonEditingReq, page);
|
|
269
|
+
const _result = await apos.modules['placeholder-page'].render(_nonEditingReq, 'page', args);
|
|
270
|
+
|
|
271
|
+
assert(!_result.includes('widget1'));
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const mediaWidgetTypeToAssertion = {
|
|
276
|
+
image: {
|
|
277
|
+
placeholderUrlOverride: '/modules/@apostrophecms/my-image-widget/placeholder.webp',
|
|
278
|
+
assertAposPlaceholderTrue(document) {
|
|
279
|
+
const imgNodes = document.querySelectorAll('img');
|
|
280
|
+
|
|
281
|
+
assert(imgNodes.length === 1);
|
|
282
|
+
assert(imgNodes[0].classList.contains('image-widget-placeholder'));
|
|
283
|
+
assert(imgNodes[0].alt === 'Image placeholder');
|
|
284
|
+
assert(imgNodes[0].src === '/apos-frontend/default/modules/@apostrophecms/image-widget/placeholder.jpg');
|
|
285
|
+
},
|
|
286
|
+
assertPreviewMode(document) {
|
|
287
|
+
const imgNodes = document.querySelectorAll('img');
|
|
288
|
+
|
|
289
|
+
assert(imgNodes.length === 0);
|
|
290
|
+
},
|
|
291
|
+
assertFalsyPlaceholderUrl(document) {
|
|
292
|
+
const imgNodes = document.querySelectorAll('img');
|
|
293
|
+
|
|
294
|
+
assert(imgNodes.length === 0);
|
|
295
|
+
},
|
|
296
|
+
assertPlaceholderUrlOverride(document) {
|
|
297
|
+
const imgNodes = document.querySelectorAll('img');
|
|
298
|
+
|
|
299
|
+
assert(imgNodes.length === 1);
|
|
300
|
+
assert(imgNodes[0].classList.contains('image-widget-placeholder'));
|
|
301
|
+
assert(imgNodes[0].alt === 'Image placeholder');
|
|
302
|
+
assert(imgNodes[0].src === '/apos-frontend/default/modules/@apostrophecms/my-image-widget/placeholder.webp');
|
|
303
|
+
}
|
|
59
304
|
},
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
]
|
|
80
|
-
|
|
305
|
+
video: {
|
|
306
|
+
placeholderUrlOverride: 'https://vimeo.com/57946935',
|
|
307
|
+
assertAposPlaceholderTrue(document) {
|
|
308
|
+
const videoWrapperNodes = document.querySelectorAll('[data-apos-video-widget]');
|
|
309
|
+
|
|
310
|
+
assert(videoWrapperNodes.length === 1);
|
|
311
|
+
assert(videoWrapperNodes[0].dataset.aposVideoUrl === 'https://youtu.be/Q5UX9yexEyM');
|
|
312
|
+
},
|
|
313
|
+
assertPreviewMode(document) {
|
|
314
|
+
const videoWrapperNodes = document.querySelectorAll('[data-apos-video-widget]');
|
|
315
|
+
|
|
316
|
+
assert(videoWrapperNodes.length === 0);
|
|
317
|
+
},
|
|
318
|
+
assertFalsyPlaceholderUrl(document) {
|
|
319
|
+
const videoWrapperNodes = document.querySelectorAll('[data-apos-video-widget]');
|
|
320
|
+
|
|
321
|
+
assert(videoWrapperNodes.length === 0);
|
|
322
|
+
},
|
|
323
|
+
assertPlaceholderUrlOverride(document) {
|
|
324
|
+
const videoWrapperNodes = document.querySelectorAll('[data-apos-video-widget]');
|
|
325
|
+
|
|
326
|
+
assert(videoWrapperNodes.length === 1);
|
|
327
|
+
assert(videoWrapperNodes[0].dataset.aposVideoUrl === 'https://vimeo.com/57946935');
|
|
81
328
|
}
|
|
82
329
|
}
|
|
83
|
-
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
Object.entries(mediaWidgetTypeToAssertion).forEach(([
|
|
333
|
+
type,
|
|
334
|
+
{
|
|
335
|
+
placeholderUrlOverride,
|
|
336
|
+
assertAposPlaceholderTrue,
|
|
337
|
+
assertPreviewMode,
|
|
338
|
+
assertFalsyPlaceholderUrl,
|
|
339
|
+
assertPlaceholderUrlOverride
|
|
340
|
+
}
|
|
341
|
+
]) => {
|
|
342
|
+
describe(`${type} widget`, function() {
|
|
343
|
+
const widgetBaseData = {
|
|
344
|
+
metaType: 'widget',
|
|
345
|
+
type: `@apostrophecms/${type}`
|
|
346
|
+
};
|
|
347
|
+
const widgets = [
|
|
348
|
+
{
|
|
349
|
+
_id: 'widget1',
|
|
350
|
+
...widgetBaseData,
|
|
351
|
+
aposPlaceholder: true
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
_id: 'widget2',
|
|
355
|
+
...widgetBaseData,
|
|
356
|
+
aposPlaceholder: false
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
_id: 'widget3',
|
|
360
|
+
...widgetBaseData
|
|
361
|
+
}
|
|
362
|
+
];
|
|
84
363
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
...item,
|
|
88
|
-
aposLocale: item.aposLocale.replace(':published', ':draft'),
|
|
89
|
-
_id: item._id.replace(':published', ':draft')
|
|
90
|
-
})));
|
|
91
|
-
assert(draftItems.result.ok === 1);
|
|
92
|
-
assert(draftItems.insertedCount === 2);
|
|
364
|
+
let page;
|
|
365
|
+
let result;
|
|
93
366
|
|
|
94
|
-
|
|
367
|
+
before(async function() {
|
|
368
|
+
await insertPage(apos, homePath, widgets);
|
|
369
|
+
page = await apos.page.find(req, { slug: '/placeholder-page' }).toObject();
|
|
95
370
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
371
|
+
const args = getRenderArgs(req, page);
|
|
372
|
+
result = await apos.modules['placeholder-page'].render(req, 'page', args);
|
|
373
|
+
});
|
|
99
374
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const goodPageDoc = await apos.page.find(req, { slug: '/good-page' })
|
|
104
|
-
.toObject();
|
|
105
|
-
goodPageDoc.metaType = 'doc';
|
|
106
|
-
|
|
107
|
-
const args = {
|
|
108
|
-
outerLayout: '@apostrophecms/template:outerLayout.html',
|
|
109
|
-
permissions: req.user && (req.user._permissions || {}),
|
|
110
|
-
scene: 'apos',
|
|
111
|
-
refreshing: false,
|
|
112
|
-
query: req.query,
|
|
113
|
-
url: req.url,
|
|
114
|
-
page: goodPageDoc
|
|
115
|
-
};
|
|
375
|
+
after(async function() {
|
|
376
|
+
await deletePage(apos, page);
|
|
377
|
+
});
|
|
116
378
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
result = await apos.modules['args-good-page'].render(req, 'page', args);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
assert(false);
|
|
122
|
-
}
|
|
379
|
+
it('should render the placeholder only when widget\'s `aposPlaceholder` doc field is `true`', function() {
|
|
380
|
+
const { document } = new JSDOM(result).window;
|
|
123
381
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
assert(result.indexOf('<li>color: 🟣</li>') !== -1);
|
|
127
|
-
});
|
|
382
|
+
assertAposPlaceholderTrue(document);
|
|
383
|
+
});
|
|
128
384
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
385
|
+
it('should not render the placeholders on preview mode', async function() {
|
|
386
|
+
// eslint-disable-next-line no-unused-vars
|
|
387
|
+
const { aposEdit, ...query } = req.query;
|
|
388
|
+
const _nonEditingReq = {
|
|
389
|
+
...req,
|
|
390
|
+
query
|
|
391
|
+
};
|
|
392
|
+
const args = getRenderArgs(_nonEditingReq, page);
|
|
393
|
+
const _result = await apos.modules['placeholder-page'].render(_nonEditingReq, 'page', args);
|
|
394
|
+
|
|
395
|
+
const { document } = new JSDOM(_result).window;
|
|
396
|
+
assertPreviewMode(document);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('placeholderUrl - falsy', function() {
|
|
400
|
+
let _apos;
|
|
401
|
+
let _page;
|
|
402
|
+
let _result;
|
|
403
|
+
|
|
404
|
+
before(async function() {
|
|
405
|
+
// Recreate local apos instance with falsy `placeholderUrl` option set to widget module
|
|
406
|
+
_apos = await t.create({
|
|
407
|
+
root: module,
|
|
408
|
+
modules: {
|
|
409
|
+
'placeholder-page': {},
|
|
410
|
+
[`@apostrophecms/${type}-widget`]: {
|
|
411
|
+
options: {
|
|
412
|
+
placeholderUrl: null
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
const _req = _apos.task.getAnonReq({
|
|
418
|
+
query: {
|
|
419
|
+
aposEdit: '1'
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const home = await _apos.page.find(_req, { level: 0 }).toObject();
|
|
424
|
+
const _homePath = home._id.replace(':en:published', '');
|
|
425
|
+
|
|
426
|
+
await insertPage(_apos, _homePath, widgets);
|
|
427
|
+
_page = await _apos.page.find(_req, { slug: '/placeholder-page' }).toObject();
|
|
145
428
|
|
|
146
|
-
|
|
147
|
-
|
|
429
|
+
const args = getRenderArgs(_req, _page);
|
|
430
|
+
_result = await _apos.modules['placeholder-page'].render(_req, 'page', args);
|
|
431
|
+
});
|
|
148
432
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
433
|
+
after(async function() {
|
|
434
|
+
await deletePage(_apos, _page);
|
|
435
|
+
await t.destroy(_apos);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('should not render the placeholder when widget\'s module `placeholderUrl` option is falsy', function() {
|
|
439
|
+
const { document } = new JSDOM(_result).window;
|
|
440
|
+
|
|
441
|
+
assertFalsyPlaceholderUrl(document);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe('placeholderUrl - override', function() {
|
|
446
|
+
let _apos;
|
|
447
|
+
let _page;
|
|
448
|
+
let _result;
|
|
449
|
+
|
|
450
|
+
before(async function() {
|
|
451
|
+
// Recreate local apos instance with falsy `placeholderUrl` option set to widget module
|
|
452
|
+
_apos = await t.create({
|
|
453
|
+
root: module,
|
|
454
|
+
modules: {
|
|
455
|
+
'placeholder-page': {},
|
|
456
|
+
[`@apostrophecms/${type}-widget`]: {
|
|
457
|
+
options: {
|
|
458
|
+
placeholderUrl: placeholderUrlOverride
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
const _req = _apos.task.getAnonReq({
|
|
464
|
+
query: {
|
|
465
|
+
aposEdit: '1'
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const home = await _apos.page.find(_req, { level: 0 }).toObject();
|
|
470
|
+
const _homePath = home._id.replace(':en:published', '');
|
|
471
|
+
|
|
472
|
+
await insertPage(_apos, _homePath, widgets);
|
|
473
|
+
_page = await _apos.page.find(_req, { slug: '/placeholder-page' }).toObject();
|
|
474
|
+
|
|
475
|
+
const args = getRenderArgs(_req, _page);
|
|
476
|
+
_result = await _apos.modules['placeholder-page'].render(_req, 'page', args);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
after(async function() {
|
|
480
|
+
await deletePage(_apos, _page);
|
|
481
|
+
await t.destroy(_apos);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('should render the placeholder set to the widget\'s module `placeholderUrl` override', function() {
|
|
485
|
+
const { document } = new JSDOM(_result).window;
|
|
486
|
+
|
|
487
|
+
assertPlaceholderUrlOverride(document);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
153
492
|
});
|
|
154
493
|
});
|