jekyll-mermaid-prebuild 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +33 -1
- data/jekyll-mermaid-prebuild.gemspec +4 -2
- data/lib/jekyll-mermaid-prebuild/configuration.rb +111 -9
- data/lib/jekyll-mermaid-prebuild/digest_calculator.rb +1 -3
- data/lib/jekyll-mermaid-prebuild/emoji_compensator.rb +31 -37
- data/lib/jekyll-mermaid-prebuild/generator.rb +73 -26
- data/lib/jekyll-mermaid-prebuild/hooks.rb +92 -94
- data/lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb +37 -27
- data/lib/jekyll-mermaid-prebuild/processor.rb +41 -26
- data/lib/jekyll-mermaid-prebuild/svg_post_processor.rb +28 -14
- data/lib/jekyll-mermaid-prebuild/version.rb +1 -1
- metadata +33 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a6f44826b0694bbba4cea5d5b0272301e8909003a9b182eb340042c80454ebb
|
|
4
|
+
data.tar.gz: 9f632d879d948c8030edc5365bc06bc75bd36809da017fd35c6f20ee5b252b0a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88b628146a89abbc660bda67b254653a1e77b74b63d55c4ba882a2abe600804129fe29405df0a720457bfbbcbf19b14e7d7841fea322835274deffdcd21c3e67
|
|
7
|
+
data.tar.gz: 3ce40596559ad01778c875cf599b693f42ee1477c70ab178f0f94ff4e735bbe1e1392d68430f2c3985b61ca82b1d8851f9e292715a501a10faf1c640d9dd001e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.5.0...v0.6.0) (2026-07-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **test:** add Mutant mutation testing with 100% kill ([#44](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/44))
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **test:** add Mutant mutation testing with 100% kill ([#44](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/44)) ([e86a82a](https://github.com/Texarkanine/jekyll-mermaid-prebuild/commit/e86a82a6eba5dc0fc037ff0f3451d12708a4f629))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **deps-dev:** migrate SimpleCov add_filter to skip ([#41](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/41)) ([#42](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/42)) ([bc068f0](https://github.com/Texarkanine/jekyll-mermaid-prebuild/commit/bc068f0268e2d974a061a1474d0973306d860f8e))
|
|
18
|
+
|
|
19
|
+
## [0.5.0](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.4.0...v0.5.0) (2026-03-22)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* support "dark mode" (resolves [#11](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/11)) ([#20](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/20)) ([1ae9322](https://github.com/Texarkanine/jekyll-mermaid-prebuild/commit/1ae93225d9f6089d3d85ce18d06f44310fea3895))
|
|
25
|
+
|
|
3
26
|
## [0.4.0](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.3.2...v0.4.0) (2026-03-22)
|
|
4
27
|
|
|
5
28
|
|
data/README.md
CHANGED
|
@@ -94,6 +94,12 @@ Add to your `_config.yml`:
|
|
|
94
94
|
mermaid_prebuild:
|
|
95
95
|
enabled: true # default: true
|
|
96
96
|
output_dir: assets/svg # default: assets/svg
|
|
97
|
+
prefers-color-scheme:
|
|
98
|
+
mode: light # light (default) | dark | auto — see [Color scheme](#color-scheme-mermaid-theme)
|
|
99
|
+
# Optional: override mmdc’s root SVG background (defaults: light=white, dark=black)
|
|
100
|
+
# background-color:
|
|
101
|
+
# light: white
|
|
102
|
+
# dark: black
|
|
97
103
|
postprocessing:
|
|
98
104
|
text_centering: true # default: true
|
|
99
105
|
overflow_protection: true # default: true
|
|
@@ -108,6 +114,7 @@ mermaid_prebuild:
|
|
|
108
114
|
|--------|---------|-------------|
|
|
109
115
|
| `enabled` | `true` | Enable/disable the plugin |
|
|
110
116
|
| `output_dir` | `assets/svg` | Directory for generated SVG files |
|
|
117
|
+
| `prefers-color-scheme` | see below | **Hash** with `mode` (`light` / `dark` / `auto`) and optional `background-color` for chart backgrounds. See [Color scheme](#color-scheme-mermaid-theme). |
|
|
111
118
|
|
|
112
119
|
#### `postprocessing` group
|
|
113
120
|
|
|
@@ -120,6 +127,31 @@ All cross-browser rendering fixes live under the `postprocessing:` key. Each can
|
|
|
120
127
|
| `edge_label_padding` | `0` | Extra SVG user units added to edge-label `<foreignObject>` widths after mmdc (off when `0`, `false`, or omitted). Applies to all diagram types. See [Cross-browser text rendering fixes](#cross-browser-text-rendering-fixes). |
|
|
121
128
|
| `emoji_width_compensation` | `{}` | Map of diagram types to booleans; see [Emoji width compensation](#emoji-width-compensation) below. |
|
|
122
129
|
|
|
130
|
+
### Color scheme (Mermaid theme)
|
|
131
|
+
|
|
132
|
+
Under `mermaid_prebuild`, the **`prefers-color-scheme`** key (hyphenated, like the CSS media feature) must be a **YAML mapping**. Use `mode` for the Mermaid theme / HTML behavior. Optionally nest **`background-color`** (same spelling as in CSS) with `light` / `dark` slots for each chart variant’s root SVG fill (mmdc always emits `background-color: white` on the root `<svg>`; the plugin rewrites that per file).
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
mermaid_prebuild:
|
|
136
|
+
prefers-color-scheme:
|
|
137
|
+
mode: auto
|
|
138
|
+
background-color:
|
|
139
|
+
light: white # default if omitted
|
|
140
|
+
dark: black # default if omitted
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| `mode` | Behavior |
|
|
144
|
+
|--------|----------|
|
|
145
|
+
| `light` | One SVG per diagram using Mermaid’s default (light) theme. |
|
|
146
|
+
| `dark` | One SVG per diagram using mmdc’s dark theme (`mmdc -t dark`). |
|
|
147
|
+
| `auto` | Two SVGs per diagram: `{digest}.svg` (light) and `{digest}-dark.svg` (dark). The embedded HTML uses two links with a small inline stylesheet so only the variant matching the visitor’s `prefers-color-scheme` is shown. **Build cost:** each diagram runs `mmdc` twice until both files are cached. |
|
|
148
|
+
|
|
149
|
+
**Chart backgrounds:** Values are injected verbatim into the root `<svg style="...">` as the CSS token after `background-color:` (for example `white`, `black`, `#fff0aa`, `rgb(0,0,0)`, `transparent`). They are validated conservatively: values containing quotes, angle brackets, backticks, semicolons, backslashes, or control characters are rejected and the default for that slot is used, with a warning. Keep values short (max 256 characters). **Do not** put raw double quotes or HTML in these strings.
|
|
150
|
+
|
|
151
|
+
**Transparency:** The standard CSS keyword **`transparent`** is supported and is the usual choice when you want that variant’s chart to show the page behind it (for example `dark: transparent` on a dark-themed site). Fully transparent RGBA such as `rgba(0, 0, 0, 0)` is also valid if you prefer explicit alpha.
|
|
152
|
+
|
|
153
|
+
If `prefers-color-scheme` is not a hash (for example a bare string), the plugin uses `mode: light` and default backgrounds and logs a warning. Unknown `mode` values fall back to `light` with a warning. Omitting `mode` is treated as `light`.
|
|
154
|
+
|
|
123
155
|
### Cross-browser text rendering fixes
|
|
124
156
|
|
|
125
157
|
When mmdc renders a diagram, headless Chromium measures text with `getBoundingClientRect()` and sets each `<foreignObject>` to exactly that width. If the viewing browser (different OS, different fonts) renders the same text at a different width, labels can clip or shift. The plugin can apply some fixes to every generated SVG (all configurable under `postprocessing:`):
|
|
@@ -179,7 +211,7 @@ Generated SVGs are cached in `.jekyll-cache/jekyll-mermaid-prebuild/`. The cache
|
|
|
179
211
|
- Modified diagrams are automatically regenerated
|
|
180
212
|
- Different diagrams with different content get different cache keys
|
|
181
213
|
- Enabling or disabling emoji width compensation for a diagram type invalidates cache for that content (keys include compensated source when applicable)
|
|
182
|
-
- Changing `edge_label_padding`, `text_centering`,
|
|
214
|
+
- Changing `edge_label_padding`, `text_centering`, `overflow_protection`, `prefers-color-scheme` mode, or chart background colors invalidates cache keys
|
|
183
215
|
|
|
184
216
|
To clear the cache:
|
|
185
217
|
|
|
@@ -35,11 +35,13 @@ Gem::Specification.new do |spec|
|
|
|
35
35
|
spec.add_dependency "jekyll", ">= 4.0", "< 5.0"
|
|
36
36
|
|
|
37
37
|
# Development dependencies
|
|
38
|
+
spec.add_development_dependency "mutant", "~> 0.16"
|
|
39
|
+
spec.add_development_dependency "mutant-rspec", "~> 0.16"
|
|
38
40
|
spec.add_development_dependency "rake", "~> 13.3"
|
|
39
41
|
spec.add_development_dependency "rspec", "~> 3.13"
|
|
40
42
|
spec.add_development_dependency "rubocop", "~> 1.81"
|
|
41
43
|
spec.add_development_dependency "rubocop-rake", "~> 0.7"
|
|
42
44
|
spec.add_development_dependency "rubocop-rspec", "~> 3.8"
|
|
43
|
-
spec.add_development_dependency "simplecov", "~> 0
|
|
44
|
-
spec.add_development_dependency "simplecov-cobertura", "~>
|
|
45
|
+
spec.add_development_dependency "simplecov", "~> 1.0"
|
|
46
|
+
spec.add_development_dependency "simplecov-cobertura", "~> 4.0"
|
|
45
47
|
end
|
|
@@ -5,8 +5,19 @@ module JekyllMermaidPrebuild
|
|
|
5
5
|
class Configuration
|
|
6
6
|
DEFAULT_OUTPUT_DIR = "assets/svg"
|
|
7
7
|
CACHE_DIR = ".jekyll-cache/jekyll-mermaid-prebuild"
|
|
8
|
+
DEFAULT_CHART_BG_LIGHT = "white"
|
|
9
|
+
DEFAULT_CHART_BG_DARK = "black"
|
|
10
|
+
# Parsed mode when PCS is omitted, invalid, or empty (not the YAML string "light").
|
|
11
|
+
DEFAULT_PREFERS_COLOR_SCHEME_MODE = :light
|
|
12
|
+
MAX_CHART_BACKGROUND_LENGTH = 256
|
|
13
|
+
INVALID_CHART_BACKGROUND = /[\x00-\x1f"'<>;`\\]/
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
# YAML keys under mermaid_prebuild — hyphenated to align with CSS (@media (prefers-color-scheme), background-color).
|
|
16
|
+
PREFERS_COLOR_SCHEME_YAML_KEY = "prefers-color-scheme"
|
|
17
|
+
BACKGROUND_COLOR_YAML_KEY = "background-color"
|
|
18
|
+
|
|
19
|
+
attr_reader :output_dir, :text_centering, :overflow_protection, :edge_label_padding, :emoji_width_compensation,
|
|
20
|
+
:prefers_color_scheme, :chart_background_light, :chart_background_dark
|
|
10
21
|
|
|
11
22
|
# Initialize configuration from Jekyll site
|
|
12
23
|
#
|
|
@@ -15,6 +26,8 @@ module JekyllMermaidPrebuild
|
|
|
15
26
|
config = site.config["mermaid_prebuild"] || {}
|
|
16
27
|
@output_dir = parse_output_dir(config["output_dir"])
|
|
17
28
|
@enabled = config.fetch("enabled", true)
|
|
29
|
+
pcs_raw = config[PREFERS_COLOR_SCHEME_YAML_KEY]
|
|
30
|
+
parse_prefers_color_scheme(pcs_raw)
|
|
18
31
|
|
|
19
32
|
pp = config["postprocessing"] || {}
|
|
20
33
|
@text_centering = pp.fetch("text_centering", true)
|
|
@@ -37,7 +50,99 @@ module JekyllMermaidPrebuild
|
|
|
37
50
|
CACHE_DIR
|
|
38
51
|
end
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
# Parse the prefers-color-scheme block (see PREFERS_COLOR_SCHEME_YAML_KEY) from a Hash only
|
|
54
|
+
# (mode + optional background-color map). Non-Hash values fall back to :light and default
|
|
55
|
+
# backgrounds with a warning.
|
|
56
|
+
#
|
|
57
|
+
# @param value [Object] raw site config value
|
|
58
|
+
# @return [void]
|
|
59
|
+
def parse_prefers_color_scheme(value)
|
|
60
|
+
if !value.nil? && !value.is_a?(Hash)
|
|
61
|
+
Jekyll.logger.warn(
|
|
62
|
+
"MermaidPrebuild:",
|
|
63
|
+
"Invalid #{PREFERS_COLOR_SCHEME_YAML_KEY} (expected a Hash); " \
|
|
64
|
+
"using light mode and default backgrounds"
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
mode_raw = config_hash_fetch(value, "mode")
|
|
69
|
+
@prefers_color_scheme = normalize_prefers_mode(mode_raw)
|
|
70
|
+
|
|
71
|
+
bg_container = config_hash_fetch(value, BACKGROUND_COLOR_YAML_KEY)
|
|
72
|
+
light_raw = config_hash_fetch(bg_container, "light")
|
|
73
|
+
dark_raw = config_hash_fetch(bg_container, "dark")
|
|
74
|
+
@chart_background_light = coerce_chart_background(light_raw, DEFAULT_CHART_BG_LIGHT, "light")
|
|
75
|
+
@chart_background_dark = coerce_chart_background(dark_raw, DEFAULT_CHART_BG_DARK, "dark")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param raw [Object]
|
|
79
|
+
# @return [Symbol] :light, :dark, or :auto
|
|
80
|
+
def normalize_prefers_mode(raw)
|
|
81
|
+
s = raw.to_s.strip.downcase
|
|
82
|
+
return DEFAULT_PREFERS_COLOR_SCHEME_MODE if s.empty?
|
|
83
|
+
|
|
84
|
+
case s
|
|
85
|
+
when "light" then :light
|
|
86
|
+
when "dark" then :dark
|
|
87
|
+
when "auto" then :auto
|
|
88
|
+
else
|
|
89
|
+
Jekyll.logger.warn(
|
|
90
|
+
"MermaidPrebuild:",
|
|
91
|
+
"Invalid #{PREFERS_COLOR_SCHEME_YAML_KEY} mode #{raw.inspect}; using light"
|
|
92
|
+
)
|
|
93
|
+
DEFAULT_PREFERS_COLOR_SCHEME_MODE
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Read a config key from a Hash (string key as in YAML, or matching Symbol).
|
|
98
|
+
#
|
|
99
|
+
# @param hash [Hash]
|
|
100
|
+
# @param key [String] exact key (e.g. "mode", "background-color", "light")
|
|
101
|
+
# @return [Object, nil]
|
|
102
|
+
def config_hash_fetch(hash, key)
|
|
103
|
+
return nil unless hash.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
hash.fetch(key) { hash.fetch(key.to_sym, nil) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @param value [Object] raw color string or nil (use default)
|
|
109
|
+
# @param default [String] fallback literal
|
|
110
|
+
# @param label [String] "light" or "dark" for logging
|
|
111
|
+
# @return [String] frozen sanitized CSS fragment
|
|
112
|
+
def coerce_chart_background(value, default, label)
|
|
113
|
+
return finalize_background(default) if value.nil?
|
|
114
|
+
|
|
115
|
+
str = value.to_s.strip
|
|
116
|
+
if str.empty?
|
|
117
|
+
Jekyll.logger.warn "MermaidPrebuild:",
|
|
118
|
+
"Invalid chart background (#{label}): empty string; using #{default.inspect}"
|
|
119
|
+
return finalize_background(default)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if str.length > MAX_CHART_BACKGROUND_LENGTH
|
|
123
|
+
Jekyll.logger.warn "MermaidPrebuild:",
|
|
124
|
+
"Invalid chart background (#{label}): value too long; using #{default.inspect}"
|
|
125
|
+
return finalize_background(default)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if chart_background_invalid?(str)
|
|
129
|
+
Jekyll.logger.warn "MermaidPrebuild:",
|
|
130
|
+
"Invalid chart background (#{label}): disallowed characters; using #{default.inspect}"
|
|
131
|
+
return finalize_background(default)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
str.freeze
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def chart_background_invalid?(value)
|
|
138
|
+
INVALID_CHART_BACKGROUND.match?(value)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @param value [String]
|
|
142
|
+
# @return [String] frozen copy
|
|
143
|
+
def finalize_background(value)
|
|
144
|
+
value.to_s.dup.freeze
|
|
145
|
+
end
|
|
41
146
|
|
|
42
147
|
# Returns a frozen Hash of diagram type (string) => boolean. Non-hash values are rejected → {}.
|
|
43
148
|
#
|
|
@@ -46,7 +151,7 @@ module JekyllMermaidPrebuild
|
|
|
46
151
|
def parse_emoji_width_compensation(value)
|
|
47
152
|
return {}.freeze unless value.is_a?(Hash)
|
|
48
153
|
|
|
49
|
-
result = value.transform_keys(&:to_s).transform_values { |v|
|
|
154
|
+
result = value.transform_keys(&:to_s).transform_values { |v| v == true }
|
|
50
155
|
result.freeze
|
|
51
156
|
end
|
|
52
157
|
|
|
@@ -56,18 +161,15 @@ module JekyllMermaidPrebuild
|
|
|
56
161
|
dir = dir.strip
|
|
57
162
|
return DEFAULT_OUTPUT_DIR if dir.empty?
|
|
58
163
|
|
|
59
|
-
dir.
|
|
164
|
+
dir.split("/").reject(&:empty?).join("/")
|
|
60
165
|
end
|
|
61
166
|
|
|
62
167
|
# @param value [Object] raw config (numeric or off)
|
|
63
168
|
# @return [Numeric] non-negative padding in SVG user units; 0 means disabled
|
|
64
169
|
def parse_edge_label_padding(value)
|
|
65
|
-
return 0
|
|
66
|
-
|
|
67
|
-
num = value.is_a?(Numeric) ? value : nil
|
|
68
|
-
return 0 unless num
|
|
170
|
+
return 0 unless value.is_a?(Numeric)
|
|
69
171
|
|
|
70
|
-
|
|
172
|
+
value.negative? ? 0 : value
|
|
71
173
|
end
|
|
72
174
|
end
|
|
73
175
|
end
|
|
@@ -5,13 +5,11 @@ require "digest"
|
|
|
5
5
|
module JekyllMermaidPrebuild
|
|
6
6
|
# Computes content-based digests for caching
|
|
7
7
|
module DigestCalculator
|
|
8
|
-
module_function
|
|
9
|
-
|
|
10
8
|
# Generate a short digest for mermaid content
|
|
11
9
|
#
|
|
12
10
|
# @param content [String] mermaid diagram source
|
|
13
11
|
# @return [String] 8-character hex digest
|
|
14
|
-
def content_digest(content)
|
|
12
|
+
def self.content_digest(content)
|
|
15
13
|
Digest::MD5.hexdigest(content)[0, 8]
|
|
16
14
|
end
|
|
17
15
|
end
|
|
@@ -4,8 +4,6 @@ module JekyllMermaidPrebuild
|
|
|
4
4
|
# Stateless module: compensates for headless Chromium emoji width undermeasurement
|
|
5
5
|
# by appending padding to emoji-containing node labels before mmdc renders.
|
|
6
6
|
module EmojiCompensator
|
|
7
|
-
module_function
|
|
8
|
-
|
|
9
7
|
# Match a single Extended_Pictographic codepoint (emoji).
|
|
10
8
|
EMOJI_RE = /\p{Extended_Pictographic}/
|
|
11
9
|
|
|
@@ -15,28 +13,22 @@ module JekyllMermaidPrebuild
|
|
|
15
13
|
#
|
|
16
14
|
# @param mermaid_source [String] raw Mermaid diagram source
|
|
17
15
|
# @return [String, nil] diagram type keyword (e.g. "flowchart", "sequenceDiagram") or nil
|
|
18
|
-
def detect_diagram_type(mermaid_source)
|
|
19
|
-
return nil
|
|
16
|
+
def self.detect_diagram_type(mermaid_source)
|
|
17
|
+
return nil unless mermaid_source
|
|
20
18
|
|
|
21
19
|
in_frontmatter = false
|
|
22
|
-
frontmatter_delim_count = 0
|
|
23
20
|
|
|
24
21
|
mermaid_source.each_line do |line|
|
|
25
22
|
stripped = line.strip
|
|
26
|
-
# Track YAML frontmatter
|
|
27
23
|
if stripped == "---"
|
|
28
|
-
|
|
29
|
-
in_frontmatter = frontmatter_delim_count.odd?
|
|
24
|
+
in_frontmatter = !in_frontmatter
|
|
30
25
|
next
|
|
31
26
|
end
|
|
32
27
|
next if in_frontmatter
|
|
33
28
|
next if stripped.empty?
|
|
34
29
|
next if stripped.start_with?("%%")
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
token = stripped.split(/\s+/, 2).first
|
|
38
|
-
return nil if token.nil? || token.empty?
|
|
39
|
-
|
|
31
|
+
token = stripped[/\A\S+/]
|
|
40
32
|
return token == "graph" ? "flowchart" : token
|
|
41
33
|
end
|
|
42
34
|
|
|
@@ -49,10 +41,10 @@ module JekyllMermaidPrebuild
|
|
|
49
41
|
# @param mermaid_source [String] Mermaid diagram source
|
|
50
42
|
# @param diagram_type [String] result of detect_diagram_type
|
|
51
43
|
# @return [String] possibly modified source
|
|
52
|
-
def compensate(mermaid_source, diagram_type)
|
|
53
|
-
return mermaid_source if diagram_type
|
|
44
|
+
def self.compensate(mermaid_source, diagram_type)
|
|
45
|
+
return compensate_flowchart_labels(mermaid_source) if diagram_type == "flowchart"
|
|
54
46
|
|
|
55
|
-
|
|
47
|
+
mermaid_source
|
|
56
48
|
end
|
|
57
49
|
|
|
58
50
|
BR_RE = %r{(<br\s*/?>)}i
|
|
@@ -62,7 +54,7 @@ module JekyllMermaidPrebuild
|
|
|
62
54
|
#
|
|
63
55
|
# @param text [String]
|
|
64
56
|
# @return [Integer]
|
|
65
|
-
def count_emoji(text)
|
|
57
|
+
def self.count_emoji(text)
|
|
66
58
|
text.scan(EMOJI_RE).length
|
|
67
59
|
end
|
|
68
60
|
|
|
@@ -71,46 +63,48 @@ module JekyllMermaidPrebuild
|
|
|
71
63
|
#
|
|
72
64
|
# @param text [String]
|
|
73
65
|
# @return [Integer]
|
|
74
|
-
def visual_length(text)
|
|
66
|
+
def self.visual_length(text)
|
|
75
67
|
text.length + count_emoji(text)
|
|
76
68
|
end
|
|
77
69
|
|
|
78
70
|
# Pad the longest line in a label if it contains emoji.
|
|
79
71
|
# Splits on <br/> variants, finds the visually longest line, and only
|
|
80
72
|
# pads that line (shorter lines center naturally in the wider container).
|
|
81
|
-
#
|
|
73
|
+
# When no padding is applied, returns the same String object (identity),
|
|
74
|
+
# not merely an equal copy.
|
|
82
75
|
#
|
|
83
76
|
# @param content [String] raw label text (may contain <br/> line breaks)
|
|
84
77
|
# @return [String] possibly padded label text
|
|
85
|
-
def pad_label_content(content)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
def self.pad_label_content(content)
|
|
79
|
+
segments = content.split(BR_RE)
|
|
80
|
+
# Even indices are text lines; odd indices are the captured <br> delimiters.
|
|
81
|
+
line_pairs = segments.each_with_index.select { |_segment, index| index.even? }
|
|
82
|
+
return content if line_pairs.empty?
|
|
83
|
+
|
|
84
|
+
# max_by keeps the first of equal-length ties — pad that one index only.
|
|
85
|
+
longest_line, longest_idx = line_pairs.max_by { |segment, _index| visual_length(segment) }
|
|
86
|
+
emoji_count = count_emoji(longest_line)
|
|
87
|
+
return content unless emoji_count.positive?
|
|
88
|
+
|
|
89
|
+
segments[longest_idx] = "#{longest_line}#{NBSP * (emoji_count * 2)}"
|
|
90
|
+
segments.join
|
|
97
91
|
end
|
|
98
92
|
|
|
99
|
-
def compensate_flowchart_labels(source)
|
|
100
|
-
result = source
|
|
93
|
+
def self.compensate_flowchart_labels(source)
|
|
94
|
+
result = source
|
|
101
95
|
|
|
102
96
|
[
|
|
103
|
-
[/\["(
|
|
104
|
-
[/\['(
|
|
105
|
-
[/\("(
|
|
106
|
-
[/\{"(
|
|
97
|
+
[/\["(.+?)"\]/m, '["', '"]'],
|
|
98
|
+
[/\['(.+?)'\]/m, "['", "']"],
|
|
99
|
+
[/\("(.+?)"\)/m, '("', '")'],
|
|
100
|
+
[/\{"(.+?)"\}/m, '{"', '"}']
|
|
107
101
|
].each do |regex, open_str, close_str|
|
|
108
102
|
result = result.gsub(regex) do
|
|
109
103
|
"#{open_str}#{pad_label_content(Regexp.last_match(1))}#{close_str}"
|
|
110
104
|
end
|
|
111
105
|
end
|
|
112
106
|
|
|
113
|
-
result.gsub(%r{\[/"((?:[^"\\]|\\.)
|
|
107
|
+
result.gsub(%r{\[/"((?:[^"\\]|\\.)+?)"/\]}) do
|
|
114
108
|
"[/\"#{pad_label_content(Regexp.last_match(1))}\"/]"
|
|
115
109
|
end
|
|
116
110
|
end
|
|
@@ -18,29 +18,21 @@ module JekyllMermaidPrebuild
|
|
|
18
18
|
#
|
|
19
19
|
# @param mermaid_source [String] mermaid diagram definition
|
|
20
20
|
# @param cache_key [String] digest for caching
|
|
21
|
-
# @
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Generate SVG using mmdc
|
|
33
|
-
success = MmdcWrapper.render(mermaid_source, cache_path)
|
|
34
|
-
return nil unless success
|
|
35
|
-
|
|
36
|
-
post_process_svg(cache_path, diagram_type)
|
|
37
|
-
|
|
38
|
-
cache_path
|
|
21
|
+
# @return [Hash{String => String}, nil] stem (cache filename without .svg) => absolute cache path, or nil on failure
|
|
22
|
+
def generate(mermaid_source, cache_key)
|
|
23
|
+
case @config.prefers_color_scheme
|
|
24
|
+
when :light
|
|
25
|
+
generate_one(mermaid_source, cache_key, theme: :default)
|
|
26
|
+
when :dark
|
|
27
|
+
generate_one(mermaid_source, cache_key, theme: :dark)
|
|
28
|
+
when :auto
|
|
29
|
+
generate_auto(mermaid_source, cache_key)
|
|
30
|
+
end
|
|
39
31
|
end
|
|
40
32
|
|
|
41
|
-
# Build SVG URL from cache key
|
|
33
|
+
# Build SVG URL from cache key (stem, e.g. digest or digest-dark)
|
|
42
34
|
#
|
|
43
|
-
# @param cache_key [String] the cache
|
|
35
|
+
# @param cache_key [String] the cache stem
|
|
44
36
|
# @return [String] URL path to SVG
|
|
45
37
|
def build_svg_url(cache_key)
|
|
46
38
|
"/#{@config.output_dir}/#{cache_key}.svg"
|
|
@@ -48,9 +40,27 @@ module JekyllMermaidPrebuild
|
|
|
48
40
|
|
|
49
41
|
# Build figure HTML for a diagram
|
|
50
42
|
#
|
|
51
|
-
# @param svg_url [String] URL to the SVG file
|
|
43
|
+
# @param svg_url [String] URL to the light (or only) SVG file
|
|
44
|
+
# @param dark_url [String, nil] when set (auto mode), second link + CSS toggle for prefers-color-scheme
|
|
52
45
|
# @return [String] HTML figure element
|
|
53
|
-
def build_figure_html(svg_url)
|
|
46
|
+
def build_figure_html(svg_url, dark_url: nil)
|
|
47
|
+
if dark_url
|
|
48
|
+
return <<~HTML
|
|
49
|
+
<figure class="mermaid-diagram">
|
|
50
|
+
<style>
|
|
51
|
+
.mermaid-diagram__light { display: inline; }
|
|
52
|
+
.mermaid-diagram__dark { display: none; }
|
|
53
|
+
@media (prefers-color-scheme: dark) {
|
|
54
|
+
.mermaid-diagram__light { display: none; }
|
|
55
|
+
.mermaid-diagram__dark { display: inline; }
|
|
56
|
+
}
|
|
57
|
+
</style>
|
|
58
|
+
<a class="mermaid-diagram__light" href="#{svg_url}"><img src="#{svg_url}" alt="Mermaid Diagram"></a>
|
|
59
|
+
<a class="mermaid-diagram__dark" href="#{dark_url}"><img src="#{dark_url}" alt="Mermaid Diagram"></a>
|
|
60
|
+
</figure>
|
|
61
|
+
HTML
|
|
62
|
+
end
|
|
63
|
+
|
|
54
64
|
<<~HTML
|
|
55
65
|
<figure class="mermaid-diagram">
|
|
56
66
|
<a href="#{svg_url}"><img src="#{svg_url}" alt="Mermaid Diagram"></a>
|
|
@@ -60,17 +70,54 @@ module JekyllMermaidPrebuild
|
|
|
60
70
|
|
|
61
71
|
private
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
# @return [Hash{String => String}, nil]
|
|
74
|
+
def generate_one(mermaid_source, stem, theme:)
|
|
75
|
+
cache_path = File.join(@config.cache_dir, "#{stem}.svg")
|
|
76
|
+
return { stem => cache_path } if File.exist?(cache_path)
|
|
77
|
+
|
|
78
|
+
FileUtils.mkdir_p(@config.cache_dir)
|
|
79
|
+
return nil unless MmdcWrapper.render(mermaid_source, cache_path, theme: theme)
|
|
80
|
+
|
|
81
|
+
bg = theme == :dark ? @config.chart_background_dark : @config.chart_background_light
|
|
82
|
+
post_process_svg(cache_path, root_background: bg)
|
|
83
|
+
{ stem => cache_path }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [Hash{String => String}, nil]
|
|
87
|
+
def generate_auto(mermaid_source, cache_key)
|
|
88
|
+
light_stem = cache_key
|
|
89
|
+
dark_stem = "#{cache_key}-dark"
|
|
90
|
+
light_path = File.join(@config.cache_dir, "#{light_stem}.svg")
|
|
91
|
+
dark_path = File.join(@config.cache_dir, "#{dark_stem}.svg")
|
|
92
|
+
|
|
93
|
+
FileUtils.mkdir_p(@config.cache_dir)
|
|
94
|
+
|
|
95
|
+
unless File.exist?(light_path)
|
|
96
|
+
return nil unless MmdcWrapper.render(mermaid_source, light_path, theme: :default)
|
|
97
|
+
|
|
98
|
+
post_process_svg(light_path, root_background: @config.chart_background_light)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
unless File.exist?(dark_path)
|
|
102
|
+
return nil unless MmdcWrapper.render(mermaid_source, dark_path, theme: :dark)
|
|
103
|
+
|
|
104
|
+
post_process_svg(dark_path, root_background: @config.chart_background_dark)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
{ light_stem => light_path, dark_stem => dark_path }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def post_process_svg(cache_path, root_background:)
|
|
64
111
|
raw = File.read(cache_path)
|
|
65
112
|
svg = raw
|
|
66
113
|
|
|
67
114
|
svg = SvgPostProcessor.ensure_text_centering(svg) if @config.text_centering
|
|
68
115
|
svg = SvgPostProcessor.ensure_foreignobject_overflow(svg) if @config.overflow_protection
|
|
116
|
+
svg = SvgPostProcessor.apply_root_svg_background(svg, root_background)
|
|
69
117
|
|
|
70
|
-
|
|
71
|
-
svg = SvgPostProcessor.apply(svg, padding: pad) if pad.is_a?(Numeric) && pad.positive?
|
|
118
|
+
svg = SvgPostProcessor.apply(svg, padding: @config.edge_label_padding)
|
|
72
119
|
|
|
73
|
-
File.write(cache_path, svg)
|
|
120
|
+
File.write(cache_path, svg) unless svg.eql?(raw)
|
|
74
121
|
end
|
|
75
122
|
end
|
|
76
123
|
end
|
|
@@ -5,15 +5,13 @@ require "fileutils"
|
|
|
5
5
|
module JekyllMermaidPrebuild
|
|
6
6
|
# Jekyll hook integration
|
|
7
7
|
module Hooks
|
|
8
|
-
module_function
|
|
9
|
-
|
|
10
8
|
# Copy generated SVGs to _site directory
|
|
11
9
|
#
|
|
12
10
|
# @param site [Jekyll::Site] the Jekyll site
|
|
13
11
|
# @param config [Configuration] plugin configuration
|
|
14
12
|
# @param svgs [Hash] cache_key => cached_path mapping
|
|
15
|
-
def copy_svgs_to_site(site, config, svgs)
|
|
16
|
-
return
|
|
13
|
+
def self.copy_svgs_to_site(site, config, svgs)
|
|
14
|
+
return if svgs.nil? || svgs.empty?
|
|
17
15
|
|
|
18
16
|
dest_dir = File.join(site.dest, config.output_dir)
|
|
19
17
|
FileUtils.mkdir_p(dest_dir)
|
|
@@ -34,110 +32,110 @@ module JekyllMermaidPrebuild
|
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
# Log helpful error message for Puppeteer issues
|
|
37
|
-
def log_puppeteer_error
|
|
38
|
-
Jekyll.logger.error "MermaidPrebuild:", "=" * 60
|
|
35
|
+
def self.log_puppeteer_error
|
|
39
36
|
Jekyll.logger.error "MermaidPrebuild:", "mmdc failed: Puppeteer cannot launch headless Chrome"
|
|
40
|
-
Jekyll.logger.error "MermaidPrebuild:", ""
|
|
41
|
-
Jekyll.logger.error "MermaidPrebuild:", "This usually means missing system libraries."
|
|
42
|
-
Jekyll.logger.error "MermaidPrebuild:", "On Debian/Ubuntu/WSL, install with:"
|
|
43
|
-
Jekyll.logger.error "MermaidPrebuild:", ""
|
|
44
|
-
Jekyll.logger.error "MermaidPrebuild:", " sudo apt-get update"
|
|
45
|
-
Jekyll.logger.error "MermaidPrebuild:", " sudo apt-get install -y libgbm1 libasound2 libatk1.0-0 \\"
|
|
46
|
-
Jekyll.logger.error "MermaidPrebuild:", " libatk-bridge2.0-0 libcups2 libdrm2 libxcomposite1 \\"
|
|
47
|
-
Jekyll.logger.error "MermaidPrebuild:", " libxdamage1 libxfixes3 libxrandr2 libxkbcommon0 \\"
|
|
48
|
-
Jekyll.logger.error "MermaidPrebuild:", " libpango-1.0-0 libcairo2 libnss3 libnspr4"
|
|
49
|
-
Jekyll.logger.error "MermaidPrebuild:", ""
|
|
50
|
-
Jekyll.logger.error "MermaidPrebuild:", "See: https://pptr.dev/troubleshooting"
|
|
51
|
-
Jekyll.logger.error "MermaidPrebuild:", "=" * 60
|
|
37
|
+
Jekyll.logger.error "MermaidPrebuild:", "Install system libraries (see https://pptr.dev/troubleshooting)"
|
|
52
38
|
end
|
|
53
39
|
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
40
|
+
# Initialize plugin state after site data is loaded (:post_read)
|
|
41
|
+
#
|
|
42
|
+
# @param site [Jekyll::Site] the Jekyll site
|
|
43
|
+
def self.initialize_system(site)
|
|
44
|
+
config = Configuration.new(site)
|
|
45
|
+
site.data["mermaid_prebuild_config"] = config
|
|
46
|
+
|
|
47
|
+
unless config.enabled?
|
|
48
|
+
Jekyll.logger.info "MermaidPrebuild:", "Disabled in configuration"
|
|
49
|
+
site.data["mermaid_prebuild_enabled"] = false
|
|
50
|
+
return
|
|
51
|
+
end
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
status = MmdcWrapper.check_status
|
|
54
|
+
|
|
55
|
+
case status
|
|
56
|
+
when :ok
|
|
57
|
+
Jekyll.logger.info "MermaidPrebuild:", "Initialized (mmdc #{MmdcWrapper.version || "unknown version"})"
|
|
58
|
+
Jekyll.logger.info "MermaidPrebuild:", "Output directory: #{config.output_dir}"
|
|
59
|
+
site.data["mermaid_prebuild_enabled"] = true
|
|
60
|
+
generator = Generator.new(config)
|
|
61
|
+
site.data["mermaid_prebuild_generator"] = generator
|
|
62
|
+
site.data["mermaid_prebuild_processor"] = Processor.new(config, generator)
|
|
63
|
+
site.data["mermaid_prebuild_svgs"] = {}
|
|
64
|
+
when :not_found
|
|
65
|
+
Jekyll.logger.warn "MermaidPrebuild:", "mmdc not found - mermaid diagrams will not be converted"
|
|
66
|
+
Jekyll.logger.warn "MermaidPrebuild:", "Install with: npm install -g @mermaid-js/mermaid-cli"
|
|
67
|
+
site.data["mermaid_prebuild_enabled"] = false
|
|
68
|
+
when :puppeteer_error
|
|
69
|
+
log_puppeteer_error
|
|
70
|
+
site.data["mermaid_prebuild_enabled"] = false
|
|
71
|
+
else
|
|
72
|
+
Jekyll.logger.warn "MermaidPrebuild:", "mmdc check failed with unknown error"
|
|
73
|
+
site.data["mermaid_prebuild_enabled"] = false
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Process documents/pages before rendering (:pre_render)
|
|
78
|
+
#
|
|
79
|
+
# @param site [Jekyll::Site] the Jekyll site
|
|
80
|
+
def self.process_site(site)
|
|
81
|
+
return unless site.data["mermaid_prebuild_enabled"]
|
|
82
|
+
|
|
83
|
+
processor = site.data["mermaid_prebuild_processor"]
|
|
84
|
+
total_count = 0
|
|
85
|
+
|
|
86
|
+
site.documents.each do |document|
|
|
87
|
+
next unless document.content
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Jekyll.logger.info "MermaidPrebuild:", "
|
|
72
|
-
|
|
73
|
-
site.data["mermaid_prebuild_enabled"] = true
|
|
74
|
-
site.data["mermaid_prebuild_generator"] = Generator.new(config)
|
|
75
|
-
site.data["mermaid_prebuild_processor"] = Processor.new(config, site.data["mermaid_prebuild_generator"])
|
|
76
|
-
site.data["mermaid_prebuild_svgs"] = {}
|
|
77
|
-
when :not_found
|
|
78
|
-
Jekyll.logger.warn "MermaidPrebuild:", "mmdc not found - mermaid diagrams will not be converted"
|
|
79
|
-
Jekyll.logger.warn "MermaidPrebuild:", "Install with: npm install -g @mermaid-js/mermaid-cli"
|
|
80
|
-
site.data["mermaid_prebuild_enabled"] = false
|
|
81
|
-
when :puppeteer_error
|
|
82
|
-
log_puppeteer_error
|
|
83
|
-
site.data["mermaid_prebuild_enabled"] = false
|
|
84
|
-
else
|
|
85
|
-
Jekyll.logger.warn "MermaidPrebuild:", "mmdc check failed with unknown error"
|
|
86
|
-
site.data["mermaid_prebuild_enabled"] = false
|
|
89
|
+
processed, count, svgs = processor.process_content(document.content, site)
|
|
90
|
+
if count.positive?
|
|
91
|
+
document.content = processed
|
|
92
|
+
site.data["mermaid_prebuild_svgs"].merge!(svgs)
|
|
93
|
+
Jekyll.logger.info "MermaidPrebuild:", "Converted #{count} diagram(s) in #{document.relative_path}"
|
|
94
|
+
total_count += count
|
|
87
95
|
end
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
Jekyll.logger.error "MermaidPrebuild:", "Error processing #{document.relative_path}: #{e.message}"
|
|
88
98
|
end
|
|
89
99
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
next unless site.data["mermaid_prebuild_enabled"]
|
|
93
|
-
|
|
94
|
-
processor = site.data["mermaid_prebuild_processor"]
|
|
95
|
-
total_count = 0
|
|
96
|
-
|
|
97
|
-
# Process all documents
|
|
98
|
-
site.documents.each do |document|
|
|
99
|
-
next unless document.content
|
|
100
|
-
|
|
101
|
-
processed, count, svgs = processor.process_content(document.content, site)
|
|
102
|
-
if count.positive?
|
|
103
|
-
document.content = processed
|
|
104
|
-
site.data["mermaid_prebuild_svgs"].merge!(svgs)
|
|
105
|
-
Jekyll.logger.info "MermaidPrebuild:", "Converted #{count} diagram(s) in #{document.relative_path}"
|
|
106
|
-
total_count += count
|
|
107
|
-
end
|
|
108
|
-
rescue StandardError => e
|
|
109
|
-
Jekyll.logger.error "MermaidPrebuild:", "Error processing #{document.relative_path}: #{e.message}"
|
|
110
|
-
end
|
|
100
|
+
site.pages.each do |page|
|
|
101
|
+
next unless page.content
|
|
111
102
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
page.content = processed
|
|
119
|
-
site.data["mermaid_prebuild_svgs"].merge!(svgs)
|
|
120
|
-
Jekyll.logger.info "MermaidPrebuild:", "Converted #{count} diagram(s) in #{page.relative_path}"
|
|
121
|
-
total_count += count
|
|
122
|
-
end
|
|
123
|
-
rescue StandardError => e
|
|
124
|
-
Jekyll.logger.error "MermaidPrebuild:", "Error processing page: #{e.message}"
|
|
103
|
+
processed, count, svgs = processor.process_content(page.content, site)
|
|
104
|
+
if count.positive?
|
|
105
|
+
page.content = processed
|
|
106
|
+
site.data["mermaid_prebuild_svgs"].merge!(svgs)
|
|
107
|
+
Jekyll.logger.info "MermaidPrebuild:", "Converted #{count} diagram(s) in #{page.relative_path}"
|
|
108
|
+
total_count += count
|
|
125
109
|
end
|
|
126
|
-
|
|
127
|
-
Jekyll.logger.
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
Jekyll.logger.error "MermaidPrebuild:", "Error processing #{page.relative_path}: #{e.message}"
|
|
128
112
|
end
|
|
129
113
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
next unless site.data["mermaid_prebuild_enabled"]
|
|
114
|
+
Jekyll.logger.info "MermaidPrebuild:", "Total: #{total_count} diagram(s) converted" if total_count.positive?
|
|
115
|
+
end
|
|
133
116
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
# Copy SVGs into _site after write (:post_write)
|
|
118
|
+
#
|
|
119
|
+
# @param site [Jekyll::Site] the Jekyll site
|
|
120
|
+
def self.copy_generated_svgs(site)
|
|
121
|
+
return unless site.data["mermaid_prebuild_enabled"]
|
|
122
|
+
|
|
123
|
+
config = site.data["mermaid_prebuild_config"]
|
|
124
|
+
svgs = site.data["mermaid_prebuild_svgs"]
|
|
125
|
+
copy_svgs_to_site(site, config, svgs)
|
|
138
126
|
end
|
|
139
127
|
end
|
|
140
128
|
end
|
|
141
129
|
|
|
142
|
-
# Register hooks when loaded
|
|
143
|
-
|
|
130
|
+
# Register Jekyll hooks when loaded
|
|
131
|
+
Jekyll::Hooks.register :site, :post_read do |site|
|
|
132
|
+
JekyllMermaidPrebuild::Hooks.initialize_system(site)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
Jekyll::Hooks.register :site, :pre_render do |site|
|
|
136
|
+
JekyllMermaidPrebuild::Hooks.process_site(site)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
Jekyll::Hooks.register :site, :post_write do |site|
|
|
140
|
+
JekyllMermaidPrebuild::Hooks.copy_generated_svgs(site)
|
|
141
|
+
end
|
|
@@ -6,39 +6,41 @@ require "tempfile"
|
|
|
6
6
|
module JekyllMermaidPrebuild
|
|
7
7
|
# Wrapper for mmdc (mermaid CLI) commands
|
|
8
8
|
module MmdcWrapper
|
|
9
|
-
module_function
|
|
10
|
-
|
|
11
9
|
# Check if mmdc is available in PATH
|
|
12
10
|
#
|
|
13
11
|
# @return [Boolean] true if mmdc is available
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
MMDC_COMMAND = "mmdc"
|
|
13
|
+
|
|
14
|
+
def self.available?
|
|
15
|
+
return @available if instance_variable_defined?(:@available)
|
|
16
16
|
|
|
17
|
-
@available = command_exists?(
|
|
17
|
+
@available = command_exists?(MMDC_COMMAND)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# Check if a command exists in PATH (cross-platform)
|
|
21
21
|
#
|
|
22
22
|
# @param cmd [String] command name
|
|
23
23
|
# @return [Boolean] true if command found
|
|
24
|
-
def command_exists?(cmd)
|
|
24
|
+
def self.command_exists?(cmd)
|
|
25
|
+
return false if cmd.nil? || cmd.empty?
|
|
26
|
+
|
|
25
27
|
cmd_name = Gem.win_platform? ? "#{cmd}.exe" : cmd
|
|
26
28
|
path_dirs = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR)
|
|
27
29
|
|
|
28
30
|
path_dirs.any? do |dir|
|
|
29
31
|
executable = File.join(dir, cmd_name)
|
|
30
|
-
File.executable?(executable)
|
|
32
|
+
File.file?(executable) && File.executable?(executable)
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
# Get mmdc version
|
|
35
37
|
#
|
|
36
38
|
# @return [String, nil] version string or nil
|
|
37
|
-
def version
|
|
38
|
-
return @version if
|
|
39
|
+
def self.version
|
|
40
|
+
return @version if instance_variable_defined?(:@version)
|
|
39
41
|
|
|
40
|
-
output, status = Open3.capture2e(
|
|
41
|
-
@version =
|
|
42
|
+
output, status = Open3.capture2e(MMDC_COMMAND, "--version")
|
|
43
|
+
@version = output.strip if status.success?
|
|
42
44
|
rescue StandardError
|
|
43
45
|
@version = nil
|
|
44
46
|
end
|
|
@@ -46,8 +48,8 @@ module JekyllMermaidPrebuild
|
|
|
46
48
|
# Check mmdc status (availability and Puppeteer)
|
|
47
49
|
#
|
|
48
50
|
# @return [Symbol] :ok, :not_found, :puppeteer_error, or :unknown_error
|
|
49
|
-
def check_status
|
|
50
|
-
return @check_status if
|
|
51
|
+
def self.check_status
|
|
52
|
+
return @check_status if instance_variable_defined?(:@check_status)
|
|
51
53
|
|
|
52
54
|
unless available?
|
|
53
55
|
@check_status = :not_found
|
|
@@ -58,25 +60,26 @@ module JekyllMermaidPrebuild
|
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
# Reset cached status (useful for testing)
|
|
61
|
-
def reset_cache!
|
|
62
|
-
remove_instance_variable(:@available) if
|
|
63
|
-
remove_instance_variable(:@version) if
|
|
64
|
-
remove_instance_variable(:@check_status) if
|
|
63
|
+
def self.reset_cache!
|
|
64
|
+
remove_instance_variable(:@available) if instance_variable_defined?(:@available)
|
|
65
|
+
remove_instance_variable(:@version) if instance_variable_defined?(:@version)
|
|
66
|
+
remove_instance_variable(:@check_status) if instance_variable_defined?(:@check_status)
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
# Test mmdc with a minimal diagram to verify Puppeteer works
|
|
68
70
|
#
|
|
69
71
|
# @return [Symbol] :ok, :puppeteer_error, or :unknown_error
|
|
70
|
-
def test_render
|
|
71
|
-
input = Tempfile.new(["
|
|
72
|
-
output = Tempfile.new(["
|
|
73
|
-
|
|
72
|
+
def self.test_render
|
|
73
|
+
input = Tempfile.new(["", ".mmd"])
|
|
74
|
+
output = Tempfile.new(["", ".svg"])
|
|
74
75
|
begin
|
|
75
76
|
input.write("graph TD\nA-->B")
|
|
76
77
|
input.close
|
|
77
78
|
output.close # Close before mmdc writes (Windows file locking)
|
|
78
79
|
|
|
79
|
-
_stdout, stderr, status = Open3.capture3(
|
|
80
|
+
_stdout, stderr, status = Open3.capture3(
|
|
81
|
+
MMDC_COMMAND, "-i", input.path, "-o", output.path, "-e", "svg"
|
|
82
|
+
)
|
|
80
83
|
|
|
81
84
|
if status.success?
|
|
82
85
|
:ok
|
|
@@ -86,8 +89,8 @@ module JekyllMermaidPrebuild
|
|
|
86
89
|
:unknown_error
|
|
87
90
|
end
|
|
88
91
|
ensure
|
|
89
|
-
input.
|
|
90
|
-
output.
|
|
92
|
+
input.unlink
|
|
93
|
+
output.unlink
|
|
91
94
|
end
|
|
92
95
|
end
|
|
93
96
|
|
|
@@ -95,15 +98,22 @@ module JekyllMermaidPrebuild
|
|
|
95
98
|
#
|
|
96
99
|
# @param mermaid_source [String] mermaid diagram definition
|
|
97
100
|
# @param output_path [String] path to write SVG file
|
|
101
|
+
ALLOWED_RENDER_THEMES = %i[default dark].freeze
|
|
102
|
+
|
|
103
|
+
# @param theme [Symbol] :default (mermaid default theme) or :dark (mmdc -t dark)
|
|
98
104
|
# @return [Boolean] true if successful
|
|
99
|
-
|
|
105
|
+
# @raise [ArgumentError] if theme is not supported
|
|
106
|
+
def self.render(mermaid_source, output_path, theme: :default)
|
|
107
|
+
raise ArgumentError, "unsupported mmdc theme #{theme.inspect}" unless ALLOWED_RENDER_THEMES.include?(theme)
|
|
108
|
+
|
|
100
109
|
input_file = Tempfile.new(["mermaid", ".mmd"])
|
|
101
110
|
|
|
102
111
|
begin
|
|
103
112
|
input_file.write(mermaid_source)
|
|
104
113
|
input_file.close
|
|
105
114
|
|
|
106
|
-
cmd = [
|
|
115
|
+
cmd = [MMDC_COMMAND, "-i", input_file.path, "-o", output_path, "-e", "svg"]
|
|
116
|
+
cmd += ["-t", "dark"] if theme == :dark
|
|
107
117
|
_stdout, _stderr, status = Open3.capture3(*cmd)
|
|
108
118
|
|
|
109
119
|
status.success?
|
|
@@ -116,7 +126,7 @@ module JekyllMermaidPrebuild
|
|
|
116
126
|
# Supports both backtick (```) and tilde (~~~) fences with 3+ characters
|
|
117
127
|
#
|
|
118
128
|
# @return [Regexp] pattern matching mermaid code blocks
|
|
119
|
-
def mermaid_fence_pattern
|
|
129
|
+
def self.mermaid_fence_pattern
|
|
120
130
|
/
|
|
121
131
|
^(`{3,}|~{3,})mermaid\s*\n # Opening fence: 3+ backticks or tildes, then 'mermaid'
|
|
122
132
|
(.*?) # Content (captured, non-greedy)
|
|
@@ -22,7 +22,7 @@ module JekyllMermaidPrebuild
|
|
|
22
22
|
# @param _site [Jekyll::Site] the Jekyll site (unused, kept for API compatibility)
|
|
23
23
|
# @return [Array<String, Integer, Hash>] [processed_content, count, svgs_to_copy]
|
|
24
24
|
def process_content(content, _site = nil)
|
|
25
|
-
return [
|
|
25
|
+
return [nil, 0, {}] unless content
|
|
26
26
|
|
|
27
27
|
converted_count = 0
|
|
28
28
|
svgs_to_copy = {}
|
|
@@ -36,21 +36,23 @@ module JekyllMermaidPrebuild
|
|
|
36
36
|
result = convert_block(block)
|
|
37
37
|
next unless result
|
|
38
38
|
|
|
39
|
+
result => { svgs:, html: }
|
|
40
|
+
block => { start:, end: block_end }
|
|
39
41
|
converted_count += 1
|
|
40
|
-
svgs_to_copy
|
|
41
|
-
processed[
|
|
42
|
+
svgs_to_copy.merge!(svgs)
|
|
43
|
+
processed[start...block_end] = html
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
[processed, converted_count, svgs_to_copy]
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
private
|
|
48
|
-
|
|
49
49
|
# @param source [String] mermaid passed to mmdc (after optional emoji compensation)
|
|
50
|
-
# @param diagram_type [String, nil]
|
|
51
50
|
# @return [String] input to MD5 for cache key
|
|
52
|
-
def digest_string_for_cache(source
|
|
51
|
+
def digest_string_for_cache(source)
|
|
53
52
|
parts = [source]
|
|
53
|
+
parts << "pcs=#{@config.prefers_color_scheme}"
|
|
54
|
+
parts << "bgL=#{@config.chart_background_light}"
|
|
55
|
+
parts << "bgD=#{@config.chart_background_dark}"
|
|
54
56
|
parts << "tc=#{@config.text_centering}" unless @config.text_centering
|
|
55
57
|
parts << "op=#{@config.overflow_protection}" unless @config.overflow_protection
|
|
56
58
|
pad = @config.edge_label_padding
|
|
@@ -61,23 +63,30 @@ module JekyllMermaidPrebuild
|
|
|
61
63
|
# Convert a single mermaid block to SVG
|
|
62
64
|
#
|
|
63
65
|
# @param block [Hash] block info with :content key
|
|
64
|
-
# @return [Hash, nil] {
|
|
66
|
+
# @return [Hash, nil] {:svgs, :html} or nil if failed
|
|
65
67
|
def convert_block(block)
|
|
66
68
|
mermaid_source = block[:content]
|
|
67
69
|
diagram_type = EmojiCompensator.detect_diagram_type(mermaid_source)
|
|
68
|
-
source_for_render = if
|
|
70
|
+
source_for_render = if @config.emoji_width_compensation[diagram_type]
|
|
69
71
|
EmojiCompensator.compensate(mermaid_source, diagram_type)
|
|
70
72
|
else
|
|
71
73
|
mermaid_source
|
|
72
74
|
end
|
|
73
|
-
digest_input = digest_string_for_cache(source_for_render
|
|
75
|
+
digest_input = digest_string_for_cache(source_for_render)
|
|
74
76
|
cache_key = DigestCalculator.content_digest(digest_input)
|
|
75
|
-
|
|
77
|
+
paths = @generator.generate(source_for_render, cache_key)
|
|
78
|
+
|
|
79
|
+
return nil if paths.nil? || paths.empty?
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
light_url = @generator.build_svg_url(cache_key)
|
|
82
|
+
html = if @config.prefers_color_scheme == :auto
|
|
83
|
+
dark_url = @generator.build_svg_url("#{cache_key}-dark")
|
|
84
|
+
@generator.build_figure_html(light_url, dark_url: dark_url)
|
|
85
|
+
else
|
|
86
|
+
@generator.build_figure_html(light_url)
|
|
87
|
+
end
|
|
78
88
|
|
|
79
|
-
|
|
80
|
-
{ cache_key: cache_key, cached_path: cached_path, html: @generator.build_figure_html(svg_url) }
|
|
89
|
+
{ svgs: paths, html: html }
|
|
81
90
|
end
|
|
82
91
|
|
|
83
92
|
# Find all top-level mermaid code blocks (not nested inside other fences)
|
|
@@ -85,13 +94,14 @@ module JekyllMermaidPrebuild
|
|
|
85
94
|
# @param content [String] markdown content
|
|
86
95
|
# @return [Array<Hash>] array of {start:, end:, content:} for each top-level mermaid block
|
|
87
96
|
def find_top_level_mermaid_blocks(content)
|
|
88
|
-
|
|
97
|
+
blocks = []
|
|
98
|
+
state = { blocks: blocks, fence_stack: [], position: 0 }
|
|
89
99
|
|
|
90
|
-
content.lines
|
|
100
|
+
content.lines do |line|
|
|
91
101
|
process_line(line, state)
|
|
92
102
|
end
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
blocks
|
|
95
105
|
end
|
|
96
106
|
|
|
97
107
|
# Process a single line for fence detection
|
|
@@ -102,8 +112,9 @@ module JekyllMermaidPrebuild
|
|
|
102
112
|
match = line.match(FENCE_OPENER)
|
|
103
113
|
if match
|
|
104
114
|
handle_fence_line(line, line_start, match, state)
|
|
105
|
-
elsif state[:current_mermaid]
|
|
106
|
-
|
|
115
|
+
elsif (current_mermaid = state[:current_mermaid])
|
|
116
|
+
content_lines = current_mermaid[:content_lines]
|
|
117
|
+
content_lines << line
|
|
107
118
|
end
|
|
108
119
|
end
|
|
109
120
|
|
|
@@ -111,7 +122,7 @@ module JekyllMermaidPrebuild
|
|
|
111
122
|
def handle_fence_line(line, line_start, match, state)
|
|
112
123
|
fence_chars = match[1]
|
|
113
124
|
language = match[2]
|
|
114
|
-
fence_type = fence_chars
|
|
125
|
+
fence_type = fence_chars.include?("~") ? "~" : "`"
|
|
115
126
|
fence_length = fence_chars.length
|
|
116
127
|
|
|
117
128
|
if state[:current_mermaid]
|
|
@@ -126,11 +137,14 @@ module JekyllMermaidPrebuild
|
|
|
126
137
|
# Handle fence line while inside a mermaid block
|
|
127
138
|
def handle_line_in_mermaid(line, fence_chars, fence_type, fence_length, state)
|
|
128
139
|
cm = state[:current_mermaid]
|
|
140
|
+
blocks = state[:blocks]
|
|
141
|
+
position = state[:position]
|
|
142
|
+
content_lines = cm[:content_lines]
|
|
129
143
|
if fence_type == cm[:fence_type] && fence_length == cm[:fence_length] && line.strip == fence_chars
|
|
130
|
-
|
|
144
|
+
blocks << { start: cm[:start], end: position, content: content_lines.join }
|
|
131
145
|
state[:current_mermaid] = nil
|
|
132
146
|
else
|
|
133
|
-
|
|
147
|
+
content_lines << line
|
|
134
148
|
end
|
|
135
149
|
end
|
|
136
150
|
|
|
@@ -146,12 +160,13 @@ module JekyllMermaidPrebuild
|
|
|
146
160
|
|
|
147
161
|
# Handle fence line while inside a non-mermaid fence
|
|
148
162
|
def handle_line_in_nested_fence(line, fence_type, fence_length, state)
|
|
149
|
-
|
|
163
|
+
fence_stack = state[:fence_stack]
|
|
164
|
+
top_fence_length, top_fence_type = fence_stack.last
|
|
150
165
|
|
|
151
|
-
if fence_type == top_fence_type && fence_length >= top_fence_length && line.strip.match?(
|
|
152
|
-
|
|
166
|
+
if fence_type == top_fence_type && fence_length >= top_fence_length && line.strip.match?(/\A[`~]+\z/)
|
|
167
|
+
fence_stack.pop
|
|
153
168
|
else
|
|
154
|
-
|
|
169
|
+
fence_stack.push([fence_length, fence_type])
|
|
155
170
|
end
|
|
156
171
|
end
|
|
157
172
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module JekyllMermaidPrebuild
|
|
4
|
-
# Post-processes mmdc-generated SVGs to fix
|
|
4
|
+
# Post-processes mmdc-generated SVGs to fix rendering issues.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# Four independent fixes:
|
|
7
7
|
# 1. Text centering: Mermaid's CSS `text-align: center` targets SVG `<g>` elements where it
|
|
8
8
|
# has no effect on HTML inside `<foreignObject>`. We inject a CSS rule so that foreignObject
|
|
9
9
|
# content centers correctly regardless of text measurement differences between the generating
|
|
@@ -14,9 +14,10 @@ module JekyllMermaidPrebuild
|
|
|
14
14
|
# 3. Edge label padding: Widens edge-label `<foreignObject>` widths in any diagram type to
|
|
15
15
|
# prevent clipping when the viewing browser renders text wider than headless Chromium measured.
|
|
16
16
|
# Opt-in via `postprocessing.edge_label_padding` config.
|
|
17
|
+
# 4. Root SVG background: mmdc always emits `background-color: white` on the root `<svg>` regardless
|
|
18
|
+
# of theme. The plugin replaces that token with configurable CSS color values for light and
|
|
19
|
+
# dark variants (defaults white / black) so charts match page background in both modes.
|
|
17
20
|
module SvgPostProcessor
|
|
18
|
-
module_function
|
|
19
|
-
|
|
20
21
|
# Opening sequence produced by mmdc for block edge labels (deterministic minified output).
|
|
21
22
|
EDGE_LABEL_FOREIGN_OBJECT_RE = /
|
|
22
23
|
(<g\sclass="edgeLabel"[^>]*><g\sclass="label"[^>]*><foreignObject)
|
|
@@ -27,9 +28,8 @@ module JekyllMermaidPrebuild
|
|
|
27
28
|
# @param svg_string [String] full SVG document from mmdc
|
|
28
29
|
# @param padding [Numeric] user units to add to each matching foreignObject width (must be positive)
|
|
29
30
|
# @return [String] possibly widened SVG, or the original string on no-op / error
|
|
30
|
-
def apply(svg_string, padding:)
|
|
31
|
-
return svg_string unless
|
|
32
|
-
return svg_string unless padding.is_a?(Numeric) && padding.positive?
|
|
31
|
+
def self.apply(svg_string, padding:)
|
|
32
|
+
return svg_string unless padding.positive?
|
|
33
33
|
|
|
34
34
|
apply_edge_label_padding(svg_string, padding)
|
|
35
35
|
rescue StandardError
|
|
@@ -46,10 +46,8 @@ module JekyllMermaidPrebuild
|
|
|
46
46
|
#
|
|
47
47
|
# @param svg_string [String] full SVG document from mmdc
|
|
48
48
|
# @return [String] SVG with overflow rule injected, or original on no-op / error
|
|
49
|
-
def ensure_foreignobject_overflow(svg_string)
|
|
50
|
-
return svg_string unless svg_string.is_a?(String)
|
|
49
|
+
def self.ensure_foreignobject_overflow(svg_string)
|
|
51
50
|
return svg_string if svg_string.include?(OVERFLOW_RULE)
|
|
52
|
-
return svg_string unless svg_string.include?("</style>")
|
|
53
51
|
|
|
54
52
|
svg_string.sub("</style>", "#{OVERFLOW_RULE}</style>")
|
|
55
53
|
rescue StandardError
|
|
@@ -65,17 +63,33 @@ module JekyllMermaidPrebuild
|
|
|
65
63
|
#
|
|
66
64
|
# @param svg_string [String] full SVG document from mmdc
|
|
67
65
|
# @return [String] SVG with centering rule injected, or original on no-op / error
|
|
68
|
-
def ensure_text_centering(svg_string)
|
|
69
|
-
return svg_string unless svg_string.is_a?(String)
|
|
66
|
+
def self.ensure_text_centering(svg_string)
|
|
70
67
|
return svg_string if svg_string.include?(CENTERING_RULE)
|
|
71
|
-
return svg_string unless svg_string.include?("</style>")
|
|
72
68
|
|
|
73
69
|
svg_string.sub("</style>", "#{CENTERING_RULE}</style>")
|
|
74
70
|
rescue StandardError
|
|
75
71
|
svg_string
|
|
76
72
|
end
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
# Replace `background-color: white` on the root <svg> style attribute with a caller-supplied
|
|
75
|
+
# CSS color (already sanitized by Configuration). Idempotent when mmdc output no longer contains
|
|
76
|
+
# the white token or the value already matches.
|
|
77
|
+
#
|
|
78
|
+
# @param svg_string [String] full SVG document from mmdc
|
|
79
|
+
# @param css_background [String] literal after `background-color:` (e.g. "black", "#fff0aa")
|
|
80
|
+
# @return [String] SVG with updated root background, or original on no-op / error
|
|
81
|
+
def self.apply_root_svg_background(svg_string, css_background)
|
|
82
|
+
return svg_string unless css_background.is_a?(String) && !css_background.empty?
|
|
83
|
+
|
|
84
|
+
svg_string.sub(
|
|
85
|
+
/(<svg\b[^>]+\bstyle="[^"]*?)background-color:\s*white;?/,
|
|
86
|
+
"\\1background-color: #{css_background};"
|
|
87
|
+
)
|
|
88
|
+
rescue StandardError
|
|
89
|
+
svg_string
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.apply_edge_label_padding(svg_string, padding)
|
|
79
93
|
svg_string.gsub(EDGE_LABEL_FOREIGN_OBJECT_RE) do
|
|
80
94
|
prefix = Regexp.last_match(1)
|
|
81
95
|
attrs = Regexp.last_match(2)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-mermaid-prebuild
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Texarkanine
|
|
@@ -29,6 +29,34 @@ dependencies:
|
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '5.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: mutant
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0.16'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0.16'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: mutant-rspec
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0.16'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0.16'
|
|
32
60
|
- !ruby/object:Gem::Dependency
|
|
33
61
|
name: rake
|
|
34
62
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -105,28 +133,28 @@ dependencies:
|
|
|
105
133
|
requirements:
|
|
106
134
|
- - "~>"
|
|
107
135
|
- !ruby/object:Gem::Version
|
|
108
|
-
version: '0
|
|
136
|
+
version: '1.0'
|
|
109
137
|
type: :development
|
|
110
138
|
prerelease: false
|
|
111
139
|
version_requirements: !ruby/object:Gem::Requirement
|
|
112
140
|
requirements:
|
|
113
141
|
- - "~>"
|
|
114
142
|
- !ruby/object:Gem::Version
|
|
115
|
-
version: '0
|
|
143
|
+
version: '1.0'
|
|
116
144
|
- !ruby/object:Gem::Dependency
|
|
117
145
|
name: simplecov-cobertura
|
|
118
146
|
requirement: !ruby/object:Gem::Requirement
|
|
119
147
|
requirements:
|
|
120
148
|
- - "~>"
|
|
121
149
|
- !ruby/object:Gem::Version
|
|
122
|
-
version: '
|
|
150
|
+
version: '4.0'
|
|
123
151
|
type: :development
|
|
124
152
|
prerelease: false
|
|
125
153
|
version_requirements: !ruby/object:Gem::Requirement
|
|
126
154
|
requirements:
|
|
127
155
|
- - "~>"
|
|
128
156
|
- !ruby/object:Gem::Version
|
|
129
|
-
version: '
|
|
157
|
+
version: '4.0'
|
|
130
158
|
description: Jekyll plugin that converts mermaid diagram code blocks to SVG files
|
|
131
159
|
at build time using the mmdc CLI. Eliminates the need for client-side mermaid.js
|
|
132
160
|
(~2MB) by generating static SVG files with intelligent caching.
|