Giter VIP home page Giter VIP logo

markdown-it-anchor's Introduction

markdown-it-anchor npm version

A markdown-it plugin that adds an id attribute to headings and optionally permalinks.

English | 中文 (v7.0.1)

Overview

This plugin adds an id attribute to headings, e.g. ## Foo becomes <h2 id="foo">Foo</h2>.

Optionally it can also include permalinks, e.g. <h2 id="foo"><a class="header-anchor" href="#foo">Foo</a></h2> and a bunch of other variants!

Usage

const md = require('markdown-it')()
  .use(require('markdown-it-anchor'), opts)

See a demo as JSFiddle.

The opts object can contain:

Name Description Default
level Minimum level to apply anchors, or array of selected levels. 1
permalink A function to render permalinks, see permalinks below. undefined
slugify A custom slugification function. See index.js
callback Called with token and info after rendering. undefined
getTokensText A custom function to get the text contents of the title from its tokens. See index.js
tabIndex Value of the tabindex attribute on headings, set to false to disable. -1
uniqueSlugStartIndex Index to start with when making duplicate slugs unique. 1

All headers greater than the minimum level will have an id attribute with a slug of their content. For example, you can set level to 2 to add anchors to all headers but h1. You can also pass an array of header levels to apply the anchor, like [2, 3] to have an anchor on only level 2 and 3 headers.

If a permalink renderer is given, it will be called for each matching header to add a permalink. See permalinks below.

If a slugify function is given, you can decide how to transform a heading text to a URL slug. See user-friendly URLs.

The callback option is a function that will be called at the end of rendering with the token and an info object. The info object has title and slug properties with the token content and the slug used for the identifier.

We set by default tabindex="-1" on headers. This marks the headers as focusable elements that are not reachable by keyboard navigation. The effect is that screen readers will read the title content when it's being jumped to. Outside of screen readers, the experience is the same as not setting that attribute. You can override this behavior with the tabIndex option. Set it to false to remove the attribute altogether, otherwise the value will be used as attribute value.

Finally, you can customize how the title text is extracted from the markdown-it tokens (to later generate the slug). See user-friendly URLs.

User-friendly URLs

Starting from v5.0.0, markdown-it-anchor dropped the string package to retain our core value of being an impartial and secure library. Nevertheless, users looking for backward compatibility may want the old slugify function:

npm install string
const string = require('string')
const slugify = s => string(s).slugify().toString()

const md = require('markdown-it')()
  .use(require('markdown-it-anchor'), { slugify })

Another popular library for this is @sindresorhus/slugify, which have better Unicode support and other cool features:

npm install @sindresorhus/slugify
const slugify = require('@sindresorhus/slugify')

const md = require('markdown-it')()
  .use(require('markdown-it-anchor'), { slugify: s => slugify(s) })

Additionally, if you want to further customize the title that gets passed to the slugify function, you can do so by customizing the getTokensText function, that gets the plain text from a list of markdown-it inline tokens:

function getTokensText (tokens) {
  return tokens
    .filter(token => !['html_inline', 'image'].includes(token.type))
    .map(t => t.content)
    .join('')
}

const md = require('markdown-it')()
  .use(require('markdown-it-anchor'), { getTokensText })

By default we include only text and code_inline tokens, which appeared to be a sensible approach for the vast majority of use cases.

An alternative approach is to include every token's content except for html_inline and image tokens, which yields the exact same results as the previous approach with a stock markdown-it, but would also include custom tokens added by any of your markdown-it plugins, which might or might not be desirable for you. Now you have the option!

Manually setting the id attribute

You might want to explicitly set the id attribute of your headings from the Markdown document, for example to keep them consistent across translations.

markdown-it-anchor is designed to reuse any existing id, making markdown-it-attrs a perfect fit for this use case. Make sure to load it before markdown-it-anchor!

Then you can do something like this:

# Your title {#your-custom-id}

The anchor link will reuse the id that you explicitly defined.

Compatible table of contents plugin

Looking for an automatic table of contents (TOC) generator? Take a look at markdown-it-toc-done-right it's made from the ground to be a great companion of this plugin.

Parsing headings from HTML blocks

markdown-it-anchor doesn't parse HTML blocks, so headings defined in HTML blocks will be ignored. If you need to add anchors to both HTML headings and Markdown headings, the easiest way would be to do it on the final HTML rather than during the Markdown parsing phase:

