Svg icon not showing in toast-ui vue image editor - vue.js

I am using vue-cliand toast-ui-vue-image-editor.
// vue.config.js
const path = require('path')
let HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
chainWebpack: config => {
config.module
.rule('svg')
.use('file-loader')
.options({
name: '[name].[ext]',
outputPath: ''
})
}
And added these line in Vue compoenent
import 'tui-image-editor/dist/svg/icon-a.svg'
import 'tui-image-editor/dist/svg/icon-b.svg'
import 'tui-image-editor/dist/svg/icon-c.svg'
import 'tui-image-editor/dist/svg/icon-d.svg'
import { ImageEditor } from '#toast-ui/vue-image-editor'
Everything is working but editor's tool Svg icon is not showing. See most bottom section of editor where white square showing instead of icons (undo ,redo, crop etc.)

I give my answer to this hoping this will help someone in the future.
I too faced this issue and solved issue with answer from this link
Here is my script section!
import 'tui-image-editor/dist/tui-image-editor.css'
import ImageEditor from '#toast-ui/vue-image-editor/src/ImageEditor.vue'
export default {
name: 'ToastUI',
components: {
'image-editor': ImageEditor
},
data () {
const icona = require('tui-image-editor/dist/svg/icon-a.svg')
const iconb = require('tui-image-editor/dist/svg/icon-b.svg')
const iconc = require('tui-image-editor/dist/svg/icon-c.svg')
const icond = require('tui-image-editor/dist/svg/icon-d.svg')
var whiteTheme = {
'menu.normalIcon.path': icond,
'menu.activeIcon.path': iconb,
'menu.disabledIcon.path': icona,
'menu.hoverIcon.path': iconc,
'submenu.normalIcon.path': icond,
'submenu.activeIcon.path': iconb,
'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
'common.bisize.width': '251px',
'common.bisize.height': '21px',
'common.backgroundImage': './img/bg.png',
'common.backgroundColor': '#fff',
'common.border': '1px solid #c1c1c1',
}
return {
useDefaultUI: true,
options: {
includeUI: {
loadImage: {
path: '',
name: ''
},
theme: whiteTheme,
initMenu: '',
menuBarPosition: 'bottom',
menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask'], //, 'filter',
},
cssMaxWidth: document.documentElement.clientWidth,
cssMaxHeight: document.documentElement.clientHeight,
selectionStyle: {
cornerSize: 20,
rotatingPointOffset: 70
}
}
}
}
}

Related

TipTap Vue custom component selects the entire line, not the selected one

I will immediately introduce the custom extension Tag.js
import { mergeAttributes, Node } from "#tiptap/core";
import { VueNodeViewRenderer } from "#tiptap/vue-3";
import { markInputRule } from "#tiptap/core";
import { markPasteRule } from "#tiptap/core";
import Component from "~/components/Editor/Tag.vue";
const starInputRegex = /(?:^|\s)((?:\*)((?:[^*]+))(?:\*))$/;
const starPasteRegex = /(?:^|\s)((?:\*)((?:[^*]+))(?:\*))/g;
const underscoreInputRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))$/;
const underscorePasteRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))/g;
export default Node.create({
name: "vuetag",
group: "block",
content: "inline*",
selectable: true,
parseHTML() {
return [
{
tag: "tag",
},
];
},
renderHTML({ HTMLAttributes }) {
return ["tag", mergeAttributes(HTMLAttributes), 0];
},
addNodeView() {
return VueNodeViewRenderer(Component);
},
addInputRules() {
return [
markInputRule({
find: starInputRegex,
type: this.type,
}),
markInputRule({
find: underscoreInputRegex,
type: this.type,
}),
];
},
addPasteRules() {
return [
markPasteRule({
find: starPasteRegex,
type: this.type,
}),
markPasteRule({
find: underscorePasteRegex,
type: this.type,
}),
];
},
addCommands() {
return {
setTag:
() =>
({ commands }) => {
return commands.setNode(this.name);
},
};
},
});
Component Tag.vue
<template>
<node-view-wrapper>
<el-tag><node-view-content /></el-tag>
</node-view-wrapper>
</template>
<script>
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from "#tiptap/vue-3";
export default {
components: {
NodeViewWrapper,
NodeViewContent,
},
props: nodeViewProps,
};
</script>
<style lang="scss"></style>
There is a text: Did you see that? That’s a Vue component. We are really living in the future.
Let's say I want the phrase Did you see that? specify as a tag. I highlight this phrase and click on the button, the event setTag() is triggered
The result I get is this:<tag>Did you see that? That’s a Vue component. We are really living in the future.</tag>
The problem is that here the whole one line becomes a tag, that is, inside the Tag component.Vue
And there should be such a result: <tag>Did you see that?</tag> That’s a Vue component. We are really living in the future.
As an el-tag, I took from https://element-plus.org/en-US/component/tag.html

