@youtyan/code-viewer 0.1.7 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Local browser-based git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
package/web/app.js CHANGED
@@ -109,6 +109,21 @@
109
109
  };
110
110
  }
111
111
 
112
+ // web-src/catch-up.ts
113
+ function shouldCatchUpDiff(route) {
114
+ return route.screen !== "repo" && !(route.screen === "file" && route.view === "blob");
115
+ }
116
+ function createCatchUpGate(now, minIntervalMs) {
117
+ let lastForceAt = 0;
118
+ return function shouldRun() {
119
+ const current = now();
120
+ if (current - lastForceAt < minIntervalMs)
121
+ return false;
122
+ lastForceAt = current;
123
+ return true;
124
+ };
125
+ }
126
+
112
127
  // web-src/routes.ts
113
128
  function assertNever(value) {
114
129
  throw new Error("unhandled route: " + JSON.stringify(value));
@@ -2686,7 +2701,7 @@
2686
2701
  syncHeaderMenu();
2687
2702
  }).catch(() => setStatus("error"));
2688
2703
  }
2689
- function load() {
2704
+ function load(options = {}) {
2690
2705
  if (STATE.route.screen === "repo")
2691
2706
  return loadRepo();
2692
2707
  setStatus("refreshing");
@@ -2697,6 +2712,8 @@
2697
2712
  params.set("from", STATE.from);
2698
2713
  if (STATE.to)
2699
2714
  params.set("to", STATE.to);
2715
+ if (options.force)
2716
+ params.set("nocache", "1");
2700
2717
  const url = "/diff.json" + (params.toString() ? "?" + params.toString() : "");
2701
2718
  return trackLoad(fetch(url).then((r) => r.json())).then((data) => {
2702
2719
  renderShell(data);
@@ -3033,24 +3050,30 @@
3033
3050
  }, 350);
3034
3051
  }
3035
3052
  const es = new EventSource("/events");
3053
+ const catchUpGate = createCatchUpGate(() => Date.now(), 1000);
3054
+ let openedOnce = false;
3036
3055
  es.addEventListener("update", () => scheduleSseLoad());
3037
3056
  es.addEventListener("reload", () => location.reload());
3038
3057
  es.addEventListener("error", () => setStatus("error"));
3039
- es.addEventListener("open", () => setStatus("live"));
3040
- let assetVersion = null;
3041
- function pollAssetVersion() {
3042
- fetch("/_asset_version").then((r) => r.ok ? r.json() : null).then((data) => {
3043
- if (!data || !data.version)
3044
- return;
3045
- if (assetVersion == null) {
3046
- assetVersion = data.version;
3047
- return;
3048
- }
3049
- if (data.version !== assetVersion)
3050
- location.reload();
3051
- }).catch(() => {});
3058
+ es.addEventListener("open", () => {
3059
+ setStatus("live");
3060
+ if (!openedOnce) {
3061
+ openedOnce = true;
3062
+ return;
3063
+ }
3064
+ catchUpDiff();
3065
+ });
3066
+ function catchUpDiff() {
3067
+ if (!shouldCatchUpDiff(STATE.route))
3068
+ return;
3069
+ if (!catchUpGate())
3070
+ return;
3071
+ load({ force: true });
3052
3072
  }
3053
- pollAssetVersion();
3054
- setInterval(pollAssetVersion, 1500);
3073
+ document.addEventListener("visibilitychange", () => {
3074
+ if (!document.hidden)
3075
+ catchUpDiff();
3076
+ });
3077
+ window.addEventListener("focus", catchUpDiff);
3055
3078
  })();
3056
3079
  })();
