From c68dc8c52a44eb2ec198331bb1d2ddade149b330 Mon Sep 17 00:00:00 2001 From: Dolan Date: Wed, 18 Dec 2019 21:11:15 +0000 Subject: [PATCH 1/9] Make hyperlinks declarative --- demo/35-hyperlinks.ts | 14 ++++-- src/export/formatter.ts | 5 ++- src/export/packer/next-compiler.ts | 32 ++++++------- src/file/core-properties/properties.ts | 6 +++ src/file/document/body/body.ts | 5 ++- src/file/document/document.ts | 4 +- src/file/file.ts | 57 ++++++++++++++++++------ src/file/paragraph/formatting/style.ts | 3 -- src/file/paragraph/links/hyperlink.ts | 4 ++ src/file/paragraph/paragraph.ts | 18 ++++++-- src/file/table/table-cell/table-cell.ts | 5 ++- src/file/xml-components/base.ts | 3 +- src/file/xml-components/index.ts | 1 + src/file/xml-components/xml-component.ts | 10 ++--- 14 files changed, 114 insertions(+), 53 deletions(-) diff --git a/demo/35-hyperlinks.ts b/demo/35-hyperlinks.ts index 6392299b84..9d2bd5062e 100644 --- a/demo/35-hyperlinks.ts +++ b/demo/35-hyperlinks.ts @@ -1,15 +1,21 @@ // Example on how to add hyperlinks to websites // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph } from "../build"; +import { Document, HyperlinkRef, Packer, Paragraph } from "../build"; -const doc = new Document(); -const link = doc.createHyperlink("http://www.example.com", "Hyperlink"); +const doc = new Document({ + hyperlinks: { + myCoolLink: { + link: "http://www.example.com", + text: "Hyperlink", + }, + }, +}); doc.addSection({ children: [ new Paragraph({ - children: [link], + children: [new HyperlinkRef("myCoolLink")], }), ], }); diff --git a/src/export/formatter.ts b/src/export/formatter.ts index 0a28070d13..b863ea4b97 100644 --- a/src/export/formatter.ts +++ b/src/export/formatter.ts @@ -1,8 +1,9 @@ import { BaseXmlComponent, IXmlableObject } from "file/xml-components"; +import { File } from "../file"; export class Formatter { - public format(input: BaseXmlComponent): IXmlableObject { - const output = input.prepForXml(); + public format(input: BaseXmlComponent, file?: File): IXmlableObject { + const output = input.prepForXml(file); if (output) { return output; diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index bff24a8ef7..a7b5fb617e 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -71,7 +71,7 @@ export class Compiler { file.verifyUpdateFields(); const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1; - const documentXmlData = xml(this.formatter.format(file.Document), prettify); + const documentXmlData = xml(this.formatter.format(file.Document, file), prettify); const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media); return { @@ -85,7 +85,7 @@ export class Compiler { ); }); - return xml(this.formatter.format(file.DocumentRelationships), prettify); + return xml(this.formatter.format(file.DocumentRelationships, file), prettify); })(), path: "word/_rels/document.xml.rels", }, @@ -99,11 +99,11 @@ export class Compiler { path: "word/document.xml", }, Styles: { - data: xml(this.formatter.format(file.Styles), prettify), + data: xml(this.formatter.format(file.Styles, file), prettify), path: "word/styles.xml", }, Properties: { - data: xml(this.formatter.format(file.CoreProperties), { + data: xml(this.formatter.format(file.CoreProperties, file), { declaration: { standalone: "yes", encoding: "UTF-8", @@ -112,15 +112,15 @@ export class Compiler { path: "docProps/core.xml", }, Numbering: { - data: xml(this.formatter.format(file.Numbering), prettify), + data: xml(this.formatter.format(file.Numbering, file), prettify), path: "word/numbering.xml", }, FileRelationships: { - data: xml(this.formatter.format(file.FileRelationships), prettify), + data: xml(this.formatter.format(file.FileRelationships, file), prettify), path: "_rels/.rels", }, HeaderRelationships: file.Headers.map((headerWrapper, index) => { - const xmlData = xml(this.formatter.format(headerWrapper.Header), prettify); + const xmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); mediaDatas.forEach((mediaData, i) => { @@ -132,12 +132,12 @@ export class Compiler { }); return { - data: xml(this.formatter.format(headerWrapper.Relationships), prettify), + data: xml(this.formatter.format(headerWrapper.Relationships, file), prettify), path: `word/_rels/header${index + 1}.xml.rels`, }; }), FooterRelationships: file.Footers.map((footerWrapper, index) => { - const xmlData = xml(this.formatter.format(footerWrapper.Footer), prettify); + const xmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); mediaDatas.forEach((mediaData, i) => { @@ -149,12 +149,12 @@ export class Compiler { }); return { - data: xml(this.formatter.format(footerWrapper.Relationships), prettify), + data: xml(this.formatter.format(footerWrapper.Relationships, file), prettify), path: `word/_rels/footer${index + 1}.xml.rels`, }; }), Headers: file.Headers.map((headerWrapper, index) => { - const tempXmlData = xml(this.formatter.format(headerWrapper.Header), prettify); + const tempXmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); // TODO: 0 needs to be changed when headers get relationships of their own const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0); @@ -165,7 +165,7 @@ export class Compiler { }; }), Footers: file.Footers.map((footerWrapper, index) => { - const tempXmlData = xml(this.formatter.format(footerWrapper.Footer), prettify); + const tempXmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); // TODO: 0 needs to be changed when headers get relationships of their own const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0); @@ -176,19 +176,19 @@ export class Compiler { }; }), ContentTypes: { - data: xml(this.formatter.format(file.ContentTypes), prettify), + data: xml(this.formatter.format(file.ContentTypes, file), prettify), path: "[Content_Types].xml", }, AppProperties: { - data: xml(this.formatter.format(file.AppProperties), prettify), + data: xml(this.formatter.format(file.AppProperties, file), prettify), path: "docProps/app.xml", }, FootNotes: { - data: xml(this.formatter.format(file.FootNotes), prettify), + data: xml(this.formatter.format(file.FootNotes, file), prettify), path: "word/footnotes.xml", }, Settings: { - data: xml(this.formatter.format(file.Settings), prettify), + data: xml(this.formatter.format(file.Settings, file), prettify), path: "word/settings.xml", }, }; diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index e807f0fd5f..3fb7bf45a4 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -18,6 +18,12 @@ export interface IPropertiesOptions { readonly styles?: IStylesOptions; readonly numbering?: INumberingOptions; readonly footnotes?: Paragraph[]; + readonly hyperlinks?: { + readonly [key: string]: { + readonly link: string; + readonly text: string; + }; + }; } export class CoreProperties extends XmlComponent { diff --git a/src/file/document/body/body.ts b/src/file/document/body/body.ts index e91b72d59a..1fcb3ab412 100644 --- a/src/file/document/body/body.ts +++ b/src/file/document/body/body.ts @@ -1,5 +1,6 @@ import { IXmlableObject, XmlComponent } from "file/xml-components"; import { Paragraph, ParagraphProperties, TableOfContents } from "../.."; +import { File } from "../../../file"; import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties"; export class Body extends XmlComponent { @@ -24,12 +25,12 @@ export class Body extends XmlComponent { this.sections.push(new SectionProperties(options)); } - public prepForXml(): IXmlableObject | undefined { + public prepForXml(file?: File): IXmlableObject | undefined { if (this.sections.length === 1) { this.root.push(this.sections.pop() as SectionProperties); } - return super.prepForXml(); + return super.prepForXml(file); } public push(component: XmlComponent): void { diff --git a/src/file/document/document.ts b/src/file/document/document.ts index d8c0c55131..80b04a379c 100644 --- a/src/file/document/document.ts +++ b/src/file/document/document.ts @@ -1,6 +1,6 @@ // http://officeopenxml.com/WPdocument.php import { XmlComponent } from "file/xml-components"; -import { Paragraph } from "../paragraph"; +import { Hyperlink, Paragraph } from "../paragraph"; import { Table } from "../table"; import { TableOfContents } from "../table-of-contents"; import { Body } from "./body"; @@ -36,7 +36,7 @@ export class Document extends XmlComponent { this.root.push(this.body); } - public add(item: Paragraph | Table | TableOfContents): Document { + public add(item: Paragraph | Table | TableOfContents | Hyperlink): Document { this.body.push(item); return this; } diff --git a/src/file/file.ts b/src/file/file.ts index f2bcad3610..14f6382b9f 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -17,7 +17,7 @@ import { Footer, Header } from "./header"; import { HeaderWrapper, IDocumentHeader } from "./header-wrapper"; import { Media } from "./media"; import { Numbering } from "./numbering"; -import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; +import { Bookmark, Hyperlink, HyperlinkRef, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { TargetModeType } from "./relationships/relationship/relationship"; import { Settings } from "./settings"; @@ -61,13 +61,13 @@ export class File { private readonly contentTypes: ContentTypes; private readonly appProperties: AppProperties; private readonly styles: Styles; + private readonly hyperlinkCache: { readonly [key: string]: Hyperlink }; constructor( options: IPropertiesOptions = { creator: "Un-named", revision: "1", lastModifiedBy: "Un-named", - footnotes: [], }, fileProperties: IFileProperties = {}, sections: ISectionOptions[] = [], @@ -134,6 +134,12 @@ export class File { this.document.Body.addSection(section.properties ? section.properties : {}); for (const child of section.children) { + if (child instanceof HyperlinkRef) { + const hyperlink = this.hyperlinkCache[child.id]; + this.document.add(hyperlink); + continue; + } + this.document.add(child); } } @@ -143,18 +149,21 @@ export class File { this.footNotes.createFootNote(paragraph); } } - } - public createHyperlink(link: string, text?: string): Hyperlink { - const newText = text === undefined ? link : text; - const hyperlink = new Hyperlink(newText, shortid.generate().toLowerCase()); - this.docRelationships.createRelationship( - hyperlink.linkId, - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", - link, - TargetModeType.EXTERNAL, - ); - return hyperlink; + if (options.hyperlinks) { + const cache = {}; + + for (const key in options.hyperlinks) { + if (!options.hyperlinks[key]) { + continue; + } + + const hyperlink = this.createHyperlink(options.hyperlinks[key].link, options.hyperlinks[key].text); + cache[key] = hyperlink; + } + + this.hyperlinkCache = cache; + } } public createInternalHyperLink(anchor: string, text?: string): Hyperlink { @@ -194,6 +203,12 @@ export class File { }); for (const child of children) { + if (child instanceof HyperlinkRef) { + const hyperlink = this.hyperlinkCache[child.id]; + this.document.add(hyperlink); + continue; + } + this.document.add(child); } } @@ -204,6 +219,18 @@ export class File { } } + private createHyperlink(link: string, text?: string): Hyperlink { + const newText = text === undefined ? link : text; + const hyperlink = new Hyperlink(newText, shortid.generate().toLowerCase()); + this.docRelationships.createRelationship( + hyperlink.linkId, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", + link, + TargetModeType.EXTERNAL, + ); + return hyperlink; + } + private createHeader(header: Header): HeaderWrapper { const wrapper = new HeaderWrapper(this.media, this.currentRelationshipId++); @@ -336,4 +363,8 @@ export class File { public get Settings(): Settings { return this.settings; } + + public get HyperlinkCache(): { readonly [key: string]: Hyperlink } { + return this.hyperlinkCache; + } } diff --git a/src/file/paragraph/formatting/style.ts b/src/file/paragraph/formatting/style.ts index a493e4b025..edde301290 100644 --- a/src/file/paragraph/formatting/style.ts +++ b/src/file/paragraph/formatting/style.ts @@ -11,11 +11,8 @@ export enum HeadingLevel { } export class Style extends XmlComponent { - public readonly styleId: string; - constructor(styleId: string) { super("w:pStyle"); - this.styleId = styleId; this.root.push( new Attributes({ val: styleId, diff --git a/src/file/paragraph/links/hyperlink.ts b/src/file/paragraph/links/hyperlink.ts index 30e60616ec..ed69331863 100644 --- a/src/file/paragraph/links/hyperlink.ts +++ b/src/file/paragraph/links/hyperlink.ts @@ -3,6 +3,10 @@ import { XmlComponent } from "file/xml-components"; import { TextRun } from "../run"; import { HyperlinkAttributes, IHyperlinkAttributesProperties } from "./hyperlink-attributes"; +export class HyperlinkRef { + constructor(public readonly id: string) {} +} + export class Hyperlink extends XmlComponent { public readonly linkId: string; private readonly textRun: TextRun; diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 7de245fab8..c97d8d34fd 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,7 +1,8 @@ // http://officeopenxml.com/WPparagraph.php import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; -import { XmlComponent } from "file/xml-components"; +import { IXmlableObject, XmlComponent } from "file/xml-components"; +import { File } from "../file"; import { Alignment, AlignmentType } from "./formatting/alignment"; import { Bidirectional } from "./formatting/bidirectional"; import { IBorderOptions, ThematicBreak } from "./formatting/border"; @@ -12,7 +13,7 @@ import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spa import { HeadingLevel, Style } from "./formatting/style"; import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; -import { Bookmark, Hyperlink, OutlineLevel } from "./links"; +import { Bookmark, HyperlinkRef, OutlineLevel } from "./links"; import { ParagraphProperties } from "./properties"; import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run"; @@ -45,7 +46,7 @@ export interface IParagraphOptions { readonly custom?: boolean; }; readonly children?: Array< - TextRun | PictureRun | Hyperlink | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | FootnoteReferenceRun + TextRun | PictureRun | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | FootnoteReferenceRun | HyperlinkRef >; } @@ -159,6 +160,17 @@ export class Paragraph extends XmlComponent { } } + public prepForXml(file: File): IXmlableObject | undefined { + for (const element of this.root) { + if (element instanceof HyperlinkRef) { + const index = this.root.indexOf(element); + this.root[index] = file.HyperlinkCache[element.id]; + } + } + + return super.prepForXml(); + } + public addRunToFront(run: Run): Paragraph { this.root.splice(1, 0, run); return this; diff --git a/src/file/table/table-cell/table-cell.ts b/src/file/table/table-cell/table-cell.ts index 469331bc08..fe8f8b5868 100644 --- a/src/file/table/table-cell/table-cell.ts +++ b/src/file/table/table-cell/table-cell.ts @@ -3,6 +3,7 @@ import { Paragraph } from "file/paragraph"; import { BorderStyle } from "file/styles"; import { IXmlableObject, XmlComponent } from "file/xml-components"; +import { File } from "../../file"; import { ITableShadingAttributesProperties } from "../shading"; import { Table } from "../table"; import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins"; @@ -110,11 +111,11 @@ export class TableCell extends XmlComponent { } } - public prepForXml(): IXmlableObject | undefined { + public prepForXml(file?: File): IXmlableObject | undefined { // Cells must end with a paragraph if (!(this.root[this.root.length - 1] instanceof Paragraph)) { this.root.push(new Paragraph({})); } - return super.prepForXml(); + return super.prepForXml(file); } } diff --git a/src/file/xml-components/base.ts b/src/file/xml-components/base.ts index cfc4ec47b3..782dfdba12 100644 --- a/src/file/xml-components/base.ts +++ b/src/file/xml-components/base.ts @@ -1,3 +1,4 @@ +import { File } from "../file"; import { IXmlableObject } from "./xmlable-object"; export abstract class BaseXmlComponent { @@ -9,7 +10,7 @@ export abstract class BaseXmlComponent { this.rootKey = rootKey; } - public abstract prepForXml(): IXmlableObject | undefined; + public abstract prepForXml(file?: File): IXmlableObject | undefined; public get IsDeleted(): boolean { return this.deleted; diff --git a/src/file/xml-components/index.ts b/src/file/xml-components/index.ts index 66e9641bfd..295161b395 100644 --- a/src/file/xml-components/index.ts +++ b/src/file/xml-components/index.ts @@ -4,3 +4,4 @@ export * from "./default-attributes"; export * from "./imported-xml-component"; export * from "./xmlable-object"; export * from "./initializable-xml-component"; +export * from "./base"; diff --git a/src/file/xml-components/xml-component.ts b/src/file/xml-components/xml-component.ts index 59192e3e4d..dfe3800b96 100644 --- a/src/file/xml-components/xml-component.ts +++ b/src/file/xml-components/xml-component.ts @@ -1,19 +1,19 @@ +import { File } from "../file"; import { BaseXmlComponent } from "./base"; import { IXmlableObject } from "./xmlable-object"; -export { BaseXmlComponent }; export const EMPTY_OBJECT = Object.seal({}); export abstract class XmlComponent extends BaseXmlComponent { - // tslint:disable-next-line:readonly-keyword - protected root: Array; + // tslint:disable-next-line:readonly-keyword no-any + protected root: Array; constructor(rootKey: string) { super(rootKey); this.root = new Array(); } - public prepForXml(): IXmlableObject | undefined { + public prepForXml(file?: File): IXmlableObject | undefined { const children = this.root .filter((c) => { if (c instanceof BaseXmlComponent) { @@ -23,7 +23,7 @@ export abstract class XmlComponent extends BaseXmlComponent { }) .map((comp) => { if (comp instanceof BaseXmlComponent) { - return comp.prepForXml(); + return comp.prepForXml(file); } return comp; }) From 3fdbca939e95223ccbb50e572915f61514e9ec0b Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sat, 21 Dec 2019 03:31:09 +0000 Subject: [PATCH 2/9] Make internal hyperlink declarative --- demo/21-bookmarks.ts | 15 +++++++------ demo/35-hyperlinks.ts | 3 ++- src/file/core-properties/properties.ts | 18 +++++++++++----- src/file/file.ts | 30 +++++++++++++++----------- src/file/paragraph/links/bookmark.ts | 4 ++++ src/file/paragraph/links/hyperlink.ts | 5 +++++ src/file/paragraph/paragraph.ts | 4 ++-- 7 files changed, 52 insertions(+), 27 deletions(-) diff --git a/demo/21-bookmarks.ts b/demo/21-bookmarks.ts index cc0bac5517..d104ca7574 100644 --- a/demo/21-bookmarks.ts +++ b/demo/21-bookmarks.ts @@ -1,7 +1,7 @@ // This demo shows how to create bookmarks then link to them with internal hyperlinks // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HeadingLevel, Packer, PageBreak, Paragraph } from "../build"; +import { Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph } from "../build"; const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante."; @@ -10,13 +10,16 @@ const doc = new Document({ creator: "Clippy", title: "Sample Document", description: "A brief example of using docx with bookmarks and internal hyperlinks", + hyperlinks: { + myAnchorId: { + text: "Hyperlink", + type: HyperlinkType.INTERNAL, + }, + }, }); -const anchorId = "anchorID"; - // First create the bookmark -const bookmark = doc.createBookmark(anchorId, "Lorem Ipsum"); -const hyperlink = doc.createInternalHyperLink(anchorId, `Click me!`); +const bookmark = doc.createBookmark("myAnchorId", "Lorem Ipsum"); doc.addSection({ children: [ @@ -30,7 +33,7 @@ doc.addSection({ children: [new PageBreak()], }), new Paragraph({ - children: [hyperlink], + children: [new HyperlinkRef("myAnchorId")], }), ], }); diff --git a/demo/35-hyperlinks.ts b/demo/35-hyperlinks.ts index 9d2bd5062e..fb84f13099 100644 --- a/demo/35-hyperlinks.ts +++ b/demo/35-hyperlinks.ts @@ -1,13 +1,14 @@ // Example on how to add hyperlinks to websites // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HyperlinkRef, Packer, Paragraph } from "../build"; +import { Document, HyperlinkRef, HyperlinkType, Packer, Paragraph } from "../build"; const doc = new Document({ hyperlinks: { myCoolLink: { link: "http://www.example.com", text: "Hyperlink", + type: HyperlinkType.EXTERNAL, }, }, }); diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index 3fb7bf45a4..ceddff39e0 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -2,10 +2,21 @@ import { XmlComponent } from "file/xml-components"; import { DocumentAttributes } from "../document/document-attributes"; import { INumberingOptions } from "../numbering"; -import { Paragraph } from "../paragraph"; +import { HyperlinkType, Paragraph } from "../paragraph"; import { IStylesOptions } from "../styles"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; +export interface IInternalHyperlinkDefinition { + readonly text: string; + readonly type: HyperlinkType.INTERNAL; +} + +export interface IExternalHyperlinkDefinition { + readonly link: string; + readonly text: string; + readonly type: HyperlinkType.EXTERNAL; +} + export interface IPropertiesOptions { readonly title?: string; readonly subject?: string; @@ -19,10 +30,7 @@ export interface IPropertiesOptions { readonly numbering?: INumberingOptions; readonly footnotes?: Paragraph[]; readonly hyperlinks?: { - readonly [key: string]: { - readonly link: string; - readonly text: string; - }; + readonly [key: string]: IInternalHyperlinkDefinition | IExternalHyperlinkDefinition; }; } diff --git a/src/file/file.ts b/src/file/file.ts index 14f6382b9f..21a8487d71 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -17,7 +17,7 @@ import { Footer, Header } from "./header"; import { HeaderWrapper, IDocumentHeader } from "./header-wrapper"; import { Media } from "./media"; import { Numbering } from "./numbering"; -import { Bookmark, Hyperlink, HyperlinkRef, Paragraph } from "./paragraph"; +import { Bookmark, Hyperlink, HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { TargetModeType } from "./relationships/relationship/relationship"; import { Settings } from "./settings"; @@ -158,7 +158,13 @@ export class File { continue; } - const hyperlink = this.createHyperlink(options.hyperlinks[key].link, options.hyperlinks[key].text); + const hyperlinkRef = options.hyperlinks[key]; + + const hyperlink = + hyperlinkRef.type === HyperlinkType.EXTERNAL + ? this.createHyperlink(hyperlinkRef.link, hyperlinkRef.text) + : this.createInternalHyperLink(key, hyperlinkRef.text); + cache[key] = hyperlink; } @@ -166,14 +172,6 @@ export class File { } } - public createInternalHyperLink(anchor: string, text?: string): Hyperlink { - const newText = text === undefined ? anchor : text; - const hyperlink = new Hyperlink(newText, shortid.generate().toLowerCase(), anchor); - // NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark - // we don't need to create a new relationship. - return hyperlink; - } - public createBookmark(name: string, text: string = name): Bookmark { return new Bookmark(name, text, this.docRelationships.RelationshipCount); } @@ -219,9 +217,8 @@ export class File { } } - private createHyperlink(link: string, text?: string): Hyperlink { - const newText = text === undefined ? link : text; - const hyperlink = new Hyperlink(newText, shortid.generate().toLowerCase()); + private createHyperlink(link: string, text: string = link): Hyperlink { + const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase()); this.docRelationships.createRelationship( hyperlink.linkId, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", @@ -231,6 +228,13 @@ export class File { return hyperlink; } + private createInternalHyperLink(anchor: string, text: string = anchor): Hyperlink { + const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase(), anchor); + // NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark + // we don't need to create a new relationship. + return hyperlink; + } + private createHeader(header: Header): HeaderWrapper { const wrapper = new HeaderWrapper(this.media, this.currentRelationshipId++); diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index c8c339e578..5ef567899b 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -3,6 +3,10 @@ import { XmlComponent } from "file/xml-components"; import { TextRun } from "../run"; import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; +export class BookmarkRef { + constructor(public readonly name: string, public readonly text: string) {} +} + export class Bookmark { public readonly linkId: number; public readonly start: BookmarkStart; diff --git a/src/file/paragraph/links/hyperlink.ts b/src/file/paragraph/links/hyperlink.ts index ed69331863..302acfd603 100644 --- a/src/file/paragraph/links/hyperlink.ts +++ b/src/file/paragraph/links/hyperlink.ts @@ -3,6 +3,11 @@ import { XmlComponent } from "file/xml-components"; import { TextRun } from "../run"; import { HyperlinkAttributes, IHyperlinkAttributesProperties } from "./hyperlink-attributes"; +export enum HyperlinkType { + INTERNAL = "INTERNAL", + EXTERNAL = "EXTERNAL", +} + export class HyperlinkRef { constructor(public readonly id: string) {} } diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index c97d8d34fd..c1a8861568 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -13,7 +13,7 @@ import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spa import { HeadingLevel, Style } from "./formatting/style"; import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; -import { Bookmark, HyperlinkRef, OutlineLevel } from "./links"; +import { Bookmark, BookmarkRef, HyperlinkRef, OutlineLevel } from "./links"; import { ParagraphProperties } from "./properties"; import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run"; @@ -46,7 +46,7 @@ export interface IParagraphOptions { readonly custom?: boolean; }; readonly children?: Array< - TextRun | PictureRun | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | FootnoteReferenceRun | HyperlinkRef + TextRun | PictureRun | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | FootnoteReferenceRun | HyperlinkRef | BookmarkRef >; } From ee5425bef71ff99301e30f8be66debbc912760d2 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sat, 21 Dec 2019 03:58:58 +0000 Subject: [PATCH 3/9] Add shortId type --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index ee20450d0e..792ca7e14b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -355,6 +355,12 @@ "@types/node": "*" } }, + "@types/shortid": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", + "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", + "dev": true + }, "@types/sinon": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.3.tgz", diff --git a/package.json b/package.json index cc50a5d9e0..2ad66b4a64 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "@types/request-promise": "^4.1.42", + "@types/shortid": "0.0.29", "@types/sinon": "^4.3.1", "@types/webpack": "^4.4.24", "awesome-typescript-loader": "^3.4.1", From b83d2c388fefb943893cae9610747eb2f6bba057 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sat, 21 Dec 2019 03:59:40 +0000 Subject: [PATCH 4/9] Make Bookmark declarative --- demo/21-bookmarks.ts | 7 ++---- src/file/file.ts | 6 +----- src/file/paragraph/links/bookmark.spec.ts | 16 +++++--------- src/file/paragraph/links/bookmark.ts | 26 ++++++++--------------- 4 files changed, 17 insertions(+), 38 deletions(-) diff --git a/demo/21-bookmarks.ts b/demo/21-bookmarks.ts index d104ca7574..1ad50eb9b1 100644 --- a/demo/21-bookmarks.ts +++ b/demo/21-bookmarks.ts @@ -1,7 +1,7 @@ // This demo shows how to create bookmarks then link to them with internal hyperlinks // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph } from "../build"; +import { Bookmark, Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph } from "../build"; const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante."; @@ -18,14 +18,11 @@ const doc = new Document({ }, }); -// First create the bookmark -const bookmark = doc.createBookmark("myAnchorId", "Lorem Ipsum"); - doc.addSection({ children: [ new Paragraph({ heading: HeadingLevel.HEADING_1, - children: [bookmark], + children: [new Bookmark("myAnchorId", "Lorem Ipsum")], }), new Paragraph("\n"), new Paragraph(LOREM_IPSUM), diff --git a/src/file/file.ts b/src/file/file.ts index 21a8487d71..e549b4b624 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -17,7 +17,7 @@ import { Footer, Header } from "./header"; import { HeaderWrapper, IDocumentHeader } from "./header-wrapper"; import { Media } from "./media"; import { Numbering } from "./numbering"; -import { Bookmark, Hyperlink, HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph"; +import { Hyperlink, HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { TargetModeType } from "./relationships/relationship/relationship"; import { Settings } from "./settings"; @@ -172,10 +172,6 @@ export class File { } } - public createBookmark(name: string, text: string = name): Bookmark { - return new Bookmark(name, text, this.docRelationships.RelationshipCount); - } - public addSection({ headers = { default: new Header() }, footers = { default: new Header() }, diff --git a/src/file/paragraph/links/bookmark.spec.ts b/src/file/paragraph/links/bookmark.spec.ts index a597347013..fe342fc374 100644 --- a/src/file/paragraph/links/bookmark.spec.ts +++ b/src/file/paragraph/links/bookmark.spec.ts @@ -1,4 +1,4 @@ -import { assert } from "chai"; +import { assert, expect } from "chai"; import { Utility } from "tests/utility"; @@ -8,7 +8,7 @@ describe("Bookmark", () => { let bookmark: Bookmark; beforeEach(() => { - bookmark = new Bookmark("anchor", "Internal Link", 0); + bookmark = new Bookmark("anchor", "Internal Link"); }); it("should create a bookmark with three root elements", () => { @@ -21,11 +21,8 @@ describe("Bookmark", () => { it("should create a bookmark with the correct attributes on the bookmark start element", () => { const newJson = Utility.jsonify(bookmark); - const attributes = { - name: "anchor", - id: "1", - }; - assert.equal(JSON.stringify(newJson.start.root[0].root), JSON.stringify(attributes)); + + assert.equal(newJson.start.root[0].root.name, "anchor"); }); it("should create a bookmark with the correct attributes on the text element", () => { @@ -35,9 +32,6 @@ describe("Bookmark", () => { it("should create a bookmark with the correct attributes on the bookmark end element", () => { const newJson = Utility.jsonify(bookmark); - const attributes = { - id: "1", - }; - assert.equal(JSON.stringify(newJson.end.root[0].root), JSON.stringify(attributes)); + expect(newJson.end.root[0].root.id).to.be.a("string"); }); }); diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index 5ef567899b..addf14530e 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -1,5 +1,6 @@ // http://officeopenxml.com/WPbookmark.php import { XmlComponent } from "file/xml-components"; +import * as shortid from "shortid"; import { TextRun } from "../run"; import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; @@ -8,46 +9,37 @@ export class BookmarkRef { } export class Bookmark { - public readonly linkId: number; public readonly start: BookmarkStart; public readonly text: TextRun; public readonly end: BookmarkEnd; - constructor(name: string, text: string, relationshipsCount: number) { - this.linkId = relationshipsCount + 1; + constructor(name: string, text: string) { + const linkId = shortid.generate().toLowerCase(); - this.start = new BookmarkStart(name, this.linkId); + this.start = new BookmarkStart(name, linkId); this.text = new TextRun(text); - this.end = new BookmarkEnd(this.linkId); + this.end = new BookmarkEnd(linkId); } } export class BookmarkStart extends XmlComponent { - public readonly linkId: number; - - constructor(name: string, relationshipsCount: number) { + constructor(name: string, linkId: string) { super("w:bookmarkStart"); - this.linkId = relationshipsCount; - const id = `${this.linkId}`; const attributes = new BookmarkStartAttributes({ name, - id, + id: linkId, }); this.root.push(attributes); } } export class BookmarkEnd extends XmlComponent { - public readonly linkId: number; - - constructor(relationshipsCount: number) { + constructor(linkId: string) { super("w:bookmarkEnd"); - this.linkId = relationshipsCount; - const id = `${this.linkId}`; const attributes = new BookmarkEndAttributes({ - id, + id: linkId, }); this.root.push(attributes); } From de03f19b46ba375832ea18fdf60777c51b5c3325 Mon Sep 17 00:00:00 2001 From: Dolan Date: Tue, 24 Dec 2019 00:44:15 +0000 Subject: [PATCH 5/9] Remove unused code and improve coverage --- src/file/paragraph/links/bookmark.ts | 4 ---- src/file/paragraph/links/hyperlink.spec.ts | 9 ++++++++ src/file/paragraph/paragraph.ts | 4 ++-- src/file/paragraph/run/picture-run.ts | 4 ---- src/file/paragraph/run/run.spec.ts | 24 ++++++++++++++++++++++ 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index addf14530e..261a756864 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -4,10 +4,6 @@ import * as shortid from "shortid"; import { TextRun } from "../run"; import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; -export class BookmarkRef { - constructor(public readonly name: string, public readonly text: string) {} -} - export class Bookmark { public readonly start: BookmarkStart; public readonly text: TextRun; diff --git a/src/file/paragraph/links/hyperlink.spec.ts b/src/file/paragraph/links/hyperlink.spec.ts index 93b59e07b6..4b06a933f5 100644 --- a/src/file/paragraph/links/hyperlink.spec.ts +++ b/src/file/paragraph/links/hyperlink.spec.ts @@ -3,6 +3,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import { Hyperlink } from "./"; +import { HyperlinkRef } from "./hyperlink"; describe("Hyperlink", () => { let hyperlink: Hyperlink; @@ -59,3 +60,11 @@ describe("Hyperlink", () => { }); }); }); + +describe("HyperlinkRef", () => { + describe("#constructor()", () => { + const hyperlinkRef = new HyperlinkRef("test-id"); + + expect(hyperlinkRef.id).to.equal("test-id"); + }); +}); diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index c1a8861568..c97d8d34fd 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -13,7 +13,7 @@ import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spa import { HeadingLevel, Style } from "./formatting/style"; import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; -import { Bookmark, BookmarkRef, HyperlinkRef, OutlineLevel } from "./links"; +import { Bookmark, HyperlinkRef, OutlineLevel } from "./links"; import { ParagraphProperties } from "./properties"; import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run"; @@ -46,7 +46,7 @@ export interface IParagraphOptions { readonly custom?: boolean; }; readonly children?: Array< - TextRun | PictureRun | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | FootnoteReferenceRun | HyperlinkRef | BookmarkRef + TextRun | PictureRun | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | FootnoteReferenceRun | HyperlinkRef >; } diff --git a/src/file/paragraph/run/picture-run.ts b/src/file/paragraph/run/picture-run.ts index c796beebfc..e98dba573a 100644 --- a/src/file/paragraph/run/picture-run.ts +++ b/src/file/paragraph/run/picture-run.ts @@ -7,10 +7,6 @@ export class PictureRun extends Run { constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) { super({}); - if (imageData === undefined) { - throw new Error("imageData cannot be undefined"); - } - const drawing = new Drawing(imageData, drawingOptions); this.root.push(drawing); diff --git a/src/file/paragraph/run/run.spec.ts b/src/file/paragraph/run/run.spec.ts index 37aadfb8d8..b5febf199e 100644 --- a/src/file/paragraph/run/run.spec.ts +++ b/src/file/paragraph/run/run.spec.ts @@ -132,6 +132,30 @@ describe("Run", () => { }); }); + describe("#subScript()", () => { + it("it should add subScript to the properties", () => { + const run = new Run({ + subScript: true, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [{ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }] }], + }); + }); + }); + + describe("#superScript()", () => { + it("it should add superScript to the properties", () => { + const run = new Run({ + superScript: true, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [{ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }] }], + }); + }); + }); + describe("#highlight()", () => { it("it should add highlight to the properties", () => { const run = new Run({ From 47533cf4e29f881c9327232d1b40bcf9ea0ac39c Mon Sep 17 00:00:00 2001 From: Dolan Date: Tue, 24 Dec 2019 03:39:55 +0000 Subject: [PATCH 6/9] Bookmark tests --- src/file/paragraph/paragraph.spec.ts | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index b4a797a8c2..cdbe99c7d8 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -1,9 +1,12 @@ import { assert, expect } from "chai"; +import * as shortid from "shortid"; +import { stub } from "sinon"; import { Formatter } from "export/formatter"; import { EMPTY_OBJECT } from "file/xml-components"; import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; +import { Bookmark } from "./links"; import { Paragraph } from "./paragraph"; describe("Paragraph", () => { @@ -638,6 +641,49 @@ describe("Paragraph", () => { }); }); + it("it should add bookmark", () => { + stub(shortid, "generate").callsFake(() => { + return "test-unique-id"; + }); + const paragraph = new Paragraph({ + children: [new Bookmark("test-id", "test")], + }); + const tree = new Formatter().format(paragraph); + expect(tree).to.deep.equal({ + "w:p": [ + { + "w:bookmarkStart": { + _attr: { + "w:id": "test-unique-id", + "w:name": "test-id", + }, + }, + }, + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test", + ], + }, + ], + }, + { + "w:bookmarkEnd": { + _attr: { + "w:id": "test-unique-id", + }, + }, + }, + ], + }); + }); + describe("#style", () => { it("should set the paragraph style to the given styleId", () => { const paragraph = new Paragraph({ From 3591e116373994f64d257eb1320c2326eac5dddb Mon Sep 17 00:00:00 2001 From: Dolan Date: Tue, 24 Dec 2019 16:31:35 +0000 Subject: [PATCH 7/9] Add hyperlink cache test --- src/file/file.spec.ts | 8 ++++++++ src/file/file.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index e34feea1b6..c9ec7b3a3e 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -148,6 +148,14 @@ describe("File", () => { }); }); + describe("#HyperlinkCache", () => { + it("should initially have empty hyperlink cache", () => { + const file = new File(); + + expect(file.HyperlinkCache).to.deep.equal({}); + }); + }); + describe("#createFootnote", () => { it("should create footnote", () => { const wrapper = new File({ diff --git a/src/file/file.ts b/src/file/file.ts index e549b4b624..583d4ace3a 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -61,7 +61,7 @@ export class File { private readonly contentTypes: ContentTypes; private readonly appProperties: AppProperties; private readonly styles: Styles; - private readonly hyperlinkCache: { readonly [key: string]: Hyperlink }; + private readonly hyperlinkCache: { readonly [key: string]: Hyperlink } = {}; constructor( options: IPropertiesOptions = { From 1a9e71bfa1a0f6e4fbe8e9f3760640977a2ea425 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Fri, 27 Dec 2019 01:21:19 +0000 Subject: [PATCH 8/9] Add children and section tests for File --- src/file/file.spec.ts | 100 +++++++++++++++++++++++++++++++++++++++++- src/file/file.ts | 2 +- 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index c9ec7b3a3e..0ad7474644 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -5,7 +5,7 @@ import { Formatter } from "export/formatter"; import { File } from "./file"; import { Footer, Header } from "./header"; -import { Paragraph } from "./paragraph"; +import { HyperlinkRef, Paragraph } from "./paragraph"; import { Table, TableCell, TableRow } from "./table"; import { TableOfContents } from "./table-of-contents"; @@ -89,6 +89,94 @@ describe("File", () => { expect(tree["w:body"][1]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even"); }); + + it("should add child", () => { + const doc = new File(undefined, undefined, [ + { + children: [new Paragraph("test")], + }, + ]); + + const tree = new Formatter().format(doc.Document.Body); + + expect(tree).to.deep.equal({ + "w:body": [ + { + "w:p": {}, + }, + { + "w:p": [ + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test", + ], + }, + ], + }, + ], + }, + { + "w:sectPr": [ + { + "w:pgSz": { + _attr: { + "w:h": 16838, + "w:orient": "portrait", + "w:w": 11906, + }, + }, + }, + { + "w:pgMar": { + _attr: { + "w:bottom": 1440, + "w:footer": 708, + "w:gutter": 0, + "w:header": 708, + "w:left": 1440, + "w:mirrorMargins": false, + "w:right": 1440, + "w:top": 1440, + }, + }, + }, + { + "w:cols": { + _attr: { + "w:num": 1, + "w:space": 708, + }, + }, + }, + { + "w:docGrid": { + _attr: { + "w:linePitch": 360, + }, + }, + }, + ], + }, + ], + }); + }); + + it("should add hyperlink child", () => { + const doc = new File(undefined, undefined, [ + { + children: [new HyperlinkRef("test")], + }, + ]); + + expect(doc.HyperlinkCache).to.deep.equal({}); + }); }); describe("#addSection", () => { @@ -102,6 +190,16 @@ describe("File", () => { expect(spy.called).to.equal(true); }); + it("should add hyperlink child", () => { + const doc = new File(); + + doc.addSection({ + children: [new HyperlinkRef("test")], + }); + + expect(doc.HyperlinkCache).to.deep.equal({}); + }); + it("should call the underlying document's add when adding a Table", () => { const file = new File(); const spy = sinon.spy(file.Document, "add"); diff --git a/src/file/file.ts b/src/file/file.ts index 583d4ace3a..59af9fa7f7 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -41,7 +41,7 @@ export interface ISectionOptions { readonly size?: IPageSizeAttributes; readonly margins?: IPageMarginAttributes; readonly properties?: SectionPropertiesOptions; - readonly children: Array; + readonly children: Array; } export class File { From f6bcaef5b58a8007caa1d64e7dab2c5fe7e4f3c5 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Fri, 27 Dec 2019 01:29:15 +0000 Subject: [PATCH 9/9] Fix test --- src/file/file.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index 0ad7474644..398a1bb72b 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -101,9 +101,6 @@ describe("File", () => { expect(tree).to.deep.equal({ "w:body": [ - { - "w:p": {}, - }, { "w:p": [ {