apple-books-export 1.0.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.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/database.d.ts +37 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +187 -0
- package/dist/db-adapter.d.ts +36 -0
- package/dist/db-adapter.d.ts.map +1 -0
- package/dist/db-adapter.js +72 -0
- package/dist/exporters/csv.d.ts +6 -0
- package/dist/exporters/csv.d.ts.map +1 -0
- package/dist/exporters/csv.js +70 -0
- package/dist/exporters/html.d.ts +3 -0
- package/dist/exporters/html.d.ts.map +1 -0
- package/dist/exporters/html.js +775 -0
- package/dist/exporters/json.d.ts +6 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +103 -0
- package/dist/exporters/markdown.d.ts +14 -0
- package/dist/exporters/markdown.d.ts.map +1 -0
- package/dist/exporters/markdown.js +145 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +232 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
function formatDate(date) {
|
|
2
|
+
return date.toLocaleDateString('en-US', {
|
|
3
|
+
year: 'numeric',
|
|
4
|
+
month: 'long',
|
|
5
|
+
day: 'numeric',
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function escapeHtml(text) {
|
|
9
|
+
return text
|
|
10
|
+
.replace(/&/g, '&')
|
|
11
|
+
.replace(/</g, '<')
|
|
12
|
+
.replace(/>/g, '>')
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/'/g, ''');
|
|
15
|
+
}
|
|
16
|
+
function getColorClass(color) {
|
|
17
|
+
const colorMap = {
|
|
18
|
+
yellow: 'yellow',
|
|
19
|
+
green: 'green',
|
|
20
|
+
blue: 'blue',
|
|
21
|
+
pink: 'pink',
|
|
22
|
+
purple: 'purple',
|
|
23
|
+
underline: 'underline',
|
|
24
|
+
};
|
|
25
|
+
return colorMap[color] || 'yellow';
|
|
26
|
+
}
|
|
27
|
+
function generateBookId(book) {
|
|
28
|
+
return `book-${book.assetId}`;
|
|
29
|
+
}
|
|
30
|
+
function renderAnnotation(annotation) {
|
|
31
|
+
const colorClass = getColorClass(annotation.color);
|
|
32
|
+
let html = ` <div class="highlight">\n`;
|
|
33
|
+
html += ` <span class="color-dot ${colorClass}"></span>\n`;
|
|
34
|
+
html += ` <div class="highlight-content">\n`;
|
|
35
|
+
if (annotation.text) {
|
|
36
|
+
html += ` <p class="highlight-text">${escapeHtml(annotation.text)}</p>\n`;
|
|
37
|
+
}
|
|
38
|
+
if (annotation.note) {
|
|
39
|
+
html += ` <p class="note">${escapeHtml(annotation.note)}</p>\n`;
|
|
40
|
+
}
|
|
41
|
+
html += ` <div class="highlight-meta">\n`;
|
|
42
|
+
if (annotation.location) {
|
|
43
|
+
html += ` <span class="location">${escapeHtml(annotation.location)}</span>\n`;
|
|
44
|
+
}
|
|
45
|
+
html += ` <time>${formatDate(annotation.createdAt)}</time>\n`;
|
|
46
|
+
html += ` </div>\n`;
|
|
47
|
+
html += ` </div>\n`;
|
|
48
|
+
html += ` </div>\n`;
|
|
49
|
+
return html;
|
|
50
|
+
}
|
|
51
|
+
function renderBook(book) {
|
|
52
|
+
const bookId = generateBookId(book);
|
|
53
|
+
const title = book.title || 'Unknown Title';
|
|
54
|
+
const author = book.author || 'Unknown Author';
|
|
55
|
+
let html = ` <section id="${bookId}" class="book">\n`;
|
|
56
|
+
html += ` <div class="book-header">\n`;
|
|
57
|
+
html += ` <div class="book-info">\n`;
|
|
58
|
+
html += ` <h2>${escapeHtml(title)}</h2>\n`;
|
|
59
|
+
html += ` <p class="author">by ${escapeHtml(author)}</p>\n`;
|
|
60
|
+
html += ` </div>\n`;
|
|
61
|
+
html += ` <button class="collapse-btn" aria-label="Toggle highlights">\n`;
|
|
62
|
+
html += ` <svg class="chevron" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n`;
|
|
63
|
+
html += ` <polyline points="6 9 12 15 18 9"></polyline>\n`;
|
|
64
|
+
html += ` </svg>\n`;
|
|
65
|
+
html += ` </button>\n`;
|
|
66
|
+
html += ` </div>\n`;
|
|
67
|
+
html += ` <div class="highlights">\n`;
|
|
68
|
+
for (const annotation of book.annotations) {
|
|
69
|
+
html += renderAnnotation(annotation);
|
|
70
|
+
}
|
|
71
|
+
html += ` </div>\n`;
|
|
72
|
+
html += ` </section>\n\n`;
|
|
73
|
+
return html;
|
|
74
|
+
}
|
|
75
|
+
function renderBookIndex(books) {
|
|
76
|
+
let html = ` <aside class="sidebar">\n`;
|
|
77
|
+
html += ` <h3>Library</h3>\n`;
|
|
78
|
+
html += ` <ul class="book-list">\n`;
|
|
79
|
+
for (const book of books) {
|
|
80
|
+
const bookId = generateBookId(book);
|
|
81
|
+
const title = book.title || 'Unknown Title';
|
|
82
|
+
const count = book.annotations.length;
|
|
83
|
+
html += ` <li>\n`;
|
|
84
|
+
html += ` <a href="#${bookId}" class="book-link">\n`;
|
|
85
|
+
html += ` <span class="book-title">${escapeHtml(title)}</span>\n`;
|
|
86
|
+
html += ` <span class="book-count">${count}</span>\n`;
|
|
87
|
+
html += ` </a>\n`;
|
|
88
|
+
html += ` </li>\n`;
|
|
89
|
+
}
|
|
90
|
+
html += ` </ul>\n`;
|
|
91
|
+
html += ` </aside>\n\n`;
|
|
92
|
+
return html;
|
|
93
|
+
}
|
|
94
|
+
export function exportToHtml(books, outputPath) {
|
|
95
|
+
const totalHighlights = books.reduce((sum, book) => sum + book.annotations.length, 0);
|
|
96
|
+
const totalBooks = books.length;
|
|
97
|
+
const exportDate = new Date().toLocaleDateString('en-US', {
|
|
98
|
+
year: 'numeric',
|
|
99
|
+
month: 'short',
|
|
100
|
+
day: 'numeric',
|
|
101
|
+
});
|
|
102
|
+
let html = `<!DOCTYPE html>
|
|
103
|
+
<html lang="en">
|
|
104
|
+
<head>
|
|
105
|
+
<meta charset="UTF-8">
|
|
106
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
107
|
+
<title>Apple Books Highlights</title>
|
|
108
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
109
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
110
|
+
<link href="https://fonts.googleapis.com/css2?family=Crimson+Text:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
|
|
111
|
+
<style>
|
|
112
|
+
* {
|
|
113
|
+
box-sizing: border-box;
|
|
114
|
+
margin: 0;
|
|
115
|
+
padding: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
:root {
|
|
119
|
+
--bg-page: #faf8f5;
|
|
120
|
+
--bg-card: #ffffff;
|
|
121
|
+
--bg-sidebar: #f5f3f0;
|
|
122
|
+
--bg-hover: #f0ede8;
|
|
123
|
+
--text-primary: #2c2c2c;
|
|
124
|
+
--text-secondary: #6b6b6b;
|
|
125
|
+
--text-tertiary: #999999;
|
|
126
|
+
--accent-primary: #d4a574;
|
|
127
|
+
--border-color: #e8e5e0;
|
|
128
|
+
--dot-yellow: #ffc107;
|
|
129
|
+
--dot-green: #4caf50;
|
|
130
|
+
--dot-blue: #2196f3;
|
|
131
|
+
--dot-pink: #e91e63;
|
|
132
|
+
--dot-purple: #9c27b0;
|
|
133
|
+
--dot-underline: #757575;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
body {
|
|
137
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
138
|
+
font-size: 1.0625rem;
|
|
139
|
+
line-height: 1.7;
|
|
140
|
+
color: var(--text-primary);
|
|
141
|
+
background: var(--bg-page);
|
|
142
|
+
margin: 0;
|
|
143
|
+
padding: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Header */
|
|
147
|
+
.header {
|
|
148
|
+
position: sticky;
|
|
149
|
+
top: 0;
|
|
150
|
+
background: var(--bg-card);
|
|
151
|
+
border-bottom: 1px solid var(--border-color);
|
|
152
|
+
padding: 1.5rem 2rem;
|
|
153
|
+
z-index: 100;
|
|
154
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.header-content {
|
|
158
|
+
display: flex;
|
|
159
|
+
justify-content: space-between;
|
|
160
|
+
align-items: center;
|
|
161
|
+
margin-bottom: 1rem;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.header h1 {
|
|
165
|
+
font-family: 'Crimson Text', Georgia, serif;
|
|
166
|
+
font-size: 2.5rem;
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
color: var(--text-primary);
|
|
169
|
+
letter-spacing: -0.02em;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.header-actions {
|
|
173
|
+
display: flex;
|
|
174
|
+
gap: 0.75rem;
|
|
175
|
+
align-items: center;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.expand-all-btn {
|
|
179
|
+
padding: 0.5rem 1rem;
|
|
180
|
+
font-size: 0.9375rem;
|
|
181
|
+
font-family: inherit;
|
|
182
|
+
background: transparent;
|
|
183
|
+
border: 1px solid var(--border-color);
|
|
184
|
+
border-radius: 6px;
|
|
185
|
+
color: var(--text-secondary);
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
transition: all 0.2s ease;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.expand-all-btn:hover {
|
|
191
|
+
background: var(--bg-hover);
|
|
192
|
+
border-color: var(--accent-primary);
|
|
193
|
+
color: var(--text-primary);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.sidebar-toggle {
|
|
197
|
+
display: none;
|
|
198
|
+
padding: 0.5rem;
|
|
199
|
+
font-size: 1.5rem;
|
|
200
|
+
background: transparent;
|
|
201
|
+
border: 1px solid var(--border-color);
|
|
202
|
+
border-radius: 6px;
|
|
203
|
+
color: var(--text-secondary);
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
transition: all 0.2s ease;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.sidebar-toggle:hover {
|
|
209
|
+
background: var(--bg-hover);
|
|
210
|
+
color: var(--text-primary);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.search-container {
|
|
214
|
+
margin-bottom: 1rem;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.search-box {
|
|
218
|
+
width: 100%;
|
|
219
|
+
padding: 0.75rem 1rem;
|
|
220
|
+
font-size: 1rem;
|
|
221
|
+
font-family: inherit;
|
|
222
|
+
border: 1px solid var(--border-color);
|
|
223
|
+
border-radius: 8px;
|
|
224
|
+
background: var(--bg-page);
|
|
225
|
+
color: var(--text-primary);
|
|
226
|
+
transition: all 0.2s ease;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.search-box:focus {
|
|
230
|
+
outline: none;
|
|
231
|
+
border-color: var(--accent-primary);
|
|
232
|
+
box-shadow: 0 0 0 3px rgba(212, 165, 116, 0.1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.stats {
|
|
236
|
+
color: var(--text-tertiary);
|
|
237
|
+
font-size: 0.9375rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Layout */
|
|
241
|
+
.container {
|
|
242
|
+
display: flex;
|
|
243
|
+
min-height: calc(100vh - 180px);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Sidebar */
|
|
247
|
+
.sidebar {
|
|
248
|
+
width: 280px;
|
|
249
|
+
background: var(--bg-sidebar);
|
|
250
|
+
border-right: 1px solid var(--border-color);
|
|
251
|
+
padding: 2rem 1.5rem;
|
|
252
|
+
overflow-y: auto;
|
|
253
|
+
position: sticky;
|
|
254
|
+
top: 180px;
|
|
255
|
+
height: calc(100vh - 180px);
|
|
256
|
+
flex-shrink: 0;
|
|
257
|
+
transition: transform 0.3s ease;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.sidebar.hidden {
|
|
261
|
+
transform: translateX(-100%);
|
|
262
|
+
position: absolute;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.sidebar h3 {
|
|
266
|
+
font-family: 'Crimson Text', Georgia, serif;
|
|
267
|
+
font-size: 1.125rem;
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
margin-bottom: 1rem;
|
|
270
|
+
color: var(--text-primary);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.book-list {
|
|
274
|
+
list-style: none;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.book-list li {
|
|
278
|
+
margin-bottom: 0.5rem;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.book-link {
|
|
282
|
+
display: flex;
|
|
283
|
+
justify-content: space-between;
|
|
284
|
+
align-items: baseline;
|
|
285
|
+
padding: 0.5rem 0.75rem;
|
|
286
|
+
text-decoration: none;
|
|
287
|
+
color: var(--text-secondary);
|
|
288
|
+
border-radius: 6px;
|
|
289
|
+
transition: all 0.2s ease;
|
|
290
|
+
font-size: 0.9375rem;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.book-link:hover {
|
|
294
|
+
background: var(--bg-hover);
|
|
295
|
+
color: var(--accent-primary);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.book-link.active {
|
|
299
|
+
background: var(--bg-card);
|
|
300
|
+
color: var(--text-primary);
|
|
301
|
+
border-left: 3px solid var(--accent-primary);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.book-title {
|
|
305
|
+
flex: 1;
|
|
306
|
+
overflow: hidden;
|
|
307
|
+
text-overflow: ellipsis;
|
|
308
|
+
white-space: nowrap;
|
|
309
|
+
margin-right: 0.5rem;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.book-count {
|
|
313
|
+
font-size: 0.875rem;
|
|
314
|
+
color: var(--text-tertiary);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Main content */
|
|
318
|
+
main {
|
|
319
|
+
flex: 1;
|
|
320
|
+
padding: 3rem 2rem;
|
|
321
|
+
max-width: 900px;
|
|
322
|
+
margin: 0 auto;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Book cards */
|
|
326
|
+
.book {
|
|
327
|
+
background: var(--bg-card);
|
|
328
|
+
border-radius: 12px;
|
|
329
|
+
padding: 2rem;
|
|
330
|
+
margin-bottom: 2rem;
|
|
331
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
332
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
333
|
+
scroll-margin-top: 200px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.book:hover {
|
|
337
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.book-header {
|
|
341
|
+
display: flex;
|
|
342
|
+
justify-content: space-between;
|
|
343
|
+
align-items: flex-start;
|
|
344
|
+
margin-bottom: 1.5rem;
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
user-select: none;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.book-info {
|
|
350
|
+
flex: 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.book h2 {
|
|
354
|
+
font-family: 'Crimson Text', Georgia, serif;
|
|
355
|
+
font-size: 1.75rem;
|
|
356
|
+
font-weight: 600;
|
|
357
|
+
color: var(--text-primary);
|
|
358
|
+
margin-bottom: 0.5rem;
|
|
359
|
+
line-height: 1.3;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.author {
|
|
363
|
+
color: var(--text-secondary);
|
|
364
|
+
font-size: 1rem;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.collapse-btn {
|
|
368
|
+
background: transparent;
|
|
369
|
+
border: none;
|
|
370
|
+
cursor: pointer;
|
|
371
|
+
padding: 0.5rem;
|
|
372
|
+
color: var(--text-secondary);
|
|
373
|
+
transition: all 0.2s ease;
|
|
374
|
+
border-radius: 6px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.collapse-btn:hover {
|
|
378
|
+
background: var(--bg-hover);
|
|
379
|
+
color: var(--text-primary);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.chevron {
|
|
383
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.book.collapsed .chevron {
|
|
387
|
+
transform: rotate(-90deg);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.highlights {
|
|
391
|
+
overflow: hidden;
|
|
392
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.book.collapsed .highlights {
|
|
396
|
+
max-height: 0 !important;
|
|
397
|
+
margin-bottom: 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Highlight items */
|
|
401
|
+
.highlight {
|
|
402
|
+
display: flex;
|
|
403
|
+
gap: 12px;
|
|
404
|
+
margin-bottom: 1.5rem;
|
|
405
|
+
padding-bottom: 1.5rem;
|
|
406
|
+
border-bottom: 1px solid var(--border-color);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.highlight:last-child {
|
|
410
|
+
border-bottom: none;
|
|
411
|
+
padding-bottom: 0;
|
|
412
|
+
margin-bottom: 0;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.color-dot {
|
|
416
|
+
width: 8px;
|
|
417
|
+
height: 8px;
|
|
418
|
+
border-radius: 50%;
|
|
419
|
+
flex-shrink: 0;
|
|
420
|
+
margin-top: 6px;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.color-dot.yellow {
|
|
424
|
+
background: var(--dot-yellow);
|
|
425
|
+
box-shadow: 0 0 8px rgba(255, 193, 7, 0.3);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.color-dot.green {
|
|
429
|
+
background: var(--dot-green);
|
|
430
|
+
box-shadow: 0 0 8px rgba(76, 175, 80, 0.3);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.color-dot.blue {
|
|
434
|
+
background: var(--dot-blue);
|
|
435
|
+
box-shadow: 0 0 8px rgba(33, 150, 243, 0.3);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.color-dot.pink {
|
|
439
|
+
background: var(--dot-pink);
|
|
440
|
+
box-shadow: 0 0 8px rgba(233, 30, 99, 0.3);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.color-dot.purple {
|
|
444
|
+
background: var(--dot-purple);
|
|
445
|
+
box-shadow: 0 0 8px rgba(156, 39, 176, 0.3);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.color-dot.underline {
|
|
449
|
+
background: var(--dot-underline);
|
|
450
|
+
box-shadow: 0 0 8px rgba(117, 117, 117, 0.3);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.highlight-content {
|
|
454
|
+
flex: 1;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.highlight-text {
|
|
458
|
+
font-size: 1.0625rem;
|
|
459
|
+
line-height: 1.7;
|
|
460
|
+
color: var(--text-primary);
|
|
461
|
+
margin-bottom: 0.5rem;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.note {
|
|
465
|
+
font-style: italic;
|
|
466
|
+
color: var(--text-secondary);
|
|
467
|
+
margin-top: 0.75rem;
|
|
468
|
+
font-size: 1rem;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.highlight-meta {
|
|
472
|
+
display: flex;
|
|
473
|
+
justify-content: space-between;
|
|
474
|
+
align-items: center;
|
|
475
|
+
margin-top: 0.75rem;
|
|
476
|
+
gap: 1rem;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.location {
|
|
480
|
+
font-size: 0.875rem;
|
|
481
|
+
color: var(--text-tertiary);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
time {
|
|
485
|
+
font-size: 0.875rem;
|
|
486
|
+
color: var(--text-tertiary);
|
|
487
|
+
text-align: right;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.hidden {
|
|
491
|
+
display: none;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Responsive design */
|
|
495
|
+
@media (max-width: 1023px) {
|
|
496
|
+
.sidebar {
|
|
497
|
+
position: fixed;
|
|
498
|
+
top: 0;
|
|
499
|
+
left: 0;
|
|
500
|
+
height: 100vh;
|
|
501
|
+
z-index: 200;
|
|
502
|
+
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.1);
|
|
503
|
+
transform: translateX(-100%);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.sidebar.visible {
|
|
507
|
+
transform: translateX(0);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.sidebar-toggle {
|
|
511
|
+
display: block;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
main {
|
|
515
|
+
padding: 2rem 1.5rem;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.book {
|
|
519
|
+
padding: 1.5rem;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
@media (max-width: 767px) {
|
|
524
|
+
.header {
|
|
525
|
+
padding: 1rem 1.25rem;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.header h1 {
|
|
529
|
+
font-size: 1.75rem;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.expand-all-btn {
|
|
533
|
+
display: none;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
main {
|
|
537
|
+
padding: 1.5rem 1rem;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.book {
|
|
541
|
+
padding: 1.25rem;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.book h2 {
|
|
545
|
+
font-size: 1.5rem;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.sidebar {
|
|
549
|
+
width: 260px;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* Print styles */
|
|
554
|
+
@media print {
|
|
555
|
+
body {
|
|
556
|
+
background: white;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.header {
|
|
560
|
+
position: static;
|
|
561
|
+
box-shadow: none;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.search-container,
|
|
565
|
+
.expand-all-btn,
|
|
566
|
+
.sidebar-toggle,
|
|
567
|
+
.collapse-btn {
|
|
568
|
+
display: none !important;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.sidebar {
|
|
572
|
+
position: static;
|
|
573
|
+
width: 100%;
|
|
574
|
+
height: auto;
|
|
575
|
+
page-break-after: always;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.book {
|
|
579
|
+
box-shadow: none;
|
|
580
|
+
page-break-inside: avoid;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.book.collapsed .highlights {
|
|
584
|
+
max-height: none !important;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
main {
|
|
588
|
+
padding: 1rem 0;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
</style>
|
|
592
|
+
</head>
|
|
593
|
+
<body>
|
|
594
|
+
<header class="header">
|
|
595
|
+
<div class="header-content">
|
|
596
|
+
<h1>Apple Books Highlights</h1>
|
|
597
|
+
<div class="header-actions">
|
|
598
|
+
<button class="expand-all-btn">Collapse All</button>
|
|
599
|
+
<button class="sidebar-toggle">☰</button>
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
<div class="search-container">
|
|
603
|
+
<input type="search" class="search-box" placeholder="Search books or highlights..." aria-label="Search" />
|
|
604
|
+
</div>
|
|
605
|
+
<div class="stats">${totalHighlights} highlights from ${totalBooks} books • Exported ${exportDate}</div>
|
|
606
|
+
</header>
|
|
607
|
+
|
|
608
|
+
<div class="container">
|
|
609
|
+
${renderBookIndex(books)}
|
|
610
|
+
<main>
|
|
611
|
+
`;
|
|
612
|
+
for (const book of books) {
|
|
613
|
+
html += renderBook(book);
|
|
614
|
+
}
|
|
615
|
+
html += ` </main>
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
<script>
|
|
619
|
+
// Initialize highlights heights for smooth transitions
|
|
620
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
621
|
+
document.querySelectorAll('.highlights').forEach(highlights => {
|
|
622
|
+
highlights.style.maxHeight = highlights.scrollHeight + 'px';
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Search functionality with debouncing
|
|
627
|
+
const searchInput = document.querySelector('.search-box');
|
|
628
|
+
let searchTimeout;
|
|
629
|
+
|
|
630
|
+
searchInput.addEventListener('input', (e) => {
|
|
631
|
+
clearTimeout(searchTimeout);
|
|
632
|
+
searchTimeout = setTimeout(() => {
|
|
633
|
+
const query = e.target.value.toLowerCase().trim();
|
|
634
|
+
const books = document.querySelectorAll('.book');
|
|
635
|
+
|
|
636
|
+
if (query === '') {
|
|
637
|
+
books.forEach(book => {
|
|
638
|
+
book.classList.remove('hidden');
|
|
639
|
+
book.style.opacity = '1';
|
|
640
|
+
});
|
|
641
|
+
} else {
|
|
642
|
+
books.forEach(book => {
|
|
643
|
+
const text = book.textContent.toLowerCase();
|
|
644
|
+
if (text.includes(query)) {
|
|
645
|
+
book.classList.remove('hidden');
|
|
646
|
+
book.style.opacity = '1';
|
|
647
|
+
} else {
|
|
648
|
+
book.style.opacity = '0';
|
|
649
|
+
setTimeout(() => book.classList.add('hidden'), 200);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}, 150);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Expand/Collapse individual book
|
|
657
|
+
document.querySelectorAll('.book-header').forEach(header => {
|
|
658
|
+
header.addEventListener('click', (e) => {
|
|
659
|
+
const book = header.closest('.book');
|
|
660
|
+
book.classList.toggle('collapsed');
|
|
661
|
+
|
|
662
|
+
// Save state to localStorage
|
|
663
|
+
const bookId = book.id;
|
|
664
|
+
const isCollapsed = book.classList.contains('collapsed');
|
|
665
|
+
localStorage.setItem('book-' + bookId, isCollapsed ? 'collapsed' : 'expanded');
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
// Expand/Collapse All button
|
|
670
|
+
const expandAllBtn = document.querySelector('.expand-all-btn');
|
|
671
|
+
let allCollapsed = false;
|
|
672
|
+
|
|
673
|
+
expandAllBtn.addEventListener('click', () => {
|
|
674
|
+
const books = document.querySelectorAll('.book');
|
|
675
|
+
allCollapsed = !allCollapsed;
|
|
676
|
+
|
|
677
|
+
books.forEach(book => {
|
|
678
|
+
if (allCollapsed) {
|
|
679
|
+
book.classList.add('collapsed');
|
|
680
|
+
localStorage.setItem('book-' + book.id, 'collapsed');
|
|
681
|
+
} else {
|
|
682
|
+
book.classList.remove('collapsed');
|
|
683
|
+
localStorage.setItem('book-' + book.id, 'expanded');
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
expandAllBtn.textContent = allCollapsed ? 'Expand All' : 'Collapse All';
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// Sidebar toggle
|
|
691
|
+
const sidebarToggle = document.querySelector('.sidebar-toggle');
|
|
692
|
+
const sidebar = document.querySelector('.sidebar');
|
|
693
|
+
|
|
694
|
+
sidebarToggle.addEventListener('click', () => {
|
|
695
|
+
sidebar.classList.toggle('visible');
|
|
696
|
+
const isVisible = sidebar.classList.contains('visible');
|
|
697
|
+
localStorage.setItem('sidebar-visible', isVisible ? 'true' : 'false');
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Close sidebar when clicking outside on mobile
|
|
701
|
+
document.addEventListener('click', (e) => {
|
|
702
|
+
if (window.innerWidth <= 1023) {
|
|
703
|
+
if (!sidebar.contains(e.target) && !sidebarToggle.contains(e.target)) {
|
|
704
|
+
sidebar.classList.remove('visible');
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// Smooth scroll for anchor links with active state
|
|
710
|
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
711
|
+
anchor.addEventListener('click', function (e) {
|
|
712
|
+
e.preventDefault();
|
|
713
|
+
const target = document.querySelector(this.getAttribute('href'));
|
|
714
|
+
if (target) {
|
|
715
|
+
// Remove active class from all links
|
|
716
|
+
document.querySelectorAll('.book-link').forEach(link => {
|
|
717
|
+
link.classList.remove('active');
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// Add active class to clicked link
|
|
721
|
+
this.classList.add('active');
|
|
722
|
+
|
|
723
|
+
// Expand book if collapsed
|
|
724
|
+
target.classList.remove('collapsed');
|
|
725
|
+
localStorage.setItem('book-' + target.id, 'expanded');
|
|
726
|
+
|
|
727
|
+
// Instant scroll
|
|
728
|
+
target.scrollIntoView({
|
|
729
|
+
behavior: 'instant',
|
|
730
|
+
block: 'start'
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// Close sidebar on mobile
|
|
734
|
+
if (window.innerWidth <= 1023) {
|
|
735
|
+
sidebar.classList.remove('visible');
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// Restore collapse states from localStorage
|
|
742
|
+
document.querySelectorAll('.book').forEach(book => {
|
|
743
|
+
const bookId = book.id;
|
|
744
|
+
const savedState = localStorage.getItem('book-' + bookId);
|
|
745
|
+
if (savedState === 'collapsed') {
|
|
746
|
+
book.classList.add('collapsed');
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// Restore sidebar state from localStorage
|
|
751
|
+
const sidebarState = localStorage.getItem('sidebar-visible');
|
|
752
|
+
if (sidebarState === 'true' && window.innerWidth <= 1023) {
|
|
753
|
+
sidebar.classList.add('visible');
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Update expand/collapse all button text based on current state
|
|
757
|
+
function updateExpandAllButton() {
|
|
758
|
+
const books = document.querySelectorAll('.book');
|
|
759
|
+
const collapsedCount = document.querySelectorAll('.book.collapsed').length;
|
|
760
|
+
allCollapsed = collapsedCount === books.length;
|
|
761
|
+
expandAllBtn.textContent = allCollapsed ? 'Expand All' : 'Collapse All';
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
updateExpandAllButton();
|
|
765
|
+
</script>
|
|
766
|
+
</body>
|
|
767
|
+
</html>
|
|
768
|
+
`;
|
|
769
|
+
// Write to file
|
|
770
|
+
const fs = require('fs');
|
|
771
|
+
const path = require('path');
|
|
772
|
+
const resolvedPath = path.resolve(outputPath);
|
|
773
|
+
fs.writeFileSync(resolvedPath, html, 'utf-8');
|
|
774
|
+
return resolvedPath;
|
|
775
|
+
}
|