quill add tooltip on custom blot - quill

I can't figure out how to add a tooltip on a created custom blot. Something like what is natively happening on links
My custom blot is displayed in the text editor with cusom tag
class CustomBlot extends BlockEmbed {
static create(value) {
let node = super.create();
node.setAttribute('object', "instrument");
node.setAttribute('id', value.id);
node.innerHTML = `
<div class="btn-group">
<button class="btn btn-sm btn-outline-info">${value.name}</button>
</div>
`;
return node;
}
static formats(node) {
return node.getAttribute('id');
}
}
CustomBlot.blotName = 'custom';
CustomBlot.tagName = 'my-custom-tag';
Quill.register(CustomBlot);
So the inserted code in my editor is between these tags
<my-custom-tag>some html</my-custom-tag>

I know it's been a while, but for anyone interested in the subject, please, see the following link:
https://github.com/loagit/Quill-Examples-and-FAQ

Related

How to make the link is clickable in textarea in ReactJS?

I have a page named Community where user can comment on another post
I have a problem when user comments the link but it's not displayed true
When user comments, the link is not displayed true
The function I used to convert the text to link
const convertToLink = (text) => {
var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/ig;
var text1 = text.replace(exp, "<a href='$1'>$1</a>");
var exp2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
return text1.replace(exp2, '$1$2');
}
You cannot render HTML inside of textarea. Use <p> instead
<p dangerouslySetInnerHTML={{ __html: convertToLink(text) }}></p>

Creation of a Formula Blot (Hierarchical Tags)

Creation of a complex formula blot, such as the one below, is successful using an Embed, but it works only in a single instance. All other instances are truncated. Attempts to create it as an Inline or Block resulted in errors.
I am developing a graphic formula editor, with a dialog and toolbars of its own, and it is successfully getting the delta from an existing blot. Unfortunately, saving more than one formula fails, and replacing an existing formula fails. When saving more than one formula, only the top level spans are saved for all but the first.
It isn't clear from the documentation if the tag hierarchy can be used as an Inline or Block element.
As a side note, I am building a complete Quill npm module for Angular 7+ apps that includes a number of other quill modules as well, translated to Typescript. The npm module will support WCAG compliance with large buttons, etc. that can be toggled.
Example tag hierarchy to be used as a blot:
<span class="pl3-quill-formula-blot" data-id="pl3qfml_1560288135645" data-editors="[{"id":1560288133767,"parentId":null,"depth":0,"position":0,"fElement":{"name":"cube root","text":"∛","type":"O","shape":"R","latex":null,"values":["x"],"nValues":1}},{"id":1560288133768,"parentId":0,"depth":0,"position":1,"fElement":{"name":"-EMPTY-","text":"","type":"S","shape":"C","latex":null,"latexCb":null,"values":[""],"nValues":1}}]">
<span contenteditable="false">
<span class="katex">
<span class="katex-mathml"><math><semantics><mrow><mroot><mi>x</mi><mn>3</mn></mroot></mrow><annotation encoding="application/x-tex">\sqrt[3] {x} </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 1.04em; vertical-align: -0.23972em;"></span><span class="mord sqrt"><span class="root"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.658556em;"><span class="" style="top: -2.83634em;"><span class="pstrut" style="height: 2.5em;"></span><span class="sizing reset-size6 size1 mtight"><span class="mord mtight"><span class="mord mtight">3</span></span></span></span></span></span></span></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.80028em;"><span class="svg-align" style="top: -3em;"><span class="pstrut" style="height: 3em;"></span><span class="mord" style="padding-left: 0.833em;"><span class="mord mathdefault">x</span></span></span><span class="" style="top: -2.76028em;"><span class="pstrut" style="height: 3em;"></span><span class="hide-tail" style="min-width: 0.853em; height: 1.08em;"><svg width="400em" height="1.08em" viewBox="0 0 400000 1080" preserveAspectRatio="xMinYMin slice"><path d="M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,
-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,
-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,
35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,
-221c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467
s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422
s-65,47,-65,47z M834 80H400000v40H845z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.23972em;"><span class=""></span></span></span></span></span></span></span>
</span>
</span>
</span>
Blot Code:
import {Pl3QuillFormulaResult} from './pl3-quill-formula.component';
const Quill = require('quill');
const Embed = Quill.import('blots/embed');
class Pl3QuillFormulaBlotComponent extends Embed {
static blotName = 'pl3-quill-formula';
static className = 'pl3-quill-formula-blot';
static tagName = 'span';
constructor(domNode: Element, value: any) {
super(domNode);
}
static create(result: Pl3QuillFormulaResult) {
const node = super.create(result.id);
node.setAttribute('data-id', result.id);
node.setAttribute('data-editors', result.editorsAsString);
node.appendChild(result.domElement);
return node;
}
/**
* Quill uses this to return a Delta with the attributes in the return.
*/
static formats(domNode: Element) {
return {
editors: domNode.getAttribute('data-editors'),
id: domNode.getAttribute('data-id')
};
}
static value(domNode: HTMLElement) {
return {
id: domNode.dataset.id,
editors: domNode.dataset.editors
};
}
public remove() {
if (this.prev == null && this.next == null) {
this.parent.remove();
}
else {
super.remove();
}
}
}
Quill.register('formats/pl3-quill-formula', Pl3QuillFormulaBlotComponent);
This is the call to insert the formula that is not working for subsequent instances, although it works for the first formula:
if (result) {
const range = this.quill.getSelection(true);
this.quill.insertEmbed(range.start, 'pl3-quill-formula', result, Quill.sources.API);
this.quill.setSelection(range.length + 1);
}
Formula Editor UI Snapshot
The expected results are to be able to insert the formula within the editor, inline, and to be able to replace it when the formula is updated.

