diffstory 0.2.1__tar.gz → 0.3.0__tar.gz
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.
- {diffstory-0.2.1 → diffstory-0.3.0}/PKG-INFO +1 -1
- {diffstory-0.2.1 → diffstory-0.3.0}/pyproject.toml +1 -1
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory/__init__.py +1 -1
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory/cli.py +59 -4
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory/git_utils.py +8 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory/html_generator.py +110 -49
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory.egg-info/PKG-INFO +1 -1
- {diffstory-0.2.1 → diffstory-0.3.0}/README.md +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/setup.cfg +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory/__main__.py +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory/diff_parser.py +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory/syntax.py +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory.egg-info/SOURCES.txt +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory.egg-info/dependency_links.txt +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory.egg-info/entry_points.txt +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory.egg-info/requires.txt +0 -0
- {diffstory-0.2.1 → diffstory-0.3.0}/src/diffstory.egg-info/top_level.txt +0 -0
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import sys
|
|
7
|
+
import webbrowser
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Optional
|
|
9
10
|
|
|
@@ -14,6 +15,7 @@ from diffstory.git_utils import (
|
|
|
14
15
|
check_git_repo,
|
|
15
16
|
get_diff,
|
|
16
17
|
get_diff_with_renames,
|
|
18
|
+
get_git_root,
|
|
17
19
|
)
|
|
18
20
|
from diffstory.html_generator import generate_report
|
|
19
21
|
|
|
@@ -141,6 +143,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
141
143
|
help="Generate report from a diff file directly (no git repository needed)",
|
|
142
144
|
)
|
|
143
145
|
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"--no-open",
|
|
148
|
+
action="store_true",
|
|
149
|
+
default=False,
|
|
150
|
+
help="Do not open the report in a browser after generation",
|
|
151
|
+
)
|
|
152
|
+
|
|
144
153
|
parser.add_argument(
|
|
145
154
|
"--verbose", "-v",
|
|
146
155
|
action="store_true",
|
|
@@ -279,6 +288,16 @@ def _export_csv(files, output_path: Path) -> None:
|
|
|
279
288
|
print(f" CSV: {output_path}")
|
|
280
289
|
|
|
281
290
|
|
|
291
|
+
def _open_in_browser(path: str) -> None:
|
|
292
|
+
"""Open the generated report in the default browser."""
|
|
293
|
+
try:
|
|
294
|
+
file_url = "file://" + path
|
|
295
|
+
webbrowser.open(file_url)
|
|
296
|
+
print(f" Opened in browser: {file_url}")
|
|
297
|
+
except Exception as e:
|
|
298
|
+
print(f" Could not open browser: {e}", file=sys.stderr)
|
|
299
|
+
|
|
300
|
+
|
|
282
301
|
def _read_diff_from_file(path: str) -> str:
|
|
283
302
|
"""Read diff content from a file."""
|
|
284
303
|
try:
|
|
@@ -291,6 +310,31 @@ def _read_diff_from_file(path: str) -> str:
|
|
|
291
310
|
sys.exit(1)
|
|
292
311
|
|
|
293
312
|
|
|
313
|
+
def _resolve_output_path(given_path: str) -> str:
|
|
314
|
+
"""Resolve the output file path.
|
|
315
|
+
|
|
316
|
+
If the given path is the default and we're inside a git repo,
|
|
317
|
+
place it in a 'stories/' directory outside the git working tree
|
|
318
|
+
so that git does not track it.
|
|
319
|
+
"""
|
|
320
|
+
given = Path(given_path)
|
|
321
|
+
# Only redirect the default path — if the user explicitly passed -o, use as-is
|
|
322
|
+
if given.name != "diffstory-report.html":
|
|
323
|
+
return str(given.resolve())
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
git_root = get_git_root()
|
|
327
|
+
if git_root:
|
|
328
|
+
git_root = Path(git_root).resolve()
|
|
329
|
+
stories_dir = git_root.parent / "stories"
|
|
330
|
+
stories_dir.mkdir(parents=True, exist_ok=True)
|
|
331
|
+
return str(stories_dir / given.name)
|
|
332
|
+
except Exception:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
return str(given.resolve())
|
|
336
|
+
|
|
337
|
+
|
|
294
338
|
def main() -> None:
|
|
295
339
|
"""Main entry point for the diffstory CLI."""
|
|
296
340
|
parser = build_parser()
|
|
@@ -304,6 +348,9 @@ def main() -> None:
|
|
|
304
348
|
if debug:
|
|
305
349
|
verbose = True # debug implies verbose
|
|
306
350
|
|
|
351
|
+
# Resolve output path — for the default, put it outside the git repo
|
|
352
|
+
output_path = _resolve_output_path(args.output)
|
|
353
|
+
|
|
307
354
|
# Handle --diff flag (read diff from file, no git needed)
|
|
308
355
|
if args.diff:
|
|
309
356
|
if verbose:
|
|
@@ -317,9 +364,9 @@ def main() -> None:
|
|
|
317
364
|
sys.exit(0)
|
|
318
365
|
has_exports = args.json or args.md or args.csv
|
|
319
366
|
if has_exports:
|
|
320
|
-
generate_exports(files,
|
|
367
|
+
generate_exports(files, output_path, args.json, args.md, args.csv)
|
|
321
368
|
try:
|
|
322
|
-
report_path = generate_report(files, output_path=
|
|
369
|
+
report_path = generate_report(files, output_path=output_path, repo_name="diff", verbose=verbose)
|
|
323
370
|
except Exception as e:
|
|
324
371
|
if debug:
|
|
325
372
|
import traceback
|
|
@@ -328,6 +375,10 @@ def main() -> None:
|
|
|
328
375
|
sys.exit(1)
|
|
329
376
|
print(f"\\n HTML: {report_path}")
|
|
330
377
|
print(" Report generated successfully!")
|
|
378
|
+
|
|
379
|
+
# Open in browser unless --no-open
|
|
380
|
+
if not args.no_open:
|
|
381
|
+
_open_in_browser(report_path)
|
|
331
382
|
return
|
|
332
383
|
|
|
333
384
|
# Validate Git repository
|
|
@@ -378,13 +429,13 @@ def main() -> None:
|
|
|
378
429
|
# Generate exports if requested
|
|
379
430
|
has_exports = args.json or args.md or args.csv
|
|
380
431
|
if has_exports:
|
|
381
|
-
generate_exports(files,
|
|
432
|
+
generate_exports(files, output_path, args.json, args.md, args.csv)
|
|
382
433
|
|
|
383
434
|
# Always generate HTML report
|
|
384
435
|
try:
|
|
385
436
|
report_path = generate_report(
|
|
386
437
|
files,
|
|
387
|
-
output_path=
|
|
438
|
+
output_path=output_path,
|
|
388
439
|
staged=args.staged,
|
|
389
440
|
commit_a=commit_a,
|
|
390
441
|
commit_b=commit_b,
|
|
@@ -399,3 +450,7 @@ def main() -> None:
|
|
|
399
450
|
|
|
400
451
|
print(f"\\n HTML: {report_path}")
|
|
401
452
|
print(" Report generated successfully!")
|
|
453
|
+
|
|
454
|
+
# Open in browser unless --no-open
|
|
455
|
+
if not args.no_open:
|
|
456
|
+
_open_in_browser(report_path)
|
|
@@ -45,6 +45,14 @@ def check_git_repo(cwd: Optional[Path] = None) -> bool:
|
|
|
45
45
|
return False
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def get_git_root(cwd: Optional[Path] = None) -> Optional[str]:
|
|
49
|
+
"""Get the absolute path to the root of the Git repository."""
|
|
50
|
+
try:
|
|
51
|
+
return _run_git(["rev-parse", "--show-toplevel"], cwd=cwd).strip()
|
|
52
|
+
except GitError:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
48
56
|
def get_diff(
|
|
49
57
|
staged: bool = False,
|
|
50
58
|
commit_a: Optional[str] = None,
|
|
@@ -362,29 +362,61 @@ def _collect_blame_data(
|
|
|
362
362
|
Returns a dict with:
|
|
363
363
|
- line_blame: dict mapping "file_idx:lineno" to blame entry
|
|
364
364
|
- commits: dict mapping commit_hash to commit metadata
|
|
365
|
+
|
|
366
|
+
Handles renamed files by using the old path for deletion blame
|
|
367
|
+
and the new path for addition/context blame. Skips blame when
|
|
368
|
+
no revision info is available (e.g. --diff mode).
|
|
365
369
|
"""
|
|
370
|
+
# If no commit info at all, skip blame entirely (e.g. --diff mode)
|
|
371
|
+
if not staged and commit_a is None and commit_b is None:
|
|
372
|
+
return {"line_blame": {}, "commits": {}}
|
|
373
|
+
|
|
366
374
|
line_blame: dict = {}
|
|
367
375
|
all_commits: set = set()
|
|
368
376
|
|
|
369
377
|
for fi, file in enumerate(files):
|
|
370
|
-
|
|
371
|
-
if
|
|
378
|
+
new_filepath = file.display_path
|
|
379
|
+
old_filepath = file.old_path if file.old_path != "/dev/null" else file.display_path
|
|
380
|
+
|
|
381
|
+
if new_filepath == "/dev/null" and old_filepath == "/dev/null":
|
|
372
382
|
continue
|
|
373
383
|
|
|
374
|
-
# Determine which
|
|
384
|
+
# Determine which revisions to blame
|
|
375
385
|
new_revision = None # None means working tree
|
|
376
386
|
if commit_b:
|
|
377
387
|
new_revision = commit_b
|
|
378
388
|
elif staged:
|
|
379
|
-
#
|
|
380
|
-
pass # blame working tree
|
|
389
|
+
pass # blame working tree for staged additions
|
|
381
390
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
391
|
+
old_revision = None
|
|
392
|
+
if commit_a:
|
|
393
|
+
old_revision = commit_a
|
|
394
|
+
elif staged:
|
|
395
|
+
old_revision = "HEAD"
|
|
396
|
+
|
|
397
|
+
# Get blame for current (new) version — skip if file doesn't exist at revision
|
|
398
|
+
blame_new: dict = {}
|
|
399
|
+
if new_filepath != "/dev/null":
|
|
400
|
+
try:
|
|
401
|
+
blame_new = get_blame_for_revision(new_filepath, revision=new_revision)
|
|
402
|
+
except Exception:
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
# Get blame for old version if different from new (e.g. renames)
|
|
406
|
+
blame_old: dict = blame_new
|
|
407
|
+
if old_revision and old_filepath != new_filepath:
|
|
408
|
+
try:
|
|
409
|
+
blame_old = get_blame_for_revision(old_filepath, revision=old_revision)
|
|
410
|
+
except Exception:
|
|
411
|
+
blame_old = {}
|
|
412
|
+
elif old_revision and old_filepath == new_filepath and old_revision != new_revision:
|
|
413
|
+
# Same path but different revision — re-blame
|
|
414
|
+
try:
|
|
415
|
+
blame_old = get_blame_for_revision(old_filepath, revision=old_revision)
|
|
416
|
+
except Exception:
|
|
417
|
+
pass
|
|
418
|
+
|
|
419
|
+
# Map blame by line number for additions and context
|
|
388
420
|
for hunk in file.hunks:
|
|
389
421
|
for line in hunk.lines:
|
|
390
422
|
if line.line_type in ("addition", "context") and line.new_lineno:
|
|
@@ -395,31 +427,22 @@ def _collect_blame_data(
|
|
|
395
427
|
all_commits.add(entry["commit"])
|
|
396
428
|
|
|
397
429
|
elif line.line_type == "deletion" and line.old_lineno:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
old_revision = "HEAD"
|
|
405
|
-
|
|
406
|
-
if old_revision:
|
|
407
|
-
try:
|
|
408
|
-
blame_old = get_blame_for_revision(filepath, revision=old_revision)
|
|
409
|
-
entry = blame_old.get(line.old_lineno)
|
|
410
|
-
if entry:
|
|
411
|
-
key = str(fi) + ":" + str(line.old_lineno)
|
|
412
|
-
line_blame[key] = entry
|
|
413
|
-
all_commits.add(entry["commit"])
|
|
414
|
-
except Exception:
|
|
415
|
-
pass
|
|
430
|
+
if blame_old:
|
|
431
|
+
entry = blame_old.get(line.old_lineno)
|
|
432
|
+
if entry:
|
|
433
|
+
key = str(fi) + ":" + str(line.old_lineno)
|
|
434
|
+
line_blame[key] = entry
|
|
435
|
+
all_commits.add(entry["commit"])
|
|
416
436
|
|
|
417
437
|
# Collect commit metadata for all unique commits
|
|
418
438
|
commits: dict = {}
|
|
419
439
|
for chash in all_commits:
|
|
420
440
|
if chash and len(chash) == 40:
|
|
421
|
-
|
|
422
|
-
|
|
441
|
+
try:
|
|
442
|
+
info = get_commit_info(chash)
|
|
443
|
+
commits[chash] = info
|
|
444
|
+
except Exception:
|
|
445
|
+
pass
|
|
423
446
|
|
|
424
447
|
return {
|
|
425
448
|
"line_blame": line_blame,
|
|
@@ -620,10 +643,27 @@ def _build_html_template(
|
|
|
620
643
|
' <button class="view-btn" data-view="inline" onclick="switchView(\'inline\')" title="Inline Edit (I)">Inline</button>\n'
|
|
621
644
|
' </div>\n'
|
|
622
645
|
' <div class="toolbar-right">\n'
|
|
623
|
-
' <button class="tool-btn" onclick="focusSearch()" id="search-btn" title="Search (F or /)">\
|
|
624
|
-
'
|
|
625
|
-
'
|
|
626
|
-
'
|
|
646
|
+
' <button class="tool-btn" onclick="focusSearch()" id="search-btn" title="Search (F or /)">\n'
|
|
647
|
+
' <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="tool-icon">\n'
|
|
648
|
+
' <path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"/>\n'
|
|
649
|
+
' </svg>\n'
|
|
650
|
+
' </button>\n'
|
|
651
|
+
' <button class="tool-btn" onclick="toggleTheme()" id="theme-btn" title="Toggle Theme (D)">\n'
|
|
652
|
+
' <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="tool-icon" id="theme-icon">\n'
|
|
653
|
+
' <path d="M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8Zm0-1.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm5.657-8.157a.75.75 0 0 1 0 1.061l-1.061 1.06a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l1.06-1.06a.75.75 0 0 1 1.06 0Zm-9.193 9.193a.75.75 0 0 1 0 1.06l-1.06 1.061a.75.75 0 1 1-1.061-1.06l1.06-1.061a.75.75 0 0 1 1.061 0ZM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0ZM3 8a.75.75 0 0 1-.75.75H.75a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 3 8Zm13 0a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 16 8Zm-8 5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13Zm3.536-1.464a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061ZM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-1.06-1.06a.75.75 0 0 1 0-1.06Z"/>\n'
|
|
654
|
+
' </svg>\n'
|
|
655
|
+
' </button>\n'
|
|
656
|
+
' <button class="tool-btn" onclick="toggleStats()" id="stats-btn" title="Statistics">\n'
|
|
657
|
+
' <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="tool-icon">\n'
|
|
658
|
+
' <path d="M1.5 1.75V13.5h13.75a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75V1.75a.75.75 0 0 1 1.5 0Zm14.28 2.53-5.25 5.25a.75.75 0 0 1-1.06 0L7 7.06 4.28 9.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.25-3.25a.75.75 0 0 1 1.06 0L10 7.94l4.72-4.72a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"/>\n'
|
|
659
|
+
' </svg>\n'
|
|
660
|
+
' </button>\n'
|
|
661
|
+
' <button class="tool-btn" onclick="toggleSidebar()" id="sidebar-btn" title="File List">\n'
|
|
662
|
+
' <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="tool-icon">\n'
|
|
663
|
+
' <path d="M6.823 7.823a.25.25 0 0 1 0 .354l-2.396 2.396A.25.25 0 0 1 4 10.396V5.604a.25.25 0 0 1 .427-.177Z"/>\n'
|
|
664
|
+
' <path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25H9.5v-13H1.75a.25.25 0 0 0-.25.25ZM11 14.5h3.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H11Z"/>\n'
|
|
665
|
+
' </svg>\n'
|
|
666
|
+
' </button>\n'
|
|
627
667
|
' </div>\n'
|
|
628
668
|
' </header>\n'
|
|
629
669
|
' <!-- Global Search Bar -->\n'
|
|
@@ -756,6 +796,8 @@ def _get_css() -> str:
|
|
|
756
796
|
--text-secondary: #656d76;
|
|
757
797
|
--border: #d0d7de;
|
|
758
798
|
--border-light: #e0e4e8;
|
|
799
|
+
--line-number-color: #6e7681;
|
|
800
|
+
--diff-context-color: #656d76;
|
|
759
801
|
--accent: #0969da;
|
|
760
802
|
--accent-hover: #0550ae;
|
|
761
803
|
--add-bg: #e6ffec;
|
|
@@ -788,6 +830,8 @@ def _get_css() -> str:
|
|
|
788
830
|
--text-secondary: #8b949e;
|
|
789
831
|
--border: #30363d;
|
|
790
832
|
--border-light: #21262d;
|
|
833
|
+
--line-number-color: #484f58;
|
|
834
|
+
--diff-context-color: #8b949e;
|
|
791
835
|
--accent: #58a6ff;
|
|
792
836
|
--accent-hover: #79c0ff;
|
|
793
837
|
--add-bg: #12262b;
|
|
@@ -813,7 +857,7 @@ def _get_css() -> str:
|
|
|
813
857
|
}
|
|
814
858
|
|
|
815
859
|
body {
|
|
816
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
|
860
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji';
|
|
817
861
|
font-size: 14px;
|
|
818
862
|
line-height: 1.5;
|
|
819
863
|
color: var(--text);
|
|
@@ -828,6 +872,15 @@ body {
|
|
|
828
872
|
height: 100vh;
|
|
829
873
|
}
|
|
830
874
|
|
|
875
|
+
.tool-icon {
|
|
876
|
+
display: block;
|
|
877
|
+
fill: currentColor;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.tool-btn svg.tool-icon {
|
|
881
|
+
pointer-events: none;
|
|
882
|
+
}
|
|
883
|
+
|
|
831
884
|
/* Toolbar */
|
|
832
885
|
#toolbar {
|
|
833
886
|
display: flex;
|
|
@@ -1075,7 +1128,7 @@ body {
|
|
|
1075
1128
|
flex: 1;
|
|
1076
1129
|
font-size: 14px;
|
|
1077
1130
|
font-weight: 600;
|
|
1078
|
-
font-family:
|
|
1131
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1079
1132
|
overflow: hidden;
|
|
1080
1133
|
text-overflow: ellipsis;
|
|
1081
1134
|
white-space: nowrap;
|
|
@@ -1130,7 +1183,7 @@ body {
|
|
|
1130
1183
|
padding: 6px 14px;
|
|
1131
1184
|
background: var(--hunk-header-bg);
|
|
1132
1185
|
color: var(--hunk-header-text);
|
|
1133
|
-
font-family:
|
|
1186
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1134
1187
|
font-size: 12px;
|
|
1135
1188
|
border-bottom: 1px solid var(--border-light);
|
|
1136
1189
|
}
|
|
@@ -1143,7 +1196,7 @@ body {
|
|
|
1143
1196
|
.diff-line {
|
|
1144
1197
|
display: flex;
|
|
1145
1198
|
align-items: stretch;
|
|
1146
|
-
font-family:
|
|
1199
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1147
1200
|
font-size: 12px;
|
|
1148
1201
|
line-height: 1.5;
|
|
1149
1202
|
min-height: 22px;
|
|
@@ -1214,7 +1267,7 @@ body {
|
|
|
1214
1267
|
width: 50%;
|
|
1215
1268
|
display: flex;
|
|
1216
1269
|
align-items: stretch;
|
|
1217
|
-
font-family:
|
|
1270
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1218
1271
|
font-size: 12px;
|
|
1219
1272
|
line-height: 1.5;
|
|
1220
1273
|
min-height: 22px;
|
|
@@ -1366,7 +1419,7 @@ body {
|
|
|
1366
1419
|
.stats-table td {
|
|
1367
1420
|
padding: 6px 8px;
|
|
1368
1421
|
border-bottom: 1px solid var(--border-light);
|
|
1369
|
-
font-family:
|
|
1422
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1370
1423
|
font-size: 12px;
|
|
1371
1424
|
}
|
|
1372
1425
|
|
|
@@ -1428,7 +1481,7 @@ body {
|
|
|
1428
1481
|
}
|
|
1429
1482
|
|
|
1430
1483
|
.tooltip-commit {
|
|
1431
|
-
font-family:
|
|
1484
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1432
1485
|
font-size: 11px;
|
|
1433
1486
|
color: var(--text-secondary);
|
|
1434
1487
|
}
|
|
@@ -1517,7 +1570,7 @@ body {
|
|
|
1517
1570
|
}
|
|
1518
1571
|
|
|
1519
1572
|
.drawer-commit-hash {
|
|
1520
|
-
font-family:
|
|
1573
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1521
1574
|
font-size: 13px;
|
|
1522
1575
|
color: var(--accent);
|
|
1523
1576
|
}
|
|
@@ -1760,7 +1813,7 @@ body {
|
|
|
1760
1813
|
font-size: 14px;
|
|
1761
1814
|
font-weight: 600;
|
|
1762
1815
|
color: var(--text);
|
|
1763
|
-
font-family:
|
|
1816
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1764
1817
|
}
|
|
1765
1818
|
|
|
1766
1819
|
.binary-note {
|
|
@@ -2030,13 +2083,19 @@ function switchView(viewName) {
|
|
|
2030
2083
|
});
|
|
2031
2084
|
}
|
|
2032
2085
|
|
|
2033
|
-
// Theme Toggle
|
|
2086
|
+
// Theme Toggle — swap sun/moon SVG
|
|
2087
|
+
const sunPath = 'M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8Zm0-1.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm5.657-8.157a.75.75 0 0 1 0 1.061l-1.061 1.06a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l1.06-1.06a.75.75 0 0 1 1.06 0Zm-9.193 9.193a.75.75 0 0 1 0 1.06l-1.06 1.061a.75.75 0 1 1-1.061-1.06l1.06-1.061a.75.75 0 0 1 1.061 0ZM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0ZM3 8a.75.75 0 0 1-.75.75H.75a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 3 8Zm13 0a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 16 8Zm-8 5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13Zm3.536-1.464a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061ZM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-1.06-1.06a.75.75 0 0 1 0-1.06Z';
|
|
2088
|
+
const moonPath = 'M9.598 1.591a.749.749 0 0 1 .785-.175 7.001 7.001 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786Zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.499 5.499 0 1 0 7.678-7.678Z';
|
|
2089
|
+
|
|
2034
2090
|
function toggleTheme() {
|
|
2035
2091
|
var html = document.documentElement;
|
|
2036
2092
|
var isDark = html.getAttribute('data-theme') === 'dark';
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
localStorage.setItem('diffstory-theme',
|
|
2093
|
+
var newTheme = isDark ? 'light' : 'dark';
|
|
2094
|
+
html.setAttribute('data-theme', newTheme);
|
|
2095
|
+
localStorage.setItem('diffstory-theme', newTheme);
|
|
2096
|
+
// Swap icon: after toggling light→new moon, dark→new sun
|
|
2097
|
+
var icon = document.querySelector('#theme-btn path');
|
|
2098
|
+
if (icon) icon.setAttribute('d', newTheme === 'dark' ? sunPath : moonPath);
|
|
2040
2099
|
}
|
|
2041
2100
|
|
|
2042
2101
|
// Load saved theme
|
|
@@ -2044,7 +2103,9 @@ function toggleTheme() {
|
|
|
2044
2103
|
var saved = localStorage.getItem('diffstory-theme');
|
|
2045
2104
|
if (saved) {
|
|
2046
2105
|
document.documentElement.setAttribute('data-theme', saved);
|
|
2047
|
-
|
|
2106
|
+
// Set initial icon
|
|
2107
|
+
var icon = document.querySelector('#theme-btn path');
|
|
2108
|
+
if (icon) icon.setAttribute('d', saved === 'dark' ? sunPath : moonPath);
|
|
2048
2109
|
}
|
|
2049
2110
|
})();
|
|
2050
2111
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|