Skip to content

Commit 325396f

Browse files
committed
fix(macOS): use native annotation and translation status dialogs
1 parent 1ec8d6c commit 325396f

4 files changed

Lines changed: 30 additions & 3 deletions

File tree

‎electron/main.mjs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,17 @@ else {
233233
try{const document=await readSystemPDF(path),source={path,reliable:true,preserveWithoutPath:false,bytes:Buffer.from(document.bytes)};state.unkeyedAnnotationSource=source;state.annotationSources.set(path,source);const ticket=randomBytes(16).toString('hex');state.tickets.set(ticket,path);return {...document,ticket};}catch{state.performance.hasDocument=false;return {error:'Could not open this PDF. Check file access and the 50 MB limit.'};}
234234
});
235235
ipcMain.handle('previewAnnotations:prepare',async(event,value)=>{const target=trustedWindow(event);if(!(value instanceof Uint8Array)||value.byteLength>50*1024*1024)throw Error('Invalid annotation PDF bytes.');const imported=await importPDFAnnotations(value);windows.get(target).unkeyedAnnotationSource={bytes:Buffer.from(value),reliable:false,preserveWithoutPath:true};return {...imported,bytes:new Uint8Array(await embedAnnotations(value,[],imported.nativeRefs))};});
236+
ipcMain.handle('previewAnnotations:confirmDelete',async(event,kind)=>{
237+
const target=trustedWindow(event);
238+
if(!['highlight','comment'].includes(kind))throw Error('Invalid annotation kind.');
239+
const {response}=await dialog.showMessageBox(target,{type:'warning',message:`删除这条${kind==='highlight'?'高亮':'批注'}?`,buttons:['取消','删除'],defaultId:0,cancelId:0,noLink:true});
240+
return response===1;
241+
});
242+
ipcMain.handle('recents:statusDialog',async(event,value)=>{
243+
const target=trustedWindow(event);
244+
if(!value||typeof value.title!=='string'||value.title.length>200||typeof value.detail!=='string'||value.detail.length>12000||typeof value.close!=='string'||value.close.length>100)throw Error('Invalid status dialog.');
245+
await dialog.showMessageBox(target,{type:'info',message:value.title,detail:value.detail,buttons:[value.close],defaultId:0,cancelId:0,noLink:true});
246+
});
236247
ipcMain.handle('previewAnnotations:palette',async event=>{trustedWindow(event);return annotations.palette();});
237248
ipcMain.handle('previewAnnotations:contextMenu',(event,kind)=>{
238249
const target=trustedWindow(event);

‎electron/preload.cjs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ contextBridge.exposeInMainWorld('previewTestMode',process.argv.includes('--previ
2525

2626
contextBridge.exposeInMainWorld('previewAnnotations',Object.freeze({
2727
contextMenu:kind=>ipcRenderer.invoke('previewAnnotations:contextMenu',kind),
28+
confirmDelete:kind=>ipcRenderer.invoke('previewAnnotations:confirmDelete',kind),
2829
palette:()=>ipcRenderer.invoke('previewAnnotations:palette'),
2930
markDeleteHint:()=>ipcRenderer.invoke('previewAnnotations:markDeleteHint'),
3031
prepare:bytes=>ipcRenderer.invoke('previewAnnotations:prepare',bytes),
@@ -55,7 +56,7 @@ contextBridge.exposeInMainWorld('previewWindow',Object.freeze({
5556
onFullscreen:callback=>{const listener=(_event,value)=>callback(value);ipcRenderer.on('window:fullscreen',listener);return ()=>ipcRenderer.removeListener('window:fullscreen',listener);}
5657
}));
5758

58-
contextBridge.exposeInMainWorld('previewRecents',Object.freeze({list:()=>ipcRenderer.invoke('recents:list'),clear:()=>ipcRenderer.invoke('recents:clear'),open:id=>ipcRenderer.invoke('recents:open',id),openWindow:id=>ipcRenderer.invoke('recents:openWindow',id),preview:id=>ipcRenderer.invoke('recents:preview',id),contextMenu:id=>ipcRenderer.invoke('recents:contextMenu',id),setThumbnail:(id,thumbnail)=>ipcRenderer.invoke('recents:setThumbnail',{id,thumbnail}),setView:(id,view)=>ipcRenderer.invoke('recents:setView',{id,view}),setTranslationStatus:(id,status)=>ipcRenderer.invoke('recents:setTranslationStatus',{id,status}),remember:(file,ticket,thumbnail)=>ipcRenderer.invoke('recents:remember',ticket?{ticket,thumbnail}:{path:webUtils.getPathForFile(file),thumbnail})}));
59+
contextBridge.exposeInMainWorld('previewRecents',Object.freeze({statusDialog:value=>ipcRenderer.invoke('recents:statusDialog',value),list:()=>ipcRenderer.invoke('recents:list'),clear:()=>ipcRenderer.invoke('recents:clear'),open:id=>ipcRenderer.invoke('recents:open',id),openWindow:id=>ipcRenderer.invoke('recents:openWindow',id),preview:id=>ipcRenderer.invoke('recents:preview',id),contextMenu:id=>ipcRenderer.invoke('recents:contextMenu',id),setThumbnail:(id,thumbnail)=>ipcRenderer.invoke('recents:setThumbnail',{id,thumbnail}),setView:(id,view)=>ipcRenderer.invoke('recents:setView',{id,view}),setTranslationStatus:(id,status)=>ipcRenderer.invoke('recents:setTranslationStatus',{id,status}),remember:(file,ticket,thumbnail)=>ipcRenderer.invoke('recents:remember',ticket?{ticket,thumbnail}:{path:webUtils.getPathForFile(file),thumbnail})}));
5960

6061
contextBridge.exposeInMainWorld('previewActivity',Object.freeze({current:()=>ipcRenderer.invoke('window:activity'),onChange:callback=>{const listener=(_event,value)=>callback(value);ipcRenderer.on('activity:changed',listener);return ()=>ipcRenderer.removeListener('activity:changed',listener);}}));
6162

‎src/ReadingAnnotations.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ async function openContextMenu(a,event){
136136
}
137137
menu.value={a,x:event.clientX,y:event.clientY};
138138
}
139-
async function action(name,a=menu.value?.a){menu.value=null;if(!a)return;try{if(name==='删除'){confirming.value=a;await nextTick();confirmPopup.value?.querySelector('button')?.focus();return;}if(name==='修改'){await comment(a);return;}const text=[a.text,a.comment].filter(Boolean).join('\n\n');if(name==='分享'&&window.previewAnnotations?.share&&await window.previewAnnotations.share(text))return;if(name==='分享'&&navigator.share){await navigator.share({text});return;}if(name==='Hand over to AI'){if(!window.previewAnnotations?.handover)throw Error('请在桌面客户端中交接到本机 ChatGPT 或 Claude。');const result=await window.previewAnnotations.handover(text);if(!result.cancelled)emit('notice',{text:`已打开 ${result.client},内容已复制,请粘贴并发送`,...pointer.value});return;}if(name==='Paraphrase'){assistController?.abort();const controller=assistController=new AbortController();const result={title:'Paraphrase',text:'',busy:true,error:''};assistance.value=result;try{const response=await fetch('/api/reading-assist',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'paraphrase',text:a.text,comment:a.comment}),signal:controller.signal});const data=await response.json();if(!response.ok)throw Error(data.error||'请求失败');result.text=data.text;}catch(e){if(e.name!=='AbortError')result.error=e.message;}finally{result.busy=false;if(!controller.signal.aborted)assistance.value={...result};}return;}await (window.previewClipboard?.writeText||navigator.clipboard.writeText.bind(navigator.clipboard))(text);emit('notice',{text:name==='分享'?'已复制分享内容':'已复制',...pointer.value});}catch(e){if(e.name!=='AbortError')emit('notice',{text:'无法完成操作:'+e.message,...pointer.value});}}
139+
async function action(name,a=menu.value?.a){menu.value=null;if(!a)return;try{if(name==='删除'){if(popupPlatform==='darwin'&&window.previewAnnotations?.confirmDelete){if(await window.previewAnnotations.confirmDelete(a.kind)){const current=props.annotations.find(item=>item.id===a.id);if(current)remove(current);}}else{confirming.value=a;await nextTick();confirmPopup.value?.querySelector('button')?.focus();}return;}if(name==='修改'){await comment(a);return;}const text=[a.text,a.comment].filter(Boolean).join('\n\n');if(name==='分享'&&window.previewAnnotations?.share&&await window.previewAnnotations.share(text))return;if(name==='分享'&&navigator.share){await navigator.share({text});return;}if(name==='Hand over to AI'){if(!window.previewAnnotations?.handover)throw Error('请在桌面客户端中交接到本机 ChatGPT 或 Claude。');const result=await window.previewAnnotations.handover(text);if(!result.cancelled)emit('notice',{text:`已打开 ${result.client},内容已复制,请粘贴并发送`,...pointer.value});return;}if(name==='Paraphrase'){assistController?.abort();const controller=assistController=new AbortController();const result={title:'Paraphrase',text:'',busy:true,error:''};assistance.value=result;try{const response=await fetch('/api/reading-assist',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'paraphrase',text:a.text,comment:a.comment}),signal:controller.signal});const data=await response.json();if(!response.ok)throw Error(data.error||'请求失败');result.text=data.text;}catch(e){if(e.name!=='AbortError')result.error=e.message;}finally{result.busy=false;if(!controller.signal.aborted)assistance.value={...result};}return;}await (window.previewClipboard?.writeText||navigator.clipboard.writeText.bind(navigator.clipboard))(text);emit('notice',{text:name==='分享'?'已复制分享内容':'已复制',...pointer.value});}catch(e){if(e.name!=='AbortError')emit('notice',{text:'无法完成操作:'+e.message,...pointer.value});}}
140140
onMounted(()=>{textObserver=new MutationObserver(records=>{if(records.some(r=>r.target.closest?.('.reading-text-layer,.reading-paragraphs')))updatePopupLayout();});if(props.host())textObserver.observe(props.host(),{subtree:true,childList:true,characterData:true,attributes:true,attributeFilter:['style','class']});popupObserver=new ResizeObserver(updatePopupLayout);window.addEventListener('resize',updatePopupLayout);window.addEventListener('scroll',updatePopupLayout,true);void window.previewAnnotations?.palette?.().then(value=>{favorite.value=value.favorite;if(value.deleteHintShown)localStorage.setItem('annotationDeleteHint','1');}).catch(()=>{});document.addEventListener('pointerup',capture);document.addEventListener('pointerdown',outside);document.addEventListener('keydown',key);document.addEventListener('pointermove',observePointer);});
141141
onBeforeUnmount(()=>{textObserver?.disconnect();popupObserver?.disconnect();cancelAnimationFrame(layoutFrame);window.removeEventListener('resize',updatePopupLayout);window.removeEventListener('scroll',updatePopupLayout,true);cancelAnimationFrame(motionFrame);eraseTimers.forEach(clearTimeout);eraseTimers.clear();assistController?.abort();clearTimeout(timer);document.removeEventListener('pointerup',capture);document.removeEventListener('pointerdown',outside);document.removeEventListener('keydown',key);document.removeEventListener('pointermove',observePointer);});
142142
watch([opened,editor,toolbar],async()=>{await nextTick();popupObserver?.disconnect();const popup=editor.value?editorPopup.value:opened.value?readerPopup.value:toolbar.value?toolbarPopup.value:null;if(popup)popupObserver?.observe(popup);updatePopupLayout();},{flush:'post'});

