Skip to content

Commit

Permalink
Fix: Incorrect paragraph breaks when next node is treated as a block
Browse files Browse the repository at this point in the history
  • Loading branch information
CatHood0 committed Dec 30, 2024
1 parent 1e81c6c commit 18a2f64
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 67 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
**/node_modules/
src/index.ts
.eslintrc.json
.prettierrc
tsconfig.json

6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"semi": true
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.0.6

* Fix: Incorrect paragraph breaks when next node is treated as a block

## 1.0.5

* Fix: header ignores the attributes from span children
Expand Down
26 changes: 20 additions & 6 deletions dist/html_to_delta.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ exports.HtmlToDelta = void 0;
const quill_delta_1 = __importDefault(require("quill-delta"));
const node_html_parser_1 = require("node-html-parser");
const default_html_to_operation_1 = require("./default_html_to_operation");
const html_utils_1 = require("./html_utils");
/**
* Default converter for html to Delta
**/
Expand All @@ -32,14 +33,21 @@ class HtmlToDelta {
*
**/
convert(htmlText) {
var _a, _b, _c, _d;
var _a, _b, _c, _d, _e;
const parsedHtmlText = htmlText.split('\n').map((e) => e.trimStart()).join('');
const delta = new quill_delta_1.default();
const document = (0, node_html_parser_1.parse)(htmlText);
const document = (0, node_html_parser_1.parse)(parsedHtmlText);
const nodesToProcess = ((_a = document.querySelector('body')) === null || _a === void 0 ? void 0 : _a.childNodes) ||
((_b = document.querySelector('html')) === null || _b === void 0 ? void 0 : _b.childNodes) ||
document.childNodes;
for (let index = 0; index < nodesToProcess.length; index++) {
const node = nodesToProcess.at(index);
let nextNode = (_c = nodesToProcess.at(index + 1)) !== null && _c !== void 0 ? _c : null;
let nextIsBlock = nextNode === null || nextNode === undefined
? false
: nextNode instanceof node_html_parser_1.HTMLElement
? (0, html_utils_1.isBlock)(nextNode.localName)
: false;
if (node instanceof node_html_parser_1.HTMLElement) {
//first just verify if the customBlocks aren't empty and then store on them to
//validate if one of them make match with the current Node
Expand All @@ -50,9 +58,9 @@ class HtmlToDelta {
if (customPart.matches(node)) {
const ops = customPart.convert(node);
for (let indexOp = 0; indexOp < ops.length; indexOp++) {
const insertion = (_c = ops.at(indexOp)) === null || _c === void 0 ? void 0 : _c.insert;
const insertion = (_d = ops.at(indexOp)) === null || _d === void 0 ? void 0 : _d.insert;
if (insertion !== undefined) {
delta.insert(insertion, (_d = ops.at(indexOp)) === null || _d === void 0 ? void 0 : _d.attributes);
delta.insert(insertion, (_e = ops.at(indexOp)) === null || _e === void 0 ? void 0 : _e.attributes);
}
}
continueLoop = true;
Expand All @@ -65,21 +73,27 @@ class HtmlToDelta {
}
if (this.blackNodesList.indexOf(node.localName) >= 0) {
delta.push({ insert: node.text });
if (nextIsBlock)
delta.insert('\n');
continue;
}
const operations = this.htmlToOp.resolveCurrentElement(node, 0);
const operations = this.htmlToOp.resolveCurrentElement(node, 0, nextIsBlock);
operations.forEach((op) => {
delta.push(op);
});
}
if (node instanceof node_html_parser_1.TextNode) {
delta.insert(node.text);
if (nextIsBlock)
delta.push({ insert: '\n' });
}
}
const operation = delta.ops[delta.ops.length - 1];
const hasAttributes = operation.attributes !== null && operation.attributes !== undefined;
const lastData = operation.insert;
if (typeof lastData !== 'string' || !lastData.endsWith('\n') || hasAttributes) {
if (typeof lastData !== 'string' ||
!lastData.endsWith('\n') ||
hasAttributes) {
delta.insert('\n');
}
return delta;
Expand Down
2 changes: 1 addition & 1 deletion dist/html_to_operation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export declare abstract class HtmlOperations {
* A list of Delta operations corresponding to the HTML element.
*
**/
resolveCurrentElement(element: HTMLElement, indentLevel?: number): Op[];
resolveCurrentElement(element: HTMLElement, indentLevel?: number, nextIsBlock?: Boolean): Op[];
abstract paragraphToOp(element: HTMLElement): Op[];
abstract spanToOp(element: HTMLElement): Op[];
abstract headerToOp(element: HTMLElement): Op[];
Expand Down
4 changes: 3 additions & 1 deletion dist/html_to_operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class HtmlOperations {
* A list of Delta operations corresponding to the HTML element.
*
**/
resolveCurrentElement(element, indentLevel = 0) {
resolveCurrentElement(element, indentLevel = 0, nextIsBlock = false) {
let ops = [];
if (!element.tagName) {
ops.push({ insert: element.text || '' });
Expand Down Expand Up @@ -85,6 +85,8 @@ class HtmlOperations {
if ((0, html_utils_1.isDivBlock)(element)) {
ops.push(...this.divToOp(element));
}
if (nextIsBlock)
ops.push({ insert: '\n' });
return ops;
}
setCustomBlocks(customBlocks) {
Expand Down
1 change: 0 additions & 1 deletion dist/html_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ function isInline(tagName) {
}
function isBlock(tagName) {
return [
'p',
'h1',
'h2',
'h3',
Expand Down
65 changes: 25 additions & 40 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,34 @@
Object.defineProperty(exports, "__esModule", { value: true });
const html_to_delta_1 = require("./html_to_delta");
const delta = new html_to_delta_1.HtmlToDelta().convert('<p>Hello <strong>Word</strong>!<br></p>');
delta.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
const delta2 = new html_to_delta_1.HtmlToDelta().convert('<p><span style="color:#F06292FF">This is just pink </span><br/><br/><span style="color:#4DD0E1FF">This is just blue</span><br></p>');
console.log('\n');
delta2.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
const delta3 = new html_to_delta_1.HtmlToDelta().convert('<p>This is a <a href="https://example.com">link</a> to example.com</p>');
console.log('\n');
delta3.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
const delta4 = new html_to_delta_1.HtmlToDelta().convert('<p style="font-size: 0.75em">This is a paragraph example</p>');
console.log('\n');
delta4.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
const delta4 = new html_to_delta_1.HtmlToDelta().convert('<p style="font-size: 0.75em">This is a paragraph example</p><h1>Header</h1>');
const delta5 = new html_to_delta_1.HtmlToDelta().convert('<p>This is an image: <img align="center" style="width: 50px;height: 250px;margin: 5px;" src="https://example.com/image.png"/> example</p>');
console.log('\n');
delta5.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
const delta6 = new html_to_delta_1.HtmlToDelta().convert('<ol><li>First <strong>item</strong><ul><li>SubItem <a href="https://www.google.com">1</a><ol><li>Sub 1.5</li></ol></li><li>SubItem 2</li></ul></li><li>Second item</li></ol>');
console.log('\n');
delta6.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
const delta7 = new html_to_delta_1.HtmlToDelta().convert('<h1>Example title <span style="font-size: 20px; color: var(--); background-color: #ffffffff">with an internal span</span></h1>');
console.log('\n');
delta7.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
const delta8 = new html_to_delta_1.HtmlToDelta(null, null, ['div']).convert('<h1>Example title <span style="font-size: 20px; color: var(--); background-color: #ffffffff">with an internal span</span></h1><div><p align="center">Paragraph ignored</p></div>');
console.log('\n');
delta8.ops.forEach((op) => {
console.log(JSON.stringify(op.insert) +
(op.attributes ? JSON.stringify(op.attributes) : ''));
});
printOps(delta);
console.log('\nPart 2\n');
printOps(delta2);
console.log('\nPart 3\n');
printOps(delta3);
console.log('\nPart 4\n');
printOps(delta4);
console.log('\nPart 5\n');
printOps(delta5);
console.log('\nPart 6\n');
printOps(delta6);
console.log('\nPart 7\n');
printOps(delta7);
console.log('\nPart 8\n');
printOps(delta8);
function printOps(delta) {
delta.ops.forEach((op) => {
var _a;
console.log({
insert: JSON.stringify(op.insert),
attributes: (_a = op.attributes) !== null && _a !== void 0 ? _a : null,
});
});
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "quill-delta-from-html",
"version": "1.0.5",
"version": "1.0.6",
"description": "Convert easily HTML inputs content to Quill Js Delta format",
"main": "dist/quill-delta-from-html.js",
"types": "dist/quill-delta-from-html.d.ts",
Expand Down
41 changes: 30 additions & 11 deletions src/html_to_delta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { parse, HTMLElement, TextNode } from 'node-html-parser';
import { DefaultHtmlToOperations } from './default_html_to_operation';
import { HtmlOperations } from './html_to_operation';
import { CustomHtmlPart } from './custom_html_part';
import { isBlock } from './html_utils';

/**
* Default converter for html to Delta
Expand All @@ -16,18 +17,18 @@ export class HtmlToDelta {
/** This is a list that must contains only the tag name
* of the all HTML Nodes (something like: [`p`, `div`, `h1`]) that will be
* inserted as plain text
*
*
* # Example
* Assume that you want to ignore just HTML containers. Then just need
* to do something like this:
*
*
* ```typescript
* let containerBlackList: string[] = ['div', 'section', 'article'];
*
*
* let converter: HtmlToDelta = new HtmlToDelta(null, null, containerBlackList);
* let delta = converter.convert(<your_html>);
* ```
**/
**/
private blackNodesList: string[];

/**
Expand Down Expand Up @@ -76,15 +77,22 @@ export class HtmlToDelta {
*
**/
convert(htmlText: string): Delta {
const parsedHtmlText = htmlText.split('\n').map((e) => e.trimStart()).join('');
const delta = new Delta();
const document = parse(htmlText);
const document = parse(parsedHtmlText);
const nodesToProcess =
document.querySelector('body')?.childNodes ||
document.querySelector('html')?.childNodes ||
document.childNodes;
for (let index: number = 0; index < nodesToProcess.length; index++) {
for (let index = 0; index < nodesToProcess.length; index++) {
const node = nodesToProcess.at(index);

let nextNode = nodesToProcess.at(index + 1) ?? null;
let nextIsBlock =
nextNode === null || nextNode === undefined
? false
: nextNode instanceof HTMLElement
? isBlock((nextNode as HTMLElement).localName)
: false;
if (node instanceof HTMLElement) {
//first just verify if the customBlocks aren't empty and then store on them to
//validate if one of them make match with the current Node
Expand Down Expand Up @@ -113,25 +121,36 @@ export class HtmlToDelta {
continue;
}
}
if(this.blackNodesList.indexOf(node.localName) >= 0){
if (this.blackNodesList.indexOf(node.localName) >= 0) {
delta.push({ insert: node.text });
if (nextIsBlock) delta.insert('\n');
continue;
}
const operations: Op[] = this.htmlToOp.resolveCurrentElement(node, 0);
const operations: Op[] = this.htmlToOp.resolveCurrentElement(
node,
0,
nextIsBlock,
);
operations.forEach((op) => {
delta.push(op);
});
}
if (node instanceof TextNode) {
delta.insert((node as TextNode).text);
if (nextIsBlock) delta.push({insert: '\n'});
}
}

const operation: Op = delta.ops[delta.ops.length - 1];
const hasAttributes = operation.attributes !== null && operation.attributes !== undefined;
const hasAttributes =
operation.attributes !== null && operation.attributes !== undefined;
const lastData = operation.insert;

if (typeof lastData !== 'string' || !lastData.endsWith('\n') || hasAttributes) {
if (
typeof lastData !== 'string' ||
!lastData.endsWith('\n') ||
hasAttributes
) {
delta.insert('\n');
}
return delta;
Expand Down
8 changes: 7 additions & 1 deletion src/html_to_operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ export abstract class HtmlOperations {
* A list of Delta operations corresponding to the HTML element.
*
**/
resolveCurrentElement(element: HTMLElement, indentLevel: number = 0): Op[] {
resolveCurrentElement(
element: HTMLElement,
indentLevel: number = 0,
nextIsBlock: Boolean = false,
): Op[] {
let ops: Op[] = [];
if (!element.tagName) {
ops.push({ insert: element.text || '' });
Expand Down Expand Up @@ -93,6 +97,8 @@ export abstract class HtmlOperations {
if (isDivBlock(element)) {
ops.push(...this.divToOp(element));
}

if (nextIsBlock) ops.push({ insert: '\n' });
return ops;
}

Expand Down
1 change: 0 additions & 1 deletion src/html_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ export function isInline(tagName: string): Boolean {

export function isBlock(tagName: string): Boolean {
return [
'p',
'h1',
'h2',
'h3',
Expand Down
15 changes: 15 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"declaration": true,
"lib": ["es2017", "es7", "es6", "dom"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

0 comments on commit 18a2f64

Please sign in to comment.