diff --git a/packages/dialtone-vue2/components/rich_text_editor/extensions/image/image.js b/packages/dialtone-vue2/components/rich_text_editor/extensions/image/image.js new file mode 100644 index 0000000000..f1cf16ce45 --- /dev/null +++ b/packages/dialtone-vue2/components/rich_text_editor/extensions/image/image.js @@ -0,0 +1,28 @@ +import Image from '@tiptap/extension-image'; + +export const ConfigurableImage = Image.extend({ + name: 'ConfigurableImage', + + addAttributes () { + return { + src: { + default: '', + }, + alt: { + default: undefined, + }, + title: { + default: undefined, + }, + width: { + default: undefined, + }, + height: { + default: undefined, + }, + style: { + default: undefined, + }, + }; + }, +}).configure({ inline: true, allowBase64: true }); diff --git a/packages/dialtone-vue2/components/rich_text_editor/extensions/image/index.js b/packages/dialtone-vue2/components/rich_text_editor/extensions/image/index.js new file mode 100644 index 0000000000..36fb4e9202 --- /dev/null +++ b/packages/dialtone-vue2/components/rich_text_editor/extensions/image/index.js @@ -0,0 +1,3 @@ +import { ConfigurableImage } from './image'; + +export default ConfigurableImage; diff --git a/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.test.js b/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.test.js index f2afe81e58..9744ee6656 100644 --- a/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.test.js +++ b/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.test.js @@ -8,6 +8,7 @@ const baseProps = { value: 'initial value', inputAriaLabel: 'aria-label text', inputClass: 'qa-editor', + allowInlineImages: true, }; const baseListeners = { input: MOCK_INPUT_STUB, @@ -22,6 +23,11 @@ describe('DtRichTextEditor tests', () => { let editor; let editorEl; + const _setValue = async (value) => { + editorEl.innerHTML = value; + await wrapper.vm.$nextTick(); + }; + const updateWrapper = async () => { editorEl?.remove(); wrapper = mount(DtRichTextEditor, { @@ -68,19 +74,26 @@ describe('DtRichTextEditor tests', () => { describe('Reactivity Tests', () => { describe('User Input Tests', () => { describe('When user inputs a value', () => { - describe('When using text output', () => { + // Shared Examples + const itBehavesLikeOutputsCorrectly = (value, output, onlyCheckOutputContained = false) => { it('should emit the output value', async () => { - await wrapper.setProps({ outputFormat: 'text' }); - - editorEl = document.getElementsByClassName('qa-editor')[0]; - - editorEl.innerHTML = 'new value'; - - await wrapper.vm.$nextTick(); - - expect(wrapper.emitted().input[0][0]).toBe('new value'); + await _setValue(value); + const emittedOutput = wrapper.emitted().input[0][0]; + if (onlyCheckOutputContained) { + expect(emittedOutput).toContain(output); + } else { + expect(emittedOutput).toEqual(output); + } expect(MOCK_INPUT_STUB).toHaveBeenCalled(); }); + }; + + describe('When using text output', () => { + beforeEach(async function () { + await wrapper.setProps({ outputFormat: 'text' }); + }); + + itBehavesLikeOutputsCorrectly('new value', 'new value'); }); describe('When using json output', () => { @@ -98,31 +111,25 @@ describe('DtRichTextEditor tests', () => { }], }; - it('should emit the output value', async () => { + beforeEach(async function () { await wrapper.setProps({ outputFormat: 'json' }); - - editorEl = document.getElementsByClassName('qa-editor')[0]; - editorEl.innerHTML = 'new value'; - - await wrapper.vm.$nextTick(); - - expect(wrapper.emitted().input[0][0]).toEqual(MOCK_JSON_OUTPUT); - expect(MOCK_INPUT_STUB).toHaveBeenCalled(); }); + + itBehavesLikeOutputsCorrectly('new value', MOCK_JSON_OUTPUT); }); describe('When using html output', () => { - it('should emit the output value', async () => { + beforeEach(async function () { await wrapper.setProps({ outputFormat: 'html' }); + }); - editorEl = document.getElementsByClassName('qa-editor')[0]; - editorEl.innerHTML = 'new value'; + itBehavesLikeOutputsCorrectly('new value', '

new value

'); - await wrapper.vm.$nextTick(); + const htmlWithImgTag = 'image '; - expect(wrapper.emitted().input[0][0]).toBe('

new value

'); - expect(MOCK_INPUT_STUB).toHaveBeenCalled(); - }); + itBehavesLikeOutputsCorrectly(htmlWithImgTag, 'height="100px"', true); + itBehavesLikeOutputsCorrectly(htmlWithImgTag, 'width="200px"', true); + itBehavesLikeOutputsCorrectly(htmlWithImgTag, 'src="http://someimgurl.com"', true); }); }); }); diff --git a/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.vue b/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.vue index 91b69c8ae6..eae6dc3ab3 100644 --- a/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.vue +++ b/packages/dialtone-vue2/components/rich_text_editor/rich_text_editor.vue @@ -20,7 +20,6 @@ import Placeholder from '@tiptap/extension-placeholder'; import HardBreak from '@tiptap/extension-hard-break'; import Bold from '@tiptap/extension-bold'; import BulletList from '@tiptap/extension-bullet-list'; -import Image from '@tiptap/extension-image'; import Italic from '@tiptap/extension-italic'; import TipTapLink from '@tiptap/extension-link'; import ListItem from '@tiptap/extension-list-item'; @@ -32,6 +31,7 @@ import TextAlign from '@tiptap/extension-text-align'; import History from '@tiptap/extension-history'; import Emoji from './extensions/emoji'; import CustomLink from './extensions/custom_link'; +import ConfigurableImage from './extensions/image'; import { MentionPlugin } from './extensions/mentions/mention'; import { ChannelPlugin } from './extensions/channels/channel'; import { SlashCommandPlugin } from './extensions/slash_command/slash_command'; @@ -465,7 +465,7 @@ export default { } if (this.allowInlineImages) { - extensions.push(Image.configure({ inline: true })); + extensions.push(ConfigurableImage); } if (this.additionalExtensions.length) { diff --git a/packages/dialtone-vue3/components/rich_text_editor/extensions/image/image.js b/packages/dialtone-vue3/components/rich_text_editor/extensions/image/image.js new file mode 100644 index 0000000000..f1cf16ce45 --- /dev/null +++ b/packages/dialtone-vue3/components/rich_text_editor/extensions/image/image.js @@ -0,0 +1,28 @@ +import Image from '@tiptap/extension-image'; + +export const ConfigurableImage = Image.extend({ + name: 'ConfigurableImage', + + addAttributes () { + return { + src: { + default: '', + }, + alt: { + default: undefined, + }, + title: { + default: undefined, + }, + width: { + default: undefined, + }, + height: { + default: undefined, + }, + style: { + default: undefined, + }, + }; + }, +}).configure({ inline: true, allowBase64: true }); diff --git a/packages/dialtone-vue3/components/rich_text_editor/extensions/image/index.js b/packages/dialtone-vue3/components/rich_text_editor/extensions/image/index.js new file mode 100644 index 0000000000..36fb4e9202 --- /dev/null +++ b/packages/dialtone-vue3/components/rich_text_editor/extensions/image/index.js @@ -0,0 +1,3 @@ +import { ConfigurableImage } from './image'; + +export default ConfigurableImage; diff --git a/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.test.js b/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.test.js index 9d89871a17..a1e4a18e7f 100644 --- a/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.test.js +++ b/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.test.js @@ -19,6 +19,7 @@ const baseProps = { modelValue: 'initial value', inputAriaLabel: 'aria-label text', inputClass: 'qa-editor', + allowInlineImages: true, }; // Helpers @@ -86,10 +87,15 @@ describe('DtRichTextEditor tests', () => { describe('User Input Tests', function () { describe('When user inputs a value', function () { // Shared Examples - const itBehavesLikeOutputsCorrectly = (value, output) => { + const itBehavesLikeOutputsCorrectly = (value, output, onlyCheckOutputContained = false) => { it('should emit the output value', async () => { await _setValue(value); - expect(wrapper.emitted().input[0][0]).toEqual(output); + const emittedOutput = wrapper.emitted().input[0][0]; + if (onlyCheckOutputContained) { + expect(emittedOutput).toContain(output); + } else { + expect(emittedOutput).toEqual(output); + } expect(inputStub).toHaveBeenCalled(); }); }; @@ -134,6 +140,12 @@ describe('DtRichTextEditor tests', () => { }); itBehavesLikeOutputsCorrectly('new value', '

new value

'); + + const htmlWithImgTag = 'image '; + + itBehavesLikeOutputsCorrectly(htmlWithImgTag, 'height="100px"', true); + itBehavesLikeOutputsCorrectly(htmlWithImgTag, 'width="200px"', true); + itBehavesLikeOutputsCorrectly(htmlWithImgTag, 'src="http://someimgurl.com"', true); }); }); }); diff --git a/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.vue b/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.vue index 806547abe4..7522411109 100644 --- a/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.vue +++ b/packages/dialtone-vue3/components/rich_text_editor/rich_text_editor.vue @@ -16,7 +16,6 @@ import Blockquote from '@tiptap/extension-blockquote'; import CodeBlock from '@tiptap/extension-code-block'; import Document from '@tiptap/extension-document'; import HardBreak from '@tiptap/extension-hard-break'; -import Image from '@tiptap/extension-image'; import Paragraph from '@tiptap/extension-paragraph'; import Placeholder from '@tiptap/extension-placeholder'; import Bold from '@tiptap/extension-bold'; @@ -32,6 +31,7 @@ import TextAlign from '@tiptap/extension-text-align'; import History from '@tiptap/extension-history'; import Emoji from './extensions/emoji'; import CustomLink from './extensions/custom_link'; +import ConfigurableImage from './extensions/image'; import { MentionPlugin } from './extensions/mentions/mention'; import { ChannelPlugin } from './extensions/channels/channel'; import { SlashCommandPlugin } from './extensions/slash_command/slash_command'; @@ -465,7 +465,7 @@ export default { } if (this.allowInlineImages) { - extensions.push(Image.configure({ inline: true })); + extensions.push(ConfigurableImage); } if (this.additionalExtensions.length) { diff --git a/packages/dialtone-vue3/recipes/conversation_view/editor/editor_default.story.vue b/packages/dialtone-vue3/recipes/conversation_view/editor/editor_default.story.vue index 14a4217eb2..9d175828a4 100644 --- a/packages/dialtone-vue3/recipes/conversation_view/editor/editor_default.story.vue +++ b/packages/dialtone-vue3/recipes/conversation_view/editor/editor_default.story.vue @@ -2,7 +2,7 @@

Editor content is:

- {{ modelValue }} + {{ value }}
@@ -46,7 +46,7 @@ export default { components: { DtRecipeEditor }, data () { return { - modelValue: this.$attrs.modelValue, + value: this.$attrs.value, }; }, };