‎src/RecentTranslationStatus.vue‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,22 @@ const emit=defineEmits(['close']);
66
const dialog=ref(null);
77
const platform=window.previewAppearance?.platform||'web';
88
const languageKeys={'Simplified Chinese':'simplifiedChinese','Traditional Chinese':'traditionalChinese',English:'english',Japanese:'japanese',Korean:'korean',French:'french',German:'german',Spanish:'spanish'};
9-
onMounted(()=>dialog.value.showModal());
9+
onMounted(async()=>{
10+
if(platform==='darwin'&&window.previewRecents?.statusDialog){
11+
const status=props.document.translationStatus;
12+
const rows=status?[
13+
['total',status.totalPages],['completed',status.completedPages],['partial',status.partialPages],
14+
['pending',status.totalPages-status.completedPages-status.partialPages-status.failedPages],['failed',status.failedPages],
15+
['engine',t({pdf_inspector:'engine.ultraFast',pdf_math_fast:'engine.fast',pdf_math_precise:'engine.precise'}[status.engine])],
16+
['language',languageKeys[status.language]?t('languages.'+languageKeys[status.language]):status.language],
17+
['updated',new Date(status.updatedAt).toLocaleString(uiLanguage.value)]
18+
].map(([key,value])=>`${t('recentStatus.'+key)}:${value}`).join('\n'):'';
19+
try{
20+
await window.previewRecents.statusDialog({title:t('recentStatus.title'),detail:[props.document.name,t(status?'recentStatus.snapshot':'recentStatus.unknown'),rows].filter(Boolean).join('\n\n'),close:t('recentStatus.close')});
21+
emit('close');
22+
}catch{dialog.value.showModal();}
23+
}else dialog.value.showModal();
24+
});
1025
</script>
1126
<template>
1227
<dialog ref="dialog" class="recent-status-dialog" :data-platform="platform" aria-labelledby="recent-status-title" @close="emit('close')" @click="event=>{if(event.target===dialog)dialog.close();}">

0 commit comments

Comments
 (0)