-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(text-editor): add insertHtml function to properly parse html string
Co-authored-by: Adrian Schmidt <[email protected]>
- Loading branch information
1 parent
2a56e72
commit b69c475
Showing
8 changed files
with
279 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
src/components/text-editor/prosemirror-adapter/plugins/trigger/create-html-inserter.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { Schema } from 'prosemirror-model'; | ||
import { createHtmlInserter } from './create-html-inserter'; | ||
|
||
describe('createHtmlInserter', () => { | ||
let mockContentConverter: any; | ||
let mockDispatchTransaction: jest.Mock; | ||
let schema: Schema; | ||
|
||
beforeEach(() => { | ||
mockContentConverter = { | ||
parseAsHTML: jest.fn((input) => Promise.resolve(`<p>${input}</p>`)), | ||
}; | ||
|
||
mockDispatchTransaction = jest.fn(); | ||
|
||
schema = new Schema({ | ||
nodes: { | ||
doc: { content: 'block+' }, | ||
paragraph: { group: 'block', content: 'inline*' }, | ||
text: { group: 'inline' }, | ||
}, | ||
marks: {}, | ||
}); | ||
}); | ||
|
||
it('resolves after inserting valid HTML into the editor', async () => { | ||
const inserter = await createHtmlInserter( | ||
{ state: { schema: schema } } as any, // Mock EditorView | ||
mockContentConverter, | ||
0, // startPos | ||
mockDispatchTransaction, | ||
); | ||
|
||
const inputHtml = '<strong>Test</strong>'; | ||
await inserter(inputHtml); | ||
|
||
expect(mockContentConverter.parseAsHTML).toHaveBeenCalledWith( | ||
inputHtml, | ||
schema, | ||
); | ||
expect(mockDispatchTransaction).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('resolves after handling invalid HTML gracefully', async () => { | ||
const inserter = await createHtmlInserter( | ||
{ state: { schema: schema } } as any, | ||
mockContentConverter, | ||
0, | ||
mockDispatchTransaction, | ||
); | ||
|
||
const inputHtml = '<div><p>Unclosed tag'; | ||
await inserter(inputHtml); | ||
|
||
expect(mockContentConverter.parseAsHTML).toHaveBeenCalledWith( | ||
inputHtml, | ||
schema, | ||
); | ||
expect(mockDispatchTransaction).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('dispatches the correct fragment for nested HTML', async () => { | ||
const inserter = await createHtmlInserter( | ||
{ state: { schema: schema } } as any, | ||
mockContentConverter, | ||
0, | ||
mockDispatchTransaction, | ||
); | ||
|
||
const inputHtml = '<div><span color="#FF0000">Nested</span></div>'; | ||
await inserter(inputHtml); | ||
|
||
expect(mockDispatchTransaction).toHaveBeenCalledTimes(1); | ||
|
||
// Convert the fragment to an array for easier testing | ||
const dispatchedArgs = mockDispatchTransaction.mock.calls[0]; | ||
const dispatchedFragment = dispatchedArgs[2]; | ||
const fragmentArray = dispatchedFragment.content.map((node) => | ||
node.toJSON(), | ||
); | ||
|
||
// Check the structure and content of the fragment | ||
// The reason that the structure doesn't completely match the input is | ||
// that ProseMirror will transform the input based on what the schema | ||
// allows. (At least I think that's why… /Ads) | ||
expect(fragmentArray).toEqual([ | ||
{ | ||
type: 'paragraph', | ||
content: [ | ||
{ | ||
type: 'text', | ||
text: 'Nested', | ||
}, | ||
], | ||
}, | ||
]); | ||
}); | ||
}); |
25 changes: 25 additions & 0 deletions
25
src/components/text-editor/prosemirror-adapter/plugins/trigger/create-html-inserter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Node, DOMParser, Fragment } from 'prosemirror-model'; | ||
import { EditorView } from 'prosemirror-view'; | ||
import { ContentTypeConverter } from '../../../utils/content-type-converter'; | ||
|
||
export const createHtmlInserter = ( | ||
view: EditorView, | ||
contentConverter: ContentTypeConverter, | ||
startPos: number, | ||
dispatchTransaction: ( | ||
view: EditorView, | ||
startPos: number, | ||
fragment: Fragment | Node, | ||
) => void, | ||
): ((input: string) => Promise<void>) => { | ||
const schema = view.state.schema; | ||
|
||
return async (input: string): Promise<void> => { | ||
const container = document.createElement('span'); | ||
container.innerHTML = await contentConverter.parseAsHTML(input, schema); | ||
|
||
const fragment = DOMParser.fromSchema(schema).parse(container).content; | ||
|
||
dispatchTransaction(view, startPos, fragment); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.