Skip to content

Commit

Permalink
Fix parsing marks
Browse files Browse the repository at this point in the history
  • Loading branch information
benmerckx committed Oct 15, 2024
1 parent 9ecc203 commit b1578cc
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 20 deletions.
35 changes: 35 additions & 0 deletions src/core/field/RichTextField.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {RichTextEditor} from 'alinea/core'
import {TextDoc} from 'alinea/core/TextDoc'
import {test} from 'uvu'
import * as assert from 'uvu/assert'

test('parse html nodes & marks', async () => {
const editor = new RichTextEditor()
const html =
'<p><strong><i>bold</i></strong><em>italic</em><b></b><u>underline</u> <s>strike</s> <a href="https://example.com">link</a></p>'
editor.addHtml(html)
assert.equal(editor.value(), [
{
_type: 'paragraph',
content: [
{
_type: 'text',
text: 'bold',
marks: [{_type: 'bold'}, {_type: 'italic'}]
},
{_type: 'text', text: 'italic', marks: [{_type: 'italic'}]},
{_type: 'text', text: 'underline', marks: [{_type: 'underline'}]},
{_type: 'text', text: ' '},
{_type: 'text', text: 'strike', marks: [{_type: 'strike'}]},
{_type: 'text', text: ' '},
{
_type: 'text',
text: 'link',
marks: [{_type: 'link', href: 'https://example.com'}]
}
]
}
] satisfies TextDoc)
})

test.run()
63 changes: 43 additions & 20 deletions src/core/field/RichTextField.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Parser} from 'htmlparser2'
import {Field, FieldMeta, FieldOptions} from '../Field.js'
import {Schema} from '../Schema.js'
import {ElementNode, TextDoc} from '../TextDoc.js'
import {ElementNode, Mark, TextDoc, TextNode} from '../TextDoc.js'
import {RichTextMutator, RichTextShape} from '../shape/RichTextShape.js'

export class RichTextField<
Expand Down Expand Up @@ -64,12 +64,6 @@ function mapNode(
return {_type: type, level, content: []}
case 'p':
return {_type: 'paragraph', content: []}
case 'b':
case 'strong':
return {_type: 'bold', content: []}
case 'i':
case 'em':
return {_type: 'italic', content: []}
case 'ul':
return {_type: 'bulletList', content: []}
case 'ol':
Expand All @@ -82,11 +76,6 @@ function mapNode(
return {_type: 'horizontalRule'}
case 'br':
return {_type: 'hardBreak'}
case 'small':
return {_type: 'small', content: []}
case 'a':
// Todo: pick what we need
return {_type: 'link', ...attributes, content: []}
case 'table':
return {_type: 'table', content: []}
case 'tbody':
Expand All @@ -100,23 +89,57 @@ function mapNode(
}
}

function mapMark(
name: string,
attributes: Record<string, string>
): Mark | undefined {
switch (name) {
case 'b':
case 'strong':
return {_type: 'bold'}
case 'i':
case 'em':
return {_type: 'italic'}
case 'u':
return {_type: 'underline'}
case 's':
case 'strike':
return {_type: 'strike'}
case 'a':
return {_type: 'link', ...attributes}
}
}

export function parseHTML(html: string): TextDoc<any> {
const doc: TextDoc<any> = []
if (typeof html !== 'string') return doc
let parents: Array<TextDoc<any> | undefined> = [doc]
let parents: Array<{tag: string; doc?: TextDoc<any>}> = [
{tag: undefined!, doc}
]
let marks: Array<Mark> = []
const parser = new Parser({
onopentag(name, attributes) {
const node = mapNode(name, attributes)
const parent = parents[parents.length - 1]
if (node) parent?.push(node)
parents.push(node?.content)
const mark = mapMark(name, attributes)
const parent = parents.at(-1)
if (node) {
parent?.doc?.push(node)
parents.push({tag: name, doc: node?.content})
} else if (mark) {
marks.push(mark)
}
},
ontext(text) {
const parent = parents[parents.length - 1]
parent?.push({_type: 'text', text})
const parent = parents.at(-1)
const node: TextNode = {_type: 'text', text}
if (marks.length) node.marks = marks
parent?.doc?.push(node)
marks = []
},
onclosetag() {
parents.pop()
onclosetag(name) {
const parent = parents.at(-1)
if (parent?.tag === name) parents.pop()
else marks.pop()
}
})
parser.write(html)
Expand Down

0 comments on commit b1578cc

Please sign in to comment.