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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9771ee497c353bffb143092dae4b8631c72587e264ca3e6081e614e582de9d1b
4
- data.tar.gz: 1def484d79741c7e88d657c485799762e7bf6909cd3bf9a3118aac00ac3cf32f
3
+ metadata.gz: 9a98936274fe3188801b1787fba5e58076a04ff6d5d4cb94183d3bad03643c25
4
+ data.tar.gz: 017da362d7d557808ac62478fc0ac5e95ac50ae68dda6143a76137da27ca5264
5
5
  SHA512:
6
- metadata.gz: 8f7902c30dbc242df9605bfc95995a01c6a23729e1c7079811b0ff9e7a7545d534225c3216cce46c6619a073d215a728dd24a1ab6fb2011206887180ed786c03
7
- data.tar.gz: 720e0fc0a1433537e956987edc1e2656931b5fb01ceb22dd57ee6b537451ab69c30c34d671ad16c3985f60d5f00ee8ddab4028c8fb4c17527b57b5d8724d70a7
6
+ metadata.gz: 4ca765bc7bab404fe43800686716eabb03e7f1d6b4b3c5210c7dd8ae610539a3fb71d929e621a176fdbae98b2054f1ca3b394b15fe92bdc73dc29a79f7abf7ae
7
+ data.tar.gz: 8d3e2954073174b3a6779aea90d6167a378c5c0fbfb7e05635f016d44061f1266db60868f8505f896759e4462729698c6895fec4956d69531089993eded8531c
@@ -0,0 +1,123 @@
1
+ /* ligarb public feedback UI — "Report as issue"
2
+ Static, backend-free: builds a prefilled GitHub issue URL and opens it.
3
+ Colors reuse the book's CSS variables so dark mode works automatically. */
4
+
5
+ #ligarb-fb-btn {
6
+ position: absolute;
7
+ z-index: 9999;
8
+ display: none;
9
+ padding: 6px 12px;
10
+ font-family: var(--font-sans);
11
+ font-size: 13px;
12
+ font-weight: 600;
13
+ line-height: 1;
14
+ color: #fff;
15
+ background: var(--color-accent);
16
+ border: none;
17
+ border-radius: 6px;
18
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
19
+ cursor: pointer;
20
+ }
21
+
22
+ #ligarb-fb-btn:hover { filter: brightness(1.08); }
23
+
24
+ #ligarb-fb-panel {
25
+ position: fixed;
26
+ z-index: 10000;
27
+ display: none;
28
+ flex-direction: column;
29
+ gap: 10px;
30
+ width: 320px;
31
+ max-width: calc(100vw - 24px);
32
+ padding: 16px;
33
+ font-family: var(--font-sans);
34
+ color: var(--color-text);
35
+ background: var(--color-bg);
36
+ border: 1px solid var(--color-border);
37
+ border-radius: 10px;
38
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.25);
39
+ }
40
+
41
+ #ligarb-fb-panel.open { display: flex; }
42
+
43
+ .ligarb-fb-title {
44
+ font-size: 14px;
45
+ font-weight: 700;
46
+ }
47
+
48
+ .ligarb-fb-quote {
49
+ max-height: 96px;
50
+ overflow-y: auto;
51
+ padding: 8px 10px;
52
+ font-size: 12px;
53
+ line-height: 1.5;
54
+ color: var(--color-text-muted);
55
+ background: var(--color-code-bg);
56
+ border: 1px solid var(--color-code-border);
57
+ border-radius: 6px;
58
+ white-space: pre-wrap;
59
+ word-break: break-word;
60
+ }
61
+
62
+ .ligarb-fb-label {
63
+ font-size: 12px;
64
+ font-weight: 600;
65
+ color: var(--color-text-muted);
66
+ }
67
+
68
+ #ligarb-fb-type,
69
+ #ligarb-fb-details {
70
+ width: 100%;
71
+ padding: 7px 9px;
72
+ font-family: inherit;
73
+ font-size: 13px;
74
+ color: var(--color-text);
75
+ background: var(--color-bg);
76
+ border: 1px solid var(--color-border);
77
+ border-radius: 6px;
78
+ }
79
+
80
+ #ligarb-fb-details { resize: vertical; min-height: 60px; }
81
+
82
+ #ligarb-fb-type:focus,
83
+ #ligarb-fb-details:focus {
84
+ outline: none;
85
+ border-color: var(--color-accent);
86
+ box-shadow: 0 0 0 2px var(--color-accent-light);
87
+ }
88
+
89
+ .ligarb-fb-actions {
90
+ display: flex;
91
+ justify-content: flex-end;
92
+ gap: 8px;
93
+ }
94
+
95
+ .ligarb-fb-submit,
96
+ .ligarb-fb-cancel {
97
+ padding: 7px 14px;
98
+ font-family: inherit;
99
+ font-size: 13px;
100
+ font-weight: 600;
101
+ border-radius: 6px;
102
+ cursor: pointer;
103
+ }
104
+
105
+ .ligarb-fb-submit {
106
+ color: #fff;
107
+ background: var(--color-accent);
108
+ border: none;
109
+ }
110
+
111
+ .ligarb-fb-submit:hover { filter: brightness(1.08); }
112
+
113
+ .ligarb-fb-cancel {
114
+ color: var(--color-text-muted);
115
+ background: transparent;
116
+ border: 1px solid var(--color-border);
117
+ }
118
+
119
+ .ligarb-fb-cancel:hover { background: var(--color-sidebar-hover); }
120
+
121
+ @media print {
122
+ #ligarb-fb-btn, #ligarb-fb-panel { display: none !important; }
123
+ }
@@ -0,0 +1,194 @@
1
+ // ligarb public feedback UI — "Report as issue"
2
+ // Static and backend-free: on text selection it offers a button that builds a
3
+ // prefilled GitHub Issue form URL (from data-src-* + window.location + the
4
+ // configured repository) and opens it in a new tab. No API calls, no tokens.
5
+ (function() {
6
+ 'use strict';
7
+
8
+ var cfg = window._ligarbReview;
9
+ if (!cfg || !cfg.base) return;
10
+
11
+ var ISSUE_BASE = cfg.base.replace(/\/+$/, '') + '/issues/new';
12
+ var TEMPLATE = cfg.issueTemplate || 'book-feedback.yml';
13
+ var LABELS = Array.isArray(cfg.labels) ? cfg.labels : [];
14
+
15
+ // Keep the whole URL comfortably under common limits (~8KB).
16
+ var MAX_QUOTE = 1200;
17
+ var MAX_URL = 7000;
18
+
19
+ // Short label -> value stored in the issue (the form keeps its own dropdown;
20
+ // we fold the reader's choice into `details` since dropdown prefill is flaky).
21
+ var TYPES = [
22
+ { value: '', label: '種類を選択 / Type…' },
23
+ { value: '誤り (error)', label: '誤り / Error' },
24
+ { value: 'わかりにくい (unclear)', label: 'わかりにくい / Unclear' },
25
+ { value: '疑問 (question)', label: '疑問 / Question' }
26
+ ];
27
+
28
+ function enc(s) { return encodeURIComponent(s == null ? '' : s); }
29
+
30
+ function escapeHTML(str) {
31
+ var div = document.createElement('div');
32
+ div.textContent = str == null ? '' : str;
33
+ return div.innerHTML;
34
+ }
35
+
36
+ // ── Selection capture (mirrors the serve review UI) ──
37
+
38
+ var btn = document.createElement('button');
39
+ btn.id = 'ligarb-fb-btn';
40
+ btn.type = 'button';
41
+ btn.textContent = 'Report as issue';
42
+ document.body.appendChild(btn);
43
+
44
+ var current = null; // { quote, chapterTitle, srcFile, headingText }
45
+
46
+ document.addEventListener('mouseup', function(e) {
47
+ if (e.target.closest('#ligarb-fb-btn, #ligarb-fb-panel')) return;
48
+
49
+ var sel = window.getSelection();
50
+ if (!sel || sel.isCollapsed || !sel.toString().trim()) {
51
+ hideButton();
52
+ return;
53
+ }
54
+
55
+ var anchor = sel.anchorNode;
56
+ var chapter = anchor && anchor.parentElement ? anchor.parentElement.closest('.chapter') : null;
57
+ if (!chapter || !chapter.dataset.srcFile) {
58
+ hideButton();
59
+ return;
60
+ }
61
+
62
+ current = {
63
+ quote: sel.toString().trim(),
64
+ chapterTitle: chapter.dataset.srcTitle || '',
65
+ srcFile: chapter.dataset.srcFile || '',
66
+ headingText: nearestHeadingText(chapter, sel)
67
+ };
68
+
69
+ var rect = sel.getRangeAt(0).getBoundingClientRect();
70
+ btn.style.display = 'block';
71
+ btn.style.top = (window.scrollY + rect.bottom + 6) + 'px';
72
+ btn.style.left = (window.scrollX + rect.left) + 'px';
73
+ });
74
+
75
+ function hideButton() {
76
+ btn.style.display = 'none';
77
+ current = null;
78
+ }
79
+
80
+ function nearestHeadingText(chapter, sel) {
81
+ var headings = chapter.querySelectorAll('h1[id], h2[id], h3[id]');
82
+ if (!headings.length) return '';
83
+ var range = sel.getRangeAt(0);
84
+ for (var i = headings.length - 1; i >= 0; i--) {
85
+ var hr = document.createRange();
86
+ hr.selectNode(headings[i]);
87
+ if (range.compareBoundaryPoints(Range.START_TO_START, hr) >= 0) {
88
+ return headings[i].textContent.trim();
89
+ }
90
+ }
91
+ return headings[0].textContent.trim();
92
+ }
93
+
94
+ btn.addEventListener('click', function(e) {
95
+ e.preventDefault();
96
+ e.stopPropagation();
97
+ if (!current) return;
98
+ openPanel(current);
99
+ hideButton();
100
+ });
101
+
102
+ // ── Report panel ──
103
+
104
+ var panel = null;
105
+
106
+ function buildPanel() {
107
+ if (panel) return;
108
+ panel = document.createElement('div');
109
+ panel.id = 'ligarb-fb-panel';
110
+ var options = TYPES.map(function(t) {
111
+ return '<option value="' + escapeHTML(t.value) + '">' + escapeHTML(t.label) + '</option>';
112
+ }).join('');
113
+ panel.innerHTML =
114
+ '<div class="ligarb-fb-title">Report as issue</div>' +
115
+ '<div class="ligarb-fb-quote"></div>' +
116
+ '<label class="ligarb-fb-label" for="ligarb-fb-type">種類 / Type</label>' +
117
+ '<select id="ligarb-fb-type">' + options + '</select>' +
118
+ '<label class="ligarb-fb-label" for="ligarb-fb-details">コメント / Comment</label>' +
119
+ '<textarea id="ligarb-fb-details" placeholder="何が問題か、どう直すとよいか…"></textarea>' +
120
+ '<div class="ligarb-fb-actions">' +
121
+ '<button type="button" class="ligarb-fb-cancel">Cancel</button>' +
122
+ '<button type="button" class="ligarb-fb-submit">Report as issue</button>' +
123
+ '</div>';
124
+ document.body.appendChild(panel);
125
+
126
+ panel.querySelector('.ligarb-fb-cancel').addEventListener('click', closePanel);
127
+ panel.querySelector('.ligarb-fb-submit').addEventListener('click', submit);
128
+ }
129
+
130
+ function openPanel(ctx) {
131
+ buildPanel();
132
+ panel._ctx = ctx;
133
+ panel.querySelector('.ligarb-fb-quote').textContent = ctx.quote;
134
+ panel.querySelector('#ligarb-fb-type').value = '';
135
+ panel.querySelector('#ligarb-fb-details').value = '';
136
+
137
+ // Center-ish, then clamp into the viewport.
138
+ panel.classList.add('open');
139
+ var w = panel.offsetWidth, h = panel.offsetHeight;
140
+ var top = Math.max(12, (window.innerHeight - h) / 2);
141
+ var left = Math.max(12, (window.innerWidth - w) / 2);
142
+ panel.style.top = top + 'px';
143
+ panel.style.left = left + 'px';
144
+ panel.querySelector('#ligarb-fb-details').focus();
145
+ }
146
+
147
+ function closePanel() {
148
+ if (panel) panel.classList.remove('open');
149
+ }
150
+
151
+ function submit() {
152
+ var ctx = panel._ctx;
153
+ if (!ctx) return;
154
+ var type = panel.querySelector('#ligarb-fb-type').value;
155
+ var comment = panel.querySelector('#ligarb-fb-details').value.trim();
156
+
157
+ var locationLines = [];
158
+ var section = [ctx.chapterTitle, ctx.headingText].filter(Boolean).join(' › ');
159
+ if (section) locationLines.push('章/節: ' + section);
160
+ if (ctx.srcFile) locationLines.push('ソース: ' + ctx.srcFile);
161
+ locationLines.push('URL: ' + window.location.href);
162
+
163
+ var detailsLines = [];
164
+ if (type) detailsLines.push('種類: ' + type);
165
+ if (comment) detailsLines.push(comment);
166
+
167
+ var quote = ctx.quote;
168
+ if (quote.length > MAX_QUOTE) quote = quote.slice(0, MAX_QUOTE) + ' …(truncated)';
169
+
170
+ var url = buildUrl(locationLines.join('\n'), quote, detailsLines.join('\n\n'));
171
+
172
+ // If still too long, progressively shorten the quote.
173
+ while (url.length > MAX_URL && quote.length > 80) {
174
+ quote = quote.slice(0, Math.floor(quote.length / 2)) + ' …(truncated)';
175
+ url = buildUrl(locationLines.join('\n'), quote, detailsLines.join('\n\n'));
176
+ }
177
+
178
+ window.open(url, '_blank', 'noopener');
179
+ closePanel();
180
+ }
181
+
182
+ function buildUrl(location, quote, details) {
183
+ var params = 'template=' + enc(TEMPLATE);
184
+ if (LABELS.length) params += '&labels=' + enc(LABELS.join(','));
185
+ params += '&location=' + enc(location);
186
+ params += '&quote=' + enc(quote);
187
+ params += '&details=' + enc(details);
188
+ return ISSUE_BASE + '?' + params;
189
+ }
190
+
191
+ document.addEventListener('keydown', function(e) {
192
+ if (e.key === 'Escape') closePanel();
193
+ });
194
+ })();
@@ -41,7 +41,8 @@ module Ligarb
41
41
  html = Template.new.render(config: @config, chapters: all_chapters,
42
42
  structure: structure, assets: assets,
43
43
  index_entries: index_entries,
44
- bibliography: bibliography)
44
+ bibliography: bibliography,
45
+ github_review: github_review_data(@config))
45
46
 
