Custom blot format in Quill not translating to HTML - javascript

I am setting up Quill to use as a rich text editor in a blog project. I have the editor working correctly and can store user posts in mongoDB, retrieve them later, and display them.
My code for doing this involves grabbing the delta from quill, stringifying it, then encoding this as a URI. This is what is stored in my db. These actions take place upon hitting my post button but before submitting the form. I am also storing the plain text for other purposes.
function parseQuill(){
document.getElementById("body").value = encodeURIComponent(JSON.stringify(quill.getContents()));
document.getElementById("bodyText").value = quill.getText();
document.getElementById("compose-form").submit();
}
When accessing a blog post, the delta is retrieved from the db and converted back into html for viewing.
This takes place on the backend. The decoded HTML is sent to my post page and rendered with ejs.
app.get("/posts/:postID", function(req, res){
User.findOne({name: "user1"}, function(err, foundUser){
let posts = foundUser.posts;
posts.forEach(function(post){
if (post._id == req.params.postID){
const decoded = JSON.parse(decodeURIComponent(post.content));
const converter = new QuillDeltaToHtmlConverter(decoded.ops);
const decodedHTML = converter.convert();
console.log(decodedHTML);
post.decoded_HTML = decodedHTML;
res.render("post", {post: post});
}
}
);
});
});
This works for all of the default formats that quill offers.
I have been following along with the Quill guide "Cloning medium with parchment" and have attempted to implement the custom divider blot. My code is identical to what is happening there minus the jQuery. My horizontal rule appears in the text editor and behaves as expected.
My issue arises when saving the delta and attempting to convert it back to html. The delta.ops for a post with a horizontal rule looks something like this.
{"ops":[
{"insert":"Some text followed by a horizontal rule.\n"},
{"insert":{"divider":true}},
{"insert":"Some more text.\n"}]}"
The line representing the horizontal rule is the second insert statement of "divider": true. After storing this as a URI component and decoding it back to HTML, the HTML looks like this:
<p>Some text followed by a horizontal rule.<br/>Some more text.</p>
The horizontal rule tag is nowhere to be found. How can I get this to be interpreted correctly and show up?
If I produce a hidden Quill editor container on my post page, I can load in the pure delta and extract the html with quill.root.innerHTML. This produces HTML that contains the HR. I was hoping to avoid inserting the hidden quill container, if at all possible.

I was an idiot and missed an important step in the html decoding process. Leaving this question up with this answer (which solves the problem) incase anyone else stumbles upon a moment of mental ineptitude.
I was using the package quill-delta-to-html to convert my delta's back to usable html. Of course this package doesn't know how to register custom blots. You have to do that manually before converting.
const dividerOp = {
insert: {
type: "divider",
value: true
},
attributes: {
renderAsBlock: true
}
}
converter.renderCustomWith(function(dividerOp){
if (dividerOp.insert.type === "divider"){
return "<hr>"
} else {
console.log("custom blot convert error");
}
});
const decodedHTML = converter.convert();
Quill was doing everything right on it's end. I had a lapse in memory and forgot I was relying on an external package to handle my delta to html conversions. Adding this customBlot render solves the issue.

Related

Insert formatted content from HTML in Quill editor on load

I want to use Quill for writing articles on my website, however I may need to edit those articles at some point.
To retrieve my formatted content from Quill and put it into the database, I call quill.root.innerHTML, and everything goes well.
However, I'm struggling to find how I could get this HTML content, and then have it displayed in my Quill editor, formatted exactly as it was when I submitted it, when the page loads.
Any help would be welcome, thanks in advance!
Quill's contents are described by the JSON Delta format, and the API provides methods to getContents and setContents using this format:
// Retrieve JSON using the Quill API
const delta = quill.getContents()
const ops = delta.ops;
// Store the JSON representation instead of the raw HTML
storeInDB(ops);
Then you can retrieve the JSON and it should all "just work":
const ops = fetchFromDB();
quill.setContents(ops);

Sanitizing JSON data for usage as JavaScript object

