Skip to content

Commit

Permalink
feat(page-link-parser): add support for defining custom page link text
Browse files Browse the repository at this point in the history
Add support for defining custom page link text via pipe syntax similar to TypeDoc's type link tag

implement feature request #24
  • Loading branch information
mipatterson committed Nov 28, 2020
1 parent a4759a5 commit 30b7625
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 31 deletions.
23 changes: 20 additions & 3 deletions src/links/page-link-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,28 @@ export class PageLinkParser {
*/
private _handlePageLink = (match: string, leading: string, tagName: string, linkText: string): string => { // TODO: Figure out how to handle multiple hits or page sections
try {
let pageTitle: string;
let linkCaption: string;

// Sanitize link text
const sanitizedLinkText = linkText.replace(/\n+/, " ").trim();

// Parse provided text for page title and caption text
const pipeIndex = sanitizedLinkText.indexOf("|");
if (pipeIndex > -1) {
pageTitle = sanitizedLinkText.substr(0, pipeIndex).trim();
linkCaption = sanitizedLinkText.substr(pipeIndex + 1).trim();
} else {
// If no alternate caption text is provided, use page title
pageTitle = sanitizedLinkText;
linkCaption = sanitizedLinkText;
}

// Attempt to find page or group with provided title
const item = this._pages.getByTitle(linkText);
const item = this._pages.getByTitle(pageTitle);

if (!item) {
throw new Error(`Failed to find page or group with title "${linkText}"`);
throw new Error(`Failed to find page or group with title "${pageTitle}"`);
}

let url = item.url;
Expand All @@ -122,7 +139,7 @@ export class PageLinkParser {
const relativeUrl = this._getRelativeUrl(url, this._currentPageUrl);

// Replace the page link tag with a HTML hyperlink
return `<a href="${relativeUrl}">${linkText}</a>`;
return `<a href="${relativeUrl}">${linkCaption}</a>`;
} catch (e) {
if (this._options.failBuildOnInvalidPageLink) {
throw new Error(`Found invalid page link tag "${match}" on page "${this._currentPageTitle}": ${e.message}`);
Expand Down
77 changes: 49 additions & 28 deletions test/unit/links/page-link-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,63 @@ import { PluginOptions } from "../../../src/options/models";
import { PageDictionary, Page, PageGroup } from "../../../src/pages/models";
import { Logger } from "typedoc/dist/lib/utils";

interface ITestCase {
interface TestCase {
title: string;
input: string;
output: string;
invalidLinks: string[];
invalidLinks?: string[];
}

const testCases: ITestCase[] = [
const testCases: TestCase[] = [
{
title: "No page links",
input: "This is sample text.",
output: "This is sample text.",
invalidLinks: [],
},
{
title: "Basic @page tag",
input: "See {@page Something} for more information.",
output: `See <a href="/something/url.html">Something</a> for more information.`,
invalidLinks: [],
},
{
title: "Basic @pagelink tag",
input: "See {@pagelink Something} for more information.",
output: `See <a href="/something/url.html">Something</a> for more information.`,
invalidLinks: [],
},
{
title: "Multiple @page tags",
input: "See {@page Something} for more information about {@page Feature}.",
output: `See <a href="/something/url.html">Something</a> for more information about <a href="/feature/url.html">Feature</a>.`,
invalidLinks: [],
},
{
title: "Space in page title",
input: "See {@pagelink Something Here} for more information.",
output: `See <a href="/something/here/url.html">Something Here</a> for more information.`,
invalidLinks: [],
},
{
title: "Custom link text (no spaces)",
input: "See {@page Something Here|custom text} for more information.",
output: `See <a href="/something/here/url.html">custom text</a> for more information.`,
},
{
title: "Custom link text (leading and trailing spaces)",
input: "See {@page Something Here | custom text} for more information.",
output: `See <a href="/something/here/url.html">custom text</a> for more information.`,
},
{
title: "Custom link text (leading space)",
input: "See {@page Something Here |custom text} for more information.",
output: `See <a href="/something/here/url.html">custom text</a> for more information.`,
},
{
title: "Custom link text (trailing space)",
input: "See {@page Something Here| custom text} for more information.",
output: `See <a href="/something/here/url.html">custom text</a> for more information.`,
},
{
title: "Sanitizes newlines",
input: "See {@page \nSomething\nHere|\ncustom text\n} for more information.",
output: `See <a href="/something/here/url.html">custom text</a> for more information.`,
},
{
title: "Invalid page title",
Expand All @@ -59,7 +79,6 @@ const testCases: ITestCase[] = [
title: "Group @page tag",
input: "See {@page Group Title} for more information.",
output: `See <a href="url.html">Group Title</a> for more information.`,
invalidLinks: [],
},
{
title: "Empty group @page tag",
Expand All @@ -79,21 +98,6 @@ describe("PageLinkParser", () => {

let sut: PageLinkParser;

beforeEach(() => {
optionsMock = Mock.ofType<PluginOptions>();
pageDictionaryMock = Mock.ofType<PageDictionary>();
loggerMock = Mock.ofType<Logger>();

addPageToDictionary("Something", "/something/url.html");
addPageToDictionary("Something Here", "/something/here/url.html");
addPageToDictionary("Feature", "/feature/url.html");
addGroupToDictionary("Group Title", "url.html");
addGroupToDictionary("Empty Group");

sut = new PageLinkParser(optionsMock.object, pageDictionaryMock.object, loggerMock.object);
(sut as any)._getRelativeUrl = (absoluteUrl: string, relativeTo: string) => absoluteUrl;
});

function addPageToDictionary(title: string, url: string): void {
const pageMock = Mock.ofType<Page>();
pageMock.setup(m => m.title).returns(() => title);
Expand All @@ -119,14 +123,29 @@ describe("PageLinkParser", () => {
pageDictionaryMock.setup(m => m.getByTitle(title)).returns(() => group);
}

beforeEach(() => {
optionsMock = Mock.ofType<PluginOptions>();
pageDictionaryMock = Mock.ofType<PageDictionary>();
loggerMock = Mock.ofType<Logger>();

addPageToDictionary("Something", "/something/url.html");
addPageToDictionary("Something Here", "/something/here/url.html");
addPageToDictionary("Feature", "/feature/url.html");
addGroupToDictionary("Group Title", "url.html");
addGroupToDictionary("Empty Group");

sut = new PageLinkParser(optionsMock.object, pageDictionaryMock.object, loggerMock.object);
(sut as any)._getRelativeUrl = (absoluteUrl: string): string => absoluteUrl;
});

function enableFeatures(parsing: boolean, invalidLinkLogging: boolean, failOnInvalidLink: boolean): void {
optionsMock.reset();
optionsMock.setup(m => m.enablePageLinks).returns(() => parsing);
optionsMock.setup(m => m.listInvalidPageLinks).returns(() => invalidLinkLogging);
optionsMock.setup(m => m.failBuildOnInvalidPageLink).returns(() => failOnInvalidLink);
}

function prepareTestCase(testCase: ITestCase): MarkdownEvent {
function prepareTestCase(testCase: TestCase): MarkdownEvent {
// Prepare page event and pass to sut
const pageEventMock = Mock.ofType<PageEvent>();
pageEventMock.setup(m => m.model).returns(() => {
Expand Down Expand Up @@ -192,7 +211,7 @@ describe("PageLinkParser", () => {
}

// assert
if (testCase.invalidLinks.length > 0) {
if (testCase.invalidLinks && testCase.invalidLinks.length > 0) {
expect(error).toBeDefined();
expect(event.parsedText).toEqual(testCase.input);
} else {
Expand Down Expand Up @@ -226,8 +245,10 @@ describe("PageLinkParser", () => {
enableFeatures(true, true, false);
let expectedLog = INVALID_LINKS_HEADER_STRING;
for (const testCase of testCases) {
for (const link of testCase.invalidLinks) {
expectedLog += `\n In ${currentPageTitle}: ${link}`;
if (testCase.invalidLinks) {
for (const link of testCase.invalidLinks) {
expectedLog += `\n In ${currentPageTitle}: ${link}`;
}
}
const event = prepareTestCase(testCase);
sut.parsePageLinks(event);
Expand Down

0 comments on commit 30b7625

Please sign in to comment.