46
47
  FileUtils.mkdir_p(@config.output_path)
47
48
  output_file = File.join(@config.output_path, "index.html")
@@ -76,8 +77,10 @@ module Ligarb
76
77
  assets.detect(all_lang_chapters)
77
78
  assets.provision!
78
79
 
80
+ gr_config = langs.first && langs.first[:config]
79
81
  html = Template.new.render_multilang(langs: langs, assets: assets,
80
- hub_data: hub_data)
82
+ hub_data: hub_data,
83
+ github_review: gr_config && github_review_data(gr_config))
81
84
 
82
85
  FileUtils.mkdir_p(output_path)
83
86
  output_file = File.join(output_path, "index.html")
@@ -198,6 +201,24 @@ module Ligarb
198
201
  end
199
202
  end
200
203
 
204
+ # Builds the data passed to the template for the reader feedback UI, or nil
205
+ # when it should not be injected. Requires `repository` (the issues/new base);
206
+ # warns and skips if the UI was requested but no repository is set.
207
+ def github_review_data(config)
208
+ return nil unless config.github_review_enabled?
209
+
210
+ unless config.repository
211
+ warn "Warning: github_review.enabled is true but 'repository' is not set; skipping the reader feedback UI"
212
+ return nil
213
+ end
214
+
215
+ {
216
+ base: config.repository.chomp("/"),
217
+ issue_template: config.github_review_issue_template,
218
+ labels: config.github_review_labels,
219
+ }
220
+ end
221
+
201
222
  def assign_relative_paths(chapters)
202
223
  git_root = find_git_root(@config.base_dir)
203
224
  chapters.each do |ch|
@@ -88,8 +88,9 @@ module Ligarb
88
88
  - You may include multiple <patch> blocks for one or more files
89
89
  - If the comment applies to multiple chapters, read all relevant chapters and provide patches for each
90
90
  - When adding citations ([@key]), also add the corresponding entry to the bibliography file
91
- - Use ligarb Markdown features (admonitions, cross-references, index, etc.) where appropriate
91
+ - Use ligarb Markdown features from the spec where appropriate
92
92
  - If no code change is needed (e.g. answering a question), omit the <patch> blocks
93
+ - Refuse changes that would introduce security issues (e.g. injecting scripts, untrusted URLs, or arbitrary HTML)
93
94
  PROMPT
94
95
  end
95
96