@riebeckite/plugin-code-enhance 0.0.31 → 0.0.33

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 CHANGED
@@ -35,6 +35,16 @@ export default defineConfig({
35
35
  });
36
36
  ```
37
37
 
38
+ ## Live example
39
+
40
+ The block below uses a filename header, line numbers, and a highlighted line.
41
+
42
+ ~~~ts title="greeting.ts" {2}
43
+ export function greet(name: string) {
44
+ return `Hello, ${name}`;
45
+ }
46
+ ~~~
47
+
38
48
  ## Features
39
49
 
40
50
  - Syntax highlighting via Shiki (rehype-pretty-code)
package/README_ja.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- Generated from docs/docs/plugins/code-enhance.ja.md. Edit the canonical documentation in docs/docs/plugins and run `pnpm docs:sync`. -->
4
4
 
5
- Shiki によるシンタックスハイライトへ、コピー、折り返し、折りたたみなどの操作を加えるプラグインです。
5
+ Shiki のシンタックスハイライトに、ファイル名ヘッダーとコピー、折り返し、折りたたみの操作を加えたコードブロックを提供するプラグインです。
6
6
 
7
7
  [English](./README.md)
8
8
 
@@ -20,6 +20,16 @@ export default defineConfig({
20
20
 
21
21
  `codeEnhance()` は `rehype-pretty-code` を使ってコードブロックを処理し、ファイル名と操作ボタンを持つ `.rr-code` 要素に整えます。ボタンの操作はクライアントエントリーが担当します。
22
22
 
23
+ ## 実行例
24
+
25
+ 次のブロックでは、ファイル名ヘッダー、行番号、行ハイライトを同時に使っています。
26
+
27
+ ~~~ts title="greeting.ts" {2}
28
+ export function greet(name: string) {
29
+ return `Hello, ${name}`;
30
+ }
31
+ ~~~
32
+
23
33
  ## 表示と操作
24
34
 
25
35
  - Shiki のテーマによる色付け、行番号、メタデータによる行・文字の強調
@@ -29,7 +39,7 @@ export default defineConfig({
29
39
  - `bash`、`console`、`sh` などのシェル言語の行に `$` を付ける
30
40
  - キーボードで内容を確認できるよう、`<pre>` をフォーカス可能にする
31
41
 
32
- Theme は `rr-code` の root に `--rr-code-syntax` を指定すると、ハイライト済みコードの
42
+ テーマは `rr-code` のルート要素に `--rr-code-syntax` を指定すると、ハイライト済みコードの
33
43
  子要素を選択せずに構文色を指定できます。
34
44
 
35
45
  ## オプション
@@ -51,12 +61,12 @@ Theme は `rr-code` の root に `--rr-code-syntax` を指定すると、ハイ
51
61
 
52
62
  ## クライアント側の初期化
53
63
 
54
- `codeEnhance()` は `copyLabel` と `copiedLabel` をクライアントエントリーへ渡します。`initCodeEnhance(options?)` はコピー、折り返し、折りたたみボタンのために document 全体のクリックハンドラーを登録します。直接呼び出す場合も同じ二つの文言を指定できます。
64
+ `codeEnhance()` は `copyLabel` と `copiedLabel` をクライアントエントリーへ渡します。`initCodeEnhance(options?)` はコピー、折り返し、折りたたみボタンのためにドキュメント全体のクリックハンドラーを登録します。直接呼び出す場合も同じ 2 つの文言を指定できます。
55
65
 
56
66
  ## 公開 API
57
67
 
58
68
  - `codeEnhance(options?)`:プラグインファクトリ
59
- - `rehypeCodeEnhance(options?)`:Rehype 変換
69
+ - `rehypeCodeEnhance(options?)`:rehype 変換
60
70
  - `initCodeEnhance(options?)`:ブラウザ初期化関数
61
71
  - `DEFAULT_COPY_LABEL`、`DEFAULT_COPIED_LABEL`:既定のコピー文言
62
72
  - `CodeEnhanceOptions`、`CodeEnhanceClientOptions`、`CodeEnhanceTheme`:型
package/dist/client.js CHANGED
@@ -4,12 +4,13 @@ var DEFAULT_COPIED_LABEL = "Copied";
4
4
  function initCodeEnhance(options = {}) {
5
5
  const copyLabel = options.copyLabel ?? DEFAULT_COPY_LABEL;
6
6
  const copiedLabel = options.copiedLabel ?? DEFAULT_COPIED_LABEL;
7
- document.addEventListener("click", async (event) => {
7
+ const timers = /* @__PURE__ */ new Set();
8
+ const onClick = async (event) => {
8
9
  const target = event.target;
9
10
  if (!(target instanceof Element)) return;
10
11
  const copyButton = target.closest("[data-code-copy]");
11
12
  if (copyButton) {
12
- await copyCode(copyButton, copyLabel, copiedLabel);
13
+ await copyCode(copyButton, copyLabel, copiedLabel, timers);
13
14
  return;
14
15
  }
15
16
  const wrapButton = target.closest(
@@ -23,16 +24,26 @@ function initCodeEnhance(options = {}) {
23
24
  "[data-code-collapse-toggle]"
24
25
  );
25
26
  if (collapseButton) toggleCollapse(collapseButton);
26
- });
27
+ };
28
+ document.addEventListener("click", onClick);
29
+ return () => {
30
+ document.removeEventListener("click", onClick);
31
+ for (const timer of timers) window.clearTimeout(timer);
32
+ timers.clear();
33
+ };
27
34
  }
28
- async function copyCode(button, copyLabel, copiedLabel) {
35
+ async function copyCode(button, copyLabel, copiedLabel, timers) {
29
36
  const code = getCodeText(button);
30
37
  if (!code) return;
31
38
  await navigator.clipboard.writeText(code);
39
+ button.dataset.codeCopyState = "success";
32
40
  button.textContent = copiedLabel;
33
- window.setTimeout(() => {
41
+ const timer = window.setTimeout(() => {
42
+ timers.delete(timer);
43
+ delete button.dataset.codeCopyState;
34
44
  button.textContent = copyLabel;
35
45
  }, 1500);
46
+ timers.add(timer);
36
47
  }
37
48
  function getCodeText(button) {
38
49
  const figure = button.closest(".rr-code");
package/dist/index.js CHANGED
@@ -11,12 +11,13 @@ var DEFAULT_COPIED_LABEL = "Copied";
11
11
  function initCodeEnhance(options = {}) {
12
12
  const copyLabel = options.copyLabel ?? DEFAULT_COPY_LABEL;
13
13
  const copiedLabel = options.copiedLabel ?? DEFAULT_COPIED_LABEL;
14
- document.addEventListener("click", async (event) => {
14
+ const timers = /* @__PURE__ */ new Set();
15
+ const onClick = async (event) => {
15
16
  const target = event.target;
16
17
  if (!(target instanceof Element)) return;
17
18
  const copyButton = target.closest("[data-code-copy]");
18
19
  if (copyButton) {
19
- await copyCode(copyButton, copyLabel, copiedLabel);
20
+ await copyCode(copyButton, copyLabel, copiedLabel, timers);
20
21
  return;
21
22
  }
22
23
  const wrapButton = target.closest(
@@ -30,16 +31,26 @@ function initCodeEnhance(options = {}) {
30
31
  "[data-code-collapse-toggle]"
31
32
  );
32
33
  if (collapseButton) toggleCollapse(collapseButton);
33
- });
34
+ };
35
+ document.addEventListener("click", onClick);
36
+ return () => {
37
+ document.removeEventListener("click", onClick);
38
+ for (const timer of timers) window.clearTimeout(timer);
39
+ timers.clear();
40
+ };
34
41
  }
35
- async function copyCode(button, copyLabel, copiedLabel) {
42
+ async function copyCode(button, copyLabel, copiedLabel, timers) {
36
43
  const code = getCodeText(button);
37
44
  if (!code) return;
38
45
  await navigator.clipboard.writeText(code);
46
+ button.dataset.codeCopyState = "success";
39
47
  button.textContent = copiedLabel;
40
- window.setTimeout(() => {
48
+ const timer = window.setTimeout(() => {
49
+ timers.delete(timer);
50
+ delete button.dataset.codeCopyState;
41
51
  button.textContent = copyLabel;
42
52
  }, 1500);
53
+ timers.add(timer);
43
54
  }
44
55
  function getCodeText(button) {
45
56
  const figure = button.closest(".rr-code");
@@ -182,6 +193,7 @@ function restoreCodeMeta(tree) {
182
193
  });
183
194
  }
184
195
  var LANGUAGE_CLASS_PREFIX = "language-";
196
+ var SOURCE_FILE_TITLE_PREFIX = "source-file:";
185
197
  function normalizeCodeLanguages(tree) {
186
198
  visitElements(tree, (node) => {
187
199
  if (node.tagName !== "code") return;
@@ -229,12 +241,16 @@ function enhancePrettyCodeFigures(tree, options) {
229
241
  const code = findDirectChild(pre, "code");
230
242
  const titleNode = findTitleNode(node);
231
243
  const title = titleNode ? getTextContent(titleNode) : null;
244
+ const sourceFileTitle = title?.startsWith(SOURCE_FILE_TITLE_PREFIX) ? title : null;
245
+ const isSourceFile = sourceFileTitle !== null;
246
+ const displayTitle = sourceFileTitle ? sourceFileTitle.slice(SOURCE_FILE_TITLE_PREFIX.length) : title;
232
247
  const language = getLanguage(pre, code);
233
248
  const isShell = isTerminalLanguage(language);
234
249
  node.properties = {
235
250
  ...node.properties,
236
251
  className: mergeClassName(node.properties?.className, "rr-code"),
237
- dataLanguage: language
252
+ dataLanguage: language,
253
+ ...isSourceFile ? { dataSourceFile: "true" } : {}
238
254
  };
239
255
  pre.properties = {
240
256
  ...pre.properties,
@@ -250,6 +266,7 @@ function enhancePrettyCodeFigures(tree, options) {
250
266
  className: mergeClassName(code.properties?.className, "rr-code__code")
251
267
  };
252
268
  if (options.lineNumbers) markLineNumbers(code);
269
+ if (isSourceFile) markSourceFileLines(code);
253
270
  if (options.diffHighlight !== false && isDiffLanguage(language))
254
271
  markDiffLines(code);
255
272
  if (options.commandPrompt !== false && isShell) markCommandPrompts(code);
@@ -258,7 +275,7 @@ function enhancePrettyCodeFigures(tree, options) {
258
275
  const body = element("div", { className: "rr-code__body" }, bodyChildren);
259
276
  node.children = [
260
277
  buildHeader({
261
- title: options.filename === false ? null : title,
278
+ title: options.filename === false ? null : displayTitle,
262
279
  language,
263
280
  copyButton: options.copyButton !== false,
264
281
  wrapToggle: options.wrapToggle !== false,
@@ -334,6 +351,15 @@ function markLineNumbers(code) {
334
351
  };
335
352
  }
336
353
  }
354
+ function markSourceFileLines(code) {
355
+ for (const [index, line] of getCodeLines(code).entries()) {
356
+ line.properties = {
357
+ ...line.properties,
358
+ id: `L${index + 1}`,
359
+ tabindex: "-1"
360
+ };
361
+ }
362
+ }
337
363
  function markDiffLines(code) {
338
364
  for (const line of getCodeLines(code)) {
339
365
  const content = getTextContent(line);
@@ -1,6 +1,5 @@
1
1
  import type { ElementNode, HastNode, TextNode } from "./types.js";
2
2
  export declare function visitElements(node: HastNode, visitor: (node: ElementNode, parent?: ElementNode, index?: number) => void, parent?: ElementNode, index?: number): void;
3
- export declare function isElementNode(node: HastNode): node is ElementNode;
4
3
  export declare function text(value: string): TextNode;
5
4
  export declare function element(tagName: string, properties?: Record<string, unknown>, children?: HastNode[]): ElementNode;
6
5
  export declare function getStringProperty(node: ElementNode, key: string): string | null;
@@ -1,4 +1,4 @@
1
1
  import type { CodeEnhanceClientOptions } from "./types.js";
2
2
  export declare const DEFAULT_COPY_LABEL = "Copy";
3
3
  export declare const DEFAULT_COPIED_LABEL = "Copied";
4
- export declare function initCodeEnhance(options?: CodeEnhanceClientOptions): void;
4
+ export declare function initCodeEnhance(options?: CodeEnhanceClientOptions): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-code-enhance",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,9 +21,9 @@
21
21
  "./style.css": "./style.css"
22
22
  },
23
23
  "dependencies": {
24
+ "@riebeckite/core": "0.0.33",
24
25
  "rehype-pretty-code": "^0.14.1",
25
- "shiki": "^3.13.0",
26
- "@riebeckite/core": "0.0.31"
26
+ "shiki": "^3.13.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "tsx": "^4.22.4"
package/style.css CHANGED
@@ -1,6 +1,9 @@
1
1
  .rr-code {
2
- --rr-code-paper: var(--rb-color-ink, #000000);
3
- --rr-code-ink: var(--rb-color-paper, #ffffff);
2
+ --rr-code-paper: var(
3
+ --rb-color-code-background,
4
+ var(--rb-color-ink, #000000)
5
+ );
6
+ --rr-code-ink: var(--rb-color-code-ink, var(--rb-color-ink, #ffffff));
4
7
  --rr-code-muted: color-mix(in srgb, var(--rr-code-ink) 56%, transparent);
5
8
  --rr-code-rule: color-mix(in srgb, var(--rr-code-ink) 18%, transparent);
6
9
  --rr-code-accent: var(--rb-color-accent, #686868);
@@ -53,8 +56,12 @@
53
56
 
54
57
  .rr-code__action:hover,
55
58
  .rr-code__action:focus-visible {
56
- border-color: var(--rr-code-accent);
57
- color: var(--rr-code-accent);
59
+ border-color: color-mix(in srgb, var(--rr-code-ink) 42%, transparent);
60
+ background: var(
61
+ --rb-code-action-hover-background,
62
+ color-mix(in srgb, var(--rr-code-ink) 14%, var(--rb-color-paper, #ffffff))
63
+ );
64
+ color: var(--rr-code-ink);
58
65
  outline: none;
59
66
  }
60
67
 
@@ -133,15 +140,3 @@
133
140
  .rr-code__pre--wrap .rr-code__line {
134
141
  white-space: pre-wrap;
135
142
  }
136
-
137
- :is(:root, .rb-theme-root)[data-theme="dark"] .rr-code {
138
- --rr-code-paper: var(--rb-color-paper, #000000);
139
- --rr-code-ink: var(--rb-color-ink, #ffffff);
140
- }
141
-
142
- @media (prefers-color-scheme: dark) {
143
- :is(:root, .rb-theme-root):not([data-theme]) .rr-code {
144
- --rr-code-paper: var(--rb-color-paper, #000000);
145
- --rr-code-ink: var(--rb-color-ink, #ffffff);
146
- }
147
- }