github-mobile-reader 0.1.0 → 0.1.2

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/dist/index.js CHANGED
@@ -21,12 +21,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Priority: () => Priority,
24
+ extractClassName: () => extractClassName,
25
+ extractJSXComponentName: () => extractJSXComponentName,
24
26
  filterDiffLines: () => filterDiffLines,
25
27
  generateReaderMarkdown: () => generateReaderMarkdown,
28
+ hasJSXContent: () => hasJSXContent,
29
+ isClassNameOnlyLine: () => isClassNameOnlyLine,
30
+ isJSXElement: () => isJSXElement,
31
+ isJSXFile: () => isJSXFile,
26
32
  normalizeCode: () => normalizeCode,
33
+ parseClassNameChanges: () => parseClassNameChanges,
27
34
  parseDiffToLogicalFlow: () => parseDiffToLogicalFlow,
35
+ parseJSXToFlowTree: () => parseJSXToFlowTree,
28
36
  parseToFlowTree: () => parseToFlowTree,
29
- renderFlowTree: () => renderFlowTree
37
+ renderFlowTree: () => renderFlowTree,
38
+ renderStyleChanges: () => renderStyleChanges
30
39
  });
31
40
  module.exports = __toCommonJS(index_exports);
32
41
 
@@ -39,6 +48,131 @@ var Priority = /* @__PURE__ */ ((Priority2) => {
39
48
  Priority2[Priority2["OTHER"] = 5] = "OTHER";
40
49
  return Priority2;
41
50
  })(Priority || {});
