@abreen/tada 1.6.0 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle.ts +11 -1
- package/build/external-links-plugin.ts +181 -22
- package/build/utils/derive-theme.ts +8 -0
- package/build/utils/markdown.ts +41 -12
- package/build/utils/render.ts +15 -0
- package/init/content/markdown.md +11 -3
- package/package.json +1 -1
- package/src/_alerts.scss +11 -6
- package/src/_base.scss +7 -10
- package/src/_layout.scss +23 -1
- package/src/_mixins.scss +41 -0
- package/src/anchor/style.scss +17 -24
- package/src/header/_base.scss +8 -0
- package/src/header/index.ts +12 -0
- package/src/header/style.scss +1 -18
- package/src/index.ts +12 -0
- package/src/navigate/index.ts +47 -29
- package/src/print/style.scss +1 -1
- package/src/style.scss +44 -50
- package/src/toc/index.ts +1 -1
- package/src/toc/style.scss +4 -33
- package/src/top/index.ts +6 -2
- package/templates/_theme.scss +11 -24
- package/templates/_top.html +2 -0
package/build/bundle.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
getDistDir,
|
|
10
10
|
toPosix,
|
|
11
11
|
} from './utils/paths';
|
|
12
|
-
import { deriveTheme } from './utils/derive-theme';
|
|
12
|
+
import { deriveTheme, deriveLinkHue } from './utils/derive-theme';
|
|
13
13
|
import type { PluginBuilder } from 'bun';
|
|
14
14
|
import type { SiteVariables } from './types';
|
|
15
15
|
import timezones from '../src/timezone/timezones.json' with { type: 'json' };
|
|
@@ -27,6 +27,12 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
27
27
|
const iconColorHoverDark = `hsl(${tintHue}deg ${(6 * tintAmount) / 100}% 60%)`;
|
|
28
28
|
const iconColorTranslucentDark = `hsl(${tintHue}deg ${(85 * tintAmount) / 100}% 90%)`;
|
|
29
29
|
|
|
30
|
+
const linkHue = deriveLinkHue(tintHue);
|
|
31
|
+
const linkColor = `hsl(${linkHue}deg 44.4% 49.4%)`;
|
|
32
|
+
const linkColorHover = `hsl(${linkHue}deg 34% 60%)`;
|
|
33
|
+
const linkColorDark = `hsl(${linkHue}deg 50% 72%)`;
|
|
34
|
+
const linkColorHoverDark = `hsl(${linkHue}deg 40% 80%)`;
|
|
35
|
+
|
|
30
36
|
const rendered = _.template(template)({
|
|
31
37
|
...theme,
|
|
32
38
|
tintHue,
|
|
@@ -36,6 +42,10 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
36
42
|
iconColorDark,
|
|
37
43
|
iconColorHoverDark,
|
|
38
44
|
iconColorTranslucentDark,
|
|
45
|
+
linkColor,
|
|
46
|
+
linkColorHover,
|
|
47
|
+
linkColorDark,
|
|
48
|
+
linkColorHoverDark,
|
|
39
49
|
});
|
|
40
50
|
|
|
41
51
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-'));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type MarkdownIt from 'markdown-it';
|
|
2
|
-
import
|
|
2
|
+
import Token from 'markdown-it/lib/token.mjs';
|
|
3
3
|
import { makeLogger } from './log';
|
|
4
4
|
import type { SiteVariables } from './types';
|
|
5
5
|
|
|
@@ -9,38 +9,197 @@ export default function externalLinks(
|
|
|
9
9
|
md: MarkdownIt,
|
|
10
10
|
siteVariables: SiteVariables,
|
|
11
11
|
): void {
|
|
12
|
-
function
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
function isExternal(href: string): boolean {
|
|
13
|
+
if (!href.match(/^https?:\/\/.*$/)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const url = new URL(href);
|
|
17
|
+
return !siteVariables.internalDomains?.includes(url.host);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function findMatchingClose(children: Token[], openIdx: number): number {
|
|
21
|
+
let depth = 1;
|
|
22
|
+
for (let i = openIdx + 1; i < children.length; i++) {
|
|
23
|
+
if (children[i].type === 'link_open') {
|
|
24
|
+
depth++;
|
|
25
|
+
} else if (children[i].type === 'link_close') {
|
|
26
|
+
depth--;
|
|
27
|
+
if (depth === 0) {
|
|
28
|
+
return i;
|
|
29
|
+
}
|
|
17
30
|
}
|
|
31
|
+
}
|
|
32
|
+
return -1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeSpanOpen(): Token {
|
|
36
|
+
const t = new Token('html_inline', '', 0);
|
|
37
|
+
t.content = '<span class="external-link-tail">';
|
|
38
|
+
return t;
|
|
39
|
+
}
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
function makeSpanClose(): Token {
|
|
42
|
+
const t = new Token('html_inline', '', 0);
|
|
43
|
+
t.content = '</span>';
|
|
44
|
+
return t;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Wrap the trailing word of an external link in
|
|
48
|
+
// <span class="external-link-tail"> so the icon (added via that span's
|
|
49
|
+
// ::after) stays glued to the last word and doesn't wrap on its own.
|
|
50
|
+
// The span is inserted inside any inline-formatting elements (em,
|
|
51
|
+
// strong, code, ...) that are still open at the split point, so the
|
|
52
|
+
// resulting markup stays nested.
|
|
53
|
+
function wrapTail(linkChildren: Token[]): Token[] | null {
|
|
54
|
+
if (linkChildren.length === 0) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
24
57
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
// Find the rightmost split char (space or hyphen) in a text token,
|
|
59
|
+
// skipping any candidate that would produce an empty tail (e.g. a
|
|
60
|
+
// trailing hyphen with no content after it).
|
|
61
|
+
let splitTokenIdx = -1;
|
|
62
|
+
let splitCharIdx = -1;
|
|
63
|
+
for (let i = linkChildren.length - 1; i >= 0; i--) {
|
|
64
|
+
const tok = linkChildren[i];
|
|
65
|
+
if (tok.type !== 'text') {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const text = tok.content;
|
|
69
|
+
const hasLaterTokens = i < linkChildren.length - 1;
|
|
70
|
+
let foundIdx = -1;
|
|
71
|
+
for (let j = text.length - 1; j >= 0; j--) {
|
|
72
|
+
const c = text[j];
|
|
73
|
+
if (c !== ' ' && c !== '-') {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (j < text.length - 1 || hasLaterTokens) {
|
|
77
|
+
foundIdx = j;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (foundIdx !== -1) {
|
|
82
|
+
splitTokenIdx = i;
|
|
83
|
+
splitCharIdx = foundIdx;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
30
87
|
|
|
31
|
-
|
|
32
|
-
|
|
88
|
+
if (splitTokenIdx === -1) {
|
|
89
|
+
// No space anywhere in the link's text. Wrap the whole content.
|
|
90
|
+
return [makeSpanOpen(), ...linkChildren, makeSpanClose()];
|
|
91
|
+
}
|
|
33
92
|
|
|
34
|
-
|
|
35
|
-
|
|
93
|
+
// Find where to put the span close: walk forward from the split
|
|
94
|
+
// point tracking nesting depth (locally opened elements after the
|
|
95
|
+
// split). The span must close before any -1 token that would close
|
|
96
|
+
// an element that was already open at the split point.
|
|
97
|
+
let spanCloseIdx = linkChildren.length;
|
|
98
|
+
let depth = 0;
|
|
99
|
+
for (let i = splitTokenIdx + 1; i < linkChildren.length; i++) {
|
|
100
|
+
const tok = linkChildren[i];
|
|
101
|
+
if (tok.nesting === 1) {
|
|
102
|
+
depth++;
|
|
103
|
+
} else if (tok.nesting === -1) {
|
|
104
|
+
if (depth > 0) {
|
|
105
|
+
depth--;
|
|
106
|
+
} else {
|
|
107
|
+
spanCloseIdx = i;
|
|
108
|
+
break;
|
|
36
109
|
}
|
|
37
110
|
}
|
|
38
111
|
}
|
|
39
112
|
|
|
40
|
-
|
|
113
|
+
const splitToken = linkChildren[splitTokenIdx];
|
|
114
|
+
const before = splitToken.content.slice(0, splitCharIdx + 1);
|
|
115
|
+
const after = splitToken.content.slice(splitCharIdx + 1);
|
|
116
|
+
|
|
117
|
+
const result: Token[] = [];
|
|
118
|
+
|
|
119
|
+
// Tokens before the split, unchanged.
|
|
120
|
+
for (let i = 0; i < splitTokenIdx; i++) {
|
|
121
|
+
result.push(linkChildren[i]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// The "before" part of the split text token.
|
|
125
|
+
if (before.length > 0) {
|
|
126
|
+
const beforeToken = new Token('text', '', 0);
|
|
127
|
+
beforeToken.content = before;
|
|
128
|
+
result.push(beforeToken);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
result.push(makeSpanOpen());
|
|
132
|
+
|
|
133
|
+
// The "after" part of the split text token.
|
|
134
|
+
if (after.length > 0) {
|
|
135
|
+
const afterToken = new Token('text', '', 0);
|
|
136
|
+
afterToken.content = after;
|
|
137
|
+
result.push(afterToken);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Tokens between the split and the span close.
|
|
141
|
+
for (let i = splitTokenIdx + 1; i < spanCloseIdx; i++) {
|
|
142
|
+
result.push(linkChildren[i]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
result.push(makeSpanClose());
|
|
146
|
+
|
|
147
|
+
// Tokens from the span close position to the end.
|
|
148
|
+
for (let i = spanCloseIdx; i < linkChildren.length; i++) {
|
|
149
|
+
result.push(linkChildren[i]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function processChildren(children: Token[]): Token[] {
|
|
156
|
+
const result: Token[] = [];
|
|
157
|
+
let i = 0;
|
|
158
|
+
while (i < children.length) {
|
|
159
|
+
const token = children[i];
|
|
160
|
+
|
|
161
|
+
if (token.type !== 'link_open') {
|
|
162
|
+
result.push(token);
|
|
163
|
+
i++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const href = token.attrGet('href');
|
|
168
|
+
if (!href || !isExternal(href)) {
|
|
169
|
+
result.push(token);
|
|
170
|
+
i++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const classAttr = token.attrGet('class');
|
|
175
|
+
const newClassAttr = classAttr ? `${classAttr} external` : 'external';
|
|
176
|
+
token.attrSet('class', newClassAttr);
|
|
177
|
+
token.attrSet('target', '_blank');
|
|
178
|
+
log.debug`${href} -> "${newClassAttr}"`;
|
|
179
|
+
|
|
180
|
+
const closeIdx = findMatchingClose(children, i);
|
|
181
|
+
if (closeIdx === -1) {
|
|
182
|
+
result.push(token);
|
|
183
|
+
i++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const linkContent = children.slice(i + 1, closeIdx);
|
|
188
|
+
const wrapped = wrapTail(linkContent);
|
|
189
|
+
|
|
190
|
+
result.push(token);
|
|
191
|
+
result.push(...(wrapped ?? linkContent));
|
|
192
|
+
result.push(children[closeIdx]);
|
|
193
|
+
i = closeIdx + 1;
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
41
196
|
}
|
|
42
197
|
|
|
43
198
|
md.core.ruler.push('external_links', state => {
|
|
44
|
-
state.tokens.
|
|
199
|
+
state.tokens.forEach(token => {
|
|
200
|
+
if (token.children) {
|
|
201
|
+
token.children = processChildren(token.children);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
45
204
|
});
|
|
46
205
|
}
|
|
@@ -14,6 +14,9 @@ const LIGHT_TEXT_L_MAX = 0.5;
|
|
|
14
14
|
const DARK_TEXT_L_MIN = 0.7;
|
|
15
15
|
const DARK_TEXT_L_MAX = 0.82;
|
|
16
16
|
|
|
17
|
+
const LINK_ANCHOR_HUE = 212;
|
|
18
|
+
const LINK_TINT_BLEND = 0.05;
|
|
19
|
+
|
|
17
20
|
function clamp(v: number, min: number, max: number): number {
|
|
18
21
|
return Math.min(Math.max(v, min), max);
|
|
19
22
|
}
|
|
@@ -75,6 +78,11 @@ export function deriveTheme(cssColor: string): DerivedTheme {
|
|
|
75
78
|
};
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
export function deriveLinkHue(tintHue: number): number {
|
|
82
|
+
const diff = ((tintHue - LINK_ANCHOR_HUE + 540) % 360) - 180;
|
|
83
|
+
return (LINK_ANCHOR_HUE + diff * LINK_TINT_BLEND + 360) % 360;
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
export function getTextOnColor(cssColor: string): '#fff' | '#000' {
|
|
79
87
|
const parsed = parse(cssColor);
|
|
80
88
|
if (!parsed) {
|
package/build/utils/markdown.ts
CHANGED
|
@@ -36,6 +36,22 @@ function capitalize(str: string): string {
|
|
|
36
36
|
return str[0].toUpperCase() + str.slice(1);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const MAX_FOOTNOTES = 35;
|
|
40
|
+
|
|
41
|
+
export function footnoteLabel(oneBasedIndex: number): string {
|
|
42
|
+
if (oneBasedIndex < 1 || oneBasedIndex > MAX_FOOTNOTES) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Tada supports at most ${MAX_FOOTNOTES} footnotes per page ` +
|
|
45
|
+
`(9 digits + 26 capital letters), but footnote ${oneBasedIndex} ` +
|
|
46
|
+
`was requested. Reduce the number of footnotes on this page.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (oneBasedIndex <= 9) {
|
|
50
|
+
return String(oneBasedIndex);
|
|
51
|
+
}
|
|
52
|
+
return String.fromCharCode('A'.charCodeAt(0) + (oneBasedIndex - 10));
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
export function createMarkdown(
|
|
40
56
|
siteVariables: SiteVariables,
|
|
41
57
|
options: CreateMarkdownOptions = {},
|
|
@@ -196,24 +212,22 @@ export function createMarkdown(
|
|
|
196
212
|
});
|
|
197
213
|
|
|
198
214
|
/*
|
|
199
|
-
* Customize markdown-it-footnote renderer
|
|
215
|
+
* Customize markdown-it-footnote renderer.
|
|
216
|
+
*
|
|
217
|
+
* Footnotes are labeled with single characters from Inter's ss06
|
|
218
|
+
* stylistic set: 1-9 for the first nine footnotes, then A-Z. The
|
|
219
|
+
* default <ol> numbering is suppressed via CSS and we inject a
|
|
220
|
+
* marker span per item so the label sequence can mix digits and
|
|
221
|
+
* letters.
|
|
200
222
|
*/
|
|
201
223
|
markdown.renderer.rules.footnote_block_open = () =>
|
|
202
224
|
'<div class="footnotes"><p class="title">Footnotes</p><ol>';
|
|
203
225
|
|
|
204
226
|
markdown.renderer.rules.footnote_block_close = () => '</ol></div>';
|
|
205
227
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
tokens: Token[],
|
|
210
|
-
idx: number,
|
|
211
|
-
options: Options,
|
|
212
|
-
env: unknown,
|
|
213
|
-
self: Renderer,
|
|
214
|
-
) => {
|
|
215
|
-
const str = caption(tokens, idx, options, env, self);
|
|
216
|
-
return str.slice(1, str.length - 1);
|
|
228
|
+
markdown.renderer.rules.footnote_caption = (tokens: Token[], idx: number) => {
|
|
229
|
+
const id = tokens[idx].meta!.id as number;
|
|
230
|
+
return footnoteLabel(id + 1);
|
|
217
231
|
};
|
|
218
232
|
|
|
219
233
|
const footnoteRef = markdown.renderer.rules.footnote_ref!;
|
|
@@ -224,11 +238,26 @@ export function createMarkdown(
|
|
|
224
238
|
env: unknown,
|
|
225
239
|
self: Renderer,
|
|
226
240
|
) =>
|
|
241
|
+
// Prepend a non-breaking space so the reference link cannot wrap
|
|
242
|
+
// away from the word it follows.
|
|
243
|
+
'\u00a0' +
|
|
227
244
|
footnoteRef(tokens, idx, options, env, self)
|
|
228
245
|
.replace('<sup class="footnote-ref">', '')
|
|
229
246
|
.replace('</sup>', '')
|
|
230
247
|
.replace('<a href="', '<a class="footnote-ref" href="');
|
|
231
248
|
|
|
249
|
+
markdown.renderer.rules.footnote_open = (tokens: Token[], idx: number) => {
|
|
250
|
+
const id = tokens[idx].meta!.id as number;
|
|
251
|
+
const label = footnoteLabel(id + 1);
|
|
252
|
+
const refid = id + 1;
|
|
253
|
+
return (
|
|
254
|
+
`<li id="fn${refid}" class="footnote-item">` +
|
|
255
|
+
`<span class="footnote-marker" aria-hidden="true">${label}</span> `
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
markdown.renderer.rules.footnote_close = () => '</li>\n';
|
|
260
|
+
|
|
232
261
|
const footnoteAnchor = markdown.renderer.rules.footnote_anchor!;
|
|
233
262
|
markdown.renderer.rules.footnote_anchor = (
|
|
234
263
|
tokens: Token[],
|
package/build/utils/render.ts
CHANGED
|
@@ -46,12 +46,17 @@ const log = makeLogger(import.meta.url);
|
|
|
46
46
|
|
|
47
47
|
const REQUIRED_FRONT_MATTER_FIELDS = ['title'];
|
|
48
48
|
|
|
49
|
+
function isWatchMode(assetFiles: string[]): boolean {
|
|
50
|
+
return assetFiles.some(f => f.includes('watch-reload-client'));
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
interface TemplateParametersInput {
|
|
50
54
|
pageVariables: Record<string, unknown>;
|
|
51
55
|
siteVariables: SiteVariables;
|
|
52
56
|
content: string | null;
|
|
53
57
|
applyBasePath: (subPath: string) => string;
|
|
54
58
|
subPath: string;
|
|
59
|
+
isWatchMode: boolean;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
function resolveAuthor(
|
|
@@ -101,6 +106,7 @@ function createTemplateParameters({
|
|
|
101
106
|
content,
|
|
102
107
|
applyBasePath,
|
|
103
108
|
subPath,
|
|
109
|
+
isWatchMode,
|
|
104
110
|
}: TemplateParametersInput): Record<string, unknown> {
|
|
105
111
|
return {
|
|
106
112
|
vars: siteVariables.vars || {},
|
|
@@ -111,6 +117,7 @@ function createTemplateParameters({
|
|
|
111
117
|
page: pageVariables,
|
|
112
118
|
content,
|
|
113
119
|
applyBasePath,
|
|
120
|
+
isWatchMode,
|
|
114
121
|
};
|
|
115
122
|
}
|
|
116
123
|
|
|
@@ -188,12 +195,14 @@ export function renderPlainTextPageAsset({
|
|
|
188
195
|
const applyBasePath = createApplyBasePath(siteVariables);
|
|
189
196
|
|
|
190
197
|
log.info`Rendering page ${B`${subPath + ext}`}`;
|
|
198
|
+
const watchMode = isWatchMode(assetFiles);
|
|
191
199
|
const { content, pageVariables, tocItems, alertIds } = renderPlainTextContent(
|
|
192
200
|
filePath,
|
|
193
201
|
subPath,
|
|
194
202
|
siteVariables,
|
|
195
203
|
applyBasePath,
|
|
196
204
|
validInternalTargets,
|
|
205
|
+
watchMode,
|
|
197
206
|
{
|
|
198
207
|
validateInternalLinks: extensionIsMarkdown(ext.toLowerCase()),
|
|
199
208
|
traceCache,
|
|
@@ -223,6 +232,7 @@ export function renderPlainTextPageAsset({
|
|
|
223
232
|
content,
|
|
224
233
|
applyBasePath,
|
|
225
234
|
subPath,
|
|
235
|
+
isWatchMode: watchMode,
|
|
226
236
|
});
|
|
227
237
|
|
|
228
238
|
const templateHtml = render(
|
|
@@ -286,6 +296,7 @@ export function renderCodePageAsset({
|
|
|
286
296
|
content,
|
|
287
297
|
applyBasePath,
|
|
288
298
|
subPath,
|
|
299
|
+
isWatchMode: isWatchMode(assetFiles),
|
|
289
300
|
});
|
|
290
301
|
|
|
291
302
|
const html = injectAssetTags(
|
|
@@ -339,6 +350,7 @@ function renderPlainTextContent(
|
|
|
339
350
|
siteVariables: SiteVariables,
|
|
340
351
|
applyBasePath: (subPath: string) => string,
|
|
341
352
|
validInternalTargets: Set<string>,
|
|
353
|
+
isWatchMode: boolean,
|
|
342
354
|
{
|
|
343
355
|
validateInternalLinks = true,
|
|
344
356
|
traceCache,
|
|
@@ -396,6 +408,7 @@ function renderPlainTextContent(
|
|
|
396
408
|
content: null,
|
|
397
409
|
applyBasePath,
|
|
398
410
|
subPath,
|
|
411
|
+
isWatchMode,
|
|
399
412
|
});
|
|
400
413
|
const pageVariablesProcessed: Record<string, unknown> = Object.entries(
|
|
401
414
|
pageVariables,
|
|
@@ -446,6 +459,7 @@ function renderPlainTextContent(
|
|
|
446
459
|
content: strippedContent,
|
|
447
460
|
applyBasePath,
|
|
448
461
|
subPath,
|
|
462
|
+
isWatchMode,
|
|
449
463
|
});
|
|
450
464
|
|
|
451
465
|
if (traceCache && contentDir && distDir) {
|
|
@@ -649,6 +663,7 @@ export function renderLiterateJavaPageAsset({
|
|
|
649
663
|
content: contentHtml,
|
|
650
664
|
applyBasePath,
|
|
651
665
|
subPath,
|
|
666
|
+
isWatchMode: isWatchMode(assetFiles),
|
|
652
667
|
});
|
|
653
668
|
|
|
654
669
|
const templateHtml = render('literate.html', templateParameters) as string;
|
package/init/content/markdown.md
CHANGED
|
@@ -182,10 +182,10 @@ footnote.[^example-footnote]
|
|
|
182
182
|
Definition lists are supported using the same syntax as Presto:
|
|
183
183
|
|
|
184
184
|
Stack
|
|
185
|
-
: Last-in first-out collection.
|
|
185
|
+
: Last-in first-out collection.[^footnote-2]
|
|
186
186
|
|
|
187
187
|
Queue
|
|
188
|
-
: First-in first-out collection.
|
|
188
|
+
: First-in first-out collection.[^footnote-3]
|
|
189
189
|
|
|
190
190
|
Binary tree
|
|
191
191
|
: Hierarchical structure where each node has up to two children.
|
|
@@ -417,7 +417,15 @@ Here's a time range:
|
|
|
417
417
|
|
|
418
418
|
|
|
419
419
|
[^example-footnote]: Footnotes render inside a numbered list at the bottom of
|
|
420
|
-
the page.
|
|
420
|
+
the page.
|
|
421
|
+
|
|
422
|
+
Here's a second paragraph.
|
|
423
|
+
|
|
424
|
+
Here's a third paragraph.
|
|
425
|
+
|
|
426
|
+
[^footnote-2]: Also known as "LIFO".
|
|
427
|
+
|
|
428
|
+
[^footnote-3]: Also known as "FIFO".
|
|
421
429
|
|
|
422
430
|
[front-matter]: https://www.npmjs.com/package/front-matter
|
|
423
431
|
[markdown-it]: https://markdown-it.github.io/
|
package/package.json
CHANGED
package/src/_alerts.scss
CHANGED
|
@@ -39,10 +39,15 @@
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
a {
|
|
42
|
-
|
|
42
|
+
@include link-underline;
|
|
43
|
+
color: var(--fg-color);
|
|
43
44
|
|
|
44
45
|
&:hover {
|
|
45
|
-
|
|
46
|
+
color: var(--fg2-color);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&:active {
|
|
50
|
+
color: var(--link-active-color);
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -60,6 +65,7 @@
|
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
.alert .title::before {
|
|
68
|
+
content: '';
|
|
63
69
|
left: 8px;
|
|
64
70
|
top: 8px;
|
|
65
71
|
position: absolute;
|
|
@@ -68,12 +74,11 @@
|
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
.alert.warning .title::before {
|
|
71
|
-
|
|
77
|
+
@include contained-mask(var(--icon-warning));
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
i.warning {
|
|
75
|
-
|
|
76
|
-
background-size: contain;
|
|
81
|
+
@include contained-mask(var(--icon-warning));
|
|
77
82
|
display: inline-block;
|
|
78
83
|
height: 1em;
|
|
79
84
|
width: 1em;
|
|
@@ -81,7 +86,7 @@ i.warning {
|
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
.alert.note .title::before {
|
|
84
|
-
|
|
89
|
+
@include contained-mask(var(--icon-note));
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
.alert .title {
|
package/src/_base.scss
CHANGED
|
@@ -66,22 +66,18 @@ html {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
a {
|
|
69
|
-
color: var(--
|
|
70
|
-
text-decoration-
|
|
71
|
-
text-decoration-thickness: 1.5px;
|
|
69
|
+
color: var(--link-color);
|
|
70
|
+
text-decoration-line: none;
|
|
72
71
|
|
|
73
72
|
@media (hover: hover) {
|
|
74
73
|
&:hover {
|
|
75
|
-
color: var(--
|
|
74
|
+
color: var(--link-color-hover);
|
|
75
|
+
@include link-underline;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
}
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@media (hover: hover) {
|
|
84
|
-
text-decoration-color: var(--link-active-color);
|
|
79
|
+
&:active {
|
|
80
|
+
color: var(--link-active-color);
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
83
|
|
|
@@ -146,6 +142,7 @@ details .content {
|
|
|
146
142
|
details summary {
|
|
147
143
|
&:hover {
|
|
148
144
|
color: var(--fg2-color);
|
|
145
|
+
@include link-underline;
|
|
149
146
|
}
|
|
150
147
|
|
|
151
148
|
&:active {
|
package/src/_layout.scss
CHANGED
|
@@ -156,10 +156,31 @@ body.literate main table {
|
|
|
156
156
|
.text {
|
|
157
157
|
color: var(--fg2-color);
|
|
158
158
|
text-decoration: underline;
|
|
159
|
-
text-decoration-color: var(--fg2-color);
|
|
160
159
|
text-decoration-thickness: 1px;
|
|
161
160
|
text-underline-offset: 3px;
|
|
162
161
|
}
|
|
162
|
+
|
|
163
|
+
&:hover .text {
|
|
164
|
+
color: var(--link-color-hover);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
&:active .text {
|
|
168
|
+
color: var(--link-active-color);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&:active .avatar {
|
|
172
|
+
border-color: var(--link-active-color);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&:focus-visible {
|
|
176
|
+
outline: none;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
&:focus-visible .text,
|
|
180
|
+
&:focus-visible .avatar {
|
|
181
|
+
outline: 2px solid var(--theme-color);
|
|
182
|
+
outline-offset: 2px;
|
|
183
|
+
}
|
|
163
184
|
}
|
|
164
185
|
|
|
165
186
|
.avatar {
|
|
@@ -167,6 +188,7 @@ body.literate main table {
|
|
|
167
188
|
height: 24px;
|
|
168
189
|
vertical-align: middle;
|
|
169
190
|
margin-left: 4px;
|
|
191
|
+
border: 1px solid transparent;
|
|
170
192
|
}
|
|
171
193
|
}
|
|
172
194
|
}
|
package/src/_mixins.scss
CHANGED
|
@@ -2,3 +2,44 @@
|
|
|
2
2
|
-webkit-user-select: $val;
|
|
3
3
|
user-select: $val;
|
|
4
4
|
}
|
|
5
|
+
|
|
6
|
+
@mixin link-underline {
|
|
7
|
+
text-decoration-line: underline;
|
|
8
|
+
text-decoration-color: currentColor;
|
|
9
|
+
text-decoration-thickness: 1px;
|
|
10
|
+
text-underline-offset: 0.11rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@mixin subdued-link {
|
|
14
|
+
color: var(--fg-color);
|
|
15
|
+
text-decoration-line: none;
|
|
16
|
+
|
|
17
|
+
@media (hover: hover) {
|
|
18
|
+
&:hover {
|
|
19
|
+
color: var(--fg2-color);
|
|
20
|
+
@include link-underline;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:active {
|
|
25
|
+
color: var(--link-active-color);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@mixin contained-icon {
|
|
30
|
+
background-size: contain;
|
|
31
|
+
background-repeat: no-repeat;
|
|
32
|
+
background-position: center center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@mixin contained-mask($url) {
|
|
36
|
+
-webkit-mask-image: $url;
|
|
37
|
+
mask-image: $url;
|
|
38
|
+
-webkit-mask-size: contain;
|
|
39
|
+
mask-size: contain;
|
|
40
|
+
-webkit-mask-repeat: no-repeat;
|
|
41
|
+
mask-repeat: no-repeat;
|
|
42
|
+
-webkit-mask-position: center center;
|
|
43
|
+
mask-position: center center;
|
|
44
|
+
background-color: currentColor;
|
|
45
|
+
}
|