From 6ffe4a51a70602d41b788ebff08c3b79a73a2f7b Mon Sep 17 00:00:00 2001 From: VladaHarbour Date: Thu, 1 May 2025 16:34:12 +0300 Subject: [PATCH 1/2] Fixes for Spark: fonts, and linked styles --- .../src/components/toolbar/super-toolbar.js | 21 ++++++++++-- .../src/core/super-converter/helpers.js | 8 +++-- .../v2/importer/listImporter.js | 1 + .../src/extensions/line-height/line-height.js | 3 +- .../extensions/linked-styles/linked-styles.js | 33 ++++++++++++------- 5 files changed, 48 insertions(+), 18 deletions(-) diff --git a/packages/super-editor/src/components/toolbar/super-toolbar.js b/packages/super-editor/src/components/toolbar/super-toolbar.js index 0ce31e715b..390a62ca4f 100644 --- a/packages/super-editor/src/components/toolbar/super-toolbar.js +++ b/packages/super-editor/src/components/toolbar/super-toolbar.js @@ -368,14 +368,31 @@ export class SuperToolbar extends EventEmitter { item.deactivate(); } + // Activate toolbar items based on linked styles + const styleIdMark = marks.find((mark) => mark.name === 'styleId'); + if (styleIdMark?.attrs.styleId) { + const markToStyleMap = { + fontSize: 'font-size', + fontFamily: 'font-family', + bold: 'bold', + textAlign: 'textAlign', + }; + const linkedStyles = this.activeEditor.converter?.linkedStyles.find((style) => style.id === styleIdMark.attrs.styleId); + if (markToStyleMap[item.name.value] in linkedStyles?.definition.styles) { + const value = { + [item.name.value]: linkedStyles?.definition.styles[markToStyleMap[item.name.value]] + }; + item.activate(value); + } + } + const spacingAttr = marks.find((mark) => mark.name === 'spacing'); if (item.name.value === 'lineHeight' && (activeMark?.attrs?.lineHeight || spacingAttr)) { item.selectedValue.value = activeMark?.attrs?.lineHeight || spacingAttr.attrs?.spacing?.line || ''; } if (item.name.value === 'tableActions') { - if (inTable) item.disabled.value = false; - else item.disabled.value = true; + item.disabled.value = !inTable; } }); } diff --git a/packages/super-editor/src/core/super-converter/helpers.js b/packages/super-editor/src/core/super-converter/helpers.js index a92364ad29..f9d253a3d7 100644 --- a/packages/super-editor/src/core/super-converter/helpers.js +++ b/packages/super-editor/src/core/super-converter/helpers.js @@ -142,14 +142,16 @@ const rgbToHex = (rgb) => { .join(''); } -const getLineHeightValueString = (lineHeight, defaultUnit) => { +const getLineHeightValueString = (lineHeight, defaultUnit, lineRule = '', isObject = false) => { let [value, unit] = parseSizeUnit(lineHeight); - if (Number.isNaN(value)) return {}; + if (Number.isNaN(value) || value === 0) return {}; + if (lineRule === 'atLeast' && value < 1) return {}; + unit = unit ? unit : defaultUnit; // MS Word has a slightly bigger gap with line spacing equal to Superdoc's value += unit ? 4 : 0.2; - return `line-height: ${value}${unit}`; + return isObject ? { ['line-height']: `${value}${unit}` } : `line-height: ${value}${unit}`; } const deobfuscateFont = (arrayBuffer, guidHex) => { diff --git a/packages/super-editor/src/core/super-converter/v2/importer/listImporter.js b/packages/super-editor/src/core/super-converter/v2/importer/listImporter.js index 1bcf8c2b95..40d5ee47f6 100644 --- a/packages/super-editor/src/core/super-converter/v2/importer/listImporter.js +++ b/packages/super-editor/src/core/super-converter/v2/importer/listImporter.js @@ -188,6 +188,7 @@ function handleListNodes({ attrs: { textAlign: textStyle?.attrs.textAlign || textStyleFromStyles?.attrs.textAlign || null, rsidRDefault: attributes?.['w:rsidRDefault'] || null, + styleId, }, content: mergeTextNodes(parNode.content), }; diff --git a/packages/super-editor/src/extensions/line-height/line-height.js b/packages/super-editor/src/extensions/line-height/line-height.js index 211b9cc17d..91ed9819e3 100644 --- a/packages/super-editor/src/extensions/line-height/line-height.js +++ b/packages/super-editor/src/extensions/line-height/line-height.js @@ -23,7 +23,8 @@ export const LineHeight = Extension.create({ parseDOM: (el) => el.style.lineHeight, renderDOM: (attrs) => { if (!attrs.lineHeight) return {}; - return { style: getLineHeightValueString(attrs.lineHeight, this.options.defaults.unit) }; + + return { style: getLineHeightValueString(attrs.lineHeight, this.options.defaults.unit, attrs.spacing?.lineRule) }; }, }, }, diff --git a/packages/super-editor/src/extensions/linked-styles/linked-styles.js b/packages/super-editor/src/extensions/linked-styles/linked-styles.js index c12bf18e10..549afbc461 100644 --- a/packages/super-editor/src/extensions/linked-styles/linked-styles.js +++ b/packages/super-editor/src/extensions/linked-styles/linked-styles.js @@ -29,7 +29,7 @@ export const LinkedStyles = Extension.create({ const parentNode = findParentNode((node) => node.type.name === 'paragraph')(tr.selection) || {}; pos = parentNode.pos; paragraphNode = parentNode.node; - }; + } tr.setNodeMarkup(pos, undefined, { ...paragraphNode.attrs, @@ -54,7 +54,7 @@ const createLinkedStylesPlugin = (editor) => { const styles = editor.converter?.linkedStyles || []; return { styles, - decorations: generateDecorations(editor.state?.doc, styles), + decorations: generateDecorations(editor.state, styles), }; }, apply(tr, prev, oldEditorState, newEditorState) { @@ -62,7 +62,7 @@ const createLinkedStylesPlugin = (editor) => { let decorations = prev.decorations || DecorationSet.empty; if (tr.docChanged) { const styles = LinkedStylesPluginKey.getState(editor.state).styles; - decorations = generateDecorations(newEditorState.doc, styles); + decorations = generateDecorations(newEditorState, styles); } return { ...prev, decorations }; @@ -79,13 +79,15 @@ const createLinkedStylesPlugin = (editor) => { /** * Generate style decorations for linked styles * - * @param {Object} doc The current document object + * @param {Object} state Editor state * @param {Array[Object]} styles The linked styles * @returns {DecorationSet} The decorations */ -const generateDecorations = (doc, styles) => { +const generateDecorations = (state, styles) => { const decorations = []; let lastStyleId = null; + const doc = state?.doc; + doc.descendants((node, pos) => { const { name } = node.type; @@ -97,7 +99,10 @@ const generateDecorations = (doc, styles) => { const linkedStyle = getLinkedStyle(lastStyleId, styles); if (!linkedStyle) return; - const styleString = generateLinkedStyleString(linkedStyle, node); + const $pos = state.doc.resolve(pos); + const parent = $pos.parent; + + const styleString = generateLinkedStyleString(linkedStyle, node, parent); if (!styleString) return; const decoration = Decoration.inline(pos, pos + node.nodeSize, { style: styleString }); @@ -112,9 +117,10 @@ const generateDecorations = (doc, styles) => { * * @param {Object} linkedStyle The linked style object * @param {Object} node The current node + * @param {Object} parent The parent of current * @returns {String} The style string */ -export const generateLinkedStyleString = (linkedStyle, node, includeSpacing = true) => { +export const generateLinkedStyleString = (linkedStyle, node, parent, includeSpacing = true) => { if (!linkedStyle?.definition?.styles) return ''; const markValue = {}; @@ -138,16 +144,19 @@ export const generateLinkedStyleString = (linkedStyle, node, includeSpacing = tr // Check if this node has the expected mark. If yes, we are not overriding it const mark = flattenedMarks.find((n) => n.key === key); + const hasParentIndent = Object.keys(parent?.attrs.indent || {}); + const hasParentSpacing = Object.keys(parent?.attrs.spacing || {}); // If no mark already in the node, we override the style if (!mark) { - if (key === 'spacing' && includeSpacing) { + if (key === 'spacing' && includeSpacing && !hasParentSpacing) { const space = getSpacingStyle(value); Object.entries(space).forEach(([k, v]) => { markValue[k] = v; }); - } else if (key === 'indent' && includeSpacing) { + } else if (key === 'indent' && includeSpacing && !hasParentIndent) { const { leftIndent, rightIndent, firstLine } = value; + if (leftIndent) markValue['margin-left'] = leftIndent + 'px'; if (rightIndent) markValue['margin-right'] = rightIndent + 'px'; if (firstLine) markValue['text-indent'] = firstLine + 'px'; @@ -158,7 +167,7 @@ export const generateLinkedStyleString = (linkedStyle, node, includeSpacing = tr } } }); - + return Object.entries(markValue).map(([key, value]) => `${key}: ${value}`).join(';'); }; @@ -174,11 +183,11 @@ const getLinkedStyle = (styleId, styles = []) => { }; export const getSpacingStyle = (spacing) => { - const { lineSpaceBefore, lineSpaceAfter, line } = spacing; + const { lineSpaceBefore, lineSpaceAfter, line, lineRule } = spacing; return { 'margin-top': lineSpaceBefore + 'px', 'margin-bottom': lineSpaceAfter + 'px', - ...getLineHeightValueString(line, '') + ...getLineHeightValueString(line, '', lineRule, true) }; }; From 5746cae3d758258d5276f6419e6e4632e1c6d8c5 Mon Sep 17 00:00:00 2001 From: VladaHarbour Date: Thu, 1 May 2025 16:43:31 +0300 Subject: [PATCH 2/2] fix failing tests --- .../super-editor/src/tests/import/listImporter.test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/super-editor/src/tests/import/listImporter.test.js b/packages/super-editor/src/tests/import/listImporter.test.js index 9e58a8314f..255ec77802 100644 --- a/packages/super-editor/src/tests/import/listImporter.test.js +++ b/packages/super-editor/src/tests/import/listImporter.test.js @@ -1167,6 +1167,7 @@ describe('custom nested list tests', () => { type: 'paragraph', attrs: { rsidRDefault: '009F090C', + styleId: 'ListParagraph', textAlign: null }, content: [ @@ -1197,6 +1198,7 @@ describe('custom nested list tests', () => { type: 'paragraph', attrs: { rsidRDefault: '006B7646', + styleId: 'ListParagraph', textAlign: null }, content: [ @@ -1408,6 +1410,7 @@ describe('custom nested list tests', () => { type: 'paragraph', attrs: { rsidRDefault: '009F090C', + styleId: 'ListParagraph', textAlign: null }, content: [ @@ -1432,6 +1435,7 @@ describe('custom nested list tests', () => { type: 'paragraph', attrs: { rsidRDefault: '009F090C', + styleId: 'ListParagraph', textAlign: null }, content: [ @@ -1640,6 +1644,7 @@ describe('custom nested list tests', () => { type: 'paragraph', attrs: { rsidRDefault: '009F090C', + styleId: 'ListParagraph', textAlign: null }, content: [ @@ -1664,6 +1669,7 @@ describe('custom nested list tests', () => { type: 'paragraph', attrs: { rsidRDefault: '009F090C', + styleId: 'ListParagraph', textAlign: null }, content: [ @@ -1757,7 +1763,8 @@ describe('custom nested list tests', () => { type: 'paragraph', attrs: { rsidRDefault: '009F090C', - textAlign: null + textAlign: null, + styleId: 'ListParagraph', }, content: [ {