@@ -41,8 +41,9 @@ function startMotion(){if(!motionFrame){motionTime=0;motionFrame=requestAnimatio
4141function finishGesture (e ,cancelled = false ){if (! gesture|| gesture .pointerId !== e .pointerId )return ;if (! cancelled)move (e);const g = gesture;gesture= null ;if (g .dragged )suppressClickUntil= performance .now ()+ 400 ;
4242 const m = motion .value ;if (! m|| m .id !== g .a .id )return ;
4343 if (! cancelled&& g .shake .recognized ){remove (g .a );}else {m .tx = 0 ;m .ty = 0 ;if (reducedMotion .matches ){cancelAnimationFrame (motionFrame);motionFrame= 0 ;motion .value = null ;}else {m .vy += 24 ;startMotion ();}}}
44- function clickAnnotation (a ,e ,note = false ){if (performance .now ()< suppressClickUntil){e .preventDefault ();return ;}activate (a);if (note&& props .annotations .some (b => b .id === a .id )){popupFromNote .value = true ;opened .value = a;}}
44+ function clickAnnotation (a ,e ,note = false ){if (performance .now ()< suppressClickUntil){e .preventDefault ();return ;}activate (a);if (note&& props .annotations .some (b => b .id === a .id )){popupFromNote .value = true ;if ( a . kind !== ' highlight ' ||! a . comment ? . trim ()){ toolbar . value = false ; opened .value = a;} }}
4545
46+ const toolbarComment = computed (()=> props .annotations .find (a => a .id === selection .value ? .id )? .comment ? .trim ()|| ' ' );
4647const colors = [' #FFFF00' ,' #00FF00' ,' #00FFFF' ,' #FF0000' ];
4748const highlightOpacity = .42 ;
4849const favorite = ref (' #00FFFF' );try {favorite .value = JSON .parse (localStorage .getItem (' highlightColors' )|| ' {}' ).favorite || ' #00FFFF' ;}catch {}
@@ -76,7 +77,7 @@ function baseNoteRect(a){
7677 const line = a .rects [0 ],anchor= box|| line,markerSize= 34 / props .zoom ;
7778 return {x: noteRail .value ,y: Math .max (0 ,Math .min (props .page .height - markerSize,anchor .y + (Math .min (line .height ,anchor .height )- markerSize)/ 2 )),width: markerSize,height: markerSize};
7879}
79- const notePositions = computed (()=> annotationNotePositions (visible .value .filter (a => a .kind === ' comment' || ((a .origin === ' translation' )!== translatedAt (a))).map (a => ({id: a .id ,y: baseNoteRect (a).y })),props .page .height ,props .zoom ));
80+ const notePositions = computed (()=> annotationNotePositions (visible .value .filter (a => a .kind === ' comment' ||!! a . comment ? . trim () || ((a .origin === ' translation' )!== translatedAt (a))).map (a => ({id: a .id ,y: baseNoteRect (a).y })),props .page .height ,props .zoom ));
8081function noteRect (a ){const rect = baseNoteRect (a);return {... rect,y: notePositions .value .get (a .id )?? rect .y };}
8182
8283function activate (a ){if (! props .annotations .some (b => b .id === a .id ))return ;popupFromNote .value = false ;toolbar .value = false ;selection .value = null ;menu .value = null ;focused .value = a;opened .value = null ;if (a .kind === ' highlight' ){selection .value = a;toolbar .value = true ;touch ();}else opened .value = a;}
@@ -89,7 +90,7 @@ function point(){
8990 layoutVersion .value ;
9091 const state = editor .value || opened .value || (toolbar .value ? selection .value : focused .value ),host= props .host ()? .getBoundingClientRect ();
9192 if (! state||! host)return {left: ' 8px' ,top: ' 8px' };
92- const fromNote = !! state .id && ((state .origin === ' translation' )!== translatedAt (state)|| ( opened . value && popupFromNote .value ) );
93+ const fromNote = !! state .id && ((state .origin === ' translation' )!== translatedAt (state)|| popupFromNote .value );
9394 const line = fromNote? noteRect (state): state .rects .at (- 1 ),popup= editor .value ? editorPopup .value : opened .value ? readerPopup .value : toolbarPopup .value ;
9495 const width = popup? .offsetWidth || (editor .value ? 298 : opened .value ? 280 : 220 ),height= popup? .offsetHeight || (editor .value ? 180 : opened .value ? 130 : 48 );
9596 const inset = 12 ,gap= 10 ,screenWidth= window .innerWidth ,screenHeight= window .innerHeight ;
@@ -113,7 +114,7 @@ watch(draft,saveComment);
113114function commit (){if (! draft .value .trim ())return ;saveComment ();focused .value = editor .value ;editor .value = null ;selection .value = null ;window .getSelection ()? .removeAllRanges ();}
114115function remove (a ){if (erasing .value .has (a .id ))return ;
115116 // Persist immediately; the remaining marks are visual snapshots only.
116- if (! reducedMotion .matches ){const snapshot = {... a,animateUntil: 0 ,eraseHasNote: opened .value ? .id !== a .id && (a .kind === ' comment' || (a .origin === ' translation' )!== translatedAt (a))};
117+ if (! reducedMotion .matches ){const snapshot = {... a,animateUntil: 0 ,eraseHasNote: opened .value ? .id !== a .id && (a .kind === ' comment' ||!! a . comment ? . trim () || (a .origin === ' translation' )!== translatedAt (a))};
117118 if (motion .value ? .id === a .id )snapshot .eraseMotion = {... motion .value };
118119 erasing .value .set (a .id ,snapshot);eraseTimers .set (a .id ,setTimeout (()=> {erasing .value .delete (a .id );eraseTimers .delete (a .id );},highlightDuration));}
119120 if (gesture? .a .id === a .id )gesture= null ;if (motion .value ? .id === a .id ){cancelAnimationFrame (motionFrame);motionFrame= 0 ;motion .value = null ;}
@@ -146,16 +147,19 @@ watch(()=>props.zoom,()=>{toolbar.value=false;updatePopupLayout();});
146147< div class = " annotation-layer" >
147148< template v- for = " a in visible" : key= " a.id" >
148149< button v- for = " (r,i) in ((a.origin==='translation')===translatedAt(a))?a.rects:[]" : key= " i" class = " annotation-mark annotation-highlight" : class = " {'annotation-erasing':erasing.has(a.id),'annotation-new-highlight':isNew(a),'annotation-comment-highlight':a.kind==='comment'}" : style= " {...highlightPosition(r,a),...motionStyle(a,r),...eraseStyle(a,i,r),background:a.kind==='comment'?'#9b9b9b':a.color,opacity:a.kind==='comment'?undefined:highlightOpacity,...strokeStyle(a,i)}" : aria- hidden= " erasing.has(a.id)||undefined" : tabindex= " erasing.has(a.id)?-1:0" : aria- label= " a.kind==='comment'?'查看批注:'+a.comment:a.text" @pointerdown .stop = " down(a,$event)" @pointermove= " move" @pointerup= " finishGesture($event)" @pointercancel= " finishGesture($event,true)" @lostpointercapture= " finishGesture($event,true)" @contextmenu .prevent .stop = " openContextMenu(a,$event)" @click .stop = " clickAnnotation(a,$event)" / >
149- <button v-if="(erasing.has(a.id)?a.eraseHasNote:opened?.id!==a.id)&&(a.kind==='comment'||((a.origin==='translation')!==translatedAt(a)))" class="annotation-mark annotation-note" :class="{'annotation-comment-note':a.kind==='comment','annotation-erasing':erasing.has(a.id)}" title="查看批注" :style="{...position(noteRect(a)),...motionStyle(a,noteRect(a)),...eraseStyle(a,undefined,noteRect(a))}" :aria-hidden="erasing.has(a.id)||undefined" :tabindex="erasing.has(a.id)?-1:0" :aria-label="a.comment||a.text" @pointerdown.stop="down(a,$event)" @pointermove="move" @pointerup="finishGesture($event)" @pointercancel="finishGesture($event,true)" @lostpointercapture="finishGesture($event,true)" @click.stop="clickAnnotation(a,$event,true)" @contextmenu.prevent.stop="openContextMenu(a,$event)"><svg class="annotation-comment-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 3.5h14a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-8l-5 3v-3H5a2 2 0 0 1-2-2v-11a2 2 0 0 1 2-2Z"/><path d="M7 8h10M7 12h7"/></svg></button>
150+ <button v-if="(erasing.has(a.id)?a.eraseHasNote:opened?.id!==a.id)&&(a.kind==='comment'||!!a.comment?.trim()||((a.origin==='translation')!==translatedAt(a)))" class="annotation-mark annotation-note" :class="{'annotation-comment-note':a.kind==='comment'||!!a.comment?.trim(),'annotation-erasing':erasing.has(a.id)}" title="查看批注" :style="{...position(noteRect(a)),...motionStyle(a,noteRect(a)),...eraseStyle(a,undefined,noteRect(a))}" :aria-hidden="erasing.has(a.id)||undefined" :tabindex="erasing.has(a.id)?-1:0" :aria-label="a.comment||a.text" @pointerdown.stop="down(a,$event)" @pointermove="move" @pointerup="finishGesture($event)" @pointercancel="finishGesture($event,true)" @lostpointercapture="finishGesture($event,true)" @click.stop="clickAnnotation(a,$event,true)" @contextmenu.prevent.stop="openContextMenu(a,$event)"><svg class="annotation-comment-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 3.5h14a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-8l-5 3v-3H5a2 2 0 0 1-2-2v-11a2 2 0 0 1 2-2Z"/><path d="M7 8h10M7 12h7"/></svg></button>
150151< / template>
151152< / div>
152153< Teleport to= " body" >
153154< div v- if = " assistance" : data- platform= " popupPlatform" class = " annotation-ui annotation-modal" >< section class = " annotation-confirm annotation-assistance" role= " dialog" aria- modal= " true" : aria- label= " assistance.title" >< strong> {{assistance .title }}< / strong>< p v- if = " assistance.busy" role= " status" > 正在处理…< / p>< p v- else > {{assistance .error || assistance .text }}< / p>< button @click= " assistController?.abort();assistance=null" > 关闭< / button>< / section>< / div>
154- < Transition name= " annotation-fade" >< div v- if = " toolbar" ref= " toolbarPopup" : data- platform= " popupPlatform" class = " annotation-ui annotation-toolbar" : style= " toolbarPoint()" role= " toolbar" aria- label= " 文字批注" @pointerdown .prevent @pointermove= " touch" @keydown= " touch" >
155+ < Transition name= " annotation-fade" >< div v- if = " toolbar" ref= " toolbarPopup" : data- platform= " popupPlatform" class = " annotation-ui annotation-toolbar" : style= " toolbarPoint()" : class = " {'annotation-toolbar-with-comment':toolbarComment}" @pointerdown .prevent @pointermove= " touch" @keydown= " touch" >
156+ < div class = " annotation-toolbar-actions" role= " toolbar" aria- label= " 文字批注" >
155157<button v-for="(c,i) in colors" :key="i" :aria-label="['荧光黄','荧光绿','荧光青','荧光红'][i]" :title="['荧光黄 (1)','荧光绿 (2)','荧光青 (3)','荧光红 (4)'][i]" :aria-keyshortcuts="String(i+1)" :aria-pressed="selection?.id?selection.color?.toUpperCase()===c:undefined" @click="add('highlight',c)"><i class="annotation-color-swatch" :style="{background:`color-mix(in srgb, ${c} ${highlightOpacity*100}%, white)`}" aria-hidden="true"/></button><button v-if="false" aria-label="自定义高亮颜色" @click="clearTimeout(timer);colorInput.click()"><i class="annotation-color-swatch annotation-custom-color" :style="{'--annotation-color':favorite}" aria-hidden="true"/></button><input ref="colorInput" type="color" class="annotation-color-input" :value="favorite" @change="add('highlight',$event.target.value)"/><span class="annotation-separator"/><button aria-label="添加批注" title="添加批注 (5)" aria-keyshortcuts="5" @click="comment(selection?.id?selection:undefined)"><svg class="annotation-comment-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 3.5h14a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-8l-5 3v-3H5a2 2 0 0 1-2-2v-11a2 2 0 0 1 2-2Z"/><path d="M7 8h10M7 12h7"/></svg></button>
156158< button v- if = " selection?.id" class = " annotation-delete" : aria- label= " deleteArmed?'双击删除高亮':'删除高亮'" : title= " deleteArmed?'双击删除高亮':'单击确认,双击删除高亮'" @click= " armToolbarDelete" @dblclick .prevent .stop = " deleteFromToolbar" >< svg class = " annotation-comment-icon" viewBox= " 0 0 24 24" fill= " none" stroke= " currentColor" stroke- width= " 1.7" stroke- linecap= " round" stroke- linejoin= " round" aria- hidden= " true" >< template v- if = " deleteArmed" >< path d= " M9 7a3 3 0 0 1 6 0c0 2-3 2.5-3 5" / >< path d= " M12 17h.01" / >< / template>< template v- else >< path d= " M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7" / >< / template>< / svg>< / button>
159+ < / div>
160+ < section v- if = " toolbarComment" class = " annotation-toolbar-comment" aria- label= " 评论" >< p> {{toolbarComment}}< / p>< / section>
157161< / div>< / Transition>
158- < Transition name= " annotation-fade" >< div v- if = " opened" ref= " readerPopup" : data- platform= " popupPlatform" class = " annotation-ui annotation-reader" : style= " readerPoint()" >< header class = " annotation-reader-header" >< span> {{opened .origin === ' translation' ? ' 译文' : ' 原文' }}{{opened .kind === ' highlight' ? ' 高亮' : ' 批注' }}< / span>< button @click= " opened=null" aria- label= " 关闭" >< svg viewBox= " 0 0 16 16" fill= " none" stroke= " currentColor" stroke- width= " 1.5" stroke- linecap= " round" aria- hidden= " true" >< path d= " m4 4 8 8M12 4l-8 8" / >< / svg>< / button>< / header>< p> {{opened .kind === ' highlight' ? (opened .text || ' ' ).replace (/ [\r\n \u2028\u2029 ] + / g ,' ' ).trim (): opened .comment || opened .text }}< / p>< time> {{opened .dateUnknown &&! opened .modifiedAt ? ' 日期未知' : new Date (opened .modifiedAt || opened .createdAt ).toLocaleString ()}}< / time>< / div>< / Transition>
162+ < Transition name= " annotation-fade" >< div v- if = " opened" ref= " readerPopup" : data- platform= " popupPlatform" class = " annotation-ui annotation-reader" : style= " readerPoint()" >< header class = " annotation-reader-header" >< span> {{opened .origin === ' translation' ? ' 译文' : ' 原文' }}{{opened .kind === ' highlight' ? ' 高亮' : ' 批注' }}< / span>< button @click= " opened=null" aria- label= " 关闭" >< svg viewBox= " 0 0 16 16" fill= " none" stroke= " currentColor" stroke- width= " 1.5" stroke- linecap= " round" aria- hidden= " true" >< path d= " m4 4 8 8M12 4l-8 8" / >< / svg>< / button>< / header>< p> {{opened .kind === ' highlight' &&! opened . comment ? . trim () ? (opened .text || ' ' ).replace (/ [\r\n \u2028\u2029 ] + / g ,' ' ).trim (): opened .comment || opened .text }}< / p>< time> {{opened .dateUnknown &&! opened .modifiedAt ? ' 日期未知' : new Date (opened .modifiedAt || opened .createdAt ).toLocaleString ()}}< / time>< / div>< / Transition>
159163< Transition name= " annotation-fade" >< div v- if = " editor" : data- platform= " popupPlatform" class = " annotation-ui annotation-modal" @pointerdown .self = " editor=null" >< form ref= " editorPopup" class = " annotation-editor" : style= " editorPoint()" role= " dialog" aria- modal= " true" aria- label= " 添加批注" @submit .prevent = " commit" >< textarea v- model= " draft" placeholder= " 输入评论…" aria- label= " 评论" / >< div>< button type= " button" @click= " editor=null" > 关闭< / button>< button type= " submit" : disabled= " !draft.trim()" > 保存 ⌘↵< / button>< / div>< / form>< / div>< / Transition>
160164< div v- if = " menu" : data- platform= " popupPlatform" class = " annotation-ui annotation-menu" : style= " {left:Math.min(menu.x,innerWidth-190)+'px',top:Math.min(menu.y,innerHeight-330)+'px'}" role= " menu" >< button v- for = " name in menu.a.kind==='highlight'?['复制','分享','谷歌搜索','谷歌学术搜索','Hand over to AI','删除']:['复制','分享','Hand over to AI','修改','删除']" : key= " name" role= " menuitem" @click= " action(name)" > {{name}}< / button>< / div>
161165< div v- if = " confirming" : data- platform= " popupPlatform" class = " annotation-ui annotation-modal" >< section ref= " confirmPopup" class = " annotation-confirm annotation-delete-confirm" role= " alertdialog" aria- modal= " true" : aria- label= " `删除这条${confirming.kind==='highlight'?'高亮':'批注'}?`" @keydown .tab .prevent = " confirmPopup.querySelectorAll('button')[($event.target===confirmPopup.querySelector('button'))?1:0].focus()" >< p> 删除这条{{confirming .kind === ' highlight' ? ' 高亮' : ' 批注' }}?< / p>< div class = " annotation-confirm-actions" >< button @click= " confirming=null" > 取消< / button>< button class = " annotation-confirm-destructive" @click= " remove(confirming)" > 删除< / button>< / div>< / section>< / div>
@@ -273,3 +277,12 @@ watch(()=>props.zoom,()=>{toolbar.value=false;updatePopupLayout();});
273277.annotation - ui[data- platform= " darwin" ] .annotation - delete - confirm{background: var (-- annotation- surface- solid);border: 1px solid var (-- annotation- border);border- radius: 14px ;box- shadow: inset 0 1px 0 var (-- annotation- inner- edge),var (-- annotation- shadow);color: var (-- annotation- foreground);}
274278.annotation - confirm- actions button: focus- visible{outline: 3px solid #007aff80 ;outline- offset: 2px ;}
275279< / style>
280+
281+ < style>
282+ .annotation - toolbar{display: flex;flex- direction: column;align- items: stretch;gap: 0 ;max- width: calc (100vw - 24px );box- sizing: border- box;}
283+ .annotation - toolbar- actions{display: flex;align- items: center;gap: 4px ;}
284+ .annotation - toolbar- comment{border- top: 1px solid var (-- annotation- divider,#8883 );margin- top: 8px ;padding: 12px 8px 6px ;min- width: 0 ;}
285+ .annotation - toolbar- comment p{margin: 0 ;white- space: pre- wrap;overflow- wrap: anywhere;max- height: min (220px ,35vh );overflow: auto;line- height: 1.5 ;user- select: text;}
286+ .annotation - toolbar- with - comment{width: max- content;}
287+ .annotation - toolbar- comment{width: 0 ;min- width: 100 % ;box- sizing: border- box;}
288+ < / style>
0 commit comments