const { parse } = require('node-html-parser')

const root = parse(html)

for (const h of root.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
  const slug = h.getAttribute('id') || slugify(h.textContent)
  h.setAttribute('id', slug)
  h.innerHTML = `<a href="#${slug}">${h.innerHTML}</a>`
}

console.log(root.toString())

Or with a (not accessible) GitHub-style anchor, replace the h.innerHTML part with:

h.insertAdjacentHTML('afterbegin', `<a class="anchor" aria-hidden="true" href="#${slug}">🔗</a> `)

While this still needs extra work like handling duplicated slugs and IDs, this should give you a solid base.

That said if you really want to use markdown-it-anchor for this even though it's not designed to, you can do like npm does with their marky-markdown parser, and transform the html_block tokens into a sequence of heading_open, inline, and heading_close tokens that can be handled by markdown-it-anchor:

const md = require('markdown-it')()
  .use(require('@npmcorp/marky-markdown/lib/plugin/html-heading'))
  .use(require('markdown-it-anchor'), opts)

While they use regexes to parse the HTML and it won't gracefully handle any arbitrary HTML, it should work okay for the happy path, which might be good enough for you.

You might also want to check this implementation which uses Cheerio for a more solid parsing, including support for HTML attributes.

The only edge cases I see it failing with are multiple headings defined in the same HTML block with arbitrary content between them, or headings where the opening and closing tag are defined in separate html_block tokens, both which should very rarely happen.

If you need a bulletproof implementation, I would recommend the first HTML parser approach I documented instead.

Browser example

See example.html.

Permalinks

Version 8.0.0 completely reworked the way permalinks work in order to offer more accessible options out of the box. You can also make your own permalink.

Instead of a single default way of rendering permalinks (which used to have a poor UX on screen readers), we now have multiple styles of permalinks for you to chose from.

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

md.use(anchor, {
  permalink: anchor.permalink[styleOfPermalink](permalinkOpts)
})

Here, styleOfPermalink is one of the available styles documented below, and permalinkOpts is an options object.

All renderers share a common set of options:

Name Description Default
class The class of the permalink anchor. header-anchor
symbol The symbol in the permalink anchor. #
renderHref A custom permalink href rendering function. See permalink.js
renderAttrs A custom permalink attributes rendering function. See permalink.js

For the symbol, you may want to use the link symbol, or a symbol from your favorite web font.

Header link

This style wraps the header itself in an anchor link. It doesn't use the symbol option as there's no symbol needed in the markup (though you could add it with CSS using ::before if you like).

It's so simple it doesn't have any behaviour to custom, and it's also accessible out of the box without any further configuration, hence it doesn't have other options than the common ones described above.

You can find this style on the MDN as well as HTTP Archive and their Web Almanac, which to me is a good sign that this is a thoughtful way of implementing permalinks. This is also the style that I chose for my own blog.

Name Description Default
safariReaderFix Add a span inside the link so Safari shows headings in reader view. false (for backwards compatibility)
See common options.
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()

md.use(anchor, {
  permalink: anchor.permalink.headerLink()
})
<h2 id="title"><a class="header-anchor" href="#title">Title</a></h2>

The main caveat of this approach is that you can't include links inside headers. If you do, consider the other styles.

Also note that this pattern breaks reader mode in Safari, an issue you can also notice on the referenced websites above. This was already reported to Apple but their bug tracker is not public. In the meantime, a fix mentioned in the article above is to insert a span inside the link. You can use the safariReaderFix option to enable it.

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

md.use(anchor, {
  permalink: anchor.permalink.headerLink({ safariReaderFix: true })
})
<h2 id="title"><a class="header-anchor" href="#title"><span>Title</span></a></h2>

Link after header

If you want to customize further the screen reader experience of your permalinks, this style gives you much more freedom than the header link.

It works by leaving the header itself alone, and adding the permalink after it, giving you different methods of customizing the assistive text. It makes the permalink symbol aria-hidden to not pollute the experience, and leverages a visuallyHiddenClass to hide the assistive text from the visual experience.

Name Description Default
style The (sub) style of link, one of visually-hidden, aria-label, aria-describedby or aria-labelledby. visually-hidden
assistiveText A function that takes the title and returns the assistive text. undefined, required for visually-hidden and aria-label styles
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
wrapper Opening and closing wrapper string, e.g. ['<div class="wrapper">', '</div>']. null
See common options.
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()

