@riebeckite/plugin-qr-code 0.0.18 → 0.0.20

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
@@ -46,20 +46,23 @@ code block untouched and reports a `@riebeckite/plugin-qr-code` diagnostic.
46
46
  Each block becomes:
47
47
 
48
48
  ```html
49
- <figure class="rb-qr" data-qr="rendered" data-qr-level="M" data-qr-margin="1">
50
- <figcaption class="rb-qr__caption">…</figcaption>
51
- <div class="rb-qr__canvas" role="img"><svg>…</svg></div>
52
- <details class="rb-qr__fallback">
53
- <summary>QR source</summary>
54
- <pre><code>…</code></pre>
55
- </details>
49
+ <figure class="rb-qr" data-qr="rendered" data-qr-level="M" data-qr-margin="1" style="--rb-qr-size:160px">
50
+ <div class="rb-qr__canvas" role="img" aria-label="QR code"><svg>…</svg></div>
51
+ <figcaption class="rb-qr__caption">
52
+ <span class="rb-qr__caption-text">…</span>
53
+ <a class="rb-qr__source" href="…">…</a>
54
+ </figcaption>
56
55
  </figure>
57
56
  ```
58
57
 
59
58
  - `data-qr` is `"rendered"` on success and `"error"` when the encoder is
60
59
  unavailable or the block cannot be encoded.
61
- - The raw text stays available in `details.rb-qr__fallback` (unless `fallback`
62
- is `false`), so an error never loses content.
60
+ - The encoded payload is always rendered as plain text in
61
+ `figcaption.rb-qr__caption`, so the value stays readable and copyable even
62
+ without scanning the code. An `http:`, `https:`, `mailto:`, or `tel:` payload
63
+ becomes an `<a class="rb-qr__source">`; anything else stays a `<span>`.
64
+ - The card width follows the `width` option instead of the payload, so a long
65
+ URL wraps rather than stretching the figure.
63
66
  - Errors and empty blocks report a snippet diagnostic with
64
67
  `source: "@riebeckite/plugin-qr-code"`.
65
68
 
@@ -78,13 +81,11 @@ virtual store.
78
81
  | `level` | `"L" \| "M" \| "Q" \| "H"` | `"M"` | Error-correction level |
79
82
  | `margin` | `number` | `1` | Quiet-zone size in modules |
80
83
  | `width` | `number` | `160` | Rendered size in pixels |
81
- | `size` | `number` | — | Alias of `width` |
82
84
  | `dark` | `string` | `"#000000"` | Dark-module colour |
83
85
  | `light` | `string` | `"#ffffff"` | Light-module colour |
84
86
  | `caption` | `boolean` | `true` | Show a caption from the title / `# caption:` line |
85
87
  | `className` | `string` | `"rb-qr"` | Figure CSS class |
86
88
  | `language` | `string` | `"qr"` | Fence language to intercept |
87
- | `fallback` | `boolean` | `true` | Keep the raw text in `<details>` |
88
89
 
89
90
  The caption comes from the code-block `title` (code meta) or a leading
90
91
  `# caption: …` line. A leading caption line is removed from the encoded body.
@@ -100,5 +101,4 @@ The caption comes from the code-block `title` (code meta) or a leading
100
101
 
101
102
  ## See also
102
103
 
103
- - [Plugin guide](../../../docs/docs/reference/plugin-api.en.md)
104
-
104
+ - [Plugin guide](../../../docs/docs/reference/plugin-api.md)
package/README_ja.md CHANGED
@@ -42,18 +42,18 @@ https://example.com/
42
42
  各ブロックは次の形になります。
43
43
 
44
44
  ```html
45
- <figure class="rb-qr" data-qr="rendered" data-qr-level="M" data-qr-margin="1">
46
- <figcaption class="rb-qr__caption">…</figcaption>
47
- <div class="rb-qr__canvas" role="img"><svg>…</svg></div>
48
- <details class="rb-qr__fallback">
49
- <summary>QR source</summary>
50
- <pre><code>…</code></pre>
51
- </details>
45
+ <figure class="rb-qr" data-qr="rendered" data-qr-level="M" data-qr-margin="1" style="--rb-qr-size:160px">
46
+ <div class="rb-qr__canvas" role="img" aria-label="QR code"><svg>…</svg></div>
47
+ <figcaption class="rb-qr__caption">
48
+ <span class="rb-qr__caption-text">…</span>
49
+ <a class="rb-qr__source" href="…">…</a>
50
+ </figcaption>
52
51
  </figure>
53
52
  ```
