#773 Better hyperlink and bookmark syntax
Allow for images to be hyperlinked as well
This commit is contained in:
@ -2,14 +2,20 @@ import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { Hyperlink } from "./";
|
||||
import { HyperlinkRef } from "./hyperlink";
|
||||
import { TextRun } from "../run";
|
||||
import { ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./hyperlink";
|
||||
|
||||
describe("Hyperlink", () => {
|
||||
let hyperlink: Hyperlink;
|
||||
describe("ConcreteHyperlink", () => {
|
||||
let hyperlink: ConcreteHyperlink;
|
||||
|
||||
beforeEach(() => {
|
||||
hyperlink = new Hyperlink("https://example.com", "superid");
|
||||
hyperlink = new ConcreteHyperlink(
|
||||
new TextRun({
|
||||
text: "https://example.com",
|
||||
style: "Hyperlink",
|
||||
}),
|
||||
"superid",
|
||||
);
|
||||
});
|
||||
|
||||
describe("#constructor()", () => {
|
||||
@ -35,7 +41,14 @@ describe("Hyperlink", () => {
|
||||
|
||||
describe("with optional anchor parameter", () => {
|
||||
beforeEach(() => {
|
||||
hyperlink = new Hyperlink("Anchor Text", "superid2", "anchor");
|
||||
hyperlink = new ConcreteHyperlink(
|
||||
new TextRun({
|
||||
text: "Anchor Text",
|
||||
style: "Hyperlink",
|
||||
}),
|
||||
"superid2",
|
||||
"anchor",
|
||||
);
|
||||
});
|
||||
|
||||
it("should create an internal link with anchor tag", () => {
|
||||
@ -61,10 +74,53 @@ describe("Hyperlink", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("HyperlinkRef", () => {
|
||||
describe("ExternalHyperlink", () => {
|
||||
describe("#constructor()", () => {
|
||||
const hyperlinkRef = new HyperlinkRef("test-id");
|
||||
it("should create", () => {
|
||||
const externalHyperlink = new ExternalHyperlink({
|
||||
child: new TextRun("test"),
|
||||
link: "http://www.google.com",
|
||||
});
|
||||
|
||||
expect(hyperlinkRef.id).to.equal("test-id");
|
||||
expect(externalHyperlink.options.link).to.equal("http://www.google.com");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("InternalHyperlink", () => {
|
||||
describe("#constructor()", () => {
|
||||
it("should create", () => {
|
||||
const internalHyperlink = new InternalHyperlink({
|
||||
child: new TextRun("test"),
|
||||
anchor: "test-id",
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(internalHyperlink);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:hyperlink": [
|
||||
{
|
||||
_attr: {
|
||||
"w:anchor": "test-id",
|
||||
"w:history": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:t": [
|
||||
{
|
||||
_attr: {
|
||||
"xml:space": "preserve",
|
||||
},
|
||||
},
|
||||
"test",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,9 @@
|
||||
// http://officeopenxml.com/WPhyperlink.php
|
||||
import * as shortid from "shortid";
|
||||
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
import { TextRun } from "../run";
|
||||
|
||||
import { ParagraphChild } from "../paragraph";
|
||||
import { HyperlinkAttributes, IHyperlinkAttributesProperties } from "./hyperlink-attributes";
|
||||
|
||||
export enum HyperlinkType {
|
||||
@ -8,15 +11,10 @@ export enum HyperlinkType {
|
||||
EXTERNAL = "EXTERNAL",
|
||||
}
|
||||
|
||||
export class HyperlinkRef {
|
||||
constructor(public readonly id: string) {}
|
||||
}
|
||||
|
||||
export class Hyperlink extends XmlComponent {
|
||||
export class ConcreteHyperlink extends XmlComponent {
|
||||
public readonly linkId: string;
|
||||
private readonly textRun: TextRun;
|
||||
|
||||
constructor(text: string, relationshipId: string, anchor?: string) {
|
||||
constructor(child: ParagraphChild, relationshipId: string, anchor?: string) {
|
||||
super("w:hyperlink");
|
||||
|
||||
this.linkId = relationshipId;
|
||||
@ -29,14 +27,16 @@ export class Hyperlink extends XmlComponent {
|
||||
|
||||
const attributes = new HyperlinkAttributes(props);
|
||||
this.root.push(attributes);
|
||||
this.textRun = new TextRun({
|
||||
text: text,
|
||||
style: "Hyperlink",
|
||||
});
|
||||
this.root.push(this.textRun);
|
||||
}
|
||||
|
||||
public get TextRun(): TextRun {
|
||||
return this.textRun;
|
||||
this.root.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
export class InternalHyperlink extends ConcreteHyperlink {
|
||||
constructor(options: { readonly child: ParagraphChild; readonly anchor: string }) {
|
||||
super(options.child, shortid.generate().toLowerCase(), options.anchor);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExternalHyperlink {
|
||||
constructor(public readonly options: { readonly child: ParagraphChild; readonly link: string }) {}
|
||||
}
|
||||
|
@ -8,8 +8,9 @@ import { EMPTY_OBJECT } from "file/xml-components";
|
||||
import { File } from "../file";
|
||||
import { ShadingType } from "../table/shading";
|
||||
import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting";
|
||||
import { Bookmark, HyperlinkRef } from "./links";
|
||||
import { Bookmark, ExternalHyperlink } from "./links";
|
||||
import { Paragraph } from "./paragraph";
|
||||
import { TextRun } from "./run";
|
||||
|
||||
describe("Paragraph", () => {
|
||||
describe("#constructor()", () => {
|
||||
@ -763,7 +764,7 @@ describe("Paragraph", () => {
|
||||
});
|
||||
|
||||
describe("#shading", () => {
|
||||
it("should set paragraph outline level to the given value", () => {
|
||||
it("should set shading to the given value", () => {
|
||||
const paragraph = new Paragraph({
|
||||
shading: {
|
||||
type: ShadingType.REVERSE_DIAGONAL_STRIPE,
|
||||
@ -793,20 +794,49 @@ describe("Paragraph", () => {
|
||||
});
|
||||
|
||||
describe("#prepForXml", () => {
|
||||
it("should set paragraph outline level to the given value", () => {
|
||||
it("should set Internal Hyperlink", () => {
|
||||
const paragraph = new Paragraph({
|
||||
children: [new HyperlinkRef("myAnchorId")],
|
||||
children: [
|
||||
new ExternalHyperlink({
|
||||
child: new TextRun("test"),
|
||||
link: "http://www.google.com",
|
||||
}),
|
||||
],
|
||||
});
|
||||
const fileMock = ({
|
||||
HyperlinkCache: {
|
||||
myAnchorId: "test",
|
||||
DocumentRelationships: {
|
||||
createRelationship: () => ({}),
|
||||
},
|
||||
// tslint:disable-next-line: no-any
|
||||
} as any) as File;
|
||||
} as unknown) as File;
|
||||
paragraph.prepForXml(fileMock);
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": ["test"],
|
||||
"w:p": [
|
||||
{
|
||||
"w:hyperlink": [
|
||||
{
|
||||
_attr: {
|
||||
"r:id": "rIdtest-unique-id",
|
||||
"w:history": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:t": [
|
||||
{
|
||||
_attr: {
|
||||
"xml:space": "preserve",
|
||||
},
|
||||
},
|
||||
"test",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,30 +1,35 @@
|
||||
// http://officeopenxml.com/WPparagraph.php
|
||||
import * as shortid from "shortid";
|
||||
|
||||
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
|
||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { File } from "../file";
|
||||
import { TargetModeType } from "../relationships/relationship/relationship";
|
||||
import { DeletedTextRun, InsertedTextRun } from "../track-revision";
|
||||
import { PageBreak } from "./formatting/page-break";
|
||||
import { Bookmark, HyperlinkRef } from "./links";
|
||||
import { Bookmark, ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./links";
|
||||
import { Math } from "./math";
|
||||
import { IParagraphPropertiesOptions, ParagraphProperties } from "./properties";
|
||||
import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run";
|
||||
|
||||
export type ParagraphChild =
|
||||
| TextRun
|
||||
| PictureRun
|
||||
| SymbolRun
|
||||
| Bookmark
|
||||
| PageBreak
|
||||
| SequentialIdentifier
|
||||
| FootnoteReferenceRun
|
||||
| InternalHyperlink
|
||||
| ExternalHyperlink
|
||||
| InsertedTextRun
|
||||
| DeletedTextRun
|
||||
| Math;
|
||||
|
||||
export interface IParagraphOptions extends IParagraphPropertiesOptions {
|
||||
readonly text?: string;
|
||||
readonly children?: (
|
||||
| TextRun
|
||||
| PictureRun
|
||||
| SymbolRun
|
||||
| Bookmark
|
||||
| PageBreak
|
||||
| SequentialIdentifier
|
||||
| FootnoteReferenceRun
|
||||
| HyperlinkRef
|
||||
| InsertedTextRun
|
||||
| DeletedTextRun
|
||||
| Math
|
||||
)[];
|
||||
readonly children?: ParagraphChild[];
|
||||
}
|
||||
|
||||
export class Paragraph extends XmlComponent {
|
||||
@ -71,9 +76,16 @@ export class Paragraph extends XmlComponent {
|
||||
|
||||
public prepForXml(file: File): IXmlableObject | undefined {
|
||||
for (const element of this.root) {
|
||||
if (element instanceof HyperlinkRef) {
|
||||
if (element instanceof ExternalHyperlink) {
|
||||
const index = this.root.indexOf(element);
|
||||
this.root[index] = file.HyperlinkCache[element.id];
|
||||
const concreteHyperlink = new ConcreteHyperlink(element.options.child, shortid.generate().toLowerCase());
|
||||
file.DocumentRelationships.createRelationship(
|
||||
concreteHyperlink.linkId,
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||
element.options.link,
|
||||
TargetModeType.EXTERNAL,
|
||||
);
|
||||
this.root[index] = concreteHyperlink;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user