51
+ function isJSXFile(filename) {
52
+ return /\.(jsx|tsx)$/.test(filename);
53
+ }
54
+ function hasJSXContent(lines) {
55
+ return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
56
+ }
57
+ function isClassNameOnlyLine(line) {
58
+ return /^className=/.test(line.trim());
59
+ }
60
+ function extractClassName(line) {
61
+ const staticMatch = line.match(/className="([^"]*)"/);
62
+ if (staticMatch) return staticMatch[1];
63
+ const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
64
+ if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
65
+ const templateMatch = line.match(/className=\{`([^`]*)`\}/);
66
+ if (templateMatch) {
67
+ const raw = templateMatch[1];
68
+ const literals = raw.replace(/\$\{[^}]*\}/g, " ").trim();
69
+ const exprStrings = [...raw.matchAll(/"([^"]*)"/g)].map((m) => m[1]);
70
+ return [literals, ...exprStrings].filter(Boolean).join(" ");
71
+ }
72
+ return null;
73
+ }
74
+ function extractComponentFromLine(line) {
75
+ const tagMatch = line.match(/<([A-Za-z][A-Za-z0-9.]*)/);
76
+ if (tagMatch) return tagMatch[1];
77
+ return "unknown";
78
+ }
79
+ function parseClassNameChanges(addedLines, removedLines) {
80
+ const componentMap = /* @__PURE__ */ new Map();
81
+ for (const line of addedLines.filter((l) => /className=/.test(l))) {
82
+ const cls = extractClassName(line);
83
+ const comp = extractComponentFromLine(line);
84
+ if (!cls) continue;
85
+ if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
86
+ cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
87
+ }
88
+ for (const line of removedLines.filter((l) => /className=/.test(l))) {
89
+ const cls = extractClassName(line);
90
+ const comp = extractComponentFromLine(line);
91
+ if (!cls) continue;
92
+ if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
93
+ cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
94
+ }
95
+ const changes = [];
96
+ for (const [comp, { added, removed }] of componentMap) {
97
+ const pureAdded = [...added].filter((c) => !removed.has(c));
98
+ const pureRemoved = [...removed].filter((c) => !added.has(c));
99
+ if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
100
+ changes.push({ component: comp, added: pureAdded, removed: pureRemoved });
101
+ }
102
+ return changes;
103
+ }
104
+ function renderStyleChanges(changes) {
105
+ const lines = [];
106
+ for (const change of changes) {
107
+ lines.push(`**${change.component}**`);
108
+ if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
109
+ if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
110
+ }
111
+ return lines;
112
+ }
113
+ function isJSXElement(line) {
114
+ const t = line.trim();
115
+ return /^<[A-Za-z]/.test(t) || /^<\/[A-Za-z]/.test(t);
116
+ }
117
+ function isJSXClosing(line) {
118
+ return /^<\/[A-Za-z]/.test(line.trim());
119
+ }
120
+ function isJSXSelfClosing(line) {
121
+ return /\/>[\s]*$/.test(line.trim());
122
+ }
123
+ function extractJSXComponentName(line) {
124
+ const trimmed = line.trim();
125
+ const closingMatch = trimmed.match(/^<\/([A-Za-z][A-Za-z0-9.]*)/);
126
+ if (closingMatch) return `/${closingMatch[1]}`;
127
+ const nameMatch = trimmed.match(/^<([A-Za-z][A-Za-z0-9.]*)/);
128
+ if (!nameMatch) return trimmed;
129
+ const name = nameMatch[1];
130
+ const eventProps = [];
131
+ for (const m of trimmed.matchAll(/\b(on[A-Z]\w+)=/g)) {
132
+ eventProps.push(m[1]);
133
+ }
134
+ return eventProps.length > 0 ? `${name}(${eventProps.join(", ")})` : name;
135
+ }
136
+ function shouldIgnoreJSX(line) {
137
+ const t = line.trim();
138
+ 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);
139
+ }
140
+ function parseJSXToFlowTree(lines) {
141
+ const roots = [];
142
+ const stack = [];
143
+ for (const line of lines) {
144
+ if (!isJSXElement(line)) continue;
145
+ if (shouldIgnoreJSX(line)) continue;
146
+ const depth = getIndentDepth(line);
147
+ if (isJSXClosing(line)) {
148
+ while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
149
+ stack.pop();
150
+ }
151
+ continue;
152
+ }
153
+ const name = extractJSXComponentName(line);
154
+ const selfClosing = isJSXSelfClosing(line);
155
+ const node = {
156
+ type: "call",
157
+ name,
158
+ children: [],
159
+ depth,
160
+ priority: 5 /* OTHER */
161
+ };
162
+ while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
163
+ stack.pop();
164
+ }
165
+ if (stack.length === 0) {
166
+ roots.push(node);
167
+ } else {
168
+ stack[stack.length - 1].node.children.push(node);
169
+ }
170
+ if (!selfClosing) {
171
+ stack.push({ node, depth });
172
+ }
173
+ }
174
+ return roots;
175
+ }
42
176
  function filterDiffLines(diffText) {
43
177
  const lines = diffText.split("\n");
44
178
  const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
@@ -48,10 +182,10 @@ function filterDiffLines(diffText) {
48
182
  function normalizeCode(lines) {
49
183
  return lines.map((line) => {
50
184
  let normalized = line;
51
- normalized = normalized.replace(/;$/, "");
52
185
  normalized = normalized.replace(/\/\/.*$/, "");
53
186
  normalized = normalized.replace(/\/\*.*?\*\//, "");
54
187
  normalized = normalized.trim();
188
+ normalized = normalized.replace(/;$/, "");
55
189
  return normalized;
56
190
  }).filter((line) => line.length > 0);
57
191
  }
@@ -88,7 +222,13 @@ function isLoop(line) {
88
222
  return /^(for|while)\s*\(/.test(line.trim());
89
223
  }
90
224
  function isFunctionDeclaration(line) {
91
- return /^(function|const|let|var)\s+\w+\s*=?\s*(async\s*)?\(/.test(line.trim()) || /^(async\s+)?function\s+\w+/.test(line.trim());
225
+ const t = line.trim();
226
+ return (
227
+ // function foo() / async function foo()
228
+ /^(async\s+)?function\s+\w+/.test(t) || // const foo = () => / const foo = async () => / const foo = async (x: T) =>
229
+ /^(const|let|var)\s+\w+\s*=\s*(async\s*)?\(/.test(t) || // const foo = function / const foo = async function
230
+ /^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(t)
231
+ );
92
232
  }
93
233
  function shouldIgnore(line) {
94
234
  const ignorePatterns = [
@@ -147,6 +287,19 @@ function parseToFlowTree(lines) {
147
287
  prevLine = line;
148
288
  continue;
149
289
  }
290
+ if (isFunctionDeclaration(line)) {
291
+ const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
292
+ roots.push({
293
+ type: "function",
294
+ name: funcMatch ? `${funcMatch[1]}()` : "function()",
295
+ children: [],
296
+ depth: relativeDepth,
297
+ priority: 4 /* FUNCTION */
298
+ });
299
+ currentChain = null;
300
+ prevLine = line;
301
+ continue;
302
+ }
150
303
  const root = extractRoot(line);
151
304
  if (root) {
152
305
  currentChain = {
@@ -177,16 +330,6 @@ function parseToFlowTree(lines) {
177
330
  priority: 3 /* LOOP */
178
331
  });
179
332
  currentChain = null;
180
- } else if (isFunctionDeclaration(line)) {
181
- const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
182
- roots.push({
183
- type: "function",
184
- name: funcMatch ? `${funcMatch[1]}()` : "function()",
185
- children: [],
186
- depth: relativeDepth,
187
- priority: 4 /* FUNCTION */
188
- });
189
- currentChain = null;
190
333
  }
191
334
  prevLine = line;
192
335
  }
@@ -214,8 +357,20 @@ function parseDiffToLogicalFlow(diffText) {
214
357
  };
215
358
  }
216
359
  function generateReaderMarkdown(diffText, meta = {}) {
217
- const result = parseDiffToLogicalFlow(diffText);
360
+ const { added, removed } = filterDiffLines(diffText);
361
+ const isJSX = Boolean(
362
+ meta.file && isJSXFile(meta.file) || hasJSXContent(added)
363
+ );
364
+ const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
365
+ const normalizedAdded = normalizeCode(addedForFlow);
366
+ const flowTree = parseToFlowTree(normalizedAdded);
367
+ const rawCode = addedForFlow.join("\n");
368
+ const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
369
+ const removedCode = removedForCode.join("\n");
370
+ const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
371
+ const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
218
372
  const sections = [];
373
+ const lang = isJSX ? "tsx" : "typescript";
219
374
  sections.push("# \u{1F4D6} GitHub Reader View\n");
220
375
  sections.push("> Generated by **github-mobile-reader**");
221
376
  if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
@@ -223,22 +378,33 @@ function generateReaderMarkdown(diffText, meta = {}) {
223
378
  if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
224
379
  if (meta.file) sections.push(`> File: \`${meta.file}\``);
