From 0502b8cfb46804ed80ee78c1f749d3828b28a2b1 Mon Sep 17 00:00:00 2001 From: ETLaurent Date: Wed, 28 Sep 2022 15:29:26 +0200 Subject: [PATCH] display placeholder for rich-text-widget (#3888) --- modules/@apostrophecms/i18n/i18n/en.json | 1 + modules/@apostrophecms/i18n/i18n/es.json | 1 + modules/@apostrophecms/i18n/i18n/fr.json | 1 + modules/@apostrophecms/i18n/i18n/pt-BR.json | 1 + modules/@apostrophecms/i18n/i18n/sk.json | 1 + .../@apostrophecms/rich-text-widget/index.js | 4 +- .../components/AposRichTextWidgetEditor.vue | 84 ++++++++++++++++--- package.json | 9 +- 8 files changed, 86 insertions(+), 16 deletions(-) diff --git a/modules/@apostrophecms/i18n/i18n/en.json b/modules/@apostrophecms/i18n/i18n/en.json index 3611e1c773..41b5dad453 100644 --- a/modules/@apostrophecms/i18n/i18n/en.json +++ b/modules/@apostrophecms/i18n/i18n/en.json @@ -263,6 +263,7 @@ "richTextItalic": "Italic", "richTextLink": "Link", "richTextParagraph": "Paragraph (P)", + "richTextPlaceholder": "Start Typing Here...", "richTextH2": "Heading 2 (H2)", "richTextH3": "Heading 3 (H3)", "richTextH4": "Heading 4 (H4)", diff --git a/modules/@apostrophecms/i18n/i18n/es.json b/modules/@apostrophecms/i18n/i18n/es.json index dc670f239f..114e7e225a 100644 --- a/modules/@apostrophecms/i18n/i18n/es.json +++ b/modules/@apostrophecms/i18n/i18n/es.json @@ -242,6 +242,7 @@ "richTextItalic": "Cursiva", "richTextLink": "Liga", "richTextParagraph": "Párrafo (P)", + "richTextPlaceholder": "Comience a escribir aquí...", "richTextH2": "Título 2 (H2)", "richTextH3": "Título 3 (H3)", "richTextH4": "Título 4 (H4)", diff --git a/modules/@apostrophecms/i18n/i18n/fr.json b/modules/@apostrophecms/i18n/i18n/fr.json index 726d6da0b4..ed2694f783 100644 --- a/modules/@apostrophecms/i18n/i18n/fr.json +++ b/modules/@apostrophecms/i18n/i18n/fr.json @@ -249,6 +249,7 @@ "richTextItalic": "Italique", "richTextLink": "Lien", "richTextParagraph": "Paragraphe (P)", + "richTextPlaceholder": "Commencez à écrire ici...", "richTextH2": "Titre de niveau 2 (H2)", "richTextH3": "Titre de niveau 3 (H3)", "richTextH4": "Titre de niveau 4 (H4)", diff --git a/modules/@apostrophecms/i18n/i18n/pt-BR.json b/modules/@apostrophecms/i18n/i18n/pt-BR.json index bf770b1a30..70c23ba0fc 100644 --- a/modules/@apostrophecms/i18n/i18n/pt-BR.json +++ b/modules/@apostrophecms/i18n/i18n/pt-BR.json @@ -240,6 +240,7 @@ "richTextItalic": "Itálico", "richTextLink": "Link", "richTextParagraph": "Parágrafo (P)", + "richTextPlaceholder": "Comece a digitar aqui...", "richTextH2": "Título 2 (H2)", "richTextH3": "Título 3 (H3)", "richTextH4": "Título 4 (H4)", diff --git a/modules/@apostrophecms/i18n/i18n/sk.json b/modules/@apostrophecms/i18n/i18n/sk.json index a7ba795d1d..4019a1594d 100644 --- a/modules/@apostrophecms/i18n/i18n/sk.json +++ b/modules/@apostrophecms/i18n/i18n/sk.json @@ -252,6 +252,7 @@ "richTextItalic": "Kurzíva", "richTextLink": "Link", "richTextParagraph": "Odstavec (P)", + "richTextPlaceholder": "Začnite písať tu...", "richTextH2": "Nadpis 2 (H2)", "richTextH3": "Nadpis 3 (H3)", "richTextH4": "Nadpis 4 (H4)", diff --git a/modules/@apostrophecms/rich-text-widget/index.js b/modules/@apostrophecms/rich-text-widget/index.js index 863e8a8ffe..f85c707f10 100644 --- a/modules/@apostrophecms/rich-text-widget/index.js +++ b/modules/@apostrophecms/rich-text-widget/index.js @@ -9,6 +9,7 @@ module.exports = { icon: 'format-text-icon', label: 'apostrophe:richText', contextual: true, + placeholderText: 'apostrophe:richTextPlaceholder', defaultData: { content: '' }, className: false, minimumDefaultOptions: { @@ -417,7 +418,8 @@ module.exports = { tools: self.options.editorTools, defaultOptions: self.options.defaultOptions, tiptapTextCommands: self.options.tiptapTextCommands, - tiptapTypes: self.options.tiptapTypes + tiptapTypes: self.options.tiptapTypes, + placeholderText: self.options.placeholderText }; return finalData; } diff --git a/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue b/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue index 8ed9002c32..eacef9f22c 100644 --- a/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +++ b/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue @@ -28,7 +28,10 @@
-
+
{{ $t('apostrophe:emptyRichTextWidget') }}
@@ -45,6 +48,8 @@ import TextAlign from '@tiptap/extension-text-align'; import Highlight from '@tiptap/extension-highlight'; import TextStyle from '@tiptap/extension-text-style'; import Underline from '@tiptap/extension-underline'; +import Placeholder from '@tiptap/extension-placeholder'; + export default { name: 'AposRichTextWidgetEditor', components: { @@ -88,7 +93,9 @@ export default { }, hasErrors: false }, - pending: null + pending: null, + isFocused: null, + showPlaceholder: null }; }, computed: { @@ -158,6 +165,9 @@ export default { tiptapTypes() { return this.moduleOptions.tiptapTypes; }, + placeholderText() { + return this.moduleOptions.placeholderText; + }, aposTiptapExtensions() { return (apos.tiptapExtensions || []) .map(extension => extension({ @@ -176,19 +186,63 @@ export default { } }, mounted() { + const extensions = [ + StarterKit, + TextAlign.configure({ + types: [ 'heading', 'paragraph' ] + }), + Highlight, + TextStyle, + Underline, + + // For this contextual widget, no need to check `widget.aposPlaceholder` value + // since `placeholderText` option is enough to decide whether to display it or not. + this.placeholderText && Placeholder.configure({ + placeholder: () => { + // Avoid brief display of the placeholder when loading the page. + if (this.isFocused === null) { + return ''; + } + + // Display placeholder after loading the page. + if (this.showPlaceholder === null) { + return this.$t(this.placeholderText); + } + + return this.showPlaceholder ? this.$t(this.placeholderText) : ''; + } + }) + ] + .filter(Boolean) + .concat(this.aposTiptapExtensions); + this.editor = new Editor({ content: this.initialContent, autofocus: this.autofocus, onUpdate: this.editorUpdate, - extensions: [ - StarterKit, - TextAlign.configure({ - types: [ 'heading', 'paragraph' ] - }), - Highlight, - TextStyle, - Underline - ].concat(this.aposTiptapExtensions) + extensions, + + // The following events are triggered: + // - before the placeholder configuration function, when loading the page + // - after it, once the page is loaded and we interact with the editors + // To solve this issue, use another `this.showPlaceholder` variable + // and toggle it after the placeholder configuration function is called, + // thanks to nextTick. + // The proper thing would be to call nextTick inside the placeholder + // function so that it can rely on the focus state set by these event + // listeners, but the placeholder function is called synchronously... + onFocus: () => { + this.isFocused = true; + this.$nextTick(() => { + this.showPlaceholder = false; + }); + }, + onBlur: () => { + this.isFocused = false; + this.$nextTick(() => { + this.showPlaceholder = true; + }); + } }); }, @@ -336,6 +390,14 @@ export default { outline: none; } + .apos-rich-text-editor__editor ::v-deep .ProseMirror p.is-empty:first-child::before { + content: attr(data-placeholder); + float: left; + pointer-events: none; + height: 0; + color: var(--a-base-4); + } + .apos-rich-text-editor__editor { @include apos-transition(); position: relative; diff --git a/package.json b/package.json index a279ca9e30..352a99658d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@opentelemetry/semantic-conventions": "^1.0.1", "@tiptap/extension-highlight": "^2.0.0-beta.33", "@tiptap/extension-link": "^2.0.0-beta.38", + "@tiptap/extension-placeholder": "^2.0.0-beta.196", "@tiptap/extension-text-align": "^2.0.0-beta.29", "@tiptap/extension-text-style": "^2.0.0-beta.23", "@tiptap/extension-underline": "^2.0.0-beta.23", @@ -124,16 +125,16 @@ "eslint-config-apostrophe": "^3.4.0", "eslint-plugin-n": "^15.2.1", "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", "eslint-plugin-vue": "^7.9.0", "mocha": "^9.1.2", "nyc": "^15.1.0", "replace-in-file": "^6.1.0", - "vue-eslint-parser": "^7.1.1", - "webpack-bundle-analyzer": "^3.9.0", - "eslint-plugin-promise": "^5.1.0", "stylelint": "^14.6.1", "stylelint-declaration-strict-value": "^1.8.0", - "stylelint-order": "^5.0.0" + "stylelint-order": "^5.0.0", + "vue-eslint-parser": "^7.1.1", + "webpack-bundle-analyzer": "^3.9.0" }, "browserslist": [ "ie >= 10"