eprec 1.9.0 → 1.10.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.
@@ -188,7 +188,6 @@ p {
188
188
  grid-template-columns: repeat(auto-fit, minmax(var(--card-min-width), 1fr));
189
189
  }
190
190
 
191
-
192
191
  .app-card {
193
192
  background: var(--color-surface);
194
193
  border-radius: var(--radius-xl);
@@ -466,11 +466,7 @@ export function EditingWorkspace(handle: Handle) {
466
466
  primaryChapterId.length > 0 &&
467
467
  secondaryChapterId.length > 0 &&
468
468
  primaryChapterId !== secondaryChapterId
469
- const commandPreview = buildCommandPreview(
470
- sourceName,
471
- chapters,
472
- sourcePath,
473
- )
469
+ const commandPreview = buildCommandPreview(sourceName, chapters, sourcePath)
474
470
  const previewTime =
475
471
  previewReady && previewDuration > 0
476
472
  ? (playhead / duration) * previewDuration
@@ -524,8 +520,7 @@ export function EditingWorkspace(handle: Handle) {
524
520
  value={videoPathInput}
525
521
  on={{
526
522
  input: (event) => {
527
- const target =
528
- event.currentTarget as HTMLInputElement
523
+ const target = event.currentTarget as HTMLInputElement
529
524
  updateVideoPathInput(target.value)
530
525
  },
531
526
  }}
@@ -854,10 +849,7 @@ export function EditingWorkspace(handle: Handle) {
854
849
  </span>
855
850
  </div>
856
851
  <span
857
- class={classNames(
858
- 'status-pill',
859
- previewStatus.className,
860
- )}
852
+ class={classNames('status-pill', previewStatus.className)}
861
853
  >
862
854
  {previewStatus.label}
863
855
  </span>
@@ -21,9 +21,7 @@ const indexHandler = {
21
21
  </header>
22
22
  <section class="app-card app-card--full">
23
23
  <h2>Source video</h2>
24
- <p class="app-muted">
25
- Paste a video file path once the UI loads.
26
- </p>
24
+ <p class="app-muted">Paste a video file path once the UI loads.</p>
27
25
  </section>
28
26
  <section class="app-card app-card--full">
29
27
  <h2>Processing actions</h2>
package/app/video-api.ts CHANGED
@@ -8,7 +8,11 @@ function isLocalhostOrigin(origin: string | null): boolean {
8
8
  try {
9
9
  const url = new URL(origin)
10
10
  const hostname = url.hostname.toLowerCase()
11
- return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]'
11
+ return (
12
+ hostname === 'localhost' ||
13
+ hostname === '127.0.0.1' ||
14
+ hostname === '[::1]'
15
+ )
12
16
  } catch {
13
17
  return false
14
18
  }
@@ -71,13 +75,16 @@ function parseRangeHeader(
71
75
  return { start: rangeStart, end: size - 1 }
72
76
  }
73
77
 
74
- const rangeEnd =
75
- end === null || end >= size ? Math.max(size - 1, 0) : end
78
+ const rangeEnd = end === null || end >= size ? Math.max(size - 1, 0) : end
76
79
  if (start > rangeEnd) return null
77
80
  return { start, end: rangeEnd }
78
81
  }
79
82
 
80
- function buildVideoHeaders(contentType: string, length: number, origin: string | null) {
83
+ function buildVideoHeaders(
84
+ contentType: string,
85
+ length: number,
86
+ origin: string | null,
87
+ ) {
81
88
  return {
82
89
  'Content-Type': contentType,
83
90
  'Content-Length': String(length),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eprec",
3
3
  "type": "module",
4
- "version": "1.9.0",
4
+ "version": "1.10.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -0,0 +1,70 @@
1
+ import { test, expect } from 'bun:test'
2
+ import { createShortcutInputHandler } from './app-server'
3
+
4
+ type ShortcutCounts = {
5
+ open: number
6
+ restart: number
7
+ stop: number
8
+ help: number
9
+ spacing: number
10
+ }
11
+
12
+ function createShortcutCounts(): ShortcutCounts {
13
+ return {
14
+ open: 0,
15
+ restart: 0,
16
+ stop: 0,
17
+ help: 0,
18
+ spacing: 0,
19
+ }
20
+ }
21
+
22
+ function createShortcutHandler() {
23
+ const counts = createShortcutCounts()
24
+ const handler = createShortcutInputHandler({
25
+ open: () => {
26
+ counts.open += 1
27
+ },
28
+ restart: () => {
29
+ counts.restart += 1
30
+ },
31
+ stop: () => {
32
+ counts.stop += 1
33
+ },
34
+ help: () => {
35
+ counts.help += 1
36
+ },
37
+ spacing: () => {
38
+ counts.spacing += 1
39
+ },
40
+ })
41
+
42
+ return { counts, handler }
43
+ }
44
+
45
+ test('createShortcutInputHandler adds spacing for enter key', () => {
46
+ const { counts, handler } = createShortcutHandler()
47
+
48
+ handler.handleInput('\r')
49
+
50
+ expect(counts.spacing).toBe(1)
51
+ })
52
+
53
+ test('createShortcutInputHandler ignores linefeed after carriage return', () => {
54
+ const { counts, handler } = createShortcutHandler()
55
+
56
+ handler.handleInput('\r\n')
57
+
58
+ expect(counts.spacing).toBe(1)
59
+ })
60
+
61
+ test('createShortcutInputHandler maps shortcuts case-insensitively', () => {
62
+ const { counts, handler } = createShortcutHandler()
63
+
64
+ handler.handleInput('OrQh?')
65
+
66
+ expect(counts.open).toBe(1)
67
+ expect(counts.restart).toBe(1)
68
+ expect(counts.stop).toBe(1)
69
+ expect(counts.help).toBe(2)
70
+ })
package/src/app-server.ts CHANGED
@@ -26,6 +26,14 @@ const SHORTCUT_COLORS: Record<string, string> = {
26
26
  const ANSI_RESET = '\u001b[0m'
27
27
  const APP_ROOT = path.resolve(import.meta.dirname, '..')
28
28
 
29
+ type ShortcutActions = {
30
+ open: () => void
31
+ restart: () => void
32
+ stop: () => void
33
+ help: () => void
34
+ spacing: () => void
35
+ }
36
+
29
37
  function colorizeShortcut(key: string) {
30
38
  if (!COLOR_ENABLED) {
31
39
  return key
@@ -55,6 +63,7 @@ function getShortcutLines(url: string) {
55
63
  ` ${colorizeShortcut('r')}: restart server`,
56
64
  ` ${colorizeShortcut('q')}: quit server`,
57
65
  ` ${colorizeShortcut('h')}: show shortcuts`,
66
+ ` ${colorizeShortcut('enter')}: add log spacing`,
58
67
  ]
59
68
  }
60
69
 
@@ -95,44 +104,70 @@ function openBrowser(url: string) {
95
104
  }
96
105
  }
97
106
 
98
- function setupShortcutHandling(options: {
99
- getUrl: () => string
100
- restart: () => void
101
- stop: () => void
102
- }) {
103
- if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') {
104
- return () => {}
105
- }
107
+ export function createShortcutInputHandler(actions: ShortcutActions) {
108
+ let lastKey: string | null = null
106
109
 
107
- const stdin = process.stdin
108
110
  const handleKey = (key: string) => {
109
111
  if (key === '\u0003') {
110
- options.stop()
112
+ actions.stop()
113
+ return
114
+ }
115
+ if (key === '\r' || key === '\n') {
116
+ actions.spacing()
111
117
  return
112
118
  }
113
119
  const lower = key.toLowerCase()
114
120
  if (lower === 'o') {
115
- openBrowser(options.getUrl())
121
+ actions.open()
116
122
  return
117
123
  }
118
124
  if (lower === 'r') {
119
- options.restart()
125
+ actions.restart()
120
126
  return
121
127
  }
122
128
  if (lower === 'q') {
123
- options.stop()
129
+ actions.stop()
124
130
  return
125
131
  }
126
132
  if (lower === 'h' || lower === '?') {
127
- logShortcuts(options.getUrl())
133
+ actions.help()
128
134
  }
129
135
  }
130
136
 
137
+ return {
138
+ handleInput: (input: string) => {
139
+ for (const key of input) {
140
+ if (key === '\n' && lastKey === '\r') {
141
+ lastKey = key
142
+ continue
143
+ }
144
+ handleKey(key)
145
+ lastKey = key
146
+ }
147
+ },
148
+ }
149
+ }
150
+
151
+ function setupShortcutHandling(options: {
152
+ getUrl: () => string
153
+ restart: () => void
154
+ stop: () => void
155
+ }) {
156
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') {
157
+ return () => {}
158
+ }
159
+
160
+ const stdin = process.stdin
161
+ const shortcutHandler = createShortcutInputHandler({
162
+ open: () => openBrowser(options.getUrl()),
163
+ restart: options.restart,
164
+ stop: options.stop,
165
+ help: () => logShortcuts(options.getUrl()),
166
+ spacing: () => console.log(''),
167
+ })
168
+
131
169
  const onData = (chunk: Buffer | string) => {
132
- const input = chunk.toString()
133
- for (const key of input) {
134
- handleKey(key)
135
- }
170
+ shortcutHandler.handleInput(chunk.toString())
136
171
  }
137
172
 
138
173
  stdin.setRawMode(true)