225
380
  sections.push("\n");
226
- if (result.root.length > 0) {
381
+ if (flowTree.length > 0) {
227
382
  sections.push("## \u{1F9E0} Logical Flow\n");
228
383
  sections.push("```");
229
- sections.push(...renderFlowTree(result.root));
384
+ sections.push(...renderFlowTree(flowTree));
230
385
  sections.push("```\n");
231
386
  }
232
- if (result.rawCode.trim()) {
387
+ if (isJSX && jsxTree.length > 0) {
388
+ sections.push("## \u{1F3A8} JSX Structure\n");
389
+ sections.push("```");
390
+ sections.push(...renderFlowTree(jsxTree));
391
+ sections.push("```\n");
392
+ }
393
+ if (isJSX && classNameChanges.length > 0) {
394
+ sections.push("## \u{1F485} Style Changes\n");
395
+ sections.push(...renderStyleChanges(classNameChanges));
396
+ sections.push("");
397
+ }
398
+ if (rawCode.trim()) {
233
399
  sections.push("## \u2705 Added Code\n");
234
- sections.push("```typescript");
235
- sections.push(result.rawCode);
400
+ sections.push(`\`\`\`${lang}`);
401
+ sections.push(rawCode);
236
402
  sections.push("```\n");
237
403
  }
238
- if (result.removedCode.trim()) {
404
+ if (removedCode.trim()) {
239
405
  sections.push("## \u274C Removed Code\n");
240
- sections.push("```typescript");
241
- sections.push(result.removedCode);
406
+ sections.push(`\`\`\`${lang}`);
407
+ sections.push(removedCode);
242
408
  sections.push("```\n");
243
409
  }
244
410
  sections.push("---");
@@ -248,10 +414,19 @@ function generateReaderMarkdown(diffText, meta = {}) {
248
414
  // Annotate the CommonJS export names for ESM import in node:
249
415
  0 && (module.exports = {
250
416
  Priority,
417
+ extractClassName,
418
+ extractJSXComponentName,
251
419
  filterDiffLines,
252
420
  generateReaderMarkdown,
421
+ hasJSXContent,
422
+ isClassNameOnlyLine,
423
+ isJSXElement,
424
+ isJSXFile,
253
425
  normalizeCode,
426
+ parseClassNameChanges,
254
427
  parseDiffToLogicalFlow,
428
+ parseJSXToFlowTree,
255
429
  parseToFlowTree,
256
- renderFlowTree
430
+ renderFlowTree,
431
+ renderStyleChanges
257
432
  });
package/dist/index.mjs CHANGED
@@ -7,6 +7,131 @@ 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
+ const pureAdded = [...added].filter((c) => !removed.has(c));
57
+ const pureRemoved = [...removed].filter((c) => !added.has(c));
58
+ if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
59
+ changes.push({ component: comp, added: pureAdded, removed: pureRemoved });
60
+ }
61
+ return changes;
62
+ }
63
+ function renderStyleChanges(changes) {
64
+ const lines = [];
65
+ for (const change of changes) {
66
+ lines.push(`**${change.component}**`);
67
+ if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
68
+ if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
69
+ }
70
+ return lines;
71
+ }
72
+ function isJSXElement(line) {
73
+ const t = line.trim();
74
+ return /^<[A-Za-z]/.test(t) || /^<\/[A-Za-z]/.test(t);
75
+ }
76
+ function isJSXClosing(line) {
77
+ return /^<\/[A-Za-z]/.test(line.trim());
78
+ }
79
+ function isJSXSelfClosing(line) {
80
+ return /\/>[\s]*$/.test(line.trim());
81
+ }
82
+ function extractJSXComponentName(line) {
83
+ const trimmed = line.trim();
84
+ const closingMatch = trimmed.match(/^<\/([A-Za-z][A-Za-z0-9.]*)/);
85
+ if (closingMatch) return `/${closingMatch[1]}`;
86
+ const nameMatch = trimmed.match(/^<([A-Za-z][A-Za-z0-9.]*)/);
87
+ if (!nameMatch) return trimmed;
88
+ const name = nameMatch[1];
89
+ const eventProps = [];
90
+ for (const m of trimmed.matchAll(/\b(on[A-Z]\w+)=/g)) {
91
+ eventProps.push(m[1]);
92
+ }
93
+ return eventProps.length > 0 ? `${name}(${eventProps.join(", ")})` : name;
94
+ }
95
+ function shouldIgnoreJSX(line) {
96
+ const t = line.trim();
97
+ 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);
98
+ }
99
+ function parseJSXToFlowTree(lines) {
100
+ const roots = [];
101
+ const stack = [];
102
+ for (const line of lines) {
103
+ if (!isJSXElement(line)) continue;
104
+ if (shouldIgnoreJSX(line)) continue;
105
+ const depth = getIndentDepth(line);
106
+ if (isJSXClosing(line)) {
107
+ while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
108
+ stack.pop();
109
+ }
110
+ continue;
111
+ }
112
+ const name = extractJSXComponentName(line);
113
+ const selfClosing = isJSXSelfClosing(line);
114
+ const node = {
115
+ type: "call",
116
+ name,
117
+ children: [],
118
+ depth,
119
+ priority: 5 /* OTHER */
120
+ };
121
+ while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
122
+ stack.pop();
123
+ }
124
+ if (stack.length === 0) {
125
+ roots.push(node);
126
+ } else {
127
+ stack[stack.length - 1].node.children.push(node);
128
+ }
129
+ if (!selfClosing) {
130
+ stack.push({ node, depth });
131
+ }
132
+ }
133
+ return roots;
134
+ }
10
135
  function filterDiffLines(diffText) {
11
136
  const lines = diffText.split("\n");
12
137
  const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
@@ -16,10 +141,10 @@ function filterDiffLines(diffText) {
16
141
  function normalizeCode(lines) {
17
142
  return lines.map((line) => {
18
143
  let normalized = line;
19
- normalized = normalized.replace(/;$/, "");
20
144
  normalized = normalized.replace(/\/\/.*$/, "");
21
145
  normalized = normalized.replace(/\/\*.*?\*\//, "");
22
146
  normalized = normalized.trim();
147
+ normalized = normalized.replace(/;$/, "");
23
148
  return normalized;
24
149
  }).filter((line) => line.length > 0);
25
150
  }
@@ -56,7 +181,13 @@ function isLoop(line) {
56
181
  return /^(for|while)\s*\(/.test(line.trim());
57
182
  }
58
183
  function isFunctionDeclaration(line) {
59
- return /^(function|const|let|var)\s+\w+\s*=?\s*(async\s*)?\(/.test(line.trim()) || /^(async\s+)?function\s+\w+/.test(line.trim());
184
+ const t = line.trim();
185
+ return (
186
+ // function foo() / async function foo()
187
+ /^(async\s+)?function\s+\w+/.test(t) || // const foo = () => / const foo = async () => / const foo = async (x: T) =>
188
+ /^(const|let|var)\s+\w+\s*=\s*(async\s*)?\(/.test(t) || // const foo = function / const foo = async function
189
+ /^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(t)
190
+ );
60
191
  }
61
192
  function shouldIgnore(line) {
62
193
  const ignorePatterns = [
@@ -115,6 +246,19 @@ function parseToFlowTree(lines) {
115
246
  prevLine = line;
116
247
  continue;
117
248
  }
249
+ if (isFunctionDeclaration(line)) {
250
+ const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
251
+ roots.push({
252
+ type: "function",
253
+ name: funcMatch ? `${funcMatch[1]}()` : "function()",
254
+ children: [],
255
+ depth: relativeDepth,
256
+ priority: 4 /* FUNCTION */
257
+ });
258
+ currentChain = null;
259
+ prevLine = line;
260
+ continue;
261
+ }
118
262
  const root = extractRoot(line);
119
263
  if (root) {
120
264
  currentChain = {
@@ -145,16 +289,6 @@ function parseToFlowTree(lines) {
145
289
  priority: 3 /* LOOP */
146
290
  });
147
291
  currentChain = null;
148
- } else if (isFunctionDeclaration(line)) {
149
- const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
150
- roots.push({
151
- type: "function",
152
- name: funcMatch ? `${funcMatch[1]}()` : "function()",
153
- children: [],
154
- depth: relativeDepth,
155
- priority: 4 /* FUNCTION */
156
- });
157
- currentChain = null;
158
292
  }
159
293
  prevLine = line;
160
294
  }
@@ -182,8 +316,20 @@ function parseDiffToLogicalFlow(diffText) {
182
316
  };
183
317
  }
184
318
  function generateReaderMarkdown(diffText, meta = {}) {
185
- const result = parseDiffToLogicalFlow(diffText);
319
+ const { added, removed } = filterDiffLines(diffText);
320
+ const isJSX = Boolean(
321
+ meta.file && isJSXFile(meta.file) || hasJSXContent(added)
322
+ );
323
+ const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
324
+ const normalizedAdded = normalizeCode(addedForFlow);
325
+ const flowTree = parseToFlowTree(normalizedAdded);
326
+ const rawCode = addedForFlow.join("\n");
327
+ const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
328
+ const removedCode = removedForCode.join("\n");
329
+ const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
330
+ const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
186
331
  const sections = [];
332
+ const lang = isJSX ? "tsx" : "typescript";
187
333
  sections.push("# \u{1F4D6} GitHub Reader View\n");
188
334
  sections.push("> Generated by **github-mobile-reader**");
189
335
  if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
@@ -191,22 +337,33 @@ function generateReaderMarkdown(diffText, meta = {}) {
191
337
  if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
192
338
  if (meta.file) sections.push(`> File: \`${meta.file}\``);
193
339
  sections.push("\n");
194
- if (result.root.length > 0) {
340
+ if (flowTree.length > 0) {
195
341
  sections.push("## \u{1F9E0} Logical Flow\n");
196
342
  sections.push("```");
197
- sections.push(...renderFlowTree(result.root));
343
+ sections.push(...renderFlowTree(flowTree));
198
344
  sections.push("```\n");
199
345
  }
200
- if (result.rawCode.trim()) {
346
+ if (isJSX && jsxTree.length > 0) {
347
+ sections.push("## \u{1F3A8} JSX Structure\n");
348
+ sections.push("```");
349
+ sections.push(...renderFlowTree(jsxTree));
350
+ sections.push("```\n");
351
+ }
352
+ if (isJSX && classNameChanges.length > 0) {
353
+ sections.push("## \u{1F485} Style Changes\n");
354
+ sections.push(...renderStyleChanges(classNameChanges));
355
+ sections.push("");
356
+ }
357
+ if (rawCode.trim()) {
201
358
  sections.push("## \u2705 Added Code\n");
202
- sections.push("```typescript");
203
- sections.push(result.rawCode);
359
+ sections.push(`\`\`\`${lang}`);
360
+ sections.push(rawCode);
204
361
  sections.push("```\n");
205
362
  }
206
- if (result.removedCode.trim()) {
363
+ if (removedCode.trim()) {
207
364
  sections.push("## \u274C Removed Code\n");
208
- sections.push("```typescript");
209
- sections.push(result.removedCode);
365
+ sections.push(`\`\`\`${lang}`);
366
+ sections.push(removedCode);
210
367
  sections.push("```\n");
211
368
  }
212
369
  sections.push("---");
@@ -215,10 +372,19 @@ function generateReaderMarkdown(diffText, meta = {}) {
215
372
  }
216
373
  export {
217
374
  Priority,
375
+ extractClassName,
376
+ extractJSXComponentName,
218
377
  filterDiffLines,
219
378
  generateReaderMarkdown,
379
+ hasJSXContent,
380
+ isClassNameOnlyLine,
381
+ isJSXElement,
382
+ isJSXFile,
220
383
  normalizeCode,
384
+ parseClassNameChanges,
221
385
  parseDiffToLogicalFlow,
386
+ parseJSXToFlowTree,
222
387
  parseToFlowTree,
223
- renderFlowTree
388
+ renderFlowTree,
389
+ renderStyleChanges
224
390
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-mobile-reader",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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:all": "npm run build && npm run build:action",
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
  },