diff --git a/.nycrc b/.nycrc index 3a897e4c79..5afadd762c 100644 --- a/.nycrc +++ b/.nycrc @@ -1,9 +1,9 @@ { "check-coverage": true, - "lines": 97.77, - "functions": 93.89, - "branches": 94.83, - "statements": 97.75, + "lines": 97.86, + "functions": 94.33, + "branches": 94.99, + "statements": 97.85, "include": [ "src/**/*.ts" ], diff --git a/demo/17-footnotes.ts b/demo/17-footnotes.ts index a41f84a3c8..0ba23c07cb 100644 --- a/demo/17-footnotes.ts +++ b/demo/17-footnotes.ts @@ -4,14 +4,14 @@ import * as fs from "fs"; import { Document, FootnoteReferenceRun, Packer, Paragraph, TextRun } from "../build"; const doc = new Document({ - footnotes: [ - new Paragraph("Foo"), - new Paragraph("Test"), - new Paragraph("My amazing reference"), - new Paragraph("Foo1"), - new Paragraph("Test1"), - new Paragraph("My amazing reference1"), - ], + footnotes: { + 1: { children: [new Paragraph("Foo"), new Paragraph("Bar")] }, + 2: { children: [new Paragraph("Test")] }, + 3: { children: [new Paragraph("My amazing reference")] }, + 4: { children: [new Paragraph("Foo1")] }, + 5: { children: [new Paragraph("Test1")] }, + 6: { children: [new Paragraph("My amazing reference1")] }, + }, }); doc.addSection({ diff --git a/demo/21-bookmarks.ts b/demo/21-bookmarks.ts index 632fcb212f..b004f48690 100644 --- a/demo/21-bookmarks.ts +++ b/demo/21-bookmarks.ts @@ -33,7 +33,12 @@ doc.addSection({ children: [ new Paragraph({ heading: HeadingLevel.HEADING_1, - children: [new Bookmark("myAnchorId", "Lorem Ipsum")], + children: [ + new Bookmark({ + id: "myAnchorId", + children: [new TextRun("Lorem Ipsum")], + }), + ], }), new Paragraph("\n"), new Paragraph(LOREM_IPSUM), diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index 17a4baed2a..c12b65ee6d 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -19,7 +19,11 @@ export interface IPropertiesOptions { readonly externalStyles?: string; readonly styles?: IStylesOptions; readonly numbering?: INumberingOptions; - readonly footnotes?: Paragraph[]; + readonly footnotes?: { + readonly [key: string]: { + readonly children: Paragraph[]; + }; + }; readonly background?: IDocumentBackgroundOptions; readonly features?: { readonly trackRevisions?: boolean; diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index 15b389c96e..7b43920dca 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -240,7 +240,11 @@ describe("File", () => { describe("#createFootnote", () => { it("should create footnote", () => { const wrapper = new File({ - footnotes: [new Paragraph("hello")], + footnotes: { + 1: { + children: [new Paragraph("hello")], + }, + }, }); const tree = new Formatter().format(wrapper.FootNotes.View); diff --git a/src/file/file.ts b/src/file/file.ts index e91906f3d1..55464d04e9 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -139,8 +139,9 @@ export class File { } if (options.footnotes) { - for (const paragraph of options.footnotes) { - this.footnotesWrapper.View.createFootNote(paragraph); + // tslint:disable-next-line: forin + for (const key in options.footnotes) { + this.footnotesWrapper.View.createFootNote(parseFloat(key), options.footnotes[key].children); } } diff --git a/src/file/footnotes/footnote/footnote-attributes.ts b/src/file/footnotes/footnote/footnote-attributes.ts index ee978d0ef2..027f9213fe 100644 --- a/src/file/footnotes/footnote/footnote-attributes.ts +++ b/src/file/footnotes/footnote/footnote-attributes.ts @@ -1,11 +1,9 @@ import { XmlAttributeComponent } from "file/xml-components"; -export interface IFootnoteAttributesProperties { +export class FootnoteAttributes extends XmlAttributeComponent<{ readonly type?: string; readonly id: number; -} - -export class FootnoteAttributes extends XmlAttributeComponent { +}> { protected readonly xmlKeys = { type: "w:type", id: "w:id", diff --git a/src/file/footnotes/footnote/footnote.spec.ts b/src/file/footnotes/footnote/footnote.spec.ts index 49e497d988..e2ce67d7b3 100644 --- a/src/file/footnotes/footnote/footnote.spec.ts +++ b/src/file/footnotes/footnote/footnote.spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { Paragraph, TextRun } from "file/paragraph"; import { Footnote, FootnoteType } from "./footnote"; @@ -10,6 +11,7 @@ describe("Footnote", () => { const footnote = new Footnote({ id: 1, type: FootnoteType.SEPERATOR, + children: [], }); const tree = new Formatter().format(footnote); @@ -20,11 +22,141 @@ describe("Footnote", () => { it("should create a footnote without a footnote type", () => { const footnote = new Footnote({ id: 1, + children: [], }); const tree = new Formatter().format(footnote); expect(Object.keys(tree)).to.deep.equal(["w:footnote"]); expect(tree["w:footnote"]).to.deep.equal({ _attr: { "w:id": 1 } }); }); + + it("should append footnote ref run on the first footnote paragraph", () => { + const footnote = new Footnote({ + id: 1, + children: [new Paragraph({ children: [new TextRun("test-footnote")] })], + }); + const tree = new Formatter().format(footnote); + + expect(tree).to.deep.equal({ + "w:footnote": [ + { + _attr: { + "w:id": 1, + }, + }, + { + "w:p": [ + { + "w:r": [ + { + "w:rPr": [ + { + "w:rStyle": { + _attr: { + "w:val": "FootnoteReference", + }, + }, + }, + ], + }, + { + "w:footnoteRef": {}, + }, + ], + }, + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test-footnote", + ], + }, + ], + }, + ], + }, + ], + }); + }); + + it("should add multiple paragraphs", () => { + const footnote = new Footnote({ + id: 1, + children: [ + new Paragraph({ children: [new TextRun("test-footnote")] }), + new Paragraph({ children: [new TextRun("test-footnote-2")] }), + ], + }); + const tree = new Formatter().format(footnote); + + expect(tree).to.deep.equal({ + "w:footnote": [ + { + _attr: { + "w:id": 1, + }, + }, + { + "w:p": [ + { + "w:r": [ + { + "w:rPr": [ + { + "w:rStyle": { + _attr: { + "w:val": "FootnoteReference", + }, + }, + }, + ], + }, + { + "w:footnoteRef": {}, + }, + ], + }, + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test-footnote", + ], + }, + ], + }, + ], + }, + { + "w:p": [ + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test-footnote-2", + ], + }, + ], + }, + ], + }, + ], + }); + }); }); }); diff --git a/src/file/footnotes/footnote/footnote.ts b/src/file/footnotes/footnote/footnote.ts index 4ef653ddfb..19c41824b7 100644 --- a/src/file/footnotes/footnote/footnote.ts +++ b/src/file/footnotes/footnote/footnote.ts @@ -12,6 +12,7 @@ export enum FootnoteType { export interface IFootnoteOptions { readonly id: number; readonly type?: FootnoteType; + readonly children: Paragraph[]; } export class Footnote extends XmlComponent { @@ -23,10 +24,15 @@ export class Footnote extends XmlComponent { id: options.id, }), ); - } - public add(paragraph: Paragraph): void { - paragraph.addRunToFront(new FootnoteRefRun()); - this.root.push(paragraph); + for (let i = 0; i < options.children.length; i++) { + const child = options.children[i]; + + if (i === 0) { + child.addRunToFront(new FootnoteRefRun()); + } + + this.root.push(child); + } } } diff --git a/src/file/footnotes/footnote/run/reference-run.ts b/src/file/footnotes/footnote/run/reference-run.ts index 7dec91473d..3bfd772f50 100644 --- a/src/file/footnotes/footnote/run/reference-run.ts +++ b/src/file/footnotes/footnote/run/reference-run.ts @@ -2,11 +2,9 @@ import { Run } from "file/paragraph/run"; import { Style } from "file/paragraph/run/style"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -export interface IFootNoteReferenceRunAttributesProperties { +export class FootNoteReferenceRunAttributes extends XmlAttributeComponent<{ readonly id: number; -} - -export class FootNoteReferenceRunAttributes extends XmlAttributeComponent { +}> { protected readonly xmlKeys = { id: "w:id", }; diff --git a/src/file/footnotes/footnotes.ts b/src/file/footnotes/footnotes.ts index 09ed315f4b..05af80b6e7 100644 --- a/src/file/footnotes/footnotes.ts +++ b/src/file/footnotes/footnotes.ts @@ -1,4 +1,5 @@ import { XmlComponent } from "file/xml-components"; + import { Paragraph } from "../paragraph"; import { Footnote, FootnoteType } from "./footnote/footnote"; import { ContinuationSeperatorRun } from "./footnote/run/continuation-seperator-run"; @@ -6,14 +7,9 @@ import { SeperatorRun } from "./footnote/run/seperator-run"; import { FootnotesAttributes } from "./footnotes-attributes"; export class FootNotes extends XmlComponent { - // tslint:disable-next-line:readonly-keyword - private currentId: number; - constructor() { super("w:footnotes"); - this.currentId = 1; - this.root.push( new FootnotesAttributes({ wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", @@ -39,41 +35,44 @@ export class FootNotes extends XmlComponent { const begin = new Footnote({ id: -1, type: FootnoteType.SEPERATOR, + children: [ + new Paragraph({ + spacing: { + after: 0, + line: 240, + lineRule: "auto", + }, + children: [new SeperatorRun()], + }), + ], }); - begin.add( - new Paragraph({ - spacing: { - after: 0, - line: 240, - lineRule: "auto", - }, - children: [new SeperatorRun()], - }), - ); + this.root.push(begin); const spacing = new Footnote({ id: 0, type: FootnoteType.CONTINUATION_SEPERATOR, + children: [ + new Paragraph({ + spacing: { + after: 0, + line: 240, + lineRule: "auto", + }, + children: [new ContinuationSeperatorRun()], + }), + ], }); - spacing.add( - new Paragraph({ - spacing: { - after: 0, - line: 240, - lineRule: "auto", - }, - children: [new ContinuationSeperatorRun()], - }), - ); + this.root.push(spacing); } - public createFootNote(paragraph: Paragraph): void { - const footnote = new Footnote({ id: this.currentId }); - footnote.add(paragraph); - this.root.push(footnote); + public createFootNote(id: number, paragraph: Paragraph[]): void { + const footnote = new Footnote({ + id: id, + children: paragraph, + }); - this.currentId++; + this.root.push(footnote); } } diff --git a/src/file/paragraph/links/bookmark.spec.ts b/src/file/paragraph/links/bookmark.spec.ts index fe342fc374..778bdd50c2 100644 --- a/src/file/paragraph/links/bookmark.spec.ts +++ b/src/file/paragraph/links/bookmark.spec.ts @@ -2,20 +2,24 @@ import { assert, expect } from "chai"; import { Utility } from "tests/utility"; +import { TextRun } from "../run"; import { Bookmark } from "./bookmark"; describe("Bookmark", () => { let bookmark: Bookmark; beforeEach(() => { - bookmark = new Bookmark("anchor", "Internal Link"); + bookmark = new Bookmark({ + id: "anchor", + children: [new TextRun("Internal Link")], + }); }); it("should create a bookmark with three root elements", () => { const newJson = Utility.jsonify(bookmark); assert.equal(newJson.rootKey, undefined); assert.equal(newJson.start.rootKey, "w:bookmarkStart"); - assert.equal(newJson.text.rootKey, "w:r"); + assert.equal(newJson.children[0].rootKey, "w:r"); assert.equal(newJson.end.rootKey, "w:bookmarkEnd"); }); @@ -27,7 +31,7 @@ describe("Bookmark", () => { it("should create a bookmark with the correct attributes on the text element", () => { const newJson = Utility.jsonify(bookmark); - assert.equal(JSON.stringify(newJson.text.root[1].root[1]), JSON.stringify("Internal Link")); + assert.equal(JSON.stringify(newJson.children[0].root[1].root[1]), JSON.stringify("Internal Link")); }); it("should create a bookmark with the correct attributes on the bookmark end element", () => { diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index 261a756864..cf217155a2 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -6,24 +6,24 @@ import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attri export class Bookmark { public readonly start: BookmarkStart; - public readonly text: TextRun; + public readonly children: TextRun[]; public readonly end: BookmarkEnd; - constructor(name: string, text: string) { + constructor(options: { readonly id: string; readonly children: TextRun[] }) { const linkId = shortid.generate().toLowerCase(); - this.start = new BookmarkStart(name, linkId); - this.text = new TextRun(text); + this.start = new BookmarkStart(options.id, linkId); + this.children = options.children; this.end = new BookmarkEnd(linkId); } } export class BookmarkStart extends XmlComponent { - constructor(name: string, linkId: string) { + constructor(id: string, linkId: string) { super("w:bookmarkStart"); const attributes = new BookmarkStartAttributes({ - name, + name: id, id: linkId, }); this.root.push(attributes); diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 4302801a01..0aa4d98b2d 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -618,6 +618,28 @@ describe("Paragraph", () => { ], }); }); + + it("should not add ListParagraph style when custom is true", () => { + const paragraph = new Paragraph({ + numbering: { + reference: "test id", + level: 0, + custom: true, + }, + }); + const tree = new Formatter().format(paragraph); + expect(tree).to.deep.equal({ + "w:p": [ + { + "w:pPr": [ + { + "w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }], + }, + ], + }, + ], + }); + }); }); it("it should add bookmark", () => { @@ -625,7 +647,12 @@ describe("Paragraph", () => { return "test-unique-id"; }); const paragraph = new Paragraph({ - children: [new Bookmark("test-id", "test")], + children: [ + new Bookmark({ + id: "test-id", + children: [new TextRun("test")], + }), + ], }); const tree = new Formatter().format(paragraph); expect(tree).to.deep.equal({ diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 7c05ee711c..ae9d9b5d99 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -64,7 +64,9 @@ export class Paragraph extends XmlComponent { for (const child of options.children) { if (child instanceof Bookmark) { this.root.push(child.start); - this.root.push(child.text); + for (const textRun of child.children) { + this.root.push(textRun); + } this.root.push(child.end); continue; }