Inserting image url in quill only displays the image icon and not the image itself

I am using quill in Next.js and I have ran into a problem. Instead of using the default base64 encoding for images, I have added a custom handler that creates an object url for the images and I later pass it to the quill.insertEmbed() method. However, once I go to the browser, quill only displays an image icon like the one displayed by alt. I have tried to use the blotFormatter module but it still does not work. Honestly I do not know how blotFormatter works. Please help.
Here is my code
`import React, { useEffect, useState, useRef } from 'react';
import dynamic from 'next/dynamic';
import { useQuill } from 'react-quilljs';
// const BlotFormatter =
// typeof window === 'object'
// ? require('quill-blot-formatter')
// : () => false;
const BlotFormatter = dynamic(
() => {
import('quill-blot-formatter');
},
{ ssr: false, loading: () => <p>Text Editor Loading...</p> }
);
const TOOLBAR_OPTIONS = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ font: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['bold', 'italic', 'underline', 'strike'],
[{ indent: '-1' }, { indent: '+1' }, { align: [] }],
[{ color: [] }, { background: [] }],
[{ script: 'sub' }, { script: 'super' }],
[{ align: [] }],
['image', 'blockquote', 'code-block'],
['clean'],
];
const QuillEditor = () => {
const { quill, quillRef, Quill } = useQuill({
modules: {
toolbar: {
container: TOOLBAR_OPTIONS,
},
},
blotFormatter: {},
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
},
theme: 'snow',
placeholder: 'Describe your question',
});
const openInputImageRef = useRef();
if (Quill && !quill) {
Quill.register('modules/blotFormatter', BlotFormatter);
}
useEffect(() => {
if (quill) {
quill.getModule('toolbar').addHandler('image', imageHandler);
quill.getModule('blotFormatter');
}
}, [quill, Quill]);
const imageHandler = () => {
openInputImageRef.current.click();
};
// handle image insertions
const quillImageCallback = (e) => {
e.preventDefault();
e.stopPropagation();
if (
e.currentTarget &&
e.currentTarget.files &&
e.currentTarget.files.length > 0
) {
const file = e.currentTarget.files[0];
let formData = new FormData();
formData.append(file.name, file);
// create an objectUrl for the file
const url = URL.createObjectURL(file);
console.log(url);
// display the image on quill
quill.focus();
let range = quill.getSelection();
let position = range ? range.index : 0;
console.log('position....', position);
quill.insertEmbed(
position,
'image',
url,
Quill.sources.USER
);
quill.setSelection(position + 1);
}
};`
return (
<div>
<div ref={quillRef} />
<div>
<input
type="file"
accept="image/*"
ref={openInputImageRef}
onChange={(e) => quillImageCallback(e)}
className="hidden"
/>
</div>
</div>
);
};
export default QuillEditor;
I am currently working on a project that works like stack overflow. In the project people can ask questions which have titles, describe their questions including posting images and add tags for topical targeting. For the rich text editor, I chose Quill. Quill handles image uploads by converting images to base 64. I however, prefer to handle images as formData objects. I tried to handle image uploads independently so that I could store them separately in a file system. After searching throughout the internet, I found ways to use the imageHandler in next js. In my imageHandler, I create an objectUrl using the URL.createObjectUrl() method and then insertEmbed it to quill for display to the user. However, quill only displays an image icon. Registering the blotFormatter module does not also work. I also admit that I do not understand how blotFormatter works exactly. I have seen some code snippets where people use Class components but it does not seem to work for me. Please advise me on how to go about this problem.
Quill displays an image icon

Why can I not edit in vue3 quill editor?

I am doing a test with the vue3 quill editor, this is what it currently looks like:
The quill editor gets displayed, but I cannot edit the text.
This is my code:
HelloWorld.vue
<template>
<quill-editor
v-model:value="state.content"
:options="state.editorOption"
:disabled="state.disabled"
#blur="onEditorBlur($event)"
#focus="onEditorFocus($event)"
#ready="onEditorReady($event)"
#change="onEditorChange($event)"
/>
</template>
<script>
import { reactive } from 'vue'
import { quillEditor } from 'vue3-quill'
export default {
name: 'App',
setup() {
const state = reactive({
content: '<p>2333</p>',
_content: '',
editorOption: {
placeholder: 'core',
modules: {
// toolbars: [
// custom toolbars options
// will override the default configuration
// ],
// other moudle options here
// otherMoudle: {}
},
// more options
},
disabled: false
})
const onEditorBlur = (quill) => {
console.log('editor blur!', quill)
}
const onEditorFocus = (quill) => {
console.log('editor focus!', quill)
}
const onEditorReady = (quill) => {
console.log('editor ready!', quill)
}
const onEditorChange = ({ quill, html, text }) => {
console.log('editor change!', quill, html, text)
state._content = html
}
setTimeout(() => {
state.disabled = true
}, 2000)
return { state, onEditorBlur, onEditorFocus, onEditorReady, onEditorChange }
},
components: {
quillEditor
}
}
</script>
Commands
These are the commands that I did to create the project.
vue create .
with default vue 3 options
npm i vue3-quill
I found the command and code here:
https://www.npmjs.com/package/vue3-quill
The lines:
setTimeout(() => {
state.disabled = true
}, 2000)
disable the editor.
Just remove them or exchange true for false

