Skip to content

Commit

Permalink
Merge pull request #21 from photogabble/v1.1.0
Browse files Browse the repository at this point in the history
Release 1.1.0
  • Loading branch information
carbontwelve authored Dec 2, 2024
2 parents 654e247 + adcbeb5 commit 4253c85
Show file tree
Hide file tree
Showing 86 changed files with 3,758 additions and 4,376 deletions.
19 changes: 7 additions & 12 deletions .github/workflows/node-test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
Expand All @@ -11,19 +8,17 @@ on:

jobs:
build:

runs-on: ubuntu-latest

runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node: [18.x, 20.x, 21.x]
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
node-version: ${{ matrix.node }}
cache: 'npm'
- run: npm ci
- run: npm test
12 changes: 6 additions & 6 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
node-version: 20
- run: npm ci
- run: npm test

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules/
npm-debug.log
coverage/
.idea
index.cjs
6 changes: 6 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.idea
.gitignore
.editorconfig
.github/workflows/
tests/
coverage/
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Make the stub post destination configurable (#59)
- Fix compatibility with Eleventy v3 (#60)
- Check that Wikilinks do not contain new lines (#55)
- Fix crashing bug when embedded file changed while in `--watch` mode (#56)
- Wikilinks should not contain new lines (#54)
- On resolving fn lookup failure, only throw error if page not found (#52)
- Clear internal state before each 11ty build (#51)
- Make dead link report configurable (#49)
- Remove internal dependency upon slugify (#48)
- Add support for custom rendering functions (#47)
- Add support for referencing files by path (#44)
- Bugfix use alias as link text if it's the lookup source (#42)
- Bugfix HTML encode link titles (#40)
- Bugfix broken dead-links lookup due to typo (#38)
- Bugfix do not render embeds if the page linked doesn't exist (#35)
- Bugfix do not parse links in pages excluded from collections (#30)
- Bugfix do not exclude root index page
- Bugfix pass 11ty page object to embed compiler function (#29)
- Add inclusion of html internal links to backlink computation (#22)
- Add detailed bad link report (#26)

## [1.0.6]

- Bugfix ensuring aliases value is array when treated as one (#17)
Expand Down Expand Up @@ -46,3 +67,4 @@ First release
[1.0.4]: https://github.com/photogabble/eleventy-plugin-font-subsetting/releases/tag/v1.0.4
[1.0.5]: https://github.com/photogabble/eleventy-plugin-font-subsetting/releases/tag/v1.0.5
[1.0.5]: https://github.com/photogabble/eleventy-plugin-font-subsetting/releases/tag/v1.0.6
[Unreleased]: https://github.com/photogabble/eleventy-plugin-interlinker/tree/v1.1.0
115 changes: 107 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

I use [Obsidian.md](https://obsidian.md/) to draft my posts before they are published on PhotoGabble. One feature of #Obsidian that I love is interlinking between notes and being able to see the connectivity graph of each note.

In January 2023 I wrote about how I [added Wiki Links support to Eleventy.js](https://www.photogabble.co.uk/noteworthy/adding-wiki-links-to-11ty/) and in doing so this plugin was borne. It has since been updated to include support for Obsidians [embedding files](https://help.obsidian.md/Linking+notes+and+files/Embedding+files).
In January 2023 I wrote about how I [added Wiki Links support to Eleventy.js](https://www.photogabble.co.uk/noteworthy/adding-wiki-links-to-11ty/) and in doing so this plugin was borne. It has since been updated to include support for Obsidian's [embedding files](https://help.obsidian.md/Linking+notes+and+files/Embedding+files).

## Install

Expand Down Expand Up @@ -40,9 +40,12 @@ type EleventyPluginInterlinkOptions = {
// that returns [UNABLE TO LOCATE EMBED].
unableToLocateEmbedFn?: ErrorRenderFn,

// slugifyFn is used to slugify strings. If a function
// isn't set then the default 11ty slugify filter is used.
slugifyFn?: SlugifyFn
// deadLinkReport is the desired output format of the dead link report, by default its set to 'console'
deadLinkReport?: 'console' | 'json' | 'none',

// resolvingFns contains functions used for resolving a wikilinks output.
// see the Custom Resolving Functions section below
resolvingFns?: Map<string, (link: WikilinkMeta, currentPage: any, interlinker: Interlinker) => Promise<string>>
}
```
Expand All @@ -62,16 +65,42 @@ 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 to build each pages inbound and outbound internal 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/).

> **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**.
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).
### Linking to fragment identifiers

If you're using a plugin such as [markdown-it-anchor](https://www.npmjs.com/package/markdown-it-anchor) to add _anchor links_ to your headings, or have otherwise added them yourself. You can link to these in your pages by adding a `#` symbol to your page reference.

For example, `[[Three laws of motion#Second law]]`.

In cases where you have the `#` in the title of a page you're linking to you can escape using `/` foe example, `[[Programming in /#C, an introduction]]`.

### Linking to files by path

You can link to pages by their project path, or a path relative to the linking page, for example: `[[/blog/post-1234.md]]` would link to the page found at `/blog/post-1234` relative to the project root path, While `[[../../something.md]]` would link to a page two directories up.

### Aliases

Aliases provide you a way of referencing a file using different names, use the `aliases` property in your font matter to list one or more aliases that can be used to reference the file from a Wiki Link. For example, you might add _AI_ as an alias of a file titled _Artificial Intelligence_ which would then be linkable via `[[AI]]`.

These can be defined as either an array as shown below or a single alias via `aliaes: AI`.

```yaml
---
title: Artificial Intelligence
aliases:
- AI
---
```

Aliases should be unique identifiers, this plugin will halt the build with an error if it finds two pages sharing the same alias.

### Linking to Pagination generated pages

A common use of pagination in 11ty is [pagination of an object](https://www.11ty.dev/docs/pagination/#paging-an-object) or data file, by default these generated pages aren't included in the all pages collection and therefore are invisible to this plugin unless you set `addAllPagesToCollections: true`.
Expand All @@ -92,6 +121,29 @@ eleventyComputed:
---
```

### Custom Resolving Functions

Custom resolving functions can be considered pluggable extensions to the wikilink lookup and rendering logic and can be invoked by usage of a `:` character in a wikilink prefixed by the functions name, for example: `[[issue:19]]`.

These functions are added to the interlinker via its `resolvingFns` configuration options, for example:

```javascript
const config = {
resolvingFns: new Map([
['howdy', (link, currentPage) => `Hello ${link.name}!`],
['issue', (link, currentPage) => `<a href="${currentPage.data.github}/issues/${link.name}">#${link.name}</a>`],
]),
};
```

When invoked the resolving function will be passed three arguments, the parsed Wikilink object (see _Wikilink Data Structure_ section below.) The linking page object from 11ty and the interlinker class instance.

The plugin has three internal resolving functions which are defined only if not already via the plugin config:

- `default`, this is the default resolving function and converts the Wikilink Data Structure directly into an HTML link
- `default-embed`, this is the default embed resolving function
- `404-embed`, this is invoked when the embed template is not found. This currently invokes the `unableToLocateEmbedFn` however, in a future version it will replace that config option entirely

### Embedding

Embedding files allows you to reuse content across your website while tracking what pages have used it.
Expand Down Expand Up @@ -144,10 +196,57 @@ You can then display this information in any way you would like, I use the below
{% endif %}
```

### Dead link Report

The default behaviour of this plugin is to report to the console every broken Wikilink and internal link. This behaviour is configurable via the `deadLinkReport` config option. This option accepts three values: `none`, `console` and `json` with `console` being the default.

Setting the value to `none` will disable the dead link report while setting it to `json` will silence console output instead writing to `.dead-links.json` within the project root folder.

### Page lookup logic

This plugin will attempt to identify the page being linked using the following steps in order:

1. if is path link, return `filePathStem` match state
2. match file url to link href
3. match file title to link identifier (name)
4. match file slug to link identifier (name)
5. match file based upon alias

### 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.

## Wikilink Data Structure

```typescript
type WikilinkMeta = {
title: string | null
name: string
anchor: string | null
link: string
slug: string
isEmbed: boolean
isPath: boolean

// If linked page has been found in the all collection exists will be
// true and page will be the 11ty page object.
exists: boolean
page?: any

// name of the resolving fn, if set it must exist
resolvingFnName?: string
// the resulting HTML of the resolving function
content?: string

// href and path are loaded from the linked page
href?: string
path?: string
}
```
## 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
- Doesn't identify regular internal links e.g `[Link](/some/file.md)`
- This plugin doesn't implement all [Obsidian's 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
- Only supports embedding one note inside another, no other Obsidian file embedding functionality is currently supported by this plugin
## Roadmap
Expand Down
84 changes: 75 additions & 9 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
interface DeadLinks {
gravestones: Map<string, Array<string>>
fileSrc: string

setFileSrc(fileSrc: string): void

add(link: string): void

report(): void
}

interface Parser {
parseSingle(link: string, pageDirectory: PageDirectoryService, filePathStem: undefined | string): WikilinkMeta

parseMultiple(link: string, pageDirectory: PageDirectoryService, filePathStem: undefined | string): Array<WikilinkMeta>

find(document: string, pageDirectory: PageDirectoryService, filePathStem: undefined | string): Array<WikilinkMeta>
}

interface Interlinker {
opts: EleventyPluginInterlinkOptions
deadLinks: DeadLinks
templateConfig: any
extensionMap: any
rm: any,
wikilinkParser: Parser & { wikiLinkRegExp: string }
HTMLLinkParser: Parser & { internalLinkRegex: string }

compute(data: any): Promise<Array<any>>
}

type EleventyPluginInterlinkOptions = {
// defaultLayout is the optional default layout you would like to use for wrapping your embeds.
defaultLayout?: string,
Expand All @@ -10,6 +41,11 @@ type EleventyPluginInterlinkOptions = {
// that embed. This will always default to `embedLayout`.
layoutKey?: string,

// stubUrl is the href you want wikilinks to link to if their linking page is not found. By default this is set
// to /stubs. Passing false will disable stub url output resulting in the wikilink being displayed directly
// in the html without transformation into a html link.
stubUrl?: string|false,

// layoutTemplateLangKey informs the template renderer which engines to use for rendering an embed's layout. This
// defaults to your 11ty projects default, typically: liquid,md
layoutTemplateLangKey?: string,
Expand All @@ -18,24 +54,54 @@ type EleventyPluginInterlinkOptions = {
// slug that you are using. This defaults to a function that returns [UNABLE TO LOCATE EMBED].
unableToLocateEmbedFn?: ErrorRenderFn,

// slugifyFn is used to slugify strings. If a function isn't set then the default 11ty slugify filter is used.
slugifyFn?: SlugifyFn
// deadLinkReport is the desired output format of the dead link report, by default its set to 'console'
deadLinkReport?: 'console' | 'json' | 'none',

// resolvingFns is a list of resolving functions. These are invoked by a wikilink containing a `:` character
// prefixed by the fn name. The page in this case is the linking page.
resolvingFns?: Map<string, (link: WikilinkMeta, currentPage: any, interlinker: Interlinker) => Promise<string>>,
}

interface ErrorRenderFn {
(slug: string): string;
}

interface SlugifyFn {
(input: string): string;
// Data structure for internal links identified by HTMLLinkParser.
// This is a subset of WikilinkMeta.
type LinkMeta = {
href: string
isEmbed: false
}

// Data structure for wikilinks identified by WikiLinkParser.
type WikilinkMeta = {
title: string|null,
name: string,
link: string,
slug: string,
title: string | null
name: string
anchor: string | null
link: string
isEmbed: boolean
isPath: boolean

// If linked page has been found in the all collection exists will be
// true and page will be the 11ty page object.
exists: boolean
page?: any

// name of the resolving fn, if set it must exist
resolvingFnName?: string
// the resulting HTML of the resolving function
content?: string

// href and path are loaded from the linked page, if the href is
// false then it disables the transformation of wikilink into html link.
href?: string|false
path?: string
}

interface PageDirectoryService {
findByLink(link: WikilinkMeta | LinkMeta): { page: any, found: boolean, foundByAlias: boolean };

findByFile(file: any): any;
}

export {EleventyPluginInterlinkOptions, SlugifyFn, WikilinkMeta};
export {EleventyPluginInterlinkOptions, WikilinkMeta, LinkMeta, PageDirectoryService};
Loading

0 comments on commit 4253c85

Please sign in to comment.