I'm going to be dynamically generating a JSON file which is then passed to SCEditor as the emoticons object; this data will come from the database, so essentially it should be safe, but one can never be 100% sure.
This is how it is being called:
// Create var to store emoticons
var emoticons = false;
$.getJSON('../../images/emoticons/default/emoticons.json')
.done(function(response) {
emoticons = response;
console.log(emoticons);
})
.always(function() {
// always initialize sceditor
$(".sceditor").sceditor({
// Other options.....
plugins: "bbcode",
emoticons: emoticons,
});
});
An example of the JSON file would look like:
{
"dropdown": {
":)": "smile.png",
":angel:": "angel.png",
":angry:": "angry.png",
"8-)": "cool.png",
":'(": "cwy.png",
}
}
So the emoticon code and filename are pulled from the database. Is there anything I need to do here other than escape double quotes? Whilst this data will be coming from the database, it's possible the codes/filenames will be provided by the user.
When I store them in the database I will be stripping tags with PHP's strip_tags function.
I wanted to avoid turning the data into html entities as it doesn't seem to play nice with the editor as it doesn't turn the emoticons into smileys within the editor if you say set the code as :") - it will literally output in the editor as :") rather than show the smiley.
Edit: To see an example of how the code is used check out the SCEditor demo. Only difference is the demo uses the default codes provided within the JS file itself and mine will be provided via a JSON file passed as an option.
What is are my best options here?

Storing HTML in Firebase (AngularFire), good idea or bad?

Is it a good idea to store HTML in Firebase (AngularFire)?
I have a website where I am creating an admin site where users can make HTML elements. I want people to save these elements and the order and the content within the elements. So I thought it would be much easier to just store the whole HTML as a string and load it in when they return. Bad idea?
Here is what I have (simplification):
$scope.save = function() {
var refState = new Firebase("https://<name>.firebaseio.com/users/" + currentAuth.uid + "/state");
var html = "<div>hello</div>";
refState.set({
"state": html
}, function(error) {
if (error) {
console.log("not been saved")
}
})
}
And in my HTML I retrieve want to display it like this using Angular, (yeah I know now how to render HTML in Angular thanks to the comments :)
<div class="well col-md-12">
{{sync[3].state}}
</div>
Storing stringified HTML in firebase is no worse than storing it in a different datastore. You'll want to consider XSS issues, including things like what if they define <style>body{display:none}</style> in their html.
Are you creating a real full fleshed content creation system? If so, it's sometimes hard to get away from user defined HTML, usually from CKeditor, tinymce, etc. However, if the items that they're building are all similar, you should consider how you can store/restore them in a better data format. Most of the time there is a better way to save and restore user defined content that storing straight HTML.
I'd suggest checking out Firepad.
Firepad is a drop-in "Open source collaborative code and text editing" experience for Firebase apps.
"Firepad can use either the CodeMirror editor or the Ace editor to render documents."
Easily allows for a rich text-editor experience that seamlessly stores/syncs the content in a Firebase instance.
As the documentation describes, this is how you initialize Firepad:
<div id="firepad"></div>
<script>
var firepadRef = new Firebase('<FIREBASE URL>');
var codeMirror = CodeMirror(document.getElementById('firepad'), { lineWrapping: true });
var firepad = Firepad.fromCodeMirror(firepadRef, codeMirror,
{ richTextShortcuts: true, richTextToolbar: true, defaultText: 'Hello, World!' });
</script>
It's perfectly fine to store HTML in Firebase.
Koding.com, Nitrous.io, and more use Firepad for their collaborative code editor products.
I think it's very bad idea to store html in firebase, store only pain text
How to render html with angular templates

How to manipulate HTML table once it's returned from backend like Node.js?

Here's the situation: I use Node.js as my backend, and use markdown to edit and post my blog article. And when a client requests the specific URL, such as http://www.example.com/blog/article_1, I returned the blog contents from Node.js with some template like ejs, which would be something like the follows:
app.get("/blog/article1", function(req, res) {
var article = something // this is a valid HTML converted from a markdown file
res.render("article1", {
title: "my blog article 1",
article: article
});
});
In the above code, I render article.ejs with title and article variable. The article variable is a valid HTML to be injected to the ejs template. So far, it' fine.
However, if I want to display a HTML table which is written in the original markdown file, with Bootstrap 3's responsive table functionality, (i.e. <div class="table-responsive"><table class="table">...actual table...</table></div>), how can I do it? Right now the table in my markdown file is just a markdown file, and I don't think that it's the best idea to just modify all of my markdown files on which I insert or wrap with the <div class="table-responsive">...</div> line; the files might also be used in a situation other than Bootstrap.
In other words, is it feasible to dynamically or programmatically inject the responsive functionality to the table once the template is returned by Node.js? And is it also feasible to inject the responsive table functionality selectively? (in other words choose arbitrarily some tables that I want to add the responsive function?)
Continuing on from the comments: It's actually not that difficult to fork and modify a project. The faster you can get used to working with open source libraries the better your experience will be with Node. Things move pretty quickly in the Node world, and sometimes things won't work like they are expected to. You can either wait around for a fix, or roll up your sleeves and pitch in.
I found a way to update the markdown templates using their addTemplate method. However the version of Marked the project is using (2.8) doesn't support custom templates. I've forked the repository and updated the version of marked as well as fixed the issues this caused with the tests. I also added a restriction to prevent it from using Express 4 which breaks all the tests. I submitted these as a pull request to the original repo, but in the mean time you could use my version to write something like the following.
untested
var
express = require('express'),
app = express(),
Poet = require('poet'),
marked = require('marked'),
renderer = new marked.Renderer();
renderer.table = function(header, body) {
return '<div class="table-responsive"><table class="table">' + header + body + '</table></div>';
}
var poet = Poet(app, {
posts: './_posts/',
postsPerPage: 5,
metaFormat: 'json'
});
poet.addTemplate({ ext: 'markdown', fn: function(s) {
return marked(s);
}});
Alternatively, if all you're using poet for is the markdown conversion, you might as well use marked directly and cut out the dependency on poet.