md.use(anchor, {
  permalink: anchor.permalink.linkAfterHeader({
    style: 'visually-hidden',
    assistiveText: title => `Permalink to “${title}”`,
    visuallyHiddenClass: 'visually-hidden',
    wrapper: ['<div class="wrapper">', '</div>']
  })
})
<div class="wrapper">
  <h2 id="title">Title</h2>
  <a class="header-anchor" href="#title">
    <span class="visually-hidden">Permalink to “Title”</span>
    <span aria-hidden="true">#</span>
  </a>
</div>

By using a visually hidden element for the assistive text, we make sure that the assistive text can be picked up by translation services, as most of the popular translation services (including Google Translate) currently ignore aria-label.

If you prefer an alternative method for the assistive text, see other styles:

aria-label variant

This removes the need from a visually hidden span, but will likely hurt the permalink experience when using a screen reader through a translation service.

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

md.use(anchor, {
  permalink: anchor.permalink.linkAfterHeader({
    style: 'aria-label'
    assistiveText: title => `Permalink to “${title}”`
  })
})
<h2 id="title">Title</h2>
<a class="header-anchor" href="#title" aria-label="Permalink to “Title”">#</a>
aria-describedby and aria-labelledby variants

This removes the need to customize the assistive text to your locale and doesn't need a visually hidden span either, but since the anchor will be described by just the text of the title without any context, it might be confusing.

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

md.use(anchor, {
  permalink: anchor.permalink.linkAfterHeader({
    style: 'aria-describedby' // Or `aria-labelledby`
  })
})
<h2 id="title">Title</h2>
<a class="header-anchor" href="#title" aria-describedby="title">#</a>

Link inside header

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.

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", "number" or "number sign") meaning that if you title is "my beautiful title", it will read "number sign my beautiful title" for example. For other common symbols, 🔗 is usually read as "link symbol" and as "pilcrow".

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. Set it to a string to customize the space (e.g. &nbsp;). 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. false
See common options.
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()

md.use(anchor, {
  permalink: anchor.permalink.linkInsideHeader({
    symbol: `
      <span class="visually-hidden">Jump to heading</span>
      <span aria-hidden="true">#</span>
    `,
    placement: 'before'
  })
})
<h2 id="title">
  <a class="header-anchor" href="#title">
    <span class="visually-hidden">Jump to heading</span>
    <span aria-hidden="true">#</span>
  </a>
  Title
</h2>

While this example allows more accessible anchors with the same markup as previous versions of markdown-it-anchor, it's still not ideal. The assistive text for permalinks will be read as part of the heading when listing all the titles of the page, e.g. "jump to heading title 1, jump to heading title 2" and so on. Also that assistive text 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 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).

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

md.use(anchor, {
  permalink: anchor.permalink.ariaHidden({
    placement: 'before'
  })
})
<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, which is also the simplest one.

Custom permalink

If none of those options suit you, you can always make your own renderer! Take inspiration from the code behind all permalinks.

The signature of the function you pass in the permalink option is the following:

function renderPermalink (slug, opts, state, idx) {}

Where opts are the markdown-it-anchor options, state is a markdown-it StateCore instance, and idx is the index of the heading_open token in the state.tokens array. That array contains Token objects.

To make sense of the "token stream" and the way token objects are organized, you will probably want to read the markdown-it design principles page.

This function can freely modify the token stream (state.tokens), usually around the given idx, to construct the anchor.

Because of the way the token stream works, a heading_open token is usually followed by a inline token that contains the actual text (and inline markup) of the heading, and finally a heading_close token. This is why you'll see most built-in permalink renderers touch state.tokens[idx + 1], because they update the contents of the inline token that follows a heading_open.

Debugging

If you want to debug this library more easily, we support source maps.

Use the source-map-support module to enable it with Node.js.

node -r source-map-support/register your-script.js

Development

# Build the library in the `dist/` directory.
npm run build

# Watch file changes to update `dist/`.
npm run dev

# Run tests, will use the build version so make sure to build after
# making changes.
npm test

markdown-it-anchor's People

Contributors

antonio-laguna avatar arve0 avatar catnose99 avatar chrysalis1215 avatar foray1010 avatar gerhobbelt avatar gtournie avatar hirasso avatar joostdecock avatar kaciras avatar manuth avatar mavaddat avatar meteorlxy avatar mjackson avatar mrdrogdrog avatar mvargeson avatar nagaozen avatar oliverjam avatar opportunityliu avatar rivy avatar stmtk1 avatar strml avatar thierryk avatar tschaub avatar valeriangalliat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