Vue storybook globalTypes not re-rendering preview

Hello all i am currently in the process to integrate Storybook-Vue into my own pattern-lib.
So far everything worked like a charm. Expect for one thing and that is adding a globalType in the preview.js and then using it inside a decorators. Registering the new Global type works, i see it in the toolbar but when i change the selected value it does not re-render the preview.
My first guess is that the context is not an observable object so Vue never knows when this object actually gets an update. But i am not sure how i could change this.
// preview.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n);
export const globalTypes = {
locale: {
name: 'Locale',
description: 'Internationalization locale',
defaultValue: 'en-US',
toolbar: {
icon: 'globe',
items: [
{ value: 'de-DE', right: '🇩🇪', title: 'German' },
{ value: 'en-US', right: '🇺🇸 ', title: 'English' },
{ value: 'cs-CZ', right: '🇨🇿', title: 'Czech' },
{ value: 'zh-CN', right: '🇨🇳', title: 'Chinese' },
],
},
},
};
const localeSelect = (story, context) => {
const wrapped = story(context)
const locale = context.globals.locale
return Vue.extend({
components: { wrapped, BiToast },
data () {
return {
locale
}
},
watch: {
locale: {
deep: true,
handler (val) {
this.$i18n.locale = val
}
}
},
template: `
<div>
{{ locale }}
<wrapped/>
</div>
`
})
}
export const decorators = [localeSelect]
Finally got it working. I just had to use useGlobals and set i18n outsite of the returned object.
import { useGlobals } from '#storybook/client-api';
const localeSelect = (story, context) => {
const [{locale}] = useGlobals();
i18n.locale = locale
return Vue.extend({
...

Using CKEditor 5 with nuxtjs

I'm trying to import a custom build of CKEditor 5 in my Nuxtjs project and I've been tried every possible way to import it correctly but none of them worked for me and this is one of them:
let ClassicEditor
let CKEditor
if (process.client) {
ClassicEditor = require('./../../static/js/ckeditor')
CKEditor = require('#ckeditor/ckeditor5-vue')
}else{
CKEditor = { component : {template:'<div></div>'}}
}
data() {
return {
editor: ClassicEditor,
}
}
components:{
ckeditor: CKEditor.component
},
<client-only><ckeditor :editor="editor" /></client-only>
Every time I change the way a different error appears, for example, Window is not Defined and when i use different way shows a different error so I want to know what is the most correct way to use CKEditor with Nuxtjs in universal mode, consider that i haven't done anything and help me with the correct way starting from the installation
try this
npm install --save #blowstack/ckeditor-nuxt
You can use by importing the package on client side.
Create Editor.vue components
<template>
<ckeditor
:editor="editor"
:config="editorConfig"
/>
</template>
<script>
let ClassicEditor
let CKEditor
if (process.client) {
ClassicEditor = require('#ckeditor/ckeditor5-build-classic')
CKEditor = require('#ckeditor/ckeditor5-vue2')
} else {
CKEditor = { component: { template: '<div></div>' } }
}
export default {
components: {
ckeditor: CKEditor.component
},
data () {
return {
editor: ClassicEditor,
editorConfig: {}
}
}
</script>
Usage:
<client-only placeholder="Loading Text Editor...">
<editor v-model="textInput"/>
</client-only>
window is not defined
If you set ssr: true in the nuxt.config.js and put your custom VCKEditor.vue into the components folder, the Nuxt will scan the components folder by Server Side and it doesn't know the window keyword which is the JavaScript object in #ckeditor/ckeditor5-vue2.
You can see more about components doc.
I don't like use the process.client to check ssr mode.
I have two solutions, just choose one
set components: false in the nuxt.config.js.
Move the your custom VCKEditor.vue to components folder outside.
Finally, register custom VCKEditor.vue as plugins and set plugins ssr: false in the nuxt.config.js.
Here is the sample project.
The code snippet
nuxt.config.js
const path = require('path')
const CKEditorWebpackPlugin = require("#ckeditor/ckeditor5-dev-webpack-plugin")
const CKEditorStyles = require("#ckeditor/ckeditor5-dev-utils").styles
export default {
// ignore other settings...
ssr: true,
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
{
src: '~/plugins/ckeditor.js', ssr: false
},
],
// set false to disable scan the components folder
components: false,
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
transpile: [/ckeditor5-[^/\\]+[/\\]src[/\\].+\.js$/],
plugins: [
// If you set ssr: true that will cause the following error. This error does not affect the operation.
// ERROR [CKEditorWebpackPlugin] Error: No translation has been found for the zh language.
new CKEditorWebpackPlugin({
// See https://ckeditor.com/docs/ckeditor5/latest/features/ui-language.html
language: "zh",
additionalLanguages: 'all',
addMainLanguageTranslationsToAllAssets: true,
})
],
// If you don't add postcss, the CKEditor css will not work.
postcss: CKEditorStyles.getPostCssConfig({
themeImporter: {
themePath: require.resolve("#ckeditor/ckeditor5-theme-lark")
},
minify: true
}),
extend(config, ctx) {
// If you do not exclude and use raw-loader to load svg, the following errors will be caused.
// Cannot read property 'getAttribute' of null
const svgRule = config.module.rules.find(item => {
return /svg/.test(item.test);
})
svgRule.exclude = [path.join(__dirname, 'node_modules', '#ckeditor')];
// add svg to load raw-loader
config.module.rules.push({
test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
use: ["raw-loader"]
})
}
}
}
components/editor/VCKEditor.vue
<template>
<ckeditor
v-model="editorData"
:editor="editor"
:config="editorConfig"
></ckeditor>
</template>
<script>
import CKEditor from '#ckeditor/ckeditor5-vue2'
import ClassicEditor from '#ckeditor/ckeditor5-editor-classic/src/classiceditor'
import Bold from '#ckeditor/ckeditor5-basic-styles/src/bold'
import Italic from '#ckeditor/ckeditor5-basic-styles/src/italic.js'
import Underline from '#ckeditor/ckeditor5-basic-styles/src/underline'
import Strikethrough from '#ckeditor/ckeditor5-basic-styles/src/strikethrough'
// less Heading + Essentials plugin can't input the text
import Heading from '#ckeditor/ckeditor5-heading/src/heading'
import Essentials from '#ckeditor/ckeditor5-essentials/src/essentials'
import ImageUpload from '#ckeditor/ckeditor5-image/src/imageupload'
import ImageInsert from '#ckeditor/ckeditor5-image/src/imageinsert'
import AutoImage from '#ckeditor/ckeditor5-image/src/autoimage'
import Image from '#ckeditor/ckeditor5-image/src/image'
import ImageResizeEditing from '#ckeditor/ckeditor5-image/src/imageresize/imageresizeediting'
import ImageResizeHandles from '#ckeditor/ckeditor5-image/src/imageresize/imageresizehandles'
import Base64UploadAdapter from '#ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter'
export default {
name: 'VCKEditor',
components: { ckeditor: CKEditor.component },
props: {
value: {
type: String,
},
},
computed: {
editorData: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
},
},
},
data() {
return {
editor: ClassicEditor,
editorConfig: {
plugins: [
Bold,
Italic,
Underline,
Strikethrough,
Heading,
Essentials,
ImageUpload,
ImageInsert,
AutoImage,
Image,
ImageResizeEditing,
ImageResizeHandles,
Base64UploadAdapter,
],
toolbar: {
items: [
'heading',
'|',
'bold',
'italic',
'underline',
'strikethrough',
'|',
'insertImage',
],
},
language: 'zh',
},
}
},
}
</script>
plugins/ckeditor.js
import Vue from 'vue';
import VCKEditor from "../components/editor/VCKEditor.vue";
Vue.component('v-ckeditor', VCKEditor);
pages/index.vue
<template>
<client-only>
<v-ckeditor v-model="text" />
</client-only>
</template>
<script>
export default {
data() {
return {
text: 'Hello World!!',
}
},
}
</script>
Use #blowstack/ckeditor-nuxt package.
Here is editor config for uploader.
Use #blowstack/ckeditor-nuxt package.
Here is editor config for uploader.
editorConfig: {
simpleUpload: {
uploadUrl: `${process.env.apiUrl}/api/console/uploads/single_file`,
headers: {
authorization: `Bearer ${_.get(
this.$store,
"state.agency.global.token"
)}`,
},
},
removePlugins: ["Title"],
}
Response data from upload API like this:
{
url: ".../image.png"
}
Refs: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/deep-dive/upload-adapter.html#passing-additional-data-to-the-response
editorConfig: {
simpleUpload: {
uploadUrl: `${process.env.apiUrl}/api/console/uploads/single_file`,
headers: {
authorization: `Bearer ${_.get(
this.$store,
"state.agency.global.token"
)}`,
},
},
removePlugins: ["Title"],
}