54
53
 
55
54
  - `data-qr` は成功時に `"rendered"`、エンコーダを読み込めない場合やエンコードに失敗した場合に `"error"` になります。
56
- - 元のテキストは `details.rb-qr__fallback` に残ります(`fallback: false` の場合は除く)。エラー時も内容が失われることはありません。
55
+ - エンコード対象の値は `figcaption.rb-qr__caption` に常にテキストとして表示されるため、コードを読み取らなくても内容を確認・コピーできます。`http:`・`https:`・`mailto:`・`tel:` の値は `<a class="rb-qr__source">` になり、それ以外は `<span>` のままです。
56
+ - カードの幅はペイロードではなく `width` オプションに従うため、長い URL はカードを押し広げず折り返します。
57
57
  - エラーと空ブロックは `source: "@riebeckite/plugin-qr-code"` の診断として報告されます。
58
58
 
59
59
  ### エンコーダの読み込み
@@ -67,13 +67,11 @@ QR エンコーダ(`qrcode`)はビルド時に動的インポートするた
67
67
  | `level` | `"L" \| "M" \| "Q" \| "H"` | `"M"` | 誤り訂正レベル |
68
68
  | `margin` | `number` | `1` | 余白(モジュール数) |
69
69
  | `width` | `number` | `160` | 表示サイズ(px) |
70
- | `size` | `number` | — | `width` の別名 |
71
70
  | `dark` | `string` | `"#000000"` | 暗モジュールの色 |
72
71
  | `light` | `string` | `"#ffffff"` | 明モジュールの色 |
73
72
  | `caption` | `boolean` | `true` | タイトルまたは `# caption:` 行をキャプションとして表示する |
74
73
  | `className` | `string` | `"rb-qr"` | figure の CSS クラス |
75
74
  | `language` | `string` | `"qr"` | 対象とするフェンス言語 |
76
- | `fallback` | `boolean` | `true` | 元のテキストを `<details>` に残す |
77
75
 
78
76
  キャプションはコードブロックの `title`(コードメタ)または先頭の `# caption: …` 行から取得します。先頭のキャプション行はエンコード対象から取り除きます。
79
77
 
@@ -87,5 +85,4 @@ QR エンコーダ(`qrcode`)はビルド時に動的インポートするた
87
85
 
88
86
  ## 関連資料
89
87
 
90
- - [プラグインシステム](../../../docs/docs/reference/plugin-api.md)
91
-
88
+ - [プラグインシステム](../../../docs/docs/reference/plugin-api.ja.md)
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ var __export = (target, all) => {
16
16
  // src/options.ts
17
17
  function resolveQrCodeOptions(options = {}) {
18
18
  const width = normalizePositiveInteger(
19
- options.width ?? options.size,
19
+ options.width,
20
20
  DEFAULT_QR_CODE_OPTIONS.width
21
21
  );
22
22
  const margin = normalizeNonNegativeInteger(
@@ -35,8 +35,7 @@ function resolveQrCodeOptions(options = {}) {
35
35
  light,
36
36
  caption: options.caption ?? DEFAULT_QR_CODE_OPTIONS.caption,
37
37
  className,
38
- language,
39
- fallback: options.fallback ?? DEFAULT_QR_CODE_OPTIONS.fallback
38
+ language
40
39
  };
41
40
  }
42
41
  function normalizeLevel(value) {
@@ -74,8 +73,7 @@ var init_options = __esm({
74
73
  light: "#ffffff",
75
74
  caption: true,
76
75
  className: "rb-qr",
77
- language: "qr",
78
- fallback: true
76
+ language: "qr"
79
77
  };
80
78
  }
81
79
  });
@@ -330,17 +328,24 @@ async function replaceQrBlock(parent, index, pre, file, options) {
330
328
  }
331
329
  }
