diff --git a/src/lib/editor/extensions/indent.ts b/src/lib/editor/extensions/indent.ts index aa64da2..90e76c2 100644 --- a/src/lib/editor/extensions/indent.ts +++ b/src/lib/editor/extensions/indent.ts @@ -110,14 +110,10 @@ function clampFirst(cm: number): number { return Math.round(Math.max(-INDENT_MAX_CM, Math.min(INDENT_MAX_CM, cm)) * 100) / 100; } +// Signed: a first-line indent may be negative (a hanging indent), and on a list the +// value is the offset from the level's 1.27cm base, so a file whose list sits closer +// to the margin than that imports a negative one. function parseIndent(value: string | null): number | null { - if (value == null || value === '') return null; - const n = parseFloat(value); - return Number.isFinite(n) && n > 0 ? n : null; -} - -// A first-line indent may be negative (a hanging indent), unlike a left margin. -function parseSignedIndent(value: string | null): number | null { if (value == null || value === '') return null; const n = parseFloat(value); return Number.isFinite(n) && n !== 0 ? n : null; @@ -171,7 +167,7 @@ export const Indent = Extension.create({ // `indent`, which is the whole block's left margin. indentFirst: { default: null, - parseHTML: (element: HTMLElement) => parseSignedIndent(element.getAttribute('data-indent-first')), + parseHTML: (element: HTMLElement) => parseIndent(element.getAttribute('data-indent-first')), renderHTML: (attributes: Record) => { if (attributes.indentFirst == null) return {}; const cm = Number(attributes.indentFirst); diff --git a/src/lib/import/docx.ts b/src/lib/import/docx.ts index b226596..c97728b 100644 --- a/src/lib/import/docx.ts +++ b/src/lib/import/docx.ts @@ -451,8 +451,10 @@ function makeListNode(ctx: Ctx, numId: number, ilvl: number): Node { if (def.start != null && def.start > 1) attrs.start = def.start; } if (ilvl === 0 && def.leftTwip != null) { - const extra = round2(twipToCm(def.leftTwip) - LIST_LEFT_STEP_CM); - if (extra > LIST_INDENT_EPS_CM) attrs.indent = extra; + // Signed: a level's w:ind w:left may sit left of the editor's 1.27cm base + // (w:left="360" is a common one). Floored there so the list stays in the text column. + const extra = round2(Math.max(-LIST_LEFT_STEP_CM, twipToCm(def.leftTwip) - LIST_LEFT_STEP_CM)); + if (Math.abs(extra) > LIST_INDENT_EPS_CM) attrs.indent = extra; } const node: Node = { type: bullet ? 'bulletList' : 'orderedList', content: [] }; if (Object.keys(attrs).length) node.attrs = attrs; diff --git a/src/lib/import/odt.ts b/src/lib/import/odt.ts index ae75943..3573905 100644 --- a/src/lib/import/odt.ts +++ b/src/lib/import/odt.ts @@ -1355,8 +1355,10 @@ function convertList(el: Element, ctx: Ctx, inheritedStyleName: string | null, d if (depth === 1) { const mlCm = listLevelMarginLeftCm(levelDef); if (mlCm != null) { - const extra = Math.round((mlCm - LIST_BASE_MARGIN_CM) * 100) / 100; - if (extra > LIST_INDENT_EPS_CM) indent = extra; + // Signed, floored at the base: a list may sit left of the editor's 1.27cm level + // margin, but never left of the text column. + const extra = Math.round(Math.max(-LIST_BASE_MARGIN_CM, mlCm - LIST_BASE_MARGIN_CM) * 100) / 100; + if (Math.abs(extra) > LIST_INDENT_EPS_CM) indent = extra; } }