Implement custom editor for Quill blot

I'm trying to customize the Quill editor for my needs. I managed to implement and insert custom blots, as described in https://quilljs.com/guides/cloning-medium-with-parchment/ But I need to edit data, which is attached to my blots, like the URL of a link for example. The default implementation of Quill displays a small "inline" edit box for links. I want to implement something like that myself, but just don't get it. I did not find any hints in the docs and guides. Reading the source code of Quill, I was not able to figure out where the editing dialog for links is implemented. Any starting point would be very appreciated.
I've tried something similar. Proper way of doing it should be creating a module. Unfortunately as you already know it is not as easy as it seems.
Let me point you to some useful resources that helped me a lot with understanding how to create extensions for quill.
Quills maintainer is curating Awesome quill list.
I recommend looking especially into
quill-emoji it contains code to display tooltip emoji while writing
quill-form maybe some form extension has some code that will point you in the right direction
Here is my try on to it using custom quill module.
const InlineBlot = Quill.import('blots/inline');
class NamedLinkBlot extends InlineBlot {
static create(value) {
const node = super.create(value);
node.setAttribute('href', value);
node.setAttribute('target', '_blank');
return node;
}
}
NamedLinkBlot.blotName = 'namedlink';
NamedLinkBlot.tagName = 'A';
Quill.register('formats/namedlink', NamedLinkBlot);
const Tooltip = Quill.import('ui/tooltip');
class NamedLinkTooltip extends Tooltip {
show() {
super.show();
this.root.classList.add('ql-editing');
}
}
NamedLinkTooltip.TEMPLATE = [
'<a class="ql-preview" target="_blank" href="about:blank"></a>',
'<input type="text" data-link="https://quilljs.com">',
'Url displayed',
'<input type="text" data-name="Link name">',
'<a class="ql-action"></a>',
'<a class="ql-remove"></a>',
].join('');
const QuillModule = Quill.import('core/module');
class NamedLinkModule extends QuillModule {
constructor(quill, options) {
super(quill, options);
this.tooltip = new NamedLinkTooltip(this.quill, options.bounds);
this.quill.getModule('toolbar').addHandler('namedlink', this.namedLinkHandler.bind(this));
}
namedLinkHandler(value) {
if (value) {
var range = this.quill.getSelection();
if (range == null || range.length === 0) return;
var preview = this.quill.getText(range);
this.tooltip.show();
}
}
}
Quill.register('modules/namedlink', NamedLinkModule);
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
namedlink: {},
toolbar: {
container: [
'bold',
'link',
'namedlink'
]
}
}
});
CodePen Demo
To see the tooltip:
Select any word
Click invisible button on the right of link button, your cursor will turn to hand.
Main issues that need to be addressed:
in order to customize the tooltip you will need to copy majority of the code from SnowTooltip Main pain point is that you cannot easily extend That tooltip.
you need to also adapt the code of event listeners but it should be straightforward
Here's a partial answer. Please see lines 41-64 in file "https://github.com/quilljs/quill/blob/08107187eb039eababf925c8216ee2b7d5166d41/themes/snow.js" (Please note, that the authors have since moved to TypeScript, lines 45-70?, and possibly made other changes.)
I haven't tried implementing something similar but it looks like Quill is watching a "selection-change" event and checks if the selection is on a LinkBlot with a defined link.
The SnowTooltip class includes references to the selectors, 'a.ql-preview', 'ql-editing', 'a.ql-action', and 'a.ql-remove', which we find in the link-editing tooltip.
this.quill.on(
Emitter.events.SELECTION_CHANGE,
(range, oldRange, source) => {
if (range == null) return;
if (range.length === 0 && source === Emitter.sources.USER) {
const [link, offset] = this.quill.scroll.descendant(
LinkBlot,
range.index,
);
if (link != null) {
this.linkRange = new Range(range.index - offset, link.length());
const preview = LinkBlot.formats(link.domNode);
this.preview.textContent = preview;
this.preview.setAttribute('href', preview);
this.show();
this.position(this.quill.getBounds(this.linkRange));
return;
}
} else {
delete this.linkRange;
}
this.hide();
},
);