markdown-it-anchor's Issues

`id`'s for Paragraphs Too?

Question

Have you considered allowing id's to be applied to paragraphs---not just headings?

Problem

  • For a lot of my blog posts/writing (especially short passages of text), I don't use headings.
  • In those situations, it would be nice to permalink to a specific area of my post/writing, rather than just link to the entire post itself.

Suggestions

You could implement it in a similar way as you do for headings.

  • You could leverage slugify in the same way
  • Have it take up to the first five words and create a string from that
    • If the paragraph has less than five words, just create an id with as many words as the paragraph has
      • If the paragraph has only four words, make the id with those four words
    • If a paragraph has more than five words, take the first five words and create a id from that
    • If two paragraphs have the same first five words, add a sixth word, and so on

Example

Consider the following Markdown source:

# Heading <!-- What `markdown-it-anchor` does already -->

This is a paragraph. <!-- Four words only -->

This is yet another paragraph. <!-- Five words -->

This is yet another paragraph that has more than five words. <!-- More than five words, and the first five are the same as the above example paragraph -->

This could be parsed to the following:

<h1 id="heading">Heading</h1> <!-- Expected default/current behavior -->

<p id="this-is-a-paragraph">This is a paragraph.</p> <!-- Since the Markdown source is a paragraph with only four words, those four words are incorporated into the `id` -->

<p id="this-is-yet-another-paragraph">This is yet another paragraph.</p> <!-- Five words, so the `id` gets those exact five words -->

<p id="this-is-yet-another-paragraph-that">This is yet another paragraph that has more than five words.</p> <!-- The Markdown source has more than five words, so the first five would be incorporated into the `id`. But since the previous paragraph would have that same `id`, `markdown-it-anchor` should add a sixth word. If another `id` with that string exists already, add another word (so the `id` would have seven words), etc. -->

