omni-viewer-core 0.5.0 → 0.6.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.
@@ -2,11 +2,14 @@ import { tomlPath } from './model.js';
2
2
  export function parseTomlText(text, limits = {}) {
3
3
  const settings = typeof limits === 'number' ? { maxDepth: limits } : limits;
4
4
  const maxDepth = settings.maxDepth ?? 100, maxNodes = settings.maxNodes ?? 100_000, maxTables = settings.maxTables ?? 50_000, maxArrayLength = settings.maxArrayLength ?? 100_000;
5
- const root = { kind: 'table', key: '$', path: '', raw: '', line: 1, children: [] };
5
+ const root = { kind: 'table', key: '$', path: '', raw: '', line: 1, range: { start: 0, end: text.length }, children: [] };
6
6
  const diagnostics = [];
7
7
  let current = root, nodeCount = 1, tableCount = 1, stopped = false;
8
8
  const tables = new Map([['', root]]);
9
- const lines = logicalTomlLines(text.replace(/^\uFEFF/, ''));
9
+ // Offsets stay relative to the untouched `text` so ranges address the same
10
+ // string a host renders. A leading BOM is dropped by `trim()` instead.
11
+ const lines = logicalTomlLines(text);
12
+ const lineAt = lineIndexer(text);
10
13
  const error = (code, line) => diagnostics.push({ severity: 'error', code, messageKey: `diag.${code}`, location: `line:${line}` });
11
14
  const unquote = (value) => {
12
15
  if (value.startsWith('"""') && value.endsWith('"""')) {
@@ -34,11 +37,15 @@ export function parseTomlText(text, limits = {}) {
34
37
  return value.slice(1, -1);
35
38
  return value;
36
39
  };
37
- const split = (value) => {
40
+ /** Split on top-level commas, reporting where each trimmed part starts so
41
+ * inline-table and array members can carry their own range. */
42
+ const split = (value, base) => {
38
43
  const out = [];
39
44
  let start = 0;
40
45
  let quote = '';
41
46
  let depth = 0;
47
+ const push = (from, to) => { const part = value.slice(from, to); const lead = part.length - part.trimStart().length; const trimmed = part.trim(); if (trimmed)
48
+ out.push({ text: trimmed, at: base + from + lead }); };
42
49
  for (let i = 0; i < value.length; i++) {
43
50
  const c = value[i];
44
51
  if (quote) {
@@ -52,16 +59,18 @@ export function parseTomlText(text, limits = {}) {
52
59
  else if (c === ']' || c === '}')
53
60
  depth--;
54
61
  else if (c === ',' && depth === 0) {
55
- out.push(value.slice(start, i).trim());
62
+ push(start, i);
56
63
  start = i + 1;
57
64
  }
58
65
  }
59
- out.push(value.slice(start).trim());
60
- return out.filter(Boolean);
66
+ push(start, value.length);
67
+ return out;
61
68
  };
62
69
  const keyParts = (key) => splitKey(key).map(unquote);
63
- const makeValue = (key, path, rawValue, line, depth) => {
70
+ const makeValue = (key, path, rawValue, line, depth, start) => {
64
71
  const raw = rawValue.trim();
72
+ const from = start + (rawValue.length - rawValue.trimStart().length);
73
+ const range = { start: from, end: from + raw.length };
65
74
  let kind = 'string';
66
75
  let value = unquote(raw);
67
76
  if (/^(true|false)$/.test(raw)) {
@@ -76,34 +85,37 @@ export function parseTomlText(text, limits = {}) {
76
85
  kind = 'datetime';
77
86
  else if (raw.startsWith('[') && raw.endsWith(']')) {
78
87
  kind = 'array';
79
- const values = split(raw.slice(1, -1));
88
+ const values = split(raw.slice(1, -1), from + 1);
80
89
  if (values.length > maxArrayLength) {
81
90
  error('toml.array-limit', line);
82
91
  values.length = maxArrayLength;
83
92
  }
84
- const children = values.map((v, i) => makeValue(String(i), `${path}[${i}]`, v, line, depth + 1));
93
+ const children = values.map((entry, i) => makeValue(String(i), `${path}[${i}]`, entry.text, lineAt(entry.at), depth + 1, entry.at));
85
94
  nodeCount += children.length;
86
- return { kind, key, path, raw, line, children };
95
+ return { kind, key, path, raw, line, range, children };
87
96
  }
88
97
  else if (raw.startsWith('{') && raw.endsWith('}')) {
89
98
  kind = 'table';
90
99
  const children = [];
91
- for (const pair of split(raw.slice(1, -1))) {
92
- const eq = indexOfEquals(pair);
100
+ for (const pair of split(raw.slice(1, -1), from + 1)) {
101
+ const eq = indexOfEquals(pair.text);
93
102
  if (eq < 0) {
94
- error('toml.invalid-inline-table', line);
103
+ error('toml.invalid-inline-table', lineAt(pair.at));
95
104
  continue;
96
105
  }
97
- const childKey = unquote(pair.slice(0, eq).trim());
98
- children.push(makeValue(childKey, tomlPath(path, childKey), pair.slice(eq + 1), line, depth + 1));
106
+ const childKey = unquote(pair.text.slice(0, eq).trim());
107
+ const child = makeValue(childKey, tomlPath(path, childKey), pair.text.slice(eq + 1), lineAt(pair.at), depth + 1, pair.at + eq + 1);
108
+ child.range = { start: pair.at, end: pair.at + pair.text.length };
109
+ children.push(child);
99
110
  }
100
- return { kind, key, path, raw, line, children };
111
+ return { kind, key, path, raw, line, range, children };
101
112
  }
102
113
  if (depth > maxDepth)
103
114
  error('toml.max-depth', line);
104
115
  nodeCount++;
105
- return { kind, key, path, value, raw, line };
116
+ return { kind, key, path, value, raw, line, range };
106
117
  };
118
+ let pending = [];
107
119
  for (const logical of lines) {
108
120
  if (settings.signal?.aborted) {
109
121
  error('aborted', logical.line);
@@ -116,9 +128,22 @@ export function parseTomlText(text, limits = {}) {
116
128
  break;
117
129
  }
118
130
  const i = logical.line - 1;
119
- const source = stripComment(logical.text).trim();
120
- if (!source)
131
+ const uncommented = stripComment(logical.text);
132
+ const trailing = commentBody(logical.text.slice(uncommented.length));
133
+ const source = uncommented.trim();
134
+ // Comment runs stay attached to the next declaration; a blank line ends
135
+ // the run so a file-header comment never leaks onto unrelated keys.
136
+ if (!source) {
137
+ if (trailing === undefined)
138
+ pending = [];
139
+ else
140
+ pending.push(trailing);
121
141
  continue;
142
+ }
143
+ const comment = [...pending, ...(trailing === undefined ? [] : [trailing])].join('\n') || undefined;
144
+ pending = [];
145
+ const base = logical.offset + (logical.text.length - logical.text.trimStart().length);
146
+ const declaration = { start: base, end: base + source.length };
122
147
  const arrayTable = source.match(/^\[\[(.+)\]\]$/);
123
148
  const table = source.match(/^\[(.+)\]$/);
124
149
  if (arrayTable || table) {
@@ -134,12 +159,12 @@ export function parseTomlText(text, limits = {}) {
134
159
  const parent = tables.get(parentPath) ?? root;
135
160
  let collection = parent.children.find(node => node.key === parts.at(-1) && node.kind === 'array');
136
161
  if (!collection) {
137
- collection = { kind: 'array', key: parts.at(-1), path, raw: '', line: i + 1, children: [] };
162
+ collection = { kind: 'array', key: parts.at(-1), path, raw: '', line: i + 1, range: declaration, children: [] };
138
163
  parent.children.push(collection);
139
164
  nodeCount++;
140
165
  }
141
166
  const index = collection.children.length;
142
- const node = { kind: 'table', key: String(index), path: `${path}[${index}]`, raw: '', line: i + 1, children: [] };
167
+ const node = { kind: 'table', key: String(index), path: `${path}[${index}]`, raw: '', line: i + 1, range: declaration, ...(comment ? { comment } : {}), children: [] };
143
168
  collection.children.push(node);
144
169
  tables.set(path, node);
145
170
  current = node;
@@ -149,7 +174,7 @@ export function parseTomlText(text, limits = {}) {
149
174
  else {
150
175
  let node = tables.get(path);
151
176
  if (!node) {
152
- node = { kind: 'table', key: parts.at(-1), path, raw: '', line: i + 1, children: [] };
177
+ node = { kind: 'table', key: parts.at(-1), path, raw: '', line: i + 1, range: declaration, ...(comment ? { comment } : {}), children: [] };
153
178
  (tables.get(parts.slice(0, -1).join('.')) ?? root).children.push(node);
154
179
  tables.set(path, node);
155
180
  nodeCount++;
@@ -175,7 +200,7 @@ export function parseTomlText(text, limits = {}) {
175
200
  for (const part of parts.slice(0, -1)) {
176
201
  let nested = parent.children.find(n => n.key === part && n.kind === 'table');
177
202
  if (!nested) {
178
- nested = { kind: 'table', key: part, path: tomlPath(parent.path, part), raw: '', line: i + 1, children: [] };
203
+ nested = { kind: 'table', key: part, path: tomlPath(parent.path, part), raw: '', line: i + 1, range: declaration, children: [] };
179
204
  parent.children.push(nested);
180
205
  }
181
206
  parent = nested;
@@ -183,25 +208,45 @@ export function parseTomlText(text, limits = {}) {
183
208
  const key = parts.at(-1);
184
209
  if (parent.children.some(n => n.key === key))
185
210
  error('toml.duplicate-key', i + 1);
186
- else
187
- parent.children.push(makeValue(key, tomlPath(parent.path, key), source.slice(eq + 1), i + 1, 0));
211
+ else {
212
+ const node = makeValue(key, tomlPath(parent.path, key), source.slice(eq + 1), i + 1, 0, base + eq + 1);
213
+ // The declaration reads better than the bare value when a host turns
214
+ // the range into a selection, so widen it to cover `key = value`.
215
+ node.range = declaration;
216
+ if (comment)
217
+ node.comment = comment;
218
+ parent.children.push(node);
219
+ }
188
220
  }
189
221
  return { document: { text, root }, diagnostics };
190
222
  }
191
223
  /** Group physical lines while a multiline string or a composite array/table
192
224
  * remains open. This preserves the declaration line for diagnostics. */
193
225
  function logicalTomlLines(text) {
194
- const physical = text.split(/\r\n|\n|\r/);
226
+ const physical = [];
227
+ {
228
+ const separator = /\r\n|\n|\r/g;
229
+ let last = 0;
230
+ let match;
231
+ while ((match = separator.exec(text))) {
232
+ physical.push({ text: text.slice(last, match.index), offset: last });
233
+ last = match.index + match[0].length;
234
+ }
235
+ physical.push({ text: text.slice(last), offset: last });
236
+ }
195
237
  const out = [];
196
- let buffer = '';
238
+ let open = false;
197
239
  let start = 1;
240
+ let offset = 0;
198
241
  let triple = '';
199
242
  let depth = 0;
200
243
  for (let i = 0; i < physical.length; i++) {
201
- const line = physical[i] ?? '';
202
- if (!buffer)
244
+ const { text: line, offset: at } = physical[i];
245
+ if (!open) {
246
+ open = true;
203
247
  start = i + 1;
204
- buffer += (buffer ? '\n' : '') + line;
248
+ offset = at;
249
+ }
205
250
  for (let p = 0; p < line.length; p++) {
206
251
  if (line.startsWith('"""', p) || line.startsWith("'''", p)) {
207
252
  const token = line.slice(p, p + 3);
@@ -219,16 +264,41 @@ function logicalTomlLines(text) {
219
264
  else if (c === ']' || c === '}')
220
265
  depth--;
221
266
  }
267
+ // Slicing the original text (rather than re-joining) keeps every offset
268
+ // inside a logical line usable as an index into `text`.
222
269
  if (!triple && depth <= 0) {
223
- out.push({ line: start, text: buffer });
224
- buffer = '';
270
+ out.push({ line: start, text: text.slice(offset, at + line.length), offset });
271
+ open = false;
225
272
  depth = 0;
226
273
  }
227
274
  }
228
- if (buffer)
229
- out.push({ line: start, text: buffer });
275
+ if (open)
276
+ out.push({ line: start, text: text.slice(offset), offset });
230
277
  return out;
231
278
  }
279
+ /** 1-based line lookup for offsets, used by inline members that may sit on a
280
+ * different physical line than their declaration. */
281
+ function lineIndexer(text) {
282
+ const starts = [0];
283
+ for (let i = 0; i < text.length; i++) {
284
+ const c = text[i];
285
+ if (c === '\n')
286
+ starts.push(i + 1);
287
+ else if (c === '\r') {
288
+ if (text[i + 1] === '\n')
289
+ i++;
290
+ starts.push(i + 1);
291
+ }
292
+ }
293
+ return offset => { let low = 0, high = starts.length - 1; while (low < high) {
294
+ const mid = (low + high + 1) >> 1;
295
+ if (starts[mid] <= offset)
296
+ low = mid;
297
+ else
298
+ high = mid - 1;
299
+ } return low + 1; };
300
+ }
301
+ function commentBody(rest) { const text = rest.trimStart(); return text.startsWith('#') ? text.slice(1).trim() : undefined; }
232
302
  function stripComment(line) { let q = ''; for (let i = 0; i < line.length; i++) {
233
303
  const c = line[i];
234
304
  if (q) {
@@ -61,6 +61,7 @@ export declare const REQIF_VIEWER_DESCRIPTOR: ViewerDescriptor;
61
61
  export declare const HDF5_MAGIC_PREFIX: readonly [137, 72, 68, 70, 13, 10, 26, 10];
62
62
  export declare const HDF5_VIEWER_DESCRIPTOR: ViewerDescriptor;
63
63
  export declare const MAT_VIEWER_DESCRIPTOR: ViewerDescriptor;
64
+ export declare const SAFETENSORS_VIEWER_DESCRIPTOR: ViewerDescriptor;
64
65
  export declare const AUDIO_VIEWER_DESCRIPTOR: ViewerDescriptor;
65
66
  export declare const VIDEO_VIEWER_DESCRIPTOR: ViewerDescriptor;
66
67
  export declare const DBC_VIEWER_DESCRIPTOR: ViewerDescriptor;
@@ -72,7 +72,7 @@ export const PDF_VIEWER_DESCRIPTOR = {
72
72
  priority: 20,
73
73
  magicPrefix: [0x25, 0x50, 0x44, 0x46, 0x2d], // '%PDF-'
74
74
  requiredServices: [],
75
- optionalServices: ['save', 'writeback', 'filePick']
75
+ optionalServices: ['save', 'writeback', 'filePick', 'clipboard']
76
76
  };
77
77
  export const PARQUET_MAGIC_SIGNATURES = [[{ offset: 0, bytes: [0x50, 0x41, 0x52, 0x31] }]];
78
78
  export const PARQUET_VIEWER_DESCRIPTOR = { id: 'parquet', displayNameKey: 'parquet.title', extensions: ['parquet'], priority: 20, magicSignatures: PARQUET_MAGIC_SIGNATURES, requiredServices: [], optionalServices: ['clipboard', 'save'] };
@@ -87,6 +87,9 @@ export const HDF5_MAGIC_PREFIX = [0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a
87
87
  // signature still enables extensionless content routing below.
88
88
  export const HDF5_VIEWER_DESCRIPTOR = { id: 'hdf5', displayNameKey: 'hdf5.title', extensions: ['h5', 'hdf5', 'he5'], priority: 20, requiredServices: [], optionalServices: ['clipboard'] };
89
89
  export const MAT_VIEWER_DESCRIPTOR = { id: 'mat', displayNameKey: 'mat.title', extensions: ['mat'], priority: 20, requiredServices: [], optionalServices: ['clipboard'] };
90
+ // Safetensors has no fixed leading magic (the file opens with an 8-byte header
91
+ // length), so it is admitted by extension and validated by the parser.
92
+ export const SAFETENSORS_VIEWER_DESCRIPTOR = { id: 'safetensors', displayNameKey: 'safetensors.title', extensions: ['safetensors'], priority: 20, requiredServices: [], optionalServices: ['clipboard'] };
90
93
  export const AUDIO_VIEWER_DESCRIPTOR = { id: 'audio', displayNameKey: 'audio.title', extensions: ['mp3', 'wav', 'pcm', 'aiff', 'aif', 'aifc', 'amr', 'awb', 'ogg', 'flac', 'ac3', 'aac', 'm4a'], priority: 20, requiredServices: [], optionalServices: [] };
91
94
  export const VIDEO_VIEWER_DESCRIPTOR = { id: 'video', displayNameKey: 'video.title', extensions: ['mp4', 'mts', 'm2ts', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'], priority: 20, requiredServices: [], optionalServices: [] };
92
95
  export const DBC_VIEWER_DESCRIPTOR = { id: 'dbc', displayNameKey: 'dbc.title', extensions: ['dbc'], priority: 20, requiredServices: [], optionalServices: ['clipboard'] };
@@ -200,6 +203,7 @@ export const CORE_VIEWER_DESCRIPTORS = [
200
203
  REQIF_VIEWER_DESCRIPTOR,
201
204
  HDF5_VIEWER_DESCRIPTOR,
202
205
  MAT_VIEWER_DESCRIPTOR,
206
+ SAFETENSORS_VIEWER_DESCRIPTOR,
203
207
  PPT_VIEWER_DESCRIPTOR,
204
208
  WORD_VIEWER_DESCRIPTOR,
205
209
  HWP_VIEWER_DESCRIPTOR,
@@ -183,6 +183,17 @@
183
183
  min-width: 56px;
184
184
  text-align: center;
185
185
  }
186
+ .omni-pdf__thumbs-toggle {
187
+ width: 30px;
188
+ height: 28px;
189
+ padding: 0;
190
+ margin-right: auto;
191
+ font-size: 16px;
192
+ line-height: 1;
193
+ }
194
+ .omni-pdf__thumbs-toggle.is-collapsed {
195
+ opacity: 0.6;
196
+ }
186
197
  .omni-pdf__view-menu-wrap { position: relative; }
187
198
  .omni-pdf__view-menu-btn {
188
199
  width: 30px;
@@ -227,12 +238,19 @@
227
238
  background: var(--omni-border, #3c3c3c);
228
239
  }
229
240
  .omni-pdf__body {
241
+ position: relative;
230
242
  flex: 1;
231
243
  min-height: 0;
232
244
  display: grid;
233
245
  grid-template-columns: 160px minmax(0, 1fr);
234
246
  overflow: hidden;
235
247
  }
248
+ .omni-pdf__body--thumbs-collapsed {
249
+ grid-template-columns: minmax(0, 1fr);
250
+ }
251
+ .omni-pdf__body--thumbs-collapsed .omni-pdf__thumbs {
252
+ display: none;
253
+ }
236
254
  .omni-pdf__thumbs {
237
255
  overflow: auto;
238
256
  min-height: 0;
@@ -464,6 +482,161 @@
464
482
  content: '';
465
483
  background: var(--omni-hl-color, #ffeb3b);
466
484
  }
485
+ /* Floating action bar shown above a selected text-markup annotation. */
486
+ .omni-pdf__markup-toolbar {
487
+ position: absolute;
488
+ z-index: 5;
489
+ display: flex;
490
+ align-items: center;
491
+ gap: 2px;
492
+ padding: 4px;
493
+ transform: translateY(calc(-100% - 8px));
494
+ background: var(--omni-bg-secondary, #252526);
495
+ border: 1px solid var(--omni-border, #3c3c3c);
496
+ border-radius: 10px;
497
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
498
+ cursor: default;
499
+ }
500
+ .omni-pdf button.omni-pdf__markup-tool {
501
+ display: inline-flex;
502
+ align-items: center;
503
+ justify-content: center;
504
+ width: 30px;
505
+ height: 30px;
506
+ padding: 0;
507
+ border: 0;
508
+ border-radius: 8px;
509
+ background: transparent;
510
+ color: var(--omni-fg, #e8e8e8);
511
+ font-size: 15px;
512
+ line-height: 1;
513
+ cursor: pointer;
514
+ }
515
+ .omni-pdf button.omni-pdf__markup-tool:hover:not(:disabled) { background: var(--omni-button-hover-bg, #45494e); }
516
+ .omni-pdf button.omni-pdf__markup-tool:disabled { opacity: 0.4; cursor: default; }
517
+ .omni-pdf__markup-color::before {
518
+ content: '';
519
+ width: 18px;
520
+ height: 18px;
521
+ border-radius: 5px;
522
+ background: var(--omni-hl-color, #ffeb3b);
523
+ border: 1px solid rgba(0, 0, 0, 0.25);
524
+ }
525
+ .omni-pdf__markup-color-wrap { position: relative; display: inline-flex; }
526
+ .omni-pdf__markup-palette {
527
+ position: absolute;
528
+ top: calc(100% + 6px);
529
+ left: 50%;
530
+ transform: translateX(-50%);
531
+ display: none;
532
+ grid-template-columns: repeat(3, auto);
533
+ gap: 6px;
534
+ padding: 8px;
535
+ background: var(--omni-bg-secondary, #252526);
536
+ border: 1px solid var(--omni-border, #3c3c3c);
537
+ border-radius: 8px;
538
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
539
+ z-index: 6;
540
+ }
541
+ .omni-pdf__markup-palette.is-open { display: grid; }
542
+ .omni-pdf button.omni-pdf__markup-swatch,
543
+ .omni-pdf button.omni-pdf__markup-swatch:hover:not(:disabled) {
544
+ width: 22px;
545
+ height: 22px;
546
+ padding: 0;
547
+ border-radius: 5px;
548
+ border: 1px solid rgba(255, 255, 255, 0.25);
549
+ background: var(--omni-hl-color, #ffeb3b);
550
+ cursor: pointer;
551
+ }
552
+ .omni-pdf button.omni-pdf__markup-swatch.is-current {
553
+ outline: 2px solid var(--omni-accent, #0e639c);
554
+ outline-offset: 1px;
555
+ }
556
+ /* Left overlay panel listing every text-markup annotation. */
557
+ .omni-pdf__markup-list {
558
+ position: absolute;
559
+ top: 0;
560
+ left: 0;
561
+ bottom: 0;
562
+ z-index: 6;
563
+ display: none;
564
+ flex-direction: column;
565
+ width: min(280px, 70%);
566
+ background: var(--omni-bg-secondary, #252526);
567
+ border-right: 1px solid var(--omni-border, #3c3c3c);
568
+ box-shadow: 2px 0 12px rgba(0, 0, 0, 0.35);
569
+ overflow-y: auto;
570
+ }
571
+ .omni-pdf__markup-list.is-open { display: flex; }
572
+ .omni-pdf__markup-list-head {
573
+ position: sticky;
574
+ top: 0;
575
+ display: flex;
576
+ align-items: center;
577
+ justify-content: space-between;
578
+ padding: 10px 12px;
579
+ background: var(--omni-bg-secondary, #252526);
580
+ border-bottom: 1px solid var(--omni-border, #3c3c3c);
581
+ }
582
+ .omni-pdf__markup-list-title { font-weight: 600; }
583
+ .omni-pdf button.omni-pdf__markup-list-close {
584
+ width: 26px;
585
+ height: 26px;
586
+ padding: 0;
587
+ border: 0;
588
+ border-radius: 6px;
589
+ background: transparent;
590
+ color: var(--omni-fg, #e8e8e8);
591
+ font-size: 18px;
592
+ line-height: 1;
593
+ cursor: pointer;
594
+ }
595
+ .omni-pdf button.omni-pdf__markup-list-close:hover:not(:disabled) { background: var(--omni-button-hover-bg, #45494e); }
596
+ .omni-pdf__markup-list-empty {
597
+ padding: 16px 12px;
598
+ color: var(--omni-fg-muted, #9d9d9d);
599
+ font-size: 13px;
600
+ }
601
+ .omni-pdf button.omni-pdf__markup-item {
602
+ display: flex;
603
+ align-items: center;
604
+ gap: 8px;
605
+ width: 100%;
606
+ padding: 10px 12px;
607
+ border: 0;
608
+ border-bottom: 1px solid var(--omni-border, #3c3c3c);
609
+ background: transparent;
610
+ color: var(--omni-fg, #e8e8e8);
611
+ text-align: left;
612
+ cursor: pointer;
613
+ }
614
+ .omni-pdf button.omni-pdf__markup-item:hover:not(:disabled) { background: var(--omni-button-hover-bg, #45494e); }
615
+ .omni-pdf button.omni-pdf__markup-item.is-selected {
616
+ background: var(--omni-button-hover-bg, #45494e);
617
+ box-shadow: inset 3px 0 0 var(--omni-accent, #0e639c);
618
+ }
619
+ .omni-pdf__markup-item-dot {
620
+ flex: none;
621
+ width: 14px;
622
+ height: 14px;
623
+ border-radius: 3px;
624
+ border: 1px solid rgba(255, 255, 255, 0.25);
625
+ background: var(--omni-hl-color, #ffeb3b);
626
+ }
627
+ .omni-pdf__markup-item-text {
628
+ flex: 1;
629
+ min-width: 0;
630
+ overflow: hidden;
631
+ text-overflow: ellipsis;
632
+ white-space: nowrap;
633
+ font-size: 13px;
634
+ }
635
+ .omni-pdf__markup-item-page {
636
+ flex: none;
637
+ color: var(--omni-fg-muted, #9d9d9d);
638
+ font-size: 12px;
639
+ }
467
640
  .omni-pdf__text-editor {
468
641
  position: absolute;
469
642
  z-index: 3;
@@ -569,9 +742,13 @@
569
742
  grid-template-columns: minmax(0, 1fr);
570
743
  grid-template-rows: minmax(120px, 30vh) minmax(0, 1fr);
571
744
  }
745
+ .omni-pdf__body--thumbs-collapsed {
746
+ grid-template-rows: minmax(0, 1fr);
747
+ }
572
748
  .omni-pdf__thumbs {
573
749
  border-right: 0;
574
750
  border-bottom: 1px solid var(--omni-border, #3c3c3c);
575
751
  }
576
752
  .omni-pdf__pages { grid-row: 2; }
753
+ .omni-pdf__body--thumbs-collapsed .omni-pdf__pages { grid-row: 1; }
577
754
  }
@@ -0,0 +1,3 @@
1
+
2
+ .omni-safetensors{height:100%;min-height:0;display:flex;flex-direction:column;background:var(--omni-bg,#fff);color:var(--omni-fg,#222);font:13px system-ui,sans-serif}.omni-safetensors__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 20px 12px;border-bottom:1px solid var(--omni-border,#ddd);background:var(--omni-panel-bg,#f7f7f7)}.omni-safetensors__eyebrow{color:var(--omni-muted,#666);font-size:12px;text-transform:uppercase}.omni-safetensors h1{margin:2px 0 4px;font-size:20px}.omni-safetensors__subtitle{color:var(--omni-muted,#666);font-size:12px}.omni-safetensors__summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1px;background:var(--omni-border,#ddd);border-bottom:1px solid var(--omni-border,#ddd)}.omni-safetensors__summary-item{min-height:64px;padding:12px 14px;background:var(--omni-bg,#fff)}.omni-safetensors__summary-value{font-size:20px;font-weight:650}.omni-safetensors__summary-label{margin-top:4px;color:var(--omni-muted,#666);font-size:12px}.omni-safetensors__toolbar{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--omni-border,#ddd);background:var(--omni-toolbar-bg,#f5f5f5)}.omni-safetensors__search{width:min(320px,36vw);min-width:180px;border:1px solid var(--omni-border,#ccc);background:var(--omni-input-bg,#fff);color:inherit;padding:7px 9px;border-radius:4px}.omni-safetensors__tabs{display:flex;flex-wrap:wrap;gap:4px;flex:1}.omni-safetensors button{border:1px solid var(--omni-border,#ccc);border-radius:4px;background:var(--omni-button-bg,#eee);color:inherit;cursor:pointer;padding:7px 10px}.omni-safetensors button:hover{background:var(--omni-hover,#e5e5e5)}.omni-safetensors button[aria-pressed=true]{background:var(--omni-accent,#0e639c);color:var(--omni-accent-fg,#fff)}.omni-safetensors button:disabled{opacity:.5;cursor:default}.omni-safetensors__warnings{padding:10px 14px;border-bottom:1px solid var(--omni-border,#ddd);background:var(--omni-warning-bg,#fff4ce);color:var(--omni-warning-fg,#5f4700)}.omni-safetensors__content{min-height:0;flex:1;overflow:auto}.omni-safetensors__panel-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-bottom:1px solid var(--omni-border,#ddd);background:var(--omni-bg,#fff)}.omni-safetensors__panel-header h2{margin:0;font-size:15px}.omni-safetensors__panel-header span{color:var(--omni-muted,#666);font-size:12px}.omni-safetensors__table-wrap{overflow:auto}.omni-safetensors table{width:100%;border-collapse:collapse}.omni-safetensors th,.omni-safetensors td{border-bottom:1px solid var(--omni-border,#ddd);padding:7px 9px;text-align:left;vertical-align:top;white-space:nowrap}.omni-safetensors th{background:var(--omni-header-bg,#f5f5f5);font-weight:650}.omni-safetensors td{max-width:520px;overflow:hidden;text-overflow:ellipsis}.omni-safetensors tr:hover td{background:var(--omni-hover,#f4f4f4)}.omni-safetensors pre{margin:0;padding:14px;font:13px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap}.omni-safetensors__empty{padding:24px;color:var(--omni-muted,#666)}
3
+ @media(max-width:720px){.omni-safetensors__header,.omni-safetensors__toolbar{align-items:stretch;flex-direction:column}.omni-safetensors__search{width:auto}}
@@ -1 +1 @@
1
- :host,.omni-viewer--toml,.omni-viewer--yaml{display:block;height:100%;color:var(--omni-fg,#ddd);background:var(--omni-bg,#1e1e1e);font:13px var(--omni-font,system-ui)}.omni-structured{height:100%;display:flex;flex-direction:column;box-sizing:border-box}.omni-structured__bar{display:flex;gap:5px;align-items:center;padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured button,.omni-structured input,.omni-structured select{font:inherit;background:var(--omni-button-bg,#333);color:inherit;border:1px solid var(--omni-border,#555);border-radius:3px;padding:3px 7px}.omni-structured button:disabled{opacity:.5}.omni-structured__search{min-width:180px}.omni-structured__diagnostics{white-space:pre-wrap;color:var(--omni-error,#f48771);padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__workspace{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:minmax(0,1fr);gap:12px;flex:1;min-height:0;padding:12px}.omni-structured__pane{display:flex;flex-direction:column;min-width:0;min-height:0;border:1px solid var(--omni-border,#444);border-radius:8px;overflow:hidden;background:var(--omni-input-bg,#171717)}.omni-structured__pane-header{display:flex;justify-content:space-between;gap:12px;padding:10px 12px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__pane-header span{color:var(--omni-fg-muted,#999)}.omni-structured__editor,.omni-structured__tree{box-sizing:border-box;flex:1;min-height:0;padding:10px;background:transparent;color:inherit;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace)}.omni-structured__editor{resize:none;border:0;outline:none;line-height:1.5;white-space:pre;overflow:auto}.omni-structured__source-surface{position:relative;flex:1;min-height:0;overflow:hidden}.omni-structured__source-highlight{position:absolute;inset:0;box-sizing:border-box;margin:0;padding:10px;overflow:auto;pointer-events:none;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5;white-space:pre}.omni-structured__pane--highlighted-source .omni-structured__editor{position:relative;z-index:1;width:100%;height:100%;padding:10px;color:transparent;caret-color:var(--omni-fg,#ddd);-webkit-text-fill-color:transparent}.omni-structured__source-key{color:#9cdcfe}.omni-structured__source-value{color:#ce9178}.omni-structured__source-table{color:#c586c0}.omni-structured__source-comment{color:var(--omni-fg-muted,#999)}.omni-structured__tree{overflow:auto}.omni-structured__preview-body{position:relative;flex:1;min-height:200px}.omni-structured__preview-body>.omni-structured__tree,.omni-structured__preview-body>.omni-structured__json-preview,.omni-structured__preview-body>.omni-structured__raw-preview{position:absolute;inset:0}.omni-structured__node{display:flex;gap:6px;align-items:baseline;min-height:25px}.omni-structured__toggle{width:24px;padding:0!important;border:0!important;background:transparent!important}.omni-structured__key{color:var(--omni-accent,#5aa9e6)}.omni-structured__kind{color:var(--omni-fg-muted,#999)}.omni-structured__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__actions{margin-left:auto;display:flex;gap:4px;opacity:0}.omni-structured__node:hover .omni-structured__actions,.omni-structured__actions:focus-within{opacity:1}.omni-structured__raw-preview,.omni-structured__json-preview{flex:1;min-height:0;margin:0;padding:10px;overflow:auto;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5}.omni-structured__flat-row .omni-structured__key{flex-shrink:0}@media(max-width:720px){.omni-structured__workspace{grid-template-columns:1fr;overflow:auto}.omni-structured__pane{min-height:320px}}
1
+ :host,.omni-viewer--toml,.omni-viewer--yaml{display:block;height:100%;color:var(--omni-fg,#ddd);background:var(--omni-bg,#1e1e1e);font:13px var(--omni-font,system-ui)}.omni-structured{height:100%;display:flex;flex-direction:column;box-sizing:border-box}.omni-structured__bar{display:flex;gap:5px;align-items:center;padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured button,.omni-structured input,.omni-structured select{font:inherit;background:var(--omni-button-bg,#333);color:inherit;border:1px solid var(--omni-border,#555);border-radius:3px;padding:3px 7px}.omni-structured button:disabled{opacity:.5}.omni-structured__search{min-width:180px}.omni-structured__diagnostics{white-space:pre-wrap;color:var(--omni-error,#f48771);padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__workspace{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:minmax(0,1fr);gap:12px;flex:1;min-height:0;padding:12px}.omni-structured__pane{display:flex;flex-direction:column;min-width:0;min-height:0;border:1px solid var(--omni-border,#444);border-radius:8px;overflow:hidden;background:var(--omni-input-bg,#171717)}.omni-structured__pane-header{display:flex;justify-content:space-between;gap:12px;padding:10px 12px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__pane-header span{color:var(--omni-fg-muted,#999)}.omni-structured__editor,.omni-structured__tree{box-sizing:border-box;flex:1;min-height:0;padding:10px;background:transparent;color:inherit;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace)}.omni-structured__editor{resize:none;border:0;outline:none;line-height:1.5;white-space:pre;overflow:auto}.omni-structured__source-surface{position:relative;flex:1;min-height:0;overflow:hidden}.omni-structured__source-highlight{position:absolute;inset:0;box-sizing:border-box;margin:0;padding:10px;overflow:auto;pointer-events:none;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5;white-space:pre}.omni-structured__pane--highlighted-source .omni-structured__editor{position:relative;z-index:1;width:100%;height:100%;padding:10px;color:transparent;caret-color:var(--omni-fg,#ddd);-webkit-text-fill-color:transparent}.omni-structured__source-key{color:#9cdcfe}.omni-structured__source-value{color:#ce9178}.omni-structured__source-table{color:#c586c0}.omni-structured__source-comment{color:var(--omni-fg-muted,#999)}.omni-structured__tree{overflow:auto}.omni-structured__preview-body{position:relative;flex:1;min-height:200px}.omni-structured__preview-body>.omni-structured__tree,.omni-structured__preview-body>.omni-structured__json-preview,.omni-structured__preview-body>.omni-structured__raw-preview{position:absolute;inset:0}.omni-structured__node{display:flex;gap:6px;align-items:baseline;min-height:25px}.omni-structured__toggle{width:24px;padding:0!important;border:0!important;background:transparent!important}.omni-structured__key{color:var(--omni-accent,#5aa9e6)}.omni-structured__kind{color:var(--omni-fg-muted,#999)}.omni-structured__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__actions{margin-left:auto;display:flex;gap:4px;opacity:0}.omni-structured__node:hover .omni-structured__actions,.omni-structured__actions:focus-within{opacity:1}.omni-structured__raw-preview,.omni-structured__json-preview{flex:1;min-height:0;margin:0;padding:10px;overflow:auto;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5}.omni-structured__flat-row .omni-structured__key{flex-shrink:0}.omni-structured__status{margin-left:auto;color:var(--omni-fg-muted,#999);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__node-comment{color:var(--omni-fg-muted,#999);font-style:italic;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__node--interactive{cursor:pointer;border-radius:3px}.omni-structured__node--interactive:hover{background:var(--omni-hover-bg,#2a2a2a)}.omni-structured__node--selected{background:var(--omni-selection-bg,#0a3a5e);outline:1px solid var(--omni-accent,#5aa9e6)}@media(max-width:720px){.omni-structured__workspace{grid-template-columns:1fr;overflow:auto}.omni-structured__pane{min-height:320px}}
@@ -1 +1 @@
1
- :host,.omni-viewer--toml,.omni-viewer--yaml{display:block;height:100%;color:var(--omni-fg,#ddd);background:var(--omni-bg,#1e1e1e);font:13px var(--omni-font,system-ui)}.omni-structured{height:100%;display:flex;flex-direction:column;box-sizing:border-box}.omni-structured__bar{display:flex;gap:5px;align-items:center;padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured button,.omni-structured input,.omni-structured select{font:inherit;background:var(--omni-button-bg,#333);color:inherit;border:1px solid var(--omni-border,#555);border-radius:3px;padding:3px 7px}.omni-structured button:disabled{opacity:.5}.omni-structured__search{min-width:180px}.omni-structured__diagnostics{white-space:pre-wrap;color:var(--omni-error,#f48771);padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__workspace{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:minmax(0,1fr);gap:12px;flex:1;min-height:0;padding:12px}.omni-structured__pane{display:flex;flex-direction:column;min-width:0;min-height:0;border:1px solid var(--omni-border,#444);border-radius:8px;overflow:hidden;background:var(--omni-input-bg,#171717)}.omni-structured__pane-header{display:flex;justify-content:space-between;gap:12px;padding:10px 12px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__pane-header span{color:var(--omni-fg-muted,#999)}.omni-structured__editor,.omni-structured__tree{box-sizing:border-box;flex:1;min-height:0;padding:10px;background:transparent;color:inherit;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace)}.omni-structured__editor{resize:none;border:0;outline:none;line-height:1.5;white-space:pre;overflow:auto}.omni-structured__source-surface{position:relative;flex:1;min-height:0;overflow:hidden}.omni-structured__source-highlight{position:absolute;inset:0;box-sizing:border-box;margin:0;padding:10px;overflow:auto;pointer-events:none;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5;white-space:pre}.omni-structured__pane--highlighted-source .omni-structured__editor{position:relative;z-index:1;width:100%;height:100%;padding:10px;color:transparent;caret-color:var(--omni-fg,#ddd);-webkit-text-fill-color:transparent}.omni-structured__source-key{color:#9cdcfe}.omni-structured__source-value{color:#ce9178}.omni-structured__source-table{color:#c586c0}.omni-structured__source-comment{color:var(--omni-fg-muted,#999)}.omni-structured__tree{overflow:auto}.omni-structured__preview-body{position:relative;flex:1;min-height:200px}.omni-structured__preview-body>.omni-structured__tree,.omni-structured__preview-body>.omni-structured__json-preview,.omni-structured__preview-body>.omni-structured__raw-preview{position:absolute;inset:0}.omni-structured__node{display:flex;gap:6px;align-items:baseline;min-height:25px}.omni-structured__toggle{width:24px;padding:0!important;border:0!important;background:transparent!important}.omni-structured__key{color:var(--omni-accent,#5aa9e6)}.omni-structured__kind{color:var(--omni-fg-muted,#999)}.omni-structured__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__actions{margin-left:auto;display:flex;gap:4px;opacity:0}.omni-structured__node:hover .omni-structured__actions,.omni-structured__actions:focus-within{opacity:1}.omni-structured__raw-preview,.omni-structured__json-preview{flex:1;min-height:0;margin:0;padding:10px;overflow:auto;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5}.omni-structured__flat-row .omni-structured__key{flex-shrink:0}@media(max-width:720px){.omni-structured__workspace{grid-template-columns:1fr;overflow:auto}.omni-structured__pane{min-height:320px}}
1
+ :host,.omni-viewer--toml,.omni-viewer--yaml{display:block;height:100%;color:var(--omni-fg,#ddd);background:var(--omni-bg,#1e1e1e);font:13px var(--omni-font,system-ui)}.omni-structured{height:100%;display:flex;flex-direction:column;box-sizing:border-box}.omni-structured__bar{display:flex;gap:5px;align-items:center;padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured button,.omni-structured input,.omni-structured select{font:inherit;background:var(--omni-button-bg,#333);color:inherit;border:1px solid var(--omni-border,#555);border-radius:3px;padding:3px 7px}.omni-structured button:disabled{opacity:.5}.omni-structured__search{min-width:180px}.omni-structured__diagnostics{white-space:pre-wrap;color:var(--omni-error,#f48771);padding:6px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__workspace{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:minmax(0,1fr);gap:12px;flex:1;min-height:0;padding:12px}.omni-structured__pane{display:flex;flex-direction:column;min-width:0;min-height:0;border:1px solid var(--omni-border,#444);border-radius:8px;overflow:hidden;background:var(--omni-input-bg,#171717)}.omni-structured__pane-header{display:flex;justify-content:space-between;gap:12px;padding:10px 12px;border-bottom:1px solid var(--omni-border,#444)}.omni-structured__pane-header span{color:var(--omni-fg-muted,#999)}.omni-structured__editor,.omni-structured__tree{box-sizing:border-box;flex:1;min-height:0;padding:10px;background:transparent;color:inherit;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace)}.omni-structured__editor{resize:none;border:0;outline:none;line-height:1.5;white-space:pre;overflow:auto}.omni-structured__source-surface{position:relative;flex:1;min-height:0;overflow:hidden}.omni-structured__source-highlight{position:absolute;inset:0;box-sizing:border-box;margin:0;padding:10px;overflow:auto;pointer-events:none;font:inherit;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5;white-space:pre}.omni-structured__pane--highlighted-source .omni-structured__editor{position:relative;z-index:1;width:100%;height:100%;padding:10px;color:transparent;caret-color:var(--omni-fg,#ddd);-webkit-text-fill-color:transparent}.omni-structured__source-key{color:#9cdcfe}.omni-structured__source-value{color:#ce9178}.omni-structured__source-table{color:#c586c0}.omni-structured__source-comment{color:var(--omni-fg-muted,#999)}.omni-structured__tree{overflow:auto}.omni-structured__preview-body{position:relative;flex:1;min-height:200px}.omni-structured__preview-body>.omni-structured__tree,.omni-structured__preview-body>.omni-structured__json-preview,.omni-structured__preview-body>.omni-structured__raw-preview{position:absolute;inset:0}.omni-structured__node{display:flex;gap:6px;align-items:baseline;min-height:25px}.omni-structured__toggle{width:24px;padding:0!important;border:0!important;background:transparent!important}.omni-structured__key{color:var(--omni-accent,#5aa9e6)}.omni-structured__kind{color:var(--omni-fg-muted,#999)}.omni-structured__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__actions{margin-left:auto;display:flex;gap:4px;opacity:0}.omni-structured__node:hover .omni-structured__actions,.omni-structured__actions:focus-within{opacity:1}.omni-structured__raw-preview,.omni-structured__json-preview{flex:1;min-height:0;margin:0;padding:10px;overflow:auto;font-family:var(--omni-font-mono,ui-monospace,monospace);line-height:1.5}.omni-structured__flat-row .omni-structured__key{flex-shrink:0}.omni-structured__status{margin-left:auto;color:var(--omni-fg-muted,#999);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__node-comment{color:var(--omni-fg-muted,#999);font-style:italic;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.omni-structured__node--interactive{cursor:pointer;border-radius:3px}.omni-structured__node--interactive:hover{background:var(--omni-hover-bg,#2a2a2a)}.omni-structured__node--selected{background:var(--omni-selection-bg,#0a3a5e);outline:1px solid var(--omni-accent,#5aa9e6)}@media(max-width:720px){.omni-structured__workspace{grid-template-columns:1fr;overflow:auto}.omni-structured__pane{min-height:320px}}
@@ -46,6 +46,8 @@ export interface PdfHighlightAnnotation {
46
46
  /** A text selection can span several lines -> several rects. */
47
47
  rects: PdfHighlightRect[];
48
48
  color: string;
49
+ /** The plain text under the selection, kept for copy and the markup list. */
50
+ text?: string;
49
51
  }
50
52
  /** A text selection rendered as a horizontal strike through each line. */
51
53
  export interface PdfStrikeoutAnnotation {
@@ -54,6 +56,7 @@ export interface PdfStrikeoutAnnotation {
54
56
  page: number;
55
57
  rects: PdfHighlightRect[];
56
58
  color: string;
59
+ text?: string;
57
60
  }
58
61
  /** A text selection rendered as an underline beneath each line. */
59
62
  export interface PdfUnderlineAnnotation {
@@ -62,6 +65,7 @@ export interface PdfUnderlineAnnotation {
62
65
  page: number;
63
66
  rects: PdfHighlightRect[];
64
67
  color: string;
68
+ text?: string;
65
69
  }
66
70
  export type PdfAnnotation = PdfTextAnnotation | PdfSignatureAnnotation | PdfHighlightAnnotation | PdfStrikeoutAnnotation | PdfUnderlineAnnotation;
67
71
  export type PdfAnnotationInput = Omit<PdfTextAnnotation, 'id'> | Omit<PdfSignatureAnnotation, 'id'> | Omit<PdfHighlightAnnotation, 'id'> | Omit<PdfStrikeoutAnnotation, 'id'> | Omit<PdfUnderlineAnnotation, 'id'>;
@@ -96,6 +100,10 @@ export type PdfAction = {
96
100
  id: string;
97
101
  x: number;
98
102
  y: number;
103
+ } | {
104
+ type: 'set-annotation-color';
105
+ id: string;
106
+ color: string;
99
107
  } | {
100
108
  type: 'remove-annotation';
101
109
  id: string;
@@ -138,6 +138,7 @@ export function createPdfController(pageCount, seed, options = {}) {
138
138
  || action.type === 'reset-pages'
139
139
  || action.type === 'add-annotation'
140
140
  || action.type === 'move-annotation'
141
+ || action.type === 'set-annotation-color'
141
142
  || action.type === 'remove-annotation';
142
143
  const before = tracksHistory ? snapshot() : undefined;
143
144
  const beforeSignature = tracksHistory ? signature() : undefined;
@@ -187,6 +188,9 @@ export function createPdfController(pageCount, seed, options = {}) {
187
188
  case 'move-annotation':
188
189
  annotations = annotations.map((a) => a.id === action.id ? { ...a, x: action.x, y: action.y } : a);
189
190
  break;
191
+ case 'set-annotation-color':
192
+ annotations = annotations.map((a) => a.id === action.id ? { ...a, color: action.color } : a);
193
+ break;
190
194
  case 'remove-annotation':
191
195
  annotations = annotations.filter((a) => a.id !== action.id);
192
196
  if (selectedAnnotationId === action.id)
@@ -8,6 +8,17 @@ export interface ParsedLayer {
8
8
  pageOrder: number[];
9
9
  annotations: PdfAnnotation[];
10
10
  }
11
+ /**
12
+ * PDF-space Y for a strike-through / underline drawn over a text rect.
13
+ * `rect.y` is the box top measured from the page top (top-left origin, as the
14
+ * DOM reports it); PDF space has a bottom-left origin, so we flip through the
15
+ * page height. Strikeout sits at the vertical middle; underline sits just above
16
+ * the bottom edge to mirror the on-screen `bottom: 8%` rendering.
17
+ */
18
+ export declare function markupLineY(kind: 'strikeout' | 'underline', rect: {
19
+ y: number;
20
+ height: number;
21
+ }, pageHeight: number): number;
11
22
  /** Persist reordered/deleted pages plus the text/signature overlays,
12
23
  * flattened into the page content (portable, non-editable output). */
13
24
  export declare function buildEditedPdf(pdfLib: PdfLibModule, source: Uint8Array, state: PdfViewState): Promise<Uint8Array>;