Skip to content

Commit b5845b7

Browse files
Add the ability to customize hovercard text
1 parent 491f086 commit b5845b7

File tree

1 file changed

+84
-16
lines changed

1 file changed

+84
-16
lines changed

markdown-it-plugins/hovercard.js

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,57 +18,57 @@ function replacer(state) {
1818
let pos;
1919

2020
const labelStart = state.pos + 1;
21-
const labelEnd = parseHovercard(state, state.pos + 1);
21+
const labelEnd = parseHovercard(state, labelStart);
2222
if (labelEnd < 0) { return false }
2323

2424
pos = labelEnd + 1;
2525
const label = state.src.slice(labelStart, labelEnd);
2626

27-
let normalizedLabel = label;
28-
if (state.env?.type === 'api') {
29-
if (label.startsWith('::')) {
30-
// Disambiguate this label name by including the name of the page we're
31-
// on.
32-
normalizedLabel = `${state.env.name}${label}`;
33-
}
34-
}
35-
let simpleLabel = simplifyLabel(normalizedLabel);
27+
let labelData = parseHovercardLabel(label, state);
3628

3729
state.pos = labelStart;
3830
state.posMax = labelEnd;
3931

4032
let href;
41-
if (STATIC_HOVERCARD_URLS.has(label)) {
42-
href = STATIC_HOVERCARD_URLS.get(label);
33+
if (STATIC_HOVERCARD_URLS.has(labelData.token)) {
34+
href = STATIC_HOVERCARD_URLS.get(labelData.token);
4335
} else {
44-
href = inferHrefFromHovercardText(label, state.env?.type === 'api');
36+
href = inferHrefFromHovercardText(labelData.token, state.env?.type === 'api');
4537
}
4638

4739
const token_o = state.push("a_open", "a", 1);
4840
const attrs = [
49-
["data-hovercard", simpleLabel],
50-
["data-hovercard-full", normalizedLabel],
41+
["data-hovercard", labelData.slug],
42+
["data-hovercard-full", labelData.normalized],
43+
["data-hovercard-text", labelData.text],
5144
["href", href]
5245
];
5346
token_o.attrs = attrs;
5447

5548
state.linkLevel++;
5649
state.md.inline.tokenize(state);
50+
if (label !== labelData.text) {
51+
// This is a bit hacky; there might be a better approach. If we need to
52+
// alter the link text, we do so by forcing the link text to be tokenized,
53+
// then changing the token's content after the fact.
54+
state.tokens[state.tokens.length - 1].content = labelData.text;
55+
}
5756
state.linkLevel--;
5857

5958
state.push("a_close", "a", -1);
6059

6160
// Sometimes we need to transform hovercard syntax without any filesystem
6261
// side-effects.
6362
if (!state.env?.skip_hovercard) {
64-
storeHovercard(simpleLabel, normalizedLabel);
63+
storeHovercard(labelData.slug, labelData.normalized);
6564
}
6665

6766
state.pos = pos;
6867
state.posMax = max;
6968
return true;
7069
}
7170

71+
// Finds the ending position of a hovercard.
7272
function parseHovercard(state, start) {
7373
const max = state.posMax;
7474
const oldPos = state.pos;
@@ -162,3 +162,71 @@ function writeHovercardStoreToDisk () {
162162
let json = JSON.stringify(obj, null, 2);
163163
FS.writeFileSync('hovercard_list.json', json);
164164
}
165+
166+
// Given the text between `{` and `}`, returns various formats of the text
167+
// within.
168+
//
169+
// An ordinary hovercard reference looks like `{TextEditor}`, but it's also
170+
// possible to use different link text in a hovercard link: `{TextEditor "the
171+
// text editor"}`. To use this format, you must wrap your link text in a
172+
// single- or double-quoted string and separate it from the token by a space.
173+
//
174+
// This method therefore returns an object with four keys. Consider the
175+
// following example, presumed to be part of a comment block inside
176+
// `src/text-editor.js`:
177+
//
178+
// It's a good idea {::save "to save the document"} before calling
179+
// this method.
180+
//
181+
// This function would return the following keys:
182+
//
183+
// * `token`: The original token (`::save` — which, if not for the presence of
184+
// the explicit link text, would be used as the anchor text)
185+
// * `slug`: The slugified version for internal use (`texteditor__save`)
186+
// * `normalized`: The expanded version of the token used for symbol resolution
187+
// (`TextEditor::save`)
188+
// * `text`: The text to be used for the anchor tag (`to save the document`)
189+
//
190+
function parseHovercardLabel (label, state) {
191+
let token = label, normalized, text;
192+
193+
if (label.includes(' ')) {
194+
let index = label.indexOf(' ');
195+
token = label.substring(0, index);
196+
let rawText = label.substring(index + 1);
197+
// Must start with a quote character.
198+
if (!(/['"]/).test(rawText.charAt(0))) {
199+
console.error(label, JSON.stringify(rawText.charAt(0)), state.env)
200+
throw new Error(`Illegal hovercard text!`)
201+
}
202+
// Must end with a quote character.
203+
if (rawText.charAt(0) !== rawText.charAt(rawText.length -1)) {
204+
console.error(label, state.env)
205+
throw new Error(`Illegal hovercard text!`)
206+
}
207+
// We expect `rawText` to be a string.
208+
text = JSON.parse(rawText);
209+
if (typeof text !== 'string') {
210+
console.error(label, state.env)
211+
throw new Error(`Illegal hovercard text!`)
212+
}
213+
} else {
214+
text = token;
215+
}
216+
217+
normalized = token;
218+
219+
if (state.env?.type === 'api') {
220+
if (label.startsWith('::') || label.startsWith('.')) {
221+
// Disambiguate this label name by including the name of the page we're
222+
// on.
223+
normalized = `${state.env.name}${token}`;
224+
if (text === token) {
225+
text = normalized;
226+
}
227+
}
228+
}
229+
230+
let result = { token, normalized, text, slug: simplifyLabel(normalized) };
231+
return result;
232+
}

0 commit comments

Comments
 (0)