ligarb 0.9.1 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fff2a914d9209b69c1b9c35d4133accfe0d5dcc4e272779959baa9c3bba64cf0
4
- data.tar.gz: da08e338979034ba836c2321adb4820f91231e942a6bedb0e7ffb31128271c52
3
+ metadata.gz: 4ba0a513e610d71f30b92bf61b42e2acdab5516e6105b56a43603692c60a3a9a
4
+ data.tar.gz: 5824b715da62d8d568d597a2edc0dc9a85dc0b7b7ef028499d7ce9cbcb65d161
5
5
  SHA512:
6
- metadata.gz: 6880ee104849e3904dad11583a8b635dc3bf105acea215df197ab4acbe1dcffa432b5c45c0c471d84424f22c7f287c2b11dc553f987c99d2613948d1f01eb766
7
- data.tar.gz: c230438379c7ff16c808b38835fe0ea764b88433e109ce0573caba710163ec3efddb86975251faa145525176946a543b70eb44670fc55718998fade9098bf7c7
6
+ metadata.gz: c58b836062f00ef965e59f44fef837fe0551d22a57f93f957b0a25d718b18ef79447b11b50f8c4a52540fec9d96656397266d4500ecacc60e42faa474099abd3
7
+ data.tar.gz: f74f174bc842665f9d68387381cd92cfc9ee48745f1bf339d3f6393e96b17a7ddec0adbd561f8e56cfc92fd6a6b9eac6d3b102e4abc1e9f071407baa7870bbd2
@@ -47,7 +47,11 @@ function makeStub(name, overrides = {}) {
47
47
  globalThis.window = globalThis;
48
48
  // nodeType 9 = DOCUMENT_NODE; DOMPurify checks it to decide it has a real DOM.
49
49
  globalThis.document = makeStub("document", { nodeType: 9 });
50
- globalThis.navigator = { userAgent: "node" };
50
+ // Node >= 21 ships a built-in read-only `navigator` global; assigning to it
51
+ // throws a TypeError. Only define our stub when the runtime lacks one.
52
+ if (!("navigator" in globalThis)) {
53
+ globalThis.navigator = { userAgent: "node" };
54
+ }
51
55
  globalThis.addEventListener = noop;
52
56
  globalThis.location = { href: "http://localhost/", protocol: "http:" };
53
57
  globalThis.Element = function Element() {};
data/assets/style.css CHANGED
@@ -77,6 +77,22 @@ body {
77
77
  opacity: 1;
78
78
  }
79
79
 
80
+ .ai-md-hint {
81
+ margin: 0 0 1.5rem;
82
+ font-size: 0.8rem;
83
+ color: var(--color-text-muted);
84
+ opacity: 0.8;
85
+ }
86
+
87
+ .ai-md-hint a {
88
+ color: inherit;
89
+ text-decoration: underline;
90
+ }
91
+
92
+ @media print {
93
+ .ai-md-hint { display: none; }
94
+ }
95
+
80
96
  .book-title {
81
97
  font-size: 1.1rem;
82
98
  font-weight: 700;
data/docs/help.md CHANGED
@@ -83,6 +83,23 @@ The output directory contains index.html plus js/ and css/ subdirectories
83
83
  for auto-downloaded libraries (highlight.js, mermaid, KaTeX, etc.).
84
84
  Open index.html directly in a browser — no web server needed.
85
85
 
86
+ The build also emits a Markdown full-text version of the book next to
87
+ index.html, intended for AI/LLM readers (HTML is noisy and costly to read;
88
+ clean Markdown is cheaper and more faithful). For a single-language build it
89
+ is `index.md`; for a multi-language (hub) build it is `index.<lang>.md` per
90
+ language. Each is the concatenated chapter sources in reading order. The HTML
91
+ points to it two ways: a small visible note at the top of the content ("For
92
+ AI/LLM readers: a clean Markdown full-text version of this book is at …") and
93
+ a `<link rel="alternate" type="text/markdown">` in the head. Both use a
94
+ relative href (the bare filename), so they resolve against whatever URL the
95
+ page is actually served from — local preview, staging, or production — rather
96
+ than hard-coding `site_url` (the absolute canonical URL still lives in
97
+ `og:url` / `<link rel="canonical">`). A fetcher resolves it against the page's
98
+ own URL, so a model that fetched the page finds the bundle next to it. No tool
99
+ is guaranteed to honor these automatically —
100
+ the visible note is what actually reaches a model that fetched the page,
101
+ since the in-page text is included in what the fetcher hands to the model.
102
+
86
103
  When `github_review.enabled` is true and `repository` is set (see
87
104
  Configuration below), the build also injects a static "Report as issue"
88
105
  feedback UI: readers select text, pick a type and add a comment, and a
@@ -41,13 +41,18 @@ module Ligarb
41
41
 
42
42
  bibliography = resolve_citations!(all_chapters)
43
43
 
44
+ FileUtils.mkdir_p(@config.output_path)
45
+
46
+ md_name = "index.md"
47
+ write_markdown_bundle(all_chapters, File.join(@config.output_path, md_name))
48
+
44
49
  html = Template.new.render(config: @config, chapters: all_chapters,
45
50
  structure: structure, assets: assets,
46
51
  index_entries: index_entries,
47
52
  bibliography: bibliography,
48
- github_review: github_review_data(@config))
53
+ github_review: github_review_data(@config),
54
+ ai_md: md_name)
49
55
 
50
- FileUtils.mkdir_p(@config.output_path)
51
56
  output_file = File.join(@config.output_path, "index.html")
52
57
  File.write(output_file, html)
53
58
 
@@ -76,6 +81,14 @@ module Ligarb
76
81
  all_lang_chapters.concat(lang_data[:chapters])
77
82
  end
78
83
 
84
+ FileUtils.mkdir_p(output_path)
85
+
86
+ langs.each do |ld|
87
+ md_name = "index.#{ld[:lang]}.md"
88
+ write_markdown_bundle(ld[:chapters], File.join(output_path, md_name))
89
+ ld[:ai_md] = md_name
90
+ end
91
+
79
92
  assets = AssetManager.new(output_path)
80
93
  assets.detect(all_lang_chapters)
81
94
  assets.provision!
@@ -428,6 +441,14 @@ module Ligarb
428
441
  text.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
429
442
  end
430
443
 
444
+ # Emit a clean Markdown full-text version of the book alongside index.html.
445
+ # AI/LLM readers (and the in-page hint) point here for low-noise content.
446
+ # The bundle is the concatenated chapter sources in flat reading order.
447
+ def write_markdown_bundle(chapters, path)
448
+ body = chapters.map { |ch| ch.source.strip }.reject(&:empty?).join("\n\n")
449
+ File.write(path, body.empty? ? body : "#{body}\n")
450
+ end
451
+
431
452
  def copy_images
432
453
  images_dir = File.join(@config.base_dir, "images")
433
454
  return unless Dir.exist?(images_dir)
@@ -8,7 +8,7 @@ module Ligarb
8
8
  class CrossReferenceError < StandardError; end
9
9
 
10
10
  attr_reader :title, :slug, :html, :headings, :number, :appendix_letter, :index_entries, :cite_entries,
11
- :path, :mermaid_blocks
11
+ :path, :mermaid_blocks, :source
12
12
  attr_accessor :part_title, :cover, :relative_path
13
13
 
14
14
  Heading = Struct.new(:level, :text, :id, :display_text, keyword_init: true)
data/lib/ligarb/config.rb CHANGED
@@ -233,6 +233,9 @@ module Ligarb
233
233
 
234
234
  def load_translations_hub(data)
235
235
  @title = data.fetch("title", "")
236
+ # The hub writes its combined build here; serve needs output_path to work
237
+ # even though hub init returns before the normal output_dir assignment.
238
+ @output_dir = data.fetch("output_dir", "build")
236
239
  @translations = []
237
240
 
238
241
  trans = data["translations"]
@@ -13,7 +13,7 @@ module Ligarb
13
13
  @css_path = File.join(ASSETS_DIR, "style.css")
14
14
  end
15
15
 
16
- def render(config:, chapters:, structure:, assets:, index_entries: [], bibliography: [], github_review: nil)
16
+ def render(config:, chapters:, structure:, assets:, index_entries: [], bibliography: [], github_review: nil, ai_md: nil)
17
17
  css = File.read(@css_path)
18
18
  template = File.read(@template_path)
19
19
 
@@ -42,6 +42,7 @@ module Ligarb
42
42
  b.local_variable_set(:og_description, og_description(config, chapters))
43
43
  b.local_variable_set(:og_locale, og_locale(config.language))
44
44
  b.local_variable_set(:og_url, og_url(config))
45
+ b.local_variable_set(:ai_md, ai_md)
45
46
 
46
47
  ERB.new(template, trim_mode: "-").result(b)
47
48
  end
@@ -74,6 +75,7 @@ module Ligarb
74
75
  footer: cfg.effective_footer,
75
76
  index_tree: build_index_tree(ld[:index_entries], ld[:chapters]),
76
77
  bibliography: ld[:bibliography],
78
+ ai_md: ld[:ai_md],
77
79
  }
78
80
  end
79
81
 
@@ -99,6 +101,7 @@ module Ligarb
99
101
  b.local_variable_set(:og_description, og_description(first_config, first[:chapters]))
100
102
  b.local_variable_set(:og_locale, og_locale(first_config.language))
101
103
  b.local_variable_set(:og_url, og_url(first_config))
104
+ b.local_variable_set(:ai_md, nil)
102
105
 
103
106
  ERB.new(template, trim_mode: "-").result(b)
104
107
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ligarb
4
- VERSION = "0.9.1"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -17,6 +17,15 @@
17
17
  <meta property="og:description" content="<%= h(og_description) %>">
18
18
  <%- end -%>
19
19
  <meta name="twitter:card" content="summary">
20
+ <%- if multilang -%>
21
+ <%- langs.each do |ld| -%>
22
+ <%- if ld[:ai_md] -%>
23
+ <link rel="alternate" type="text/markdown" hreflang="<%= h(ld[:language]) %>" href="<%= h(ld[:ai_md]) %>" title="<%= h(ld[:title]) %> (Markdown)">
24
+ <%- end -%>
25
+ <%- end -%>
26
+ <%- elsif ai_md -%>
27
+ <link rel="alternate" type="text/markdown" href="<%= h(ai_md) %>" title="Markdown version">
28
+ <%- end -%>
20
29
  <%- if author && !author.to_s.empty? -%>
21
30
  <meta property="book:author" content="<%= h(author) %>">
22
31
  <%- end -%>
@@ -272,6 +281,15 @@
272
281
  <button class="sidebar-toggle" id="sidebar-toggle" aria-label="Toggle sidebar">&#9776;</button>
273
282
 
274
283
  <main class="content" id="content">
284
+ <%- if multilang -%>
285
+ <%- langs.each do |ld| -%>
286
+ <%- if ld[:ai_md] -%>
287
+ <p class="ai-md-hint lang-content" data-lang="<%= ld[:lang] %>">For AI/LLM readers: a clean Markdown full-text version of this book is at <a href="<%= h(ld[:ai_md]) %>"><%= h(ld[:ai_md]) %></a>.</p>
288
+ <%- end -%>
289
+ <%- end -%>
290
+ <%- elsif ai_md -%>
291
+ <p class="ai-md-hint">For AI/LLM readers: a clean Markdown full-text version of this book is at <a href="<%= h(ai_md) %>"><%= h(ai_md) %></a>.</p>
292
+ <%- end -%>
275
293
  <%- if multilang -%>
276
294
  <%- langs.each do |ld| -%>
277
295
  <%- ld[:chapters].select(&:cover?).each do |chapter| -%>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ligarb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ligarb contributors