webOS/Ares : read JSON from URL, assign to label

I've used the webOS Ares tool to create a relatively simple App. It displays an image and underneath the image are two labels. One is static, and the other label should be updated with new information by tapping the image.
When I tap the image, I wish to obtain a JSON object via a URL (http://jonathanstark.com/card/api/latest). The typcial JSON that is returned looks like this:
{"balance":{"amount":"0","amount_formatted":"$0.00","balance_id":"28087","created_at":"2011-08-09T12:17:02-0700","message":"My balance is $0.00 as of Aug 9th at 3:17pm EDT (America\/New_York)"}}
I want to parse the JSON's "amount_formatted" field and assign the result to the dynamic label (called cardBalance in main-chrome.js). I know that the JSON should return a single object, per the API.
If that goes well, I will create an additional label and convert/assign the "created_at" field to an additional label, but I want to walk before I run.
I'm having some trouble using AJAX to get the JSON, parse the JSON, and assign a string to one of the labels.
After I get this working, I plan to see if I can load this result on the application's load instead of first requiring the user to tap.
So far, this is my code in the main-assistant.js file. jCard is the image.
Code:
function MainAssistant(argFromPusher) {}
MainAssistant.prototype = {
setup: function() {
Ares.setupSceneAssistant(this);
},
cleanup: function() {
Ares.cleanupSceneAssistant(this);
},
giveCoffeeTap: function(inSender, event) {
window.location = "http://jonathanstark.com/card/#give-a-coffee";
},
jcardImageTap: function(inSender, event) {
//get "amount_formatted" in JSON from http://jonathanstark.com/card/api/latest
//and assign it to the "updatedBalance" label.
// I need to use Ajax.Request here.
Mojo.Log.info("Requesting latest card balance from Jonathan's Card");
var balanceRequest = new Ajax.Request("http://jonathanstark.com/card/api/latest", {
method: 'get',
evalJSON: 'false',
onSuccess: this.balanceRequestSuccess.bind(this),
onFailure: this.balanceRequestFailure.bind(this)
});
//After I can get the balance working, also get "created_at", parse it, and reformat it in the local time prefs.
},
//Test
balanceRequestSuccess: function(balanceResponse) {
//Chrome says that the page is returning X-JSON.
balanceJSON = balanceResponse.headerJSON;
var balanceAmtFromWeb = balanceJSON.getElementsByTagName("amount_formatted");
Mojo.Log.info(balanceAmtFromWeb[0]);
//The label I wish to update is named "updatedBalance" in main-chrome.js
updatedBalance.label = balanceAmtFromWeb[0];
},
balanceRequestFailure: function(balanceResponse) {
Mojo.Log.info("Failed to get the card balance: " + balanceResponse.getAllHeaders());
Mojo.Log.info(balanceResponse.responseText);
Mojo.Controller.errorDialog("Failed to load the latest card balance.");
},
//End test
btnGiveCoffeeTap: function(inSender, event) {
window.location = "http://jonathanstark.com/card/#give-a-coffee";
}
};
Here is a screenshot of the application running in the Chrome browser:
In the browser, I get some additional errors that weren't present in the Ares log viewer:
XMLHttpRequest cannot load http://jonathanstark.com/card/api/latest. Origin https://ares.palm.com is not allowed by Access-Control-Allow-Origin.
and
Refused to get unsafe header "X-JSON"
Any assistance is appreciated.
Ajax is the right tool for the job. Since webOS comes packaged with the Prototype library, try using it's Ajax.Request function to do the job. To see some examples of it, you can check out the source code to a webOS app I wrote, Plogger, that accesses Blogger on webOS using Ajax calls. In particular, the source for my post-list-assistant is probably the cleanest to look at to get the idea.
Ajax is pretty much the way you want to get data, even if it sometimes feels like overkill, since it's one of the few ways you can get asynchronous behavior in JavaScript. Otherwise you'd end up with code that hangs the interface while waiting on a response from a server (JavaScript is single threaded).

Categories

Resources