zotero-chinese / website

Zotero 中文社区官方网站源码 | Source code for website of Zotero Chinese community
https://zotero-chinese.com
MIT License
6 stars 2 forks source link

CSL 页面:兼容插件分发 #61

Open northword opened 5 months ago

northword commented 5 months ago

debug with:

Zotero.openInViewer("http://localhost:5173/styles/")

可能的方案:

以下方案大部分都需要一个组件用来与插件/ Zotero 交互,可能是打开链接,可能是剪贴板,可能是调用程序。

理想中,这个组件在 非 Zotero 打开时,应该隐藏,可以考虑通过 UA,使用 https://github.com/faisalman/ua-parser-js 解析 UA。

-> 不可行,Zotero 没有特殊UA。已证实不可行:Zotero 内置浏览器 UA 为

Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0

因此这个组件可以始终显示,但做好提示依赖 xx 插件。

方案1

网页提供不加 _target=black 的 csl 超链接,理想状态该链接被 Zotero 识别并安装。

-> 不可行,Zotero 会把所有链接都打开到浏览器,包括 zotero.org/styles 也是,可能是bug,也可能是新的安装途径。

方案2

组件提供一个复制链接的按钮,

插件打开页面时监听剪贴板,直到特殊格式的字符串出现(用户点击按钮),获取到URL,调用 Zotero.Styles.install。页面关闭时停止监听剪贴板。

方案3

组件提供一个按钮,用来拉起 protocol,例如 zotero://jasminum/install-style=url

插件方注册这个protocol,解析出 url,并调用相关方法从 url 安装样式。

安装 csl 方法:

Zotero.Styles.install({ url: downloadLinks.github }, downloadLinks.github, false)

注册 protocol 可以参考:https://github.com/windingwind/zotero-plugin-toolkit/blob/master/src/utils/pluginBridge.ts

northword commented 5 months ago

补充说明

Zotero.openInViewer 可以传入一个 onLoad 回调,可以在这个回调里注册监听页面关闭事件。似乎这个方法本身就提供了很多钩子?

https://github.com/zotero/zotero/commit/0757109c16307076ab53372f506007c3f16b7a08

可以通过 onLoad 回调修改 document,

