remark-wiki-link
remark-wiki-link copied to clipboard
Real URLs, Preserved Hashes w/ `pageResolver`
Since this would be a departure from how this plugin works (at least, it would break the current functionality) I don't expect this change to be implemented by you necessarily, however, I'd love to know where to start should I maintain that change for our project.
Summary
I want to link to pages with hash marks, without removing the hash marks, but while benefiting from pageResolver
;
Such that all of the following are true:
Input | Default | Currently | Expected |
---|---|---|---|
hello world |
#/page/hello_world |
/hello+world |
/hello+world |
food |
#/page/food |
/food |
/food |
foods |
#/page/foods |
/food |
/food |
#help |
(fails) | (fails) | ... href="permalink#help" ... |
drinks#unhealthy |
(fails) | /drinks (fails) |
/drinks#unhealthy |
category:food |
#/page/category (fails) |
/food (fails) |
/Category:food |
The Ask
I either need to:
- Modify the signature of
pageResolver
to allow changing thehref
andlabel
(breaking); or - Have another config function called after
pageResolver
which allows changing thelabel
- Plugin supporting hash URLs specifically (unlikely, large breaking changes based on default function.)
If there was an option to parse the link before/after pageResolver
matching, I would be able to apply this logic myself.
Detail
Our project works closer to WikiPedia, with the following rules on URL generation:
alphabet: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+
separators: _^-~()<>+.,:;/\\|!&*
Slug is sourced from the document's "Title", with the following transformations:
- accented characters are replaced with their latin/arabic equivalents
- separators are replaced with
+
, series of+
are squashed into one+
- any leading or trailing
+
are removed - if reserved,
/page/${slug}
, otherwise,/${slug}
Then when it comes to linking, we have some WikiPedia-esque conveniences:
- Every singular word slug is also available at its equivalent plural
- Every plural word slug is also available at its equivalents singular
- Links with hashes should allow you to link to sections (:boom:)
Using the options provided by the plugin, we could get rather close to realizing this:
.use(remarkWikiLink, {
hrefTemplate: (permalink: string) => `/page/${permalink}`,
permalinks: getAllDocs().map((x) => x.slug),
pageResolver: (pageName: string) => {
const slug = pageName.replace(/ /g, '+').toLowerCase()
const possible = [slug]
if (pluralize.isPlural(slug)) {
possible.push(pluralize.singular(slug))
} else {
possible.push(pluralize.plural(slug))
}
return possible
}
}
However, if I want 3 to work, I need some logic around how the link itself is parsed.
I tried to do this with pageResolver
, but this removes the hash from the link.
const parts = pageName.split('#', 2)
// part[0] is the actual permalink / page slug
// part.length > 1 && part[1] is a section hash
const slug = parts[0].replace(/ /g, '_').toLowerCase()
// ...
Sorry for the delay. I see what you're saying -- you need the hash fragment for further processing, but to match against permalinks
, the page returned by pageResolver
mustn't have the hash (or the permalinks
list must include hashes, which is unwieldy). And then the matched permalink of course doesn't include the hash.
Here's my proposed solution:
Instead of having pageResolver
return a list of permalinks that are matched with permalinks
, have pageResolver
actually resolve the page and return an object, whose type looks something like this:
{
id: string, // Previously `permalink`
exists: boolean,
href: string | undefined,
data: any | undefined
}
TBD on the exact key names, but the idea is that you would do the actual matching yourself in pageResolver
(ensuring that exists
is set). You could also attach whatever data you wanted, including the hash, for retrieval from the AST later. You could also define the href there instead of using hrefTemplate
.
Less relevant to you, but we could retain backward compatibility by using hrefTemplate
and permalinks
conditionally on the return type of pageResolver
.
Would that solve your problem?
{ id: string, // Previously `permalink` exists: boolean, href: string | undefined, data: any | undefined }
Would that solve your problem?
Hmm, then I'd be sending out something like:
[[Food#Important]]
[[Food#References]]
[[Food#nonexist]]
[[Food#nonexist:Alias Always Wins]]
Hypothetical usage of the type you suggested
const allDocs = getAllDocs() // TODO: I need to cache this
// const allDocs = ['food', 'bar', 'baz', 'foods#important']
pageResolver: (pageName: string) => {
const slug = pageName.replace(/ /g, '+').toLowerCase()
const base = {
data: null, // what consumes this?
href: slug,
id: slug,
}
// 4ace13:
// add the slug and it's plural or singular (whichever is missing)
// this will fail for unknown hashes, that's okay, see below
const possible = [{ ...base, exists: allDocs.any(x => x == slug) }]
if (pluralize.isPlural(slug)) {
const tmp = pluralize.singular(slug)
possible.push({ ... base, href: tmp, id: tmp, exists: allDocs.any(x => x == tmp) })
} else {
const tmp = pluralize.plural(slug)
possible.push({ ... base, href: tmp, id: tmp, exists: allDocs.any(x => x == tmp) })
}
// if ( slug contains # )
// ... now what? I can't re-write the label.
return possible
}
Output I want to make
<a class="internal" href="/foods#important">Important Foods</a> <!-- this would be caught by 4ace13 -->
<a class="internal" href="/food#references">References (Food)</a> <!-- this would be rewritten by slug contains # -->
<a class="internal new" href="/food#nonexist">Nonexist (Food)</a> <!-- I also want to rewrite non-exist labels -->
<a class="internal new" href="/food#nonexist">Alias Always Wins</a> <!-- but a coded alias should always in out -->
Hmm.. I think I didn't understand how to use the example, I realize after trying to think up a usage ^
I really tried to think about how I would end up using this, and hit a bit of a visualization wall.
The pieces of input data that exist are what?
type Input = {
name: string
alias?: string
}
And the output data seems to be something like:
type Output = {
className: string // or w/e adds `internal` and/or `new`
label: string // doesn't seem I can see/change this atm
permalink: string
}
What I'm having trouble understanding is how I would match the permalink while masking the label.
I apologize that my ask seems far away from what the plugin does at the moment, and that I'm bad at explaining things (haha.)