Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ class SuperConverter {
});

static markTypes = [
{ name: 'w:b', type: 'bold' },
{ name: 'w:bCs', type: 'bold' },
{ name: 'w:b', type: 'bold', property: 'value' },
// { name: 'w:bCs', type: 'bold' },
{ name: 'w:i', type: 'italic' },
{ name: 'w:iCs', type: 'italic' },
// { name: 'w:iCs', type: 'italic' },
{ name: 'w:u', type: 'underline', mark: 'underline', property: 'underlineType' },
{ name: 'w:strike', type: 'strike', mark: 'strike' },
{ name: 'w:color', type: 'color', mark: 'textStyle', property: 'color' },
Expand All @@ -56,6 +56,7 @@ class SuperConverter {
{ name: 'link', type: 'link', mark: 'link', property: 'href' },
{ name: 'w:highlight', type: 'highlight', mark: 'highlight', property: 'color' },
{ name: 'w:shd', type: 'highlight', mark: 'highlight', property: 'color' },
{ name: 'w:caps', type: 'textTransform', mark: 'textStyle', property: 'textTransform' },
];

static propertyTypes = Object.freeze({
Expand Down
19 changes: 18 additions & 1 deletion packages/super-editor/src/core/super-converter/exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -1676,9 +1676,17 @@ function translateMark(mark) {

const { attrs } = mark;
let value;

switch (mark.type) {
case 'bold':
if (attrs?.value) {
markElement.attributes['w:val'] = attrs.value;
} else {
delete markElement.attributes;
}
markElement.type = 'element';
break;

case 'italic':
delete markElement.attributes;
markElement.type = 'element';
Expand Down Expand Up @@ -1718,6 +1726,15 @@ function translateMark(mark) {
case 'textIndent':
markElement.attributes['w:firstline'] = inchesToTwips(attrs.textIndent);
break;

case 'textTransform':
if (attrs?.textTransform === 'none') {
markElement.attributes['w:val'] = '0';
} else {
delete markElement.attributes;
}
markElement.type = 'element';
break;

case 'lineHeight':
markElement.attributes['w:line'] = linesToTwips(attrs.lineHeight);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,29 @@ export function parseMarks(property, unknownMarks = [], docx = null) {
const { attributes = {} } = element;
const newMark = { type: m.type };

if (attributes['w:val'] === '0' || attributes['w:val'] === 'none') {
return;
}

// this probably requires a more thorough check.
if (['w:bCs'].includes(m.name) && langAttrs['w:eastAsia']) {
const exceptionMarks = ['w:b', 'w:caps'];
if (
(attributes['w:val'] === '0' || attributes['w:val'] === 'none')
&& !exceptionMarks.includes(m.name)
) {
return;
}

// Use the parent mark (ie: textStyle) if present
if (m.mark) newMark.type = m.mark;

// Special handling of "w:caps".
if (m.name === 'w:caps') {
newMark.attrs = {};
if (attributes['w:val'] === '0') {
newMark.attrs[m.property] = 'none';
} else {
newMark.attrs[m.property] = 'uppercase';
}
marks.push(newMark);
return;
}

// Marks with attrs: we need to get their values
if (Object.keys(attributes).length) {
const value = getMarkValue(m.type, attributes, docx);
Expand All @@ -65,6 +76,7 @@ export function parseMarks(property, unknownMarks = [], docx = null) {
newMark.attrs = {};
newMark.attrs[m.property] = value;
}

marks.push(newMark);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,6 @@ export const handleParagraphNode = (params) => {
}
}

if (docx) {
const { textCase } = getDefaultParagraphStyle(docx, styleId);
if (textCase) {
schemaNode.attrs.textCase = textCase;
}
}

if (framePr && framePr.attributes['w:dropCap']) {
schemaNode.attrs.dropcap = {
type: framePr.attributes['w:dropCap'],
Expand Down Expand Up @@ -270,22 +263,11 @@ const getDefaultParagraphStyle = (docx, styleId = '') => {
let pPrStyleIdSpacingTag = {};
let pPrStyleIdIndentTag = {};
let pPrStyleJc = {};
let textCase = null;
if (styleId) {
const stylesById = styles.elements[0].elements?.find(
(el) => el.name === 'w:style' && el.attributes['w:styleId'] === styleId,
);
const pPrById = stylesById?.elements?.find((el) => el.name === 'w:pPr');

const basedOn = stylesById?.elements.find((el) => el.name === 'w:basedOn');
const baseStyles = styles.elements[0].elements?.find(
(el) => el.name === 'w:style' && el.attributes['w:styleId'] === basedOn?.attributes['w:val']
);
const rprBaseStyles = baseStyles?.elements?.find((el) => el.name === 'w:rPr');

const caps = rprBaseStyles?.elements?.find((el) => el.name === 'w:caps');
if (caps) textCase = 'uppercase';

pPrStyleIdSpacingTag = pPrById?.elements?.find((el) => el.name === 'w:spacing') || {};
pPrStyleIdIndentTag = pPrById?.elements?.find((el) => el.name === 'w:ind') || {};
pPrStyleJc = pPrById?.elements?.find((el) => el.name === 'w:jc') || {};
Expand All @@ -312,7 +294,6 @@ const getDefaultParagraphStyle = (docx, styleId = '') => {
spacing: pPrByIdSpacingAttr || spacingRest,
indent: pPrByIdIndentAttr || indentRest,
justify: pPrByIdJcAttr,
textCase,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ const handleRunNode = (params) => {
let processedRun = nodeListHandler.handler(childParams)?.filter((n) => n) || [];
const hasRunProperties = node.elements?.some((el) => el.name === 'w:rPr');
const defaultNodeStyles = getMarksFromStyles(docx, parentStyleId);

if (hasRunProperties) {
const { marks = [], attributes = {} } = parseProperties(node);

// Apply fonts from related style definition if there is no marks
const textStyleMark = marks.find((m) => m.type === 'textStyle');
const hasFontStyle = textStyleMark && Object.keys(textStyleMark.attrs).length > 0;
if (defaultNodeStyles.marks && !hasFontStyle) {
marks.push(...defaultNodeStyles.marks);
const hasBoldDisabled = marks.find((m) => m.type === 'bold')?.attrs?.value === '0';
for (let mark of defaultNodeStyles.marks) {
if (['bold'].includes(mark.type) && hasBoldDisabled) continue;
marks.push(mark);
}
}

if (node.marks) marks.push(...node.marks);
Expand Down
16 changes: 16 additions & 0 deletions packages/super-editor/src/extensions/bold/bold.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ export const Bold = Mark.create({
};
},

addAttributes() {
return {
value: {
default: null,
renderDOM: (attrs) => {
if (!attrs.value) return {};

if (attrs.value === '0') {
return { style: 'font-weight: normal' };
}
return {};
},
},
};
},

parseDOM() {
return [
{ tag: 'strong' },
Expand Down
3 changes: 3 additions & 0 deletions packages/super-editor/src/extensions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { Strike } from './strike/index.js';
import { Link } from './link/index.js';
import { TrackInsert, TrackDelete, TrackFormat } from './track-changes/index.js';
import { CommentsMark } from './comment/index.js';
import { TextTransform } from './text-transform/index.js';

// Plugins
import { CommentsPlugin } from './comment/index.js';
Expand Down Expand Up @@ -174,6 +175,7 @@ const getStarterExtensions = () => {
DocumentSection,
NodeResizer,
CustomSelection,
TextTransform,
];
};

Expand Down Expand Up @@ -239,4 +241,5 @@ export {
DocumentSection,
NodeResizer,
CustomSelection,
TextTransform,
};
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export const generateLinkedStyleString = (linkedStyle, basedOnStyle, node, paren
if (!linkedDefinitionStyles['font-size'] && basedOnDefinitionStyles['font-size']) {
resultStyles['font-size'] = basedOnDefinitionStyles['font-size'];
}
if (!linkedDefinitionStyles['text-transform'] && basedOnDefinitionStyles['text-transform']) {
resultStyles['text-transform'] = basedOnDefinitionStyles['text-transform'];
}

Object.entries(resultStyles).forEach(([k, value]) => {
const key = kebabCase(k);
Expand All @@ -156,6 +159,8 @@ export const generateLinkedStyleString = (linkedStyle, basedOnStyle, node, paren
const hasParentIndent = Object.keys(parent?.attrs?.indent || {});
const hasParentSpacing = Object.keys(parent?.attrs?.spacing || {});

const listTypes = ['orderedList', 'listItem'];

// If no mark already in the node, we override the style
if (!mark) {
if (key === 'spacing' && includeSpacing && !hasParentSpacing) {
Expand All @@ -170,7 +175,17 @@ export const generateLinkedStyleString = (linkedStyle, basedOnStyle, node, paren
if (rightIndent) markValue['margin-right'] = rightIndent + 'px';
if (firstLine) markValue['text-indent'] = firstLine + 'px';
} else if (key === 'bold') {
markValue['font-weight'] = 'bold';
if (!listTypes.includes(node.type.name)) {
markValue['font-weight'] = 'bold';
}
} else if (key === 'text-transform') {
if (!listTypes.includes(node.type.name)) {
Copy link
Copy Markdown
Contributor Author

@artem-harbour artem-harbour Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why I have to add these checks, but without them the marks seem to be applied in the wrong places. fyi @harbournick

markValue[key] = value;
}
} else if (key === 'font-size') {
if (!listTypes.includes(node.type.name)) {
markValue[key] = value;
}
} else if (typeof value === 'string') {
markValue[key] = value;
}
Expand Down
8 changes: 0 additions & 8 deletions packages/super-editor/src/extensions/paragraph/paragraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,6 @@ export const Paragraph = Node.create({
return { style };
},
},
textCase: {
renderDOM: (attrs) => {
if (!attrs.textCase) return {};
return {
style: `text-transform: ${attrs.textCase}`,
}
},
},
};
},

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './text-transform.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Extension } from '@core/index.js';

export const TextTransform = Extension.create({
name: 'textTransform',

addOptions() {
return {
types: ['textStyle'],
};
},

addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
textTransform: {
default: null,
renderDOM: (attrs) => {
if (!attrs.textCase) return {};
return {
style: `text-transform: ${attrs.textCase}`,
};
},
},
},
},
];
},
});
Loading