@@ -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+ / \[ ( [ ^ \] ] + ) \] \( ( h t t p s ? : \/ \/ [ ^ ) ] + ) \) / g,
444+ '<a href="#" data-url="$2">$1</a>' ,
445+ )
446+ . replace (
447+ / ( h t t p s ? : \/ \/ [ ^ \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" ) ;
0 commit comments