Skip to content

Commit

Permalink
Add permalink.linkInsideHeader
Browse files Browse the repository at this point in the history
  • Loading branch information
valeriangalliat committed Aug 26, 2021
1 parent 84d170a commit b757020
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 25 deletions.
97 changes: 78 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,11 @@ md.use(anchor, {
```

Here, `styleOfPermalink` is one of the available styles documented
below, and `permalinkOpts` is an options object. All renderers share a
common set of options:
below, and `permalinkOpts` is an options object.

<div id="common-options"></div>

All renderers share a common set of options:

| Name | Description | Default |
|---------------|---------------------------------------------------|------------------------------------|
Expand Down Expand Up @@ -202,6 +205,7 @@ text from the visual experience.
| `visuallyHiddenClass` | The class you use to make an element visually hidden. | `undefined`, required for `visually-hidden` style |
| `space` | Add a space between the assistive text and the permalink symbol. | `true` |
| `placement` | Placement of the permalink symbol relative to the assistive text, can be `before` or `after` the header. | `after` |
| | See [common options](#common-options). | |

```js
const anchor = require('markdown-it-anchor')
Expand Down Expand Up @@ -283,29 +287,79 @@ md.use(anchor, {

</details>

### ARIA hidden
### Link inside header

This is the closest one to the old default permalink, and is similar to
GitHub's way of rendering permalinks.
This is the equivalent of the default permalink in previous versions.
The reason it's not the first one in the list is because this method has
accessibility issues.

It's the same markup as before with the addition of `aria-hidden="true"`
to make that permalink explicitly inaccessible instead of having the
permalink and its symbol being read by screen readers as part of every
single headings (which was a pretty terrible experience).
If you use a symbol like just `#` without adding any markup around,
screen readers will read it as part of every heading (in the case of
`#`, it could be read "pound" or "number sign") meaning that if you
title is "my beautiful title", it will read "number sign my beautiful
title" for example.

While no experience might be arguably better than a bad experience, I
would instead recommend using one of the above renderers to provide an
accessible experience. My favorite one is the [header link](#header-link),
which is also the simplest one.
Additionally, screen readers users commonly request the list of all
links in the page, so they'll be flooded with "number sign, number sign,
number sign" for each of your headings.

I would highly recommend using one of the markups above which have a
better experience, but if you really want to use this markup, make sure
to pass accessible HTML as `symbol` to make things usable, like in the
example below, but even that has some flaws.

With that said, this permalink allows the following options:

| Name | Description | Default |
|--------------|---------------------------------------------------------------------------------------------------------------------|---------|
| `space` | Add a space between the header text and the permalink symbol. | `true` |
| `placement` | Placement of the permalink, can be `before` or `after` the header. This option used to be called `permalinkBefore`. | `after` |
| `ariaHidden` | Whether to add `aria-hidden="true"`, see [ARIA hidden](#aria-hidden). | `false` |
| | See [common options](#common-options). | |

```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()

md.use(anchor, {
permalink: anchor.permalink.linkInsideHeader({
symbol: `
<span aria-label="Link symbol" role="img">🔗</span>
<span class="visually-hidden">Jump to heading</span>
`,
placement: 'before'
})
})
```

If the `aria-hidden` style is still your way to go, it offers a number
of options:
```html
<h2 id="title">
<a class="header-anchor" href="#title">
<span aria-label="Link symbol" role="img">🔗</span>
<span class="visually-hidden">Jump to heading</span>
</a>
Title
</h2>
```

| Name | Description | Default |
|-------------|---------------------------------------------------------------------------------------------------------------------|---------|
| `space` | Add a space between the header text and the permalink symbol. | `true` |
| `placement` | Placement of the permalink, can be `before` or `after` the header. This option used to be called `permalinkBefore`. | `after` |
While this example allows more accessible anchors with the same markup
as previous versions of markdown-it-anchor, it's still not ideal.
`aria-label` is known to be ignored by most translation tools (including
Google Translate), and a generic assistive text like "jump to heading"
is not very useful when listing the links in the page (which will read
"jump to heading, jump to heading, jump to heading" for each of your
permalinks).

### ARIA hidden

This is just an alias for [`linkInsideHeader`](#link-inside-header) with
`ariaHidden: true` by default, to mimic GitHub's way of rendering
permalinks.

Setting `aria-hidden="true"` makes the permalink explicitly inaccessible
instead of having the permalink and its symbol being read by screen
readers as part of every single headings (which was a pretty terrible
experience).

```js
const anchor = require('markdown-it-anchor')
Expand All @@ -322,6 +376,11 @@ md.use(anchor, {
<h2 id="title"><a class="header-anchor" href="#title" aria-hidden="true">#</a> Title</h2>
```

While no experience might be arguably better than a bad experience, I
would instead recommend using one of the above renderers to provide an
accessible experience. My favorite one is the [header link](#header-link),
which is also the simplest one.

## Debugging

If you want to debug this library more easily, we support source maps.
Expand Down
20 changes: 14 additions & 6 deletions permalink.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,28 @@ const commonDefaults = {
renderAttrs
}

export function makePermalink (wrappedRenderPermalink) {
export function makePermalink (renderPermalinkImpl) {
function renderPermalink (opts) {
opts = Object.assign({}, renderPermalink.defaults, opts)

return (slug, anchorOpts, state, idx) => {
return wrappedRenderPermalink(slug, opts, anchorOpts, state, idx)
return renderPermalinkImpl(slug, opts, anchorOpts, state, idx)
}
}

renderPermalink.defaults = Object.assign({}, commonDefaults)
renderPermalink.renderPermalinkImpl = renderPermalinkImpl

return renderPermalink
}

export const ariaHidden = makePermalink((slug, opts, anchorOpts, state, idx) => {
export const linkInsideHeader = makePermalink((slug, opts, anchorOpts, state, idx) => {
const linkTokens = [
Object.assign(new state.Token('link_open', 'a', 1), {
attrs: [
...(opts.class ? [['class', opts.class]] : []),
['href', opts.renderHref(slug, state)],
['aria-hidden', 'true'],
...(opts.ariaHidden ? [['aria-hidden', 'true']] : []),
...Object.entries(opts.renderAttrs(slug, state))
]
}),
Expand All @@ -93,9 +94,16 @@ export const ariaHidden = makePermalink((slug, opts, anchorOpts, state, idx) =>
state.tokens[idx + 1].children[position[opts.placement]](...linkTokens)
})

Object.assign(ariaHidden.defaults, {
Object.assign(linkInsideHeader.defaults, {
space: true,
placement: 'after'
placement: 'after',
ariaHidden: false
})

export const ariaHidden = makePermalink(linkInsideHeader.renderPermalinkImpl)

ariaHidden.defaults = Object.assign({}, linkInsideHeader.defaults, {
ariaHidden: true
})

export const headerLink = makePermalink((slug, opts, anchorOpts, state, idx) => {
Expand Down
23 changes: 23 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,29 @@ test('uniqueSlugStartIndex', t => {
)
})

nest('permalink.linkInsideHeader', test => {
test('default', t => {
t.is(
md().use(anchor, { permalink: anchor.permalink.linkInsideHeader() }).render('# H1'),
'<h1 id="h1" tabindex="-1">H1 <a class="header-anchor" href="#h1">#</a></h1>\n'
)
})

test('html', t => {
const symbol = '<span aria-label="Link symbol" role="img">🔗</span> <span class="visually-hidden">Jump to heading</span>'

t.is(
md().use(anchor, {
permalink: anchor.permalink.linkInsideHeader({
symbol,
placement: 'before'
})
}).render('# H1'),
`<h1 id="h1" tabindex="-1"><a class="header-anchor" href="#h1">${symbol}</a> H1</h1>\n`
)
})
})

nest('permalink.ariaHidden', test => {
test('default', t => {
t.is(
Expand Down

0 comments on commit b757020

Please sign in to comment.