@d10f/asciidoc-astro-loader 0.0.2 → 0.0.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/README.md +153 -95
- package/dist/chunk-2UGTFP4R.js +22 -0
- package/dist/chunk-5P6LDJGO.js +65 -0
- package/dist/{chunk-BRMWIQA2.js → chunk-NC6IZHLR.js} +8 -10
- package/dist/{index-BNxO58s3.d.cts → index-CS3PBqxf.d.cts} +159 -66
- package/dist/{index-BNxO58s3.d.ts → index-CS3PBqxf.d.ts} +159 -66
- package/dist/index.cjs +227 -254
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +47 -37
- package/dist/lib/asciidoc/converters/index.cjs +6 -100
- package/dist/lib/asciidoc/converters/index.d.cts +5 -5
- package/dist/lib/asciidoc/converters/index.d.ts +5 -5
- package/dist/lib/asciidoc/converters/index.js +2 -2
- package/dist/lib/asciidoc/templates/engines/index.d.cts +1 -1
- package/dist/lib/asciidoc/templates/engines/index.d.ts +1 -1
- package/dist/lib/shiki/transformers/index.cjs +120 -79
- package/dist/lib/shiki/transformers/index.d.cts +52 -32
- package/dist/lib/shiki/transformers/index.d.ts +52 -32
- package/dist/lib/shiki/transformers/index.js +76 -4
- package/package.json +1 -1
- package/dist/chunk-DDIUST2Z.js +0 -113
package/README.md
CHANGED
|
@@ -8,39 +8,39 @@ This package will allow you to load Asciidoc files (with either an `.asciidoc` o
|
|
|
8
8
|
- [x] Support for custom templating engines.
|
|
9
9
|
- [x] Support for custom converters for maximum control over the output HTML.
|
|
10
10
|
- [x] Full TypeScript support.
|
|
11
|
+
- [x] Restructure configuration options regarding Shiki transformer integration.
|
|
11
12
|
|
|
12
13
|
## Roadmap
|
|
13
14
|
|
|
14
15
|
- [ ] Async support on custom template and converter class render methods.
|
|
15
16
|
- [ ] Include support for more template engines out of the box.
|
|
16
|
-
- [ ] Restructure configuration options regarding Shiki transformer integration.
|
|
17
17
|
|
|
18
18
|
## Getting Started
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Install the package from `npm`:
|
|
21
21
|
|
|
22
22
|
```console
|
|
23
23
|
npm install @d10f/asciidoc-astro-loader
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
And import the loader function inside your `content.config.ts` file to define a new collection:
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
```ts
|
|
29
|
+
import { defineCollection, z } from "astro:content";
|
|
30
|
+
import { asciidocLoader } from "asciidoc-astro-loader";
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
const blog = defineCollection({
|
|
33
|
+
loader: asciidocLoader({
|
|
34
|
+
base: ".src/content/blog",
|
|
35
|
+
}),
|
|
36
|
+
schema: z.object({
|
|
37
|
+
title: z.string(),
|
|
38
|
+
preamble: z.string().optional(),
|
|
39
|
+
createdAt: z.coerce.date(),
|
|
40
|
+
updatedAt: z.coerce.date().optional(),
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
## Configuration
|
|
@@ -64,15 +64,15 @@ You can specify an object for different light and dark themes, as well:
|
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
66
|
const blog = defineCollection({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
loader: asciidocLoader({
|
|
68
|
+
base: ".src/content/blog",
|
|
69
|
+
syntaxHighlighting: {
|
|
70
|
+
theme: {
|
|
71
|
+
light: "everforest-light",
|
|
72
|
+
dark: "everforest-dark",
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
76
|
});
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -80,16 +80,16 @@ And even provide additional themes. Note that you will have to take care of impl
|
|
|
80
80
|
|
|
81
81
|
```ts
|
|
82
82
|
const blog = defineCollection({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
loader: asciidocLoader({
|
|
84
|
+
base: ".src/content/blog",
|
|
85
|
+
syntaxHighlighting: {
|
|
86
|
+
theme: {
|
|
87
|
+
light: "gruvbox-light-hard",
|
|
88
|
+
dark: "gruvbox-dark-hard",
|
|
89
|
+
dim: "gruvbox-dark-medium"
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
}),
|
|
93
93
|
});
|
|
94
94
|
```
|
|
95
95
|
|
|
@@ -99,26 +99,66 @@ One of the coolest features from Shiki are [transformers](https://shiki.style/gu
|
|
|
99
99
|
|
|
100
100
|
```ts
|
|
101
101
|
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
transformerNotationDiff,
|
|
103
|
+
transformerNotationFocus,
|
|
104
|
+
transformerNotationHighlight,
|
|
105
105
|
} from '@shikijs/transformers';
|
|
106
106
|
|
|
107
107
|
const blog = defineCollection({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
loader: asciidocLoader({
|
|
109
|
+
base: ".src/content/blog",
|
|
110
|
+
syntaxHighlighting: {
|
|
111
|
+
transformers: [
|
|
112
|
+
transformerNotationDiff(),
|
|
113
|
+
transformerNotationHighlight(),
|
|
114
|
+
transformerNotationFocus(),
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
118
|
});
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
If you want to write your own transformers, you can just follow Shiki's documentation and provide them here. However, you might also be interested in performing some conditional logic based on the type of Asciidoc node you're working with. To gain access to the node, define the transformer as a factory function using the `ShikiTransformerFactory` type:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import type { ShikiTransformerFactory } from '../../../types/index.js';
|
|
125
|
+
|
|
126
|
+
type TranformerOptions = {
|
|
127
|
+
cssClasses: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const transformerAsciidocSomething: ShikiTransformerFactory<
|
|
131
|
+
TransformerOptions
|
|
132
|
+
> = ({ cssClasses }) => {
|
|
133
|
+
return (node) => {
|
|
134
|
+
return {
|
|
135
|
+
preprocess() {
|
|
136
|
+
if (node.getAttribute('custom-attribute')) {
|
|
137
|
+
// Maybe you only want to do something based on
|
|
138
|
+
// some custom attribute?
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { transformerAsciidocSomething } from './usr/share/transformers';
|
|
148
|
+
|
|
149
|
+
const blog = defineCollection({
|
|
150
|
+
loader: asciidocLoader({
|
|
151
|
+
base: ".src/content/blog",
|
|
152
|
+
syntaxHighlighting: {
|
|
153
|
+
transformers: [
|
|
154
|
+
transformerAsciidocSomethign({
|
|
155
|
+
cssClasses: 'text-red-500'
|
|
156
|
+
}),
|
|
157
|
+
],
|
|
158
|
+
}
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
```
|
|
122
162
|
|
|
123
163
|
## Custom Templates
|
|
124
164
|
|
|
@@ -126,12 +166,12 @@ A nice feature of Asciidoctor.js is the use of default templates for rendering t
|
|
|
126
166
|
|
|
127
167
|
```ts
|
|
128
168
|
const blog = defineCollection({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
169
|
+
loader: asciidocLoader({
|
|
170
|
+
base: ".src/content/blog",
|
|
171
|
+
document: {
|
|
172
|
+
template: "./usr/share/templates"
|
|
173
|
+
}
|
|
174
|
+
}),
|
|
135
175
|
});
|
|
136
176
|
```
|
|
137
177
|
|
|
@@ -162,7 +202,7 @@ export class PhpEngine extends AbstractEngine implements AsciidocTemplate, Files
|
|
|
162
202
|
/**
|
|
163
203
|
* This method is enforced by the FilesystemTemplate interface.
|
|
164
204
|
*/
|
|
165
|
-
|
|
205
|
+
renderFile(filepath: string, options?: Record<string, unknown>) {}
|
|
166
206
|
}
|
|
167
207
|
```
|
|
168
208
|
|
|
@@ -177,15 +217,15 @@ When your engine is ready, you can import it and pass it to the loader configura
|
|
|
177
217
|
import { PhpEngine } from './usr/share/engines';
|
|
178
218
|
|
|
179
219
|
const blog = defineCollection({
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
loader: asciidocLoader({
|
|
221
|
+
base: ".src/content/blog",
|
|
222
|
+
document: {
|
|
223
|
+
template: "./usr/share/templates",
|
|
224
|
+
templateEngines: [
|
|
225
|
+
New PhpEngine({ name: 'php', extensions: ['php'], root: './usr/share/templates' })
|
|
226
|
+
],
|
|
227
|
+
}
|
|
228
|
+
}),
|
|
189
229
|
});
|
|
190
230
|
```
|
|
191
231
|
|
|
@@ -197,40 +237,40 @@ import { AbstractEngine } from '@d10f/asciidoc-astro-loader/engines';
|
|
|
197
237
|
|
|
198
238
|
import type { AbstractBlock } from 'asciidoctor';
|
|
199
239
|
import type {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
240
|
+
AsciidocTemplate,
|
|
241
|
+
FilesystemTemplate,
|
|
242
|
+
NodeContext
|
|
203
243
|
} from '@d10f/asciidoc-astro-loader';
|
|
204
244
|
|
|
205
245
|
export class PhpEngine extends AbstractEngine implements AsciidocTemplate, FilesystemTemplate {
|
|
206
|
-
|
|
246
|
+
private server: Php;
|
|
207
247
|
|
|
208
248
|
constructor({ name = 'php', extensions = ['php'], root: string }) {
|
|
209
249
|
super(name, extensions);
|
|
210
250
|
this.server = new Php({ docroot: root });
|
|
211
251
|
}
|
|
212
252
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
253
|
+
renderNode(node: AbstractBlock, options?: Record<string, unknown>) {
|
|
254
|
+
const context = node.getNodeName() as NodeContext;
|
|
255
|
+
const content = node.getContent();
|
|
256
|
+
const templateFile = this.templateList.get(context);
|
|
257
|
+
|
|
258
|
+
if (templateFile) {
|
|
259
|
+
const filepath = templateFile.replace(/^.+\/(.+)$/, '$1');
|
|
260
|
+
return this.renderFile(filepath, { content });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
renderFile(filepath: string, options?: Record<string, unknown>): string {
|
|
265
|
+
const request = new Request({
|
|
266
|
+
method: 'POST',
|
|
267
|
+
url: 'http://localhost/' + filepath,
|
|
268
|
+
body: Buffer.from(JSON.stringify(options)),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const response = this.server.handleRequestSync(request);
|
|
272
|
+
return response.body.toString();
|
|
273
|
+
}
|
|
234
274
|
}
|
|
235
275
|
```
|
|
236
276
|
|
|
@@ -250,16 +290,33 @@ Custom converters are refined versions of templates. The main difference is that
|
|
|
250
290
|
|
|
251
291
|
For maximum flexibility, both custom converters and templates can be used at the same time. You can even define templates that aren't designed to be used to render nodes directly, but called directly from within your custom converters. This is why the use of interfaces when defining custom templates is important, to ensure consistency and type-safety.
|
|
252
292
|
|
|
253
|
-
In general, prefer using converters for anything that requires heavy use of logic, and templates for simpler HTML output.
|
|
293
|
+
In general, prefer using converters for anything that requires heavy use of logic or that reads configuration options provided by the user, and templates for simpler HTML output.
|
|
294
|
+
|
|
295
|
+
Custom converters are provided as an array to the `document.converters` option.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
// src/content.config.ts
|
|
299
|
+
|
|
300
|
+
const blog = defineCollection({
|
|
301
|
+
loader: asciidocLoader({
|
|
302
|
+
base: ".src/content/blog",
|
|
303
|
+
document: {
|
|
304
|
+
converters: [
|
|
305
|
+
myCustomConverter({ /* ... */ })
|
|
306
|
+
]
|
|
307
|
+
},
|
|
308
|
+
}),
|
|
309
|
+
});
|
|
310
|
+
```
|
|
254
311
|
|
|
255
312
|
### Registering a custom converter
|
|
256
313
|
|
|
257
|
-
A custom converter is declared as a factory function that returns
|
|
314
|
+
A custom converter is declared as a factory function that accepts a configuration object, and returns an inner function that gets called automatically, receiving the options provided to the loader and the instance of the Shiki highlighter.
|
|
258
315
|
|
|
259
316
|
```ts
|
|
260
317
|
import type { CustomConverterFactoryFn, NodeContext } from '@d10f/asciidoc-astro-loader';
|
|
261
318
|
|
|
262
|
-
export const
|
|
319
|
+
export const myCustomConverter: CustomConverterFactoryFn = ({ nodeContext }) => {
|
|
263
320
|
return (options, highlighter) => {
|
|
264
321
|
return {
|
|
265
322
|
|
|
@@ -284,3 +341,4 @@ export const customConverter: CustomConverterFactoryFn = ({ nodeContext }) => {
|
|
|
284
341
|
}
|
|
285
342
|
```
|
|
286
343
|
|
|
344
|
+
In addition, the `convert` method receives the node that is being processed, as well as an instance of the template engine registry. You can use this to render the HTML from a template as well, combining both to get the best of both worlds.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/lib/utils.ts
|
|
2
|
+
function slugify(text) {
|
|
3
|
+
return text.trim().normalize().toLowerCase().replace(/\s+/g, "-").replace(/[^\w-]+/g, "").replace(/--+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
|
|
4
|
+
}
|
|
5
|
+
function escapeRegexCharacters(str) {
|
|
6
|
+
const re = /[-\\^$*+?.()|\[\]{}]/g;
|
|
7
|
+
return str.replace(re, "\\$&");
|
|
8
|
+
}
|
|
9
|
+
function splitFilenameComponents(filename) {
|
|
10
|
+
const match = filename.match(/^(?<path>.*\/)*(?<name>[^\.]+)\.(?<ext>.*)$/);
|
|
11
|
+
return {
|
|
12
|
+
filepath: match?.groups?.path ?? null,
|
|
13
|
+
filename: match?.groups?.name ?? null,
|
|
14
|
+
extension: match?.groups?.ext ?? null
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
slugify,
|
|
20
|
+
escapeRegexCharacters,
|
|
21
|
+
splitFilenameComponents
|
|
22
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeRegexCharacters
|
|
3
|
+
} from "./chunk-2UGTFP4R.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/shiki/transformers/transformAsciidocCallout.ts
|
|
6
|
+
var transformAsciidocCallout = (options) => {
|
|
7
|
+
return (node) => {
|
|
8
|
+
const lineComments = ["//", "#", ";;"];
|
|
9
|
+
const customLineComment = node.getAttribute("line-comment");
|
|
10
|
+
if (customLineComment) {
|
|
11
|
+
lineComments.push(escapeRegexCharacters(customLineComment));
|
|
12
|
+
}
|
|
13
|
+
const calloutReList = [
|
|
14
|
+
// Handles C-style and similar comments like Perl, Python...
|
|
15
|
+
new RegExp(`\\s+(?:${lineComments.join("|")})((?:\\s+<(\\d+)>)+)`),
|
|
16
|
+
// Handles XML comments
|
|
17
|
+
new RegExp(/((?:\s*<!--(\d+)-->)+)/)
|
|
18
|
+
];
|
|
19
|
+
const commentTokensRe = new RegExp(
|
|
20
|
+
`(?:${lineComments.join("|")}|<!--|-->|[<>])`,
|
|
21
|
+
"g"
|
|
22
|
+
);
|
|
23
|
+
const linesWithCallout = {};
|
|
24
|
+
return {
|
|
25
|
+
preprocess(code) {
|
|
26
|
+
return code.split("\n").map((line, idx) => {
|
|
27
|
+
for (const re of calloutReList) {
|
|
28
|
+
const match = line.match(re);
|
|
29
|
+
if (!match) continue;
|
|
30
|
+
const callouts = match[0].replaceAll(commentTokensRe, "").trim().split(" ");
|
|
31
|
+
linesWithCallout[idx + 1] = callouts;
|
|
32
|
+
return line.replace(re, "");
|
|
33
|
+
}
|
|
34
|
+
return line;
|
|
35
|
+
}).join("\n");
|
|
36
|
+
},
|
|
37
|
+
line(hast, line) {
|
|
38
|
+
const callouts = linesWithCallout[line];
|
|
39
|
+
if (!callouts) return;
|
|
40
|
+
callouts.forEach((calloutId) => {
|
|
41
|
+
hast.properties[`data-callout-${calloutId}`] = "";
|
|
42
|
+
hast.children.push({
|
|
43
|
+
type: "element",
|
|
44
|
+
tagName: "span",
|
|
45
|
+
properties: {
|
|
46
|
+
class: options?.cssClasses ?? "conum",
|
|
47
|
+
style: options?.cssClasses ? "" : "user-select:none; pointer-events:none; opacity:0.5; margin-inline:8px;",
|
|
48
|
+
"data-value": calloutId
|
|
49
|
+
},
|
|
50
|
+
children: [
|
|
51
|
+
{
|
|
52
|
+
type: "text",
|
|
53
|
+
value: calloutId
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
transformAsciidocCallout
|
|
65
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
splitFilenameComponents
|
|
3
|
-
|
|
4
|
-
transformConsoleCodeBlock
|
|
5
|
-
} from "./chunk-DDIUST2Z.js";
|
|
2
|
+
splitFilenameComponents
|
|
3
|
+
} from "./chunk-2UGTFP4R.js";
|
|
6
4
|
|
|
7
5
|
// src/lib/asciidoc/converters/sourceCodeConverter.ts
|
|
8
6
|
import { resolve } from "path";
|
|
9
|
-
var sourceCodeConverter = (
|
|
7
|
+
var sourceCodeConverter = (converterOptions) => {
|
|
8
|
+
const transformers = converterOptions?.transformers;
|
|
9
|
+
const template = converterOptions?.template;
|
|
10
10
|
return (options, highlighter) => {
|
|
11
11
|
return {
|
|
12
12
|
nodeContext: "listing",
|
|
@@ -17,11 +17,9 @@ var sourceCodeConverter = ({ transformers, template }) => {
|
|
|
17
17
|
const output = highlighter.codeToHtml(input, {
|
|
18
18
|
...options.syntaxHighlighting,
|
|
19
19
|
lang,
|
|
20
|
-
transformers: [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
transformConsoleCodeBlock()
|
|
24
|
-
]
|
|
20
|
+
transformers: (transformers ?? []).map((transformer) => {
|
|
21
|
+
return typeof transformer === "function" ? transformer(node) : transformer;
|
|
22
|
+
})
|
|
25
23
|
});
|
|
26
24
|
if (templateEngine && template) {
|
|
27
25
|
const { extension } = splitFilenameComponents(template);
|