Zotero.openInViewer("http://localhost:5173/styles/") ```js [object Window] { "close": function close() { [native code] } "stop": function stop() { [native code] } "focus": function focus() { [native code] } "blur": function blur() { [native code] } "open": function open() { [native code] } "alert": function alert() { [native code] } "confirm": function confirm() { [native code] } "prompt": function prompt() { [native code] } "print": function print() { [native code] } "printPreview": function printPreview() { [native code] } "postMessage": function postMessage() { [native code] } "captureEvents": function captureEvents() { [native code] } "releaseEvents": function releaseEvents() { [native code] } "getSelection": function getSelection() { [native code] } "getComputedStyle": function getComputedStyle() { [native code] } "matchMedia": function matchMedia() { [native code] } "moveTo": function moveTo() { [native code] } "moveBy": function moveBy() { [native code] } "resizeTo": function resizeTo() { [native code] } "resizeBy": function resizeBy() { [native code] } "scroll": function scroll() { [native code] } "scrollTo": function scrollTo() { [native code] } "scrollBy": function scrollBy() { [native code] } "getDefaultComputedStyle": function getDefaultComputedStyle() { [native code] } "scrollByLines": function scrollByLines() { [native code] } "scrollByPages": function scrollByPages() { [native code] } "sizeToContent": function sizeToContent() { [native code] } "updateCommands": function updateCommands() { [native code] } "find": function find() { [native code] } "dump": function dump() { [native code] } "setResizable": function setResizable() { [native code] } "getAttention": function getAttention() { [native code] } "getAttentionWithCycleCount": function getAttentionWithCycleCount() { [native code] } "setCursor": function setCursor() { [native code] } "maximize": function maximize() { [native code] } "minimize": function minimize() { [native code] } "restore": function restore() { [native code] } "getWorkspaceID": function getWorkspaceID() { [native code] } "moveToWorkspace": function moveToWorkspace() { [native code] } "notifyDefaultButtonLoaded": function notifyDefaultButtonLoaded() { [native code] } "getGroupMessageManager": function getGroupMessageManager() { [native code] } "promiseDocumentFlushed": function promiseDocumentFlushed() { [native code] } "requestIdleCallback": function requestIdleCallback() { [native code] } "cancelIdleCallback": function cancelIdleCallback() { [native code] } "getRegionalPrefsLocales": function getRegionalPrefsLocales() { [native code] } "getWebExposedLocales": function getWebExposedLocales() { [native code] } "requestAnimationFrame": function requestAnimationFrame() { [native code] } "cancelAnimationFrame": function cancelAnimationFrame() { [native code] } "reportError": function reportError() { [native code] } "btoa": function btoa() { [native code] } "atob": function atob() { [native code] } "setTimeout": function setTimeout() { [native code] } "clearTimeout": function clearTimeout() { [native code] } "setInterval": function setInterval() { [native code] } "clearInterval": function clearInterval() { [native code] } "queueMicrotask": function queueMicrotask() { [native code] } "createImageBitmap": function createImageBitmap() { [native code] } "structuredClone": function structuredClone() { [native code] } "fetch": function fetch() { [native code] } "self": <> "name": "" "history": [object History] <> "customElements": [object CustomElementRegistry] <> "locationbar": [object BarProp] <> "menubar": [object BarProp] <> "personalbar": [object BarProp] <> "scrollbars": [object BarProp] <> "statusbar": [object BarProp] <> "toolbar": [object BarProp] <> "status": "" "closed": false "event": undefined "frames": <> "length": 0 "opener": null "parent": <> "frameElement": null "navigator": [object Navigator] <> "clientInformation": [object Navigator] <> "external": [object External] <> "screen": [object Screen] <> "innerWidth": 118 "innerHeight": 0 "scrollX": 0 "pageXOffset": 0 "scrollY": 0 "pageYOffset": 0 "screenLeft": 0 "screenTop": 0 "screenX": 0 "screenY": 0 "outerWidth": 131 "outerHeight": 36 "performance": { "timeOrigin": 1718634156827.9246, "timing": { "navigationStart": 1718634156828, "unloadEventStart": 0, "unloadEventEnd": 0, "redirectStart": 0, "redirectEnd": 0, "fetchStart": 1718634156828, "domainLookupStart": 1718634156828, "domainLookupEnd": 1718634156828, "connectStart": 1718634156828, "connectEnd": 1718634156828, "secureConnectionStart": 1718634156828, "requestStart": 1718634156828, "responseStart": 1718634156828, "responseEnd": 1718634156828, "domLoading": 0, "domInteractive": 0, "domContentLoadedEventStart": 0, "domContentLoadedEventEnd": 0, "domComplete": 0, "loadEventStart": 0, "loadEventEnd": 0 }, "navigation": { "type": 0, "redirectCount": 0 } } "mozInnerScreenX": 0 "mozInnerScreenY": 0 "devicePixelRatio": 2 "scrollMaxX": 0 "scrollMaxY": 0 "fullScreen": false "ondevicemotion": null "ondeviceorientation": null "ondeviceorientationabsolute": null "InstallTrigger": null "windowState": 3 "isFullyOccluded": false "browserDOMWindow": null "messageManager": [object ChromeMessageBroadcaster] <> "isChromeWindow": true "intlUtils": [object IntlUtils] <> "visualViewport": [object VisualViewport] <> "crypto": [object Crypto] <> "onabort": null "onblur": null "onfocus": null "onauxclick": null "onbeforeinput": null "oncanplay": null "oncanplaythrough": null "onchange": null "onclick": null "onclose": null "oncontextmenu": null "oncopy": null "oncuechange": null "oncut": null "ondblclick": null "ondrag": null "ondragend": null "ondragenter": null "ondragexit": null "ondragleave": null "ondragover": null "ondragstart": null "ondrop": null "ondurationchange": null "onemptied": null "onended": null "onformdata": null "oninput": null "oninvalid": null "onkeydown": null "onkeypress": null "onkeyup": null "onload": null "onloadeddata": null "onloadedmetadata": null "onloadstart": null "onmousedown": null "onmouseenter": null "onmouseleave": null "onmousemove": null "onmouseout": null "onmouseover": null "onmouseup": null "onwheel": null "onpaste": null "onpause": null "onplay": null "onplaying": null "onprogress": null "onratechange": null "onreset": null "onresize": null "onscroll": null "onscrollend": null "onsecuritypolicyviolation": null "onseeked": null "onseeking": null "onselect": null "onslotchange": null "onstalled": null "onsubmit": null "onsuspend": null "ontimeupdate": null "onvolumechange": null "onwaiting": null "onselectstart": null "onselectionchange": null "ontoggle": null "onpointercancel": null "onpointerdown": null "onpointerup": null "onpointermove": null "onpointerout": null "onpointerover": null "onpointerenter": null "onpointerleave": null "ongotpointercapture": null "onlostpointercapture": null "onmozfullscreenchange": null "onmozfullscreenerror": null "onanimationcancel": null "onanimationend": null "onanimationiteration": null "onanimationstart": null "ontransitioncancel": null "ontransitionend": null "ontransitionrun": null "ontransitionstart": null "onwebkitanimationend": null "onwebkitanimationiteration": null "onwebkitanimationstart": null "onwebkittransitionend": null "onerror": null "speechSynthesis": [object SpeechSynthesis] <> "onafterprint": null "onbeforeprint": null "onbeforeunload": null "onhashchange": null "onlanguagechange": null "onmessage": null "onmessageerror": null "onoffline": null "ononline": null "onpagehide": null "onpageshow": null "onpopstate": null "onrejectionhandled": null "onstorage": null "onunhandledrejection": null "onunload": null "ongamepadconnected": null "ongamepaddisconnected": null "localStorage": <> "origin": "null" "crossOriginIsolated": false "isSecureContext": true "indexedDB": [object IDBFactory] <> "caches": [object CacheStorage] <> "sessionStorage": <> "STATE_MAXIMIZED": 1 "STATE_MINIMIZED": 2 "STATE_NORMAL": 3 "STATE_FULLSCREEN": 4 "mozScrollSnap": function mozScrollSnap() { [native code] } "sizeToContentConstrained": function sizeToContentConstrained() { [native code] } "openDialog": function openDialog() { [native code] } "getInterface": function getInterface() { [native code] } "shouldReportForServiceWorkerScope": function shouldReportForServiceWorkerScope() { [native code] } "setScrollMarks": function setScrollMarks() { [native code] } "controllers": [object Object] <> "realFrameElement": null "docShell": [object Object] <> "browsingContext": [object CanonicalBrowsingContext] <> "desktopToDeviceScale": 1 "screenEdgeSlopX": 0 "screenEdgeSlopY": 0 "scrollMinX": 0 "scrollMinY": 0 "windowRoot": [object WindowRoot] <> "windowUtils": [object Object] <> "windowGlobalChild": [object WindowGlobalChild] <> "clientPrincipal": [object Object] <> "isInFullScreenTransition": false "Glean": [object GleanImpl] <> "GleanPings": [object GleanPingsImpl] <> "window": <> "document": [object HTMLDocument] <> "location": [object Location] <> "top": <> "addEventListener": function addEventListener() { [native code] } "removeEventListener": function removeEventListener() { [native code] } "dispatchEvent": function dispatchEvent() { [native code] } "setEventHandler": function setEventHandler() { [native code] } "getEventHandler": function getEventHandler() { [native code] } "ownerGlobal": <> } ```
northword commented 5 months ago

CC @jiaojiaodubai 我觉得方案3的可行性比较高。

jiaojiaodubai commented 5 months ago

我有空在 Jasminum 7 临时仓库 试验一下,待基本功能完善我会 transfer 给 @l0o0