Dave Winer made this concept famous: WinerLinks (there's even a WordPress plugin)!

But I know markdown-it-anchor could have a more intelligent approach than the WordPress plugin.

I'd like to know your thoughts :)

Allow custom anchor attributes

It would be nice to be able to control what attributes are set on the anchor.

For example I'd like to set an aria-label so screenreader users get a useful name instead of whatever unicode symbol/icon is inside the anchor. This is kind of related to #58.

Rather than continually adding new options for each possible attribute could we support passing a permalinkAttrs object as an option? E.g.

const md = require('markdown-it')()
  .use(require('markdown-it-anchor'), {
    permalinkAttrs: { 'aria-label': 'heading permalink' }
  })

I think this would be relatively simple to implement:

  const linkTokens = [
    Object.assign(new state.Token('link_open', 'a', 1), {
      attrs: [
        ['class', opts.permalinkClass],
        ['href', opts.permalinkHref(slug, state)],
+       ...Object.entries(opts.permalinkAttrs)
      ]
    }),
    Object.assign(new state.Token('html_block', '', 0), { content: opts.permalinkSymbol }),
    new state.Token('link_close', 'a', -1)
  ]

Repeated problem with anchor

version: v5.0.1
device:macbook pro
description:
Using the markdown-it-anchor plugin and tui-editor combined, using react, when publishing the edited content, it is found that each release adds an anchor.

tui.Editor.markdownit.use(
    mdAnchor,
    {
        level: [1, 2, 3, 4, 5, 6],
        permalink: true,
        permalinkSymbol: '#',
        slugify: s => slug(s)
    }
);

As shown in the figure below, multiple anchors will be generated, but I only need one.

Z7nlMd.png

I found the problem is that when you execute renderPermalink , you will continue to add anchors. So, is there any good solution?

Headers with special characters between words should be converted to "-"

Bug report

When creating headers with special characters between words, is it expected that the special character will be changed to "-"

Steps to reproduce

Create duplicated Headers (same name) inside a markdown file

# header & header

What is expected?

The anchor link for the header should be:

#header--header

What is actually happening?

It is generating the following anchor links:

#header-header

BUG: Table of contents does not support markdown

Scenario

Given I have the following markdown string:

[[toc]]

# Introduction to American Novels
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut labore.

## Literary Analysis of _Catcher in the Rye_ 
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut labore.

## Understanding Race in *Huckleberry Finn*
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut labore.

This will render the following table of contents:

  <div class="table-of-contents">
    <ul>
      <li>
        <a href="#introduction-to-american-novels">Introduction to American Novels</a>
        <ul>
          <li><a href="#literary-analysis-of-catcher-in-the-rye">Literary Analysis of _Catcher in the Rye_</a></li>
          <li><a href="#understanding-race-in-huckleberry-finn">Understanding Race in *Huckleberry Finn*</a></li>
        </ul>
      </li>
    </ul>
  </div>

  <h1 id="introduction-to-american-novels">Introduction to American Novels</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
    tempor incididunt ut labore.
  </p>
  
  <h2 id="literary-analysis-of-catcher-in-the-rye">Literary Analysis of <em>Catcher in the Rye</em></h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
    tempor incididunt ut labore.
  </p>
  
  <h2 id="understanding-race-in-huckleberry-finn">Understanding Race in <em>Huckleberry Finn</em></h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
    tempor incididunt ut labore.
  </p>

Problem

Notice that the ToC headers contain raw / unprocessed markdown (e.g. Literary Analysis of _Catcher in the Rye_) whereas the actual headers have converted the markdown into HTML (e.g. Literary Analysis of <em>Catcher in the Rye</em>).

I would expect to see the markdown be converted into HTML in both locations.

Next steps

Looking at your code, I think the md object may have access to the underlying rendering functions. For example, I wrote a markdown-it extension which did something like:

this.env.md.renderInline("Some text which _may have formatting_")

And this allowed me to process markdown text within my extension – which I think is what we want in this case as well.

I'm not 100% sure how this would work in your code, but I think it's possible (and probably pretty easy). If you need help, lemme know and could probably make a PR.

2.7.0 not working for strange reason

Hello, I have your package as dependency of aglio-theme-olio,

When I try to gulp build, I have a strange error:

/build/proj/api/node_modules/markdown-it-anchor/index.js:79
      var slug = token.attrGet('id');
                       ^

TypeError: token.attrGet is not a function
    at /build/proj/api/node_modules/markdown-it-anchor/index.js:79:24
    at Array.forEach (native)
    at Array.<anonymous> (/build/proj/api/node_modules/markdown-it-anchor/index.js:71:8)
    at Core.process (/build/proj/api/node_modules/markdown-it/lib/parser_core.js:51:13)
    at MarkdownIt.parse (/build/proj/api/node_modules/markdown-it/lib/index.js:493:13)
    at MarkdownIt.render (/build/proj/api/node_modules/markdown-it/lib/index.js:513:36)
    at decorate (/build/proj/api/node_modules/aglio-theme-olio/lib/main.js:404:32)
    at Object.exports.render (/build/proj/api/node_modules/aglio-theme-olio/lib/main.js:646:5)
    at /build/proj/api/node_modules/aglio/lib/main.js:133:20

If I force install 2.6.0 version - all works ok.

Anchor links only working in chrome

For some reason, the anchor links are only working properly in chrome. When I test in Firefox or Safari, the page is scrolled to the top.

2.5.1 and newer are not tagged, no release notes exist

I’ve just found that 3.0.0 is out, however, I can’t find any reference to any changes, and I can’t seem to glean from the commit history exactly what’s changed. 2.5.1 and up are also missing from tags, so I can’t tell what changed between them either.

Is 3.0.0 really a major API change?

Add anchor after heading text

Right now, the anchor is always added before the heading text:

<!-- Currently -->
<h1><a href="...">P</a>I am a header</h1>

<!-- What I would like to see -->
<h1>I am a header<a href="...">P</a></h1>

This makes is easy to hide the anchor until mouse over and doesn't require shifting headings outside of their container to keep alignment on the left side (for LTR text). Ultimately it should probably just be an option.

Is this something you'd be willing to add? Otherwise I think this plugin is great!

Possible feature? target anchor rather than header id

It turns out that the best way to handle a fixed top toolbar for anchors (ie creating a top offset to avoid scrolling to a position under the toolbar), is to use a target anchor rather than a header id for the target.

So I've forked markdown-it-anchor to accommodate that. Thought you might be interested.

Basically: I've modified the processing logic as follows:

        if (slug == null) {
          slug = uniqueSlug(opts.slugify(title), slugs)
          token.attrPush(['class',opts.headerClassName])
          token.attrPush(['style','position:relative;padding-left:16px;margin-left:-16px;'])

          if (opts.useTargetlink) {

            opts.renderTargetlink(slug, opts, state, tokens.indexOf(token),title,token.tag)

          } else {

            token.attrPush(['id', slug])

          }
        }

        if (opts.permalink) {
          opts.renderPermalink(slug, opts, state, tokens.indexOf(token))
        }

some new options:

let defaults = {
  level: 1,
  slugify,
  permalink: false,
  renderPermalink,
  permalinkClass: 'header-anchor',
  permalinkSymbol: '¶',
  permalinkBefore: false,
  permalinkHref,
 // new...
  useTargetlink:false,
  renderTargetlink,
  targetlinkClass: 'target-anchor',
  headerClassName:'content-header',
}

To create the renderTargetLink I just copied and modified your renderPermalink.

Congrats on a well-written utility! It was very straightforward to fork and extend it.

Just thought you might be interested in case you want to accommodate this use case in your excellent plugin.

How to add prefix to id?

In your documentation you mentioned that by using slugify we can create custo id but it uses jquery.
SO I wonder is there any way to add custom prefix to Id?

Create package

Would be great to see this available packaged up on bower, preferably precompiled to ES5.

Edit: I just saw it's on npm, this only leaves bower then.

Nested anchor slugs

Is there an option to use nesting when generating slugs? ie:

# Tacos

## Beef

# Burritos

## Beef

resulting in:

<h1 id="tacos">Tacos</h1>
<h2 id="taco-beef"Beef</h2>
<h1 id="burritos">Burritos</h1>
<h2 id="burritos-beef"Beef</h2>

Duplicated Headers generates wrong anchor links (incremented by 1)

Bug report

When creating duplicated headers, is it expected that the duplicated anchor links will increase starting from "xxxx-1".
It starts from "xxxx-2".

Steps to reproduce

Create duplicated Headers (same name) inside a markdown file

# header

# header

# header

What is expected?

The anchor link for each header should be:

#header
#header-1
#header-2

What is actually happening?

It is generating the following anchor links:

#header
#header-2
#header-3

How to work in Angular

Hi,
Is it possible to work in Angular?
I did this below:
import * as MarkdownAnchor from 'markdown-it-anchor';
But it doesn't work when running.

Lambda function breaks minifying

Hi there,

I successfully use markdown-it-anchor until I try to make a production build using Webpack, along with UglifyJS plugin.

ERROR in dashboard.js from UglifyJs
Unexpected token: operator (>) [dashboard.js:98703,19]

I think I've identified what breaks minifying: the use of lambda functions (() => {})

Shouldn't this package be available as a 100% ES5 version ?

Reverse level

Doesn't level works as opposite as its supposed to?

For example, i want to give an id attribute to my main headers, h1, h2 and h3, but i can't use the level attribute to control that. How i'm supposed to exclude the h4 ones? It should work the opposite :/

level: 3 <- minimum header level of 3 to apply the functionality.

But right now that's just adding the attributes to h3-h6 tags, which is... weird.

GitHub inspired permalink rendering

If possible, could we get permalinks to render just like GitHub does for Readme's?

e.g. the hover and the entire header is a link itself?

TypeError: plugin.apply is not a function with v5.2.3

Running into "TypeError: plugin.apply is not a function" with version 5.2.3.

TypeError: plugin.apply is not a function
    at MarkdownIt.push../node_modules/markdown-it/lib/index.js.MarkdownIt.use (vendors.._src_components_pages_blog__year__month__day__post.._src_components_pages_index.js:13325)
    at _callee$ (._src_components_pages_blog__year__month__day__post.js:553)
    at tryCatch (commons.app.js:6771)
    at Generator.invoke [as _invoke] (commons.app.js:6997)
    at Generator.prototype.<computed> [as next] (commons.app.js:6823)
    at asyncGeneratorStep (vendors.app.js:31)
    at _next (vendors.app.js:53)
    at vendors.app.js:60
    at new Promise (<anonymous>)
    at vendors.app.js:49

Reproducible using this repo.

plugin.apply is not a function

I'm using markdown-it v10.0.0 and markdown-it-anchor v5.2.7. My code is

const md = require('markdown-it')({ linkify: true, xhtmlOut: true,  typographer: true });
md.use(require('markdown-it-anchor'), { permalink: true, permalinkBefore: true, permalinkSymbol: '§' })

the error is: Uncaught TypeError: plugin.apply is not a function. And the error still exist when the options { permalink: true, permalinkBefore: true, permalinkSymbol: '§' } is removed.

Does someone know what the problem is?

TypeError: token.attrGet is not a function

   "TypeError: token.attrGet is not a function",
    "    at /home1/user/61916c91a4e81be8/node_modules/markdown-it-anchor/index.js:79:24",
    "    at Array.forEach (native)",
    "    at Array.<anonymous> (/home1/user/61916c91a4e81be8/node_modules/markdown-it-anchor/index.js:71:8)",
    "    at Core.process (/home1/user/61916c91a4e81be8/node_modules/markdown-it/lib/parser_core.js:51:13)",
    "    at MarkdownIt.parse (/home1/user/61916c91a4e81be8/node_modules/markdown-it/lib/index.js:493:13)",
    "    at MarkdownIt.render (/home1/user/61916c91a4e81be8/node_modules/markdown-it/lib/index.js:513:36)",
    "    at decorate (/home1/user/61916c91a4e81be8/node_modules/aglio-theme-olio/lib/main.js:422:44)",
    "    at Object.exports.render (/home1/user/61916c91a4e81be8/node_modules/aglio-theme-olio/lib/main.js:646:5)",
    "    at /home1/user/61916c91a4e81be8/node_modules/aglio/lib/main.js:133:20"

The following works fine. I seem to have installed v2.5.0 some time ago.

This new version fails.

Please add enough test to verify the changes, and if some of them are breaking, always increment the major version, not the minor.

Opened a corresponding issue for aglio-theme-olio so that they always use the exact version at danielgtaylor/aglio#319.

Specifying a base URL in a Nuxt app breaks the links

Hello,

After doing something like this in nuxt.config.js:

router: {
    base: process.env.NODE_ENV === 'production'
      ? '/zeta/'
      : '/'
  }

All header anchors break and direct the user to the root of the app.
Is there anything I can configure so it works as it should?

Live article that exhibits the behaviour:
https://lobotuerto.com/zeta/blog/vuejs-components-inside-markdown

Try clicking on anything on the TOC, or the § links besides the headers in the main content.

permalinkAttrs is not working

I'm tring to add custom attribute to the anchor link but it is not working. This is the code I'm using:

import markdownIt from 'markdown-it';
import markdownItAnchor from 'markdown-it-anchor';

const md = new markdownIt().use(markdownItAnchor, {
  permalinkAttrs: () => ({ "aria-hidden":  false })
});

I've cloned this repository and when I run the tests with yarn test it returns an error.

TypeError: plugin.apply is not a function

...\node_modules\markdown-it\lib\index.js:477
  plugin.apply(plugin, args);
         ^

TypeError: plugin.apply is not a function

Seems like the babel upgrade uses exports.default, which node does not load as a function:

$ npm install markdown-it-anchor
$ node
> require("markdown-it-anchor")
{ default:
   { [Function: anchor]
     defaults:
      { level: 1,
        slugify: [Function: slugify],
        permalink: false,
        renderPermalink: [Function: renderPermalink],
        permalinkClass: 'header-anchor',
        permalinkSymbol: '',
        permalinkBefore: false } } }

Which breaks md.use(require('markdown-it-anchor')).

Option to remove aria-hidden from output

I believe that adding aria-hidden is incorrect for these links. These links should be accessible and would be useful to screen reader users too!

Notably this throws an error when using the axe-core accessibility linting tool.

Violation of "aria-hidden-focus" with 6 occurrences!
Ensures aria-hidden elements do not contain focusable elements. Correct invalid elements at:

The options to fix here are to remove aria-hidden (thus, this issue) or to set tabindex="-1" on the link, which wouldn’t be right either. I want these links to be focusable.

Thank you!

TypeError: require(...).use is not a function

I use it as the README.md said, but got such errors:

const md = require('markdown-it').use(require('markdown-it-anchor'))({
                                  ^

TypeError: require(...).use is not a function
    at Object.<anonymous> (D:\workspace\mobi.css\gulpfile.js:15:35)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Liftoff.handleArguments (D:\workspace\mobi.css\node_modules\gulp\bin\gulp.js:116:3)
    at Liftoff.<anonymous> (D:\workspace\mobi.css\node_modules\liftoff\index.js:198:16)

I think the README.md should update to something like this:

const md = require('markdown-it')()
  .use(require('markdown-it-anchor'), opts)

TypeError: plugin.apply is not a function with v5.2.4

Running into "TypeError: plugin.apply is not a function" with version 5.2.4.

./node_modules/markdown-it/lib/index.js.MarkdownIt.use
./node_modules/markdown-it/lib/index.js:496
  493 |  **/
  494 | MarkdownIt.prototype.use = function (plugin /*, params, ... */) {
  495 |   var args = [ this ].concat(Array.prototype.slice.call(arguments, 1));
> 496 |   plugin.apply(plugin, args);
  497 |   return this;
  498 | };
  499 | 

TypeError: linkTokens[position[(!opts.permalinkBefore)]](...) is not a function

I've added this plugin to my rollup build (by npm) and I get the following error when enabling the permlinks:

TypeError: linkTokens[position[(!opts.permalinkBefore)]](...) is not a function

This happens in line https://github.com/valeriangalliat/markdown-it-anchor/blob/master/index.js#L33

The rollup output of those lines are:

linkTokens[position[!opts.permalinkBefore]](space())
(ref = state.tokens[idx + 1].children)[position[opts.permalinkBefore]].apply(ref, linkTokens);

a simple semicolon after the first line would solve this issue:

linkTokens[position[!opts.permalinkBefore]](space());

How about add ES Modules support?

Build failed when integrated with React:

Creating an optimized production build...
Failed to compile.

Failed to minify the code from this file:

 	./node_modules/markdown-it-anchor/index.js:1

Read more here: http://bit.ly/2tRViJ9

Doesn't work with markdown-it-toc

If I use markdown-it-toc after, your plugin just do nothing.
And if I use it before, your plugin add anchor, but something seems broken

<h2>
  <a id="Installation_3"></a>
  <a class="markdownIt-Header-anchor" href="#installation">#</a>
  Installation
</h2>

Any idea how to get anchor links + toc ?

My options

markdownIt({
html: true,
  linkify: true,
  typographer: true,
  highlight: (code, lang) => {
    code = code.trim()
    // language is recognized by highlight.js
    if (lang && hljs.getLanguage(lang)) {
      return hljs.highlight(lang, code).value
    }
    // fallback to auto
    return hljs.highlightAuto(code).value
  })
        .use(markdownItToc)
        .use(markdownItAnchor, {
          permalink: true,
          permalinkClass: "markdownIt-Header-anchor",
          permalinkSymbol: "#",
          permalinkBefore: true,
        })

Add callback option

I'm interested in generating data for a table of contents and would like to make use of this plugin if possible. Ideally, I could provide a callback that was called with the heading level (or the token itself), the title, and the slug. This would be enough to generate a table of contents using the id attributes added by this plugin.

I could use the existing slugify option to gather titles, but the return value may not be used as the id (given the call to uniqueSlug), and knowing the heading level would make for a nicer table of contents.

I could also use the existing renderPermalink option, but that would require repeating a lot of what this plugin does to get the same permalink rendering (plus I'd have to redo the title generation).

Would you be in favor of adding support for a callback(token, slug, title) option (or the second arg could be info with slug and title properties)?

Bigfoot.js Footnotes Broken

Bigfoot.js is a JavaScript tool used to render footnote popups in lieu of regular-style 'bounce-around-the-page' footnotes.

I am using Casey Liss's node-based blogging engine Camel, and have found that when viewing the source for a rendered page, the footnote placeholders become <undefined> in the HTML.

Here is a snippet parsed HTML showing the <undefined> element, in the place of where a footnote identifier/class should be.

<li id="fn10"  class="footnote-item"><undefined>And the pictures in the article only show the flappy box <a href="#fnref10" class="footnote-backref"></a><undefined></li>

Ideas?

Error thrown when headers below level are present

When rendering a document with headers that are below the specified level, an error is thrown. The below example illustrates the problem.

If the # Title declaration is removed, the error is no longer thrown. So it appears that headers below the specified level cause this error.

markdownIt = require('markdown-it')

content = """
# Title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

## Sub Title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

### Sub Sub Title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
"""

rendered = markdownIt()
  .use require('markdown-it-anchor'), level: 2, permalink: true
  .render(content)

console.log rendered

not support Chinese language

.md
## [# 添加权限](#ss)
result
<h2><a href="#ss"># 添加权限</a></h2>

.md
## [# 添加权限role](#ss)
result
<h2 id="role-2"><a href="#ss"># 添加权限role</a></h2>

is there a way to solve?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.