-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathminimap.ts
136 lines (130 loc) · 5.65 KB
/
minimap.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import renderMinimap from "./components";
import stylesheet from "./minimap.sass";
import { G } from "../global";
export function mountMinimap(reader: _ZoteroTypes.ReaderInstance) {
const doc = reader._iframeWindow!.document,
outerContainer = doc.getElementById('primary-view');
addon.ui.appendElement(
{
tag: 'style',
namespace: 'html',
properties: { textContent: stylesheet },
ignoreIfExists: true,
},
doc.head!
);
let timer: ReturnType<typeof setTimeout>;
const container = addon.ui.appendElement({
tag: 'div',
id: 'chartero-minimap-container',
listeners: [
{
type: 'mouseenter',
listener: () => timer = setTimeout(() => container.classList.add('hovered'), 500)
}, {
type: 'mouseleave',
listener: () => {
clearTimeout(timer);
container.classList.remove('hovered');
}
}
],
removeIfExists: true
}, outerContainer!) as HTMLDivElement;
updateMinimap(reader);
}
export function updateMinimap(reader: _ZoteroTypes.ReaderInstance) {
const doc = reader._iframeWindow!.document,
container = doc.getElementById('chartero-minimap-container');
if (!container || reader.type == 'snapshot') return;
// 共用的变量:总页数、背景色、注释
const numPages = reader._state.primaryViewStats.pagesCount!,
annotations = reader._state.annotations,
history = addon.history.getByAttachment(reader.itemID!),
seconds = history?.record.pageArr.map(p => p.totalS),
maxSeconds = seconds?.reduce((a, b) => Math.max(a ?? 0, b ?? 0), 0),
background = new Array<number>(numPages).fill(0).map((_, i) => {
let val = (history && maxSeconds) ?
200 * (1 - (history.record.pages[i]?.totalS ?? 0) / maxSeconds)
: 255;
// 暗黑模式下反色
if (Zotero.getMainWindow().matchMedia('(prefers-color-scheme: dark)')?.matches)
val = 255 - val;
return val;
}),
annArr: Array<AnnotationInfo[] | null> =
new Array<AnnotationInfo[]>(numPages);
if (reader.type == 'pdf') {
const view = reader._primaryView as _ZoteroTypes.Reader.PDFView,
pagesHeight = view._iframeWindow!.PDFViewerApplication.pdfViewer!
._pages!.map((p: any) => p.viewport.viewBox[3]);
for (const ann of annotations) {
const pos = ann.position as _ZoteroTypes.Reader.PDFPosition,
arr = annArr[pos.pageIndex] ??= [];
arr.push({
color: ann.color ?? 'transparent',
rects: ann.type == 'ink' ?
pos.paths!.map(path => {
// 寻找Path的边界
const y = path.filter((_, i) => i % 2 == 1);
return {
bottom: Math.min(...y),
top: Math.max(...y),
};
}) :
pos.rects?.map(r => ({
bottom: r[1],
top: r[3],
})) ?? [],
});
}
renderMinimap(container, { background, pagesHeight, annotations: annArr });
} else if (reader.type == 'epub') {
if (reader.flowMode == 'paginated') {
container.toggleAttribute('hidden', true);
return;
}
container.toggleAttribute('hidden', false);
const view = reader._primaryView as _ZoteroTypes.Reader.EPUBView,
totalHeight = (view as any)
._sectionsContainer.getBoundingClientRect().bottom,
mappingStr = (
reader._state.primaryViewState as _ZoteroTypes.Reader.EPUBViewState
).savedPageMapping,
mappingArr: Array<[cfi: string, idx: string]> =
JSON.parse(mappingStr!)?.mappings,
cfiArr = mappingArr?.sort((a, b) => parseInt(a[1]) - parseInt(b[1])),
ranges = cfiArr?.map(map => view.getRange('epubcfi(' + map[0] + ')')?.toRange()),
pagesHeight = ranges?.reduce((arr, range, i) => {
if (i == 0 || !range || !ranges[i - 1]) return arr;
return [...arr, range.getBoundingClientRect().y -
ranges[i - 1]!.getBoundingClientRect().y];
}, [] as number[]) ?? [];
// 相邻两页的差值
pagesHeight.push(totalHeight - (ranges?.at(-1)?.getBoundingClientRect().bottom ?? 0));
for (const ann of annotations) {
const pos = ann.position as _ZoteroTypes.Reader.FragmentSelector,
range = view.getRange(pos.value)?.toRange(),
rect = range?.getBoundingClientRect(),
// 寻找当前注释所在的页码
idx = ranges.findIndex(
r => r && r.compareBoundaryPoints(G('Range').END_TO_END, range!) > 0
) - 1,
pageRect = ranges.at(idx)?.getBoundingClientRect(),
arr = annArr[idx] ??= [];
// epub暂时不存在ink注释
arr.push({
color: ann.color ?? 'transparent',
rects: [{
bottom: (rect?.bottom ?? 0) - (pageRect?.top ?? 0),
top: (rect?.top ?? 0) - (pageRect?.top ?? 0),
}],
});
}
renderMinimap(container, { background, pagesHeight, annotations: annArr });
}
}
export interface AnnotationInfo {
color: string;
rects: Array<{ bottom: number, top: number }>;
}