Skip to content

Commit 32f424a

Browse files
committed
feat: bookmark schema migration, cookie expiration, and progress UI improvements
- Add bookmark schema migration to preserve user bookmarks on version upgrade - Add 10-minute expiration check for CNKI Home cookie with auto-refresh - Improve progress window with popover messages and clickable links - Add developer contact links in locale files - Comment out unused reader text selection popup listener
1 parent d49a3a9 commit 32f424a

File tree

8 files changed

+373
-40
lines changed

8 files changed

+373
-40
lines changed

addon/chrome/content/progress.xhtml

Lines changed: 187 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
7272
display: flex;
7373
align-items: center;
7474
cursor: pointer;
75+
position: relative;
7576
}
7677

7778
.task-status {
@@ -155,7 +156,6 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
155156

156157
.task-msg {
157158
width: 15px;
158-
margin-left: 8px;
159159
vertical-align: middle;
160160
cursor: pointer;
161161
display: inline-block;
@@ -194,13 +194,96 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
194194
}
195195
}
196196

197+
/* Wrapper for icon + popover hover area */
198+
.task-msg-wrapper {
199+
position: relative;
200+
display: inline-block;
201+
margin-left: 8px;
202+
vertical-align: middle;
203+
}
204+
197205
/* 鼠标悬停时暂停动画并放大 */
198-
.task-msg:hover {
206+
.task-msg-wrapper:hover .task-msg,
207+
.task-msg.active {
199208
animation-play-state: paused;
200209
transform: scale(1.5);
201210
filter: drop-shadow(0 0 3px rgba(255, 193, 7, 0.8));
202211
}
203212

213+
/* Custom popover for task messages */
214+
.task-msg-popover {
215+
position: absolute;
216+
left: 0;
217+
top: calc(100% + 8px);
218+
padding: 12px 16px;
219+
background: rgba(255, 255, 255, 0.82);
220+
backdrop-filter: blur(16px) saturate(180%);
221+
-webkit-backdrop-filter: blur(16px) saturate(180%);
222+
border: 1px solid rgba(0, 0, 0, 0.08);
223+
border-radius: 10px;
224+
box-shadow:
225+
0 8px 32px rgba(0, 0, 0, 0.12),
226+
0 2px 8px rgba(0, 0, 0, 0.06);
227+
font-size: 12px;
228+
font-weight: normal;
229+
line-height: 1.7;
230+
white-space: pre-wrap;
231+
word-break: break-all;
232+
max-width: 420px;
233+
min-width: 240px;
234+
max-height: 250px;
235+
overflow-y: auto;
236+
z-index: 100;
237+
color: #1d1d1f;
238+
/* Hover transition */
239+
opacity: 0;
240+
visibility: hidden;
241+
transform: translateY(4px);
242+
transition:
243+
opacity 0.2s ease,
244+
visibility 0.2s ease,
245+
transform 0.2s ease;
246+
pointer-events: none;
247+
}
248+
249+
/* Arrow */
250+
.task-msg-popover::before {
251+
content: "";
252+
position: absolute;
253+
top: -6px;
254+
left: 12px;
255+
width: 12px;
256+
height: 12px;
257+
background: rgba(255, 255, 255, 0.82);
258+
backdrop-filter: blur(16px) saturate(180%);
259+
-webkit-backdrop-filter: blur(16px) saturate(180%);
260+
border-top: 1px solid rgba(0, 0, 0, 0.08);
261+
border-left: 1px solid rgba(0, 0, 0, 0.08);
262+
transform: rotate(45deg);
263+
}
264+
265+
.task-msg-popover.visible {
266+
opacity: 1;
267+
visibility: visible;
268+
transform: translateY(0);
269+
pointer-events: auto;
270+
}
271+
272+
.task-msg-popover a {
273+
color: #0066cc;
274+
text-decoration: none;
275+
border-bottom: 1px solid rgba(0, 102, 204, 0.3);
276+
transition:
277+
border-color 0.15s ease,
278+
color 0.15s ease;
279+
cursor: pointer;
280+
}
281+
282+
.task-msg-popover a:hover {
283+
color: #0047ab;
284+
border-bottom-color: rgba(0, 71, 171, 0.6);
285+
}
286+
204287
/* 黑暗模式样式 */
205288
@media (prefers-color-scheme: dark) {
206289
body {
@@ -247,6 +330,31 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
247330
.confirm-button:disabled {
248331
background-color: #666;
249332
}
333+
334+
.task-msg-popover {
335+
background: rgba(40, 40, 40, 0.85);
336+
border-color: rgba(255, 255, 255, 0.1);
337+
color: #e0e0e0;
338+
box-shadow:
339+
0 8px 32px rgba(0, 0, 0, 0.4),
340+
0 2px 8px rgba(0, 0, 0, 0.2);
341+
}
342+
343+
.task-msg-popover::before {
344+
background: rgba(40, 40, 40, 0.85);
345+
border-top-color: rgba(255, 255, 255, 0.1);
346+
border-left-color: rgba(255, 255, 255, 0.1);
347+
}
348+
349+
.task-msg-popover a {
350+
color: #8ab4f8;
351+
border-bottom-color: rgba(138, 180, 248, 0.3);
352+
}
353+
354+
.task-msg-popover a:hover {
355+
color: #aecbfa;
356+
border-bottom-color: rgba(174, 203, 250, 0.6);
357+
}
250358
}
251359
</style>
252360
</head>
@@ -311,7 +419,8 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
311419
type: "attachment",
312420
item: { getField: () => "论文标题 3" },
313421
status: "fail",
314-
message: "抓取失败",
422+
message:
423+
"抓取失败\n[小红书l0o0](https://www.xiaohongshu.com/user/profile/6153b4fa000000001f03ac8c)",
315424
},
316425
];
317426

