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 @@ -18,3 +18,8 @@
min-height: inherit;
word-wrap: break-word;
}

/* temporary fix */
.sd-editor-list-item-node-view .sd-custom-selection {
font-size: inherit!important;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fyi @VladaHarbour - temporary fix for selection.

}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ onMounted(() => {
>
<div
class="style-name"
:style="generateLinkedStyleString(style, null, false)"
:style="generateLinkedStyleString(style, null, null, false)"
data-item="btn-linkedStyles-option"
>
{{ style.definition.attrs.name }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export const handleListNode = (params) => {
if (isList) {
node.isList = true;
const result = handleListNodes(params, node);

return {
nodes: [result],
consumed: 1,
Expand Down Expand Up @@ -71,9 +70,15 @@ function handleListNodes(params, node) {
// If no iLvl, try an outline level
if (!iLvl && iLvl !== 0) iLvl = getOutlineLevelFromStyleTag(styleId, docx);

const { listType, listOrderingType, listrPrs, listpPrs, start, lvlText, lvlJc, customFormat } =
getNodeNumberingDefinition(node, iLvl, docx);
let numberingDefinition = getNodeNumberingDefinition(node, iLvl, docx);
if (!Object.keys(numberingDefinition).length) {
const { definition, ilvl } = getNodeNumberingDefinitionByStyle(node, docx);
if (definition) numberingDefinition = definition;
if (Number.isNaN(iLvl)) iLvl = ilvl;
}

const { listType, listOrderingType, listrPrs, listpPrs, start, lvlText, lvlJc, customFormat } = numberingDefinition;

// Fallback if the list definition is not found or is invalid
// See invalid-list-def-fallback.docx for example and
if (!listType) {
Expand Down Expand Up @@ -196,6 +201,7 @@ export function testForList(node, docx) {
let outlinelvl;

const styleId = paragraphStyle?.attributes['w:val'];

const styleTag = getStyleTagFromStyleId(styleId, docx);
if (styleTag && !numId) {
const { numPr: numPrRecursve, type } = getNumPrRecursive({ node, styleId, docx });
Expand All @@ -212,6 +218,11 @@ export function testForList(node, docx) {
const levelDefinition = abstractNumDefinition?.elements?.find(
(el) => el.name === 'w:lvl' && el.attributes?.['w:ilvl'] == ilvl,
);

if (numId && !levelDefinition && abstractNumDefinition) {
return true;
}

if (!levelDefinition) return false;

return !!numId;
Expand Down Expand Up @@ -478,7 +489,7 @@ export function normalizeLvlTextChar(lvlText) {
* @returns
*/
export function getNodeNumberingDefinition(item, level, docx) {
if (!item) return;
if (!item) return {};
const { attributes = {} } = item;

const { paragraphProperties = {} } = attributes;
Expand Down Expand Up @@ -525,6 +536,137 @@ export function getNodeNumberingDefinition(item, level, docx) {
return { listType, listOrderingType: listTypeDef, listrPrs, listpPrs, start, lvlText, lvlJc, customFormat };
}

export function getNodeNumberingDefinitionByStyle(item, docx) {
if (!item) return {};

const initialPpr = item.elements?.find((el) => el.name === 'w:pPr');
const styleTag = initialPpr?.elements?.find((el) => el.name === 'w:pStyle');
const styleId = styleTag?.attributes['w:val'];
const styleDef = getStyleTagFromStyleId(styleId, docx);
if (!styleDef) return {};

const pPr = styleDef.elements?.find((el) => el.name === 'w:pPr');
const numPr = pPr?.elements?.find((el) => el.name === 'w:numPr');
const numIdTag = numPr?.elements?.find((el) => el.name === 'w:numId');
const numId = numIdTag?.attributes?.['w:val'];
if (!numId) return {};

const abstractNumId = getAbstractNumIdByNumId(numId, docx);
if (!abstractNumId) return {};

const levelData = getLevelDataFromAbstractNum(abstractNumId, styleId, docx);
if (!levelData) return {};

const definition = extractDefinitionFromLevel(levelData.level, initialPpr);

return {
definition,
ilvl: levelData.ilvl,
};
}

function getAbstractNumIdByNumId(numId, docx) {
const numbering = docx['word/numbering.xml'];
if (!numbering) return null;

const { elements } = numbering;
const listData = elements[0];
const numberingElements = listData.elements || [];

const numDef = numberingElements.find((el) =>
el.name === 'w:num' &&
el.attributes?.['w:numId'] === numId
);

if (!numDef) return null;

const abstractNumIdRef = numDef.elements?.find((el) => el.name === 'w:abstractNumId');
return abstractNumIdRef?.attributes?.['w:val'];
}

function getLevelDataFromAbstractNum(abstractNumId, styleId, docx) {
const numbering = docx['word/numbering.xml'];
if (!numbering) return null;

const { elements } = numbering;
const listData = elements[0];
const numberingElements = listData.elements || [];

const abstractNum = numberingElements.find((el) =>
el.name === 'w:abstractNum' &&
el.attributes?.['w:abstractNumId'] === abstractNumId
);

if (!abstractNum) return null;

const levels = abstractNum.elements?.filter(el => el.name === 'w:lvl') || [];
for (const level of levels) {
const pStyle = level.elements?.find((el) => el.name === 'w:pStyle');
if (pStyle?.attributes?.['w:val'] === styleId) {
const found = {
level,
ilvl: Number(level.attributes?.['w:ilvl']) || 0
};
return found;
}
}

const level0 = levels.find((level) => level.attributes?.['w:ilvl'] === '0');
if (level0) {
return {
level: level0,
ilvl: 0,
};
}

return null;
}

function extractDefinitionFromLevel(level, initialPpr) {
if (!level) return {};

const start = level.elements?.find((el) => el.name === 'w:start')?.attributes?.['w:val'];

let numFmtTag = level.elements?.find((el) => el.name === 'w:numFmt');
let numFmt = numFmtTag?.attributes?.['w:val'];

let lvlText = level.elements?.find((el) => el.name === 'w:lvlText')?.attributes?.['w:val'];
lvlText = normalizeLvlTextChar(lvlText);

let customFormat;
if (numFmt === 'custom') customFormat = numFmtTag?.attributes?.['w:format'];

const lvlJc = level.elements?.find((el) => el.name === 'w:lvlJc')?.attributes?.['w:val'];
const pPr = level.elements?.find((el) => el.name === 'w:pPr');
const rPr = level.elements?.find((el) => el.name === 'w:rPr');

let listpPrs, listrPrs;
if (pPr) listpPrs = _processListParagraphProperties(pPr, initialPpr);
if (rPr) listrPrs = _processListRunProperties(rPr);

let listType;
if (unorderedListTypes.includes(numFmt?.toLowerCase())) {
listType = 'bulletList';
} else if (orderedListTypes.includes(numFmt)) {
listType = 'orderedList';
} else if (numFmt === 'custom') {
listType = 'orderedList';
} else {
return {};
}

return {
listType,
listOrderingType: numFmt,
listrPrs,
listpPrs,
start,
lvlText,
lvlJc,
customFormat,
};
}

export function getDefinitionForLevel(data, level) {
return data?.elements?.find((item) => Number(item.attributes['w:ilvl']) === level);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ 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 @@ -262,12 +269,22 @@ 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 @@ -286,6 +303,7 @@ const getDefaultParagraphStyle = (docx, styleId = '') => {
spacing: pPrByIdSpacingAttr || pPrDefaultSpacingAttr || pPrNormalSpacingAttr,
indent: pPrByIdIndentAttr || pPrDefaultIndentAttr || pPrNormalIndentAttr,
justify: pPrByIdJcAttr,
textCase,
};
};

Expand Down Expand Up @@ -366,6 +384,10 @@ export function getDefaultStyleDefinition(defaultStyleId, docx) {
else pageBreakAfterVal = Number(pageBreakAfter?.attributes?.['w:val']);
}

const basedOn = elementsWithId
.find((el) => el.elements.some((inner) => inner.name === 'w:basedOn'))
?.elements.find((inner) => inner.name === 'w:basedOn')?.attributes['w:val'];

const parsedAttrs = {
name,
qFormat: qFormat ? true : false,
Expand All @@ -374,6 +396,7 @@ export function getDefaultStyleDefinition(defaultStyleId, docx) {
outlineLevel: outlineLevel ? parseInt(outlineLvlValue) : null,
pageBreakBefore: pageBreakBeforeVal ? true : false,
pageBreakAfter: pageBreakAfterVal ? true : false,
basedOn: basedOn ?? null,
};

// rPr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ const generateDecorations = (state, styles) => {
if (name === 'paragraph' && !node.attrs?.styleId) lastStyleId = null;
if (name !== 'text' && name !== 'listItem' && name !== 'orderedList') return;

const linkedStyle = getLinkedStyle(lastStyleId, styles);
const { linkedStyle, basedOnStyle } = getLinkedStyle(lastStyleId, styles);
if (!linkedStyle) return;

const $pos = state.doc.resolve(pos);
const parent = $pos.parent;

const styleString = generateLinkedStyleString(linkedStyle, node, parent);
const styleString = generateLinkedStyleString(linkedStyle, basedOnStyle, node, parent);
if (!styleString) return;

const decoration = Decoration.inline(pos, pos + node.nodeSize, { style: styleString });
Expand All @@ -116,15 +116,24 @@ const generateDecorations = (state, styles) => {
* If the node contains a given mark, we don't override it with the linked style per MS Word behavior
*
* @param {Object} linkedStyle The linked style object
* @param {Object} basedOnStyle The basedOn 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, parent, includeSpacing = true) => {
export const generateLinkedStyleString = (linkedStyle, basedOnStyle, node, parent, includeSpacing = true) => {
if (!linkedStyle?.definition?.styles) return '';
const markValue = {};

Object.entries(linkedStyle.definition.styles).forEach(([k, value]) => {
const linkedDefinitionStyles = { ...linkedStyle.definition.styles };
const basedOnDefinitionStyles = { ...basedOnStyle?.definition?.styles };
const resultStyles = { ...linkedDefinitionStyles };

if (!linkedDefinitionStyles['font-size'] && basedOnDefinitionStyles['font-size']) {
resultStyles['font-size'] = basedOnDefinitionStyles['font-size'];
}

Object.entries(resultStyles).forEach(([k, value]) => {
const key = kebabCase(k);
const flattenedMarks = [];

Expand Down Expand Up @@ -182,7 +191,10 @@ export const generateLinkedStyleString = (linkedStyle, node, parent, includeSpac
* @returns {Object} The linked style
*/
const getLinkedStyle = (styleId, styles = []) => {
return styles.find((style) => style.id === styleId);
const linkedStyle = styles.find((style) => style.id === styleId);
const basedOn = linkedStyle?.definition?.attrs?.basedOn;
const basedOnStyle = styles.find((style) => style.id === basedOn);
return { linkedStyle, basedOnStyle };
};

export const getSpacingStyle = (spacing) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ const getStylesFromLinkedStyles = ({ node, pos, editor }) => {
const { state } = editor.view;
const linkedStyles = LinkedStylesPluginKey.getState(state)?.decorations;
const decorationsInPlace = linkedStyles?.find(pos, pos + node.nodeSize);
const styleDeco = decorationsInPlace?.find((dec) => dec.type.attrs?.style);
// We are looking from the end as there may be several decorations
// and we need to find the most specific one.
const styleDeco = decorationsInPlace?.findLast((dec) => dec.type.attrs?.style);
const style = styleDeco?.type.attrs?.style;

const stylesArray = style?.split(';') || [];
Expand Down
8 changes: 8 additions & 0 deletions packages/super-editor/src/extensions/paragraph/paragraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ export const Paragraph = Node.create({
return { style };
},
},
textCase: {
renderDOM: (attrs) => {
if (!attrs.textCase) return {};
return {
style: `text-transform: ${attrs.textCase}`,
}
},
},
};
},

Expand Down
Loading