How can I make it so only an empty state or a drop target placeholder of ui-sortable show?

I have two connected ui-sortable lists. When one of the lists is empty, I need to show a message; when that empty list is hovered while dragging, I need to show a styled drop target and hide the empty list message. I was able to program the vast majority of this code and here is a simplifed Codepen of it working.
The bug is that when you drag from the populated list over the empty list and then out again, the empty list shows both the empty list placeholder and the styled drop target. Here is a screen capture:
The root of the problem appears to be in way I calculate if the list is empty for the sortableList directive:
scope.isEmpty = function() {
if (!scope.attachments) {
return true;
} else if (scope.dragDirection === 'drag-out' && !scope.hovered) {
return scope.attachments.length <= 1;
} else if (scope.hovered) {
return false;
} else {
return scope.attachments.length === 0;
}
};
Note that I am keeping track of the state on the scope and using $apply to ensure the DOM updates like so:
function onDragStart() {
scope.$apply(function() {
scope.dragDirection = 'drag-out';
});
}
function onDragStop() {
scope.$apply(function() {
scope.dragDirection = '';
});
}
function onDragOver() {
scope.$apply(function() {
scope.hovered = true;
});
}
function onDragOut() {
scope.$apply(function() {
scope.hovered = false;
});
}
Here is the html for the directives template:
<div class="drop-target" ui-sortable="sortOptions" ng-model="attachments">
<div ng-repeat="attachment in attachments" class="attachment-box">
<span class="fa fa-bars pull-left drag-handle"></span>
<div class="link-attachment">
<a href ng-href="{{ attachment.fileUrl }}" target="_blank" class="attachment-name">{{ attachment.name }}</a>
<div class="extra-info link-info">{{ attachment.fileType }}</div>
</div>
</div>
<attachment-empty-state ng-show="isEmpty()"></attachment-empty-state>
</div>
The dependency list is quite long for the codepen to work, I simplified the code from actual production code and eliminating the dependencies would have made the custom code quite substantial. Here is a list of the dependencies if you want to try to get it running yourself: jquery, jquery-ui, angular, bootstrap, lodash, and sortable from angular-ui. There is some font-awesome in there as well.
I think I solved the problem. Here is a codepen with the solution.
Basically, the problem was that the dragout event was being (correctly) fired when your cursor dragged the item out of a sortable-list, but the placeholder would stay in the sortable-list until you dragged it into another sortable-list. So in that in between time, both the attachment-empty-state element and the placeholder would be shown in the sortable-list.
Here are the lines that I edited in the code:
Less file:
attachment-empty-state {
...
// hide empty state when the placeholder is in this list
.placeholderShown & {
display:none;
}
}
JS:
//Inside sortable-list
// Helper function
function setPlaceholderShownClass(element) {
$(".drop-target").removeClass("placeholderShown");
$(element).addClass("placeholderShown");
}
...
function onPlaceholderUpdate(container, placeholder) {
setPlaceholderShownClass(container.element.context);
...
}
If you don't like using jQuery to add and remove classes globally, you could use $rootScope.$broadcast("placeholderShown") and $rootScope.$on("placeholderShown",function() { // scope logic }. I figured a little jQuery is less complex, even though it isn't pure Angular.

Adding an active class on ng-click function

I have 2 buttons and I just want to add a class when the button has been clicked.
<button class="panel-btn"
ui-sref="basechat"
ng-click="setBaseChat()" id="base-chat-btn">Base Chat</button>
<button class="panel-btn"
ui-sref="banner"
ng-click="setBannerChat()"
id="banner-chat-btn">Banner</button>
The function on ng-click returns a true value.
Hope you guys can help me, I still don't understand very well how ng-class works
If you want to add a class to a button on click you can do something like this:
<button ng-class="{someClass: someTruthyValue}" ng-click="someFunction()">
The ng-class will look at the value of someTruthyValue and display someClass if it is True, and not otherwise.
For example, your JS could look something like:
$scope.someTruthyValue = False;
$scope.someFunction = function() {
$scope.someTruthyValue = True;
}
The docs are fairly good here
Also, as you can see in the other answers, there is more than 1 way to use ng-class, however I like my solution because it allows you the option to link additional button to reverse the class change:
<button ng-click="someReverseFunction()">
Where, certainly:
$scope.someReverseFunction = function() {
$scope.someTruthyValue = False;
}
As a simple example, you could have:
$scope.bannerClass = "baseClass"; // baseClass defined in your CSS
$scope.setBannerChat = function() {
$scope.bannerClass = "myClass"; // myClass defined in your CSS
// or $scope.bannerClass = "baseClass myClass";
}
and then:
<div ng-class="bannerClass">Hello</div>
<button ng-click=setBannerChat()></button>
You can do this in your controller:
$scope.clickFucntion = function(){
$scope.myClass = 'my-class';
}
and this in your view:
<button class="{{myClass}}" ng-click="clickFucntion()">

Resources