I'm using QuillJS for an editor, and in this editor I'd like to create some custom text styles. You have the default, bold etc. which already exist, however i'd like to extend upon these. For example, there's blockquote which'll create a block quote, however I want an inline quote. For this i'd ideally wrap it with say a span and class to apply the desired style, however I can't figure out how this is to be achieved with Quills API. Sure I can create a custom block, but that applies to the whole section of text rather then just the selected text. So i've tried using .formatText with my custom block, but not had any luck although if I change 'quote' to 'bold' it does... Any help / suggestions would be greatly appreciated!
let Block = Quill.import('blots/block');
class quote extends Block { }
quote.blotName = 'quote';
quote.className = 'quote';
quote.tagName = 'span';
Quill.register({ 'formats/quote': quote });
//Handler to change inline
var quoteHandler = function(){
var range = quill.getSelection();
console.log(range);
quill.formatText(range.index, range.length, 'quote', true);
}
/* Quill */
var quill = new Quill('.editor_space', {
theme: 'snow',
placeholder: 'Compose an epic...',
modules: {
toolbar:{
container: '.main_toolbar',
handlers: {
'linebreak': linebreakHandler,
'inlineQuote': quoteHandler,
}
}
}
});
To answer my own question, I should have been extending Inline for it to obviously be inline. No need for a handler function.
let Inline = Quill.import('blots/inline');
class quote extends Inline {
static create(value) {
let node = super.create(value);
return node;
}
}
quote.blotName = 'quote';
quote.className = 'quote';
quote.tagName = 'div';
Quill.register(quote);
Related
I am using the Quill.js editor, and I have created a custom Embed like below:
var Embed = Quill.import('blots/embed');
class QuillHashtag extends Embed {
static create(value) {
console.log(value);
let node = super.create(value);
node.innerHTML = value;
return node;
}
}
QuillHashtag.blotName = 'hashtag';
QuillHashtag.className = 'quill-hashtag';
QuillHashtag.tagName = 'span';
Quill.register({
'formats/hashtag': QuillHashtag
});
var quill = new Quill('#templateEditor', {
debug: 'debug',
modules: {
toolbar: {
container: '#toolbar'
}
},
placeholder: 'Compose an epic...',
readOnly: false,
theme: 'snow'
});
I then simply have a button, that can insert this custom embed QuillHashtag:
function insertVariable() {
quill.focus();
var selection = this.quill.getSelection();
quill.insertEmbed(selection.index, 'hashtag', '#games');
quill.setSelection(selection.index + selection.length + 1); //Place cursor to the right.
}
This works fine and the embedded hashtag is inserted to the editor. I
Something weird is happening though when I copy the hashtag and pasting it into the editor again and a space is present after the hashtag. When I do this, it will show true instead of the actual hashtag.
The thing is, it only shows the true value when there is a space after the hashtag.
I have created a JSFiddle here.
See below small gif showing this behavior:
I solved this issue by adding the below to the QuillHashtag class:
static value(domNode) {
return domNode;
}
I'm trying to figure out how to add a custom class when the user clicks the blockquote toolbar button. At the moment, when blockquote is clicked, the element is created as so:
<blockquote class="ql-align-justify">this is my quoted text</blockquote>
I would like to add .blockquote to the class as so:
<blockquote class="ql-align-justify blockquote">this is my quoted text</blockquote>
I'm currently looking at adding a handler, but there doesn't appear to be much documentation on how this works:
this.editor = new Quill(this.$refs.editor, this.editorOptions)
// Handlers can also be added post initialization
var toolbar = this.editor.getModule('toolbar');
toolbar.addHandler('blockquote', function(value) {
//todo: working on adding the blockquote class to blockquotes.
console.log('blockquote called');
console.log('value:');
console.log(value);
if (value) {
this.quill.format('blockquote');
}
});
I don't think handlers can do this (as far as I know)
But you can extend the BlockQuote format, by doing so, you will have full control of the node, you can add classes, or even click handler.
The format that is responsible for blockquote is:
const BlockQuote = Quill.import('formats/blockquote');
So simply you can do:
const BlockQuote = Quill.import('formats/blockquote');
class CustomBlockQuote extends BlockQuote {
static create(value) {
const node = super.create(value);
node.classList.add('test');
return node;
}
}
And you can update the tag name and the blot name like:
CustomBlockQuote.blotName = 'custom-blockquote';
CustomBlockQuote.tagName = 'blockquote';
Or even simpler for only changing the class:
class CustomBlockQuote extends BlockQuote {}
CustomBlockQuote.blotName = 'custom-blockquote';
CustomBlockQuote.tagName = 'blockquote';
CustomBlockQuote.className = 'custom-class-name';
Quill.register(CustomBlockQuote, true);
I have just created a jsfiddle as an example, I hope it works for you:
https://jsfiddle.net/hassansalem/095mh3fn/4/
I'm planning on implementing Quill into my website but unfortunately the insertText function is producing the following:
TypeError: n.appendChild is not a function shadow.ts:150
wrap shadow.ts:150
formatAt shadow.ts:70
format embed.ts:26
value cursor.js:25
formatAt embed.ts:30
formatAt container.ts:98
forEachAt linked-list.ts:114
formatAt container.ts:97
formatAt block.ts:42
value block.js:78
value cursor.js:35
value selection.js:110
value quill.js:157
a quill.js:437
value quill.js:149
value toolbar.js:101
I'm extending the text blot and attempting to use the documentation notes from here (copying the divider code) but the output ends up just printing true to the editor.
JS
const Text = Quill.import("blots/text");
class SchoolNameBlot extends Text {}
SchoolNameBlot.blotName = "tagName";
SchoolNameBlot.tagName = "NAME";
const toolbarOptions = [['bold', 'italic'], ['link', 'image', 'tagName']];
Quill.register(SchoolNameBlot);
const options = {
debug: 'info',
theme: 'snow',
modules: {
toolbar: toolbarOptions
}
}
const editor = new Quill("#msgText", options);
$("#tagName-Button").click(function() {
let range = editor.getSelection(true);
editor.insertText(range.index, "insertText");
});
HTML Element:
<div class="col-md-11">
<div id="msgText"></div>
</div>
Output
From what I can tell, I am using Quill correctly so I'm not to sure why this error is being produced. I'm using the CDN's provided on their page.
I'm extending the text blot and attempting to use the documentation
notes from here (copying the divider code) but the output ends up just
printing true to the editor.
In the link presented talking about how to clone Medium, there is no blot being created that extends blots/text. Divider is created using blots/block/embed. Basically, there are 3 types of blots that can be created:
Block Blot
Inline Blot
Embed Blot
To help you better understand what I'm talking about, I suggest you to read a little about Parchment and Blots.
Now, about your problem itself... As you can see from your example, you just created a blot, but didn't add any behavior to it, and you have set your created blot tag name to NAME. Of all existing tags in HTML, there is not one with the name <NAME>. Look:
https://www.w3schools.com/TAGs/
https://techspirited.com/all-html-tags-list-of-all-html-tags
The name you give to tagName will be the HTML tag used for the result, ie what your blot will represent. If you want to add an image, for example, you need to give tagName the value IMG. For a header title, you could use h1, h2, h3, and so on.
Looking at your code, and seeing the name "tag" written on it, it seems to me that you just want to add some stylized text. Would it be? If this is your case, look at the following example:
let Inline = Quill.import('blots/inline');
class SchoolNameBlot extends Inline {
// Overriding this method, in this particular case, is what
// causes the Delta returned as content by Quill to have
// the desired information.
static formats(domNode) {
if(domNode.classList.contains('my-style')){
return true;
}
else{
return super.formats(domNode);
}
}
formats() {
// Returns the formats list this class (this format).
let formats = super.formats();
// Inline has the domNode reference, which in this
// case represents the SPAN, result defined in tagName.
formats['tag-name'] = SchoolNameBlot.formats(this.domNode);
// In the code above, it is as if we are adding this new format.
return formats;
}
}
SchoolNameBlot.blotName = 'tag-name';
SchoolNameBlot.tagName = 'span';
SchoolNameBlot.className = 'my-style';
Quill.register(SchoolNameBlot);
$(document).ready(function () {
var toolbarOptions = {
container: [['bold', 'italic'], ['link', 'image', 'tag-name']],
handlers: {
'tag-name': function(){
this.quill.insertText(0, 'Hello', 'tag-name', true);
}
}
};
var quill = new Quill('#editor', {
theme: 'snow',
modules: {
'toolbar': toolbarOptions
}
});
});
.my-style{
background: rgb(254, 255, 171);
border-radius: 2px;
padding: 2px 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<p>Instructions:</p>
<ol>
<li>Press the invisible button (with no icon) next to the add image button.</li>
</ol>
<div id="editor">
</div>
To just style text, I do not advise creating a new Blot, as there is no need for something so complex. You could use Attributors. The previous code would look as:
const Parchment = Quill.import('parchment')
var config = {
scope: Parchment.Scope.INLINE,
whitelist: ['yellow', 'red', 'blue' , 'green']
};
var SchoolName = new Parchment.Attributor.Class('my-attributor', 'style' , config);
Quill.register(SchoolName);
$(document).ready(function () {
var toolbarOptions = {
container: [['bold', 'italic'], ['link', 'image', 'my-button'] , ['clean']] ,
handlers: {
'my-button': function () {
let range = this.quill.getSelection();
this.quill.insertText(range.index, 'Hello', 'my-attributor' , 'yellow');
}
}
};
var quill = new Quill('#editor', {
theme: 'snow',
modules: {
'toolbar': toolbarOptions
}
});
});
.style-yellow{
background: rgb(254, 255, 171);
border-radius: 2px;
padding: 2px 2px;
}
.style-red{
background: rgb(255, 171, 171);
border-radius: 2px;
padding: 2px 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<p>Instructions:</p>
<ol>
<li>Press the invisible button (with no icon) next to the add image button.</li>
</ol>
<div id="editor">
</div>
As a final tip, you can always get more information from Quill official website, as well as from its repositories. For even more information, examples and frequently asked questions (Quill FAQ), take a look here.
Is there a way to only show part of a document, or in monacos case of a model, while still getting intellisense for the whole document?
I only want a user to edit a part of a document, but the user should be able to get the right contextual intellisense.
It would be best for my usecase to hide the uneditable sections, but deactivating them would also be ok.
In case this is not possible, is there any embedded editor that can do this, or can this be achived by modifying the language server?
Monaco editor loads every line as a container under a section with the class name "view-lines". Once the editor content has loaded, set "display: none" to the corresponding container for each line that you want to hide.
Implementation: https://jsfiddle.net/renatodc/s6fxedo2/
let value = `function capitalizeFirstLetter(string) {
\treturn string.charAt(0).toUpperCase() + string.slice(1);
}
$(function() {
\tlet word = "script";
\tlet result = capitalizeFirstLetter(word);
\tconsole.log(result);
});
`
let linesToDisable = [1,2,3];
let editor = monaco.editor.create(document.getElementById('container'), {
value,
language: 'javascript',
theme: 'vs-dark',
scrollbar: {
vertical: "hidden",
handleMouseWheel: false
},
scrollBeyondLastLine: false
});
// onLoad event for Monaco Editor: https://github.com/Microsoft/monaco-editor/issues/115
let didScrollChangeDisposable = editor.onDidScrollChange(function() {
didScrollChangeDisposable.dispose();
setTimeout(function() {
$(".monaco-editor .view-lines > div").each(function(i) {
if(linesToDisable.includes(i+1)) {
$(this).css("display","none");
$(this).css("pointer-events","none");
}
});
},1000);
});
Scrolling from Monaco will render the lines again and break the implementation. To prevent this, disable the scrolling feature in Monaco, set a fixed height for the editor container, and use the browser or a parent container to scroll instead.
If you use the arrow keys 'up' or 'down' to navigate to the hidden content, the cursor will still work, and typing will break the implementation. You might be able to use the editor's onKeyDown event to prevent this.
If you're looking for a break-proof implementation, I would suggest loading Monaco editor only with the portion of the document that you wish to edit. Then extend the completion provider (Intellisense) as shown in this example: https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems: function(model, position) {
return {
suggestions: [
{
label: "capitalizeFirstLetter",
kind: monaco.languages.CompletionItemKind.Method,
documentation: "Capitalize the first letter of a word",
insertText: "capitalizeFirstLetter("
}
]
};
}
});
monaco.editor.create(document.getElementById("container"), {
value: `$(function() {
\tlet word = "script";
\tlet result = capitalizeFirstLetter(word);
\tconsole.log(result);
});
`,
language: "javascript"
});
Use an AST parser like Esprima to get the identifiers from your source document, and plug these into the suggestions array.
I have created a Web Component which hosts Wiris. However when the component is rendered the Wiris editor is (very) badly formed:
You can see the issue live here.
The code is as follows:
class WirisComponent extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// Create a shadow root
var shadow = this.attachShadow( { mode: 'open' } );
// Create a div to host the Wiris editor
var div = document.createElement('div');
div.id = 'editorContainer';
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
// Insert the Wiris instance into the div
editor.insertInto(div);
// Append it to the shadow route
shadow.appendChild(div);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
and the HTML mark-up is:
<wiris-component></wiris-component>
Note that I've tried this in Chrome which does have full support for web components.
Any idea what the problem is? Is the problem related to the styling issue found in this issue?
Don't use a Shadow DOM: the styles imported with your library are not working with it.
class WirisComponent extends HTMLElement {
connectedCallback() {
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
editor.insertInto(this);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
<script src="https://www.wiris.net/demo/editor/editor"></script>
<wiris-component></wiris-component>