Skip to content

Commit

Permalink
fix(links): Fix relative link resolution (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackpope committed Oct 27, 2019
1 parent fa38eec commit 233345c
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 21 deletions.
81 changes: 65 additions & 16 deletions spec/link_utils_spec.js
Expand Up @@ -29,7 +29,41 @@ describe('linkUtils.getAbsolute', () => {
});
});

xdescribe('linkUtils.resolveLinks', () => {
describe('linkUtils.getRelative', () => {
const files = [
{
actualUrl: 'https://www.github.com/user/repo/blob/master/docs/doc.md',
name: 'doc.md',
path: 'docs/doc.md',
relativePath: './doc.md',
repo: 'repo'
},
{
actualUrl: 'https://www.github.com/user/repo/blob/master/docs/subdir/doc.md',
name: 'doc.md',
path: 'docs/subdir/doc.md',
relativePath: './subdir/doc.md',
repo: 'repo'
},
{
actualUrl: 'https://www.github.com/user/repo2/blob/master/docs/sub/doc.md',
name: 'doc.md',
path: 'docs/sub/doc.md',
relativePath: './sub/docs.md',
repo: 'repo2'
}
];

it('returns a relative path from one absolute location to another within the same repo', () => {
expect(linkUtils.getRelative(files[1], files[0])).toEqual('../doc.md');
});

it('returns a relative path from one absolute location to another within another known repo', () => {
expect(linkUtils.getRelative(files[2], files[0])).toEqual('../../repo/doc.md');
});
});

describe('linkUtils.resolveLinks', () => {
describe('given markdown content', () => {
const unkownRelativeMarkdown = `
# headline
Expand All @@ -56,15 +90,32 @@ xdescribe('linkUtils.resolveLinks', () => {
const knownLocations = [
{
actualUrl: 'https://www.github.com/user/repo/blob/master/docs/doc.md',
path: 'docs/doc.md'
name: 'doc.md',
path: 'docs/doc.md',
relativePath: './doc.md',
repo: 'repo'
},
{
actualUrl: 'https://www.github.com/user/repo/blob/master/docs/doc.md',
name: 'docs/doc.md',
path: 'docs/doc.md',
relativePath: './doc.md',
repo: 'repo'
},
{
actualUrl: 'https://www.github.com/user/repo2/blob/master/docs/sub/doc.md',
name: 'doc.md',
path: 'docs/sub/doc.md',
relativePath: './sub/docs.md',
repo: 'repo2'
}
];

it('updates unknown relative links to be exact', () => {
const output = linkUtils.resolveLinks(
unkownRelativeMarkdown,
knownLocations,
'https://www.github.com/user/repo/blob/master/docs/'
knownLocations[0]
);
expect(output).toContain(
'Some content with an unknown [link](https://www.github.com/user/repo/blob/master/images/thing.png)'
Expand All @@ -75,7 +126,7 @@ xdescribe('linkUtils.resolveLinks', () => {
const output = linkUtils.resolveLinks(
knownRelativeMarkdown,
knownLocations,
'https://www.github.com/user/repo/blob/master/'
knownLocations[1]
);
expect(output).toContain('Some content with a known [link](./doc.md)');
});
Expand All @@ -84,26 +135,24 @@ xdescribe('linkUtils.resolveLinks', () => {
const output = linkUtils.resolveLinks(
knownRelativeMarkdownInCurrentDir,
knownLocations,
'https://www.github.com/user/repo/blob/master/docs/'
knownLocations[0]
);
expect(output).toContain('a known [link](./doc.md)');
});

it('updates known exact links to be relative', () => {
const output = linkUtils.resolveLinks(
absoluteMarkdown,
knownLocations,
'https://www.github.com/user/repo/blob/master/docs/'
);
it('updates known exact links to be relative within the same repo', () => {
const output = linkUtils.resolveLinks(absoluteMarkdown, knownLocations, knownLocations[0]);
expect(output).toContain('a [known doc](./doc.md)');
});

it('updates known exact links to be relative from another repo', () => {
// Go back out to root, then back in on another project
const output = linkUtils.resolveLinks(absoluteMarkdown, knownLocations, knownLocations[2]);
expect(output).toContain('a [known doc](../../repo/doc.md)');
});

it('does not update local header references', () => {
const output = linkUtils.resolveLinks(
localMarkdown,
knownLocations,
'https://www.github.com/user/repo/blob/master/docs/'
);
const output = linkUtils.resolveLinks(localMarkdown, knownLocations, knownLocations[0]);
expect(output).toEqual(localMarkdown);
});
});
Expand Down
33 changes: 30 additions & 3 deletions src/link_utils.js
Expand Up @@ -30,7 +30,29 @@ const getAbsolute = (baseUrl, path) => {
return stack.join('/');
};

const resolveLinks = (markdownContent, knownFiles, absoluteLocation) => {
const getRelative = (fromLink, toLink) => {
let fromLinkRelative = '';
const removeFirstTwoChars = toLink.relativePath.slice(0, 2) === './';
let toLinkRelative = removeFirstTwoChars
? toLink.relativePath.slice(2, toLink.relativePath.length)
: toLink.relativePath;

const fromDirCount = fromLink.relativePath.split('/').length - 2;

for (let index = 0; index < fromDirCount; index++) {
fromLinkRelative += '../';
}

if (fromLink.repo !== toLink.repo) {
toLinkRelative = `../${toLink.repo}/${toLinkRelative}`;
}

const result = (fromLinkRelative || './') + toLinkRelative;

return result;
};

const resolveLinks = (markdownContent, knownFiles, currentFile) => {
const markdownLinkMatcher = /\[([^[\]]+)\]\(([^)]+)/gm;
let markdownContentCopy = markdownContent;
let match;
Expand All @@ -39,11 +61,15 @@ const resolveLinks = (markdownContent, knownFiles, absoluteLocation) => {
const linkIsLocalHeader = match[2][0] === '#';
if (linkIsLocalHeader) return markdownContentCopy;

const absoluteUrl = getAbsolute(absoluteLocation, match[2]);
const absLocation = currentFile.actualUrl.replace(currentFile.name, '');
const absoluteUrl = getAbsolute(absLocation, match[2]);
const knownFile = knownFiles.find(fileData => fileData.actualUrl === absoluteUrl);

if (knownFile) {
markdownContentCopy = markdownContentCopy.replace(match[2], `${knownFile.relativePath}`);
markdownContentCopy = markdownContentCopy.replace(
match[2],
getRelative(currentFile, knownFile)
);
} else {
markdownContentCopy = markdownContentCopy.replace(match[2], absoluteUrl);
}
Expand All @@ -54,5 +80,6 @@ const resolveLinks = (markdownContent, knownFiles, absoluteLocation) => {

module.exports = {
getAbsolute,
getRelative,
resolveLinks
};
3 changes: 1 addition & 2 deletions src/resolve_links.js
Expand Up @@ -20,8 +20,7 @@ const resolveLinks = fileList => {
return reject(readError);
}

const absLocation = file.actualUrl.replace(file.name, '');
const resolvedData = linkUtils.resolveLinks(data, fileList, absLocation);
const resolvedData = linkUtils.resolveLinks(data, fileList, file);

fs.writeFile(fileUtils.findLocalFilePath(file), resolvedData, writeError => {
if (writeError) {
Expand Down

0 comments on commit 233345c

Please sign in to comment.