reviw 0.10.7 → 0.11.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.
- package/README.md +56 -0
- package/cli.cjs +745 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ A lightweight browser-based tool for reviewing and annotating tabular data, text
|
|
|
20
20
|
### Media Fullscreen
|
|
21
21
|
- Click images in Markdown preview to open fullscreen viewer
|
|
22
22
|
- Click videos to open fullscreen playback with native controls
|
|
23
|
+
- Click anywhere (including the image/video itself) to close the fullscreen overlay
|
|
24
|
+
- Clicking media automatically highlights the corresponding source line in the Markdown panel
|
|
23
25
|
|
|
24
26
|
### UI Features
|
|
25
27
|
- **Theme toggle**: Switch between light and dark modes
|
|
@@ -103,10 +105,64 @@ comments:
|
|
|
103
105
|
summary: Overall the data looks good, minor issues noted above.
|
|
104
106
|
```
|
|
105
107
|
|
|
108
|
+
## Claude Code Plugin
|
|
109
|
+
|
|
110
|
+
This repository also serves as a Claude Code plugin marketplace. The plugin integrates reviw into Claude Code workflows with task management and review automation.
|
|
111
|
+
|
|
112
|
+
### Installation
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# In Claude Code
|
|
116
|
+
/plugin marketplace add kazuph/reviw
|
|
117
|
+
/plugin install reviw-plugin@reviw-marketplace
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Plugin Features
|
|
121
|
+
|
|
122
|
+
| Component | Name | Description |
|
|
123
|
+
|-----------|------|-------------|
|
|
124
|
+
| **Command** | `/reviw:do` | Start a task - create worktree, plan, register todos |
|
|
125
|
+
| **Command** | `/reviw:done` | Complete checklist - collect evidence, start review with reviw |
|
|
126
|
+
| **Agent** | `report-builder` | Prepare reports and evidence for user review |
|
|
127
|
+
| **Skill** | `artifact-proof` | Collect evidence (screenshots, videos, logs) + reviw review workflow |
|
|
128
|
+
| **Hook** | PreToolUse | Remind to review before git commit/push |
|
|
129
|
+
| **Hook** | Stop | Warn if task is still in progress |
|
|
130
|
+
|
|
131
|
+
### Workflow
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
/reviw:do <task description>
|
|
135
|
+
↓
|
|
136
|
+
Create worktree + Plan
|
|
137
|
+
↓
|
|
138
|
+
Implementation
|
|
139
|
+
↓
|
|
140
|
+
/reviw:done
|
|
141
|
+
↓
|
|
142
|
+
Collect evidence + Create report
|
|
143
|
+
↓
|
|
144
|
+
npx reviw opens report (foreground)
|
|
145
|
+
↓
|
|
146
|
+
User comments → Submit & Exit
|
|
147
|
+
↓
|
|
148
|
+
Register feedback to Todo
|
|
149
|
+
↓
|
|
150
|
+
Fix → Re-review until approved
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Completion Criteria
|
|
154
|
+
|
|
155
|
+
| Stage | Content |
|
|
156
|
+
|-------|---------|
|
|
157
|
+
| 1/3 | Implementation complete |
|
|
158
|
+
| 2/3 | Build, start, verification complete |
|
|
159
|
+
| 3/3 | Review with reviw → User approval |
|
|
160
|
+
|
|
106
161
|
## Development
|
|
107
162
|
|
|
108
163
|
- Main source: `cli.cjs`
|
|
109
164
|
- Tests: `npm test` (vitest + playwright)
|
|
165
|
+
- Plugin: `plugin/` directory
|
|
110
166
|
|
|
111
167
|
## License
|
|
112
168
|
|
package/cli.cjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require("fs");
|
|
15
15
|
const http = require("http");
|
|
16
|
+
const os = require("os");
|
|
16
17
|
const path = require("path");
|
|
17
18
|
const { spawn } = require("child_process");
|
|
18
19
|
const chardet = require("chardet");
|
|
@@ -465,7 +466,8 @@ function loadDiff(diffText) {
|
|
|
465
466
|
return {
|
|
466
467
|
rows,
|
|
467
468
|
files: sortedFiles,
|
|
468
|
-
|
|
469
|
+
projectRoot: "",
|
|
470
|
+
relativePath: "Git Diff",
|
|
469
471
|
mode: "diff",
|
|
470
472
|
};
|
|
471
473
|
}
|
|
@@ -574,7 +576,7 @@ function loadCsv(filePath) {
|
|
|
574
576
|
return {
|
|
575
577
|
rows,
|
|
576
578
|
cols: Math.max(1, maxCols),
|
|
577
|
-
|
|
579
|
+
...formatTitlePaths(filePath),
|
|
578
580
|
};
|
|
579
581
|
}
|
|
580
582
|
|
|
@@ -585,7 +587,7 @@ function loadText(filePath) {
|
|
|
585
587
|
return {
|
|
586
588
|
rows: lines.map((line) => [line]),
|
|
587
589
|
cols: 1,
|
|
588
|
-
|
|
590
|
+
...formatTitlePaths(filePath),
|
|
589
591
|
preview: null,
|
|
590
592
|
};
|
|
591
593
|
}
|
|
@@ -598,6 +600,7 @@ function loadMarkdown(filePath) {
|
|
|
598
600
|
// Parse YAML frontmatter
|
|
599
601
|
let frontmatterHtml = "";
|
|
600
602
|
let contentStart = 0;
|
|
603
|
+
let reviwQuestions = []; // Extract reviw questions for modal
|
|
601
604
|
|
|
602
605
|
if (lines[0] && lines[0].trim() === "---") {
|
|
603
606
|
let frontmatterEnd = -1;
|
|
@@ -615,30 +618,91 @@ function loadMarkdown(filePath) {
|
|
|
615
618
|
try {
|
|
616
619
|
const frontmatter = yaml.load(frontmatterText);
|
|
617
620
|
if (frontmatter && typeof frontmatter === "object") {
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
621
|
+
// Extract reviw questions if present
|
|
622
|
+
if (frontmatter.reviw && Array.isArray(frontmatter.reviw.questions)) {
|
|
623
|
+
reviwQuestions = frontmatter.reviw.questions.map((q, idx) => ({
|
|
624
|
+
id: q.id || "q" + (idx + 1),
|
|
625
|
+
question: q.question || "",
|
|
626
|
+
resolved: q.resolved === true,
|
|
627
|
+
answer: q.answer || "",
|
|
628
|
+
options: Array.isArray(q.options) ? q.options : [],
|
|
629
|
+
}));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Create HTML table for frontmatter (show reviw questions in detail for 1:1 correspondence with YAML source)
|
|
633
|
+
const displayFrontmatter = { ...frontmatter };
|
|
634
|
+
// Keep reviw as-is for detailed rendering
|
|
635
|
+
|
|
636
|
+
if (Object.keys(displayFrontmatter).length > 0) {
|
|
637
|
+
frontmatterHtml = '<div class="frontmatter-table"><table>';
|
|
638
|
+
frontmatterHtml += '<colgroup><col style="width:12%"><col style="width:88%"></colgroup>';
|
|
639
|
+
frontmatterHtml += '<thead><tr><th colspan="2">Document Metadata</th></tr></thead>';
|
|
640
|
+
frontmatterHtml += "<tbody>";
|
|
641
|
+
|
|
642
|
+
// Render reviw.questions as detailed cards
|
|
643
|
+
function renderReviwQuestions(questions) {
|
|
644
|
+
if (!Array.isArray(questions) || questions.length === 0) {
|
|
645
|
+
return '<span class="fm-tag">質問なし</span>';
|
|
646
|
+
}
|
|
647
|
+
let html = '<div class="reviw-questions-preview">';
|
|
648
|
+
questions.forEach((q, idx) => {
|
|
649
|
+
const statusIcon = q.resolved ? '✅' : '⏳';
|
|
650
|
+
const statusClass = q.resolved ? 'resolved' : 'pending';
|
|
651
|
+
html += '<div class="reviw-q-card ' + statusClass + '">';
|
|
652
|
+
html += '<div class="reviw-q-header">' + statusIcon + ' <strong>' + escapeHtmlChars(q.id || 'Q' + (idx + 1)) + '</strong></div>';
|
|
653
|
+
html += '<div class="reviw-q-question">' + escapeHtmlChars(q.question || '') + '</div>';
|
|
654
|
+
if (q.options && Array.isArray(q.options) && q.options.length > 0) {
|
|
655
|
+
html += '<div class="reviw-q-options">';
|
|
656
|
+
q.options.forEach(opt => {
|
|
657
|
+
html += '<span class="fm-tag">' + escapeHtmlChars(opt) + '</span>';
|
|
658
|
+
});
|
|
659
|
+
html += '</div>';
|
|
660
|
+
}
|
|
661
|
+
if (q.answer) {
|
|
662
|
+
html += '<div class="reviw-q-answer">💬 ' + escapeHtmlChars(q.answer) + '</div>';
|
|
663
|
+
}
|
|
664
|
+
html += '</div>';
|
|
665
|
+
});
|
|
666
|
+
html += '</div>';
|
|
667
|
+
return html;
|
|
629
668
|
}
|
|
630
|
-
|
|
631
|
-
|
|
669
|
+
|
|
670
|
+
function renderValue(val, key) {
|
|
671
|
+
// Special handling for reviw object
|
|
672
|
+
if (key === 'reviw' && typeof val === 'object' && val !== null) {
|
|
673
|
+
let html = '';
|
|
674
|
+
if (val.questions && Array.isArray(val.questions)) {
|
|
675
|
+
html += renderReviwQuestions(val.questions);
|
|
676
|
+
}
|
|
677
|
+
// Render other reviw properties
|
|
678
|
+
const { questions, ...rest } = val;
|
|
679
|
+
if (Object.keys(rest).length > 0) {
|
|
680
|
+
html += '<div style="margin-top: 8px;">';
|
|
681
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
682
|
+
html += '<div><strong>' + escapeHtmlChars(k) + ':</strong> ' + escapeHtmlChars(String(v)) + '</div>';
|
|
683
|
+
}
|
|
684
|
+
html += '</div>';
|
|
685
|
+
}
|
|
686
|
+
return html || '<span class="fm-tag">-</span>';
|
|
687
|
+
}
|
|
688
|
+
if (Array.isArray(val)) {
|
|
689
|
+
return val
|
|
690
|
+
.map((v) => '<span class="fm-tag">' + escapeHtmlChars(String(v)) + "</span>")
|
|
691
|
+
.join(" ");
|
|
692
|
+
}
|
|
693
|
+
if (typeof val === "object" && val !== null) {
|
|
694
|
+
return "<pre>" + escapeHtmlChars(JSON.stringify(val, null, 2)) + "</pre>";
|
|
695
|
+
}
|
|
696
|
+
return escapeHtmlChars(String(val));
|
|
632
697
|
}
|
|
633
|
-
return escapeHtmlChars(String(val));
|
|
634
|
-
}
|
|
635
698
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
699
|
+
for (const [key, val] of Object.entries(displayFrontmatter)) {
|
|
700
|
+
frontmatterHtml +=
|
|
701
|
+
"<tr><th>" + escapeHtmlChars(key) + "</th><td>" + renderValue(val, key) + "</td></tr>";
|
|
702
|
+
}
|
|
640
703
|
|
|
641
|
-
|
|
704
|
+
frontmatterHtml += "</tbody></table></div>";
|
|
705
|
+
}
|
|
642
706
|
contentStart = frontmatterEnd + 1;
|
|
643
707
|
}
|
|
644
708
|
} catch (e) {
|
|
@@ -654,8 +718,9 @@ function loadMarkdown(filePath) {
|
|
|
654
718
|
return {
|
|
655
719
|
rows: lines.map((line) => [line]),
|
|
656
720
|
cols: 1,
|
|
657
|
-
|
|
721
|
+
...formatTitlePaths(filePath),
|
|
658
722
|
preview,
|
|
723
|
+
reviwQuestions, // Pass questions to UI
|
|
659
724
|
};
|
|
660
725
|
}
|
|
661
726
|
|
|
@@ -667,6 +732,20 @@ function escapeHtmlChars(str) {
|
|
|
667
732
|
.replace(/"/g, """);
|
|
668
733
|
}
|
|
669
734
|
|
|
735
|
+
function formatTitlePaths(filePath) {
|
|
736
|
+
const cwd = process.cwd();
|
|
737
|
+
const home = os.homedir();
|
|
738
|
+
const relativePath = path.relative(cwd, filePath) || path.basename(filePath);
|
|
739
|
+
let projectRoot = cwd;
|
|
740
|
+
if (projectRoot.startsWith(home)) {
|
|
741
|
+
projectRoot = "~" + projectRoot.slice(home.length);
|
|
742
|
+
}
|
|
743
|
+
if (!projectRoot.endsWith("/")) {
|
|
744
|
+
projectRoot += "/";
|
|
745
|
+
}
|
|
746
|
+
return { projectRoot, relativePath };
|
|
747
|
+
}
|
|
748
|
+
|
|
670
749
|
function loadData(filePath) {
|
|
671
750
|
// Check if path exists
|
|
672
751
|
if (!fs.existsSync(filePath)) {
|
|
@@ -714,7 +793,7 @@ function serializeForScript(value) {
|
|
|
714
793
|
}
|
|
715
794
|
|
|
716
795
|
function diffHtmlTemplate(diffData) {
|
|
717
|
-
const { rows,
|
|
796
|
+
const { rows, projectRoot, relativePath } = diffData;
|
|
718
797
|
const serialized = serializeForScript(rows);
|
|
719
798
|
const fileCount = rows.filter((r) => r.type === "file").length;
|
|
720
799
|
|
|
@@ -726,7 +805,7 @@ function diffHtmlTemplate(diffData) {
|
|
|
726
805
|
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
|
|
727
806
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
728
807
|
<meta http-equiv="Expires" content="0" />
|
|
729
|
-
<title>${
|
|
808
|
+
<title>${relativePath} | reviw</title>
|
|
730
809
|
<style>
|
|
731
810
|
:root {
|
|
732
811
|
color-scheme: dark;
|
|
@@ -795,7 +874,9 @@ function diffHtmlTemplate(diffData) {
|
|
|
795
874
|
}
|
|
796
875
|
header .meta { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
|
797
876
|
header .actions { display: flex; gap: 8px; align-items: center; }
|
|
798
|
-
header h1 {
|
|
877
|
+
header h1 { display: flex; flex-direction: column; margin: 0; line-height: 1.3; }
|
|
878
|
+
header h1 .title-path { font-size: 11px; font-weight: 400; color: var(--muted); }
|
|
879
|
+
header h1 .title-file { font-size: 16px; font-weight: 600; }
|
|
799
880
|
header .badge {
|
|
800
881
|
background: var(--selected-bg);
|
|
801
882
|
color: var(--text);
|
|
@@ -958,11 +1039,14 @@ function diffHtmlTemplate(diffData) {
|
|
|
958
1039
|
display: none;
|
|
959
1040
|
}
|
|
960
1041
|
.floating header {
|
|
961
|
-
position:
|
|
1042
|
+
position: static;
|
|
962
1043
|
background: transparent;
|
|
1044
|
+
backdrop-filter: none;
|
|
963
1045
|
border: none;
|
|
964
1046
|
padding: 0 0 10px 0;
|
|
1047
|
+
display: flex;
|
|
965
1048
|
justify-content: space-between;
|
|
1049
|
+
align-items: center;
|
|
966
1050
|
}
|
|
967
1051
|
.floating h2 { font-size: 14px; margin: 0; font-weight: 600; }
|
|
968
1052
|
.floating button {
|
|
@@ -987,6 +1071,10 @@ function diffHtmlTemplate(diffData) {
|
|
|
987
1071
|
font-size: 13px;
|
|
988
1072
|
font-family: inherit;
|
|
989
1073
|
}
|
|
1074
|
+
.floating textarea:focus {
|
|
1075
|
+
outline: none;
|
|
1076
|
+
border-color: var(--accent);
|
|
1077
|
+
}
|
|
990
1078
|
.floating .actions {
|
|
991
1079
|
display: flex;
|
|
992
1080
|
gap: 8px;
|
|
@@ -1104,7 +1192,7 @@ function diffHtmlTemplate(diffData) {
|
|
|
1104
1192
|
<body>
|
|
1105
1193
|
<header>
|
|
1106
1194
|
<div class="meta">
|
|
1107
|
-
<h1>${title}</h1>
|
|
1195
|
+
<h1>${projectRoot ? `<span class="title-path">${projectRoot}</span>` : ""}<span class="title-file">${relativePath}</span></h1>
|
|
1108
1196
|
<span class="badge">${fileCount} file${fileCount !== 1 ? "s" : ""} changed</span>
|
|
1109
1197
|
<span class="pill">Comments <strong id="comment-count">0</strong></span>
|
|
1110
1198
|
</div>
|
|
@@ -1594,10 +1682,11 @@ function diffHtmlTemplate(diffData) {
|
|
|
1594
1682
|
}
|
|
1595
1683
|
|
|
1596
1684
|
// --- HTML template ---------------------------------------------------------
|
|
1597
|
-
function htmlTemplate(dataRows, cols,
|
|
1685
|
+
function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHtml, reviwQuestions = []) {
|
|
1598
1686
|
const serialized = serializeForScript(dataRows);
|
|
1599
1687
|
const modeJson = serializeForScript(mode);
|
|
1600
|
-
const titleJson = serializeForScript(
|
|
1688
|
+
const titleJson = serializeForScript(relativePath); // Use relativePath as file identifier
|
|
1689
|
+
const questionsJson = serializeForScript(reviwQuestions || []);
|
|
1601
1690
|
const hasPreview = !!previewHtml;
|
|
1602
1691
|
return `<!doctype html>
|
|
1603
1692
|
<html lang="ja">
|
|
@@ -1607,7 +1696,7 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
1607
1696
|
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
|
|
1608
1697
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
1609
1698
|
<meta http-equiv="Expires" content="0" />
|
|
1610
|
-
<title>${
|
|
1699
|
+
<title>${relativePath} | reviw</title>
|
|
1611
1700
|
<style>
|
|
1612
1701
|
/* Dark theme (default) */
|
|
1613
1702
|
:root {
|
|
@@ -1688,7 +1777,9 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
1688
1777
|
}
|
|
1689
1778
|
header .meta { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
|
1690
1779
|
header .actions { display: flex; gap: 8px; align-items: center; }
|
|
1691
|
-
header h1 {
|
|
1780
|
+
header h1 { display: flex; flex-direction: column; margin: 0; line-height: 1.3; }
|
|
1781
|
+
header h1 .title-path { font-size: 11px; font-weight: 400; color: var(--muted); }
|
|
1782
|
+
header h1 .title-file { font-size: 16px; font-weight: 700; }
|
|
1692
1783
|
header .badge {
|
|
1693
1784
|
background: var(--selected-bg);
|
|
1694
1785
|
color: var(--text);
|
|
@@ -1909,12 +2000,14 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
1909
2000
|
transition: background 200ms ease, border-color 200ms ease;
|
|
1910
2001
|
}
|
|
1911
2002
|
.floating header {
|
|
1912
|
-
position:
|
|
1913
|
-
top: 0;
|
|
2003
|
+
position: static;
|
|
1914
2004
|
background: transparent;
|
|
2005
|
+
backdrop-filter: none;
|
|
1915
2006
|
border: none;
|
|
1916
2007
|
padding: 0 0 8px 0;
|
|
2008
|
+
display: flex;
|
|
1917
2009
|
justify-content: space-between;
|
|
2010
|
+
align-items: center;
|
|
1918
2011
|
}
|
|
1919
2012
|
.floating h2 { font-size: 14px; margin: 0; color: var(--text); }
|
|
1920
2013
|
.floating button {
|
|
@@ -1943,6 +2036,10 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
1943
2036
|
line-height: 1.4;
|
|
1944
2037
|
transition: background 200ms ease, border-color 200ms ease;
|
|
1945
2038
|
}
|
|
2039
|
+
.floating textarea:focus {
|
|
2040
|
+
outline: none;
|
|
2041
|
+
border-color: var(--accent);
|
|
2042
|
+
}
|
|
1946
2043
|
.floating .actions {
|
|
1947
2044
|
display: flex;
|
|
1948
2045
|
gap: 8px;
|
|
@@ -2120,6 +2217,50 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
2120
2217
|
border-radius: 4px;
|
|
2121
2218
|
font-size: 11px;
|
|
2122
2219
|
}
|
|
2220
|
+
/* Reviw questions preview cards */
|
|
2221
|
+
.reviw-questions-preview {
|
|
2222
|
+
display: flex;
|
|
2223
|
+
flex-direction: column;
|
|
2224
|
+
gap: 8px;
|
|
2225
|
+
}
|
|
2226
|
+
.reviw-q-card {
|
|
2227
|
+
background: var(--code-bg);
|
|
2228
|
+
border: 1px solid var(--border);
|
|
2229
|
+
border-radius: 8px;
|
|
2230
|
+
padding: 10px 12px;
|
|
2231
|
+
}
|
|
2232
|
+
.reviw-q-card.resolved {
|
|
2233
|
+
border-left: 3px solid #22c55e;
|
|
2234
|
+
}
|
|
2235
|
+
.reviw-q-card.pending {
|
|
2236
|
+
border-left: 3px solid #f59e0b;
|
|
2237
|
+
}
|
|
2238
|
+
.reviw-q-header {
|
|
2239
|
+
font-size: 12px;
|
|
2240
|
+
color: var(--text-dim);
|
|
2241
|
+
margin-bottom: 4px;
|
|
2242
|
+
}
|
|
2243
|
+
.reviw-q-header strong {
|
|
2244
|
+
color: var(--accent);
|
|
2245
|
+
}
|
|
2246
|
+
.reviw-q-question {
|
|
2247
|
+
font-size: 13px;
|
|
2248
|
+
color: var(--text);
|
|
2249
|
+
margin-bottom: 6px;
|
|
2250
|
+
}
|
|
2251
|
+
.reviw-q-options {
|
|
2252
|
+
display: flex;
|
|
2253
|
+
flex-wrap: wrap;
|
|
2254
|
+
gap: 4px;
|
|
2255
|
+
margin-bottom: 6px;
|
|
2256
|
+
}
|
|
2257
|
+
.reviw-q-answer {
|
|
2258
|
+
font-size: 12px;
|
|
2259
|
+
color: #22c55e;
|
|
2260
|
+
background: rgba(34, 197, 94, 0.1);
|
|
2261
|
+
padding: 4px 8px;
|
|
2262
|
+
border-radius: 4px;
|
|
2263
|
+
}
|
|
2123
2264
|
[data-theme="light"] .frontmatter-table tbody th {
|
|
2124
2265
|
color: #7c3aed;
|
|
2125
2266
|
}
|
|
@@ -2267,6 +2408,267 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
2267
2408
|
border-radius: 8px;
|
|
2268
2409
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
2269
2410
|
}
|
|
2411
|
+
/* Reviw Questions Modal */
|
|
2412
|
+
.reviw-questions-overlay {
|
|
2413
|
+
display: none;
|
|
2414
|
+
position: fixed;
|
|
2415
|
+
inset: 0;
|
|
2416
|
+
background: rgba(0, 0, 0, 0.8);
|
|
2417
|
+
z-index: 1100;
|
|
2418
|
+
justify-content: center;
|
|
2419
|
+
align-items: center;
|
|
2420
|
+
}
|
|
2421
|
+
.reviw-questions-overlay.visible {
|
|
2422
|
+
display: flex;
|
|
2423
|
+
}
|
|
2424
|
+
.reviw-questions-modal {
|
|
2425
|
+
background: var(--card-bg);
|
|
2426
|
+
border: 1px solid var(--border);
|
|
2427
|
+
border-radius: 16px;
|
|
2428
|
+
width: 90%;
|
|
2429
|
+
max-width: 600px;
|
|
2430
|
+
max-height: 80vh;
|
|
2431
|
+
display: flex;
|
|
2432
|
+
flex-direction: column;
|
|
2433
|
+
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.5);
|
|
2434
|
+
}
|
|
2435
|
+
.reviw-questions-header {
|
|
2436
|
+
display: flex;
|
|
2437
|
+
justify-content: space-between;
|
|
2438
|
+
align-items: center;
|
|
2439
|
+
padding: 16px 20px;
|
|
2440
|
+
border-bottom: 1px solid var(--border);
|
|
2441
|
+
}
|
|
2442
|
+
.reviw-questions-header h2 {
|
|
2443
|
+
margin: 0;
|
|
2444
|
+
font-size: 16px;
|
|
2445
|
+
font-weight: 600;
|
|
2446
|
+
color: var(--text);
|
|
2447
|
+
}
|
|
2448
|
+
.reviw-questions-header h2 span {
|
|
2449
|
+
font-size: 14px;
|
|
2450
|
+
color: var(--text-dim);
|
|
2451
|
+
font-weight: 400;
|
|
2452
|
+
}
|
|
2453
|
+
.reviw-questions-close {
|
|
2454
|
+
width: 32px;
|
|
2455
|
+
height: 32px;
|
|
2456
|
+
border: none;
|
|
2457
|
+
background: transparent;
|
|
2458
|
+
color: var(--text-dim);
|
|
2459
|
+
font-size: 18px;
|
|
2460
|
+
cursor: pointer;
|
|
2461
|
+
border-radius: 8px;
|
|
2462
|
+
transition: all 150ms ease;
|
|
2463
|
+
}
|
|
2464
|
+
.reviw-questions-close:hover {
|
|
2465
|
+
background: var(--border);
|
|
2466
|
+
color: var(--text);
|
|
2467
|
+
}
|
|
2468
|
+
.reviw-questions-body {
|
|
2469
|
+
flex: 1;
|
|
2470
|
+
overflow-y: auto;
|
|
2471
|
+
padding: 16px 20px;
|
|
2472
|
+
}
|
|
2473
|
+
.reviw-questions-footer {
|
|
2474
|
+
padding: 12px 20px;
|
|
2475
|
+
border-top: 1px solid var(--border);
|
|
2476
|
+
display: flex;
|
|
2477
|
+
justify-content: flex-end;
|
|
2478
|
+
}
|
|
2479
|
+
.reviw-questions-later {
|
|
2480
|
+
padding: 8px 16px;
|
|
2481
|
+
border: 1px solid var(--border);
|
|
2482
|
+
background: transparent;
|
|
2483
|
+
color: var(--text-dim);
|
|
2484
|
+
border-radius: 8px;
|
|
2485
|
+
cursor: pointer;
|
|
2486
|
+
font-size: 13px;
|
|
2487
|
+
transition: all 150ms ease;
|
|
2488
|
+
}
|
|
2489
|
+
.reviw-questions-later:hover {
|
|
2490
|
+
background: var(--border);
|
|
2491
|
+
color: var(--text);
|
|
2492
|
+
}
|
|
2493
|
+
/* Question Item */
|
|
2494
|
+
.reviw-question-item {
|
|
2495
|
+
margin-bottom: 20px;
|
|
2496
|
+
padding-bottom: 20px;
|
|
2497
|
+
border-bottom: 1px solid var(--border);
|
|
2498
|
+
}
|
|
2499
|
+
.reviw-question-item:last-child {
|
|
2500
|
+
margin-bottom: 0;
|
|
2501
|
+
padding-bottom: 0;
|
|
2502
|
+
border-bottom: none;
|
|
2503
|
+
}
|
|
2504
|
+
.reviw-question-text {
|
|
2505
|
+
font-size: 14px;
|
|
2506
|
+
color: var(--text);
|
|
2507
|
+
margin-bottom: 12px;
|
|
2508
|
+
line-height: 1.5;
|
|
2509
|
+
}
|
|
2510
|
+
.reviw-question-options {
|
|
2511
|
+
display: flex;
|
|
2512
|
+
flex-wrap: wrap;
|
|
2513
|
+
gap: 8px;
|
|
2514
|
+
margin-bottom: 12px;
|
|
2515
|
+
}
|
|
2516
|
+
.reviw-question-option {
|
|
2517
|
+
padding: 8px 14px;
|
|
2518
|
+
border: 1px solid var(--border);
|
|
2519
|
+
background: transparent;
|
|
2520
|
+
color: var(--text);
|
|
2521
|
+
border-radius: 8px;
|
|
2522
|
+
cursor: pointer;
|
|
2523
|
+
font-size: 13px;
|
|
2524
|
+
transition: all 150ms ease;
|
|
2525
|
+
}
|
|
2526
|
+
.reviw-question-option:hover {
|
|
2527
|
+
border-color: var(--accent);
|
|
2528
|
+
background: rgba(96, 165, 250, 0.1);
|
|
2529
|
+
}
|
|
2530
|
+
.reviw-question-option.selected {
|
|
2531
|
+
border-color: var(--accent);
|
|
2532
|
+
background: var(--accent);
|
|
2533
|
+
color: var(--text-inverse);
|
|
2534
|
+
}
|
|
2535
|
+
.reviw-question-input {
|
|
2536
|
+
width: 100%;
|
|
2537
|
+
padding: 10px 12px;
|
|
2538
|
+
border: 1px solid var(--border);
|
|
2539
|
+
background: var(--input-bg);
|
|
2540
|
+
color: var(--text);
|
|
2541
|
+
border-radius: 8px;
|
|
2542
|
+
font-size: 13px;
|
|
2543
|
+
resize: vertical;
|
|
2544
|
+
min-height: 60px;
|
|
2545
|
+
}
|
|
2546
|
+
.reviw-question-input:focus {
|
|
2547
|
+
outline: none;
|
|
2548
|
+
border-color: var(--accent);
|
|
2549
|
+
}
|
|
2550
|
+
.reviw-question-input::placeholder {
|
|
2551
|
+
color: var(--text-dim);
|
|
2552
|
+
}
|
|
2553
|
+
.reviw-check-mark {
|
|
2554
|
+
color: #22c55e;
|
|
2555
|
+
font-weight: bold;
|
|
2556
|
+
}
|
|
2557
|
+
.reviw-question-item.answered {
|
|
2558
|
+
border-color: #22c55e;
|
|
2559
|
+
background: rgba(34, 197, 94, 0.05);
|
|
2560
|
+
}
|
|
2561
|
+
.reviw-question-submit {
|
|
2562
|
+
margin-top: 10px;
|
|
2563
|
+
padding: 8px 16px;
|
|
2564
|
+
border: none;
|
|
2565
|
+
background: var(--accent);
|
|
2566
|
+
color: var(--text-inverse);
|
|
2567
|
+
border-radius: 8px;
|
|
2568
|
+
cursor: pointer;
|
|
2569
|
+
font-size: 13px;
|
|
2570
|
+
font-weight: 500;
|
|
2571
|
+
transition: all 150ms ease;
|
|
2572
|
+
}
|
|
2573
|
+
.reviw-question-submit:hover {
|
|
2574
|
+
filter: brightness(1.1);
|
|
2575
|
+
}
|
|
2576
|
+
.reviw-question-submit:disabled {
|
|
2577
|
+
opacity: 0.5;
|
|
2578
|
+
cursor: not-allowed;
|
|
2579
|
+
}
|
|
2580
|
+
/* Resolved Section */
|
|
2581
|
+
.reviw-resolved-section {
|
|
2582
|
+
margin-top: 16px;
|
|
2583
|
+
border-top: 1px solid var(--border);
|
|
2584
|
+
padding-top: 12px;
|
|
2585
|
+
}
|
|
2586
|
+
.reviw-resolved-toggle {
|
|
2587
|
+
display: flex;
|
|
2588
|
+
align-items: center;
|
|
2589
|
+
gap: 8px;
|
|
2590
|
+
background: none;
|
|
2591
|
+
border: none;
|
|
2592
|
+
color: var(--text-dim);
|
|
2593
|
+
font-size: 13px;
|
|
2594
|
+
cursor: pointer;
|
|
2595
|
+
padding: 4px 0;
|
|
2596
|
+
}
|
|
2597
|
+
.reviw-resolved-toggle:hover {
|
|
2598
|
+
color: var(--text);
|
|
2599
|
+
}
|
|
2600
|
+
.reviw-resolved-toggle .arrow {
|
|
2601
|
+
transition: transform 150ms ease;
|
|
2602
|
+
}
|
|
2603
|
+
.reviw-resolved-toggle.open .arrow {
|
|
2604
|
+
transform: rotate(90deg);
|
|
2605
|
+
}
|
|
2606
|
+
.reviw-resolved-list {
|
|
2607
|
+
display: none;
|
|
2608
|
+
margin-top: 12px;
|
|
2609
|
+
}
|
|
2610
|
+
.reviw-resolved-list.visible {
|
|
2611
|
+
display: block;
|
|
2612
|
+
}
|
|
2613
|
+
.reviw-resolved-item {
|
|
2614
|
+
padding: 10px 12px;
|
|
2615
|
+
background: var(--input-bg);
|
|
2616
|
+
border-radius: 8px;
|
|
2617
|
+
margin-bottom: 8px;
|
|
2618
|
+
opacity: 0.7;
|
|
2619
|
+
}
|
|
2620
|
+
.reviw-resolved-item:last-child {
|
|
2621
|
+
margin-bottom: 0;
|
|
2622
|
+
}
|
|
2623
|
+
.reviw-resolved-q {
|
|
2624
|
+
font-size: 12px;
|
|
2625
|
+
color: var(--text-dim);
|
|
2626
|
+
margin-bottom: 4px;
|
|
2627
|
+
}
|
|
2628
|
+
.reviw-resolved-a {
|
|
2629
|
+
font-size: 13px;
|
|
2630
|
+
color: var(--text);
|
|
2631
|
+
}
|
|
2632
|
+
/* Notice Bar */
|
|
2633
|
+
.reviw-questions-bar {
|
|
2634
|
+
display: none;
|
|
2635
|
+
position: fixed;
|
|
2636
|
+
top: 0;
|
|
2637
|
+
left: 0;
|
|
2638
|
+
right: 0;
|
|
2639
|
+
background: var(--accent);
|
|
2640
|
+
color: var(--text-inverse);
|
|
2641
|
+
padding: 8px 16px;
|
|
2642
|
+
font-size: 13px;
|
|
2643
|
+
z-index: 1050;
|
|
2644
|
+
justify-content: center;
|
|
2645
|
+
align-items: center;
|
|
2646
|
+
gap: 12px;
|
|
2647
|
+
}
|
|
2648
|
+
.reviw-questions-bar.visible {
|
|
2649
|
+
display: flex;
|
|
2650
|
+
}
|
|
2651
|
+
.reviw-questions-bar button {
|
|
2652
|
+
padding: 4px 12px;
|
|
2653
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
2654
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2655
|
+
color: var(--text-inverse);
|
|
2656
|
+
border-radius: 6px;
|
|
2657
|
+
cursor: pointer;
|
|
2658
|
+
font-size: 12px;
|
|
2659
|
+
transition: all 150ms ease;
|
|
2660
|
+
}
|
|
2661
|
+
.reviw-questions-bar button:hover {
|
|
2662
|
+
background: rgba(255, 255, 255, 0.2);
|
|
2663
|
+
}
|
|
2664
|
+
/* Adjust layout when bar is visible */
|
|
2665
|
+
body.has-questions-bar header {
|
|
2666
|
+
top: 36px;
|
|
2667
|
+
}
|
|
2668
|
+
body.has-questions-bar .toolbar,
|
|
2669
|
+
body.has-questions-bar .table-wrap {
|
|
2670
|
+
margin-top: 36px;
|
|
2671
|
+
}
|
|
2270
2672
|
/* Copy notification toast */
|
|
2271
2673
|
.copy-toast {
|
|
2272
2674
|
position: fixed;
|
|
@@ -2529,7 +2931,7 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
2529
2931
|
<body>
|
|
2530
2932
|
<header>
|
|
2531
2933
|
<div class="meta">
|
|
2532
|
-
<h1>${title}</h1>
|
|
2934
|
+
<h1><span class="title-path">${projectRoot}</span><span class="title-file">${relativePath}</span></h1>
|
|
2533
2935
|
<span class="badge">Click to comment / ESC to cancel</span>
|
|
2534
2936
|
<span class="pill">Comments <strong id="comment-count">0</strong></span>
|
|
2535
2937
|
</div>
|
|
@@ -2682,11 +3084,32 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
2682
3084
|
<div class="video-container" id="video-container"></div>
|
|
2683
3085
|
</div>
|
|
2684
3086
|
|
|
3087
|
+
<!-- Reviw Questions Modal -->
|
|
3088
|
+
<div class="reviw-questions-overlay" id="reviw-questions-overlay">
|
|
3089
|
+
<div class="reviw-questions-modal" id="reviw-questions-modal">
|
|
3090
|
+
<div class="reviw-questions-header">
|
|
3091
|
+
<h2>📋 AIからの質問 <span id="reviw-questions-count"></span></h2>
|
|
3092
|
+
<button class="reviw-questions-close" id="reviw-questions-close" aria-label="Close">✕</button>
|
|
3093
|
+
</div>
|
|
3094
|
+
<div class="reviw-questions-body" id="reviw-questions-body"></div>
|
|
3095
|
+
<div class="reviw-questions-footer">
|
|
3096
|
+
<button class="reviw-questions-later" id="reviw-questions-later">後で回答する</button>
|
|
3097
|
+
</div>
|
|
3098
|
+
</div>
|
|
3099
|
+
</div>
|
|
3100
|
+
|
|
3101
|
+
<!-- Reviw Questions Notice Bar -->
|
|
3102
|
+
<div class="reviw-questions-bar" id="reviw-questions-bar">
|
|
3103
|
+
<span id="reviw-questions-bar-message">\ud83d\udccb \u672a\u56de\u7b54\u306e\u8cea\u554f\u304c<span id="reviw-questions-bar-count">0</span>\u4ef6\u3042\u308a\u307e\u3059</span>
|
|
3104
|
+
<button id="reviw-questions-bar-open">\u8cea\u554f\u3092\u898b\u308b</button>
|
|
3105
|
+
</div>
|
|
3106
|
+
|
|
2685
3107
|
<script>
|
|
2686
3108
|
const DATA = ${serialized};
|
|
2687
3109
|
const MAX_COLS = ${cols};
|
|
2688
3110
|
const FILE_NAME = ${titleJson};
|
|
2689
3111
|
const MODE = ${modeJson};
|
|
3112
|
+
const REVIW_QUESTIONS = ${questionsJson};
|
|
2690
3113
|
|
|
2691
3114
|
// --- Theme Management ---
|
|
2692
3115
|
(function initTheme() {
|
|
@@ -3045,6 +3468,12 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
3045
3468
|
|
|
3046
3469
|
function openCardForSelection() {
|
|
3047
3470
|
if (!selection) return;
|
|
3471
|
+
// Don't open card while image/video modal is visible
|
|
3472
|
+
const imageOverlay = document.getElementById('image-fullscreen');
|
|
3473
|
+
const videoOverlay = document.getElementById('video-fullscreen');
|
|
3474
|
+
if (imageOverlay?.classList.contains('visible') || videoOverlay?.classList.contains('visible')) {
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3048
3477
|
const { startRow, endRow, startCol, endCol } = selection;
|
|
3049
3478
|
const isSingleCell = startRow === endRow && startCol === endCol;
|
|
3050
3479
|
|
|
@@ -3561,6 +3990,22 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
3561
3990
|
if (globalComment.trim()) {
|
|
3562
3991
|
data.summary = globalComment.trim();
|
|
3563
3992
|
}
|
|
3993
|
+
// Include answered questions
|
|
3994
|
+
if (window.REVIW_ANSWERS) {
|
|
3995
|
+
const answeredQuestions = [];
|
|
3996
|
+
for (const [id, answer] of Object.entries(window.REVIW_ANSWERS)) {
|
|
3997
|
+
if (answer.selected || answer.text.trim()) {
|
|
3998
|
+
answeredQuestions.push({
|
|
3999
|
+
id,
|
|
4000
|
+
selected: answer.selected,
|
|
4001
|
+
text: answer.text.trim()
|
|
4002
|
+
});
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
if (answeredQuestions.length > 0) {
|
|
4006
|
+
data.reviwAnswers = answeredQuestions;
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
3564
4009
|
return data;
|
|
3565
4010
|
}
|
|
3566
4011
|
function sendAndExit(reason = 'pagehide') {
|
|
@@ -4042,8 +4487,9 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
4042
4487
|
}
|
|
4043
4488
|
|
|
4044
4489
|
if (imageOverlay) {
|
|
4490
|
+
// Close on any click (including image itself)
|
|
4045
4491
|
imageOverlay.addEventListener('click', (e) => {
|
|
4046
|
-
|
|
4492
|
+
closeImageOverlay();
|
|
4047
4493
|
});
|
|
4048
4494
|
}
|
|
4049
4495
|
|
|
@@ -4058,7 +4504,8 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
4058
4504
|
img.title = 'Click to view fullscreen';
|
|
4059
4505
|
|
|
4060
4506
|
img.addEventListener('click', (e) => {
|
|
4061
|
-
|
|
4507
|
+
// Don't stop propagation - allow select to work
|
|
4508
|
+
e.preventDefault();
|
|
4062
4509
|
|
|
4063
4510
|
imageContainer.innerHTML = '';
|
|
4064
4511
|
const clonedImg = img.cloneNode(true);
|
|
@@ -4103,8 +4550,9 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
4103
4550
|
}
|
|
4104
4551
|
|
|
4105
4552
|
if (videoOverlay) {
|
|
4553
|
+
// Close on any click (including video itself)
|
|
4106
4554
|
videoOverlay.addEventListener('click', (e) => {
|
|
4107
|
-
|
|
4555
|
+
closeVideoOverlay();
|
|
4108
4556
|
});
|
|
4109
4557
|
}
|
|
4110
4558
|
|
|
@@ -4123,7 +4571,7 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
4123
4571
|
|
|
4124
4572
|
link.addEventListener('click', (e) => {
|
|
4125
4573
|
e.preventDefault();
|
|
4126
|
-
|
|
4574
|
+
// Don't stop propagation - allow select to work
|
|
4127
4575
|
|
|
4128
4576
|
// Remove existing video if any
|
|
4129
4577
|
const existingVideo = videoContainer.querySelector('video');
|
|
@@ -4347,15 +4795,11 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
4347
4795
|
|
|
4348
4796
|
// Click on block elements
|
|
4349
4797
|
preview.addEventListener('click', (e) => {
|
|
4350
|
-
// Handle image clicks
|
|
4798
|
+
// Handle image clicks - always select, even if modal is showing
|
|
4351
4799
|
if (e.target.tagName === 'IMG') {
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
e.preventDefault();
|
|
4356
|
-
e.stopPropagation();
|
|
4357
|
-
selectSourceRange(line);
|
|
4358
|
-
}
|
|
4800
|
+
const line = findImageSourceLine(e.target.src);
|
|
4801
|
+
if (line > 0) {
|
|
4802
|
+
selectSourceRange(line);
|
|
4359
4803
|
}
|
|
4360
4804
|
return;
|
|
4361
4805
|
}
|
|
@@ -4425,6 +4869,227 @@ function htmlTemplate(dataRows, cols, title, mode, previewHtml) {
|
|
|
4425
4869
|
}, 10);
|
|
4426
4870
|
});
|
|
4427
4871
|
})();
|
|
4872
|
+
|
|
4873
|
+
// --- Reviw Questions Modal ---
|
|
4874
|
+
(function initReviwQuestions() {
|
|
4875
|
+
if (MODE !== 'markdown') return;
|
|
4876
|
+
if (!REVIW_QUESTIONS || REVIW_QUESTIONS.length === 0) return;
|
|
4877
|
+
|
|
4878
|
+
const overlay = document.getElementById('reviw-questions-overlay');
|
|
4879
|
+
const modal = document.getElementById('reviw-questions-modal');
|
|
4880
|
+
const body = document.getElementById('reviw-questions-body');
|
|
4881
|
+
const closeBtn = document.getElementById('reviw-questions-close');
|
|
4882
|
+
const laterBtn = document.getElementById('reviw-questions-later');
|
|
4883
|
+
const countSpan = document.getElementById('reviw-questions-count');
|
|
4884
|
+
const bar = document.getElementById('reviw-questions-bar');
|
|
4885
|
+
const barMessage = document.getElementById('reviw-questions-bar-message');
|
|
4886
|
+
const barCount = document.getElementById('reviw-questions-bar-count');
|
|
4887
|
+
const barOpenBtn = document.getElementById('reviw-questions-bar-open');
|
|
4888
|
+
|
|
4889
|
+
if (!overlay || !modal || !body) return;
|
|
4890
|
+
|
|
4891
|
+
// Store answers locally
|
|
4892
|
+
const answers = {};
|
|
4893
|
+
REVIW_QUESTIONS.forEach(q => {
|
|
4894
|
+
answers[q.id] = { selected: '', text: '' };
|
|
4895
|
+
});
|
|
4896
|
+
|
|
4897
|
+
// Count unresolved questions
|
|
4898
|
+
const unresolvedQuestions = REVIW_QUESTIONS.filter(q => !q.resolved);
|
|
4899
|
+
const resolvedQuestions = REVIW_QUESTIONS.filter(q => q.resolved);
|
|
4900
|
+
|
|
4901
|
+
function getUnansweredCount() {
|
|
4902
|
+
// Count questions that have no answer (no selection and no text)
|
|
4903
|
+
return unresolvedQuestions.filter(q => {
|
|
4904
|
+
const a = answers[q.id];
|
|
4905
|
+
return !a.selected && !a.text.trim();
|
|
4906
|
+
}).length;
|
|
4907
|
+
}
|
|
4908
|
+
|
|
4909
|
+
function updateCounts() {
|
|
4910
|
+
const unansweredCount = getUnansweredCount();
|
|
4911
|
+
if (unansweredCount > 0) {
|
|
4912
|
+
countSpan.textContent = '(' + unansweredCount + '\u4ef6\u672a\u56de\u7b54)';
|
|
4913
|
+
barMessage.innerHTML = '\ud83d\udccb \u672a\u56de\u7b54\u306e\u8cea\u554f\u304c<span id="reviw-questions-bar-count">' + unansweredCount + '</span>\u4ef6\u3042\u308a\u307e\u3059';
|
|
4914
|
+
laterBtn.textContent = '\u5f8c\u3067\u56de\u7b54\u3059\u308b';
|
|
4915
|
+
} else {
|
|
4916
|
+
countSpan.textContent = '(\u5168\u3066\u56de\u7b54\u6e08\u307f)';
|
|
4917
|
+
barMessage.innerHTML = '\u2705 \u5168\u3066\u306e\u8cea\u554f\u306b\u56de\u7b54\u3057\u307e\u3057\u305f';
|
|
4918
|
+
laterBtn.textContent = '\u9589\u3058\u308b';
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
|
|
4922
|
+
function checkAllAnswered() {
|
|
4923
|
+
if (getUnansweredCount() === 0) {
|
|
4924
|
+
// All answered - close modal but keep bar visible
|
|
4925
|
+
setTimeout(() => {
|
|
4926
|
+
overlay.classList.remove('visible');
|
|
4927
|
+
// Keep bar visible with different message
|
|
4928
|
+
bar.classList.add('visible');
|
|
4929
|
+
document.body.classList.add('has-questions-bar');
|
|
4930
|
+
}, 500);
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
|
|
4934
|
+
function renderQuestions() {
|
|
4935
|
+
body.innerHTML = '';
|
|
4936
|
+
|
|
4937
|
+
// Render unresolved questions
|
|
4938
|
+
unresolvedQuestions.forEach((q, idx) => {
|
|
4939
|
+
const item = document.createElement('div');
|
|
4940
|
+
item.className = 'reviw-question-item';
|
|
4941
|
+
item.dataset.id = q.id;
|
|
4942
|
+
|
|
4943
|
+
let optionsHtml = '';
|
|
4944
|
+
if (q.options && q.options.length > 0) {
|
|
4945
|
+
optionsHtml = '<div class="reviw-question-options">' +
|
|
4946
|
+
q.options.map(opt =>
|
|
4947
|
+
'<button class="reviw-question-option" data-value="' + escapeAttr(opt) + '">' + escapeHtml(opt) + '</button>'
|
|
4948
|
+
).join('') +
|
|
4949
|
+
'</div>';
|
|
4950
|
+
}
|
|
4951
|
+
|
|
4952
|
+
const isOkOnly = q.options && q.options.length === 1 && q.options[0] === 'OK';
|
|
4953
|
+
|
|
4954
|
+
item.innerHTML =
|
|
4955
|
+
'<div class="reviw-question-text">Q' + (idx + 1) + '. ' + escapeHtml(q.question) + '<span class="reviw-check-mark"></span></div>' +
|
|
4956
|
+
optionsHtml +
|
|
4957
|
+
(isOkOnly ? '' : '<textarea class="reviw-question-input" placeholder="\u30c6\u30ad\u30b9\u30c8\u3067\u56de\u7b54\u30fb\u88dc\u8db3..."></textarea>');
|
|
4958
|
+
|
|
4959
|
+
body.appendChild(item);
|
|
4960
|
+
|
|
4961
|
+
// Check mark element
|
|
4962
|
+
const checkMark = item.querySelector('.reviw-check-mark');
|
|
4963
|
+
|
|
4964
|
+
function updateCheckMark() {
|
|
4965
|
+
const answer = answers[q.id];
|
|
4966
|
+
const hasAnswer = answer.selected || answer.text.trim();
|
|
4967
|
+
if (hasAnswer) {
|
|
4968
|
+
checkMark.textContent = ' \u2713';
|
|
4969
|
+
item.classList.add('answered');
|
|
4970
|
+
} else {
|
|
4971
|
+
checkMark.textContent = '';
|
|
4972
|
+
item.classList.remove('answered');
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
|
|
4976
|
+
// Option click handlers - always toggle
|
|
4977
|
+
const optionBtns = item.querySelectorAll('.reviw-question-option');
|
|
4978
|
+
optionBtns.forEach(btn => {
|
|
4979
|
+
btn.addEventListener('click', () => {
|
|
4980
|
+
const wasSelected = btn.classList.contains('selected');
|
|
4981
|
+
optionBtns.forEach(b => b.classList.remove('selected'));
|
|
4982
|
+
if (!wasSelected) {
|
|
4983
|
+
btn.classList.add('selected');
|
|
4984
|
+
answers[q.id].selected = btn.dataset.value;
|
|
4985
|
+
} else {
|
|
4986
|
+
answers[q.id].selected = '';
|
|
4987
|
+
}
|
|
4988
|
+
updateCheckMark();
|
|
4989
|
+
updateCounts();
|
|
4990
|
+
checkAllAnswered();
|
|
4991
|
+
});
|
|
4992
|
+
});
|
|
4993
|
+
|
|
4994
|
+
// Text input handler
|
|
4995
|
+
const textarea = item.querySelector('.reviw-question-input');
|
|
4996
|
+
if (textarea) {
|
|
4997
|
+
textarea.addEventListener('input', () => {
|
|
4998
|
+
answers[q.id].text = textarea.value;
|
|
4999
|
+
updateCheckMark();
|
|
5000
|
+
updateCounts();
|
|
5001
|
+
checkAllAnswered();
|
|
5002
|
+
});
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
updateCheckMark();
|
|
5006
|
+
});
|
|
5007
|
+
|
|
5008
|
+
// Render resolved questions (collapsed)
|
|
5009
|
+
if (resolvedQuestions.length > 0) {
|
|
5010
|
+
const section = document.createElement('div');
|
|
5011
|
+
section.className = 'reviw-resolved-section';
|
|
5012
|
+
section.innerHTML =
|
|
5013
|
+
'<button class="reviw-resolved-toggle">' +
|
|
5014
|
+
'<span class="arrow">\u25b6</span> \u89e3\u6c7a\u6e08\u307f (' + resolvedQuestions.length + '\u4ef6)' +
|
|
5015
|
+
'</button>' +
|
|
5016
|
+
'<div class="reviw-resolved-list">' +
|
|
5017
|
+
resolvedQuestions.map(q =>
|
|
5018
|
+
'<div class="reviw-resolved-item">' +
|
|
5019
|
+
'<div class="reviw-resolved-q">' + escapeHtml(q.question) + '</div>' +
|
|
5020
|
+
'<div class="reviw-resolved-a">\u2192 ' + escapeHtml(q.answer || '(no answer)') + '</div>' +
|
|
5021
|
+
'</div>'
|
|
5022
|
+
).join('') +
|
|
5023
|
+
'</div>';
|
|
5024
|
+
body.appendChild(section);
|
|
5025
|
+
|
|
5026
|
+
const toggle = section.querySelector('.reviw-resolved-toggle');
|
|
5027
|
+
const list = section.querySelector('.reviw-resolved-list');
|
|
5028
|
+
toggle.addEventListener('click', () => {
|
|
5029
|
+
toggle.classList.toggle('open');
|
|
5030
|
+
list.classList.toggle('visible');
|
|
5031
|
+
});
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
|
|
5035
|
+
function escapeHtml(str) {
|
|
5036
|
+
return String(str)
|
|
5037
|
+
.replace(/&/g, '&')
|
|
5038
|
+
.replace(/</g, '<')
|
|
5039
|
+
.replace(/>/g, '>');
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
function escapeAttr(str) {
|
|
5043
|
+
return String(str)
|
|
5044
|
+
.replace(/&/g, '&')
|
|
5045
|
+
.replace(/"/g, '"');
|
|
5046
|
+
}
|
|
5047
|
+
|
|
5048
|
+
function openModal() {
|
|
5049
|
+
overlay.classList.add('visible');
|
|
5050
|
+
bar.classList.remove('visible');
|
|
5051
|
+
document.body.classList.remove('has-questions-bar');
|
|
5052
|
+
}
|
|
5053
|
+
|
|
5054
|
+
function closeModal(allAnswered) {
|
|
5055
|
+
overlay.classList.remove('visible');
|
|
5056
|
+
const unansweredCount = getUnansweredCount();
|
|
5057
|
+
if (unansweredCount > 0 && !allAnswered) {
|
|
5058
|
+
bar.classList.add('visible');
|
|
5059
|
+
document.body.classList.add('has-questions-bar');
|
|
5060
|
+
} else {
|
|
5061
|
+
bar.classList.remove('visible');
|
|
5062
|
+
document.body.classList.remove('has-questions-bar');
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
|
|
5066
|
+
// Event listeners
|
|
5067
|
+
closeBtn.addEventListener('click', () => closeModal(false));
|
|
5068
|
+
laterBtn.addEventListener('click', () => closeModal(false));
|
|
5069
|
+
barOpenBtn.addEventListener('click', openModal);
|
|
5070
|
+
|
|
5071
|
+
overlay.addEventListener('click', (e) => {
|
|
5072
|
+
if (e.target === overlay) closeModal(false);
|
|
5073
|
+
});
|
|
5074
|
+
|
|
5075
|
+
document.addEventListener('keydown', (e) => {
|
|
5076
|
+
if (e.key === 'Escape' && overlay.classList.contains('visible')) {
|
|
5077
|
+
closeModal(false);
|
|
5078
|
+
}
|
|
5079
|
+
});
|
|
5080
|
+
|
|
5081
|
+
// Expose answers for submit (only answered ones)
|
|
5082
|
+
window.REVIW_ANSWERS = answers;
|
|
5083
|
+
|
|
5084
|
+
// Initialize
|
|
5085
|
+
updateCounts();
|
|
5086
|
+
renderQuestions();
|
|
5087
|
+
|
|
5088
|
+
// Show modal on load if there are unresolved questions
|
|
5089
|
+
if (unresolvedQuestions.length > 0) {
|
|
5090
|
+
setTimeout(() => openModal(), 300);
|
|
5091
|
+
}
|
|
5092
|
+
})();
|
|
4428
5093
|
</script>
|
|
4429
5094
|
</body>
|
|
4430
5095
|
</html>`;
|
|
@@ -4435,8 +5100,8 @@ function buildHtml(filePath) {
|
|
|
4435
5100
|
if (data.mode === "diff") {
|
|
4436
5101
|
return diffHtmlTemplate(data);
|
|
4437
5102
|
}
|
|
4438
|
-
const { rows, cols,
|
|
4439
|
-
return htmlTemplate(rows, cols,
|
|
5103
|
+
const { rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions } = data;
|
|
5104
|
+
return htmlTemplate(rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions);
|
|
4440
5105
|
}
|
|
4441
5106
|
|
|
4442
5107
|
// --- HTTP Server -----------------------------------------------------------
|
|
@@ -4468,6 +5133,20 @@ function outputAllResults() {
|
|
|
4468
5133
|
const yamlOut = yaml.dump(combined, { noRefs: true, lineWidth: 120 });
|
|
4469
5134
|
console.log(yamlOut.trim());
|
|
4470
5135
|
}
|
|
5136
|
+
|
|
5137
|
+
// Output answered questions if any
|
|
5138
|
+
const allAnswers = [];
|
|
5139
|
+
for (const result of allResults) {
|
|
5140
|
+
if (result.reviwAnswers && result.reviwAnswers.length > 0) {
|
|
5141
|
+
allAnswers.push(...result.reviwAnswers);
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
if (allAnswers.length > 0) {
|
|
5145
|
+
console.log("\n[REVIW_ANSWERS]");
|
|
5146
|
+
const answersYaml = yaml.dump(allAnswers, { noRefs: true, lineWidth: 120 });
|
|
5147
|
+
console.log(answersYaml.trim());
|
|
5148
|
+
console.log("[/REVIW_ANSWERS]");
|
|
5149
|
+
}
|
|
4471
5150
|
}
|
|
4472
5151
|
|
|
4473
5152
|
function checkAllDone() {
|
|
@@ -4714,7 +5393,14 @@ function createFileServer(filePath, fileIndex = 0) {
|
|
|
4714
5393
|
const delay = fileIndex * 300;
|
|
4715
5394
|
setTimeout(() => {
|
|
4716
5395
|
try {
|
|
4717
|
-
spawn(opener, [url], { stdio: "ignore", detached: true });
|
|
5396
|
+
const child = spawn(opener, [url], { stdio: "ignore", detached: true });
|
|
5397
|
+
child.on('error', (err) => {
|
|
5398
|
+
console.warn(
|
|
5399
|
+
"Failed to open browser automatically. Please open this URL manually:",
|
|
5400
|
+
url,
|
|
5401
|
+
);
|
|
5402
|
+
});
|
|
5403
|
+
child.unref();
|
|
4718
5404
|
} catch (err) {
|
|
4719
5405
|
console.warn(
|
|
4720
5406
|
"Failed to open browser automatically. Please open this URL manually:",
|
|
@@ -4882,7 +5568,14 @@ function createDiffServer(diffContent) {
|
|
|
4882
5568
|
? "start"
|
|
4883
5569
|
: "xdg-open";
|
|
4884
5570
|
try {
|
|
4885
|
-
spawn(opener, [url], { stdio: "ignore", detached: true });
|
|
5571
|
+
const child = spawn(opener, [url], { stdio: "ignore", detached: true });
|
|
5572
|
+
child.on('error', (err) => {
|
|
5573
|
+
console.warn(
|
|
5574
|
+
"Failed to open browser automatically. Please open this URL manually:",
|
|
5575
|
+
url,
|
|
5576
|
+
);
|
|
5577
|
+
});
|
|
5578
|
+
child.unref();
|
|
4886
5579
|
} catch (err) {
|
|
4887
5580
|
console.warn(
|
|
4888
5581
|
"Failed to open browser automatically. Please open this URL manually:",
|