Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .ackrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--ignore-directory=packages/superdoc/dist/
--ignore-directory=packages/super-editor/dist
--ignore-directory=examples/chrome-extension/lib/
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,17 @@ export const handleParagraphNode = (params) => {
const defaultStyleId = node.attributes?.['w:rsidRDefault'];
schemaNode.attrs['spacing'] = getParagraphSpacing(node, docx, styleId, schemaNode.attrs.marksAttrs);
schemaNode.attrs['rsidRDefault'] = defaultStyleId;
}

if (docx) {
// Handle right-aligned tabs for paragraph floating
const tabs = getParagraphTabs(node, docx, styleId);
if (tabs.hasRightTab) {
schemaNode.attrs['tabs'] = tabs;
// If there's a right tab, it typically means the paragraph content should align right
if (!schemaNode.attrs['textAlign']) {
schemaNode.attrs['textAlign'] = 'right';
}
}

const { justify } = getDefaultParagraphStyle(docx, styleId);
if (justify) {
schemaNode.attrs.justify = {
Expand Down Expand Up @@ -239,6 +247,38 @@ export const getParagraphSpacing = (node, docx, styleId = '', marks = []) => {
return spacing;
};

export const getParagraphTabs = (node, docx, styleId = '') => {
const tabs = {
rightTab: null,
hasRightTab: false,
};

const { tabs: pDefaultTabs = {} } = getDefaultParagraphStyle(docx, styleId);

const pPr = node.elements?.find((el) => el.name === 'w:pPr');
const inLineTabsTag = pPr?.elements?.find((el) => el.name === 'w:tabs');
const inLineTabs = inLineTabsTag?.elements || [];

// Check for right-aligned tabs in inline tabs
const rightTab = inLineTabs.find((tab) => tab.name === 'w:tab' && tab.attributes?.['w:val'] === 'right');

if (rightTab) {
tabs.rightTab = {
position: rightTab.attributes?.['w:pos'] ? twipsToPixels(rightTab.attributes['w:pos']) : null,
val: rightTab.attributes?.['w:val'],
};
tabs.hasRightTab = true;
}

// Check for right-aligned tabs in default styles if not found inline
if (!tabs.hasRightTab && pDefaultTabs.rightTab) {
tabs.rightTab = pDefaultTabs.rightTab;
tabs.hasRightTab = true;
}

return tabs;
};

const getDefaultParagraphStyle = (docx, styleId = '') => {
const styles = docx['word/styles.xml'];
if (!styles) {
Expand All @@ -262,6 +302,8 @@ const getDefaultParagraphStyle = (docx, styleId = '') => {
let pPrStyleIdSpacingTag = {};
let pPrStyleIdIndentTag = {};
let pPrStyleJc = {};
let styleRightTab = null;

if (styleId) {
const stylesById = styles.elements[0].elements?.find(
(el) => el.name === 'w:style' && el.attributes['w:styleId'] === styleId,
Expand All @@ -271,6 +313,11 @@ const getDefaultParagraphStyle = (docx, styleId = '') => {
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') || {};

// Check for tabs in style definition
const pPrStyleIdTabsTag = pPrById?.elements?.find((el) => el.name === 'w:tabs');
const pPrStyleIdTabs = pPrStyleIdTabsTag?.elements || [];
styleRightTab = pPrStyleIdTabs.find((tab) => tab.name === 'w:tab' && tab.attributes?.['w:val'] === 'right');
}

const { attributes: pPrDefaultSpacingAttr } = pPrDefaultSpacingTag;
Expand All @@ -282,10 +329,19 @@ const getDefaultParagraphStyle = (docx, styleId = '') => {
const { attributes: pPrNormalIndentAttr } = pPrNormalIndentTag;
const { attributes: pPrByIdIndentAttr } = pPrStyleIdIndentTag;

let tabs = {};
if (styleRightTab) {
tabs.rightTab = {
position: styleRightTab.attributes?.['w:pos'] ? twipsToPixels(styleRightTab.attributes['w:pos']) : null,
val: styleRightTab.attributes?.['w:val'],
};
}

return {
spacing: pPrByIdSpacingAttr || pPrDefaultSpacingAttr || pPrNormalSpacingAttr,
indent: pPrByIdIndentAttr || pPrDefaultIndentAttr || pPrNormalIndentAttr,
justify: pPrByIdJcAttr,
tabs,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const handleTabNode = (params) => {
}

const { attributes = {} } = node;

const processedNode = {
type: 'tab',
attrs: {
Expand Down
123 changes: 123 additions & 0 deletions packages/super-editor/src/tests/import/paragraphNodeImporter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,126 @@ describe('Check that paragraph-level sectPr is retained', () => {
expect(p2sectPrData).toEqual(sectPr2);
});
});

describe('paragraph tests to check right-aligned tabs', () => {
it('correctly handles paragraph with right-aligned tabs', () => {
const mockParagraph = {
name: 'w:p',
elements: [
{
name: 'w:pPr',
elements: [
{
name: 'w:tabs',
elements: [
{
name: 'w:tab',
attributes: {
'w:val': 'right',
'w:pos': '9270',
},
},
],
},
],
},
],
};

const { nodes } = handleParagraphNode({
nodes: [mockParagraph],
docx: {},
nodeListHandler: defaultNodeListHandler(),
});

const node = nodes[0];
expect(node.type).toBe('paragraph');
expect(node.attrs.tabs).toBeDefined();
expect(node.attrs.tabs.hasRightTab).toBe(true);
expect(node.attrs.tabs.rightTab.val).toBe('right');
expect(node.attrs.tabs.rightTab.position).toBe(618); // 9270 twips converted to pixels
expect(node.attrs.textAlign).toBe('right');
});

it('correctly handles paragraph with right-aligned tabs and existing textAlign', () => {
const mockParagraph = {
name: 'w:p',
elements: [
{
name: 'w:pPr',
elements: [
{
name: 'w:jc',
attributes: {
'w:val': 'center',
},
},
{
name: 'w:tabs',
elements: [
{
name: 'w:tab',
attributes: {
'w:val': 'right',
'w:pos': '9270',
},
},
],
},
],
},
],
};

const { nodes } = handleParagraphNode({
nodes: [mockParagraph],
docx: {},
nodeListHandler: defaultNodeListHandler(),
});

const node = nodes[0];
expect(node.type).toBe('paragraph');
expect(node.attrs.tabs).toBeDefined();
expect(node.attrs.tabs.hasRightTab).toBe(true);
expect(node.attrs.tabs.rightTab.val).toBe('right');
expect(node.attrs.tabs.rightTab.position).toBe(618);
// Should preserve existing textAlign, not override with 'right'
expect(node.attrs.textAlign).toBe('center');
});

it('correctly handles paragraph without right-aligned tabs', () => {
const mockParagraph = {
name: 'w:p',
elements: [
{
name: 'w:pPr',
elements: [
{
name: 'w:tabs',
elements: [
{
name: 'w:tab',
attributes: {
'w:val': 'left',
'w:pos': '1440',
},
},
],
},
],
},
],
};

const { nodes } = handleParagraphNode({
nodes: [mockParagraph],
docx: {},
nodeListHandler: defaultNodeListHandler(),
});

const node = nodes[0];
expect(node.type).toBe('paragraph');
expect(node.attrs.tabs).toBeUndefined();
expect(node.attrs.textAlign).toBeUndefined();
});
});