From ede99bc1713c94471e4ac0c12634f045b23854b6 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 27 Jan 2025 11:04:54 -0600 Subject: [PATCH] add parseHtml --- app-ugc/package.json | 3 ++- app-ugc/src/setBody.ts | 5 ++--- app/src/components/addNote.tsx | 4 ++-- app/src/components/fieldEditor.tsx | 4 ++-- app/src/components/noteSync.tsx | 5 ++--- app/src/hubMessenger.ts | 17 ++++++++--------- app/src/sqlite/crsqlite.ts | 4 +--- app/src/sqlite/note.ts | 13 +++---------- app/src/sqlite/template.ts | 17 +++++------------ app/src/sqlite/util.ts | 5 +++-- hub-ugc/package.json | 3 ++- hub-ugc/src/setBody.ts | 5 ++--- pnpm-lock.yaml | 6 ++++++ shared-dom/src/utility.ts | 6 ++++++ 14 files changed, 46 insertions(+), 51 deletions(-) diff --git a/app-ugc/package.json b/app-ugc/package.json index f5c05463..81b9de05 100644 --- a/app-ugc/package.json +++ b/app-ugc/package.json @@ -39,6 +39,7 @@ "app": "workspace:*", "comlink": "^4.4.1", "micromorph": "^0.4.5", - "shared": "workspace:*" + "shared": "workspace:*", + "shared-dom": "workspace:*" } } diff --git a/app-ugc/src/setBody.ts b/app-ugc/src/setBody.ts index 237d62e1..02dffc74 100644 --- a/app-ugc/src/setBody.ts +++ b/app-ugc/src/setBody.ts @@ -6,6 +6,7 @@ import { import { resizeIframe } from './registerServiceWorker' import diff from 'micromorph' import '@iframe-resizer/child' +import { parseHtml } from 'shared-dom/utility' self.onmessage = async (event) => { const data = event.data as unknown @@ -21,10 +22,8 @@ self.onmessage = async (event) => { } } -const domParser = new DOMParser() - export async function setBody({ body, css }: RawRenderBodyInput) { - await diff(document, domParser.parseFromString(body, 'text/html')) + await diff(document, parseHtml(body)) if (css != null) { const style = document.createElement('style') style.textContent = css diff --git a/app/src/components/addNote.tsx b/app/src/components/addNote.tsx index 48ec139d..f34c57c1 100644 --- a/app/src/components/addNote.tsx +++ b/app/src/components/addNote.tsx @@ -22,6 +22,7 @@ import { assertNever, objEntries, objValues } from 'shared/utility' import { CardsRemote } from './cardsRemote' import { createMutation } from '@tanstack/solid-query' import { useTableCountContext } from './tableCountContext' +import { parseHtml } from 'shared-dom/utility' function toView(template: Template): NoteCardView { const now = C.getDate() @@ -95,11 +96,10 @@ export const AddNote: VoidComponent<{ const noteCard = wip.noteCard! if (noteCard.cards.length === 0) C.toastFatal('There must be at least 1 card') - const dp = new DOMParser() await tx(async () => { const fieldValues = await Promise.all( objEntries(noteCard.note.fieldValues).map(async ([f, v]) => { - const doc = dp.parseFromString(v, 'text/html') + const doc = parseHtml(v) await Promise.all( Array.from(doc.images).map(async (i) => { await mutate(i) diff --git a/app/src/components/fieldEditor.tsx b/app/src/components/fieldEditor.tsx index 9df2e8ad..afe08a17 100644 --- a/app/src/components/fieldEditor.tsx +++ b/app/src/components/fieldEditor.tsx @@ -31,6 +31,7 @@ import { type NoteCardView } from '../uiLogic/cards' import { toOneLine } from 'shared/htmlToText' import { type NoteId, type MediaId } from 'shared/brand' import { C } from '../topLevelAwait' +import { parseHtml } from 'shared-dom/utility' // import "prosemirror-image-plugin/src/styles/sideResize.css" // cf. https://gitlab.com/emergence-engineering/prosemirror-image-plugin/-/blob/master/src/updateImageNode.ts @@ -131,7 +132,6 @@ const mySchema = makeSchema('editor') const mySchemaSerializer = makeSchema('serializer') const domSerializer = DOMSerializer.fromSchema(mySchemaSerializer) -const domParser = new DOMParser() const proseMirrorDOMParser = ProseMirrorDOMParser.fromSchema(mySchema) export const FieldEditor: VoidComponent<{ @@ -220,7 +220,7 @@ async function updateImgSrc(img: HTMLImageElement) { } async function createEditorState(value: string) { - const doc = domParser.parseFromString(value, 'text/html') + const doc = parseHtml(value) await Promise.all(Array.from(doc.images).map(updateImgSrc)) return EditorState.create({ doc: proseMirrorDOMParser.parse(doc), diff --git a/app/src/components/noteSync.tsx b/app/src/components/noteSync.tsx index 429e9bf0..205e3ba8 100644 --- a/app/src/components/noteSync.tsx +++ b/app/src/components/noteSync.tsx @@ -18,6 +18,7 @@ import { type NoteRemote, type Note } from 'shared/domain/note' import { UploadEntry } from './uploadEntry' import { uploadNotes } from '../domain/sync' import { C } from '../topLevelAwait' +import { parseHtml } from 'shared-dom/utility' const NoteSync: VoidComponent<{ template: Template; note: Note }> = (props) => ( <> @@ -64,8 +65,6 @@ export const NoteNookSync: VoidComponent<{ ) } -const dp = new DOMParser() - const NoteNookSyncActual: VoidComponent<{ note: Note template: Template @@ -84,7 +83,7 @@ const NoteNookSyncActual: VoidComponent<{ } if (remoteNote != null) { for (const [field, value] of objEntries(remoteNote.fieldValues)) { - const doc = dp.parseFromString(value, 'text/html') + const doc = parseHtml(value) await Promise.all( Array.from(doc.images).map(async (imgEl) => { const rmId = imgEl diff --git a/app/src/hubMessenger.ts b/app/src/hubMessenger.ts index 572de9a1..db979412 100644 --- a/app/src/hubMessenger.ts +++ b/app/src/hubMessenger.ts @@ -13,6 +13,7 @@ import { type Template } from 'shared/domain/template' import { type Note } from 'shared/domain/note' import { type Card } from 'shared/domain/card' import { objEntries } from 'shared/utility' +import { parseHtml } from 'shared-dom/utility' export const appExpose = { ping: () => {}, @@ -32,11 +33,10 @@ export const appExpose = { [rt.nook]: { remoteTemplateId: rt.id, uploadDate: now }, }, } satisfies Template - const dp = new DOMParser() if (template.templateType.tag === 'standard') { await Promise.all( template.templateType.templates.map(async (t) => { - const { imgSrcs, front, back } = getTemplateImages(t, dp) + const { imgSrcs, front, back } = getTemplateImages(t) t.front = serializer.serializeToString(front) t.back = serializer.serializeToString(back) return await downloadImages(imgSrcs) @@ -45,7 +45,6 @@ export const appExpose = { } else { const { imgSrcs, front, back } = getTemplateImages( template.templateType.template, - dp, ) await downloadImages(imgSrcs) template.templateType.template.front = @@ -73,7 +72,7 @@ export const appExpose = { [nook, { remoteNoteId: rn.id, uploadDate: now }], ]), } satisfies Note - await downloadImages(getNoteImages(n.fieldValues, new DOMParser())) + await downloadImages(getNoteImages(n.fieldValues)) await C.db.upsertNote(n) const ords = noteOrds.bind(C)(n, template) const cards = ords.map((i) => { @@ -100,10 +99,10 @@ export const appExpose = { // highTODO needs security on the origin Comlink.expose(appExpose, Comlink.windowEndpoint(self.parent)) -function getNoteImages(fieldValues: Record, dp: DOMParser) { +function getNoteImages(fieldValues: Record) { const imgSrcs = new Map() for (const [f, v] of objEntries(fieldValues)) { - const doc = dp.parseFromString(v, 'text/html') + const doc = parseHtml(v) Array.from(doc.images).forEach((i) => { mutate(i, imgSrcs) }) @@ -128,10 +127,10 @@ function mutate(img: HTMLImageElement, imgSrcs: Map) { } } -function getTemplateImages(ct: ChildTemplate, dp: DOMParser) { +function getTemplateImages(ct: ChildTemplate) { const imgSrcs = new Map() - const front = dp.parseFromString(ct.front, 'text/html') - const back = dp.parseFromString(ct.back, 'text/html') + const front = parseHtml(ct.front) + const back = parseHtml(ct.back) Array.from(front.images).forEach((i) => { mutate(i, imgSrcs) }) diff --git a/app/src/sqlite/crsqlite.ts b/app/src/sqlite/crsqlite.ts index d8764ef3..aec1efe1 100644 --- a/app/src/sqlite/crsqlite.ts +++ b/app/src/sqlite/crsqlite.ts @@ -12,13 +12,11 @@ import { SQLITE_DETERMINISTIC, SQLITE_UTF8 } from '@vlcn.io/wa-sqlite' import initSql from 'shared/sql.json' import { ftsNormalize } from 'shared/htmlToText' -// const dp = new DOMParser() - function getMediaIds(fvs: string) { // highTODO uncomment and fix by adding the mediaId table back // const values = parseMap(fvs).values() // return Array.from(values) - // .flatMap((v) => dp.parseFromString(v, 'text/html')) + // .flatMap((v) => parseHtml(v)) // .flatMap((d) => Array.from(d.images)) // .map((i) => i.getAttribute('src')) // .join(unitSeparator) diff --git a/app/src/sqlite/note.ts b/app/src/sqlite/note.ts index 85f4446b..3ad0b414 100644 --- a/app/src/sqlite/note.ts +++ b/app/src/sqlite/note.ts @@ -260,7 +260,6 @@ JOIN noteFieldValue ON noteFieldValue.noteId = x.noteId AND noteFieldValue.field ) }, getNewNotesToUpload: async function (noteId?: NoteId, nook?: NookId) { - const dp = new DOMParser() const remoteTemplates = await ky .selectFrom('remoteTemplate') .selectAll() @@ -285,7 +284,7 @@ JOIN noteFieldValue ON noteFieldValue.noteId = x.noteId AND noteFieldValue.field .filter(notEmpty) return domainToCreateRemote(note, remoteIds) }) - .map(async (n) => await remotifyNote(dp, n).then((x) => x.note)), + .map(async (n) => await remotifyNote(n).then((x) => x.note)), ) }, getNewNotesToUploadDom: async function (noteId?: NoteId) { @@ -318,7 +317,6 @@ JOIN noteFieldValue ON noteFieldValue.noteId = x.noteId AND noteFieldValue.field ) }, getEditedNotesToUpload: async function (noteId?: NoteId, nook?: NookId) { - const dp = new DOMParser() const remoteTemplates = await ky .selectFrom('remoteTemplate') .selectAll() @@ -360,7 +358,7 @@ JOIN noteFieldValue ON noteFieldValue.noteId = x.noteId AND noteFieldValue.field ) return domainToEditRemote(note, remotes) }) - .map(async (n) => await remotifyNote(dp, n).then((x) => x.note)), + .map(async (n) => await remotifyNote(n).then((x) => x.note)), ) }, getEditedNotesToUploadDom: async function (noteId?: NoteId) { @@ -402,7 +400,6 @@ JOIN noteFieldValue ON noteFieldValue.noteId = x.noteId AND noteFieldValue.field > & { note: Note }, ) { const { hashByLocal } = await remotifyNote( - new DOMParser(), domainToCreateRemote(remoteNote.note, [ /* this doesn't need any real values */ ]), @@ -479,14 +476,10 @@ JOIN noteFieldValue ON noteFieldValue.noteId = x.noteId AND noteFieldValue.field } async function remotifyNote( - dp: DOMParser, note: T, ) { const fieldValues: Record = {} satisfies T['fieldValues'] - const { docs, hashByLocal } = await remotifyDoms( - dp, - objValues(note.fieldValues), - ) + const { docs, hashByLocal } = await remotifyDoms(objValues(note.fieldValues)) let i = 0 for (const field of objKeys(note.fieldValues)) { fieldValues[field] = docs[i]!.body.innerHTML diff --git a/app/src/sqlite/template.ts b/app/src/sqlite/template.ts index 56f7d318..5b790de7 100644 --- a/app/src/sqlite/template.ts +++ b/app/src/sqlite/template.ts @@ -219,14 +219,11 @@ export const templateCollectionMethods = { templateId?: TemplateId, nook?: NookId, ) { - const dp = new DOMParser() const templatesAndStuff = await this.getNewTemplatesToUploadDom(templateId) return await Promise.all( templatesAndStuff .map((n) => domainToCreateRemote(n, nook)) - .map( - async (n) => await remotifyTemplate(dp, n).then((x) => x.template), - ), + .map(async (n) => await remotifyTemplate(n).then((x) => x.template)), ) }, getNewTemplatesToUploadDom: async function (templateId?: TemplateId) { @@ -246,15 +243,12 @@ export const templateCollectionMethods = { templateId?: TemplateId, nook?: NookId, ) { - const dp = new DOMParser() const templatesAndStuff = await this.getEditedTemplatesToUploadDom(templateId) return await Promise.all( templatesAndStuff .map((n) => domainToEditRemote(n, nook)) - .map( - async (n) => await remotifyTemplate(dp, n).then((x) => x.template), - ), + .map(async (n) => await remotifyTemplate(n).then((x) => x.template)), ) }, getEditedTemplatesToUploadDom: async function (templateId?: TemplateId) { @@ -293,7 +287,6 @@ export const templateCollectionMethods = { .where('id', '=', templateDbId) .executeTakeFirstOrThrow() const { hashByLocal } = await remotifyTemplate( - new DOMParser(), domainToCreateRemote(toTemplate([{ ...template, ...remoteTemplate }])!), ) const srcs = new Set(hashByLocal.keys()) @@ -392,7 +385,7 @@ function toTemplate(allTemplates: TemplateRow[]) { async function remotifyTemplate< T extends CreateRemoteTemplate | EditRemoteTemplate, ->(dp: DOMParser, template: T) { +>(template: T) { const serializer = new XMLSerializer() const serialize = (doc: Document) => { const s = serializer.serializeToString(doc) @@ -407,7 +400,7 @@ async function remotifyTemplate< t.front, t.back, ]) - const { docs, hashByLocal } = await remotifyDoms(dp, rawDoms) + const { docs, hashByLocal } = await remotifyDoms(rawDoms) let i = 0 for (const t of template.templateType.templates) { t.front = serialize(docs[i]!) @@ -420,7 +413,7 @@ async function remotifyTemplate< hashByLocal, } } else { - const { docs, hashByLocal } = await remotifyDoms(dp, [ + const { docs, hashByLocal } = await remotifyDoms([ template.templateType.template.front, template.templateType.template.back, ]) diff --git a/app/src/sqlite/util.ts b/app/src/sqlite/util.ts index 76b79aac..1f143544 100644 --- a/app/src/sqlite/util.ts +++ b/app/src/sqlite/util.ts @@ -35,13 +35,14 @@ import { jsonArrayFrom } from 'kysely/helpers/sqlite' import { sql, type AliasedRawBuilder, type ExpressionBuilder } from 'kysely' import { arrayToBase64, base64ToArray } from 'shared/binary' import { nullNook } from 'shared-edge' +import { parseHtml } from 'shared-dom/utility' export function parseTags(rawTags: string) { return parseSet(rawTags) } -export async function remotifyDoms(dp: DOMParser, rawDoms: string[]) { - const docs = rawDoms.map((rawDom) => dp.parseFromString(rawDom, 'text/html')) +export async function remotifyDoms(rawDoms: string[]) { + const docs = rawDoms.map(parseHtml) const imgSrcs = new Set( docs .flatMap((pd) => Array.from(pd.images)) diff --git a/hub-ugc/package.json b/hub-ugc/package.json index 2815da74..006f306b 100644 --- a/hub-ugc/package.json +++ b/hub-ugc/package.json @@ -39,6 +39,7 @@ "comlink": "^4.4.1", "hub": "workspace:*", "micromorph": "^0.4.5", - "shared": "workspace:*" + "shared": "workspace:*", + "shared-dom": "workspace:*" } } diff --git a/hub-ugc/src/setBody.ts b/hub-ugc/src/setBody.ts index 03b263e1..f30120ea 100644 --- a/hub-ugc/src/setBody.ts +++ b/hub-ugc/src/setBody.ts @@ -6,6 +6,7 @@ import { import { resizeIframe } from './registerServiceWorker' import diff from 'micromorph' import '@iframe-resizer/child' +import { parseHtml } from 'shared-dom/utility' self.onmessage = async (event) => { const data = event.data as unknown @@ -21,10 +22,8 @@ self.onmessage = async (event) => { } } -const domParser = new DOMParser() - export async function setBody({ body, css }: RawRenderBodyInput) { - await diff(document, domParser.parseFromString(body, 'text/html')) + await diff(document, parseHtml(body)) if (css != null) { const style = document.createElement('style') style.textContent = css diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee138e0e..f1b9b1cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -525,6 +525,9 @@ importers: shared: specifier: workspace:* version: link:../shared + shared-dom: + specifier: workspace:* + version: link:../shared-dom devDependencies: '@cloudflare/workers-types': specifier: ^4.20240725.0 @@ -844,6 +847,9 @@ importers: shared: specifier: workspace:* version: link:../shared + shared-dom: + specifier: workspace:* + version: link:../shared-dom devDependencies: '@cloudflare/workers-types': specifier: ^4.20240725.0 diff --git a/shared-dom/src/utility.ts b/shared-dom/src/utility.ts index f7f50224..ef6f7948 100644 --- a/shared-dom/src/utility.ts +++ b/shared-dom/src/utility.ts @@ -7,3 +7,9 @@ export function disposeObserver(ro: ResizeObserver | undefined, ref: Element) { ro.disconnect() } } + +let domParser: DOMParser +export function parseHtml(html: string) { + if (domParser == null) domParser = new DOMParser() + return domParser.parseFromString(html, 'text/html') +}