diff --git a/packages/super-editor/src/core/super-converter/exporter.js b/packages/super-editor/src/core/super-converter/exporter.js index 20ceb55559..9c8468c329 100644 --- a/packages/super-editor/src/core/super-converter/exporter.js +++ b/packages/super-editor/src/core/super-converter/exporter.js @@ -1731,17 +1731,25 @@ function prepareCheckboxAnnotation(params) { */ function prepareHtmlAnnotation(params) { const { - node: { attrs = {} }, + node: { attrs = {}, marks = [] }, + editorSchema, } = params; const parser = new window.DOMParser(); const paragraphHtml = parser.parseFromString(attrs.rawHtml || attrs.displayLabel, 'text/html'); + const marksFromAttrs = translateFieldAttrsToMarks(attrs); + const allMarks = [...marks, ...marksFromAttrs] - const state = EditorState.create({ - doc: PMDOMParser.fromSchema(params.editorSchema).parse(paragraphHtml), + let state = EditorState.create({ + doc: PMDOMParser.fromSchema(editorSchema).parse(paragraphHtml), }); + if (allMarks.length) { + state = applyMarksToHtmlAnnotation(state, allMarks); + } + const htmlAnnotationNode = state.doc.toJSON(); + return { name: 'htmlAnnotation', elements: translateChildNodes({ @@ -1896,6 +1904,20 @@ function translateFieldAnnotation(params) { 'w:val': attrs.multipleImage, }, }, + { + name: 'w:fieldFontFamily', + attributes: { + 'xmlns:w': customXmlns, + 'w:val': attrs.fontFamily, + }, + }, + { + name: 'w:fieldFontSize', + attributes: { + 'xmlns:w': customXmlns, + 'w:val': attrs.fontSize, + }, + }, ], }, { @@ -2103,3 +2125,46 @@ function resizeKeepAspectRatio(width, height, maxWidth) { } return { width, height }; } + +function applyMarksToHtmlAnnotation(state, marks) { + const { tr, doc, schema } = state; + const allowedMarks = ['fontFamily', 'fontSize']; + + if ( + !marks.some((m) => allowedMarks.includes(m.type)) + ) { + return state; + } + + const fontFamily = marks.find((m) => m.type === 'fontFamily'); + const fontSize = marks.find((m) => m.type === 'fontSize'); + + doc.descendants((node, pos) => { + if (!node.isText) return; + + const found = node.marks.find((m) => m.type.name === 'textStyle'); + const textStyleType = schema.marks.textStyle; + + if (!found) { + tr.addMark(pos, pos + node.nodeSize, textStyleType.create({ + ...fontFamily?.attrs, + ...fontSize?.attrs, + })); + return; + } + + if (!found?.attrs.fontFamily && fontFamily) { + tr.addMark(pos, pos + node.nodeSize, textStyleType.create({ + ...found?.attrs, + ...fontFamily.attrs, + })); + } else if (!found?.attrs.fontSize && fontSize) { + tr.addMark(pos, pos + node.nodeSize, textStyleType.create({ + ...found?.attrs, + ...fontSize.attrs, + })); + } + }); + + return state.apply(tr); +}; diff --git a/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js b/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js index d097292057..1c88fdee2b 100644 --- a/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js +++ b/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js @@ -13,7 +13,8 @@ export const handleAnnotationNode = (params) => { const node = nodes[0]; const sdtPr = node.elements.find((el) => el.name === 'w:sdtPr'); const sdtContent = node.elements.find((el) => el.name === 'w:sdtContent'); - const { attrs: marksAsAttrs, marks } = parseAnnotationMarks(sdtContent); + const type = sdtPr?.elements.find((el) => el.name === 'w:fieldTypeShort')?.attributes['w:val']; + const { attrs: marksAsAttrs, marks } = parseAnnotationMarks(sdtContent, type); const docPartObj = sdtPr?.elements.find((el) => el.name === 'w:docPartObj'); if (docPartObj) { @@ -23,9 +24,10 @@ export const handleAnnotationNode = (params) => { const alias = sdtPr?.elements.find((el) => el.name === 'w:alias'); const tag = sdtPr?.elements.find((el) => el.name === 'w:tag'); const fieldType = sdtPr?.elements.find((el) => el.name === 'w:fieldType')?.attributes['w:val']; - const type = sdtPr?.elements.find((el) => el.name === 'w:fieldTypeShort')?.attributes['w:val']; const fieldColor = sdtPr?.elements.find((el) => el.name === 'w:fieldColor')?.attributes['w:val']; const isMultipleImage = sdtPr?.elements.find((el) => el.name === 'w:fieldMultipleImage')?.attributes['w:val']; + const fontFamily = sdtPr?.elements.find((el) => el.name === 'w:fieldFontFamily')?.attributes['w:val']; + const fontSize = sdtPr?.elements.find((el) => el.name === 'w:fieldFontSize')?.attributes['w:val']; const attrs = { type, @@ -34,8 +36,12 @@ export const handleAnnotationNode = (params) => { fieldType, fieldColor, multipleImage: isMultipleImage === 'true', + fontFamily: fontFamily !== 'null' ? fontFamily : null, + fontSize: fontSize !== 'null' ? fontSize : null, }; + const allAttrs = { ...attrs, ...marksAsAttrs }; + if (!attrs.fieldId || !attrs.displayLabel) { return { nodes: [], consumed: 0 }; } @@ -43,14 +49,14 @@ export const handleAnnotationNode = (params) => { let result = { type: 'text', text: `{{${attrs.displayLabel}}}`, - attrs: { ...attrs, ...marksAsAttrs }, + attrs: allAttrs, marks, }; if (params.editor.options.annotations) { result = { type: 'fieldAnnotation', - attrs: { ...attrs, ...marksAsAttrs } + attrs: allAttrs, }; }; @@ -65,8 +71,20 @@ export const handleAnnotationNode = (params) => { * @param {Object} content The sdtContent node * @returns {Object} The attributes object */ -export const parseAnnotationMarks = (content = {}) => { - const run = content.elements?.find((el) => el.name === 'w:r'); +export const parseAnnotationMarks = (content = {}, type) => { + let mainContent = content; + + if (type === 'html') { + /// Note: html annotation has a different structure and can include + /// several paragraphs with different styles. We could find the first paragraph + /// and take the marks from there, but we take fontFamily and fontSize from the annotation attributes. + + /// Example: + /// const firstPar = content.elements?.find((el) => el.name === 'w:p'); + /// if (firstPar) mainContent = firstPar; + } + + const run = mainContent.elements?.find((el) => el.name === 'w:r'); const rPr = run?.elements?.find((el) => el.name === 'w:rPr'); if (!rPr) return {}; diff --git a/packages/super-editor/src/dev/components/DeveloperPlayground.vue b/packages/super-editor/src/dev/components/DeveloperPlayground.vue index dddd72a6dc..c04057ffc1 100644 --- a/packages/super-editor/src/dev/components/DeveloperPlayground.vue +++ b/packages/super-editor/src/dev/components/DeveloperPlayground.vue @@ -45,6 +45,15 @@ const onCreate = ({ editor }) => { // Set debugging pagination value from editor plugin state isDebuggingPagination.value = PaginationPluginKey.getState(editor.state)?.isDebugging; + + // editor.commands.addFieldAnnotation(0, { + // type: 'html', + // displayLabel: 'Paragraph', + // fieldId: `123`, + // fieldType: 'TEXTINPUT', + // fieldColor: '#980043', + // rawHtml: '
Par 1
Par 2
', + // }); }; const onCommentClicked = ({ conversation }) => { @@ -68,6 +77,7 @@ const editorOptions = computed(() => { users: [], // For comment @-mentions, only users that have access to the document pagination: true, telemetry: telemetry.value, + annotations: true, } });