@@ -0,0 +1,37 @@
1
+ import { basename } from 'node:path';
2
+
3
+ export type WatchFn = (
4
+ path: string,
5
+ options: { persistent?: boolean },
6
+ listener: (eventType: string, filename: string | Buffer | null) => void,
7
+ ) => unknown;
8
+
9
+ type DevAssetReloadOptions = {
10
+ enabled: boolean;
11
+ webRoot: string;
12
+ watchedFiles: readonly string[];
13
+ watch: WatchFn;
14
+ sendReload: () => void;
15
+ setTimeoutFn?: typeof setTimeout;
16
+ clearTimeoutFn?: typeof clearTimeout;
17
+ debounceMs?: number;
18
+ };
19
+
20
+ export function startDevAssetReload(options: DevAssetReloadOptions): boolean {
21
+ if (!options.enabled) return false;
22
+ const watched = new Set(options.watchedFiles);
23
+ const setTimer = options.setTimeoutFn || setTimeout;
24
+ const clearTimer = options.clearTimeoutFn || clearTimeout;
25
+ const debounceMs = options.debounceMs ?? 150;
26
+ let timer: ReturnType<typeof setTimeout> | null = null;
27
+
28
+ options.watch(options.webRoot, { persistent: false }, (_event, filename) => {
29
+ if (!filename || !watched.has(basename(filename.toString()))) return;
30
+ if (timer) clearTimer(timer);
31
+ timer = setTimer(() => {
32
+ timer = null;
33
+ options.sendReload();
34
+ }, debounceMs);
35
+ });
36
+ return true;
37
+ }
@@ -56,7 +56,12 @@ function startServer() {
56
56
  firstStart = false;
57
57
  server = Bun.spawn([
58
58
  'bun', 'run', 'web-src/server/preview.ts', ...args,
59
- ], { cwd: ROOT, stdout: 'inherit', stderr: 'inherit' }) as ChildProcess;
59
+ ], {
60
+ cwd: ROOT,
61
+ stdout: 'inherit',
62
+ stderr: 'inherit',
63
+ env: { ...process.env, CODE_VIEWER_DEV: '1' },
64
+ }) as ChildProcess;
60
65
  }
61
66
 
62
67
  async function restartServer() {
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
3
+ import { existsSync, readFileSync, realpathSync, statSync, watch } from 'node:fs';
4
4
  import { basename, extname, join, normalize, relative } from 'node:path';
5
5
  import { APP_ENTRY_PATHS, SPA_PATHS } from '../routes';
6
6
  import type { DiffMeta, FileDiffResponse, FileMeta, FileRangeResponse, RepoTreeResponse } from '../types';
7
7
  import { cacheFresh, type TimedCacheEntry } from './cache';
8
+ import { startDevAssetReload } from './dev-assets';
8
9
  import * as git from './git';
9
10
  import { isSameWorktreeRange } from './range';
10
11
 
@@ -458,10 +459,6 @@ const server = Bun.serve({
458
459
  if (url.pathname === '/file_range') return handleFileRange(url);
459
460
  if (url.pathname === '/_file') return handleRawFile(url);
460
461
  if (url.pathname === '/_refs') return json(git.refs(cwd));
461
- if (url.pathname === '/_asset_version') {
462
- const version = Math.max(...WATCHED_ASSET_FILES.map((name) => statSync(join(WEB_ROOT, name)).mtimeMs));
463
- return json({ version });
464
- }
465
462
  if (url.pathname === '/refresh' && req.method === 'POST') {
466
463
  generation++;
467
464
  fileCache.clear();
@@ -502,5 +499,13 @@ const server = Bun.serve({
502
499
  },
503
500
  });
504
501
 
502
+ startDevAssetReload({
503
+ enabled: process.env.CODE_VIEWER_DEV === '1',
504
+ webRoot: WEB_ROOT,
505
+ watchedFiles: WATCHED_ASSET_FILES,
506
+ watch,
507
+ sendReload: () => sendSse('reload'),
508
+ });
509
+
505
510
  console.log(`GDP_LISTEN_URL=http://127.0.0.1:${server.port}/`);
506
511
  console.log(`git-diff-preview serving ${cwd}`);
@@ -17,6 +17,7 @@ declare const Bun: {
17
17
 
18
18
  declare const process: {
19
19
  argv: string[];
20
+ env: Record<string, string | undefined>;
20
21
  cwd(): string;
21
22
  platform: 'darwin' | 'win32' | string;
22
23
  on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void;
@@ -34,6 +35,11 @@ declare module 'node:fs' {
34
35
  export function readFileSync(path: string, encoding: BufferEncoding): string;
35
36
  export function realpathSync(path: string): string;
36
37
  export function statSync(path: string): { mtimeMs: number };
38
+ export function watch(
39
+ path: string,
40
+ options: { persistent?: boolean },
41
+ listener: (eventType: string, filename: string | Buffer | null) => void,
42
+ ): unknown;
37
43
  }
38
44
 
39
45
  declare module 'node:path' {
package/web-src/types.ts CHANGED
@@ -86,10 +86,6 @@ export type RefResponse = {
86
86
  current?: string;
87
87
  };
88
88
 
89
- export type AssetVersionResponse = {
90
- version?: number;
91
- };
92
-
93
89
  declare global {
94
90
  interface Window {
95
91
  Diff2HtmlUI: any;