@abreen/tada 1.0.0
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/LICENSE +21 -0
- package/README.md +290 -0
- package/bin/tada.js +361 -0
- package/config/authors.json +1 -0
- package/config/nav.json +28 -0
- package/content/index.md +19 -0
- package/content/lectures/01/Pair.java.md +296 -0
- package/content/lectures/01/Rectangle.java +80 -0
- package/content/lectures/01/demo.py +9 -0
- package/content/lectures/01/index.md +39 -0
- package/content/lectures/01/lecture1.pdf +0 -0
- package/content/lectures/index.md +25 -0
- package/content/markdown.md +379 -0
- package/content/problem_sets/index.md +6 -0
- package/fonts/google-sans-code/GoogleSansCodeVariable-Italic.ttf +0 -0
- package/fonts/google-sans-code/GoogleSansCodeVariable.ttf +0 -0
- package/fonts/google-sans-code/LICENSE.txt +93 -0
- package/fonts/inter/InterVariable-Italic.ttf +0 -0
- package/fonts/inter/InterVariable.ttf +0 -0
- package/fonts/inter/LICENSE.txt +92 -0
- package/package.json +70 -0
- package/public/avatars/alex.jpg +0 -0
- package/public/test.txt +1 -0
- package/src/_mixins.scss +4 -0
- package/src/anchor/README.md +6 -0
- package/src/anchor/index.ts +34 -0
- package/src/anchor/style.scss +48 -0
- package/src/code/README.md +5 -0
- package/src/code/index.ts +113 -0
- package/src/code/style.scss +101 -0
- package/src/code.scss +54 -0
- package/src/header/README.md +8 -0
- package/src/header/index.ts +43 -0
- package/src/header/style.scss +228 -0
- package/src/index.ts +73 -0
- package/src/layout.scss +144 -0
- package/src/literate/style.scss +60 -0
- package/src/print/README.md +4 -0
- package/src/print/index.ts +32 -0
- package/src/print/style.scss +82 -0
- package/src/question/README.md +3 -0
- package/src/question/index.ts +25 -0
- package/src/question/style.scss +116 -0
- package/src/search/README.md +6 -0
- package/src/search/index.ts +574 -0
- package/src/search/style.scss +217 -0
- package/src/style.scss +815 -0
- package/src/timezone/index.test.ts +100 -0
- package/src/timezone/index.ts +298 -0
- package/src/timezone/style.scss +16 -0
- package/src/timezone/timezones.json +58 -0
- package/src/toc/README.md +3 -0
- package/src/toc/index.ts +322 -0
- package/src/toc/style.scss +203 -0
- package/src/top/README.md +4 -0
- package/src/top/index.ts +75 -0
- package/src/util.ts +122 -0
- package/templates/_author.html +27 -0
- package/templates/_bottom.html +3 -0
- package/templates/_download.html +1 -0
- package/templates/_heading.html +19 -0
- package/templates/_nav.html +18 -0
- package/templates/_theme.scss +97 -0
- package/templates/_top.html +87 -0
- package/templates/authors.schema.json +13 -0
- package/templates/code.html +31 -0
- package/templates/default.html +13 -0
- package/templates/literate.html +16 -0
- package/templates/nav.schema.json +27 -0
- package/tsconfig.json +15 -0
- package/types/dev.ts +3 -0
- package/types/sass.d.ts +1 -0
- package/types/site-variables.d.ts +16 -0
- package/webpack/apply-base-path-plugin.js +78 -0
- package/webpack/build-state.js +97 -0
- package/webpack/code.test.js +162 -0
- package/webpack/colors.js +15 -0
- package/webpack/config.base.js +147 -0
- package/webpack/config.dev.js +23 -0
- package/webpack/config.prod.js +32 -0
- package/webpack/content-watch-plugin.js +153 -0
- package/webpack/deflist-id-plugin.js +62 -0
- package/webpack/external-links-plugin.js +37 -0
- package/webpack/features.js +5 -0
- package/webpack/flair.json +1 -0
- package/webpack/generate-content-assets-plugin.js +308 -0
- package/webpack/generate-favicon-plugin.js +198 -0
- package/webpack/generate-fonts-plugin.js +69 -0
- package/webpack/generate-manifest-plugin.js +116 -0
- package/webpack/globals.js +74 -0
- package/webpack/heading-subtitle-plugin.js +80 -0
- package/webpack/json-schema.js +19 -0
- package/webpack/log.js +143 -0
- package/webpack/markdown-plugins.test.js +203 -0
- package/webpack/pagefind-plugin.js +379 -0
- package/webpack/pagefind-plugin.test.js +131 -0
- package/webpack/pdf-text.js +163 -0
- package/webpack/print-flair-plugin.js +22 -0
- package/webpack/reachability.js +273 -0
- package/webpack/reachability.test.js +80 -0
- package/webpack/serve.js +104 -0
- package/webpack/site-variables.js +53 -0
- package/webpack/site.schema.json +67 -0
- package/webpack/templates.js +128 -0
- package/webpack/text-to-id.js +8 -0
- package/webpack/toc-plugin.js +167 -0
- package/webpack/util.js +49 -0
- package/webpack/utils/code.js +439 -0
- package/webpack/utils/content-files.js +147 -0
- package/webpack/utils/define-plugin.js +20 -0
- package/webpack/utils/file-types.js +26 -0
- package/webpack/utils/front-matter.js +57 -0
- package/webpack/utils/jdi-runner/LiterateRunner.class +0 -0
- package/webpack/utils/jdi-runner/LiterateRunner.java +241 -0
- package/webpack/utils/literate-java.js +153 -0
- package/webpack/utils/markdown.js +244 -0
- package/webpack/utils/parse-hsl.js +8 -0
- package/webpack/utils/paths.js +58 -0
- package/webpack/utils/render.js +466 -0
- package/webpack/utils/shiki-highlighter.js +26 -0
- package/webpack/validate-internal-links-plugin.js +155 -0
- package/webpack/watch-reachability-state.js +273 -0
- package/webpack/watch-reachability-state.test.js +198 -0
- package/webpack/watch-reload-client.js +54 -0
- package/webpack/watch.js +166 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const { convertMarkdown: curlyQuote } = require('quote-quote');
|
|
2
|
+
|
|
3
|
+
function tocPlugin(md) {
|
|
4
|
+
md.core.ruler.push('toc_collector', state => {
|
|
5
|
+
if (!state.env) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const tokens = state.tokens;
|
|
10
|
+
const items = [];
|
|
11
|
+
const containerStack = [];
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
14
|
+
const token = tokens[i];
|
|
15
|
+
|
|
16
|
+
// Headings (included at any nesting level)
|
|
17
|
+
if (token.type === 'heading_open') {
|
|
18
|
+
const inline = tokens[i + 1];
|
|
19
|
+
if (!inline || inline.type !== 'inline') {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const level = token.tag[1]; // 'h2' -> '2'
|
|
24
|
+
const id = token.attrGet('id') || '';
|
|
25
|
+
const innerHtml = md.renderer.renderInline(
|
|
26
|
+
inline.children,
|
|
27
|
+
md.options,
|
|
28
|
+
state.env,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
items.push({ kind: 'heading', level, id, innerHtml });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Thematic breaks / dinkuses (only at top level)
|
|
36
|
+
if (token.type === 'hr' && containerStack.length === 0) {
|
|
37
|
+
items.push({ kind: 'dinkus' });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Alerts (only at top level or directly inside a section)
|
|
42
|
+
// Must be checked before generic container tracking below
|
|
43
|
+
if (token.type === 'container_alert_open') {
|
|
44
|
+
const depth = containerStack.length;
|
|
45
|
+
const parentIsSection = depth === 1 && containerStack[0] === 'section';
|
|
46
|
+
|
|
47
|
+
if (depth === 0 || parentIsSection) {
|
|
48
|
+
const match = token.info.trim().match(/^(note|warning)\s*"?(.+)?"?$/);
|
|
49
|
+
if (match) {
|
|
50
|
+
const type = match[1];
|
|
51
|
+
let title = match[2]?.trim();
|
|
52
|
+
if (title) {
|
|
53
|
+
title = md.utils.escapeHtml(curlyQuote(title));
|
|
54
|
+
} else {
|
|
55
|
+
title = type === 'warning' ? 'Warning' : 'Note';
|
|
56
|
+
}
|
|
57
|
+
items.push({ kind: 'alert', type, title });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Fall through to push 'alert' onto container stack
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Track container nesting
|
|
64
|
+
if (token.type.startsWith('container_') && token.type.endsWith('_open')) {
|
|
65
|
+
const containerType = token.type.slice(
|
|
66
|
+
'container_'.length,
|
|
67
|
+
-'_open'.length,
|
|
68
|
+
);
|
|
69
|
+
containerStack.push(containerType);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
73
|
+
token.type.startsWith('container_') &&
|
|
74
|
+
token.type.endsWith('_close')
|
|
75
|
+
) {
|
|
76
|
+
containerStack.pop();
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
state.env.tocItems = items;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function generateTocHtml(tocItems) {
|
|
86
|
+
if (!tocItems || tocItems.length === 0) {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let lastHeadingLevel = 1;
|
|
91
|
+
const parts = ['<ol>'];
|
|
92
|
+
|
|
93
|
+
for (const item of tocItems) {
|
|
94
|
+
if (item.kind === 'dinkus') {
|
|
95
|
+
parts.push('<li class="dinkus-item"></li>');
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (item.kind === 'heading') {
|
|
100
|
+
parts.push(
|
|
101
|
+
`<li class="heading-item level${item.level}">` +
|
|
102
|
+
`<a href="#${item.id}">${item.innerHtml}</a></li>`,
|
|
103
|
+
);
|
|
104
|
+
lastHeadingLevel = parseInt(item.level);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (item.kind === 'alert') {
|
|
109
|
+
const level = lastHeadingLevel + 1;
|
|
110
|
+
parts.push(
|
|
111
|
+
`<li class="alert-item level${level} ${item.type}">` +
|
|
112
|
+
`<a href="#">${item.title}</a></li>`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
parts.push('</ol>');
|
|
118
|
+
return parts.join('');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function escapeHtml(str) {
|
|
122
|
+
return str
|
|
123
|
+
.replace(/&/g, '&')
|
|
124
|
+
.replace(/</g, '<')
|
|
125
|
+
.replace(/>/g, '>')
|
|
126
|
+
.replace(/"/g, '"');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const GROUP_LABELS = {
|
|
130
|
+
field: 'Fields',
|
|
131
|
+
constructor: 'Constructors',
|
|
132
|
+
method: 'Methods',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function generateCodeTocHtml(codeTocItems) {
|
|
136
|
+
if (!codeTocItems || codeTocItems.length === 0) {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const groups = Object.create(null);
|
|
141
|
+
const kindOrder = [];
|
|
142
|
+
for (const item of codeTocItems) {
|
|
143
|
+
if (!groups[item.kind]) {
|
|
144
|
+
groups[item.kind] = [];
|
|
145
|
+
kindOrder.push(item.kind);
|
|
146
|
+
}
|
|
147
|
+
groups[item.kind].push(item);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const parts = ['<ol>'];
|
|
151
|
+
for (const kind of kindOrder) {
|
|
152
|
+
const label = GROUP_LABELS[kind];
|
|
153
|
+
if (label) {
|
|
154
|
+
parts.push(`<li class="label">${label}</li>`);
|
|
155
|
+
}
|
|
156
|
+
for (const item of groups[kind]) {
|
|
157
|
+
parts.push(
|
|
158
|
+
`<li class="heading-item level2">` +
|
|
159
|
+
`<a href="#L${item.line}">${escapeHtml(item.name)}</a></li>`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
parts.push('</ol>');
|
|
164
|
+
return parts.join('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { tocPlugin, generateTocHtml, generateCodeTocHtml };
|
package/webpack/util.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const {
|
|
2
|
+
extensionIsMarkdown,
|
|
3
|
+
getBuildContentFiles,
|
|
4
|
+
getContentFiles,
|
|
5
|
+
getValidInternalTargets,
|
|
6
|
+
shouldSkipContentFile,
|
|
7
|
+
} = require('./utils/content-files');
|
|
8
|
+
const { createDefinePlugin } = require('./utils/define-plugin');
|
|
9
|
+
const { createMarkdown } = require('./utils/markdown');
|
|
10
|
+
const {
|
|
11
|
+
createApplyBasePath,
|
|
12
|
+
getContentDir,
|
|
13
|
+
getDistDir,
|
|
14
|
+
getPackageDir,
|
|
15
|
+
getProjectDir,
|
|
16
|
+
getPublicDir,
|
|
17
|
+
normalizeOutputPath,
|
|
18
|
+
} = require('./utils/paths');
|
|
19
|
+
const {
|
|
20
|
+
injectWebpackAssets,
|
|
21
|
+
renderCodePageAsset,
|
|
22
|
+
renderCopiedContentAsset,
|
|
23
|
+
renderLiterateJavaPageAsset,
|
|
24
|
+
renderPlainTextPageAsset,
|
|
25
|
+
} = require('./utils/render');
|
|
26
|
+
const { parseFrontMatter } = require('./utils/front-matter');
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
createMarkdown,
|
|
30
|
+
getContentDir,
|
|
31
|
+
getDistDir,
|
|
32
|
+
getPackageDir,
|
|
33
|
+
getProjectDir,
|
|
34
|
+
getPublicDir,
|
|
35
|
+
getContentFiles,
|
|
36
|
+
getBuildContentFiles,
|
|
37
|
+
getValidInternalTargets,
|
|
38
|
+
shouldSkipContentFile,
|
|
39
|
+
extensionIsMarkdown,
|
|
40
|
+
parseFrontMatter,
|
|
41
|
+
createDefinePlugin,
|
|
42
|
+
createApplyBasePath,
|
|
43
|
+
injectWebpackAssets,
|
|
44
|
+
normalizeOutputPath,
|
|
45
|
+
renderCodePageAsset,
|
|
46
|
+
renderCopiedContentAsset,
|
|
47
|
+
renderLiterateJavaPageAsset,
|
|
48
|
+
renderPlainTextPageAsset,
|
|
49
|
+
};
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
const MarkdownIt = require('markdown-it');
|
|
2
|
+
const { parse: parseJava } = require('java-parser');
|
|
3
|
+
const { JSDOM } = require('jsdom');
|
|
4
|
+
const { makeLogger } = require('../log');
|
|
5
|
+
const { getHighlighter } = require('./shiki-highlighter');
|
|
6
|
+
|
|
7
|
+
const log = makeLogger(__filename);
|
|
8
|
+
|
|
9
|
+
const PROSE_LINE = /^\s*\/\/\/(\s|$)/;
|
|
10
|
+
|
|
11
|
+
function createCodeMarkdown(siteVariables, options = {}) {
|
|
12
|
+
return new MarkdownIt({ html: true, typographer: true })
|
|
13
|
+
.use(require('../external-links-plugin'), siteVariables)
|
|
14
|
+
.use(require('../apply-base-path-plugin'), siteVariables, options);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const KIND_LABELS = {
|
|
18
|
+
constructor: 'Constructor',
|
|
19
|
+
field: 'Field',
|
|
20
|
+
method: 'Method',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const JAVA_TYPE_DECLARATION_NODES = new Set([
|
|
24
|
+
'classDeclaration',
|
|
25
|
+
'interfaceDeclaration',
|
|
26
|
+
'enumDeclaration',
|
|
27
|
+
'recordDeclaration',
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
function extractJavaMethodMeta(methodNode, requireBody = true) {
|
|
31
|
+
const methodBody = methodNode.children?.methodBody?.[0];
|
|
32
|
+
const hasBody = Boolean(methodBody?.children?.block?.length);
|
|
33
|
+
if (requireBody && !hasBody) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const methodHeader = methodNode.children?.methodHeader?.[0];
|
|
38
|
+
const methodDeclarator = methodHeader?.children?.methodDeclarator?.[0];
|
|
39
|
+
const identifier = methodDeclarator?.children?.Identifier?.[0];
|
|
40
|
+
if (!identifier?.image || !identifier.startLine) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
baseName: identifier.image,
|
|
46
|
+
line: identifier.startLine,
|
|
47
|
+
params: extractParameterNames(
|
|
48
|
+
methodDeclarator?.children?.formalParameterList?.[0],
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function extractJavaConstructorMeta(constructorNode) {
|
|
54
|
+
const constructorDeclarator =
|
|
55
|
+
constructorNode.children?.constructorDeclarator?.[0];
|
|
56
|
+
const identifier =
|
|
57
|
+
constructorDeclarator?.children?.simpleTypeName?.[0]?.children
|
|
58
|
+
?.typeIdentifier?.[0]?.children?.Identifier?.[0];
|
|
59
|
+
if (!identifier?.image || !identifier.startLine) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
baseName: identifier.image,
|
|
65
|
+
line: identifier.startLine,
|
|
66
|
+
params: extractParameterNames(
|
|
67
|
+
constructorDeclarator?.children?.formalParameterList?.[0],
|
|
68
|
+
),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function extractParameterNames(formalParameterListNode) {
|
|
73
|
+
if (!formalParameterListNode) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const formalParameters =
|
|
78
|
+
formalParameterListNode.children?.formalParameter || [];
|
|
79
|
+
return formalParameters
|
|
80
|
+
.map(parameterNode => {
|
|
81
|
+
const regularParameter =
|
|
82
|
+
parameterNode.children?.variableParaRegularParameter?.[0];
|
|
83
|
+
if (regularParameter) {
|
|
84
|
+
const declaratorId =
|
|
85
|
+
regularParameter.children?.variableDeclaratorId?.[0];
|
|
86
|
+
const identifier = declaratorId?.children?.Identifier?.[0];
|
|
87
|
+
const underscore = declaratorId?.children?.Underscore?.[0];
|
|
88
|
+
return identifier?.image || underscore?.image || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const varArgParameter =
|
|
92
|
+
parameterNode.children?.variableArityParameter?.[0];
|
|
93
|
+
return varArgParameter?.children?.Identifier?.[0]?.image || null;
|
|
94
|
+
})
|
|
95
|
+
.filter(Boolean);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatCallableName(baseName, parameterNames) {
|
|
99
|
+
return `${baseName}(${parameterNames.join(', ')})`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function collectTokensInOrder(node) {
|
|
103
|
+
const tokens = [];
|
|
104
|
+
function collect(n) {
|
|
105
|
+
if (!n) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (n.image !== undefined) {
|
|
109
|
+
tokens.push(n);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const children = n.children || {};
|
|
113
|
+
for (const childArray of Object.values(children)) {
|
|
114
|
+
for (const child of childArray) {
|
|
115
|
+
if (child) {
|
|
116
|
+
collect(child);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
collect(node);
|
|
122
|
+
tokens.sort((a, b) => a.startOffset - b.startOffset);
|
|
123
|
+
return tokens;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildTypeString(unannTypeNode) {
|
|
127
|
+
return collectTokensInOrder(unannTypeNode)
|
|
128
|
+
.map(t => (t.image === ',' ? ', ' : t.image))
|
|
129
|
+
.join('');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function extractJavaFieldMetas(fieldNode) {
|
|
133
|
+
const unannType = fieldNode.children?.unannType?.[0];
|
|
134
|
+
if (!unannType) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const typeStr = buildTypeString(unannType);
|
|
138
|
+
|
|
139
|
+
const variableDeclaratorList =
|
|
140
|
+
fieldNode.children?.variableDeclaratorList?.[0];
|
|
141
|
+
if (!variableDeclaratorList) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const results = [];
|
|
146
|
+
for (const declarator of variableDeclaratorList.children
|
|
147
|
+
?.variableDeclarator || []) {
|
|
148
|
+
const declaratorId = declarator.children?.variableDeclaratorId?.[0];
|
|
149
|
+
const identifier = declaratorId?.children?.Identifier?.[0];
|
|
150
|
+
if (!identifier?.image || !identifier.startLine) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const dimsNode = declaratorId.children?.dims?.[0];
|
|
155
|
+
const dimsStr = dimsNode
|
|
156
|
+
? collectTokensInOrder(dimsNode)
|
|
157
|
+
.map(t => t.image)
|
|
158
|
+
.join('')
|
|
159
|
+
: '';
|
|
160
|
+
|
|
161
|
+
results.push({
|
|
162
|
+
name: `${typeStr}${dimsStr} ${identifier.image}`,
|
|
163
|
+
line: identifier.startLine,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return results;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function extractJavaMethodToc(sourceCode) {
|
|
170
|
+
let cst;
|
|
171
|
+
try {
|
|
172
|
+
cst = parseJava(sourceCode);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
log.error`Failed to parse Java source for TOC: ${err.message}`;
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const callables = [];
|
|
179
|
+
|
|
180
|
+
function visit(node, typeDepth) {
|
|
181
|
+
if (!node || !node.name) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (node.name === 'methodDeclaration' && typeDepth <= 1) {
|
|
186
|
+
const method = extractJavaMethodMeta(node);
|
|
187
|
+
if (method) {
|
|
188
|
+
callables.push({ ...method, kind: 'method' });
|
|
189
|
+
}
|
|
190
|
+
} else if (node.name === 'interfaceMethodDeclaration' && typeDepth <= 1) {
|
|
191
|
+
const method = extractJavaMethodMeta(node, false);
|
|
192
|
+
if (method) {
|
|
193
|
+
callables.push({ ...method, kind: 'method' });
|
|
194
|
+
}
|
|
195
|
+
} else if (node.name === 'constructorDeclaration' && typeDepth === 1) {
|
|
196
|
+
const constructor = extractJavaConstructorMeta(node);
|
|
197
|
+
if (constructor) {
|
|
198
|
+
callables.push({ ...constructor, kind: 'constructor' });
|
|
199
|
+
}
|
|
200
|
+
} else if (
|
|
201
|
+
(node.name === 'fieldDeclaration' ||
|
|
202
|
+
node.name === 'constantDeclaration') &&
|
|
203
|
+
typeDepth <= 1
|
|
204
|
+
) {
|
|
205
|
+
for (const field of extractJavaFieldMetas(node)) {
|
|
206
|
+
callables.push({ ...field, kind: 'field' });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const nextTypeDepth = JAVA_TYPE_DECLARATION_NODES.has(node.name)
|
|
211
|
+
? typeDepth + 1
|
|
212
|
+
: typeDepth;
|
|
213
|
+
const children = node.children || {};
|
|
214
|
+
for (const value of Object.values(children)) {
|
|
215
|
+
for (const child of value) {
|
|
216
|
+
if (child && child.name) {
|
|
217
|
+
visit(child, nextTypeDepth);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
visit(cst, 0);
|
|
224
|
+
|
|
225
|
+
return callables.map(callable => {
|
|
226
|
+
const label = KIND_LABELS[callable.kind] ?? 'Member';
|
|
227
|
+
if (callable.name !== undefined) {
|
|
228
|
+
return { ...callable, label };
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
kind: callable.kind,
|
|
232
|
+
label,
|
|
233
|
+
name: formatCallableName(callable.baseName, callable.params),
|
|
234
|
+
line: callable.line,
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function escapeAttr(str) {
|
|
240
|
+
return str.replace(/&/g, '&').replace(/"/g, '"');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function escapeHtml(text) {
|
|
244
|
+
return text
|
|
245
|
+
.replace(/&/g, '&')
|
|
246
|
+
.replace(/</g, '<')
|
|
247
|
+
.replace(/>/g, '>');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function createCodeLine(document) {
|
|
251
|
+
const line = document.createElement('span');
|
|
252
|
+
line.className = 'code-line';
|
|
253
|
+
return line;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function cloneOpenElements(openElements, line) {
|
|
257
|
+
const containers = [line];
|
|
258
|
+
|
|
259
|
+
for (const openElement of openElements) {
|
|
260
|
+
const clone = openElement.cloneNode(false);
|
|
261
|
+
containers[containers.length - 1].appendChild(clone);
|
|
262
|
+
containers.push(clone);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return containers;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function splitHighlightedHtmlIntoLines(highlightedHtml, lineCount) {
|
|
269
|
+
const fragment = JSDOM.fragment(`<code>${highlightedHtml}</code>`);
|
|
270
|
+
const codeEl = fragment.firstChild;
|
|
271
|
+
const document = codeEl.ownerDocument;
|
|
272
|
+
const lines = [];
|
|
273
|
+
const openElements = [];
|
|
274
|
+
let currentLine = createCodeLine(document);
|
|
275
|
+
let currentContainers = [currentLine];
|
|
276
|
+
let currentLineHasContent = false;
|
|
277
|
+
|
|
278
|
+
function finishCurrentLine() {
|
|
279
|
+
if (!currentLineHasContent) {
|
|
280
|
+
currentContainers[currentContainers.length - 1].appendChild(
|
|
281
|
+
document.createTextNode('\u00A0'),
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
lines.push(currentLine);
|
|
285
|
+
currentLine = createCodeLine(document);
|
|
286
|
+
currentContainers = cloneOpenElements(openElements, currentLine);
|
|
287
|
+
currentLineHasContent = false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function visit(node) {
|
|
291
|
+
if (node.nodeType === 3) {
|
|
292
|
+
const parts = (node.textContent || '').split('\n');
|
|
293
|
+
for (let i = 0; i < parts.length; i++) {
|
|
294
|
+
if (parts[i].length > 0) {
|
|
295
|
+
currentContainers[currentContainers.length - 1].appendChild(
|
|
296
|
+
document.createTextNode(parts[i]),
|
|
297
|
+
);
|
|
298
|
+
currentLineHasContent = true;
|
|
299
|
+
}
|
|
300
|
+
if (i < parts.length - 1) {
|
|
301
|
+
finishCurrentLine();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (node.nodeType !== 1) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const clone = node.cloneNode(false);
|
|
312
|
+
currentContainers[currentContainers.length - 1].appendChild(clone);
|
|
313
|
+
openElements.push(node);
|
|
314
|
+
currentContainers.push(clone);
|
|
315
|
+
|
|
316
|
+
for (const child of Array.from(node.childNodes)) {
|
|
317
|
+
visit(child);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
currentContainers.pop();
|
|
321
|
+
openElements.pop();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
for (const child of Array.from(codeEl.childNodes)) {
|
|
325
|
+
visit(child);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (currentLineHasContent || lines.length < lineCount) {
|
|
329
|
+
if (!currentLineHasContent) {
|
|
330
|
+
currentContainers[currentContainers.length - 1].appendChild(
|
|
331
|
+
document.createTextNode('\u00A0'),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
lines.push(currentLine);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
while (lines.length < lineCount) {
|
|
338
|
+
lines.push(createCodeLine(document));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return lines.map(line => line.innerHTML);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function renderCodeSegment(lines, startLine, lang) {
|
|
345
|
+
const source = lines.join('\n');
|
|
346
|
+
let lineHtml;
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const highlighter = getHighlighter();
|
|
350
|
+
const html = highlighter.codeToHtml(source, {
|
|
351
|
+
lang,
|
|
352
|
+
themes: { light: 'github-light', dark: 'github-dark' },
|
|
353
|
+
defaultColor: false,
|
|
354
|
+
});
|
|
355
|
+
const fragment = JSDOM.fragment(html);
|
|
356
|
+
const inner = fragment.querySelector('code').innerHTML;
|
|
357
|
+
lineHtml = splitHighlightedHtmlIntoLines(inner, lines.length);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
log.error`Failed to highlight code block: ${err.message}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (!lineHtml) {
|
|
363
|
+
lineHtml = lines.map(line => escapeHtml(line));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const rows = lineHtml.map((line, i) => {
|
|
367
|
+
const lineNumber = startLine + i;
|
|
368
|
+
return `<span class="code-row"><a class="line-number" data-pagefind-ignore tabindex="-1" id="L${lineNumber}" href="#L${lineNumber}">${lineNumber}</a><code class="shiki language-${lang}">${line}</code></span>`;
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return `<pre>${rows.join('')}</pre>`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function renderCodeWithComments(sourceCode, lang, siteVariables) {
|
|
375
|
+
const md = createCodeMarkdown(siteVariables);
|
|
376
|
+
const lines = sourceCode.split('\n');
|
|
377
|
+
if (lines[lines.length - 1] === '') {
|
|
378
|
+
lines.pop();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Group lines into segments
|
|
382
|
+
const segments = [];
|
|
383
|
+
let currentType = null;
|
|
384
|
+
let currentLines = [];
|
|
385
|
+
let currentStart = 1;
|
|
386
|
+
|
|
387
|
+
for (let i = 0; i < lines.length; i++) {
|
|
388
|
+
const type =
|
|
389
|
+
lang === 'java' && PROSE_LINE.test(lines[i]) ? 'prose' : 'code';
|
|
390
|
+
if (type !== currentType) {
|
|
391
|
+
if (currentLines.length > 0) {
|
|
392
|
+
segments.push({
|
|
393
|
+
type: currentType,
|
|
394
|
+
lines: currentLines,
|
|
395
|
+
startLine: currentStart,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
currentType = type;
|
|
399
|
+
currentLines = [lines[i]];
|
|
400
|
+
currentStart = i + 1;
|
|
401
|
+
} else {
|
|
402
|
+
currentLines.push(lines[i]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (currentLines.length > 0) {
|
|
406
|
+
segments.push({
|
|
407
|
+
type: currentType,
|
|
408
|
+
lines: currentLines,
|
|
409
|
+
startLine: currentStart,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return segments
|
|
414
|
+
.map(segment => {
|
|
415
|
+
if (segment.type === 'code') {
|
|
416
|
+
return renderCodeSegment(segment.lines, segment.startLine, lang);
|
|
417
|
+
} else {
|
|
418
|
+
const indent = Math.min(
|
|
419
|
+
...segment.lines.map(line => {
|
|
420
|
+
const match = line.match(/^(\s*)\/\/\//);
|
|
421
|
+
return match ? match[1].length : 0;
|
|
422
|
+
}),
|
|
423
|
+
);
|
|
424
|
+
const prose = segment.lines
|
|
425
|
+
.map(line => line.replace(/^\s*\/\/\/(\s?)/, ''))
|
|
426
|
+
.join('\n');
|
|
427
|
+
const source = escapeAttr(segment.lines.join('\n'));
|
|
428
|
+
return `<div class="code-prose" data-prose-source="${source}" style="--prose-indent: ${indent}ch"><div class="code-prose-gutter"></div><div class="code-prose-content">${md.render(prose)}</div></div>`;
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
.join('');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
module.exports = {
|
|
435
|
+
createCodeMarkdown,
|
|
436
|
+
extractJavaMethodToc,
|
|
437
|
+
renderCodeSegment,
|
|
438
|
+
renderCodeWithComments,
|
|
439
|
+
};
|