Injecting highlight.js to work with vue2-editor (Quill) - javascript

I have a trouble connecting vue2-editor (based on quill) with highlight.js
No matter what I do, I get an error saying:
Syntax module requires highlight.js. Please include the library on the page before Quill.
I'm using nuxt if it changes anything.
I've tried adding this line at the beginning of script tag:
import hljs from'highlightjs';
So it looks like:
<script>
import hljs from'highlightjs';
export default {
middleware: 'hasPermissions',
permissions: [ 'createPosts' ],
...
}
</script>
My plugin where I require vue2-editor:
import Vue from'vue';
import VueEditor from'vue2-editor';
Vue.use(VueEditor);
VueEditor component in my page:
<VueEditor
class="my-4"
v-model="content"
:editor-options="{ modules: { syntax: true } }"
placeholder="Post content" />
EDIT:
I've tried creating my own component and it shows the same error:
<template>
<v-layout
row
wrap>
<v-flex xs12>
<div ref="editor" />
</v-flex>
</v-layout>
</template>
<script>
import Quill from'quill';
export default {
data() {
return {
editor: null
};
},
mounted() {
window.hljs = require('highlight.js');
this.editor = new Quill(this.$refs.editor, {
modules: {
toolbar: [
[{ header: [ 1, 2, 3, 4, false ]}],
[ 'bold', 'italic', 'underline' ]
],
syntax: true
},
theme: 'snow',
formats: [ 'bold', 'underline', 'header', 'italic' ]
});
this.editor.root.innerHTML = this.value;
}
};
</script>
I can successfully print hljs in console in development tools in my browser. What's wrong?

This one should be a better solution, works for me.
https://github.com/surmon-china/vue-quill-editor/issues/39
In fact, this is because quill internal self-closure caused by the
problem, the solution is as follows: modules.syntax from true to
replace a function:
import hljs from 'highlight.js'
import 'highlight.js/styles/monokai-sublime.css'
editorOption: {
modules: {
syntax: {
highlight: text => hljs.highlightAuto(text).value
}
}
}

I struggled with this for a long time too and this answer worked for me!
// highlight.js component
import Vue from 'vue'
import Hljs from 'highlight.js'
import 'highlight.js/styles/googlecode.css'
let Highlight = {}
Highlight.install = function (Vue, options) {
Vue.directive('highlight', function (el) {
let blocks = el.querySelectorAll('pre code');
blocks.forEach((block) => {
Hljs.highlightBlock(block)
})
})
}
export default Highlight
// in main.js
import Highlight from 'path/to/Highlight.js'
Vue.use(Highlight)
I changed import 'highlight.js/styles/googlecode.css'
to import 'highlight.js/styles/monokai-sublime.css' seems to be a more popular and pleasing style.
you could also probably add a
hljs.configure({ // optionally configure hljs
languages: ['javascript', 'ruby', 'python']
});
to select certain languages, but I haven't tried.
Although I still haven't figured out how to change the background color. it shows up white in other places and black background in the quill window.

Related

Vue3 with Vite only accepts kebab case tags while Vue3 cli accepts Pascal case tags for custom components

I have a project using Vue3 with Vite (on Laravel) which have a Wiki.vue page which loads a "MyContent.vue" component.
//On MyContent.vue:
<template>
<div>content component</div>
</template>
<script>
export default {
name: "MyContent",
};
</script>
//On Wiki.vue:
<template>
<MyContent />
</template>
<script>
import MyContent from "./wiki/components/MyContent.vue";
export default {
components: { MyContent },
};
</script>
//On vite.config.js
import { defineConfig } from "vite";
import laravel from "laravel-vite-plugin";
import vue from "#vitejs/plugin-vue";
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => ["MyContent"].includes(tag),
},
},
}),
laravel(["resources/css/app.css", "resources/js/app.js"]),
],
});
On Wiki.vue If I dont change the tag from MyContent to my-content the component won't load at all.
I tried to start a new Vue3 Cli project and I notice that the HelloWorld tag is able to remain Pascal case and load properly which I really wonder what makes the difference.
Thanks in advance!
You've configured a compiler option to treat any elements whose name matches "MyContent" as custom elements, which prevents them from being parsed as Vue components:
// vite.config.js
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: { 👇
isCustomElement: (tag) => ["MyContent"].includes(tag),
},
},
}),
],
});
Wiki.vue's template tries to use a Vue component named "MyContent", which is ignored per the config above. HelloWorld.vue is unaffected by this because the config above only looks for "MyContent".
Solution
You should either remove the isCustomElement config, or rename MyContent.vue. It doesn't sound like you're actually using custom elements, so I think the former is the best solution.

