diff --git a/README.md b/README.md index 623d958..fff4b96 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,11 @@ module.exports = (eleventyConfig) => { ### Internal Links / Wikilinks -This plugin will now parse all Wiki Links formatted for example, `[[Eleventy.js Interlink Plugin]]` appears as [Eleventy.js Interlink Plugin](https://photogabble.co.uk/projects/eleventyjs-interlink-plugin/). +This plugin will parse both Wikilinks and internal anchor links. The Wikilink format is a **page reference** wrapped in double square brackets, for example: `[[Eleventy.js Interlink Plugin]]` will appear as [Eleventy.js Interlink Plugin](https://photogabble.co.uk/projects/eleventyjs-interlink-plugin/). Using the vertical bar (`|`) you can change the text used to display a link. This can be useful when you want to work a link into a sentence without using the title of the file, for example: `[[Eleventy.js Interlink Plugin|custom display text]]` appears as [custom display text](https://www.photogabble.co.uk/projects/eleventyjs-interlink-plugin/). -> NOTE: By default this plugin will use the `title` front-matter attribute of your pages or one of the aliases (as detailed below). +> **NOTE**: By default this plugin will use the `title` front-matter attribute of your pages or one of the aliases (as detailed below) as the **page reference**. ### Aliases @@ -144,6 +144,10 @@ You can then display this information in any way you would like, I use the below {% endif %} ``` +### Pages Excluded from Collections + +Due to how this plugin obtains a pages template content, all pages with `eleventyExcludeFromCollections:true` set will **NOT** be parsed by the interlinker. + ## Known Caveats - This plugin doesn't implement all [Obsidians wikilink support](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for example linking to a block in a note and linking to a heading in a note is not currently supported by this plugin diff --git a/src/interlinker.js b/src/interlinker.js index 965a3c6..fc4dc06 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -85,6 +85,8 @@ module.exports = class Interlinker { const dependencies = [data.title, data.page, data.collections.all]; if (dependencies[0] === undefined || !dependencies[1].inputPath || dependencies[2].length === 0) return []; + this.deadLinks.setFileSrc(data.page.inputPath); + const {slugifyFn} = this.opts; const compilePromises = []; @@ -98,8 +100,7 @@ module.exports = class Interlinker { const currentSlug = slugifyFn(data.title); let currentSlugs = new Set([currentSlug, data.page.fileSlug]); const currentPage = pageDirectory.findByFile(data); - - this.deadLinks.setFileSrc(currentPage.inputPath); + if (!currentPage) return []; // Populate our link map for use later in replacing WikiLinks with page permalinks. // Pages can list aliases in their front matter, if those exist we should map them diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 80e1a55..0d3bcd4 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,15 +1,13 @@ const Eleventy = require("@11ty/eleventy"); const path = require('node:path'); -const {normalize} = require('./helpers'); -const sinon = require('sinon'); +const {normalize, consoleMockMessages, findResultByUrl, fixturePath} = require('./helpers'); const test = require("ava"); +const sinon = require("sinon"); -function findResultByUrl(results, url) { - const [result] = results.filter(result => result.url === url); - return result; -} - -const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); +// NOTE: Tests using sinon to mock console.warn need to be run with +// `test.serial` so that they don't run at the same time as one another to +// avoid the "Attempted to wrap warn which is already wrapped" error. +// @see https://stackoverflow.com/a/37900956/1225977 test("Sample page (wikilinks and regular links)", async t => { let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { @@ -31,25 +29,15 @@ test("Sample page (wikilinks and regular links)", async t => { ); }); -test("Broken page (wikilinks and regular links)", async t => { - const messages = []; - const consoleStub = sinon.stub(console, 'warn').callsFake(msg => messages.push()); +test.serial("Broken page (wikilinks and regular links)", async t => { + const mock = sinon.stub(console, 'warn'); let elev = new Eleventy(fixturePath('website-with-broken-links'), fixturePath('website-with-broken-links/_site'), { configPath: fixturePath('website-with-broken-links/eleventy.config.js'), }); let results = await elev.toJSON(); - - let logLines = []; - for (let i = 0; i < consoleStub.callCount; i++) { - const line = normalize(consoleStub.getCall(i).args.join(' ')) - // Sometimes 11ty will output benchmark info, failing the test randomly. - if (line.includes('[11ty]')) continue; - logLines.push(line); - } - - consoleStub.restore(); + mock.restore(); // Markdown will have the link href set to /stubs t.is( @@ -63,7 +51,7 @@ test("Broken page (wikilinks and regular links)", async t => { `<div>This is to show that we can identify <a href="/broken">broken <em>internal</em> links</a>.</div>` ); - t.is(logLines.length, 4, 'console.warn should be called three times'); + t.is(consoleMockMessages(mock).length, 4, 'console.warn should be called four times'); }); test("Sample page (markdown with embed)", async t => { @@ -85,3 +73,28 @@ test("Sample page (markdown with embed)", async t => { `<div><p><p>Hello world.</p></p></div><div></div>` ); }); + +test.serial("Sample page (eleventyExcludeFromCollections set true)", async t => { + const mock = sinon.stub(console, 'warn'); + + let elev = new Eleventy(fixturePath('sample-with-excluded-file'), fixturePath('sample-with-excluded-file/_site'), { + configPath: fixturePath('sample-with-excluded-file/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + mock.restore(); + t.is(consoleMockMessages(mock).length, 2, 'console.warn should be called twice'); + + // Embedded page is aware of its embedding + t.is( + normalize(findResultByUrl(results, '/about/').content), + `<div><p>This wikilink to <a href="/stubs">something</a> will not parse because the destination has eleventyExcludeFromCollections set true.</p></div><div></div>` + ); + + // Embed shows + t.is( + normalize(findResultByUrl(results, '/').content), + `<div><p>Hello World, no links, wiki or otherwise will be parsed by the interlinker due to being excluded from collections.</p></div><div></div>` + ); +}); diff --git a/tests/fixtures/sample-with-excluded-file/_layouts/default.liquid b/tests/fixtures/sample-with-excluded-file/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/_layouts/default.liquid @@ -0,0 +1,2 @@ +<div>{{ content }}</div> +<div>{%- for link in backlinks %}<a href="{{ link.url }}">{{ link.title }}</a>{%- endfor %}</div> diff --git a/tests/fixtures/sample-with-excluded-file/about.md b/tests/fixtures/sample-with-excluded-file/about.md new file mode 100644 index 0000000..54144ba --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/about.md @@ -0,0 +1,6 @@ +--- +title: About +layout: default.liquid +--- + +This wikilink to [[something]] will not parse because the destination has eleventyExcludeFromCollections set true. diff --git a/tests/fixtures/sample-with-excluded-file/eleventy.config.js b/tests/fixtures/sample-with-excluded-file/eleventy.config.js new file mode 100644 index 0000000..9b10b5c --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/sample-with-excluded-file/index.md b/tests/fixtures/sample-with-excluded-file/index.md new file mode 100644 index 0000000..182dac6 --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/index.md @@ -0,0 +1,7 @@ +--- +title: Something +layout: default.liquid +eleventyExcludeFromCollections: true +--- + +Hello World, no links, wiki or otherwise will be parsed by the interlinker due to being excluded from collections. diff --git a/tests/helpers.js b/tests/helpers.js index ab75801..2a9f11a 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -1,7 +1,30 @@ +const path = require("node:path"); + function normalize(str) { return str.trim().replace(/\r?\n|\r/g, ''); } +function consoleMockMessages(mock) { + let logLines = []; + for (let i = 0; i < mock.callCount; i++) { + const line = normalize(mock.getCall(i).args.join(' ')) + // Sometimes 11ty will output benchmark info, failing the test randomly. + if (line.includes('[11ty]')) continue; + logLines.push(line); + } + return logLines; +} + +function findResultByUrl(results, url) { + const [result] = results.filter(result => result.url === url); + return result; +} + +const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); + module.exports = { normalize, + consoleMockMessages, + findResultByUrl, + fixturePath, }