@@ -324,6 +433,24 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
324433
fail: "❌",
325434
};
326435

436+
// Convert [text](url) and bare URLs to clickable links
437+
function linkifyText(text) {
438+
return text
439+
.split("\n")
440+
.map((line) =>
441+
line
442+
.replace(
443+
/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,
444+
'<a href="#" data-url="$2">$1</a>',
445+
)
446+
.replace(
447+
/(https?:\/\/[^\s<]+)(?![^<]*<\/a>)/g,
448+
'<a href="#" data-url="$1">$1</a>',
449+
),
450+
)
451+
.join("<br>");
452+
}
453+
327454
// 渲染任务列表
328455
function renderTaskList(tasks) {
329456
const taskList = document.getElementById("task-list");
@@ -335,8 +462,12 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
335462
<div class="task" data-task-id="${task.id}">
336463
<div class="task-header">
337464
<span class="task-status">${statusIcons[task.status]}</span>
338-
<span class="task-title">${task.item.getField("title")}</span>
339-
<span class="task-msg" id="task-msg-${task.id}" title="${task.message}">⚠️</span>
465+
<span class="task-title">${task.item.getField("title")}
466+
<span class="task-msg-wrapper">
467+
<span class="task-msg" id="task-msg-${task.id}">⚠️</span>
468+
<div class="task-msg-popover" id="task-msg-popover-${task.id}">${linkifyText(task.message)}</div>
469+
</span>
470+
</span>
340471
${
341472
task.searchResult && task.searchResult.length > 0
342473
? `<span class="toggle-icon" id="toggle-icon-${task.id}">▼</span>`
@@ -392,9 +523,60 @@ href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
392523
}
393524
}
394525