332
330
  function buildRenderedFigure(source, svg, caption, options) {
331
+ return buildFigure(source, caption, options, "rendered", [raw(svg)]);
332
+ }
333
+ function buildErrorFigure(source, caption, options, message, file) {
334
+ reportDiagnostic(file, `QR code could not be rendered: ${message}`, "render");
335
+ return buildFigure(source, caption, options, "error", []);
336
+ }
337
+ function buildFigure(source, caption, options, state, canvasChildren) {
333
338
  const labelId = `${figureId(source, options)}-caption`;
334
339
  return element(
335
340
  "figure",
336
341
  {
337
342
  className: options.className,
338
- dataQr: "rendered",
343
+ dataQr: state,
339
344
  dataQrLevel: options.level,
340
- dataQrMargin: String(options.margin)
345
+ dataQrMargin: String(options.margin),
346
+ style: `--rb-qr-size:${options.width}px`
341
347
  },
342
348
  [
343
- ...renderCaption(caption, options, labelId),
344
349
  element(
345
350
  "div",
346
351
  {
@@ -349,64 +354,46 @@ function buildRenderedFigure(source, svg, caption, options) {
349
354
  ariaLabelledby: caption ? labelId : void 0,
350
355
  ariaLabel: caption ? void 0 : "QR code"
351
356
  },
352
- [raw(svg)]
357
+ canvasChildren
353
358
  ),
354
- ...renderFallback(source, options)
359
+ renderCaption(source, caption, options, labelId)
355
360
  ]
356
361
  );
357
362
  }
358
- function buildErrorFigure(source, caption, options, message, file) {
359
- reportDiagnostic(file, `QR code could not be rendered: ${message}`, "render");
360
- const labelId = `${figureId(source, options)}-caption`;
361
- return element(
362
- "figure",
363
- {
364
- className: options.className,
365
- dataQr: "error",
366
- dataQrLevel: options.level,
367
- dataQrMargin: String(options.margin)
368
- },
369
- [
370
- ...renderCaption(caption, options, labelId),
363
+ function renderCaption(source, caption, options, labelId) {
364
+ const children = [];
365
+ if (caption) {
366
+ children.push(
371
367
  element(
372
- "div",
368
+ "span",
373
369
  {
374
- className: qrElementClassName(options.className, "canvas"),
375
- role: "img",
376
- ariaLabelledby: caption ? labelId : void 0,
377
- ariaLabel: caption ? void 0 : "QR code"
370
+ id: labelId,
371
+ className: qrElementClassName(options.className, "caption-text")
378
372
  },
379
- []
380
- ),
381
- ...renderFallback(source, options)
382
- ]
373
+ [text(caption)]
374
+ )
375
+ );
376
+ }
377
+ children.push(renderSource(source, options));
378
+ return element(
379
+ "figcaption",
380
+ { className: qrElementClassName(options.className, "caption") },
381
+ children
383
382
  );
384
383
  }
385
- function renderCaption(caption, options, labelId) {
386
- if (!caption) return [];
387
- return [
388
- element(
389
- "figcaption",
390
- {
391
- id: labelId,
392
- className: qrElementClassName(options.className, "caption")
393
- },
394
- [text(caption)]
395
- )
396
- ];
397
- }
398
- function renderFallback(source, options) {
399
- if (!options.fallback) return [];
400
- return [
401
- element(
402
- "details",
403
- { className: qrElementClassName(options.className, "fallback") },
404
- [
405
- element("summary", {}, [text("QR source")]),
406
- element("pre", {}, [element("code", {}, [text(source)])])
407
- ]
408
- )
409
- ];
384
+ function renderSource(source, options) {
385
+ const className = qrElementClassName(options.className, "source");
386
+ const href = navigableHref(source);
387
+ if (href) return element("a", { className, href }, [text(source)]);
388
+ return element("span", { className }, [text(source)]);
389
+ }
390
+ function navigableHref(source) {
391
+ try {
392
+ const { protocol } = new URL(source);
393
+ return NAVIGABLE_PROTOCOLS.has(protocol) ? source : null;
394
+ } catch {
395
+ return null;
396
+ }
410
397
  }
411
398
  function extractCaption(source, pre, code) {
412
399
  const title = getStringProperty(pre, "title") ?? getStringProperty(code ?? pre, "title") ?? metaTitle(getStringProperty(code ?? pre, "data-meta")) ?? metaTitle(getStringProperty(code ?? pre, "dataMeta")) ?? metaTitle(getCodeDataMeta(code));
@@ -494,13 +481,14 @@ function reportDiagnostic(file, message, ruleId) {
494
481
  Object.assign(diagnostic, { source: PLUGIN_SOURCE, ruleId });
495
482
  }
496
483
  }
497
- var CAPTION_PATTERN, META_TITLE_PATTERN, PLUGIN_SOURCE;
484
+ var CAPTION_PATTERN, META_TITLE_PATTERN, PLUGIN_SOURCE, NAVIGABLE_PROTOCOLS;
498
485
  var init_rehype = __esm({
499
486
  "src/rehype.ts"() {
500
487
  init_options();
501
488
  CAPTION_PATTERN = /^[ \t]*#[ \t]*caption[ \t]*:[ \t]*(.+?)[ \t]*(?:\r?\n|$)/i;
502
489
  META_TITLE_PATTERN = /title=(?:"([^"]*)"|'([^']*)'|(\S+))/;
503
490
  PLUGIN_SOURCE = "@riebeckite/plugin-qr-code";
491
+ NAVIGABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
504
492
  }
505
493
  });
506
494
 
@@ -518,7 +506,7 @@ function qrCode(options = {}) {
518
506
  name: PLUGIN_NAME,
519
507
  order: -10,
520
508
  processedContentCache: {
521
- version: "qr-code-v1",
509
+ version: "qr-code-v2",
522
510
  dependencyMode: "none"
523
511
  },
524
512
  options,
@@ -533,13 +521,16 @@ var qrCodePlugin = qrCode;
533
521
  function validateQrCodeOptions(options) {
534
522
  if (!options) return [];
535
523
  const issues = [];
524
+ if (Object.hasOwn(options, "size")) {
525
+ issues.push({ path: "size", message: 'Unknown option. Use "width".' });
526
+ }
536
527
  if (options.level !== void 0 && !QR_CODE_LEVELS.includes(options.level)) {
537
528
  issues.push({
538
529
  path: "level",
539
530
  message: `Expected one of: ${QR_CODE_LEVELS.join(", ")}.`
540
531
  });
541
532
  }
542
- for (const key of ["margin", "width", "size"]) {
533
+ for (const key of ["margin", "width"]) {
543
534
  const value = options[key];
544
535
  if (value === void 0) continue;
545
536
  if (!Number.isInteger(value) || value < 0 || key !== "margin" && value === 0) {
@@ -555,7 +546,7 @@ function validateQrCodeOptions(options) {
555
546
  issues.push({ path: key, message: "Expected a non-empty string." });
556
547
  }
557
548
  }
558
- for (const key of ["caption", "fallback"]) {
549
+ for (const key of ["caption"]) {
559
550
  if (options[key] !== void 0 && typeof options[key] !== "boolean") {
560
551
  issues.push({ path: key, message: "Expected a boolean." });
561
552
  }
@@ -7,8 +7,6 @@ export type QrCodeOptions = {
7
7
  margin?: number;
8
8
  /** Rendered width/height in pixels. Defaults to `160`. */
9
9
  width?: number;
10
- /** Alias of `width`; `width` wins when both are present. */
11
- size?: number;
12
10
  /** Dark-module colour. Defaults to `"#000000"`. */
13
11
  dark?: string;
14
12
  /** Light-module colour. Defaults to `"#ffffff"`. */
@@ -19,8 +17,6 @@ export type QrCodeOptions = {
19
17
  className?: string;
20
18
  /** Fence language that selects the block. Defaults to `"qr"`. */
21
19
  language?: string;
22
- /** Keep the raw source in a collapsible `<details>`. Defaults to `true`. */
23
- fallback?: boolean;
24
20
  };
25
21
  /** `QrCodeOptions` with every field resolved to a concrete value. */
26
22
  export type ResolvedQrCodeOptions = {
@@ -32,7 +28,6 @@ export type ResolvedQrCodeOptions = {
32
28
  caption: boolean;
33
29
  className: string;
34
30
  language: string;
35
- fallback: boolean;
36
31
  };
37
32
  /**
38
33
  * Build-time result of encoding a fence body. A failure carries a human
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-qr-code",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "qrcode": "^1.5.4",
19
- "@riebeckite/core": "0.0.18"
19
+ "@riebeckite/core": "0.0.20"
20
20
  },
21
21
  "publishConfig": {
22
22
  "access": "public"
@@ -51,6 +51,7 @@
51
51
  "./style.css"
52
52
  ],
53
53
  "scripts": {
54
- "build": "node ../../../scripts/build_package.mjs"
54
+ "build": "node ../../../scripts/build_package.mjs",
55
+ "test": "node --import tsx --test \"test/*.test.ts\""
55
56
  }
56
57
  }
package/style.css CHANGED
@@ -6,67 +6,76 @@
6
6
  --rb-color-border,
7
7
  color-mix(in srgb, var(--rb-qr-ink) 18%, transparent)
8
8
  );
9
+ --rb-qr-gutter: 1rem;
9
10
  box-sizing: border-box;
10
- width: fit-content;
11
- max-width: 100%;
11
+ inline-size: calc(var(--rb-qr-size, 160px) + 2 * var(--rb-qr-gutter));
12
+ max-inline-size: 100%;
12
13
  margin: 2rem auto;
13
14
  border: 1px solid var(--rb-qr-rule);
14
15
  background: var(--rb-qr-paper);
15
16
  color: var(--rb-qr-ink);
16
17
  }
17
18
 
18
- .rb-qr__caption {
19
- padding: 0.5rem 0.75rem;
20
- border-bottom: 1px solid var(--rb-qr-rule);
21
- color: var(--rb-qr-muted);
22
- font-size: 0.8125rem;
23
- text-align: center;
24
- }
25
-
26
19
  .rb-qr__canvas {
27
20
  display: grid;
28
21
  place-items: center;
29
- padding: 1rem;
22
+ padding: var(--rb-qr-gutter);
30
23
  }
31
24
 
32
25
  .rb-qr__canvas > svg {
33
26
  display: block;
34
- max-width: 100%;
35
- height: auto;
27
+ inline-size: 100%;
28
+ block-size: auto;
29
+ max-inline-size: var(--rb-qr-size, 160px);
36
30
  }
37
31
 
38
- .rb-qr__fallback {
32
+ .rb-qr__caption {
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: 0.25rem;
36
+ margin: 0;
37
+ padding: 0.5rem 0.75rem;
39
38
  border-top: 1px solid var(--rb-qr-rule);
40
39
  color: var(--rb-qr-muted);
41
40
  font-size: 0.8125rem;
41
+ text-align: center;
42
42
  }
43
43
 
44
- .rb-qr__fallback summary {
45
- cursor: pointer;
46
- padding: 0.5rem 0.75rem;
47
- }
48
-
49
- .rb-qr__fallback pre {
50
- overflow: auto;
51
- margin: 0 1rem 0.75rem;
52
- padding: 0.75rem;
53
- border-top: 1px solid var(--rb-qr-rule);
44
+ .rb-qr__caption-text {
54
45
  color: var(--rb-qr-ink);
55
- background: color-mix(in srgb, var(--rb-qr-ink) 6%, transparent);
46
+ overflow-wrap: anywhere;
56
47
  }
57
48
 
58
- .rb-qr[data-qr="error"] .rb-qr__canvas:empty::before {
59
- content: "QR code could not be rendered.";
49
+ .rb-qr__source {
50
+ min-inline-size: 0;
60
51
  color: var(--rb-qr-muted);
52
+ font-family: var(--rb-font-mono, monospace);
53
+ font-size: 0.75rem;
54
+ text-align: start;
55
+ white-space: pre-wrap;
56
+ overflow-wrap: anywhere;
57
+ word-break: break-word;
61
58
  }
62
59
 
63
- .rb-qr[data-qr="pending"] .rb-qr__canvas:empty::before {
64
- content: "Rendering QR code…";
60
+ a.rb-qr__source {
61
+ color: inherit;
62
+ text-decoration: underline;
63
+ text-underline-offset: 0.15em;
64
+ }
65
+
66
+ .rb-qr[data-qr="error"] .rb-qr__canvas:empty::before {
67
+ content: "QR code could not be rendered.";
65
68
  color: var(--rb-qr-muted);
66
69
  }
67
70
 
68
- html[data-theme="dark"] .rb-qr,
69
- html.dark .rb-qr {
71
+ :is(:root, .rb-theme-root)[data-theme="dark"] .rb-qr {
70
72
  --rb-qr-paper: var(--rb-color-ink, #000000);
71
73
  --rb-qr-ink: var(--rb-color-paper, #ffffff);
72
74
  }
75
+
76
+ @media (prefers-color-scheme: dark) {
77
+ :is(:root, .rb-theme-root):not([data-theme]) .rb-qr {
78
+ --rb-qr-paper: var(--rb-color-ink, #000000);
79
+ --rb-qr-ink: var(--rb-color-paper, #ffffff);
80
+ }
81
+ }