diff --git a/demo/demo2.js b/demo/demo2.js new file mode 100644 index 0000000000..147133a6a5 --- /dev/null +++ b/demo/demo2.js @@ -0,0 +1,70 @@ +const docx = require('../build'); + +const styles = new docx.Styles(); +styles.createParagraphStyle('Heading1', 'Heading 1') + .basedOn("Normal") + .next("Normal") + .quickFormat() + .size(28) + .bold() + .italics() + .spacing({after: 120}); + +styles.createParagraphStyle('Heading2', 'Heading 2') + .basedOn("Normal") + .next("Normal") + .quickFormat() + .size(26) + .bold() + .underline('double', 'FF0000') + .spacing({before: 240, after: 120}); + +styles.createParagraphStyle('aside', 'Aside') + .basedOn('Normal') + .next('Normal') + .color('999999') + .italics() + .indent(720) + .spacing({line: 276}); + +styles.createParagraphStyle('wellSpaced', 'Well Spaced') + .basedOn('Normal') + .spacing({line: 276, before: 20 * 72 * .1, after: 20 * 72 * .05}); + +styles.createParagraphStyle('ListParagraph', 'List Paragraph') + .quickFormat() + .basedOn('Normal'); + + +const numbering = new docx.Numbering(); +const numberedAbstract = numbering.createAbstractNumbering(); +numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left"); + +const doc = new docx.Document({ + creator: 'Clippy', + title: 'Sample Document', + description: 'A brief example of using docx', +}); + +doc.createParagraph('Test heading1, bold and italicized').heading1(); +doc.createParagraph('Some simple content'); +doc.createParagraph('Test heading2 with double red underline').heading2(); + +const letterNumbering = numbering.createConcreteNumbering(numberedAbstract); +const letterNumbering5 = numbering.createConcreteNumbering(numberedAbstract); +letterNumbering5.overrideLevel(0, 5); + +doc.createParagraph('Option1').setNumbering(letterNumbering, 0); +doc.createParagraph('Option5 -- override 2 to 5').setNumbering(letterNumbering5, 0); +doc.createParagraph('Option3').setNumbering(letterNumbering, 0); + +doc.createParagraph() + .createTextRun('Some monospaced content') + .font('Monospace'); + +doc.createParagraph('An aside, in light gray italics and indented').style('aside'); +doc.createParagraph('This is normal, but well-spaced text').style('wellSpaced'); +doc.createParagraph('This is normal'); + +const exporter = new docx.LocalPacker(doc, styles, undefined, numbering); +exporter.pack('test.docx'); diff --git a/package.json b/package.json index a329a674be..a7ff13b159 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docx", - "version": "2.0.0", + "version": "2.0.1", "description": "Generate .docx documents with JavaScript (formerly Office-Clippy)", "main": "build/index.js", "scripts": { @@ -9,7 +9,8 @@ "prepublishOnly": "npm run build", "lint": "tslint --project ./ts", "build": "rimraf ./build && tsc -p ts", - "demo": "npm run build && node ./demo/demo.js" + "demo": "npm run build && node ./demo/demo.js", + "demo2": "npm run build && node ./demo/demo2.js" }, "files": [ "ts", diff --git a/ts/numbering/level.ts b/ts/numbering/level.ts index 4aabee238a..8f8cf7bce6 100644 --- a/ts/numbering/level.ts +++ b/ts/numbering/level.ts @@ -56,21 +56,29 @@ class LevelJc extends XmlComponent { } } -export class Level extends XmlComponent { +class LevelBase extends XmlComponent { private paragraphProperties: ParagraphProperties; private runProperties: RunProperties; - constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) { + constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) { super("w:lvl"); this.root.push(new LevelAttributes({ ilvl: level, tentative: 1, })); - this.root.push(new Start(1)); - this.root.push(new NumberFormat(numberFormat)); - this.root.push(new LevelText(levelText)); - this.root.push(new LevelJc(lvlJc)); + if (start !== undefined) { + this.root.push(new Start(start)); + } + if (numberFormat !== undefined) { + this.root.push(new NumberFormat(numberFormat)); + } + if (levelText !== undefined) { + this.root.push(new LevelText(levelText)); + } + if (lvlJc !== undefined) { + this.root.push(new LevelJc(lvlJc)); + } this.paragraphProperties = new ParagraphProperties(); this.runProperties = new RunProperties(); @@ -198,3 +206,13 @@ export class Level extends XmlComponent { return this; }; } + +export class Level extends LevelBase { + // This is the level that sits under abstractNum. We make a + // handful of properties required + constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) { + super(level, 1, numberFormat, levelText, lvlJc); + } +} + +export class LevelForOverride extends LevelBase {} diff --git a/ts/numbering/num.ts b/ts/numbering/num.ts index ef7281dc78..8bdd6e9adc 100644 --- a/ts/numbering/num.ts +++ b/ts/numbering/num.ts @@ -1,4 +1,5 @@ import { Attributes, XmlAttributeComponent, XmlComponent } from "../docx/xml-components"; +import { LevelForOverride } from "./level"; class AbstractNumId extends XmlComponent { @@ -29,4 +30,51 @@ export class Num extends XmlComponent { this.root.push(new AbstractNumId(abstractNumId)); this.id = numId; } + + public overrideLevel(num: number, start?: number): LevelOverride { + const olvl = new LevelOverride(num, start); + this.root.push(olvl); + return olvl; + } +} + +class LevelOverrideAttributes extends XmlAttributeComponent<{ilvl: number}> { + protected xmlKeys = {ilvl: "w:ilvl"}; +} + +class LevelOverride extends XmlComponent { + private levelNum: number; + private lvl?: LevelForOverride; + + constructor(levelNum: number, start?: number) { + super("w:lvlOverride"); + this.root.push(new LevelOverrideAttributes({ilvl: levelNum})); + if (start !== undefined) { + this.root.push(new StartOverride(start)); + } + this.levelNum = levelNum; + } + + get level(): LevelForOverride { + let lvl: LevelForOverride; + if (!this.lvl) { + lvl = new LevelForOverride(this.levelNum); + this.root.push(lvl); + this.lvl = lvl; + } else { + lvl = this.lvl; + } + return lvl; + } +} + +class StartOverrideAttributes extends XmlAttributeComponent<{val: number}> { + protected xmlKeys = {val: "w:val"}; +} + +class StartOverride extends XmlComponent { + constructor(start: number) { + super("w:startOverride"); + this.root.push(new StartOverrideAttributes({val: start})); + } } diff --git a/ts/tests/numberingTest.ts b/ts/tests/numberingTest.ts index 59a71e720e..f280a00dc4 100644 --- a/ts/tests/numberingTest.ts +++ b/ts/tests/numberingTest.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import { Formatter } from "../export/formatter"; import { Numbering } from "../numbering"; import { AbstractNumbering } from "../numbering/abstract-numbering"; +import { LevelForOverride } from "../numbering/level"; import { Num } from "../numbering/num"; describe("Numbering", () => { @@ -394,3 +395,50 @@ describe("AbstractNumbering", () => { }); }); }); + +describe("concrete numbering", () => { + describe("#overrideLevel", () => { + let numbering; + let abstractNumbering; + let concreteNumbering; + beforeEach(() => { + numbering = new Numbering(); + abstractNumbering = numbering.createAbstractNumbering(); + concreteNumbering = numbering.createConcreteNumbering(abstractNumbering); + + }); + + it("sets a new override level for the given level number", () => { + concreteNumbering.overrideLevel(3); + const tree = new Formatter().format(concreteNumbering); + expect(tree["w:num"]).to.include({"w:lvlOverride": [{_attr: {"w:ilvl": 3}}]}); + }); + + it("sets the startOverride element if start is given", () => { + concreteNumbering.overrideLevel(1, 9); + const tree = new Formatter().format(concreteNumbering); + expect(tree["w:num"]).to.include({ + "w:lvlOverride": [ + {_attr: {"w:ilvl": 1}}, + {"w:startOverride": [{_attr: {"w:val": 9}}]}, + ], + }); + }); + + it("sets the lvl element if overrideLevel.level is accessed", () => { + const ol = concreteNumbering.overrideLevel(1); + expect(ol.level).to.be.instanceof(LevelForOverride); + const tree = new Formatter().format(concreteNumbering); + expect(tree["w:num"]).to.include({ + "w:lvlOverride": [ + {_attr: {"w:ilvl": 1}}, + {"w:lvl": [ + {_attr: {"w15:tentative": 1, "w:ilvl": 1}}, + {"w:pPr": []}, + {"w:rPr": []}, + ]}, + ], + }); + }); + }); +});