landakram / remark-wiki-link

Parse and render wiki links.
MIT License
90 stars 15 forks source link

Setting `hrefTemplate` throws: `pagePermalinks.find is not a function` #18

Closed AlbinoGeek closed 3 years ago

AlbinoGeek commented 3 years ago

Synopsis

When attempting to specify a pageResolver in configuration via API use, 💥

.use(remarkWikiLink, {
  pageResolver: (permalink: string) => `/${permalink}` // real paths at root
})

Specifying an empty object {} or omitting it works as expected. (so this is the only thing breaking.)

Why?

Analytics (not everything supports PWA rooted on hashes) Logging (lots of webservers and HTTPd strip hash fragments in logs) Search Engine Optimization Server-Side Generation in NextJS User Sanity (easily linking to legible URLs.) Why would all pages be HASH FRAGMENTS !?

Output

TypeError: pagePermalinks.find is not a function

Stack Trace

Uncaught
    at Object.exitWikiLink (file:///home/damon/Projects/wikish/node_modules/mdast-util-wiki-link/dist/index.cjs.js:60:36)
    at compile (file:///home/damon/Projects/wikish/node_modules/mdast-util-from-markdown/lib/index.js:291:40)
    at fromMarkdown (file:///home/damon/Projects/wikish/node_modules/mdast-util-from-markdown/lib/index.js:109:29)
    at parser (file:///home/damon/Projects/wikish/node_modules/remark-parse/lib/index.js:15:12)
    at Function.parse (file:///home/damon/Projects/wikish/node_modules/unified/lib/index.js:273:12)
    at executor (file:///home/damon/Projects/wikish/node_modules/unified/lib/index.js:393:31)
    at new Promise (<anonymous>)
    at Function.process (file:///home/damon/Projects/wikish/node_modules/unified/lib/index.js:380:14)
    at render (webpack-internal:///./lib/markdown.ts:49:6)
    at getStaticProps (webpack-internal:///./pages/[slug].tsx:48:79)

Input

TypeScript

async function render(
  input: string
): Promise<string> {
  const result = await unified()
    .use(remarkParse)
    .use(remarkGfm)
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(remarkWikiLink, {
      pageResolver: (permalink: string) => `/${permalink}` // real paths at root
    })
    .use(rehypeRaw)
    .use(rehypeSlug)
    .use(rehypeToc, { nav: false })
    .use(rehypePresetMinify)
    .use(rehypeStringify, {
      quoteSmart:       true,
      closeSelfClosing: true,
      omitOptionalTags: true,
      entities:         { useShortestReferences: true }
    })
    .process(input)

  return result.toString()
}

Markdown

- [[Foobar]]

What I've also tried:

Brute-force guessing the acceptable configuration, none of the following work:

    .use(remarkWikiLink, {
      permalinks: () => [], // maybe this is required when specifying config?
      pageResolver: (permalink: string) => `/${permalink}` // real paths at root
    })
    .use(remarkWikiLink, {
      permalinks: [], // maybe ": [string]" doesn't mean "=> string[]" ?
      pageResolver: (permalink: string) => `/${permalink}` // real paths at root
    })
    .use(remarkWikiLink, {
      pagePermalinks: [], // maybe it's the name the error mentions
      pageResolver: (permalink: string) => `/${permalink}` // real paths at root
    })
landakram commented 3 years ago

Thanks for the report. pageResolver should return a list of strings, rather than a string. This list is then cross-referenced with whatever is in permalinks to determine whether the permalink is valid. Generally, the list returned by pageResolver will be of one element.

This is documented in the README:

options.pageResolver (pageName: String) -> [String]: A function that maps a page name to an array of possible permalinks. These possible permalinks are cross-referenced with options.permalinks to determine whether a page exists. If a page doesn't exist, the first element of the array is considered the permalink.

The default pageResolver is:

(name) => [name.replace(/ /g, '_').toLowerCase()]

Btw, I don't like that this is how it works. It's like this because I wrote this plugin a very long time ago and I didn't know any better. It fit my use-case at the time, which was supporting multiple possible transformations from a page name to permalink (i.e. maybe the actual permalink is underscore or camelcase). Probably, there should be no separate list of permalinks and pageResolver should be responsible for returning the actual permalink rather than a list of "candidates" that are cross-checked with permalinks.

Let me know if this answers your question and I'll close the issue.

AlbinoGeek commented 3 years ago

This was 100% my fault.

I went too long without sleep. Here's what works:

async function render(
  markdown: string
): Promise<string> {
  const result = await unified()
    .use(remarkParse)
    .use(remarkGfm)
    .use(remarkEmoji, { emoticon: true })
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(remarkWikiLink, {
      hrefTemplate: (permalink: string) => `/${permalink}` // real paths at root
    })
    .use(rehypeRaw)
    .use(rehypeSlug)
    .use(rehypeToc, { nav: false })
    // .use(rehypeFormat)
    .use(rehypePresetMinify)
    .use(rehypeStringify, {
      quoteSmart:       true,
      closeSelfClosing: true,
      omitOptionalTags: true,
      entities:         { useShortestReferences: true }
    })
    .process(markdown)
  return result.toString()
}

I conflated hrefTemplate and pageResolver when going through the README.


I don't know if you can fix it (might be GFM fault, having a code block within a list item's contents), but the code samples for each definition not being indented makes them confusing to read (I have limited vision, indent levels help me follow content.)