@prose-reader/enhancer-annotations 1.222.0 → 1.223.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.
@@ -6,8 +6,8 @@
6
6
 
7
7
  class Commands extends core.DestroyableClass {
8
8
  commandSubject = new rxjs.Subject();
9
- highlight$ = this.commandSubject.pipe(
10
- rxjs.filter((command) => command.type === "highlight")
9
+ annotate$ = this.commandSubject.pipe(
10
+ rxjs.filter((command) => command.type === "annotate")
11
11
  );
12
12
  add$ = this.commandSubject.pipe(
13
13
  rxjs.filter((command) => command.type === "add")
@@ -24,8 +24,11 @@
24
24
  reset$ = this.commandSubject.pipe(
25
25
  rxjs.filter((command) => command.type === "reset")
26
26
  );
27
- highlight = (params) => {
28
- this.commandSubject.next({ type: "highlight", data: params });
27
+ annotate = (params) => {
28
+ this.commandSubject.next({ type: "annotate", data: params });
29
+ };
30
+ annotateAbsolutePage = (params) => {
31
+ this.commandSubject.next({ type: "annotate", data: params });
29
32
  };
30
33
  add = (data) => {
31
34
  this.commandSubject.next({ type: "add", data });
@@ -48,44 +51,6 @@
48
51
  }
49
52
  }
50
53
 
51
- class ProseHighlight {
52
- cfi;
53
- endCfi;
54
- itemIndex;
55
- color;
56
- contents;
57
- range;
58
- selectionAsText;
59
- /**
60
- * Unique local ID. This is to ensure unicity
61
- * for duplicate selections
62
- */
63
- id;
64
- constructor(params) {
65
- this.cfi = params.cfi;
66
- this.endCfi = params.endCfi;
67
- this.itemIndex = params.itemIndex;
68
- this.color = params.color;
69
- this.contents = params.contents;
70
- this.id = params.id;
71
- this.selectionAsText = params.selectionAsText;
72
- }
73
- update(params) {
74
- Object.assign(this, params);
75
- return this;
76
- }
77
- toJSON() {
78
- return {
79
- cfi: this.cfi,
80
- endCfi: this.endCfi,
81
- color: this.color,
82
- contents: this.contents,
83
- id: this.id,
84
- selectionAsText: this.selectionAsText
85
- };
86
- }
87
- }
88
-
89
54
  const createElementForRange = (range, container, color) => {
90
55
  const rects = Array.from(range.getClientRects());
91
56
  const lineGroups = /* @__PURE__ */ new Map();
@@ -158,7 +123,10 @@
158
123
  rxjs.map((event) => ({ event, highlight: this.highlight })),
159
124
  rxjs.share()
160
125
  );
161
- this.render();
126
+ this.resolvedCfi$ = this.spineItem.loaded$.pipe(
127
+ rxjs.map(() => this.reader.cfi.resolveCfi({ cfi: this.highlight.cfi })),
128
+ rxjs.shareReplay({ refCount: true, bufferSize: 1 })
129
+ );
162
130
  this.isSelected.pipe(
163
131
  rxjs.skip(1),
164
132
  rxjs.tap((isSelected2) => {
@@ -170,38 +138,47 @@
170
138
  }),
171
139
  rxjs.takeUntil(this.destroy$)
172
140
  ).subscribe();
141
+ rxjs.merge(this.resolvedCfi$).pipe(rxjs.takeUntil(this.destroy$)).subscribe();
173
142
  }
174
143
  container;
175
144
  tap$;
145
+ resolvedCfi$;
176
146
  render() {
177
- this.container.innerHTML = "";
178
- const range = this.highlight.range;
179
- if (!range) {
180
- return;
181
- }
182
- const rectElements = createElementForRange(
183
- range,
184
- this.container,
185
- this.highlight.color ?? "yellow"
147
+ return this.resolvedCfi$.pipe(
148
+ rxjs.first(),
149
+ rxjs.map((resolvedCfi) => {
150
+ if (!resolvedCfi || !resolvedCfi.isRange) return void 0;
151
+ const range = resolvedCfi.range;
152
+ this.container.innerHTML = "";
153
+ if (!range) {
154
+ return;
155
+ }
156
+ const rectElements = createElementForRange(
157
+ range,
158
+ this.container,
159
+ this.highlight.highlightColor ?? "yellow"
160
+ );
161
+ rectElements.forEach((elt) => {
162
+ elt.style.pointerEvents = "initial";
163
+ elt.style.cursor = "pointer";
164
+ elt.dataset.highlightRect = this.highlight.id;
165
+ this.container.appendChild(elt);
166
+ });
167
+ const firstElement = rectElements[0];
168
+ if (firstElement && this.highlight.notes) {
169
+ const noteIcon = document.createElement("span");
170
+ noteIcon.textContent = "📝";
171
+ noteIcon.style.position = "absolute";
172
+ noteIcon.style.top = "0";
173
+ noteIcon.style.left = "0";
174
+ noteIcon.style.transform = "translate(-0%, -80%)";
175
+ noteIcon.style.fontSize = "18px";
176
+ noteIcon.style.opacity = "50%";
177
+ firstElement.appendChild(noteIcon);
178
+ }
179
+ return null;
180
+ })
186
181
  );
187
- rectElements.forEach((elt) => {
188
- elt.style.pointerEvents = "initial";
189
- elt.style.cursor = "pointer";
190
- elt.dataset.highlightRect = this.highlight.id;
191
- this.container.appendChild(elt);
192
- });
193
- const firstElement = rectElements[0];
194
- if (firstElement && this.highlight.contents?.length) {
195
- const noteIcon = document.createElement("span");
196
- noteIcon.textContent = "📝";
197
- noteIcon.style.position = "absolute";
198
- noteIcon.style.top = "0";
199
- noteIcon.style.left = "0";
200
- noteIcon.style.transform = "translate(-0%, -80%)";
201
- noteIcon.style.fontSize = "18px";
202
- noteIcon.style.opacity = "50%";
203
- firstElement.appendChild(noteIcon);
204
- }
205
182
  }
206
183
  isWithinTarget(target) {
207
184
  return this.container.contains(target);
@@ -213,9 +190,9 @@
213
190
  }
214
191
 
215
192
  class SpineItemHighlights extends core.DestroyableClass {
216
- constructor(highlights$, spineItem, reader, selectedHighlight) {
193
+ constructor(annotations$, spineItem, reader, selectedHighlight) {
217
194
  super();
218
- this.highlights$ = highlights$;
195
+ this.annotations$ = annotations$;
219
196
  this.spineItem = spineItem;
220
197
  this.reader = reader;
221
198
  this.selectedHighlight = selectedHighlight;
@@ -224,7 +201,7 @@
224
201
  this.spineItem.containerElement,
225
202
  firstLayerElement
226
203
  );
227
- const itemHighlights$ = this.highlights$.pipe(
204
+ const itemHighlights$ = this.annotations$.pipe(
228
205
  rxjs.switchMap((annotations) => {
229
206
  this.highlights.forEach((highlight) => highlight.destroy());
230
207
  this.highlights = [];
@@ -253,7 +230,7 @@
253
230
  (highlights) => rxjs.merge(...highlights.map((highlight) => highlight.tap$))
254
231
  )
255
232
  );
256
- highlights$.subscribe();
233
+ annotations$.subscribe();
257
234
  }
258
235
  layer;
259
236
  highlights = [];
@@ -261,7 +238,9 @@
261
238
  layout() {
262
239
  const firstLayerElement = this.spineItem.renderer.documentContainer ?? document.createElement("div");
263
240
  layoutAnnotationLayer(firstLayerElement, this.layer);
264
- this.highlights.forEach((highlight) => highlight.render());
241
+ return rxjs.forkJoin(
242
+ this.highlights.map((highlight) => highlight.render())
243
+ ).pipe(rxjs.defaultIfEmpty(null));
265
244
  }
266
245
  getHighlightsForTarget(target) {
267
246
  return this.highlights.filter(
@@ -323,97 +302,97 @@
323
302
  return !!this.getHighlightsForTarget(target).length;
324
303
  };
325
304
  layout() {
326
- this.spineItemHighlights.value.forEach((item) => item.layout());
305
+ return rxjs.forkJoin(
306
+ this.spineItemHighlights.value.map((item) => item.layout())
307
+ ).pipe(rxjs.defaultIfEmpty(null));
327
308
  }
328
309
  }
329
310
 
330
- const consolidate = (highlight, reader) => {
331
- const { itemIndex } = reader.cfi.parseCfi(highlight.cfi ?? "");
332
- const spineItem = reader.spineItemsManager.get(itemIndex);
333
- if (!spineItem) return rxjs.of(highlight);
334
- return rxjs.of(highlight).pipe(
335
- rxjs.withLatestFrom(spineItem.isReady$),
336
- rxjs.tap(([, isItemReady]) => {
337
- const startCfi = reader.cfi.resolveCfi({ cfi: highlight.cfi ?? "" });
338
- const resolvedFocusCfi = reader.cfi.resolveCfi({
339
- cfi: highlight.endCfi ?? ""
340
- });
341
- if (startCfi?.node && resolvedFocusCfi?.node && isItemReady) {
342
- const range = startCfi?.node.ownerDocument?.createRange();
343
- range?.setStart(startCfi?.node, startCfi.offset ?? 0);
344
- range?.setEnd(resolvedFocusCfi?.node, resolvedFocusCfi.offset ?? 0);
345
- highlight.range = range;
346
- highlight.selectionAsText = range?.toString();
347
- } else {
348
- highlight.range = void 0;
349
- }
350
- }),
351
- rxjs.map(() => highlight)
352
- );
353
- };
354
-
355
311
  const d = () => {
356
312
  if (!(typeof window > "u"))
357
313
  return window;
358
314
  };
359
- function E() {
315
+ function h() {
360
316
  var n;
361
317
  const e = (n = d()) == null ? void 0 : n.__PROSE_READER_DEBUG;
362
318
  return e === true || e === "true";
363
319
  }
364
- const i = (e) => `[${e}]`, s = (e, n = E()) => ({
365
- namespace: (t, o) => s(`${e} ${t}`, o),
366
- debug: n ? e ? Function.prototype.bind.call(console.debug, console, i(e)) : Function.prototype.bind.call(console.debug, console) : () => {
320
+ const u = (e, n = h(), t) => ({
321
+ namespace: (o, l) => u(`[${e}] [${o}]`, l, t),
322
+ debug: n ? e ? Function.prototype.bind.call(console.debug, console, e) : Function.prototype.bind.call(console.debug, console) : () => {
367
323
  },
368
- info: n ? e ? Function.prototype.bind.call(console.info, console, i(e)) : Function.prototype.bind.call(console.info, console) : () => {
324
+ info: n ? e ? Function.prototype.bind.call(
325
+ console.info,
326
+ console,
327
+ `%c${e}`,
328
+ t != null && t.color ? `color: ${t.color}` : void 0
329
+ ) : Function.prototype.bind.call(console.info, console) : () => {
369
330
  },
370
- log: n ? e ? Function.prototype.bind.call(console.log, console, i(e)) : Function.prototype.bind.call(console.log, console) : () => {
331
+ log: n ? e ? Function.prototype.bind.call(console.log, console, e) : Function.prototype.bind.call(console.log, console) : () => {
371
332
  },
372
- warn: n ? e ? Function.prototype.bind.call(console.warn, console, i(e)) : Function.prototype.bind.call(console.warn, console) : () => {
333
+ warn: n ? e ? Function.prototype.bind.call(console.warn, console, e) : Function.prototype.bind.call(console.warn, console) : () => {
373
334
  },
374
- error: n ? e ? Function.prototype.bind.call(console.error, console, i(e)) : Function.prototype.bind.call(console.error, console) : () => {
335
+ error: n ? e ? Function.prototype.bind.call(console.error, console, e) : Function.prototype.bind.call(console.error, console) : () => {
375
336
  }
376
- }), b = {
377
- ...s(),
378
- namespace: (e, n) => s(e, n)
337
+ }), F = {
338
+ ...u(),
339
+ namespace: (e, n, t) => u(e, n, t)
379
340
  };
380
341
 
381
342
  const name = "@prose-reader/enhancer-annotations";
382
343
 
383
344
  const IS_DEBUG_ENABLED = true;
384
- const report = b.namespace(name, IS_DEBUG_ENABLED);
345
+ const report = F.namespace(name, IS_DEBUG_ENABLED);
385
346
 
386
347
  const annotationsEnhancer = (next) => (options) => {
387
348
  const reader = next(options);
388
349
  const commands = new Commands();
389
- const highlightsSubject = new rxjs.BehaviorSubject([]);
350
+ const annotationsSubject = new rxjs.BehaviorSubject([]);
390
351
  const selectedHighlightSubject = new rxjs.BehaviorSubject(
391
352
  void 0
392
353
  );
393
- const highlights$ = highlightsSubject.asObservable().pipe(rxjs.distinctUntilChanged());
354
+ const annotations$ = annotationsSubject.asObservable().pipe(
355
+ rxjs.distinctUntilChanged(),
356
+ rxjs.tap((annotations) => {
357
+ report.debug("annotations", annotations);
358
+ }),
359
+ rxjs.shareReplay({
360
+ refCount: true,
361
+ bufferSize: 1
362
+ })
363
+ );
394
364
  const readerHighlights = new ReaderHighlights(
395
365
  reader,
396
- highlightsSubject,
366
+ annotationsSubject,
397
367
  selectedHighlightSubject
398
368
  );
399
- const highlighted$ = commands.highlight$.pipe(
400
- rxjs.map(({ data: { itemIndex, selection, ...rest } }) => {
369
+ const resolveItemInformation = (params) => {
370
+ if (params.itemIndex !== void 0)
371
+ return { itemIndex: params.itemIndex, pageIndex: void 0 };
372
+ if (params.absolutePageIndex !== void 0) {
373
+ return reader.spine.locator.getSpineInfoFromAbsolutePageIndex({
374
+ absolutePageIndex: params.absolutePageIndex
375
+ });
376
+ }
377
+ return void 0;
378
+ };
379
+ const annotated$ = commands.annotate$.pipe(
380
+ rxjs.map(({ data: { selection, ...rest } }) => {
381
+ const { itemIndex, pageIndex = 0 } = resolveItemInformation(rest) ?? {};
401
382
  const spineItem = reader.spineItemsManager.get(itemIndex);
402
383
  if (!spineItem) return void 0;
403
- const range = reader.selection.createOrderedRangeFromSelection({
384
+ const range = selection ? reader.selection.createOrderedRangeFromSelection({
404
385
  selection,
405
386
  spineItem
406
- });
407
- if (!range) return void 0;
408
- const { start: startCfi, end: endCfi } = reader.cfi.generateCfiFromRange(range, spineItem.item);
409
- const highlight = new ProseHighlight({
410
- cfi: startCfi,
411
- endCfi,
412
- itemIndex,
387
+ }) : void 0;
388
+ const cfi = range ? reader.cfi.generateCfiFromRange(range, spineItem.item) : reader.cfi.generateCfiForSpineItemPage({ pageIndex, spineItem });
389
+ const highlight = {
390
+ cfi,
391
+ itemIndex: spineItem.index,
413
392
  id: window.crypto.randomUUID(),
414
393
  ...rest
415
- });
416
- highlightsSubject.next([...highlightsSubject.getValue(), highlight]);
394
+ };
395
+ annotationsSubject.next([...annotationsSubject.getValue(), highlight]);
417
396
  return [highlight.id];
418
397
  }),
419
398
  rxjs.filter(reactjrx.isDefined),
@@ -423,28 +402,31 @@
423
402
  rxjs.map(({ data }) => {
424
403
  const annotations = Array.isArray(data) ? data : [data];
425
404
  const addedHighlights = annotations.map((annotation) => {
426
- const { itemIndex } = reader.cfi.parseCfi(annotation.cfi ?? "");
427
- const highlight = new ProseHighlight({ ...annotation, itemIndex });
405
+ const { itemIndex = 0 } = reader.cfi.parseCfi(annotation.cfi ?? "");
406
+ const highlight = {
407
+ ...annotation,
408
+ itemIndex
409
+ };
428
410
  return highlight;
429
411
  });
430
- highlightsSubject.next([
431
- ...highlightsSubject.getValue(),
412
+ annotationsSubject.next([
413
+ ...annotationsSubject.getValue(),
432
414
  ...addedHighlights
433
415
  ]);
434
416
  })
435
417
  );
436
418
  const delete$ = commands.delete$.pipe(
437
419
  rxjs.tap(({ id }) => {
438
- highlightsSubject.next(
439
- highlightsSubject.getValue().filter((highlight) => highlight.id !== id)
420
+ annotationsSubject.next(
421
+ annotationsSubject.getValue().filter((highlight) => highlight.id !== id)
440
422
  );
441
423
  })
442
424
  );
443
425
  const update$ = commands.update$.pipe(
444
426
  rxjs.tap(({ id, data }) => {
445
- highlightsSubject.next(
446
- highlightsSubject.getValue().map((highlight) => {
447
- return highlight.id === id ? highlight.update(data) : highlight;
427
+ annotationsSubject.next(
428
+ annotationsSubject.getValue().map((highlight) => {
429
+ return highlight.id === id ? { ...highlight, ...data } : highlight;
448
430
  })
449
431
  );
450
432
  })
@@ -456,55 +438,52 @@
456
438
  );
457
439
  const reset$ = commands.reset$.pipe(
458
440
  rxjs.tap(() => {
459
- highlightsSubject.next([]);
441
+ annotationsSubject.next([]);
460
442
  })
461
443
  );
462
- const highlightsConsolidation$ = rxjs.merge(highlighted$, reader.layout$).pipe(
444
+ const renderAnnotations$ = rxjs.merge(annotations$, reader.layout$).pipe(
463
445
  rxjs.debounceTime(50),
464
- rxjs.withLatestFrom(highlights$),
465
- rxjs.mergeMap(
466
- () => rxjs.forkJoin(
467
- highlightsSubject.value.map(
468
- (highlight) => consolidate(highlight, reader)
469
- )
446
+ rxjs.switchMap(() => readerHighlights.layout())
447
+ );
448
+ const candidates$ = reader.layoutInfo$.pipe(
449
+ rxjs.switchMap(
450
+ ({ pages }) => rxjs.combineLatest(
451
+ pages.map((page) => {
452
+ const item = reader.spineItemsManager.get(page.itemIndex);
453
+ if (!item) return rxjs.of(false);
454
+ if (item.renditionLayout === "pre-paginated") return rxjs.of(true);
455
+ if (page.firstVisibleNode) return rxjs.of(true);
456
+ return rxjs.of(false);
457
+ })
470
458
  )
471
- ),
472
- rxjs.tap((consolidatedHighlights) => {
473
- const consolidatedExistingHighlights = highlightsSubject.value.map(
474
- (highlight) => consolidatedHighlights.find((c) => c.id === highlight.id) ?? highlight
475
- );
476
- highlightsSubject.next(consolidatedExistingHighlights);
477
- readerHighlights.layout();
478
- })
459
+ )
479
460
  );
480
461
  rxjs.merge(
481
- highlighted$,
462
+ annotated$,
482
463
  add$,
483
464
  delete$,
484
465
  update$,
485
466
  select$,
486
467
  reset$,
487
- highlightsConsolidation$,
488
- highlights$.pipe(
489
- rxjs.tap((annotations) => {
490
- report.debug("highlights", annotations);
491
- })
492
- )
468
+ renderAnnotations$,
469
+ annotations$
493
470
  ).pipe(rxjs.takeUntil(reader.$.destroy$)).subscribe();
494
471
  return {
495
472
  ...reader,
496
473
  __PROSE_READER_ENHANCER_ANNOTATIONS: true,
497
474
  destroy: () => {
498
- highlightsSubject.complete();
475
+ annotationsSubject.complete();
499
476
  commands.destroy();
500
477
  readerHighlights.destroy();
501
478
  reader.destroy();
502
479
  },
503
480
  annotations: {
504
- highlights$,
481
+ annotations$,
505
482
  highlightTap$: readerHighlights.tap$,
483
+ candidates$,
506
484
  isTargetWithinHighlight: readerHighlights.isTargetWithinHighlight,
507
- highlight: commands.highlight,
485
+ annotate: commands.annotate,
486
+ annotateAbsolutePage: commands.annotateAbsolutePage,
508
487
  add: commands.add,
509
488
  delete: commands.delete,
510
489
  update: commands.update,
@@ -514,7 +493,6 @@
514
493
  };
515
494
  };
516
495
 
517
- exports.ProseHighlight = ProseHighlight;
518
496
  exports.annotationsEnhancer = annotationsEnhancer;
519
497
 
520
498
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });