nodebb-plugin-composer-quill: WYSIWYG alternative to redactor
-
@JJSagan I never really needed the sourcemap file on the client side since the compiled unminified js code is essentially the same as the typescript source. Are .map files not being generated in the build directory alongside the compiled js files?
-
Hi @PitaJ ,
The issue with source map was my bad: emoji-dialog.js is compiled as module and I was working in dev mode, so the code got minified by nodebb engine. Switched to prod, and able to work.
Now for the goodies, in emoji-dialog.js kindly consider adding the following code in line 174:
//if Quill if(document.querySelectorAll('button.ql-emoji-add-emoji').length) opener = (<HTMLScriptElement[]><any>document.querySelectorAll('button.ql-emoji-add-emoji'))[0];
In a nutshell, opener is undefined when Quill is used, specifically because the emoji icon is placed on a different bar belonging to quill. The above code checks if Quill emoji selector exists, and if so properly assigns opener.
nodebb-plugin-emoji is without a set, so I installed nodebb-plugin-emoji-one, and... we have emojis displayed in the modal. Next I need to figure out how to insert them into quill.
Stay tuned.
JJ. -
Finally got nodebb-plugin-emoji working on Quill.
As an added bonus, the emojis can be used inside the post AND in the title bar.In emoji-dialog.js make the following changes, allowing quill to work side by side with composer.
- Top of file, add import:
import { active as activeComposer } from 'composer';
- In toggle, update opener if quill is available:
export function toggle( opener: HTMLElement, onClick: (e: JQuery.Event, name: string, dialog: JQuery) => void, ) { function after(dialog: JQuery) { if (dialog.hasClass('open')) { dialogActions.close(dialog); return; } dialog.off('click').on('click', '.emoji-link', (e) => { e.preventDefault(); const name = (e.currentTarget as HTMLAnchorElement).name; onClick(e, name, dialog); }); //If Quill if($('[data-uuid="' + activeComposer + '"] .ql-container.ql-snow')) opener = (<HTMLScriptElement[]><any>document.querySelectorAll('button.ql-emoji-add-emoji'))[0];
- Update toggleForInsert as follows. Note the changes allowing emojis in title bar:
export function toggleForInsert(textarea: HTMLTextAreaElement) { toggle($('[data-format="emoji-add-emoji"]').filter(':visible')[0], (e, name) => { var quill = $('[data-uuid="' + activeComposer + '"] .ql-container.ql-snow').data("quill"); if(quill) { var range = quill.getSelection(); if (range) { if (range.length == 0) { //User cursor is at index } else { //User has highlighted text - deleting and replacing with emoji quill.deleteText(range.index, range.length); } quill.clipboard.dangerouslyPasteHTML(range.index, (<HTMLElement>(<HTMLInputElement>e.currentTarget).children[0]).outerHTML); return; } //User cursor is not in editor (range is null) -> change focus to the title's textarea textarea = <HTMLTextAreaElement>(<any>(<HTMLScriptElement>document.querySelectorAll('.composer.resizable[data-uuid="' + activeComposer + '"] input.title.form-control')[0])); var text = <string>(<any>(<HTMLInputElement>e.currentTarget).children[0]).alt; var selectionStart = <number>(<any>(<HTMLScriptElement>document.querySelectorAll('.composer.resizable[data-uuid="' + activeComposer + '"] input.title.form-control')[0])).selectionStart; var selectionEnd = <number>(<any>(<HTMLScriptElement>document.querySelectorAll('.composer.resizable[data-uuid="' + activeComposer + '"] input.title.form-control')[0])).selectionStart; } else { var text = `:${name}: `; var {selectionStart, selectionEnd} = textarea; } var end = selectionEnd + text.length; var start = selectionStart === selectionEnd ? end : selectionStart; insertIntoTextarea(textarea, text); updateTextareaSelection(textarea, start, end); $(textarea).trigger('input'); }); }
Note I am using dangerouslyPasteHTML that slightly changes the link/img html of the emoji element (for example, removes emoji classes). We can use the quill converter from quill-delta-to-html, perhaps it will make a slightly better job, or hardcode ourselves, that is, if these classes means anything...
As for dangerouslyPaste, using the converter above will not make it any safer. We have to sanitize the delta in the server. Any attempts to sanitize in the client are futile, as someone can just take over the client code and push whatever he wants to the server...
JJ.
-
@JJSagan is there any specific reason you aren't implementing the same composer API which the redactor and default composers implement?
Congrats on getting it to work, though.
-
Hi @PitaJ,
Unfortunately Quill does not use textarea to store html code representing the edited text, but has its own JSON-like format. On the negative side, the default composer implementation will not work. On the positive side, this is a big plus for security, as someone can't just paste HTML and have that break the server. That does not mean we should not implement code-sanitize on the server side, as someone can post evil code that will be converted to html when other users view posts.
Note that I edited my post above to include my final emoji-dialog code. It will now insert the emojis into the proper place, and also allow emojis in the post tile as an added bonus.
Have a good weekend!
JJ. -
@JJSagan does redactor have a textarea doing that? I didn't think it did. The purpose of the composer API is so that these manual implementations don't have to be done. If there are changes we need to make to the API for it to work with quils model, we can do that.
-
Let me expand on that. The
textarea
parameter doesn't have to be an HTML textarea. Really, all it is in the context of the API is an object on which the various helper functions incomposer/controls
operate. Realistically, you can either ignore it or provide a mock object emulating some of its behavior (like theselectionStart
andselectionEnd
properties).Edit: another thing. Right now it looks like you're just pushing in the parsed HTML of the emoji. I'm sure this works and has the advantage of showing up in the preview. However, this isn't ideal because parsed emojis link directly to the emoji images. If the forum switches emoji packs, these will all break since they are not parsed server-side when a post is requested, but rather stored as pre-parsed HTML.
-
Hi @PitaJ
In Composer and Redactor, you have one lumped textarea. The plugin can insert anything to it, as html. The text area is saved in the db, as-is.
So for example, in Composer/Redactor, the phrase: "Gandalf the Grey" would be saved as is, or maybe as "<p>Gandalf the Grey</p>" however with Quill, you'd get:
delta = { "ops": [ { "insert": "Gandalf the Grey\n" } ] }
Read about delta here: https://quilljs.com/docs/delta/
As you can see, with quill, the notion of pushing "textarea" around (for example, in composer handlers) simply does not work. Perhaps for a unified architecture, consider pushing just the "composer.active" (uuid, source of events) and the changes.
As such, a unified composer would receive changes from the plugins, in the form of (composer.active, index, len, data), and translate these changes to the editors (whether in html or delta format), without user intervention or knowledge.
A good place to start is reviewing the quill's APIs, https://quilljs.com/docs/api/. They may perhaps provide an idea how composer APIs can be enhanced.
JJ.
-
Hi @PitaJ ,
I can look into changing the store format.
May I assume you would like to see emojis stored as follows::${name}:
- for example: " "Can you please let me know what function i can call to render the emoji? is the function accepting ":${name}: " returns html link?
Rendering the emojis in posts should be possible (i'll have to write a small blot).
However, displaying the emojis inside quill editor may prove challenging as I'll have to write a plugin for quill.One Q/D solution is to use the store a link (as I do now), but just before the post is sent out for storage, to replace the link with :${name}:. However, long term you may want to capture text such as and automatically replace with emoji, so a quill plugin may make more sense.
Looking forward for your response,
JJ. -
@JJSagan said in nodebb-plugin-composer-quill: WYSIWYG alternative to redactor:
Can you please let me know what function i can call to render the emoji?
The function you're looking for is
buildEmoji
: https://github.com/NodeBB/nodebb-plugin-emoji/blob/master/public/lib/emoji.ts#L7You have to pass in the emoji object, which you get from the
table
. Both are exported from theemoji
requirejs module. -
Hi @PitaJ ,
Looks like I am failing to do this from a quill-plugin. Note however, I am pretty novice, and perhaps lack sufficient knowledge to make it work.
May I propose to insert the emoji name and bitmap (without the link) into the editor. That will enable the emoji to display properly inside the editor prior to saving. When the post data is read from the DB on its way to the client, the client will use the :CODE: and replace the bitmap, that is PRIOR to rendering or editing by quill again.
The disadvantage is client processing time on scanning/updating the delta.
Your thoughts?
JJ. -
Not yet, but one problem at a time. There are a lot of posts here in this thread!
Keep in mind emoji already work in Quill, just not the toolbar selector and it doesn't show the emoji in the editor itself. Right now it just shows
:laughing:
or whatnot, which is acceptable, but of course, not 100%. I put in a bit of effort at the start to ensure the autocomplete is working (because mentions requires it as well), so the emoji autocomplete works as expected as well. -
Hi @julian great job!
I reviewed the github page, and looks like you were able to reproduce the issues I mentioned.
I noted that you have bypassed rendering quill by keeping two versions of the post, one in html and one in delta. That will solve a lot of issues down the line, so I completely understand.
Kindly consider:- Fixing the tooltips,
- Allowing icons in the chat windows.
- Move the tag below the topic (so it may appear in mobile).
All have a potential solution above.
Do you need help with anything?
LMK,
JJ.