github-mobile-reader 0.1.1 → 0.1.3
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.ko.md +76 -8
- package/README.md +77 -9
- package/dist/action.js +180 -174
- package/dist/cli.js +421 -0
- package/dist/index.d.mts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +218 -18
- package/dist/index.mjs +206 -17
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -21,12 +21,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Priority: () => Priority,
|
|
24
|
+
extractChangedSymbols: () => extractChangedSymbols,
|
|
25
|
+
extractClassName: () => extractClassName,
|
|
26
|
+
extractJSXComponentName: () => extractJSXComponentName,
|
|
24
27
|
filterDiffLines: () => filterDiffLines,
|
|
25
28
|
generateReaderMarkdown: () => generateReaderMarkdown,
|
|
29
|
+
hasJSXContent: () => hasJSXContent,
|
|
30
|
+
isClassNameOnlyLine: () => isClassNameOnlyLine,
|
|
31
|
+
isJSXElement: () => isJSXElement,
|
|
32
|
+
isJSXFile: () => isJSXFile,
|
|
26
33
|
normalizeCode: () => normalizeCode,
|
|
34
|
+
parseClassNameChanges: () => parseClassNameChanges,
|
|
27
35
|
parseDiffToLogicalFlow: () => parseDiffToLogicalFlow,
|
|
36
|
+
parseJSXToFlowTree: () => parseJSXToFlowTree,
|
|
28
37
|
parseToFlowTree: () => parseToFlowTree,
|
|
29
|
-
renderFlowTree: () => renderFlowTree
|
|
38
|
+
renderFlowTree: () => renderFlowTree,
|
|
39
|
+
renderJSXTreeCompact: () => renderJSXTreeCompact,
|
|
40
|
+
renderStyleChanges: () => renderStyleChanges
|
|
30
41
|
});
|
|
31
42
|
module.exports = __toCommonJS(index_exports);
|
|
32
43
|
|
|
@@ -39,6 +50,132 @@ var Priority = /* @__PURE__ */ ((Priority2) => {
|
|
|
39
50
|
Priority2[Priority2["OTHER"] = 5] = "OTHER";
|
|
40
51
|
return Priority2;
|
|
41
52
|
})(Priority || {});
|
|
53
|
+
function isJSXFile(filename) {
|
|
54
|
+
return /\.(jsx|tsx)$/.test(filename);
|
|
55
|
+
}
|
|
56
|
+
function hasJSXContent(lines) {
|
|
57
|
+
return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
|
|
58
|
+
}
|
|
59
|
+
function isClassNameOnlyLine(line) {
|
|
60
|
+
return /^className=/.test(line.trim());
|
|
61
|
+
}
|
|
62
|
+
function extractClassName(line) {
|
|
63
|
+
const staticMatch = line.match(/className="([^"]*)"/);
|
|
64
|
+
if (staticMatch) return staticMatch[1];
|
|
65
|
+
const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
|
|
66
|
+
if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
|
|
67
|
+
const templateMatch = line.match(/className=\{`([^`]*)`\}/);
|
|
68
|
+
if (templateMatch) {
|
|
69
|
+
const raw = templateMatch[1];
|
|
70
|
+
const literals = raw.replace(/\$\{[^}]*\}/g, " ").trim();
|
|
71
|
+
const exprStrings = [...raw.matchAll(/"([^"]*)"/g)].map((m) => m[1]);
|
|
72
|
+
return [literals, ...exprStrings].filter(Boolean).join(" ");
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
function extractComponentFromLine(line) {
|
|
77
|
+
const tagMatch = line.match(/<([A-Za-z][A-Za-z0-9.]*)/);
|
|
78
|
+
if (tagMatch) return tagMatch[1];
|
|
79
|
+
return "unknown";
|
|
80
|
+
}
|
|
81
|
+
function parseClassNameChanges(addedLines, removedLines) {
|
|
82
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const line of addedLines.filter((l) => /className=/.test(l))) {
|
|
84
|
+
const cls = extractClassName(line);
|
|
85
|
+
const comp = extractComponentFromLine(line);
|
|
86
|
+
if (!cls) continue;
|
|
87
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
88
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
|
|
89
|
+
}
|
|
90
|
+
for (const line of removedLines.filter((l) => /className=/.test(l))) {
|
|
91
|
+
const cls = extractClassName(line);
|
|
92
|
+
const comp = extractComponentFromLine(line);
|
|
93
|
+
if (!cls) continue;
|
|
94
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
95
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
|
|
96
|
+
}
|
|
97
|
+
const changes = [];
|
|
98
|
+
for (const [comp, { added, removed }] of componentMap) {
|
|
99
|
+
if (comp === "unknown") continue;
|
|
100
|
+
const pureAdded = [...added].filter((c) => !removed.has(c));
|
|
101
|
+
const pureRemoved = [...removed].filter((c) => !added.has(c));
|
|
102
|
+
if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
|
|
103
|
+
changes.push({ component: comp, added: pureAdded, removed: pureRemoved });
|
|
104
|
+
}
|
|
105
|
+
return changes;
|
|
106
|
+
}
|
|
107
|
+
function renderStyleChanges(changes) {
|
|
108
|
+
const lines = [];
|
|
109
|
+
for (const change of changes) {
|
|
110
|
+
lines.push(`**${change.component}**`);
|
|
111
|
+
if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
|
|
112
|
+
if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
|
|
113
|
+
}
|
|
114
|
+
return lines;
|
|
115
|
+
}
|
|
116
|
+
function isJSXElement(line) {
|
|
117
|
+
const t = line.trim();
|
|
118
|
+
return /^<[A-Za-z]/.test(t) || /^<\/[A-Za-z]/.test(t);
|
|
119
|
+
}
|
|
120
|
+
function isJSXClosing(line) {
|
|
121
|
+
return /^<\/[A-Za-z]/.test(line.trim());
|
|
122
|
+
}
|
|
123
|
+
function isJSXSelfClosing(line) {
|
|
124
|
+
return /\/>[\s]*$/.test(line.trim());
|
|
125
|
+
}
|
|
126
|
+
function extractJSXComponentName(line) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
const closingMatch = trimmed.match(/^<\/([A-Za-z][A-Za-z0-9.]*)/);
|
|
129
|
+
if (closingMatch) return `/${closingMatch[1]}`;
|
|
130
|
+
const nameMatch = trimmed.match(/^<([A-Za-z][A-Za-z0-9.]*)/);
|
|
131
|
+
if (!nameMatch) return trimmed;
|
|
132
|
+
const name = nameMatch[1];
|
|
133
|
+
const eventProps = [];
|
|
134
|
+
for (const m of trimmed.matchAll(/\b(on[A-Z]\w+)=/g)) {
|
|
135
|
+
eventProps.push(m[1]);
|
|
136
|
+
}
|
|
137
|
+
return eventProps.length > 0 ? `${name}(${eventProps.join(", ")})` : name;
|
|
138
|
+
}
|
|
139
|
+
function shouldIgnoreJSX(line) {
|
|
140
|
+
const t = line.trim();
|
|
141
|
+
return isClassNameOnlyLine(t) || /^style=/.test(t) || /^aria-/.test(t) || /^data-/.test(t) || /^strokeLinecap=/.test(t) || /^strokeLinejoin=/.test(t) || /^strokeWidth=/.test(t) || /^viewBox=/.test(t) || /^fill=/.test(t) || /^stroke=/.test(t) || /^d="/.test(t) || t === "{" || t === "}" || t === "(" || t === ")" || t === "<>" || t === "</>" || /^\{\/\*/.test(t);
|
|
142
|
+
}
|
|
143
|
+
function parseJSXToFlowTree(lines) {
|
|
144
|
+
const roots = [];
|
|
145
|
+
const stack = [];
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
if (!isJSXElement(line)) continue;
|
|
148
|
+
if (shouldIgnoreJSX(line)) continue;
|
|
149
|
+
const depth = getIndentDepth(line);
|
|
150
|
+
if (isJSXClosing(line)) {
|
|
151
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
152
|
+
stack.pop();
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const name = extractJSXComponentName(line);
|
|
157
|
+
const selfClosing = isJSXSelfClosing(line);
|
|
158
|
+
const node = {
|
|
159
|
+
type: "call",
|
|
160
|
+
name,
|
|
161
|
+
children: [],
|
|
162
|
+
depth,
|
|
163
|
+
priority: 5 /* OTHER */
|
|
164
|
+
};
|
|
165
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
166
|
+
stack.pop();
|
|
167
|
+
}
|
|
168
|
+
if (stack.length === 0) {
|
|
169
|
+
roots.push(node);
|
|
170
|
+
} else {
|
|
171
|
+
stack[stack.length - 1].node.children.push(node);
|
|
172
|
+
}
|
|
173
|
+
if (!selfClosing) {
|
|
174
|
+
stack.push({ node, depth });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return roots;
|
|
178
|
+
}
|
|
42
179
|
function filterDiffLines(diffText) {
|
|
43
180
|
const lines = diffText.split("\n");
|
|
44
181
|
const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
|
|
@@ -222,8 +359,59 @@ function parseDiffToLogicalFlow(diffText) {
|
|
|
222
359
|
removedCode: removed.join("\n")
|
|
223
360
|
};
|
|
224
361
|
}
|
|
362
|
+
function extractChangedSymbols(addedLines, removedLines) {
|
|
363
|
+
const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s*)?\(/;
|
|
364
|
+
const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
|
|
365
|
+
const extract = (lines) => {
|
|
366
|
+
const names = /* @__PURE__ */ new Set();
|
|
367
|
+
for (const line of lines) {
|
|
368
|
+
const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
|
|
369
|
+
if (cm) {
|
|
370
|
+
const name = cm[1] || cm[2];
|
|
371
|
+
if (name) names.add(name);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return names;
|
|
375
|
+
};
|
|
376
|
+
const addedNames = extract(addedLines);
|
|
377
|
+
const removedNames = extract(removedLines);
|
|
378
|
+
const results = [];
|
|
379
|
+
const seen = /* @__PURE__ */ new Set();
|
|
380
|
+
for (const name of addedNames) {
|
|
381
|
+
seen.add(name);
|
|
382
|
+
results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
|
|
383
|
+
}
|
|
384
|
+
for (const name of removedNames) {
|
|
385
|
+
if (!seen.has(name)) {
|
|
386
|
+
results.push({ name, status: "removed" });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return results;
|
|
390
|
+
}
|
|
391
|
+
function renderJSXTreeCompact(nodes, maxDepth = 3) {
|
|
392
|
+
const lines = [];
|
|
393
|
+
function walk(node, depth) {
|
|
394
|
+
if (depth > maxDepth) return;
|
|
395
|
+
const indent = " ".repeat(depth);
|
|
396
|
+
const hasChildren = node.children.length > 0;
|
|
397
|
+
lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
|
|
398
|
+
for (const child of node.children) {
|
|
399
|
+
walk(child, depth + 1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (const root of nodes) {
|
|
403
|
+
walk(root, 0);
|
|
404
|
+
}
|
|
405
|
+
return lines.join("\n");
|
|
406
|
+
}
|
|
225
407
|
function generateReaderMarkdown(diffText, meta = {}) {
|
|
226
|
-
const
|
|
408
|
+
const { added, removed } = filterDiffLines(diffText);
|
|
409
|
+
const isJSX = Boolean(
|
|
410
|
+
meta.file && isJSXFile(meta.file) || hasJSXContent(added)
|
|
411
|
+
);
|
|
412
|
+
const changedSymbols = extractChangedSymbols(added, removed);
|
|
413
|
+
const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
|
|
414
|
+
const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
|
|
227
415
|
const sections = [];
|
|
228
416
|
sections.push("# \u{1F4D6} GitHub Reader View\n");
|
|
229
417
|
sections.push("> Generated by **github-mobile-reader**");
|
|
@@ -232,35 +420,47 @@ function generateReaderMarkdown(diffText, meta = {}) {
|
|
|
232
420
|
if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
|
|
233
421
|
if (meta.file) sections.push(`> File: \`${meta.file}\``);
|
|
234
422
|
sections.push("\n");
|
|
235
|
-
if (
|
|
236
|
-
sections.push("
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
423
|
+
if (changedSymbols.length > 0) {
|
|
424
|
+
sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
|
|
425
|
+
const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
|
|
426
|
+
for (const { name, status } of changedSymbols) {
|
|
427
|
+
sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
|
|
428
|
+
}
|
|
429
|
+
sections.push("");
|
|
240
430
|
}
|
|
241
|
-
if (
|
|
242
|
-
sections.push("
|
|
243
|
-
sections.push("```
|
|
244
|
-
sections.push(
|
|
431
|
+
if (isJSX && jsxTree.length > 0) {
|
|
432
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
433
|
+
sections.push("```");
|
|
434
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
245
435
|
sections.push("```\n");
|
|
246
436
|
}
|
|
247
|
-
if (
|
|
248
|
-
sections.push("
|
|
249
|
-
sections.push(
|
|
250
|
-
sections.push(
|
|
251
|
-
sections.push("```\n");
|
|
437
|
+
if (isJSX && classNameChanges.length > 0) {
|
|
438
|
+
sections.push("### \u{1F485} Style Changes\n");
|
|
439
|
+
sections.push(...renderStyleChanges(classNameChanges));
|
|
440
|
+
sections.push("");
|
|
252
441
|
}
|
|
253
442
|
sections.push("---");
|
|
254
|
-
sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/
|
|
443
|
+
sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
|
|
255
444
|
return sections.join("\n");
|
|
256
445
|
}
|
|
257
446
|
// Annotate the CommonJS export names for ESM import in node:
|
|
258
447
|
0 && (module.exports = {
|
|
259
448
|
Priority,
|
|
449
|
+
extractChangedSymbols,
|
|
450
|
+
extractClassName,
|
|
451
|
+
extractJSXComponentName,
|
|
260
452
|
filterDiffLines,
|
|
261
453
|
generateReaderMarkdown,
|
|
454
|
+
hasJSXContent,
|
|
455
|
+
isClassNameOnlyLine,
|
|
456
|
+
isJSXElement,
|
|
457
|
+
isJSXFile,
|
|
262
458
|
normalizeCode,
|
|
459
|
+
parseClassNameChanges,
|
|
263
460
|
parseDiffToLogicalFlow,
|
|
461
|
+
parseJSXToFlowTree,
|
|
264
462
|
parseToFlowTree,
|
|
265
|
-
renderFlowTree
|
|
463
|
+
renderFlowTree,
|
|
464
|
+
renderJSXTreeCompact,
|
|
465
|
+
renderStyleChanges
|
|
266
466
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -7,6 +7,132 @@ var Priority = /* @__PURE__ */ ((Priority2) => {
|
|
|
7
7
|
Priority2[Priority2["OTHER"] = 5] = "OTHER";
|
|
8
8
|
return Priority2;
|
|
9
9
|
})(Priority || {});
|
|
10
|
+
function isJSXFile(filename) {
|
|
11
|
+
return /\.(jsx|tsx)$/.test(filename);
|
|
12
|
+
}
|
|
13
|
+
function hasJSXContent(lines) {
|
|
14
|
+
return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
|
|
15
|
+
}
|
|
16
|
+
function isClassNameOnlyLine(line) {
|
|
17
|
+
return /^className=/.test(line.trim());
|
|
18
|
+
}
|
|
19
|
+
function extractClassName(line) {
|
|
20
|
+
const staticMatch = line.match(/className="([^"]*)"/);
|
|
21
|
+
if (staticMatch) return staticMatch[1];
|
|
22
|
+
const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
|
|
23
|
+
if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
|
|
24
|
+
const templateMatch = line.match(/className=\{`([^`]*)`\}/);
|
|
25
|
+
if (templateMatch) {
|
|
26
|
+
const raw = templateMatch[1];
|
|
27
|
+
const literals = raw.replace(/\$\{[^}]*\}/g, " ").trim();
|
|
28
|
+
const exprStrings = [...raw.matchAll(/"([^"]*)"/g)].map((m) => m[1]);
|
|
29
|
+
return [literals, ...exprStrings].filter(Boolean).join(" ");
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function extractComponentFromLine(line) {
|
|
34
|
+
const tagMatch = line.match(/<([A-Za-z][A-Za-z0-9.]*)/);
|
|
35
|
+
if (tagMatch) return tagMatch[1];
|
|
36
|
+
return "unknown";
|
|
37
|
+
}
|
|
38
|
+
function parseClassNameChanges(addedLines, removedLines) {
|
|
39
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const line of addedLines.filter((l) => /className=/.test(l))) {
|
|
41
|
+
const cls = extractClassName(line);
|
|
42
|
+
const comp = extractComponentFromLine(line);
|
|
43
|
+
if (!cls) continue;
|
|
44
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
45
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
|
|
46
|
+
}
|
|
47
|
+
for (const line of removedLines.filter((l) => /className=/.test(l))) {
|
|
48
|
+
const cls = extractClassName(line);
|
|
49
|
+
const comp = extractComponentFromLine(line);
|
|
50
|
+
if (!cls) continue;
|
|
51
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
52
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
|
|
53
|
+
}
|
|
54
|
+
const changes = [];
|
|
55
|
+
for (const [comp, { added, removed }] of componentMap) {
|
|
56
|
+
if (comp === "unknown") continue;
|
|
57
|
+
const pureAdded = [...added].filter((c) => !removed.has(c));
|
|
58
|
+
const pureRemoved = [...removed].filter((c) => !added.has(c));
|
|
59
|
+
if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
|
|
60
|
+
changes.push({ component: comp, added: pureAdded, removed: pureRemoved });
|
|
61
|
+
}
|
|
62
|
+
return changes;
|
|
63
|
+
}
|
|
64
|
+
function renderStyleChanges(changes) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
for (const change of changes) {
|
|
67
|
+
lines.push(`**${change.component}**`);
|
|
68
|
+
if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
|
|
69
|
+
if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
|
|
70
|
+
}
|
|
71
|
+
return lines;
|
|
72
|
+
}
|
|
73
|
+
function isJSXElement(line) {
|
|
74
|
+
const t = line.trim();
|
|
75
|
+
return /^<[A-Za-z]/.test(t) || /^<\/[A-Za-z]/.test(t);
|
|
76
|
+
}
|
|
77
|
+
function isJSXClosing(line) {
|
|
78
|
+
return /^<\/[A-Za-z]/.test(line.trim());
|
|
79
|
+
}
|
|
80
|
+
function isJSXSelfClosing(line) {
|
|
81
|
+
return /\/>[\s]*$/.test(line.trim());
|
|
82
|
+
}
|
|
83
|
+
function extractJSXComponentName(line) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
const closingMatch = trimmed.match(/^<\/([A-Za-z][A-Za-z0-9.]*)/);
|
|
86
|
+
if (closingMatch) return `/${closingMatch[1]}`;
|
|
87
|
+
const nameMatch = trimmed.match(/^<([A-Za-z][A-Za-z0-9.]*)/);
|
|
88
|
+
if (!nameMatch) return trimmed;
|
|
89
|
+
const name = nameMatch[1];
|
|
90
|
+
const eventProps = [];
|
|
91
|
+
for (const m of trimmed.matchAll(/\b(on[A-Z]\w+)=/g)) {
|
|
92
|
+
eventProps.push(m[1]);
|
|
93
|
+
}
|
|
94
|
+
return eventProps.length > 0 ? `${name}(${eventProps.join(", ")})` : name;
|
|
95
|
+
}
|
|
96
|
+
function shouldIgnoreJSX(line) {
|
|
97
|
+
const t = line.trim();
|
|
98
|
+
return isClassNameOnlyLine(t) || /^style=/.test(t) || /^aria-/.test(t) || /^data-/.test(t) || /^strokeLinecap=/.test(t) || /^strokeLinejoin=/.test(t) || /^strokeWidth=/.test(t) || /^viewBox=/.test(t) || /^fill=/.test(t) || /^stroke=/.test(t) || /^d="/.test(t) || t === "{" || t === "}" || t === "(" || t === ")" || t === "<>" || t === "</>" || /^\{\/\*/.test(t);
|
|
99
|
+
}
|
|
100
|
+
function parseJSXToFlowTree(lines) {
|
|
101
|
+
const roots = [];
|
|
102
|
+
const stack = [];
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
if (!isJSXElement(line)) continue;
|
|
105
|
+
if (shouldIgnoreJSX(line)) continue;
|
|
106
|
+
const depth = getIndentDepth(line);
|
|
107
|
+
if (isJSXClosing(line)) {
|
|
108
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
109
|
+
stack.pop();
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const name = extractJSXComponentName(line);
|
|
114
|
+
const selfClosing = isJSXSelfClosing(line);
|
|
115
|
+
const node = {
|
|
116
|
+
type: "call",
|
|
117
|
+
name,
|
|
118
|
+
children: [],
|
|
119
|
+
depth,
|
|
120
|
+
priority: 5 /* OTHER */
|
|
121
|
+
};
|
|
122
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
123
|
+
stack.pop();
|
|
124
|
+
}
|
|
125
|
+
if (stack.length === 0) {
|
|
126
|
+
roots.push(node);
|
|
127
|
+
} else {
|
|
128
|
+
stack[stack.length - 1].node.children.push(node);
|
|
129
|
+
}
|
|
130
|
+
if (!selfClosing) {
|
|
131
|
+
stack.push({ node, depth });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return roots;
|
|
135
|
+
}
|
|
10
136
|
function filterDiffLines(diffText) {
|
|
11
137
|
const lines = diffText.split("\n");
|
|
12
138
|
const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
|
|
@@ -190,8 +316,59 @@ function parseDiffToLogicalFlow(diffText) {
|
|
|
190
316
|
removedCode: removed.join("\n")
|
|
191
317
|
};
|
|
192
318
|
}
|
|
319
|
+
function extractChangedSymbols(addedLines, removedLines) {
|
|
320
|
+
const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s*)?\(/;
|
|
321
|
+
const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
|
|
322
|
+
const extract = (lines) => {
|
|
323
|
+
const names = /* @__PURE__ */ new Set();
|
|
324
|
+
for (const line of lines) {
|
|
325
|
+
const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
|
|
326
|
+
if (cm) {
|
|
327
|
+
const name = cm[1] || cm[2];
|
|
328
|
+
if (name) names.add(name);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return names;
|
|
332
|
+
};
|
|
333
|
+
const addedNames = extract(addedLines);
|
|
334
|
+
const removedNames = extract(removedLines);
|
|
335
|
+
const results = [];
|
|
336
|
+
const seen = /* @__PURE__ */ new Set();
|
|
337
|
+
for (const name of addedNames) {
|
|
338
|
+
seen.add(name);
|
|
339
|
+
results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
|
|
340
|
+
}
|
|
341
|
+
for (const name of removedNames) {
|
|
342
|
+
if (!seen.has(name)) {
|
|
343
|
+
results.push({ name, status: "removed" });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return results;
|
|
347
|
+
}
|
|
348
|
+
function renderJSXTreeCompact(nodes, maxDepth = 3) {
|
|
349
|
+
const lines = [];
|
|
350
|
+
function walk(node, depth) {
|
|
351
|
+
if (depth > maxDepth) return;
|
|
352
|
+
const indent = " ".repeat(depth);
|
|
353
|
+
const hasChildren = node.children.length > 0;
|
|
354
|
+
lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
|
|
355
|
+
for (const child of node.children) {
|
|
356
|
+
walk(child, depth + 1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const root of nodes) {
|
|
360
|
+
walk(root, 0);
|
|
361
|
+
}
|
|
362
|
+
return lines.join("\n");
|
|
363
|
+
}
|
|
193
364
|
function generateReaderMarkdown(diffText, meta = {}) {
|
|
194
|
-
const
|
|
365
|
+
const { added, removed } = filterDiffLines(diffText);
|
|
366
|
+
const isJSX = Boolean(
|
|
367
|
+
meta.file && isJSXFile(meta.file) || hasJSXContent(added)
|
|
368
|
+
);
|
|
369
|
+
const changedSymbols = extractChangedSymbols(added, removed);
|
|
370
|
+
const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
|
|
371
|
+
const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
|
|
195
372
|
const sections = [];
|
|
196
373
|
sections.push("# \u{1F4D6} GitHub Reader View\n");
|
|
197
374
|
sections.push("> Generated by **github-mobile-reader**");
|
|
@@ -200,34 +377,46 @@ function generateReaderMarkdown(diffText, meta = {}) {
|
|
|
200
377
|
if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
|
|
201
378
|
if (meta.file) sections.push(`> File: \`${meta.file}\``);
|
|
202
379
|
sections.push("\n");
|
|
203
|
-
if (
|
|
204
|
-
sections.push("
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
380
|
+
if (changedSymbols.length > 0) {
|
|
381
|
+
sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
|
|
382
|
+
const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
|
|
383
|
+
for (const { name, status } of changedSymbols) {
|
|
384
|
+
sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
|
|
385
|
+
}
|
|
386
|
+
sections.push("");
|
|
208
387
|
}
|
|
209
|
-
if (
|
|
210
|
-
sections.push("
|
|
211
|
-
sections.push("```
|
|
212
|
-
sections.push(
|
|
388
|
+
if (isJSX && jsxTree.length > 0) {
|
|
389
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
390
|
+
sections.push("```");
|
|
391
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
213
392
|
sections.push("```\n");
|
|
214
393
|
}
|
|
215
|
-
if (
|
|
216
|
-
sections.push("
|
|
217
|
-
sections.push(
|
|
218
|
-
sections.push(
|
|
219
|
-
sections.push("```\n");
|
|
394
|
+
if (isJSX && classNameChanges.length > 0) {
|
|
395
|
+
sections.push("### \u{1F485} Style Changes\n");
|
|
396
|
+
sections.push(...renderStyleChanges(classNameChanges));
|
|
397
|
+
sections.push("");
|
|
220
398
|
}
|
|
221
399
|
sections.push("---");
|
|
222
|
-
sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/
|
|
400
|
+
sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
|
|
223
401
|
return sections.join("\n");
|
|
224
402
|
}
|
|
225
403
|
export {
|
|
226
404
|
Priority,
|
|
405
|
+
extractChangedSymbols,
|
|
406
|
+
extractClassName,
|
|
407
|
+
extractJSXComponentName,
|
|
227
408
|
filterDiffLines,
|
|
228
409
|
generateReaderMarkdown,
|
|
410
|
+
hasJSXContent,
|
|
411
|
+
isClassNameOnlyLine,
|
|
412
|
+
isJSXElement,
|
|
413
|
+
isJSXFile,
|
|
229
414
|
normalizeCode,
|
|
415
|
+
parseClassNameChanges,
|
|
230
416
|
parseDiffToLogicalFlow,
|
|
417
|
+
parseJSXToFlowTree,
|
|
231
418
|
parseToFlowTree,
|
|
232
|
-
renderFlowTree
|
|
419
|
+
renderFlowTree,
|
|
420
|
+
renderJSXTreeCompact,
|
|
421
|
+
renderStyleChanges
|
|
233
422
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-mobile-reader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Transform git diffs into mobile-friendly Markdown — no more horizontal scrolling when reviewing code on your phone.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github",
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/3rdflr/github-mobile-reader.git"
|
|
20
|
+
"url": "git+https://github.com/3rdflr/github-mobile-reader.git"
|
|
21
21
|
},
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"author": "3rdflrhtl@gmail.com",
|
|
24
|
+
"bin": {
|
|
25
|
+
"github-mobile-reader": "dist/cli.js"
|
|
26
|
+
},
|
|
24
27
|
"main": "./dist/index.js",
|
|
25
28
|
"module": "./dist/index.mjs",
|
|
26
29
|
"types": "./dist/index.d.ts",
|
|
@@ -39,7 +42,8 @@
|
|
|
39
42
|
"scripts": {
|
|
40
43
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
41
44
|
"build:action": "tsup src/action.ts --format cjs --outDir dist --no-splitting",
|
|
42
|
-
"build:
|
|
45
|
+
"build:cli": "tsup src/cli.ts --format cjs --outDir dist --no-splitting",
|
|
46
|
+
"build:all": "npm run build && npm run build:action && npm run build:cli",
|
|
43
47
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
44
48
|
"prepublishOnly": "npm run build:all"
|
|
45
49
|
},
|