valeriangalliat / markdown-it-anchor

A markdown-it plugin that adds an `id` attribute to headings and optionally permalinks.
The Unlicense
292 stars 72 forks source link

Possible feature? target anchor rather than header id #36

Closed HenrikBechmann closed 5 years ago

HenrikBechmann commented 6 years ago

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.

nagaozen commented 6 years ago

@valeriangalliat any thoughts about this?! This code smells... specially the token.attrPush(['style','position:relative;padding-left:16px;margin-left:-16px;'])

HenrikBechmann commented 6 years ago

That particular line was a hack of mine. Please disregard for the purposes of this proposal.

Here's code for renderTargetLink

// added by HB, copied and modified renderPermalink
const renderTargetlink = (slug, opts, state, idx, text, tag) => {
  const space = () =>
    Object.assign(new state.Token('text', '', 0), { content: ' ' })

  const linkTokens = [
    Object.assign(new state.Token('link_open', 'a', 1), {
      attrs: [
        ['class', opts.targetlinkClass],
        ['id', slug],
        ['data-text',text],
        ['data-level',tag],
        ['aria-hidden', 'true']
      ]
    }),
    Object.assign(new state.Token('html_block', '', 0), { content: '' }),
    new state.Token('link_close', 'a', -1)
  ]

  // `push` or `unshift` according to position option.
  // Space is at the opposite side.
  linkTokens[position[(!opts.permalinkBefore).toString()]](space())
  state.tokens[idx + 1].children[position[opts.permalinkBefore]](...linkTokens)
}

here are the defaults for the new properties:

let defaults = {
 ...
  useTargetlink:false,
  renderTargetlink,
  targetlinkClass: 'target-anchor hash-anchor',
  headerClassName:'content-header',
}

here's the target-anchor class

a.target-anchor {
    position:absolute;
    top:-64px;
}

So, the point is, the permalink goes after the target anchor when selected, which places the link 64p below the link to leave room for the fixed title.

(I use hash-anchor for identification -- no styles -- to pull the anchors out of the dom for creation of a table of contents, so ignore that too)