joelcolucci / node-quill-converter

Convert HTML to a Quill Delta or a Quill Delta to HTML
MIT License
95 stars 25 forks source link

Round-tripped HTML sometimes leaves the Quill component in an unusable state. #22

Open Drarok opened 3 years ago

Drarok commented 3 years ago
const { convertHtmlToDelta, convertDeltaToHtml } = require('node-quill-converter');

const sampleData = [
  `<pre>Hola</pre>`,
  `<h3>Hello</h3>`,
];

sampleData.forEach((sample) => {
  const delta = convertHtmlToDelta(sample);
  const html = convertDeltaToHtml(delta);
  console.log('html', html);
});

Output:

html <pre class="ql-syntax" spellcheck="false">Hola
</pre>
about:blank:7
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Quill=e():t.Quill=e()} // SNIPPED MASSIVE JS DUMP

TypeError: Cannot read property 'length' of undefined
    at s.insert (about:blank:7:10439)
    at e.value (about:blank:7:158285)
    at e.value (about:blank:7:158101)
    at exports.convertHtmlToDelta (/Users/drarok/Development/project_name/node_modules/node-quill-converter/lib/index.js:53:37)
    at /Users/drarok/Development/project_name/wat.js:9:17
    at Array.forEach (<anonymous>)
    at Object.<anonymous> (/Users/drarok/Development/project_name/wat.js:8:12)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
Drarok commented 3 years ago

The same sample code is runnable on RunKit.

Drarok commented 3 years ago

Removing the use of a cached instance fixes this issue, but might be a nuclear option – I don't know how inefficient this is.

I've found a better way! Keep the cached instance, but clear its text before each use! Also, I replaced the infinite loop previously caused by replacing execCommand with a function that calls itself.

This works:

const fs = require('fs');
const path = require('path');

const jsdom = require('jsdom');
const { JSDOM } = jsdom;

let quillFilePath = require.resolve('quill');
let quillMinFilePath = quillFilePath.replace('quill.js', 'quill.min.js');

let quillLibrary = fs.readFileSync(quillMinFilePath);
let mutationObserverPolyfill = fs.readFileSync(path.join(__dirname, 'polyfill.js'));

const JSDOM_TEMPLATE = `
  <div id="editor">hello</div>
  <script>${mutationObserverPolyfill}</script>
  <script>${quillLibrary}</script>
  <script>
    document.getSelection = function() {
      return {
        getRangeAt: function() { }
      };
    };
    document.execCommand = () => false;
  </script>
`;

const cache = {};
const JSDOM_OPTIONS = { runScripts: 'dangerously', resources: 'usable' };
const getQuill = () => {
  if (!cache.quill) {
    const DOM = new JSDOM(JSDOM_TEMPLATE, JSDOM_OPTIONS);
    cache.quill = new DOM.window.Quill('#editor');
  } else {
    cache.quill.setText(''); // This is the special sauce
  }

  return cache.quill;
}

exports.convertTextToDelta = (text) => {
  const quill = getQuill();
  quill.setText(text);
  return quill.getContents();
};

exports.convertHtmlToDelta = (html) => {
  const quill = getQuill();
  return quill.clipboard.convert(html);
};

exports.convertDeltaToHtml = (delta) => {
  const quill = getQuill();
  quill.setContents(delta);
  return quill.root.innerHTML;
};
Drarok commented 3 years ago

Oops, I didn't mean for this issue to close. I've made a fork to solve my issue ASAP.