ligarb 0.7.0 → 0.8.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/assets/feedback.css +123 -0
- data/assets/feedback.js +194 -0
- data/lib/ligarb/builder.rb +23 -2
- data/lib/ligarb/claude_runner.rb +2 -1
- data/lib/ligarb/cli.rb +10 -625
- data/lib/ligarb/config.rb +25 -2
- data/lib/ligarb/github_review.rb +245 -0
- data/lib/ligarb/template.rb +33 -2
- data/lib/ligarb/version.rb +1 -1
- data/lib/ligarb/writer.rb +2 -2
- data/templates/book.html.erb +29 -6
- data/templates/github_review/.github/ISSUE_TEMPLATE/book-feedback.yml +43 -0
- data/templates/github_review/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/templates/github_review/.github/workflows/build-check.yml +25 -0
- data/templates/github_review/.github/workflows/claude-feedback.yml +128 -0
- data/templates/github_review/.github/workflows/claude-pr-mention.yml +121 -0
- data/templates/github_review/.github/workflows/deploy-book.yml +45 -0
- data/templates/github_review/SETUP.md +167 -0
- data/templates/github_review/SETUP.sh +49 -0
- metadata +12 -1
data/lib/ligarb/config.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Ligarb
|
|
|
10
10
|
# output_dir is intentionally excluded — it always comes from the
|
|
11
11
|
# config file that was directly passed to `ligarb build`.
|
|
12
12
|
INHERITABLE_KEYS = %w[author language chapter_numbers style
|
|
13
|
-
repository ai_generated footer bibliography
|
|
13
|
+
repository ai_generated footer bibliography
|
|
14
|
+
github_review].freeze
|
|
14
15
|
|
|
15
16
|
# Represents a structural entry in the book
|
|
16
17
|
StructEntry = Struct.new(:type, :path, :children, keyword_init: true)
|
|
@@ -25,7 +26,7 @@ module Ligarb
|
|
|
25
26
|
attr_reader :title, :author, :language, :output_dir, :base_dir,
|
|
26
27
|
:chapter_numbers, :structure, :style, :repository,
|
|
27
28
|
:ai_generated, :footer, :bibliography, :sources,
|
|
28
|
-
:translations
|
|
29
|
+
:translations, :github_review
|
|
29
30
|
|
|
30
31
|
def initialize(path, parent_data: nil)
|
|
31
32
|
@base_dir = File.dirname(File.expand_path(path))
|
|
@@ -68,6 +69,7 @@ module Ligarb
|
|
|
68
69
|
@ai_generated = data.fetch("ai_generated", false)
|
|
69
70
|
@footer = data.fetch("footer", nil)
|
|
70
71
|
@bibliography = data.fetch("bibliography", nil)
|
|
72
|
+
@github_review = data.fetch("github_review", nil)
|
|
71
73
|
@sources = parse_sources(data.fetch("sources", []))
|
|
72
74
|
@structure = parse_structure(data["chapters"])
|
|
73
75
|
@translations = []
|
|
@@ -95,6 +97,23 @@ module Ligarb
|
|
|
95
97
|
@language == "ja" ? "付録" : "Appendix"
|
|
96
98
|
end
|
|
97
99
|
|
|
100
|
+
# Whether the reader's "Report as issue" feedback UI is requested in book.yml.
|
|
101
|
+
# This reflects intent only; actual injection also requires `repository`
|
|
102
|
+
# (the issues/new base) — the builder warns and skips when it is missing.
|
|
103
|
+
def github_review_enabled?
|
|
104
|
+
@github_review.is_a?(Hash) && @github_review.fetch("enabled", false) == true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def github_review_issue_template
|
|
108
|
+
tmpl = @github_review.is_a?(Hash) ? @github_review["issue_template"] : nil
|
|
109
|
+
tmpl && !tmpl.to_s.empty? ? tmpl.to_s : "book-feedback.yml"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def github_review_labels
|
|
113
|
+
labels = @github_review.is_a?(Hash) ? @github_review["labels"] : nil
|
|
114
|
+
Array(labels || ["feedback"]).map(&:to_s)
|
|
115
|
+
end
|
|
116
|
+
|
|
98
117
|
def effective_footer
|
|
99
118
|
return @footer if @footer
|
|
100
119
|
return nil unless @ai_generated
|
|
@@ -254,6 +273,10 @@ module Ligarb
|
|
|
254
273
|
unless data["chapters"].is_a?(Array) && !data["chapters"].empty?
|
|
255
274
|
abort "Error: 'chapters' must be a non-empty array"
|
|
256
275
|
end
|
|
276
|
+
|
|
277
|
+
if data.key?("github_review") && !data["github_review"].is_a?(Hash)
|
|
278
|
+
abort "Error: 'github_review' must be a mapping (e.g. { enabled: true })"
|
|
279
|
+
end
|
|
257
280
|
end
|
|
258
281
|
end
|
|
259
282
|
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Ligarb
|
|
7
|
+
# Sets up the GitHub-based review scaffolding (.github/ + SETUP.md) in a
|
|
8
|
+
# project — the `ligarb setup-github-review` command. This is pure file
|
|
9
|
+
# copying: ligarb never calls Claude or GitHub at runtime. The templates are
|
|
10
|
+
# classified into layers so a future option could split them apart:
|
|
11
|
+
# - generic layer : works without Claude (Pages deploy, build check, forms)
|
|
12
|
+
# - claude layer : opt-in Claude integration (issue/PR handlers, SETUP.md)
|
|
13
|
+
#
|
|
14
|
+
# Re-running OVERWRITES the generated files so a project can follow upstream
|
|
15
|
+
# template changes (e.g. after a ligarb upgrade). The user's own book.yml is
|
|
16
|
+
# never overwritten. Since projects are git repos, `git diff` is the safety
|
|
17
|
+
# net for reviewing/reverting changes after a re-sync.
|
|
18
|
+
class GithubReview
|
|
19
|
+
TEMPLATE_DIR = File.expand_path("../../templates/github_review", __dir__)
|
|
20
|
+
|
|
21
|
+
# Generic layer: no Claude dependency.
|
|
22
|
+
GENERIC_FILES = %w[
|
|
23
|
+
.github/workflows/deploy-book.yml
|
|
24
|
+
.github/workflows/build-check.yml
|
|
25
|
+
.github/ISSUE_TEMPLATE/book-feedback.yml
|
|
26
|
+
.github/ISSUE_TEMPLATE/config.yml
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
# Claude integration layer: opt-in.
|
|
30
|
+
CLAUDE_FILES = %w[
|
|
31
|
+
.github/workflows/claude-feedback.yml
|
|
32
|
+
.github/workflows/claude-pr-mention.yml
|
|
33
|
+
SETUP.md
|
|
34
|
+
SETUP.sh
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
TEMPLATE_FILES = (GENERIC_FILES + CLAUDE_FILES).freeze
|
|
38
|
+
|
|
39
|
+
Result = Struct.new(:created, :updated, :unchanged, keyword_init: true)
|
|
40
|
+
|
|
41
|
+
# `ligarb setup-github-review [DIR]` entry point. Sets up the scaffolding in
|
|
42
|
+
# an existing ligarb project (book.yml must exist), enables the reader
|
|
43
|
+
# feedback UI in book.yml, and prints the remaining manual-setup steps.
|
|
44
|
+
# Safe to re-run to pull in updated templates (generated files are
|
|
45
|
+
# overwritten; book.yml is not).
|
|
46
|
+
def self.run(directory = nil)
|
|
47
|
+
target = File.expand_path(directory || ".")
|
|
48
|
+
unless File.exist?(File.join(target, "book.yml"))
|
|
49
|
+
$stderr.puts "Error: book.yml not found in #{target}"
|
|
50
|
+
$stderr.puts "Run 'ligarb init' or 'ligarb write' first, then set up the GitHub review scaffolding."
|
|
51
|
+
exit 1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
reviewer = new(target)
|
|
55
|
+
# book.yml edits must run BEFORE generate so the templates (SETUP.sh,
|
|
56
|
+
# issue forms, README) are substituted with the resolved repository.
|
|
57
|
+
repository = reviewer.ensure_repository_in_book_yml
|
|
58
|
+
enabled = reviewer.enable_in_book_yml
|
|
59
|
+
readme = reviewer.create_readme_if_absent
|
|
60
|
+
result = reviewer.generate
|
|
61
|
+
reviewer.print_notice(result, repository: repository, enabled: enabled, readme: readme)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def initialize(target)
|
|
65
|
+
@target = File.expand_path(target)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Writes all template files into the project, substituting __OWNER__ /
|
|
69
|
+
# __REPO__ from book.yml's `repository:`. Existing files are OVERWRITTEN so a
|
|
70
|
+
# project can follow upstream template changes; only files whose content is
|
|
71
|
+
# already identical are left untouched. Returns a Result listing
|
|
72
|
+
# created/updated/unchanged files.
|
|
73
|
+
def generate
|
|
74
|
+
owner, repo = extract_owner_repo
|
|
75
|
+
created = []
|
|
76
|
+
updated = []
|
|
77
|
+
unchanged = []
|
|
78
|
+
|
|
79
|
+
TEMPLATE_FILES.each do |rel|
|
|
80
|
+
dest = File.join(@target, rel)
|
|
81
|
+
content = render(rel, File.read(File.join(TEMPLATE_DIR, rel)), owner, repo)
|
|
82
|
+
|
|
83
|
+
if !File.exist?(dest)
|
|
84
|
+
write_file(dest, content)
|
|
85
|
+
created << rel
|
|
86
|
+
elsif File.read(dest) == content
|
|
87
|
+
unchanged << rel
|
|
88
|
+
else
|
|
89
|
+
write_file(dest, content)
|
|
90
|
+
updated << rel
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
Result.new(created: created, updated: updated, unchanged: unchanged)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Ensures `github_review.enabled: true` is present in book.yml so the reader
|
|
98
|
+
# feedback UI activates (once `repository` is also set). Appends the key only
|
|
99
|
+
# when absent, preserving existing formatting/comments. Returns :added,
|
|
100
|
+
# :present, or :unsupported (translations hub / unparsable).
|
|
101
|
+
def enable_in_book_yml
|
|
102
|
+
book_yml = File.join(@target, "book.yml")
|
|
103
|
+
data = YAML.safe_load_file(book_yml)
|
|
104
|
+
return :unsupported unless data.is_a?(Hash)
|
|
105
|
+
return :present if data.key?("github_review")
|
|
106
|
+
|
|
107
|
+
content = File.read(book_yml).rstrip
|
|
108
|
+
File.write(book_yml, "#{content}\n\ngithub_review:\n enabled: true\n")
|
|
109
|
+
:added
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Seeds a default `repository:` in book.yml when it has none, guessing
|
|
113
|
+
# https://github.com/<os-user>/<dir-name>. This drives __OWNER__/__REPO__
|
|
114
|
+
# substitution and the GH Pages link; the user edits it if the guess is
|
|
115
|
+
# wrong. Returns :added, :present, or :unsupported. (@default_repository is
|
|
116
|
+
# set to the guessed URL when :added, for the notice.)
|
|
117
|
+
def ensure_repository_in_book_yml
|
|
118
|
+
book_yml = File.join(@target, "book.yml")
|
|
119
|
+
data = YAML.safe_load_file(book_yml)
|
|
120
|
+
return :unsupported unless data.is_a?(Hash)
|
|
121
|
+
return :present if data.key?("repository")
|
|
122
|
+
|
|
123
|
+
user = ENV["USER"] || ENV["USERNAME"] || "your-github-account"
|
|
124
|
+
@default_repository = "https://github.com/#{user}/#{File.basename(@target)}"
|
|
125
|
+
content = File.read(book_yml).rstrip
|
|
126
|
+
File.write(book_yml, %(#{content}\n\nrepository: "#{@default_repository}"\n))
|
|
127
|
+
:added
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Creates a project README.md that links to the published GitHub Pages site,
|
|
131
|
+
# but only when one does not already exist (the reader's own README is never
|
|
132
|
+
# overwritten). Returns :created or :present.
|
|
133
|
+
def create_readme_if_absent
|
|
134
|
+
readme = File.join(@target, "README.md")
|
|
135
|
+
return :present if File.exist?(readme)
|
|
136
|
+
|
|
137
|
+
owner, repo = extract_owner_repo
|
|
138
|
+
pages = owner && repo ? "https://#{owner}.github.io/#{repo}/" : "https://__OWNER__.github.io/__REPO__/"
|
|
139
|
+
issues = owner && repo ? "https://github.com/#{owner}/#{repo}/issues/new?template=book-feedback.yml" \
|
|
140
|
+
: "https://github.com/__OWNER__/__REPO__/issues/new?template=book-feedback.yml"
|
|
141
|
+
title = book_title.to_s.empty? ? "Book" : book_title
|
|
142
|
+
|
|
143
|
+
File.write(readme, <<~MD)
|
|
144
|
+
# #{title}
|
|
145
|
+
|
|
146
|
+
📖 **公開版(GitHub Pages)**: #{pages}
|
|
147
|
+
|
|
148
|
+
この本は [ligarb](https://github.com/ko1/ligarb) で生成しています。
|
|
149
|
+
|
|
150
|
+
## フィードバック
|
|
151
|
+
|
|
152
|
+
本文の誤り・わかりにくい点・疑問は [Issue](#{issues}) からどうぞ。
|
|
153
|
+
公開ページでは本文を選択して「Report as issue」からも送れます。
|
|
154
|
+
|
|
155
|
+
## ローカルでビルド
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
ligarb build # build/index.html を生成
|
|
159
|
+
ligarb serve # ローカルプレビュー
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
セットアップ手順は [SETUP.md](SETUP.md) を参照してください。
|
|
163
|
+
MD
|
|
164
|
+
:created
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def print_notice(result, repository:, enabled:, readme:)
|
|
168
|
+
puts "Set up GitHub review scaffolding in #{@target}:"
|
|
169
|
+
result.created.each { |path| puts " created #{path}" }
|
|
170
|
+
result.updated.each { |path| puts " updated #{path}" }
|
|
171
|
+
puts " created README.md (with the GitHub Pages link)" if readme == :created
|
|
172
|
+
case repository
|
|
173
|
+
when :added then puts " updated book.yml (repository: #{@default_repository})"
|
|
174
|
+
end
|
|
175
|
+
case enabled
|
|
176
|
+
when :added then puts " updated book.yml (github_review.enabled: true)"
|
|
177
|
+
when :present then puts " kept book.yml github_review setting"
|
|
178
|
+
end
|
|
179
|
+
unless result.unchanged.empty?
|
|
180
|
+
puts " unchanged #{result.unchanged.size} file(s) already up to date"
|
|
181
|
+
end
|
|
182
|
+
if result.updated.any?
|
|
183
|
+
puts
|
|
184
|
+
puts "Note: existing scaffolding files were overwritten with the latest"
|
|
185
|
+
puts "templates. Review with 'git diff' and revert any local edits you"
|
|
186
|
+
puts "want to keep."
|
|
187
|
+
end
|
|
188
|
+
puts
|
|
189
|
+
puts "Next: edit 'repository:' in book.yml if the guess is wrong, then run"
|
|
190
|
+
puts "the gh CLI quickstart:"
|
|
191
|
+
puts " bash SETUP.sh # repo create + secret + Pages + permissions + labels"
|
|
192
|
+
puts
|
|
193
|
+
puts "It still needs a token (see SETUP.md): generate one with"
|
|
194
|
+
puts "'claude setup-token' before running SETUP.sh (it prompts for it)."
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def write_file(dest, content)
|
|
200
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
201
|
+
File.write(dest, content)
|
|
202
|
+
File.chmod(0o755, dest) if dest.end_with?(".sh")
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def book_title
|
|
206
|
+
data = YAML.safe_load_file(File.join(@target, "book.yml"))
|
|
207
|
+
data.is_a?(Hash) ? data["title"] : nil
|
|
208
|
+
rescue StandardError
|
|
209
|
+
nil
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Substitute __OWNER__ / __REPO__ placeholders. When repository is unset we
|
|
213
|
+
# leave the placeholders as-is, except config.yml's Discussions link which
|
|
214
|
+
# we comment out so the issue chooser does not show a broken URL.
|
|
215
|
+
def render(rel, content, owner, repo)
|
|
216
|
+
if owner && repo
|
|
217
|
+
content.gsub("__OWNER__", owner).gsub("__REPO__", repo)
|
|
218
|
+
elsif rel == ".github/ISSUE_TEMPLATE/config.yml"
|
|
219
|
+
comment_out_contact_links(content)
|
|
220
|
+
else
|
|
221
|
+
content
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def comment_out_contact_links(content)
|
|
226
|
+
content.lines.map { |line|
|
|
227
|
+
line.start_with?("contact_links:") || line.start_with?(" ") ? "# #{line}" : line
|
|
228
|
+
}.join
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Reads book.yml's `repository:` and extracts [owner, repo].
|
|
232
|
+
# Returns [nil, nil] when book.yml or repository is missing/unparsable.
|
|
233
|
+
def extract_owner_repo
|
|
234
|
+
book_yml = File.join(@target, "book.yml")
|
|
235
|
+
return [nil, nil] unless File.exist?(book_yml)
|
|
236
|
+
|
|
237
|
+
data = YAML.safe_load_file(book_yml)
|
|
238
|
+
url = data.is_a?(Hash) ? data["repository"] : nil
|
|
239
|
+
return [nil, nil] unless url.is_a?(String)
|
|
240
|
+
|
|
241
|
+
m = url.chomp("/").match(%r{github\.com[/:]([^/]+)/([^/]+?)(?:\.git)?\z})
|
|
242
|
+
m ? [m[1], m[2]] : [nil, nil]
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
data/lib/ligarb/template.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
+
require "json"
|
|
4
5
|
|
|
5
6
|
module Ligarb
|
|
6
7
|
class Template
|
|
@@ -12,7 +13,7 @@ module Ligarb
|
|
|
12
13
|
@css_path = File.join(ASSETS_DIR, "style.css")
|
|
13
14
|
end
|
|
14
15
|
|
|
15
|
-
def render(config:, chapters:, structure:, assets:, index_entries: [], bibliography: [])
|
|
16
|
+
def render(config:, chapters:, structure:, assets:, index_entries: [], bibliography: [], github_review: nil)
|
|
16
17
|
css = File.read(@css_path)
|
|
17
18
|
template = File.read(@template_path)
|
|
18
19
|
|
|
@@ -37,12 +38,13 @@ module Ligarb
|
|
|
37
38
|
b.local_variable_set(:bibliography, bibliography)
|
|
38
39
|
b.local_variable_set(:multilang, false)
|
|
39
40
|
b.local_variable_set(:langs, [])
|
|
41
|
+
b.local_variable_set(:feedback, load_feedback(github_review))
|
|
40
42
|
|
|
41
43
|
ERB.new(template, trim_mode: "-").result(b)
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
# Render a single HTML with all languages, switchable via JS
|
|
45
|
-
def render_multilang(langs:, assets:, hub_data:)
|
|
47
|
+
def render_multilang(langs:, assets:, hub_data:, github_review: nil)
|
|
46
48
|
css = File.read(@css_path)
|
|
47
49
|
template = File.read(@template_path)
|
|
48
50
|
|
|
@@ -90,12 +92,41 @@ module Ligarb
|
|
|
90
92
|
b.local_variable_set(:bibliography, first[:bibliography])
|
|
91
93
|
b.local_variable_set(:multilang, true)
|
|
92
94
|
b.local_variable_set(:langs, lang_data)
|
|
95
|
+
b.local_variable_set(:feedback, load_feedback(github_review))
|
|
93
96
|
|
|
94
97
|
ERB.new(template, trim_mode: "-").result(b)
|
|
95
98
|
end
|
|
96
99
|
|
|
97
100
|
private
|
|
98
101
|
|
|
102
|
+
# When github_review (a hash with :base/:issue_template/:labels) is given,
|
|
103
|
+
# returns the inlined feedback assets + a JSON config blob for the template
|
|
104
|
+
# to embed; otherwise nil (the UI is not injected). Inlining keeps the build
|
|
105
|
+
# output self-contained, like the main style.css.
|
|
106
|
+
def load_feedback(github_review)
|
|
107
|
+
return nil unless github_review
|
|
108
|
+
|
|
109
|
+
config_json = JSON.generate(
|
|
110
|
+
base: github_review[:base],
|
|
111
|
+
issueTemplate: github_review[:issue_template],
|
|
112
|
+
labels: github_review[:labels]
|
|
113
|
+
).gsub("</", '<\/')
|
|
114
|
+
|
|
115
|
+
{
|
|
116
|
+
css: File.read(File.join(ASSETS_DIR, "feedback.css")),
|
|
117
|
+
js: File.read(File.join(ASSETS_DIR, "feedback.js")),
|
|
118
|
+
config_json: config_json,
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# data-src-* attributes for a chapter <section>, used by the feedback UI to
|
|
123
|
+
# locate the source Markdown. Empty unless the UI is active and the chapter
|
|
124
|
+
# has a resolved source path (set when `repository` is configured).
|
|
125
|
+
def src_attrs(chapter, feedback)
|
|
126
|
+
return "" unless feedback && chapter.relative_path
|
|
127
|
+
%( data-src-file="#{h(chapter.relative_path)}" data-src-title="#{h(chapter.display_title)}")
|
|
128
|
+
end
|
|
129
|
+
|
|
99
130
|
# HTML-escape helper for ERB templates (available via binding)
|
|
100
131
|
def h(s)
|
|
101
132
|
ERB::Util.html_escape(s.to_s)
|
data/lib/ligarb/version.rb
CHANGED
data/lib/ligarb/writer.rb
CHANGED
|
@@ -185,8 +185,8 @@ module Ligarb
|
|
|
185
185
|
lines << "Create book.yml first, then each chapter .md file."
|
|
186
186
|
lines << "Include a cover page for books with 4+ chapters."
|
|
187
187
|
lines << "Each chapter: substantive content with multiple ## sections."
|
|
188
|
-
lines << "Use code blocks, admonitions,
|
|
189
|
-
lines << "Chapter filenames:
|
|
188
|
+
lines << "Use code blocks, admonitions, and other ligarb features from the spec where appropriate."
|
|
189
|
+
lines << "Chapter filenames: topic-name.md (e.g. introduction.md, getting-started.md). Do NOT use number prefixes."
|
|
190
190
|
lines << ""
|
|
191
191
|
lines << "Create references.bib with real bibliography entries in BibTeX format."
|
|
192
192
|
lines << "In book.yml, set: bibliography: references.bib"
|
data/templates/book.html.erb
CHANGED
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
<%= custom_css %>
|
|
17
17
|
</style>
|
|
18
18
|
<%- end -%>
|
|
19
|
+
<%- if feedback -%>
|
|
20
|
+
<style>
|
|
21
|
+
<%= feedback[:css] %>
|
|
22
|
+
</style>
|
|
23
|
+
<%- end -%>
|
|
19
24
|
<%- if assets.need?(:highlight) -%>
|
|
20
25
|
<link rel="stylesheet" href="css/highlight.css">
|
|
21
26
|
<%- end -%>
|
|
@@ -247,6 +252,11 @@
|
|
|
247
252
|
<main class="content" id="content">
|
|
248
253
|
<%- if multilang -%>
|
|
249
254
|
<%- langs.each do |ld| -%>
|
|
255
|
+
<%- ld[:chapters].select(&:cover?).each do |chapter| -%>
|
|
256
|
+
<section class="chapter cover-page" id="chapter-<%= chapter.slug %>" data-lang="<%= ld[:lang] %>" style="display: none;">
|
|
257
|
+
<%= chapter.html %>
|
|
258
|
+
</section>
|
|
259
|
+
<%- end -%>
|
|
250
260
|
<section class="chapter toc-page" id="chapter-<%= ld[:lang] %>--__toc__" data-lang="<%= ld[:lang] %>" style="display: none;">
|
|
251
261
|
<h1><%= ld[:language] == 'ja' ? '目次' : 'Contents' %></h1>
|
|
252
262
|
<nav class="toc-full">
|
|
@@ -297,8 +307,8 @@
|
|
|
297
307
|
<%- end -%>
|
|
298
308
|
</nav>
|
|
299
309
|
</section>
|
|
300
|
-
<%- ld[:chapters].each_with_index do |chapter, idx| -%>
|
|
301
|
-
<section class="
|
|
310
|
+
<%- ld[:chapters].reject(&:cover?).each_with_index do |chapter, idx| -%>
|
|
311
|
+
<section class="chapter" id="chapter-<%= chapter.slug %>" data-lang="<%= ld[:lang] %>"<%= src_attrs(chapter, feedback) %> style="display: none;">
|
|
302
312
|
<%= chapter.html %>
|
|
303
313
|
<%- if ld[:repository] && !chapter.part_title? && !chapter.cover? -%>
|
|
304
314
|
<div class="edit-link">
|
|
@@ -358,6 +368,11 @@
|
|
|
358
368
|
<%- end -%>
|
|
359
369
|
<%- end -%>
|
|
360
370
|
<%- else -%>
|
|
371
|
+
<%- chapters.select(&:cover?).each do |chapter| -%>
|
|
372
|
+
<section class="chapter cover-page" id="chapter-<%= chapter.slug %>" style="display: none;">
|
|
373
|
+
<%= chapter.html %>
|
|
374
|
+
</section>
|
|
375
|
+
<%- end -%>
|
|
361
376
|
<section class="chapter toc-page" id="chapter-__toc__" style="display: none;">
|
|
362
377
|
<h1><%= language == 'ja' ? '目次' : 'Contents' %></h1>
|
|
363
378
|
<nav class="toc-full">
|
|
@@ -408,8 +423,8 @@
|
|
|
408
423
|
<%- end -%>
|
|
409
424
|
</nav>
|
|
410
425
|
</section>
|
|
411
|
-
<%- chapters.each_with_index do |chapter, idx| -%>
|
|
412
|
-
<section class="
|
|
426
|
+
<%- chapters.reject(&:cover?).each_with_index do |chapter, idx| -%>
|
|
427
|
+
<section class="chapter" id="chapter-<%= chapter.slug %>"<%= src_attrs(chapter, feedback) %> style="display: none;">
|
|
413
428
|
<%= chapter.html %>
|
|
414
429
|
<%- if repository && !chapter.part_title? && !chapter.cover? -%>
|
|
415
430
|
<div class="edit-link">
|
|
@@ -475,7 +490,8 @@
|
|
|
475
490
|
<%- if multilang -%>
|
|
476
491
|
var langChapters = {
|
|
477
492
|
<%- langs.each do |ld| -%>
|
|
478
|
-
|
|
493
|
+
<%- ld_cover = ld[:chapters].find(&:cover?) -%>
|
|
494
|
+
"<%= ld[:lang] %>": [<%= "\"#{ld_cover.slug}\", " if ld_cover %>"<%= ld[:lang] %>--__toc__", <%= ld[:chapters].reject(&:cover?).map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ", \"#{ld[:lang]}--__bibliography__\"" unless ld[:bibliography].empty? %><%= ", \"#{ld[:lang]}--__index__\"" unless ld[:index_tree].empty? %>],
|
|
479
495
|
<%- end -%>
|
|
480
496
|
};
|
|
481
497
|
var allLangs = [<%= langs.map { |ld| "\"#{ld[:lang]}\"" }.join(", ") %>];
|
|
@@ -483,7 +499,8 @@
|
|
|
483
499
|
if (allLangs.indexOf(currentLang) === -1) currentLang = allLangs[0];
|
|
484
500
|
var chapters = langChapters[currentLang];
|
|
485
501
|
<%- else -%>
|
|
486
|
-
|
|
502
|
+
<%- cover_ch = chapters.find(&:cover?) -%>
|
|
503
|
+
var chapters = [<%= "\"#{cover_ch.slug}\", " if cover_ch %>"__toc__", <%= chapters.reject(&:cover?).map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ', "__bibliography__"' unless bibliography.empty? %><%= ', "__index__"' unless index_tree.empty? %>];
|
|
487
504
|
<%- end -%>
|
|
488
505
|
var currentChapter = null;
|
|
489
506
|
|
|
@@ -1111,5 +1128,11 @@ renderSpecialBlocks();
|
|
|
1111
1128
|
<script src="js/function-plot.min.js"></script>
|
|
1112
1129
|
<script>renderSpecialBlocks();</script>
|
|
1113
1130
|
<%- end -%>
|
|
1131
|
+
<%- if feedback -%>
|
|
1132
|
+
<script>window._ligarbReview = <%= feedback[:config_json] %>;</script>
|
|
1133
|
+
<script>
|
|
1134
|
+
<%= feedback[:js] %>
|
|
1135
|
+
</script>
|
|
1136
|
+
<%- end -%>
|
|
1114
1137
|
</body>
|
|
1115
1138
|
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: 本へのフィードバック
|
|
2
|
+
description: 本文の誤り・わかりにくい点・疑問を報告する
|
|
3
|
+
title: "[feedback] "
|
|
4
|
+
labels: ["feedback"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
本を読んで気づいた点を報告してください。
|
|
10
|
+
(ligarb の公開ページから「Report as issue」で来た場合、いくつかの項目は自動入力されています。)
|
|
11
|
+
- type: input
|
|
12
|
+
id: location
|
|
13
|
+
attributes:
|
|
14
|
+
label: 対象箇所
|
|
15
|
+
description: 章・節、ソースの Markdown ファイル名、公開サイトの該当 URL など
|
|
16
|
+
placeholder: "例: 第3章 2節 / 03-advanced.md / https://__OWNER__.github.io/__REPO__/#ch3-2"
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: quote
|
|
21
|
+
attributes:
|
|
22
|
+
label: 引用テキスト
|
|
23
|
+
description: 問題の箇所を本文から引用してください
|
|
24
|
+
render: markdown
|
|
25
|
+
validations:
|
|
26
|
+
required: true
|
|
27
|
+
- type: dropdown
|
|
28
|
+
id: type
|
|
29
|
+
attributes:
|
|
30
|
+
label: 種類
|
|
31
|
+
options:
|
|
32
|
+
- 誤り(事実誤認・誤字・壊れたコード例など)
|
|
33
|
+
- わかりにくい(説明・構成・例の改善提案)
|
|
34
|
+
- 疑問(直す必要はないが確認したい)
|
|
35
|
+
validations:
|
|
36
|
+
required: true
|
|
37
|
+
- type: textarea
|
|
38
|
+
id: details
|
|
39
|
+
attributes:
|
|
40
|
+
label: 詳細
|
|
41
|
+
description: 何が問題か、どう直すとよいか、または何を確認したいか
|
|
42
|
+
validations:
|
|
43
|
+
required: true
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Build check
|
|
2
|
+
|
|
3
|
+
# PR で ligarb build が通るかを検証する。Claude は関与しない。
|
|
4
|
+
# このチェックを Settings > Branches の必須チェックに指定すると、
|
|
5
|
+
# ビルドが落ちる PR を merge できなくできる。
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [master] # main を使う場合はここを main に変更する
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: ruby/setup-ruby@v1
|
|
20
|
+
with:
|
|
21
|
+
ruby-version: "3.3"
|
|
22
|
+
- name: Install ligarb
|
|
23
|
+
run: gem install ligarb
|
|
24
|
+
- name: Build book
|
|
25
|
+
run: ligarb build # 失敗すればチェックが落ちる
|