How to render/show Quill raw HTML in vuejs

I have quill editor in vuejs app. I save raw HTML generated by Quill directly to Database (i.e. no cleaning at all). When I fetch it from the backend, text and all styling are shown correctly (i.e. if the text was saved with color it is rendered with the same color, bold is bold etc). But text alignment is not shown correctly.
I am using v-html to show content but text alignment is not rendered.
Basically, I want to have text alignment. This is achieved if I inject (I don't want the user to modify the text) the text into quill editor, but I want to show raw content to normal HTML (i.e. vue component). On digging deep I found, it uses classes as ql-align-right, ql-align-center. So, I tried to explicitly add styling for these classes in styles still no results.
Code snippet
my homeComponent.vue
<template>
<div class="cell medium-8 large-6 columns">
<div class="blog-post" v-for="feed in feedArray" :key="feed.id">
<h3>
<router-link
:to="`article/${feed.post_uniqueidentity}`"
class="heading-link"
>
{{ feed.post_head }}
</router-link>
</h3>
<small>{{
moment(feed.post_timestamp).format("dddd, MMMM Do YYYY")
}}</small>
<div class="postBody-simple" v-html="feed.post_body"></div>
</div>
<div
v-if="feedArray.length"
v-observe-visibility="HandledScrollEvent"
></div>
</div>
</template>
<script>
// ignore from now on...
import moment from "moment";
import { mapGetters, mapActions } from "vuex";
import "../assets/css/bootstrap-icons-1.5.0/bootstrap-icons.css";
import { ObserveVisibility } from "vue-observe-visibility";
export default {
name: "feeds",
metaInfo: {
title: "Home",
},
directives: {
ObserveVisibility,
},
data() {
return {
next: null,
previous: 1,
feedArray: [],
};
},
computed: { ...mapGetters(["feedStore"]) },
methods: {
...mapActions([
"fetchFeed",
"fetchFeedPagination",
]),
async contentLoader(pageNumber) {
var localArrayData = await this.fetchFeedPagination(pageNumber);
this.feedArray.push(...localArrayData.results);
this.next = localArrayData.next;
},
HandledScrollEvent(isVisible) {
if (!isVisible) {
return;
}
if (this.next) {
this.contentLoader(this.next);
}
},
},
async created() {
this.moment = moment;
let rawResponse = await this.fetchFeed();
let isNextPageAvailable = rawResponse.next;
let isPreviousPageAvailable = rawResponse.previous;
if (isNextPageAvailable) {
this.next = isNextPageAvailable;
}
this.previous = isPreviousPageAvailable;
this.feedArray.push(...rawResponse.results);
},
};
</script>
Frontend
Vue: 2.6.11,
vue-observe-visibility: 1.0.0,
vue-quill-editor: 3.0.6,
vue-router: 3.2.0,
vuex: 3.4.0
Backend
Django: 3.0.5
django-cors-headers: 3.7.0
djangorestframework: 3.12.4
psycopg2: 2.9.1
Database: PostgreSQL
This is also tested without any styling, still not getting expected results.
As shown in the README on Github - you should import the proper CSS stylesheets:
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'
export default {
components: {
quillEditor
}
}
These stylesheets are required anywhere you need your HTML content to be rendered - because Quill by default prefers CSS classes instead of inline styles. You can customize this behavior by using attributors as explained in the manual
var ColorClass = Quill.import('attributors/class/color');
var SizeStyle = Quill.import('attributors/style/size');
Quill.register(ColorClass, true);
Quill.register(SizeStyle, true);
// Initialize as you would normally
var quill = new Quill('#editor', {
modules: {
toolbar: true
},
theme: 'snow'
});

Unhandled Rejection (Error): element with ID «editorjs» is missing. Pass correct holder's ID

I am developing a react app which uses Editor.js as an editor and that page is working fine. But when ever i try to access other pages it gives Unhandled Rejection. This is confusing because i am importing editorjs packages only to the editor page, but it's asking for element with id "element-js".
This is editor connfig file.
const editor = new EditorJS({
holder: 'editorjs',
autofocus: true,
tools: {
paragraph: {
class: Paragraph,
inlineToolbar: true,
config: {
placeholder: 'Write Here....'
},
},
table: {
class: Table,
inlineToolbar: true,
config: {
rows: 2,
cols: 3,
},
},
header: {
class: Header,
/**
* This property will override the common settings
* That means that this tool will have only Marker and Link inline tools
* If 'true', the common settings will be used.
* If 'false' or omitted, the Inline Toolbar wont be shown
*/
inlineToolbar: true,
config: {
placeholder: 'Header'
},
shortcut: 'CMD+SHIFT+H'
},
delimiter: Delimiter,
warning: Warning,
list: {
class: List,
inlineToolbar: [
'link',
'bold'
]
},
quote: Quote,
checklist: {
class: Checklist,
inlineToolbar: true,
},
Marker: {
class: Marker,
shortcut: 'CMD+SHIFT+M',
},
embed: {
class: Embed,
inlineToolbar: false,
config: {
services: {
youtube: true,
coub: true
},
},
},
image: ImageTool,
}
});
And how i am importing:
import EditorJS from '#editorjs/editorjs';
import Header from '#editorjs/header';
import List from '#editorjs/list';
import Checklist from '#editorjs/checklist';
import Embed from '#editorjs/embed';
import Marker from '#editorjs/marker';
import Warning from '#editorjs/warning';
import Quote from '#editorjs/quote';
import Delimiter from '#editorjs/delimiter';
import ImageTool from '#editorjs/image';
import Table from "#editorjs/table";
import Paragraph from "#editorjs/paragraph";
I don't know what's the problem here. In my opinion these imports are importing globally to the whole app.
I know this is a bit late but some other people like me still arrive here with the same issue and some of us don't want to use an unofficial editor.js component.
So the issue is pretty simple, that error means that you must have an element with id editorjs in the DOM but since Im using Next.js I will explain how to use it step by step. (you won't need extra steps if you are only using React)
Create a component that looks like this: (You have to install plugins otherwise you will get some errors)
import Embed from '#editorjs/embed'
import Table from '#editorjs/table'
import List from '#editorjs/list'
import Warning from '#editorjs/warning'
import Code from '#editorjs/code'
import LinkTool from '#editorjs/link'
import Image from '#editorjs/image'
import Raw from '#editorjs/raw'
import Header from '#editorjs/header'
import Quote from '#editorjs/quote'
import Marker from '#editorjs/marker'
import CheckList from '#editorjs/checklist'
import Delimiter from '#editorjs/delimiter'
import InlineCode from '#editorjs/inline-code'
import SimpleImage from '#editorjs/simple-image'
import EditorJS from '#editorjs/editorjs'
const EditorNoSSR = ({ type }) => {
const TOOLS = {
embed: Embed,
table: Table,
marker: Marker,
list: List,
warning: Warning,
code: Code,
linkTool: LinkTool,
image: Image,
raw: Raw,
header: Header,
quote: Quote,
checklist: CheckList,
delimiter: Delimiter,
inlineCode: InlineCode,
simpleImage: SimpleImage,
}
const editor = new EditorJS({
/**
* Id of Element that should contain the Editor
*/
holder: 'editorjs',
tools: TOOLS,
})
return (<>
<div>
<div id="editorjs">
</div>
</div>
</>);
}
export default EditorNoSSR;
That is a component that you will import using next/dynamic and it will work perfectly. And to share data from this component to another you can use react's context.
Now a page where you call the component will look like this:
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
const EditorNoSSR = dynamic(() => import("../../../components/EditorNoSSR"), { ssr: false })
const EditorPage = () => {
return (<>
<EditorNoSSR />
</>);
}
export default EditorPage;
Now I have used React-editor-js and it's working fine.
https://www.npmjs.com/package/react-editor-js

React-Image-Annotate - SyntaxError: Cannot use import statement outside a module

I'm trying to use react-image-annotate but it's giving me this issue when I first try to set it up.
And here's how I'm using it:
import React from 'react'
import ReactImageAnnotate from 'react-image-annotate'
function ImageAnnotator() {
return (
<ReactImageAnnotate
selectedImage="https://images.unsplash.com/photo-1561518776-e76a5e48f731?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80"
// taskDescription="# Draw region around each face\n\nInclude chin and hair."
// images={[
// { src: 'https://example.com/image1.png', name: 'Image 1' },
// ]}
// regionClsList={['Man Face', 'Woman Face']}
/>
)
}
export default ImageAnnotator
I'm using Next.js if that matters
UPDATE 1
I tried using this babel plugin as suggested by Alejandro Vales. It gives the same error as before. Here's the babel key in my package.json:
"babel": {
"presets": [
"next/babel"
],
"plugins": [
[
"#babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"#babel/plugin-transform-modules-commonjs",
{
"allowTopLevelThis": true
}
]
]
}
I would say that the issue relies in the library itself by what they replied in here (similar bug) https://github.com/UniversalDataTool/react-image-annotate/issues/90#issuecomment-683221311
Indeed one way to fix it I would say is adding babel to the project so you can transform the imports in your project to require automatically without having to change the code on your whole project.
This is the babel package you are looking for https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs
Another reason for this could be an outdated version of your package, as some people report to have this fixed after using a newer version of Create React App (https://github.com/UniversalDataTool/react-image-annotate/issues/37#issuecomment-607372287)
Another fix you could do (a little crazier depending on your resources) is forking the library, creating a CJS version of the lib, and then pushing that to the library, so you and anybody else can use that in the future.
I got a tricky solution!
Problem is that react-image-annotate can only be imported in client-side(SSR got error for import keyword)
So, let react-image-annotate in Nextjs be imported only in client side
(https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr)
in Next Page that needs this component, You can make component like this
import dynamic from "next/dynamic";
const DynamicComponentWithNoSSR = dynamic(() => import("src/components/Upload/Annotation"), { ssr: false });
import { NextPage } from "next";
const Page: NextPage = () => {
return (
<>
<DynamicComponentWithNoSSR />
</>
);
};
export default Page;
Make component like this
//#ts-ignore
import ReactImageAnnotate from "react-image-annotate";
import React from "react";
const Annotation = () => {
return (
<ReactImageAnnotate
labelImages
regionClsList={["Alpha", "Beta", "Charlie", "Delta"]}
regionTagList={["tag1", "tag2", "tag3"]}
images={[
{
src: "https://placekitten.com/408/287",
name: "Image 1",
regions: [],
},
]}
/>
);
};
export default Annotation;

How to connect a custom image selector to CKeditor5 insert image in react js

I am trying to intergrate CKeditor into a custom document editor, using ckeditor5-react module it was easy to intergrate the state into the data however the default behaviour for inserting an image is not desirable in my particular use case. I have a image library built into my application so I'd like the CKeditor button to open my image library component so the user can select the image from the library, then insert that image where the cursor is placed.
After trying to acheive this with the regular, out-of-the-box ClassicEditor build I realised it was not going to be possible so I created a custom plugin that should achieve what I want:
import ClassicEditorBase from '#ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '#ckeditor/ckeditor5-essentials/src/essentials';
import Autoformat from '#ckeditor/ckeditor5-autoformat/src/autoformat';
import Bold from '#ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '#ckeditor/ckeditor5-basic-styles/src/italic';
import BlockQuote from '#ckeditor/ckeditor5-block-quote/src/blockquote';
import EasyImage from '#ckeditor/ckeditor5-easy-image/src/easyimage';
import Heading from '#ckeditor/ckeditor5-heading/src/heading';
import Image from '#ckeditor/ckeditor5-image/src/image';
import ImageStyle from '#ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '#ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '#ckeditor/ckeditor5-image/src/imageupload';
import Link from '#ckeditor/ckeditor5-link/src/link';
import List from '#ckeditor/ckeditor5-list/src/list';
import MediaEmbed from '#ckeditor/ckeditor5-media-embed/src/mediaembed';
import Paragraph from '#ckeditor/ckeditor5-paragraph/src/paragraph';
import PasteFromOffice from '#ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
import Table from '#ckeditor/ckeditor5-table/src/table';
import TableToolbar from '#ckeditor/ckeditor5-table/src/tabletoolbar';
import ObservableMixin from '#ckeditor/ckeditor5-utils/src/observablemixin';
import mix from '#ckeditor/ckeditor5-utils/src/mix';
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '#ckeditor/ckeditor5-ui/src/button/buttonview';
import imageIcon from '#ckeditor/ckeditor5-core/theme/icons/image.svg';
export default class ClassicEditor extends ClassicEditorBase {}
class InsertImage extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( 'insertImage', locale => {
const view = new ButtonView( locale );
view.set( {
label: 'Insert image',
icon: imageIcon,
tooltip: true,
} );
// set observables on editor
editor.set( {
insertImageRequested: false,
imageFileRequested: null
} );
// Callback executed once the image button is clicked.
view.on( 'execute', () => {
// set observable to indicate a request to insert image has been made...
editor.set( { insertImageRequested: true } );
});
// Now this waits for the user to have selected a file URL which should show up
// in the imageFileRequested observable
editor.on( 'change:imageFileRequested', (evt, newShizz, oldShizz) => {
// // Which then injects the image into the body...
const imageUrl = editor.imageFileRequested;
editor.model.change( writer => {
const imageElement = writer.createElement( 'image', {
src: imageUrl
} );
// Insert the image in the current selection location.
editor.model.insertContent( imageElement, editor.model.document.selection );
});
// and unsets our observables back to their original state
editor.set( {
insertImageRequested: false,
imageFileRequested: null
} );
} )
return view;
} );
}
}
mix( InsertImage, ObservableMixin );
// Plugins to include in the build.
ClassicEditor.builtinPlugins = [
Essentials,
Autoformat,
Bold,
Italic,
BlockQuote,
EasyImage,
Heading,
Image,
ImageStyle,
ImageToolbar,
ImageUpload,
Link,
List,
MediaEmbed,
Paragraph,
PasteFromOffice,
Table,
TableToolbar,
InsertImage
];
// Editor configuration.
ClassicEditor.defaultConfig = {
toolbar: {
items: [
'heading',
'|',
'bold',
'italic',
'link',
'bulletedList',
'numberedList',
'blockQuote',
'insertTable',
'mediaEmbed',
'undo',
'redo',
'|',
'InsertImage'
]
},
image: {
toolbar: [
'imageStyle:full',
'imageStyle:side',
'|',
'imageTextAlternative'
]
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
language: 'en'
};
From the above code you can see that it uses the Observable events feature of CKeditor5 to set 2 observables: insertImageRequested (bool) or imageFileRequested (null or string).
Now in my react component I recieve the "insertImageRequested" change event and update my state so that the library should open and my component does that ok (however the cursor does move to the beginning of the document, which I dont want). The problem is I just cant seem to get the editor to allow me to set the second Observable "imageFileRequested" from outside of the editor with the selected images URL, so I cant send the fileURL string back to the custom editor to set the Observable
import React from 'react';
import PropTypes from 'prop-types';
...
import CKEditor from '#ckeditor/ckeditor5-react';
//import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
import CustomEditor from './customCKeditor/ckeditor.js';
import styles from './articleEditor.scss';
class ArticleEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
insertImageRequested: false,
imageFileRequested: null,
eventLogData: null
}
...
this.imageClick = this.imageClick.bind(this);
}
...
imageClick(){
// Temp Data to be passed to custom Ckeditor
let tempURL = "https://via.placeholder.com/350x150";
this.setState({
insertImageRequested: false,
imageFileRequested: tempURL
})
// HOW DO I PASS 'tempURL' variable to editor so that I can set it as the value for the imageFileRequested observable (so CKeditor on change event is triggered as shown above)?
}
render() {
return(
<div className={styles.container}>
<CKEditor
editor={ CustomEditor }
data={this.state.articleBody}
name={'articleBody'}
config={{
toolbar: [ 'InsertImage', '|', 'heading', '|', 'bold', 'italic', '|', 'link', 'bulletedList', 'numberedList', 'blockQuote', '|', "mediaEmbed", '|', 'undo', 'redo', '|', "insertTable", "tableColumn", "tableRow", "mergeTableCells", '|' ],
heading: {
options: [
{ model: 'paragraph', title: 'Paragraph' },
{ model: 'heading1', view: 'h1', title: 'Heading 1'},
{ model: 'heading2', view: 'h2', title: 'Heading 2'},
{ model: 'heading3', view: 'h3', title: 'Heading 3'},
{ model: 'heading4', view: 'h4', title: 'Heading 4'},
{ model: 'heading5', view: 'h5', title: 'Heading 5'},
{ model: 'heading6', view: 'h6', title: 'Heading 6'}
]
}
}}
onInit={ editor => {
// A request for a new image has been made
editor.on( 'change:insertImageRequested', (evt, newShizz, oldShizz) => {
//reflect that in the react app state
this.setState({
insertImageRequested: true
})
} )
} }
onChange={ ( event, editor ) => {
console.log(event);
console.log(editor);
//console.log(editor.insertImageRequested);
const data = editor.getData();
console.log( { event, editor, data } );
this.updateArticleBodyState(data);
this.eventLogger(editor);
// THIS DOES NOT WORK
// //if(editor.imageFileRequested != null){
// console.log("imagefilerequested is defined!")
// editor.set( { imageFileRequested: this.state.imageFileRequested } );
// //}
} }
onBlur={ editor => {
//console.log( 'Blur.', editor );
} }
onFocus={ editor => {
//console.log( 'Focus.', editor );
} }
/>
</div>
);
}
}
...
After 2 days of wrestling with this, with the solution going from relatively straightforward to needlessly complex it occurred to me that the way I am doing this is most likely not right and my entire approach has been wrong, so I suppose my question is two-fold. First, is the above approach to what I am doing the correct way?
If it is, can you help me set the tempUrl variable as the observable in ckeditor.
Secondly if this is completely the wrong approach can you please help me find the correct way to go about implementing this. I am happy to provide more information if its needed however this post has become very long so I'll end with, thanks in advance for any help you can provide.
I opted for Quill and React-quill in the end. It does not require webpack changes and has a suitable API for my modest needs.

Categories