526+
// Close all open popovers
527+
function closeAllPopovers() {
528+
document.querySelectorAll(".task-msg-popover.visible").forEach((p) => {
529+
p.classList.remove("visible");
530+
});
531+
document.querySelectorAll(".task-msg.active").forEach((i) => {
532+
i.classList.remove("active");
533+
});
534+
}
535+
536+
// Show popover on hover, close on click outside
537+
document.getElementById("task-list").addEventListener(
538+
"mouseenter",
539+
(event) => {
540+
const wrapper = event.target.closest(".task-msg-wrapper");
541+
if (!wrapper) return;
542+
closeAllPopovers();
543+
const popover = wrapper.querySelector(".task-msg-popover");
544+
const icon = wrapper.querySelector(".task-msg");
545+
if (popover) popover.classList.add("visible");
546+
if (icon) icon.classList.add("active");
547+
},
548+
true,
549+
);
550+
551+
document.addEventListener("click", (event) => {
552+
if (
553+
!event.target.closest(".task-msg-popover") &&
554+
!event.target.closest(".task-msg-wrapper")
555+
) {
556+
closeAllPopovers();
557+
}
558+
});
559+
395560
// 事件委托:绑定点击事件
396561
document.getElementById("task-list").addEventListener("click", (event) => {
397562
console.log("click", event.target);
563+
564+
// Handle popover link clicks via Zotero.launchURL
565+
const link = event.target.closest(".task-msg-popover a[data-url]");
566+
if (link) {
567+
event.preventDefault();
568+
event.stopPropagation();
569+
const url = link.getAttribute("data-url");
570+
if (url) {
571+
if (typeof Zotero !== "undefined") {
572+
Zotero.launchURL(url);
573+
} else {
574+
window.open(url, "_blank");
575+
}
576+
}
577+
return;
578+
}
579+
398580
const taskHeader = event.target.closest(".task-header");
399581
if (taskHeader) {
400582
const taskId = taskHeader.closest(".task").getAttribute("data-task-id");

addon/locale/en-US/addon.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,5 @@ bookmark-add = Add bookmark
7272
bookmark-delete = Delete bookmark
7373
7474
# Progress window
75-
task-msg-header = If you need help with capture issues, please screenshot the following content and contact the developer: RedBook l0o0
75+
task-msg-header = If you need help with capture issues, please screenshot the following content and contact the developer: [RedBook l0o0](https://www.xiaohongshu.com/user/profile/6153b4fa000000001f03ac8c)
7676
task-already-exists = Task already exists: { $title }

addon/locale/zh-CN/addon.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,5 @@ bookmark-add = 添加书签
7373
bookmark-delete = 删除书签
7474
7575
# Progress window
76-
task-msg-header = 如果抓取异常需要帮助,请截图以下内容并联系开发者:小红书l0o0
76+
task-msg-header = 如果抓取异常需要帮助,请截图以下内容并联系开发者:[小红书l0o0](https://www.xiaohongshu.com/user/profile/6153b4fa000000001f03ac8c)
7777
task-already-exists = 任务已存在:{ $title }

addon/locale/zh-TW/addon.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,5 @@ bookmark-add = 添加書籤
7373
bookmark-delete = 刪除書籤
7474
7575
# Progress window
76-
task-msg-header = 如果抓取異常需要幫助,請截圖以下內容並聯繫開發者:小紅書l0o0
76+
task-msg-header = 如果抓取異常需要幫助,請截圖以下內容並聯繫開發者:[小紅書l0o0](https://www.xiaohongshu.com/user/profile/6153b4fa000000001f03ac8c)
7777
task-already-exists = 已存在任務:{ $title }

src/modules/notifier.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,13 @@ export function registerTab() {
128128
config.addonID,
129129
);
130130

131-
Zotero.Reader.registerEventListener(
132-
"renderTextSelectionPopup",
133-
(event: any) => {
134-
ztoolkit.log(event);
135-
event.append("<div>Jasminum</div>");
136-
},
137-
);
131+
// Zotero.Reader.registerEventListener(
132+
// "renderTextSelectionPopup",
133+
// (event: any) => {
134+
// ztoolkit.log(event);
135+
// event.append("<div>Jasminum</div>");
136+
// },
137+
// );
138138
}
139139

140140
async function tabRegisterCallback(event: any) {

src/modules/outline/bookmark.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,27 @@ function getRandomBookmarkColor(): string {
3030
return DEFAULT_BOOKMARK_COLORS[randomIndex];
3131
}
3232

33+
function migrateBookmarkInfo(
34+
raw: any,
35+
fromSchema: number,
36+
): { bookmarks: BookmarkNode[]; baseFontSize: number } {
37+
let bookmarks: BookmarkNode[] = raw.bookmarks ?? [];
38+
let baseFontSize = DEFAULT_BOOKMARK_FONT_SIZE;
39+
40+
// v1 → v2: add baseFontSize and bookmark color
41+
if (fromSchema < 2) {
42+
baseFontSize = raw.info?.baseFontSize ?? DEFAULT_BOOKMARK_FONT_SIZE;
43+
bookmarks = bookmarks.map((b: any) => ({
44+
...b,
45+
color: b.color || getRandomBookmarkColor(),
46+
}));
47+
}
48+
49+
// Future v2 → v3 migrations go here
50+
51+
return { bookmarks, baseFontSize };
52+
}
53+
3354
function getReaderPagePosition(): PdfPosition {
3455
const reader = Zotero.Reader.getByTabID(
3556
ztoolkit.getGlobal("Zotero_Tabs").selectedID,
@@ -105,8 +126,16 @@ export async function loadBookmarkInfoFromJSON(
105126
bookmarkPath,
106127
)) as string;
107128
const tmp = JSON.parse(content);
108-
if (tmp.info.schema < BOOKMARK_SCHEMA) {
109-
return null;
129+
const fileSchema = tmp.info?.schema ?? 1;
130+
if (fileSchema < BOOKMARK_SCHEMA) {
131+
// Migrate old bookmark data instead of discarding
132+
const migrated = migrateBookmarkInfo(tmp, fileSchema);
133+
await saveBookmarksToJSON(
134+
item,
135+
migrated.bookmarks,
136+
migrated.baseFontSize,
137+
);
138+
return migrated;
110139
} else {
111140
const bookmarkInfo = JSON.parse(content) as BookmarkInfo;
112141
return {

0 commit comments

Comments
 (0)