jekyll-shiki 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/README.md +394 -12
- data/lib/jekyll-shiki.rb +16 -1
- data/lib/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d817cbf9b2c57a70533d9e027c90b409b330af9baaa1e13ba9665c3ece231e4
|
|
4
|
+
data.tar.gz: b047f687b0632dabbf5fbe0959790c8b97c3acd258a0a86cfc4a340d225fc79d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6bbab53e0d7b252926e406ba2ea6a9cd12d7a0c29da93a737ed872f87ef1ba0969f7a060068bdf4ef8695a6ab1790055c468e91f73613b22dc096e78dc13f8e
|
|
7
|
+
data.tar.gz: e676ed0561ca127973a2bc15beefce6282ba1394c40b91dea1b5ac9218e8bf8b7b9d610027eaadb4ebbbc1a483ec4e5d8e08eebb2ae8021dc3c7e1c3ca58ee5e
|
data/README.md
CHANGED
|
@@ -7,27 +7,409 @@
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Jekyll plugin for [Shiki JS](https://shiki.style/)
|
|
10
|
+
Jekyll plugin for [Shiki JS](https://shiki.style/), that bridges the Ruby-based Jekyll environment with the Node.js Shiki ecosystem.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
> The first build can take some time, depending on the number of code blocks, before the cache is generated. It also requires the path to the `shiki` code used on the Node.js side.
|
|
14
|
-
> This plugin was created for use with the `mmdocs` theme. Performance has not been thoroughly tested, and the documentation is not yet complete.
|
|
12
|
+
## Installation and Usage
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
### Installation
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
The jekyll-shiki gem requires Ruby version `>= 3.3.0` and `Jekyll ~> 4.4`.
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
- **Add the Gem**
|
|
19
|
+
|
|
20
|
+
Add the gem to your project's `Gemfile`:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
group :jekyll_plugins do
|
|
24
|
+
# other jekyll plugins
|
|
25
|
+
gem "jekyll-shiki"
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Alternatively, execute the following command:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
bundle add jekyll-shiki
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **Install Dependencies**
|
|
36
|
+
|
|
37
|
+
Run bundler to install the gem and its requirements (including nokogiri for HTML parsing)
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
bundle install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Configuration
|
|
44
|
+
|
|
45
|
+
The plugin requires a specific configuration block in your Jekyll `_config.yml` file to locate the Node.js Shiki implementation.
|
|
46
|
+
You must define the shiki key. The most critical sub-key is `shiki.file_path`, which points to the JavaScript file responsible for performing the actual highlighting via the Node.js bridge.
|
|
47
|
+
|
|
48
|
+
`_config.yml`
|
|
49
|
+
|
|
50
|
+
```yml
|
|
51
|
+
plugins:
|
|
52
|
+
# other jekyll plugins
|
|
53
|
+
- jekyll-shiki
|
|
54
|
+
# this is required to run shiki code
|
|
55
|
+
shiki:
|
|
56
|
+
# recommended to use .mjs extension to avoid conflict with `type` in `package.json`.
|
|
57
|
+
# Don't use .ts extension
|
|
58
|
+
file_path: shiki/index.mjs
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### First Build and Latency
|
|
62
|
+
|
|
63
|
+
Users should be aware that the first build will experience significant latency.
|
|
64
|
+
|
|
65
|
+
Why the delay occurs:
|
|
66
|
+
|
|
67
|
+
1. **Process Spawning**: The plugin must initialize the Node.js environment for code blocks.
|
|
68
|
+
2. **Cache Generation**: On the first run, every code block must be processed and transformed. Subsequent builds utilize the `Jekyll::Cache` system to skip already-processed blocks.
|
|
69
|
+
3. **Dependency Loading**: Shiki JS loads themes and grammars into memory during its first execution.
|
|
70
|
+
|
|
71
|
+
### Creating shiki highlighter
|
|
72
|
+
|
|
73
|
+
You can find detail at [Shiki Installation & Usage](https://shiki.style/guide/install).
|
|
74
|
+
|
|
75
|
+
#### Install Shiki
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
npm i shiki
|
|
22
79
|
```
|
|
23
80
|
|
|
24
|
-
|
|
81
|
+
#### Example `shiki.file_path` Js file and structure
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import { createHighlighter } from "shiki";
|
|
85
|
+
|
|
86
|
+
// create shiki highlighter
|
|
87
|
+
async function shikiHL(code, lang) {
|
|
88
|
+
// Load shiki bundledLanguages as you want
|
|
89
|
+
/** @type {import("shiki").BundledLanguage} */
|
|
90
|
+
const defaultLangs = [
|
|
91
|
+
"js",
|
|
92
|
+
"ts",
|
|
93
|
+
"sh",
|
|
94
|
+
"json",
|
|
95
|
+
"html",
|
|
96
|
+
"css",
|
|
97
|
+
"ruby",
|
|
98
|
+
"md",
|
|
99
|
+
"yaml",
|
|
100
|
+
"yml",
|
|
101
|
+
"bash",
|
|
102
|
+
];
|
|
103
|
+
const highlighter = await createHighlighter({
|
|
104
|
+
langs: [...defaultLangs, "text"],
|
|
105
|
+
// load shiki bundled themes (light and dark mode)
|
|
106
|
+
themes: ["dark-plus", "light-plus"],
|
|
107
|
+
});
|
|
108
|
+
lang = defaultLangs.includes(lang) ? lang : "text";
|
|
109
|
+
return highlighter.codeToHtml(code, {
|
|
110
|
+
lang: lang,
|
|
111
|
+
// Defined both light and dark mode for correct load from ruby side
|
|
112
|
+
themes: {
|
|
113
|
+
light: "light-plus",
|
|
114
|
+
dark: "dark-plus",
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// The following structure must required to correct load from ruby side
|
|
119
|
+
async function readStdin() {
|
|
120
|
+
const chunks = [];
|
|
121
|
+
|
|
122
|
+
for await (const chunk of process.stdin) {
|
|
123
|
+
chunks.push(chunk);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return chunks.join("");
|
|
127
|
+
}
|
|
128
|
+
if (import.meta.url === new URL(process.argv[1], "file:").href) {
|
|
129
|
+
try {
|
|
130
|
+
const args = await readStdin();
|
|
131
|
+
const { code: c, lang: t } = JSON.parse(args);
|
|
132
|
+
// Replace `shikiHL` to your actual shiki highlighter function name.
|
|
133
|
+
const highlighted = await shikiHL(c, t);
|
|
134
|
+
process.stdout.write(highlighted);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
process.stderr.write(
|
|
137
|
+
`${error instanceof Error ? error.message : String(error)}`,
|
|
138
|
+
);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Wrapper HTML Structure
|
|
145
|
+
|
|
146
|
+
The plugin does not just return the highlighted code from Shiki; it wraps it in a UI-friendly container. The `create_wrapper`method generates a `div` with specific classes and data attributes for styling and functionality.
|
|
147
|
+
|
|
148
|
+
| Element | Class/Attribute | Purpose |
|
|
149
|
+
| ----------- | ------------------------ | ------------------------------------------------------ |
|
|
150
|
+
| Container | `div.shiki_code` | Main wrapper for the code block. |
|
|
151
|
+
| Hook | `data-shiki-highlighter` | Attribute for JS or CSS targeting. |
|
|
152
|
+
| Header | `div.code_head` | Contains the language label and copy button. |
|
|
153
|
+
| Label | `span` | Displays the raw language string (e.g., ruby). |
|
|
154
|
+
| Copy Button | `button[data-copy-btn]` | An empty button intended for clipboard JS integration. |
|
|
155
|
+
| Output | `shiki_highlight` | The actual HTML returned by the Node.js Shiki process. |
|
|
156
|
+
|
|
157
|
+
### Generated HTML Template
|
|
158
|
+
|
|
159
|
+
The structure is defined as a heredoc in Ruby:
|
|
25
160
|
|
|
26
|
-
```
|
|
27
|
-
|
|
161
|
+
```html
|
|
162
|
+
<div class="shiki_code" data-shiki-highlighter>
|
|
163
|
+
<div class="code_head">
|
|
164
|
+
<span>#{lan}</span>
|
|
165
|
+
<button type="button" aria-label="Highlight-#{lang}" data-copy-btn></button>
|
|
166
|
+
</div>
|
|
167
|
+
#{highlighted_code}
|
|
168
|
+
</div>
|
|
28
169
|
```
|
|
29
170
|
|
|
30
|
-
|
|
171
|
+
#### Styles for generated HTML template
|
|
172
|
+
|
|
173
|
+
Create `scss` file `_codeBlock.scss` with the following code.
|
|
174
|
+
|
|
175
|
+
You can edit color or whatever but don't edit `shiki` and `shiki span` in dark mode , that are rely to shiki generated code.
|
|
176
|
+
|
|
177
|
+
```scss
|
|
178
|
+
// cspell:disable
|
|
179
|
+
$iconCopy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E");
|
|
180
|
+
$iconCopied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E");
|
|
181
|
+
|
|
182
|
+
@mixin iconMask($icon, $color) {
|
|
183
|
+
background-color: $color;
|
|
184
|
+
-webkit-mask-image: $icon;
|
|
185
|
+
mask-image: $icon;
|
|
186
|
+
-webkit-mask-position: 50%;
|
|
187
|
+
mask-position: 50%;
|
|
188
|
+
-webkit-mask-repeat: no-repeat;
|
|
189
|
+
mask-repeat: no-repeat;
|
|
190
|
+
-webkit-mask-size: 20px;
|
|
191
|
+
mask-size: 20px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
:root {
|
|
195
|
+
--hr-bg: #ffffff;
|
|
196
|
+
--hr-bg-2: #f3f3f3;
|
|
197
|
+
--hr-lang: #1f1f1f;
|
|
198
|
+
--hr-lang-muted: #616161;
|
|
199
|
+
--hr-border: #e5e5e5;
|
|
200
|
+
--hr-success: #18794e;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.dark,
|
|
204
|
+
*[data-theme="dark"] {
|
|
205
|
+
--hr-bg: #1e1e1e;
|
|
206
|
+
--hr-bg-2: #252526;
|
|
207
|
+
--hr-lang: #d4d4d4;
|
|
208
|
+
--hr-lang-muted: #cccccc;
|
|
209
|
+
--hr-border: #3c3c3c;
|
|
210
|
+
--hr-success: #3dd68c;
|
|
211
|
+
// don't edit
|
|
212
|
+
.shiki,
|
|
213
|
+
.shiki span {
|
|
214
|
+
color: var(--shiki-dark) !important;
|
|
215
|
+
background-color: var(--shiki-dark-bg) !important;
|
|
216
|
+
/* Optional, if you also want font styles */
|
|
217
|
+
font-style: var(--shiki-dark-font-style) !important;
|
|
218
|
+
font-weight: var(--shiki-dark-font-weight) !important;
|
|
219
|
+
text-decoration: var(--shiki-dark-text-decoration) !important;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
div.shiki_code {
|
|
224
|
+
position: relative;
|
|
225
|
+
margin: auto;
|
|
226
|
+
width: 100%;
|
|
227
|
+
background-color: var(--hr-bg);
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
transition: background-color 0.5s;
|
|
230
|
+
margin-top: 7px;
|
|
231
|
+
margin-bottom: 7px;
|
|
232
|
+
border-radius: 8px;
|
|
233
|
+
border: 1px solid var(--hr-border);
|
|
234
|
+
box-shadow: 0 4px 14px color-mix(in srgb, var(--hr-border) 10%, transparent);
|
|
235
|
+
|
|
236
|
+
@media (max-width: 640px) {
|
|
237
|
+
border-radius: 8px;
|
|
238
|
+
margin: 16px 0;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
div.shiki_code > div.code_head {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: row;
|
|
245
|
+
padding: 0.42rem 1rem;
|
|
246
|
+
align-items: center;
|
|
247
|
+
justify-content: space-between;
|
|
248
|
+
background-color: var(--hr-bg-2);
|
|
249
|
+
border-bottom: 1px solid var(--hr-border);
|
|
250
|
+
}
|
|
251
|
+
div.shiki_code > div.code_head > span {
|
|
252
|
+
font-size: 0.84rem;
|
|
253
|
+
font-weight: 400;
|
|
254
|
+
letter-spacing: 0.01em;
|
|
255
|
+
-webkit-user-select: none;
|
|
256
|
+
user-select: none;
|
|
257
|
+
color: var(--hr-lang-muted);
|
|
258
|
+
transition:
|
|
259
|
+
color 0.4s,
|
|
260
|
+
opacity 0.4s;
|
|
261
|
+
}
|
|
262
|
+
div.shiki_code > div.code_head > button {
|
|
263
|
+
position: relative;
|
|
264
|
+
border: none;
|
|
265
|
+
border-radius: 4px;
|
|
266
|
+
width: 20px;
|
|
267
|
+
height: 20px;
|
|
268
|
+
background-color: transparent;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
transition:
|
|
271
|
+
border-color 0.25s,
|
|
272
|
+
background-color 0.25s,
|
|
273
|
+
opacity 0.25s;
|
|
274
|
+
|
|
275
|
+
&:hover {
|
|
276
|
+
background-color: color-mix(in srgb, var(--hr-lang-muted) 10%, transparent);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
&::before {
|
|
280
|
+
content: "";
|
|
281
|
+
position: absolute;
|
|
282
|
+
inset: 0;
|
|
283
|
+
@include iconMask($iconCopy, var(--hr-lang));
|
|
284
|
+
transition: background-color 0.25s;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
div.shiki_code > div.code_head > button.copied,
|
|
288
|
+
div.shiki_code > div.code_head > button:hover.copied {
|
|
289
|
+
&::before {
|
|
290
|
+
@include iconMask($iconCopied, var(--hr-success));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
div.shiki_code > pre.shiki {
|
|
295
|
+
position: relative;
|
|
296
|
+
z-index: 1;
|
|
297
|
+
margin: 0;
|
|
298
|
+
padding: 12px 0;
|
|
299
|
+
background: transparent;
|
|
300
|
+
overflow-x: auto;
|
|
301
|
+
scrollbar-gutter: stable;
|
|
302
|
+
direction: ltr;
|
|
303
|
+
text-align: left;
|
|
304
|
+
white-space: pre;
|
|
305
|
+
word-spacing: normal;
|
|
306
|
+
word-break: normal;
|
|
307
|
+
word-wrap: normal;
|
|
308
|
+
-moz-tab-size: 4;
|
|
309
|
+
-o-tab-size: 4;
|
|
310
|
+
tab-size: 4;
|
|
311
|
+
-webkit-hyphens: none;
|
|
312
|
+
-moz-hyphens: none;
|
|
313
|
+
-ms-hyphens: none;
|
|
314
|
+
hyphens: none;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.shiki_code > pre.shiki > code {
|
|
318
|
+
display: block;
|
|
319
|
+
padding: 0 24px;
|
|
320
|
+
width: fit-content;
|
|
321
|
+
min-width: 100%;
|
|
322
|
+
line-height: 1.65;
|
|
323
|
+
font-size: 0.95rem;
|
|
324
|
+
font-family: var(--font-mono);
|
|
325
|
+
color: var(--hr-lang);
|
|
326
|
+
transition: color 0.5s;
|
|
327
|
+
direction: ltr;
|
|
328
|
+
text-align: left;
|
|
329
|
+
white-space: pre;
|
|
330
|
+
word-spacing: normal;
|
|
331
|
+
word-break: normal;
|
|
332
|
+
word-wrap: normal;
|
|
333
|
+
-moz-tab-size: 4;
|
|
334
|
+
-o-tab-size: 4;
|
|
335
|
+
tab-size: 4;
|
|
336
|
+
-webkit-hyphens: none;
|
|
337
|
+
-moz-hyphens: none;
|
|
338
|
+
-ms-hyphens: none;
|
|
339
|
+
hyphens: none;
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Use in your `sass` entry (like `main.scss`) , make sure your actual `sass` entry and `_codeBlock.scss` are same directory.
|
|
344
|
+
|
|
345
|
+
```scss
|
|
346
|
+
---
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
@use "codeBlock";
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### Js for data-copy-btn
|
|
353
|
+
|
|
354
|
+
You can use following Js code for `data-copy-btn`.
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
function codeBlockCopy() {
|
|
358
|
+
const codeBlocks = document.querySelectorAll("[data-shiki-highlighter]");
|
|
359
|
+
if (!codeBlocks.length) return;
|
|
360
|
+
|
|
361
|
+
function fallbackCopy(text) {
|
|
362
|
+
const textarea = $.document.createElement("textarea");
|
|
363
|
+
textarea.value = text;
|
|
364
|
+
textarea.setAttribute("readonly", "");
|
|
365
|
+
textarea.style.position = "fixed";
|
|
366
|
+
textarea.style.opacity = "0";
|
|
367
|
+
document.body.appendChild(textarea);
|
|
368
|
+
textarea.select();
|
|
369
|
+
|
|
370
|
+
let success = false;
|
|
371
|
+
try {
|
|
372
|
+
success = document.execCommand("copy");
|
|
373
|
+
} catch (e) {
|
|
374
|
+
success = false;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
$.document.body.removeChild(textarea);
|
|
378
|
+
return success;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function copyText(text) {
|
|
382
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
383
|
+
try {
|
|
384
|
+
await navigator.clipboard.writeText(text);
|
|
385
|
+
return true;
|
|
386
|
+
} catch (e) {
|
|
387
|
+
return fallbackCopy(text);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return fallbackCopy(text);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
codeBlocks.forEach((block) => {
|
|
394
|
+
const copyBtn = block.querySelector("[data-copy-btn]");
|
|
395
|
+
const code = block.querySelector("pre code");
|
|
396
|
+
if (!copyBtn || !code) return;
|
|
397
|
+
|
|
398
|
+
copyBtn.addEventListener("click", async () => {
|
|
399
|
+
const text = code.textContent;
|
|
400
|
+
const success = await copyText(text);
|
|
401
|
+
if (success) {
|
|
402
|
+
copyBtn.classList.add("copied");
|
|
403
|
+
|
|
404
|
+
setTimeout(() => {
|
|
405
|
+
copyBtn.classList.remove("copied");
|
|
406
|
+
}, 1000);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
codeBlockCopy();
|
|
412
|
+
```
|
|
31
413
|
|
|
32
414
|
## Contributing
|
|
33
415
|
|
data/lib/jekyll-shiki.rb
CHANGED
|
@@ -11,8 +11,11 @@ require "digest"
|
|
|
11
11
|
require_relative "version"
|
|
12
12
|
|
|
13
13
|
module Jekyll
|
|
14
|
-
#
|
|
14
|
+
# Shiki is a small Jekyll plugin that replaces standard markdown code blocks
|
|
15
|
+
# with syntax-highlighted HTML generated by a Shiki bundle executed via Node.
|
|
15
16
|
module Shiki
|
|
17
|
+
# Resolve the configured Shiki bundle path from the Jekyll site config.
|
|
18
|
+
# Raises when the required `shiki.file_path` value is missing.
|
|
16
19
|
def self.resolve_shiki_bundle_path(site)
|
|
17
20
|
shiki_config = site.config["shiki"]
|
|
18
21
|
raise "Shiki highlight failed: Shiki config not found in jekyll config" unless shiki_config
|
|
@@ -23,6 +26,8 @@ module Jekyll
|
|
|
23
26
|
File.join(site.source, bundle_path)
|
|
24
27
|
end
|
|
25
28
|
|
|
29
|
+
# Highlight the given code using the Shiki bundle, caching results per code/lang.
|
|
30
|
+
# The method uses Open3 to execute the Node script and returns highlighted HTML.
|
|
26
31
|
def self.shiki_highlight(code, lang, site)
|
|
27
32
|
script_path = resolve_shiki_bundle_path(site)
|
|
28
33
|
|
|
@@ -36,6 +41,8 @@ module Jekyll
|
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
|
|
44
|
+
# Build the final markup wrapper around Shiki highlighted HTML.
|
|
45
|
+
# This includes the language label and a copy button container.
|
|
39
46
|
def self.create_wrapper(lan, code, site)
|
|
40
47
|
lang = lan.capitalize
|
|
41
48
|
highlighted_code = shiki_highlight(code, lan, site)
|
|
@@ -50,6 +57,8 @@ module Jekyll
|
|
|
50
57
|
HTML
|
|
51
58
|
end
|
|
52
59
|
|
|
60
|
+
# Replace a matching <pre><code class="language-..."></code></pre> node
|
|
61
|
+
# with the Shiki wrapper generated from the raw code text.
|
|
53
62
|
def self.replace_elements(node, site)
|
|
54
63
|
code_el = node.at_css('> code[class^="language-"]')
|
|
55
64
|
return unless code_el
|
|
@@ -63,10 +72,14 @@ module Jekyll
|
|
|
63
72
|
node.replace(fragment)
|
|
64
73
|
end
|
|
65
74
|
|
|
75
|
+
# Detect whether the provided HTML is a full document or just a fragment.
|
|
76
|
+
# This controls whether Nokogiri should parse it as an HTML document or fragment.
|
|
66
77
|
def self.full_document?(html_content)
|
|
67
78
|
html_content.match?(/\A\s*(<!doctype\s+html|<html\b)/i)
|
|
68
79
|
end
|
|
69
80
|
|
|
81
|
+
# Transform the supplied HTML content by highlighting all code blocks.
|
|
82
|
+
# It preserves non-code markup and rewrites only language-specific code blocks.
|
|
70
83
|
def self.transform_html(html_content, site)
|
|
71
84
|
doc = if full_document?(html_content)
|
|
72
85
|
Nokogiri::HTML.parse(html_content)
|
|
@@ -82,12 +95,14 @@ module Jekyll
|
|
|
82
95
|
end
|
|
83
96
|
end
|
|
84
97
|
|
|
98
|
+
# Register Jekyll hook for rendered pages so HTML outputs are rewritten after page rendering.
|
|
85
99
|
Jekyll::Hooks.register :pages, :post_render do |page|
|
|
86
100
|
next unless page.output_ext == ".html"
|
|
87
101
|
|
|
88
102
|
page.output = Jekyll::Shiki.transform_html(page.output, page.site)
|
|
89
103
|
end
|
|
90
104
|
|
|
105
|
+
# Register Jekyll hook for rendered documents so HTML outputs are rewritten after document rendering.
|
|
91
106
|
Jekyll::Hooks.register :documents, :post_render do |document|
|
|
92
107
|
next unless document.output_ext == ".html"
|
|
93
108
|
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-shiki
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- phothinmg
|
|
@@ -47,12 +47,12 @@ files:
|
|
|
47
47
|
- README.md
|
|
48
48
|
- lib/jekyll-shiki.rb
|
|
49
49
|
- lib/version.rb
|
|
50
|
-
homepage: https://
|
|
50
|
+
homepage: https://phothinmg.github.io/jekyll-shiki/
|
|
51
51
|
licenses:
|
|
52
52
|
- MIT
|
|
53
53
|
metadata:
|
|
54
|
-
source_code_uri: https://github.com/
|
|
55
|
-
changelog_uri: https://github.com/
|
|
54
|
+
source_code_uri: https://github.com/phothinmg/jekyll-shiki
|
|
55
|
+
changelog_uri: https://github.com/phothinmg/jekyll-shiki/blob/main/CHANGELOG.md
|
|
56
56
|
rubygems_mfa_required: 'true'
|
|
57
57
|
rdoc_options: []
|
|
58
58
|
require_paths:
|
|
@@ -70,5 +70,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
70
|
requirements: []
|
|
71
71
|
rubygems_version: 4.0.15
|
|
72
72
|
specification_version: 4
|
|
73
|
-
summary: Jekyll plugin for Shiki Js
|
|
73
|
+
summary: Jekyll plugin for Shiki Js
|
|
74
74
|
test_files: []
|