您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A port of Zhongwen Chinese-English Pop-Up Dictionary as UserScript for Safari
// ==UserScript== // @name Hanzipopup - Popup Dictionary for Chinese Language // @namespace https://krmanik.github.io/hanzipopup/ // @version 0.0.1 // @description A port of Zhongwen Chinese-English Pop-Up Dictionary as UserScript for Safari // @homepageURL https://krmanik.github.io/hanzipopup/ // @supportURL https://github.com/krmanik/hanzipopup // @icon https://krmanik.github.io/hanzipopup/icon.png // @match *://*/* // @exclude-match *://*/*/wordlist.* // @exclude-match *://*/*/tts.* // @inject-into content // @grant GM.addStyle // @grant GM.getValue // @grant GM.setValue // @grant GM.xmlHttpRequest // @license GPL-2.0 // ==/UserScript== /* Hanzipopup - A Chinese-English Pop-Up Dictionary UserScript Copyright (C) 2024 krmanik https://github.com/krmanik/hanzipopup --- Zhongwen - A Chinese-English Pop-Up Dictionary Copyright (C) 2010-2023 Christian Schiller https://chrome.google.com/extensions/detail/kkmlkkjojmombglmlpbpapmhcaljjkde --- Originally based on Rikaikun 0.8 Copyright (C) 2010 Erek Speed http://code.google.com/p/rikaikun/ --- Originally based on Rikaichan 1.07 by Jonathan Zarate http://www.polarcloud.com/ --- Originally based on RikaiXUL 0.4 by Todd Rudick http://www.rikai.com/ http://rikaixul.mozdev.org/ --- This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA --- Please do not change or remove any of the copyrights or links to web pages when modifying any of the files. */ 'use strict'; // https://github.com/cschiller/zhongwen // https://github.com/krmanik/hanzipopup let style = ` .unselectable { -khtml-user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; } #custom-popup-container { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; box-shadow: rgba(0, 0, 0, 0.8) 0px 0px 200px 28px; z-index: 99999999999; width: 90%; height: 400px; } #custom-title-container { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ccc; padding: 8px 8px 8px 18px; } .custom-popup-label { display: flex; margin-bottom: 2px; font-weight: bold; font-size: 14px; border-bottom: 1px solid #ccc; width: 95%; padding-bottom: 4px; place-items: center; align-items: center; } #custom-popup-container a { text-decoration: none; color: #2196F3; } #custom-popup-container input[type=checkbox], input[type=radio] { accent-color: #2196F3; } #zhongwenPopupContainer { height: 348px; overflow-y: scroll; margin-left: 20px; margin-bottom: 60px; } #zhongwenPopupContainer::-webkit-scrollbar { width: 6px; } #zhongwenPopupContainer::-webkit-scrollbar-track { background: #fff; } #zhongwenPopupContainer::-webkit-scrollbar-thumb { background: #4CAF50; border-radius: 20px; } #zhongwenPopupContainer::-webkit-scrollbar-thumb:hover { background: #555; } @media (prefers-color-scheme: dark) { #custom-popup-container { background-color: #2f2f2f !important; color: #fff; } #zhongwenPopupContainer { select { color: #fff; background-color: #2f2f2f !important; margin: 0; } } #zhongwenPopupContainer::-webkit-scrollbar-track { background: #2f2f2f; } } /* Generic Styles */ #zhongwen-window, #zhongwen-window * { width: auto; height: auto; background: transparent; border: none !important; margin: 0px; padding: 0px; letter-spacing: normal; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: normal; font-weight: normal; font-size: 12px; font-family: Tahoma, Geneva, sans-serif; visibility: visible; line-height: initial; } #zhongwen-window { position: absolute; z-index: 99999999; border: 1px solid #d0d0d0 !important; padding: 4px; background: #e6f4ff; top: 5px; left: 5px; min-width: 100px; border-radius: 5px; box-shadow: 10px 10px 5px -5px #999999; } #zhongwen-window .w-hanzi { font-size: 32px; margin-right: 0.7em; } #zhongwen-window .w-pinyin { font-size: 18px; } #zhongwen-window .w-def { font-size: 16px; } #zhongwen-window .w-zhuyin { font-family: PMingLiU, 'Apple LiGothic', sans-serif; font-size: 16px; } #zhongwen-window .w-hanzi-small { font-size: 18px; margin-right: 0.7em; } #zhongwen-window .w-pinyin-small { font-size: 16px; } #zhongwen-window .w-def-small { font-size: 12px; } #zhongwen-window .w-zhuyin-small { font-family: PMingLiU, 'Apple LiGothic', sans-serif; font-size: 12px; } #zhongwen-window .grammar { font-weight: bold; } #zhongwen-window .vocab { font-weight: bold; } /* Yellow Background */ #zhongwen-window.background-yellow, #zhongwen-window.background-yellow * { color: #000000; background: #ffffbf; } #zhongwen-window.background-yellow .w-hanzi, #zhongwen-window.background-yellow .w-hanzi-small { color: #7070e0; } #zhongwen-window.background-yellow .grammar { color: #00008b; } #zhongwen-window.background-yellow .vocab { color: #00008b; } /* Lightblue Background */ #zhongwen-window.background-lightblue, #zhongwen-window.background-lightblue * { color: #000000; background: #e6f4ff; } #zhongwen-window.background-lightblue .w-hanzi, #zhongwen-window.background-lightblue .w-hanzi-small { color: #3082bf; } #zhongwen-window.background-lightblue .w-pinyin, #zhongwen-window.background-lightblue .w-pinyin-small { color: #00b366; } #zhongwen-window.background-lightblue .grammar { color: #00008b; } #zhongwen-window.background-lightblue .vocab { color: #00008b; } /* Blue Background */ #zhongwen-window.background-blue, #zhongwen-window.background-blue * { color: #ffffff; background: #5c73b8; } #zhongwen-window.background-blue .w-hanzi, #zhongwen-window.background-blue .w-hanzi-small { color: #b7e7ff; } #zhongwen-window.background-blue .w-pinyin, #zhongwen-window.background-blue .w-pinyin-small { color: #c0ffc0; } #zhongwen-window.background-blue .grammar { color: #add8e6; } #zhongwen-window.background-blue .vocab { color: #add8e6; } /* Black Background */ #zhongwen-window.background-black, #zhongwen-window.background-black * { color: #ffffff; background: #000000; } #zhongwen-window.background-black .w-hanzi, #zhongwen-window.background-black .w-hanzi-small { color: #7070e0; } #zhongwen-window.background-black .w-pinyin, #zhongwen-window.background-black .w-pinyin-small { color: #20a020; } #zhongwen-window.background-black .grammar { color: #add8e6; } #zhongwen-window.background-black .vocab { color: #add8e6; } /* Standard Tone Colors */ #zhongwen-window.tonecolor-standard .tone1 { color: #ee363e; } #zhongwen-window.tonecolor-standard .tone2 { color: #f47c36; } #zhongwen-window.tonecolor-standard .tone3 { color: #73bb4f; } #zhongwen-window.tonecolor-standard .tone4 { color: #649cd3; } #zhongwen-window.tonecolor-standard .tone5 { color: #a0a0a0; } /* Pleco Tone Colors */ #zhongwen-window.tonecolor-pleco .tone1 { color: #e30000; } #zhongwen-window.tonecolor-pleco .tone2 { color: #02b31c; } #zhongwen-window.tonecolor-pleco .tone3 { color: #1510f0; } #zhongwen-window.tonecolor-pleco .tone4 { color: #8900bf; } #zhongwen-window.tonecolor-pleco .tone5 { color: #777777; } /* Hanping Tone Colors */ #zhongwen-window.tonecolor-hanping .tone1 { color: #64b4ff; } #zhongwen-window.tonecolor-hanping .tone2 { color: #30b030; } #zhongwen-window.tonecolor-hanping .tone3 { color: #f08000; } #zhongwen-window.tonecolor-hanping .tone4 { color: #d00020; } #zhongwen-window.tonecolor-hanping .tone5 { color: #a0a0a0; } #hanzipopup-fab-container, #custom-popup-container { .info-button { background: #828282; padding: 8px; margin: 1px; border-radius: 8px; color: white; text-align: center; cursor: pointer; border: none; } #zhongwen-info-buttons { display: flex; flex-direction: column; position: fixed; bottom: 64px; padding: 4px; z-index: 9999999999; } .option-div { margin: 4px 0px 4px 2px; } .h-icon-btn { width: 36px; height: 36px; position: relative; border: none; border-radius: 10%; cursor: pointer; outline: none; top: unset; left: unset; right: unset; bottom: unset; } .h-icon-btn:before { content: ""; position: absolute; left: 6px; top: 0; bottom: 0; width: 24px; } .init { border-radius: 2px; display: inline-flex; width: 24px !important; height: 24px !important; cursor: pointer; } .init:before { left: 0px !important; position: absolute; } .hero:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='m476-80 182-480h84L924-80h-84l-43-122H603L560-80h-84ZM160-200l-56-56 202-202q-35-35-63.5-80T190-640h84q20 39 40 68t48 58q33-33 68.5-92.5T484-720H40v-80h280v-80h80v80h280v80H564q-21 72-63 148t-83 116l96 98-30 82-122-125-202 201Zm468-72h144l-72-204-72 204Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .enable:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='m476-80 182-480h84L924-80h-84l-43-122H603L560-80h-84ZM160-200l-56-56 202-202q-35-35-63.5-80T190-640h84q20 39 40 68t48 58q33-33 68.5-92.5T484-720H40v-80h280v-80h80v80h280v80H564q-21 72-63 148t-83 116l96 98-30 82-122-125-202 201Zm468-72h144l-72-204-72 204Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .disable:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='m476-80 182-480h84L924-80h-84l-43-122H603L560-80h-84ZM160-200l-56-56 202-202q-35-35-63.5-80T190-640h84q20 39 40 68t48 58q33-33 68.5-92.5T484-720H40v-80h280v-80h80v80h280v80H564q-21 72-63 148t-83 116l96 98-30 82-122-125-202 201Zm468-72h144l-72-204-72 204Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .option:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .save:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .view:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M360-240h440v-107H360v107ZM160-613h120v-107H160v107Zm0 187h120v-107H160v107Zm0 186h120v-107H160v107Zm200-186h440v-107H360v107Zm0-187h440v-107H360v107ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .show:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M500-640v320l160-160-160-160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .hide:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M460-320v-320L300-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm440-80h120v-560H640v560Zm-80 0v-560H200v560h360Zm80 0h120-120Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .tts:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M160-80q-33 0-56.5-23.5T80-160v-640q0-33 23.5-56.5T160-880h360l-80 80H160v640h440v-120h80v120q0 33-23.5 56.5T600-80H160Zm80-160v-80h280v80H240Zm0-120v-80h200v80H240Zm360 0L440-520H320v-200h120l160-160v520Zm80-122v-276q36 21 58 57t22 81q0 45-22 81t-58 57Zm0 172v-84q70-25 115-86.5T840-620q0-78-45-139.5T680-846v-84q104 27 172 112.5T920-620q0 112-68 197.5T680-310Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .prev:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M440-760v-80h80v80h-80Zm0 640v-80h80v80h-80ZM280-760v-80h80v80h-80Zm0 640v-80h80v80h-80ZM120-760v-80h80v80h-80Zm0 640v-80h80v80h-80Zm480 0v-80h80v-560h-80v-80h240v80h-80v560h80v80H600ZM280-320 120-480l160-160 56 56-63 64h287v80H273l63 64-56 56Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .next:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M440-120v-80h80v80h-80Zm0-640v-80h80v80h-80Zm160 640v-80h80v80h-80Zm0-640v-80h80v80h-80Zm160 640v-80h80v80h-80Zm0-640v-80h80v80h-80ZM120-120v-80h80v-560h-80v-80h240v80h-80v560h80v80H120Zm560-200-56-56 63-64H400v-80h287l-63-64 56-56 160 160-160 160Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .up:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M240-400v-480h480v480H240Zm80-80h320v-320H320v320Zm320 240v-80h80v80h-80Zm-400 0v-80h80v80h-80ZM640-80v-80h80v80h-80Zm-200 0v-80h80v80h-80Zm-200 0v-80h80v80h-80Zm240-560Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .down:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M240-80v-480h480v480H240Zm80-80h320v-320H320v320Zm-80-480v-80h80v80h-80Zm400 0v-80h80v80h-80ZM240-800v-80h80v80h-80Zm200 0v-80h80v80h-80Zm200 0v-80h80v80h-80ZM480-320Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .copy:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M120-220v-80h80v80h-80Zm0-140v-80h80v80h-80Zm0-140v-80h80v80h-80ZM260-80v-80h80v80h-80Zm100-160q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480Zm40 240v-80h80v80h-80Zm-200 0q-33 0-56.5-23.5T120-160h80v80Zm340 0v-80h80q0 33-23.5 56.5T540-80ZM120-640q0-33 23.5-56.5T200-720v80h-80Zm420 80Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .alt:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Zm280-40h320v-240H440v240Zm80-80v-80h160v80H520Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .more-info:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='m357-384 123-123 123 123 57-56-180-180-180 180 57 56ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .popup:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-480H200v480Zm80-280v-80h400v80H280Zm0 160v-80h240v80H280Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .pinyin:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M340-468h56v-82h-56v82ZM101-260l-17-45h39q5 0 9.5-3.5t4.5-8.5v-101l-52 17-11-44 63-19v-90H81v-42h56v-92h44v92h45v42h-46v77l40-13 6 42-46 16v125q0 18-10.5 32.5T142-260h-41Zm139 6-29-32q35-23 57.5-59t23.5-79h-68v-45h72v-81h-54v-41h253v44h-57v83h70l-2 40h-68v165h-44v-165h-58q-2 52-27.5 96.5T240-254Zm193-327-41-10 22-48q11-24 19-49l45 16q-10 23-21.5 45.5T433-581Zm-121-2q-10-23-21-45t-25-42l40-17q14 20 25 42t21 45l-40 17Zm390 249q28 0 54.5-13t48.5-37v-106q-23 3-42.5 7t-36.5 9q-45 14-67.5 35T636-390q0 26 18 41t48 15Zm-23 68q-57 0-90-32.5T556-387q0-52 33-85t106-53q23-6 50.5-11t59.5-9q-2-47-22-68.5T721-635q-26 0-51.5 9.5T604-592l-32-56q33-25 77.5-40.5T740-704q71 0 108 44t37 128v257h-67l-6-45q-28 25-61.5 39.5T679-266Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .font:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M560-160v-520H360v-120h520v120H680v520H560Zm-360 0v-320H80v-120h360v120H320v320H200Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .translate:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='m476-80 182-480h84L924-80h-84l-43-122H603L560-80h-84ZM160-200l-56-56 202-202q-35-35-63.5-80T190-640h84q20 39 40 68t48 58q33-33 68.5-92.5T484-720H40v-80h280v-80h80v80h280v80H564q-21 72-63 148t-83 116l96 98-30 82-122-125-202 201Zm468-72h144l-72-204-72 204Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .book:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M160-391h45l23-66h104l24 66h44l-97-258h-46l-97 258Zm81-103 38-107h2l38 107h-78Zm319-70v-68q33-14 67.5-21t72.5-7q26 0 51 4t49 10v64q-24-9-48.5-13.5T700-600q-38 0-73 9.5T560-564Zm0 220v-68q33-14 67.5-21t72.5-7q26 0 51 4t49 10v64q-24-9-48.5-13.5T700-380q-38 0-73 9t-67 27Zm0-110v-68q33-14 67.5-21t72.5-7q26 0 51 4t49 10v64q-24-9-48.5-13.5T700-490q-38 0-73 9.5T560-454ZM260-320q47 0 91.5 10.5T440-278v-394q-41-24-87-36t-93-12q-36 0-71.5 7T120-692v396q35-12 69.5-18t70.5-6Zm260 42q44-21 88.5-31.5T700-320q36 0 70.5 6t69.5 18v-396q-33-14-68.5-21t-71.5-7q-47 0-93 12t-87 36v394Zm-40 118q-48-38-104-59t-116-21q-42 0-82.5 11T100-198q-21 11-40.5-1T40-234v-482q0-11 5.5-21T62-752q46-24 96-36t102-12q58 0 113.5 15T480-740q51-30 106.5-45T700-800q52 0 102 12t96 36q11 5 16.5 15t5.5 21v482q0 23-19.5 35t-40.5 1q-37-20-77.5-31T700-240q-60 0-116 21t-104 59ZM280-499Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .message:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M320-520q17 0 28.5-11.5T360-560q0-17-11.5-28.5T320-600q-17 0-28.5 11.5T280-560q0 17 11.5 28.5T320-520Zm160 0q17 0 28.5-11.5T520-560q0-17-11.5-28.5T480-600q-17 0-28.5 11.5T440-560q0 17 11.5 28.5T480-520Zm160 0q17 0 28.5-11.5T680-560q0-17-11.5-28.5T640-600q-17 0-28.5 11.5T600-560q0 17 11.5 28.5T640-520ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .zi:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='M560-480h80v-120h-80v120ZM180-160l-20-80h60q8 0 14-6t6-14v-125q-17 7-33 13.5T176-360l-16-78q19-4 39.5-11t40.5-15v-136h-60v-80h60v-120h80v120h60v80h-60v96q15-9 29-18t27-18v80q-12 10-26 19.5T320-423v203q0 23-18.5 41.5T260-160h-80Zm222 0-44-66q42-28 72.5-75t42.5-99h-73v-80h80v-120h-60v-80h360v80h-60v120h80v80h-80v240h-80v-240h-86q-14 71-54.5 136.5T402-160Zm288-490-69-30q16-27 35.5-59t31.5-61l74 27q-15 29-35 62t-37 61Zm-189-8q-17-25-39-55t-42-53l72-34q18 23 38.5 52t37.5 53l-67 37Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } .close:before { position: absolute; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='25' viewBox='0 -960 960 960' fill='white'%3E%3Cpath d='m336-280 144-144 144 144 56-56-144-144 144-144-56-56-144 144-144-144-56 56 144 144-144 144 56 56ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z'%3E%3C/path%3E%3C/svg%3E") center / contain no-repeat; } } @keyframes tts-loading { to { transform: rotate(360deg); } } .tts-loading:before { content: '' !important; box-sizing: border-box !important; position: absolute !important; top: 50% !important; left: 50% !important; width: 20px !important; height: 20px !important; margin-top: -10px !important; margin-left: -10px !important; border-radius: 50% !important; border: 2px solid #fff !important; border-top-color: #ec440e !important; animation: tts-loading .6s linear infinite !important; -webkit-animation: tts-loading .6s linear infinite !important; } #hanzipopup-fab-container { position: fixed; transform: translate(-50%, -50%); bottom: 0px; right: 0px; width: 36px; height: 36px; border-radius: 50%; z-index: 9999999999; touch-action: none; .fab-btn { position: absolute; top: 0; left: 0; display: flex; justify-content: center; align-items: center; width: 36px; height: 36px; border-radius: 10%; background-color: #585e88; color: white; z-index: 1000; box-shadow: 0px 2px 18px -1px rgba(0, 0, 0, 0.3); outline: none; border: none; cursor: pointer; } ul { li { position: absolute; top: 0; left: 0; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; list-style-type: none; transition: .5s; border-radius: 10%; cursor: pointer; margin: 0 auto; } } &.fab-active { &.fab-active { :nth-child(1) { --nth-child: 1 } :nth-child(2) { --nth-child: 2 } :nth-child(3) { --nth-child: 3 } :nth-child(4) { --nth-child: 4 } :nth-child(5) { --nth-child: 5 } :nth-child(6) { --nth-child: 6 } :nth-child(7) { --nth-child: 7 } :nth-child(8) { --nth-child: 8 } li { top: 0; } &.left { li:nth-child(n+1) { left: calc(var(--nth-child) * 40px); transition-delay: calc(var(--nth-child) * 0.01s); } } &.right { li:nth-child(n+1) { left: calc(var(--nth-child) * -40px); transition-delay: calc(var(--nth-child) * 0.01s); } } } } } `; GM.addStyle(style); // https://github.com/krmanik/hanzipopup let defaultConfig = { popupColor: 'yellow', fontSize: 'small', zhuyin: false, grammar: true, vocab: true, simpTrad: 'classic', toneColorScheme: 'standard', enable: false, tts: false, prev: false, next: false, more: false, cnTtsEngine: 'browser', ttsvoice: "zh-CN-XiaoxiaoNeural", } async function setConfig(key, value) { let config = await GM.getValue('config', JSON.stringify(defaultConfig)); config = JSON.parse(config); config[key] = value; let jsonStr = JSON.stringify(config); return await GM.setValue('config', jsonStr); } async function getConfig() { let config = await GM.getValue('config', null); if (config === null) { config = JSON.stringify(defaultConfig); await GM.setValue('config', config); return defaultConfig; } config = JSON.parse(config); return config } // https://github.com/cschiller/zhongwen function altViewInfo() { altView = (altView + 1) % 3; triggerSearch(); } function copyToClip() { copyToClipboard(getTextForClipboard()); } function copyToClipboard(data) { let txt = document.createElement('textarea'); txt.style.position = "absolute"; txt.style.left = "-100%"; txt.value = data; document.body.appendChild(txt); txt.select(); document.execCommand('copy'); document.body.removeChild(txt); showPopup('Copied to clipboard', null, -1, -1); } function wordPrev() { let offset = selStartDelta; selStartDelta = --offset; let ret = triggerSearch(); if (ret === 0) { return } else if (ret === 2) { savedRangeNode = findPreviousTextNode(savedRangeNode.parentNode, savedRangeNode); savedRangeOffset = 0; offset = savedRangeNode.data.length; } } function wordNext() { selStartDelta += selStartIncrement; let ret = triggerSearch(); if (ret === 0) { return; } else if (ret === 2) { savedRangeNode = findNextTextNode(savedRangeNode.parentNode, savedRangeNode); savedRangeOffset = 0; selStartDelta = 0; selStartIncrement = 0; } } async function grammarInfo() { let config = await getConfig(); if (config['grammar'] && savedSearchResults.grammar) { let sel = encodeURIComponent(window.getSelection().toString()); // https://resources.allsetlearning.com/chinese/grammar/%E4%B8%AA let allset = 'https://resources.allsetlearning.com/chinese/grammar/' + sel; infoWindowOpen(allset); } } async function vocabInfo() { let config = await getConfig(); if (config['vocab'] && savedSearchResults.vocab) { let sel = encodeURIComponent(window.getSelection().toString()); // https://resources.allsetlearning.com/chinese/vocabulary/%E4%B8%AA let allset = 'https://resources.allsetlearning.com/chinese/vocabulary/' + sel; infoWindowOpen(allset); } } function tatoebaInfo() { let sel = encodeURIComponent(window.getSelection().toString()); // https://tatoeba.org/eng/sentences/search?from=cmn&to=eng&query=%E8%BF%9B%E8%A1%8C let tatoeba = 'https://tatoeba.org/eng/sentences/search?from=cmn&to=eng&query=' + sel; infoWindowOpen(tatoeba); } function movePopupUp() { altView = 0; popY -= 20; triggerSearch(); } function movePopupDown() { altView = 0; popY += 20; triggerSearch(); } function lineDictInfo() { // use the simplified character for linedict lookup let simp = savedSearchResults[0][0]; // https://english.dict.naver.com/english-chinese-dictionary/#/search?query=%E8%AF%8D%E5%85%B8 let linedict = 'https://english.dict.naver.com/english-chinese-dictionary/#/search?query=' + encodeURIComponent(simp); infoWindowOpen(linedict); } function forvoInfo() { let sel = encodeURIComponent(window.getSelection().toString()); // https://forvo.com/search/%E4%B8%AD%E6%96%87/zh/ var forvo = 'https://forvo.com/search/' + sel + '/zh/'; infoWindowOpen(forvo); } function dictInfo() { let sel = encodeURIComponent(window.getSelection().toString()); // https://dict.cn/%E7%BF%BB%E8%AF%91 let dictcn = 'https://dict.cn/' + sel; infoWindowOpen(dictcn); } function icibaInfo() { let sel = encodeURIComponent(window.getSelection().toString()); // https://www.iciba.com/%E4%B8%AD%E9%A4%90 let iciba = 'https://www.iciba.com/' + sel; infoWindowOpen(iciba); } function mdbgInfo() { let sel = encodeURIComponent(window.getSelection().toString()); // https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E4%B8%AD%E6%96%87 let mdbg = 'https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=' + sel; infoWindowOpen(mdbg); } function reversoInfo() { let sel = encodeURIComponent( window.getSelection().toString()); let reverso = 'https://context.reverso.net/translation/chinese-english/' + sel; infoWindowOpen(reverso); } function moedictInfo() { // use the traditional character for moedict lookup let trad = savedSearchResults[0][1]; // https://www.moedict.tw/~%E4%B8%AD%E6%96%87 let moedict = 'https://www.moedict.tw/~' + encodeURIComponent(trad); infoWindowOpen(moedict); } let infoButtons = [ { id: 'grammar_info', label: 'Grammar', func: grammarInfo }, { id: 'vocab_info', label: 'Vocabulary', func: vocabInfo }, { id: 'tatoeba_info', label: 'Tatoeba', func: tatoebaInfo }, { id: 'line_dict_info', label: 'Line Dict', func: lineDictInfo }, { id: 'forvo_info', label: 'Forvo', func: forvoInfo }, { id: 'dict_info', label: 'Dict.cn', func: dictInfo }, { id: 'iciba_info', label: 'Iciba', func: icibaInfo }, { id: 'mdbg_info', label: 'MDBG', func: mdbgInfo }, { id: 'reverso_info', label: 'Reverso', func: reversoInfo }, { id: 'moedict_info', label: 'Moedict', func: moedictInfo } ]; const infoButtonContainer = document.createElement('div'); infoButtonContainer.id = 'zhongwen-info-buttons'; infoButtonContainer.style.display = 'none'; async function setupInfoButtons() { let config = await getConfig(); infoButtons.forEach(function (button) { let infoButton = createLabelButton(button.label, button.func); infoButton.classList.add('info-button'); infoButton.id = button.id; if (!config[button.id]) { infoButton.style.display = 'none'; config[button.id] = false; } else { infoButton.style.display = 'inline-block'; config[button.id] = true; } infoButtonContainer.appendChild(infoButton); }); } async function updateInfoButtons(id) { id = id.replace("hanzipopup-", ""); let config = await getConfig(); let infoButton = document.querySelector(`#${id}`); if (!config[id]) { infoButton.style.display = 'none'; } else { infoButton.style.display = 'inline-block'; } } setupInfoButtons(); document.body.appendChild(infoButtonContainer); function onKeyDown(keyDown) { let sel = window.getSelection().toString(); if (sel.length === 0) { return; } if (keyDown.ctrlKey || keyDown.metaKey) { return; } if (keyDown.keyCode === 27) { // esc key pressed hidePopup(); return; } if (keyDown.altKey && keyDown.keyCode === 87) { // Alt + w viewWordList(); return; } switch (keyDown.keyCode) { case 65: // 'a' altViewInfo(); break; case 66: // 'b' wordPrev(); break; case 67: // 'c' copyToClip(); break; case 68: // 'd' disableTab(); break; case 71: // 'g' grammarInfo(); break; case 77: // 'm' selStartIncrement = 1; // falls through case 78: // 'n' wordNext(); break; case 80: // 'p' ttsPlay(); break; case 82: // 'r' saveWordList(); break; case 84: // 't' tatoebaInfo(); break; case 86: // 'v' vocabInfo(); break; case 88: // 'x' movePopupUp(); break; case 89: // 'y' movePopupDown(); break; case 49: // '1' lineDictInfo(); break; case 50: // '2' forvoInfo(); break; case 51: // '3' dictInfo(); break; case 52: // '4' icibaInfo(); break; case 53: // '5' mdbgInfo(); break; case 54: // '6' reversoInfo(); break; case 55: // '7' moedictInfo(); break; default: return; } } // https://gist.github.com/likev/c36fcc8a08ba1a2c5d08f9c7d806a0ad // JS port of https://github.com/Migushthe2nd/MsEdgeTTS let socket = null; let ttsText = null; let ttsWindow = null; let ttsError = false; let ttsAudio = new Audio(""); let langList = [{ "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)", "ShortName": "zh-CN-XiaoxiaoNeural", "Gender": "Female", "Locale": "zh-CN", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Xiaoxiao Online (Natural) - Chinese (Mainland)", "Status": "GA", "VoiceTag": { "ContentCategories": ["News", "Novel"], "VoicePersonalities": ["Warm"] } }, { "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoyiNeural)", "ShortName": "zh-CN-XiaoyiNeural", "Gender": "Female", "Locale": "zh-CN", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Xiaoyi Online (Natural) - Chinese (Mainland)", "Status": "GA", "VoiceTag": { "ContentCategories": ["Cartoon", "Novel"], "VoicePersonalities": ["Lively"] } }, { "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunjianNeural)", "ShortName": "zh-CN-YunjianNeural", "Gender": "Male", "Locale": "zh-CN", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Yunjian Online (Natural) - Chinese (Mainland)", "Status": "GA", "VoiceTag": { "ContentCategories": ["Sports", " Novel"], "VoicePersonalities": ["Passion"] } }, { "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunxiNeural)", "ShortName": "zh-CN-YunxiNeural", "Gender": "Male", "Locale": "zh-CN", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Yunxi Online (Natural) - Chinese (Mainland)", "Status": "GA", "VoiceTag": { "ContentCategories": ["Novel"], "VoicePersonalities": ["Lively", "Sunshine"] } }, { "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunxiaNeural)", "ShortName": "zh-CN-YunxiaNeural", "Gender": "Male", "Locale": "zh-CN", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Yunxia Online (Natural) - Chinese (Mainland)", "Status": "GA", "VoiceTag": { "ContentCategories": ["Cartoon", "Novel"], "VoicePersonalities": ["Cute"] } }, { "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunyangNeural)", "ShortName": "zh-CN-YunyangNeural", "Gender": "Male", "Locale": "zh-CN", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Yunyang Online (Natural) - Chinese (Mainland)", "Status": "GA", "VoiceTag": { "ContentCategories": ["News"], "VoicePersonalities": ["Professional", "Reliable"] } }, { "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN-liaoning, XiaobeiNeural)", "ShortName": "zh-CN-liaoning-XiaobeiNeural", "Gender": "Female", "Locale": "zh-CN-liaoning", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Xiaobei Online (Natural) - Chinese (Northeastern Mandarin)", "Status": "GA", "VoiceTag": { "ContentCategories": ["Dialect"], "VoicePersonalities": ["Humorous"] } }, { "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN-shaanxi, XiaoniNeural)", "ShortName": "zh-CN-shaanxi-XiaoniNeural", "Gender": "Female", "Locale": "zh-CN-shaanxi", "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", "FriendlyName": "Microsoft Xiaoni Online (Natural) - Chinese (Zhongyuan Mandarin Shaanxi)", "Status": "GA", "VoiceTag": { "ContentCategories": ["Dialect"], "VoicePersonalities": ["Bright"] } }]; function create_edge_TTS({ voice = "zh-CN-XiaoxiaoNeural", timeout = 10, auto_reconnect = true } = {}) { const TRUSTED_CLIENT_TOKEN = "6A5AA1D4EAFF4E9FB37E23D68491D6F4"; // const VOICES_URL = `https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=${TRUSTED_CLIENT_TOKEN}`; const SYNTH_URL = `wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=${TRUSTED_CLIENT_TOKEN}`; const BINARY_DELIM = "Path:audio\r\n"; const VOICE_LANG_REGEX = /\w{2}-\w{2}/; let _outputFormat = "audio-24khz-48kbitrate-mono-mp3"; let _voiceLocale = 'zh-CN'; let _voice = voice; const _queue = { message: [], url_resolve: {}, url_reject: {} }; let ready = false; function _SSMLTemplate(input) { return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${_voiceLocale}"> <voice name="${_voice}"> ${input} </voice> </speak>`; } function uuidv4() { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); } create_new_ws(); function setFormat(format) { if (format) { _outputFormat = format; } socket.send(`Content-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n { "context": { "synthesis": { "audio": { "metadataoptions": { "sentenceBoundaryEnabled": "false", "wordBoundaryEnabled": "false" }, "outputFormat": "${_outputFormat}" } } } } `); } async function createURL(requestId) { let index_message = 0; for (let message of _queue.message) { const isbinary = message instanceof Blob; if (!isbinary) { continue; } const data = await message.text(); const Id = /X-RequestId:(.*?)\r\n/gm.exec(data)[1]; if (Id !== requestId) { continue; } if (data.charCodeAt(0) === 0x00 && data.charCodeAt(1) === 0x67 && data.charCodeAt(2) === 0x58) { // Last (empty) audio fragment const blob = new Blob(_queue[requestId], { 'type': 'audio/mp3' }); _queue[requestId] = null; const url = URL.createObjectURL(blob); _queue.url_resolve[requestId](url); } else { const index = data.indexOf(BINARY_DELIM) + BINARY_DELIM.length; const audioData = message.slice(index); _queue[requestId].push(audioData); _queue.message[index_message] = null; } ++index_message; } } function onopen(event) { setFormat(); ready = true; } async function onmessage(event) { const isbinary = event.data instanceof Blob; _queue.message.push(event.data) if (!isbinary) { const requestId = /X-RequestId:(.*?)\r\n/gm.exec(event.data)[1]; if (event.data.includes("Path:turn.end")) { createURL(requestId); addLoading(false); } } } function onerror(event) { ready = false; addLoading(false); } function onclose(event) { ready = false; addLoading(false); } function addSocketListeners() { socket.addEventListener('open', onopen); socket.addEventListener('message', onmessage); socket.addEventListener('error', onerror); socket.addEventListener('close', onclose); } function create_new_ws() { try { if (ttsError) { addLoading(false); return; } socket = new WebSocket(SYNTH_URL); socket.onerror = function (event) { ttsError = true; ttsPostMessage(); addLoading(false); } addSocketListeners(); } catch (e) { console.log(e); } } let toStream = function (input) { let requestSSML = _SSMLTemplate(input); const requestId = uuidv4().replaceAll('-', ''); const request = `X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n` + requestSSML.trim(); _queue[requestId] = []; return new Promise((resolve, reject) => { _queue.url_resolve[requestId] = resolve, _queue.url_reject[requestId] = reject; if (!ready) { if (auto_reconnect) { create_new_ws(); socket.addEventListener('open', _ => socket.send(request)); setTimeout(_ => { if (!ready) reject('reconnect timeout') }, timeout * 1000); } else reject('socket error or timeout'); } else { socket.send(request) } }); } async function play(input) { const url = await toStream(input); let play_resolve = function () { }; ttsAudio.src = url; ttsAudio.onended = (e) => { addLoading(false); play_resolve(true); } await ttsAudio.play(); return new Promise((resolve, reject) => { play_resolve = resolve }); } return new Promise((resolve, reject) => { setTimeout(_ => reject('socket open timeout'), timeout * 1000); // Connection opened socket.addEventListener('open', function (event) { resolve({ play, toStream, setVoice: (voice, locale) => { _voice = voice; if (!locale) { const voiceLangMatch = VOICE_LANG_REGEX.exec(_voice); if (!voiceLangMatch) { throw new Error("Could not infer voiceLocale from voiceName!"); } _voiceLocale = voiceLangMatch[0]; } else { _voiceLocale = locale; } }, setFormat, isReady: _ => ready }) }); }); } function addLoading(add) { let speakBtn = document.querySelector("#hanzi-popup-tts-btn > button"); if (speakBtn) { if (add) { speakBtn.classList.remove("tts"); speakBtn.classList.add("tts-loading"); } else { speakBtn.classList.add("tts"); speakBtn.classList.remove("tts-loading"); } } } function ttsPostMessage(ttsText, voice) { if (!ttsWindow || ttsWindow.closed) { ttsWindow = window.open(`${host}/tts.html`); } else { ttsWindow.postMessage({ message: "ttsPlay", text: ttsText, voice: voice }, "*"); } } async function edgeTtsPlay(text, voice = "zh-CN-XiaoxiaoNeural") { if (text === undefined || text === null || text === '') { return; } addLoading(true); if (ttsError) { ttsPostMessage(text, voice); addLoading(false); return; } ttsText = text; const tts = await create_edge_TTS({ voice }); try { await tts.play(text); } catch (e) { ttsError = true; console.log(e); addLoading(false); // again edgeTtsPlay(text); } } window.addEventListener("message", function (event) { if (event.data.message === "ttsResult") { if (!event.data.result) { if (confirm("TTS failed. Open Hanzipopup TTS page again?")) { if (ttsWindow) { ttsWindow.focus(); } } } } }); function enableAutoTTS() { if (typeof window === 'undefined') { return; } const isiOS = navigator.userAgent.match(/ipad|iphone/i); if (!isiOS) { return; } const simulateSpeech = () => { const lecture = new SpeechSynthesisUtterance('hello'); lecture.volume = 0; speechSynthesis.speak(lecture); document.removeEventListener('click', simulateSpeech); }; document.addEventListener('click', simulateSpeech); } enableAutoTTS(); let savedTarget; let savedRangeNode; let savedRangeOffset; let savedTtsStr; let selText; let clientX; let clientY; let selStartDelta; let selStartIncrement; let popX = 0; let popY = 0; let timer; let altView = 0; let savedSearchResults = []; let savedSelStartOffset = 0; let savedSelEndList = []; let dict; let enable = false; let host = "https://krmanik.github.io/hanzipopup"; let wordListWindow; let infoWindow; let clickedTarget; function createLabel(labelText, icon) { let labelIcon = document.createElement('div'); labelIcon.classList.add("h-icon-btn"); labelIcon.classList.add("init"); labelIcon.classList.add(icon.name); labelIcon.style.background = icon.color; let labelDiv = document.createElement('div'); labelDiv.style.marginLeft = '4px'; labelDiv.innerHTML = labelText; let label = document.createElement('label'); label.classList.add("custom-popup-label"); label.appendChild(labelIcon); label.appendChild(labelDiv); return label; } function createRadioFormGroup(labelText, groupName, options, icon) { let formGroup = document.createElement('div'); formGroup.id = groupName; formGroup.style.marginBottom = '20px'; formGroup.style.textAlign = "left"; let label = createLabel(labelText, icon); formGroup.appendChild(label); options.forEach(option => { let radioDiv = document.createElement('div'); radioDiv.classList.add("option-div"); let radioInput = document.createElement('input'); radioInput.type = 'radio'; radioInput.id = option.id; radioInput.name = groupName; radioInput.value = option.value; radioInput.addEventListener('change', () => { setConfig(groupName, option.value); }); let radioLabel = document.createElement('label'); radioLabel.style.marginLeft = '4px'; radioLabel.style.fontSize = '16px'; radioLabel.htmlFor = option.id; radioLabel.innerHTML = option.label; radioDiv.appendChild(radioInput); radioDiv.appendChild(radioLabel); formGroup.appendChild(radioDiv); }); return formGroup; } function createCheckboxFormGroup(id, labelText, options, svg) { let formGroup = document.createElement('div'); formGroup.id = id; formGroup.style.marginBottom = '20px'; formGroup.style.textAlign = "left"; let label = createLabel(labelText, svg); formGroup.appendChild(label); options.forEach(option => { let checkboxDiv = document.createElement('div'); checkboxDiv.classList.add("option-div"); let checkboxInput = document.createElement('input'); checkboxInput.type = 'checkbox'; checkboxInput.id = `hanzipopup-${option.id}`; checkboxInput.name = `hanzipopup-${option.id}`; checkboxInput.addEventListener('click', async (e) => { setTimeout(async () => { await setConfig(option.id, checkboxInput.checked); }, 50); setTimeout(async () => { if (checkboxInput.id === "hanzipopup-more") { let config = await getConfig(); let moreInfo = document.querySelector("#hanzipopup-more-info"); if (config['more']) { moreInfo.style.display = "block"; moreInfo.scrollIntoView(); } else { moreInfo.style.display = "none"; moreInfo.scrollIntoView(); } } else if (checkboxInput.id.includes("_info")) { await updateInfoButtons(checkboxInput.id); } await setupToolbar(); }, 100); }); let checkboxLabelElem = document.createElement('label'); checkboxLabelElem.style.marginLeft = "4px"; checkboxLabelElem.style.fontSize = '16px'; checkboxLabelElem.htmlFor = option.id; checkboxLabelElem.innerHTML = option.label; checkboxDiv.appendChild(checkboxInput); checkboxDiv.appendChild(checkboxLabelElem); formGroup.appendChild(checkboxDiv); }); return formGroup; } function createLabelButton(text, clickCallback) { const button = document.createElement('button'); button.innerHTML = text; button.className = "unselectable"; button.addEventListener('click', clickCallback); return button; } async function createLangList(langList) { let config = await getConfig(); let container = document.createElement("div"); container.style.margin = "8px 0px 8px 20px"; container.style.width = "80%"; container.style.display = "inline-block"; container.id = "tts-voice-select"; if (!config["tts"]) { container.style.display = "none"; } let labelElement = document.createElement("label"); labelElement.setAttribute("for", "voiceSelection"); labelElement.textContent = "Select voice"; let selectElement = document.createElement("select"); selectElement.id = "voiceSelection"; selectElement.name = "voiceSelection"; selectElement.style.width = "90%"; selectElement.addEventListener("change", async (e) => { selectElement.value = e.target.value; config['ttsvoice'] = selectElement.value; await setConfig('ttsvoice', config['ttsvoice']); }); for (let i = 0; i < langList.length; i++) { let option = document.createElement("option"); option.value = langList[i].ShortName; option.textContent = langList[i].FriendlyName + " (" + langList[i].Gender + ")"; selectElement.add(option); } container.appendChild(labelElement); container.appendChild(selectElement); document.querySelector("#cnTtsEngineEdge").parentElement.appendChild(container); } async function ttsPlay() { let hanzi = savedSearchResults[0][0]; let config = await getConfig(); let cnTtsEngine = config['cnTtsEngine'] || 'browser'; if (cnTtsEngine === 'browser') { let utterance = new SpeechSynthesisUtterance(hanzi); utterance.lang = "zh-CN"; speechSynthesis.speak(utterance); } else { edgeTtsPlay(hanzi, config['ttsvoice']); } } function toggleFeature() { if (enable) { disableTab(); document.querySelector("#hanzi-popup-enable-btn > button").style.background = "#a1a1a1"; document.getElementById("hanzipopup-fab-btn").style.borderBottom = "unset"; } else { enableTab(); document.querySelector("#hanzi-popup-enable-btn > button").style.background = "#33b249"; document.getElementById("hanzipopup-fab-btn").style.borderBottom = "4px solid #31eb3f"; } enable = !enable; setConfig('enable', enable); } let buttonList = { 'option': { func: toggleOption, color: "#424769" }, 'view': { func: viewWordList, color: "#3E64FF" }, 'save': { func: saveWordList, color: "#3E64FF" }, 'more-info': { func: toggleMore, color: "#5C6BC0" }, 'next': { func: wordNext, color: "#5C6BC0" }, 'prev': { func: wordPrev, color: "#5C6BC0" }, 'tts': { func: ttsPlay, color: "#5C6BC0" }, 'enable': { func: toggleFeature, color: "#a1a1a1" } }; const fabContainer = document.createElement("div"); fabContainer.id = "hanzipopup-fab-container"; fabContainer.classList.add('right'); const fabElement = document.createElement('button'); fabElement.id = "hanzipopup-fab-btn"; fabElement.classList.add("h-icon-btn"); fabElement.classList.add("pinyin"); fabElement.classList.add("fab-btn"); async function setupToolbar() { let config = await getConfig(); let elementsToRemove = []; if (!config['tts']) { elementsToRemove.push('tts'); document.querySelector("#cnTtsEngine").style.display = "none"; } else { document.querySelector("#cnTtsEngine").style.display = "unset"; document.querySelector("#cnTtsEngine").value = config['cnTtsEngine']; } if (!config['prev']) { elementsToRemove.push('prev'); } if (!config['next']) { elementsToRemove.push('next'); } if (!config['more']) { elementsToRemove.push('more-info'); } let filteredButtonList = Object.keys(buttonList).reduce((result, key) => { if (!elementsToRemove.includes(key)) { result[key] = buttonList[key]; } return result; }, {}); let windowWidth = window.innerWidth; fabContainer.style.left = windowWidth - 38 + "px"; fabContainer.style.top = window.innerHeight - 38 + "px"; let toolList = document.querySelector("#hanzipopup-tools"); if (toolList) { toolList.innerHTML = ""; } else { toolList = document.createElement("ul"); toolList.id = "hanzipopup-tools"; } fabContainer.appendChild(toolList); for (let key in filteredButtonList) { const liElem = document.createElement("li"); liElem.onclick = filteredButtonList[key].func; liElem.id = `hanzi-popup-${key}-btn` let icon = document.createElement("button"); icon.classList.add("h-icon-btn"); icon.classList.add(key); icon.style.background = filteredButtonList[key].color; liElem.appendChild(icon) toolList.appendChild(liElem); } if (config['enable']) { document.querySelector("#hanzi-popup-enable-btn > button").style.background = "#33b249"; document.getElementById("hanzipopup-fab-btn").style.borderBottom = "4px solid #31eb3f"; } let oldPositionX = fabContainer.style.left; let oldPositionY = fabContainer.style.top; const move = (e) => { if (e.type === "touchmove") { fabContainer.style.top = e.touches[0].clientY + "px"; fabContainer.style.left = e.touches[0].clientX + "px"; } else { fabContainer.style.top = e.clientY + "px"; fabContainer.style.left = e.clientX + "px"; } }; const mouseDown = (e) => { oldPositionY = fabContainer.style.top; oldPositionX = fabContainer.style.left; if (e.type === "mousedown") { window.addEventListener("mousemove", move); } else { window.addEventListener("touchmove", move); } fabContainer.style.transition = "none"; }; const mouseUp = (e) => { if (e.type === "mouseup") { window.removeEventListener("mousemove", move); } else { window.removeEventListener("touchmove", move); } snapToSide(e); fabContainer.style.transition = "0.3s ease-in-out left"; }; const snapToSide = (e) => { windowWidth = window.innerWidth; let currPositionX, currPositionY; if (e.type === "touchend") { currPositionX = e.changedTouches[0].clientX; currPositionY = e.changedTouches[0].clientY; } else { currPositionX = e.clientX; currPositionY = e.clientY; } if (currPositionY < 38) { fabContainer.style.top = 38 + "px"; } if (currPositionX < windowWidth / 2) { if (!isFabButtonClicked(e)) { return; } fabContainer.style.left = 30 + "px"; fabContainer.classList.remove('right'); fabContainer.classList.add('left'); } else { if (!isFabButtonClicked(e)) { return; } fabContainer.style.left = windowWidth - 38 + "px"; fabContainer.classList.remove('left'); fabContainer.classList.add('right'); } }; fabContainer.addEventListener("mousedown", mouseDown); fabContainer.addEventListener("mouseup", mouseUp); fabContainer.addEventListener("touchstart", mouseDown); fabContainer.addEventListener("touchend", mouseUp); fabContainer.addEventListener("click", (e) => { // console.log({ oldPositionX, oldPositionY, currentX: fabContainer.style.left, currentY: fabContainer.style.top, clientX: e.clientX, clientY: e.clientY }) if (oldPositionY === fabContainer.style.top && oldPositionX === fabContainer.style.left) { if (isFabButtonClicked(e)) { fabContainer.classList.toggle("fab-active"); return; } } }); function isFabButtonClicked(e) { if (e.target.id === "hanzipopup-fab-btn") { return true; } return false; } } function toggleMore(e) { infoButtonContainer.style.left = e.clientX - 40 + "px"; if (e.clientY < 400) { infoButtonContainer.style.top = e.clientY + 20 + "px"; } else { infoButtonContainer.style.bottom = window.innerHeight - e.clientY + 28 + "px"; } infoButtonContainer.style.display = infoButtonContainer.style.display === 'none' ? 'flex' : 'none'; } // Background color of the pop-up window const formGroupColors = createRadioFormGroup('Popup Background Color', 'popupColor', [{ id: 'popupColorYellow', value: 'yellow', label: 'Yellow pop-up background' }, { id: 'popupColorBlue', value: 'blue', label: 'Blue pop-up background' }, { id: 'popupColorLightBlue', value: 'lightblue', label: 'Light blue pop-up background' }, { id: 'popupColorBlack', value: 'black', label: 'Black pop-up background' }], { name: 'popup', color: "#de4e4e" } ); formGroupColors.style.marginTop = '8px'; // Coloring pinyin syllables based on the tone of the character const formGroupToneColors = createRadioFormGroup( 'Pinyin Color', 'toneColors', [{ id: 'toneColorsStandard', value: 'standard', label: 'Use the default color scheme.' }, { id: 'toneColorsPleco', value: 'pleco', label: 'Use the <a href="https://pleco.com" target="_blank">Pleco</a> color scheme.' }, { id: 'toneColorsHanping', value: 'hanping', label: 'Use the <a href="https://hanpingchinese.com/" target="_blank">Hanping</a> color scheme.' }, { id: 'toneColorsNone', value: 'none', label: 'Don\'t show any tone colors.' }], { name: 'pinyin', color: "#34d941" } ); // Font size of the characters in the pop-up window const formGroupFontSize = createRadioFormGroup('Font Size', 'fontSize', [{ id: 'fontSizeSmall', value: 'small', label: 'Display characters in a smaller font size.' }, { id: 'fontSizeLarge', value: 'large', label: 'Display characters in a larger font size.' }], { name: 'font', color: "#6272cf" } ); // Simplified and traditional characters const formGroupSimpTrad = createRadioFormGroup('Simplified & Traditional Characters', 'simpTrad', [{ id: 'simpTradClassic', value: 'classic', label: 'Display both simplified and traditional characters.' }, { id: 'simpTradAuto', value: 'auto', label: 'Use the character style automatically detected on the page (either simplified or traditional).' }], { name: 'zi', color: "#ff9800" } ); // Saving entries to the Built-in Word List const formGroupSaveToWordList = createRadioFormGroup('Save Words', 'saveToWordList', [{ id: 'saveToWordListAllEntries', value: 'allEntries', label: 'Save all entries.' }, { id: 'saveToWordListFirstEntryOnly', value: 'firstEntryOnly', label: 'Save only the first entry.' }], { name: 'save', color: "#3E64FF" } ); // Saving entries to the Built-in Word List const formGroupTtsEngine = createRadioFormGroup('Text to Speech Engine', 'cnTtsEngine', [{ id: 'cnTtsEngineBrowser', value: 'browser', label: 'Use Browser Speech Synthesis' }, { id: 'cnTtsEngineEdge', value: 'msedge', label: 'Use MS Edge TTS' }], { name: 'tts', color: "#3E64FF" } ); // Additional Chinese transliterations const formGroupAdditional = createCheckboxFormGroup('optional-feature', 'Optional Feature', [{ id: 'zhuyin', label: 'Show <a href="https://wikipedia.org/wiki/Bopomofo" target="_blank">Zhuyin (Bopomofo)</a> phonetic symbols.' }, { id: 'tts', label: 'Text to Speech for selected Chinese characters' }, { id: 'prev', label: 'Select previous Chinese character' }, { id: 'next', label: 'Select next Chinese character' }, { id: 'more', label: 'Show more button to display additional information' }], { name: 'translate', color: "#ff7043" } ); // Grammar and usage notes const formGroupGrammarNotes = createCheckboxFormGroup('grammar-vocab', 'Grammar & Vocabulary Notes', [{ id: 'grammar', label: 'Show grammar and usage notes' }, { id: 'vocab', label: 'Show vocabulary notes' }], { name: 'book', color: "#efa22f" }); const grammarNotesInfo = document.createElement('div'); grammarNotesInfo.style.marginLeft = '4px'; grammarNotesInfo.innerHTML = 'Grammar and usage notes from the <a href="https://resources.allsetlearning.com/chinese/grammar" target="_blank">Chinese Grammar Wiki</a>.'; grammarNotesInfo.innerHTML += '<br>'; grammarNotesInfo.innerHTML += 'Vocabulary notes from the <a href="https://resources.allsetlearning.com/chinese/vocabulary" target="_blank">Chinese Vocabulary Wiki</a>.'; formGroupGrammarNotes.appendChild(grammarNotesInfo); // More information list button const formGroupMoreInfo = createCheckboxFormGroup('hanzipopup-more-info', 'More Info', infoButtons, { name: 'message', color: "#4caf50" }); // create header title const headerTitle = document.createElement('div'); headerTitle.textContent = 'Options'; headerTitle.style.fontSize = '20px'; headerTitle.style.fontWeight = 'bold'; const titleContainer = document.createElement('div'); titleContainer.id = 'custom-title-container'; const closeBtn = document.createElement("button"); closeBtn.classList.add("h-icon-btn"); closeBtn.classList.add("init"); closeBtn.classList.add("close"); closeBtn.style.background = "red"; closeBtn.onclick = toggleOption; titleContainer.appendChild(headerTitle); titleContainer.appendChild(closeBtn); // Create popup container element const popupContainer = document.createElement('div'); popupContainer.id = 'custom-popup-container'; popupContainer.style.display = 'none'; // Create zhongwen config container element const zhongwenPopupContainer = document.createElement('div'); zhongwenPopupContainer.id = 'zhongwenPopupContainer'; zhongwenPopupContainer.appendChild(formGroupColors); zhongwenPopupContainer.appendChild(formGroupToneColors); zhongwenPopupContainer.appendChild(formGroupFontSize); zhongwenPopupContainer.appendChild(formGroupSimpTrad); zhongwenPopupContainer.appendChild(formGroupSaveToWordList); zhongwenPopupContainer.appendChild(formGroupGrammarNotes); zhongwenPopupContainer.appendChild(formGroupAdditional); zhongwenPopupContainer.appendChild(formGroupTtsEngine); zhongwenPopupContainer.appendChild(formGroupMoreInfo); popupContainer.appendChild(titleContainer); popupContainer.appendChild(zhongwenPopupContainer); document.body.appendChild(popupContainer); createLangList(langList); setupToolbar(); fabContainer.append(fabElement); document.body.appendChild(fabContainer); // https://github.com/cschiller/zhongwen class ZhongwenDictionary { constructor(wordDict, wordIndex, grammarKeywords, vocabKeywords) { this.wordDict = wordDict; this.wordIndex = wordIndex; this.grammarKeywords = grammarKeywords; this.vocabKeywords = vocabKeywords; this.cache = {}; } static find(needle, haystack) { let beg = 0; let end = haystack.length - 1; while (beg < end) { let mi = Math.floor((beg + end) / 2); let i = haystack.lastIndexOf('\n', mi) + 1; let mis = haystack.substr(i, needle.length); if (needle < mis) { end = i - 1; } else if (needle > mis) { beg = haystack.indexOf('\n', mi + 1) + 1; } else { return haystack.substring(i, haystack.indexOf('\n', mi + 1)); } } return null; } hasGrammarKeyword(keyword) { return this.grammarKeywords[keyword]; } hasVocabKeyword(keyword) { return this.vocabKeywords[keyword]; } wordSearch(word, max) { let entry = { data: [] }; let dict = this.wordDict; let index = this.wordIndex; let maxTrim = max || 7; let count = 0; let maxLen = 0; WHILE: while (word.length > 0) { let ix = this.cache[word]; if (!ix) { ix = ZhongwenDictionary.find(word + ',', index); if (!ix) { this.cache[word] = []; continue; } ix = ix.split(','); this.cache[word] = ix; } for (let j = 1; j < ix.length; ++j) { let offset = ix[j]; let dentry = dict.substring(offset, dict.indexOf('\n', offset)); if (count >= maxTrim) { entry.more = 1; break WHILE; } ++count; if (maxLen === 0) { maxLen = word.length; } entry.data.push([dentry, word]); } word = word.substr(0, word.length - 1); } if (entry.data.length === 0) { return null; } entry.matchLen = maxLen; return entry; } } function makeGetRequest(url, responseType = 'text') { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: 'GET', url: url, responseType: responseType, onload: function (response) { if (response.status === 200) { resolve(response.response); } else { reject(new Error(`Request failed with status: ${response.status}`)); } }, onerror: function (error) { reject(new Error(`Request failed with error: ${error}`)); } }); }); } async function getCedict(url) { let arraybuffer = await makeGetRequest(url, 'arraybuffer'); const { entries } = await unzipit.unzip(new Uint8Array(arraybuffer)); const arrayBuffer = await entries['cedict_ts.u8'].arrayBuffer(); const string = new TextDecoder().decode(arrayBuffer); return string; } async function loadDictData() { let wordDict = getCedict(`${host}/data/cedict_ts.zip`); // let wordDict = makeGetRequest(`${host}/data/cedict_ts.u8`); let wordIndex = makeGetRequest(`${host}/data/cedict.idx`); let grammarKeywords = makeGetRequest(`${host}/data/grammarKeywordsMin.json`, 'json'); let vocabKeywords = makeGetRequest(`${host}/data/vocabularyKeywordsMin.json`, 'json'); return Promise.all([wordDict, wordIndex, grammarKeywords, vocabKeywords]); } async function loadDictionary() { const [wordDict, wordIndex, grammarKeywords, vocabKeywords] = await loadDictData(); return new ZhongwenDictionary(wordDict, wordIndex, grammarKeywords, vocabKeywords); } async function loadDict() { try { if (dict) { return; } dict = await loadDictionary().then(r => dict = r); console.log("Dictionary Loaded..."); } catch (e) { disableTab(); console.log(e); } } // regular expression for zero-width non-joiner U+200C ‌ let zwnj = /\u200c/g; function search(text) { if (!dict) { return; } let entry = dict.wordSearch(text); if (entry) { for (let i = 0; i < entry.data.length; i++) { let word = entry.data[i][1]; if (dict.hasGrammarKeyword(word) && (entry.matchLen === word.length)) { // the final index should be the last one with the maximum length entry.grammar = { keyword: word, index: i }; } if (dict.hasVocabKeyword(word) && (entry.matchLen === word.length)) { // the final index should be the last one with the maximum length entry.vocab = { keyword: word, index: i }; } } } return entry; } // https://github.com/cschiller/zhongwen function onMouseMove(mouseMove) { clickedTarget = mouseMove.target; if(!shouldHidePopup()) { return; } if (clientX && clientY) { if (mouseMove.clientX === clientX && mouseMove.clientY === clientY) { return; } } clientX = mouseMove.clientX; clientY = mouseMove.clientY; let range; let rangeNode; let rangeOffset; let moveClientX = mouseMove.clientX; let moveClientY = mouseMove.clientY; // Handle Chrome and Firefox if (document.caretRangeFromPoint) { range = document.caretRangeFromPoint(moveClientX, moveClientY); if (range === null) { return; } rangeNode = range.startContainer; rangeOffset = range.startOffset; } else if (document.caretPositionFromPoint) { range = document.caretPositionFromPoint(moveClientX, moveClientY); if (range === null) { return; } rangeNode = range.offsetNode; rangeOffset = range.offset; } if (mouseMove.target === savedTarget) { if (rangeNode === savedRangeNode && rangeOffset === savedRangeOffset) { return; } } if (timer) { clearTimeout(timer); timer = null; } if (rangeNode.data && rangeOffset === rangeNode.data.length) { rangeNode = findNextTextNode(rangeNode.parentNode, rangeNode); rangeOffset = 0; } if (!rangeNode || rangeNode.parentNode !== mouseMove.target) { rangeNode = null; rangeOffset = -1; } savedTarget = mouseMove.target; savedRangeNode = rangeNode; savedRangeOffset = rangeOffset; selStartDelta = 0; selStartIncrement = 1; if (rangeNode && rangeNode.data && rangeOffset < rangeNode.data.length) { popX = mouseMove.clientX; popY = mouseMove.clientY; timer = setTimeout(() => triggerSearch(), 50); return; } // Don't close just because we moved from a valid pop-up slightly over to a place with nothing. let dx = popX - mouseMove.clientX; let dy = popY - mouseMove.clientY; let distance = Math.sqrt(dx * dx + dy * dy); if (distance > 4) { clearHighlight(); hidePopup(); } } async function triggerSearch() { let rangeNode = savedRangeNode; let selStartOffset = savedRangeOffset + selStartDelta; selStartIncrement = 1; if (!rangeNode) { // clearHighlight(); hidePopup(); return 1; } if (selStartOffset < 0 || rangeNode.data.length <= selStartOffset) { // clearHighlight(); hidePopup(); return 2; } let u = rangeNode.data.charCodeAt(selStartOffset); let isChineseCharacter = !isNaN(u) && ( u === 0x25CB || (0x3400 <= u && u <= 0x9FFF) || (0xF900 <= u && u <= 0xFAFF) || (0xFF21 <= u && u <= 0xFF3A) || (0xFF41 <= u && u <= 0xFF5A) || (0xD800 <= u && u <= 0xDFFF) ); if (!isChineseCharacter) { clearHighlight(); hidePopup(); return 3; } let selEndList = []; let originalText = getText(rangeNode, selStartOffset, selEndList, 30 /*maxlength*/); // Workaround for Google Docs: remove zero-width non-joiner ‌ let text = originalText.replace(zwnj, ''); savedSelStartOffset = selStartOffset; savedSelEndList = selEndList; let result = search(text); result.originalText = originalText; processSearchResult(result); return 0; } async function processSearchResult(result) { let selStartOffset = savedSelStartOffset; let selEndList = savedSelEndList; if (!result) { hidePopup(); clearHighlight(); return; } let highlightLength; let index = 0; for (let i = 0; i < result.matchLen; i++) { // Google Docs workaround: determine the correct highlight length while (result.originalText[index] === '\u200c') { index++; } index++; } highlightLength = index; selStartIncrement = result.matchLen; selStartDelta = (selStartOffset - savedRangeOffset); let rangeNode = savedRangeNode; // don't try to highlight form elements if (!('form' in savedTarget)) { let doc = rangeNode.ownerDocument; if (!doc) { clearHighlight(); hidePopup(); return; } highlightMatch(doc, rangeNode, selStartOffset, highlightLength, selEndList); } let config = await getConfig(); showPopup(await makeHtml(result, config.toneColorScheme), savedTarget, popX, popY, false); } // modifies selEndList as a side-effect function getText(startNode, offset, selEndList, maxLength) { let text = ''; let endIndex; if (startNode.nodeType !== Node.TEXT_NODE) { return ''; } endIndex = Math.min(startNode.data.length, offset + maxLength); text += startNode.data.substring(offset, endIndex); selEndList.push({ node: startNode, offset: endIndex }); let nextNode = startNode; while ((text.length < maxLength) && ((nextNode = findNextTextNode(nextNode.parentNode, nextNode)) !== null)) { text += getTextFromSingleNode(nextNode, selEndList, maxLength - text.length); } return text; } // modifies selEndList as a side-effect function getTextFromSingleNode(node, selEndList, maxLength) { let endIndex; if (node.nodeName === '#text') { endIndex = Math.min(maxLength, node.data.length); selEndList.push({ node: node, offset: endIndex }); return node.data.substring(0, endIndex); } else { return ''; } } function highlightMatch(doc, rangeStartNode, rangeStartOffset, matchLen, selEndList) { if (!selEndList || selEndList.length === 0) return; let selEnd; let offset = rangeStartOffset + matchLen; for (let i = 0, len = selEndList.length; i < len; i++) { selEnd = selEndList[i]; if (offset <= selEnd.offset) { break; } offset -= selEnd.offset; } let range = doc.createRange(); range.setStart(rangeStartNode, rangeStartOffset); range.setEnd(selEnd.node, offset); let sel = window.getSelection(); if (!sel.isCollapsed && selText !== sel.toString()) return; sel.empty(); sel.addRange(range); selText = sel.toString(); return selText; } function getTextForClipboard() { let result = ''; for (let i = 0; i < savedSearchResults.length; i++) { result += savedSearchResults[i].slice(0, -1).join('\t'); result += '\n'; } return result; } function clearHighlight() { if (selText === null) { return; } let selection = window.getSelection(); if (selection.isCollapsed || selText === selection.toString()) { selection.empty(); } selText = null; } function findNextTextNode(root, previous) { if (root === null) { return null; } let nodeIterator = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, null); let node = nodeIterator.nextNode(); while (node !== previous) { node = nodeIterator.nextNode(); if (node === null) { return findNextTextNode(root.parentNode, previous); } } let result = nodeIterator.nextNode(); if (result !== null) { return result; } else { return findNextTextNode(root.parentNode, previous); } } function findPreviousTextNode(root, previous) { if (root === null) { return null; } let nodeIterator = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, null); let node = nodeIterator.nextNode(); while (node !== previous) { node = nodeIterator.nextNode(); if (node === null) { return findPreviousTextNode(root.parentNode, previous); } } nodeIterator.previousNode(); let result = nodeIterator.previousNode(); if (result !== null) { return result; } else { return findPreviousTextNode(root.parentNode, previous); } } // https://github.com/cschiller/zhongwen function enableTab() { document.addEventListener('mousedown', onMouseMove); document.addEventListener('touchstart', onMouseMove); document.addEventListener('keydown', onKeyDown); } function disableTab() { document.removeEventListener('mousedown', onMouseMove); document.removeEventListener('touchstart', onMouseMove); document.removeEventListener('keydown', onKeyDown); let popup = document.getElementById('zhongwen-window'); if (popup) { popup.parentNode.removeChild(popup); } clearHighlight(); } function saveWordList() { let entries = []; for (let j = 0; j < savedSearchResults.length; j++) { let entry = { simplified: savedSearchResults[j][0], traditional: savedSearchResults[j][1], pinyin: savedSearchResults[j][2], definition: savedSearchResults[j][3] }; entries.push(entry); } if (wordListWindow && !wordListWindow.closed) { wordListWindow.postMessage({ message: "requestResult", entries: entries }, "*"); } else { wordListWindow = window.open(`${host}/wordlist.html`); } let interval = setInterval(function () { try { wordListWindow.postMessage({ message: "requestResult", entries: entries }, "*"); } catch (e) { if (wordListWindow.closed) { clearInterval(interval); return; } } }, 500); window.addEventListener("message", function (event) { if (event.data.message === "deliverResult") { clearInterval(interval); wordListWindow.postMessage({ message: "refreshPage", refresh: true }, "*"); } }, false); showPopup(`Added to word list.<p>Press View button to open word list.`, null, -1, -1); } function viewWordList() { // Alt + w if (!wordListWindow) { wordListWindow = window.open(`${host}/wordlist.html`); } else { wordListWindow.focus(); wordListWindow.location.href = `${host}/wordlist.html`; } } function infoWindowOpen(url) { if (!infoWindow || infoWindow.closed) { infoWindow = window.open(url); } else { infoWindow.location.href = url; infoWindow.focus(); } } function toggleOption() { popupContainer.style.display = (popupContainer.style.display === 'none' || popupContainer.style.display === '') ? 'block' : 'none'; } async function loadVals() { let config = await getConfig(); const popupColor = config['popupColor'] || 'yellow'; document.querySelector(`input[name="popupColor"][value="${popupColor}"]`).checked = true; const toneColorScheme = config['toneColorScheme'] || 'standard'; if (toneColorScheme === 'none') { document.querySelector('#toneColorsNone').checked = true; } else { document.querySelector(`input[name="toneColors"][value="${toneColorScheme}"]`).checked = true; } const fontSize = config['fontSize'] || 'small'; document.querySelector(`input[name="fontSize"][value="${fontSize}"]`).checked = true; const simpTrad = config['simpTrad'] || 'classic'; document.querySelector(`input[name="simpTrad"][value="${simpTrad}"]`).checked = true; const saveToWordList = config['saveToWordList'] || 'allEntries'; document.querySelector(`input[name="saveToWordList"][value="${saveToWordList}"]`).checked = true; const cnTtsEngine = config['cnTtsEngine'] || 'browser'; document.querySelector(`input[name="cnTtsEngine"][value="${cnTtsEngine}"]`).checked = true; document.querySelector('#hanzipopup-grammar').checked = config['grammar']; document.querySelector('#hanzipopup-vocab').checked = config['vocab']; document.querySelector('#hanzipopup-zhuyin').checked = config['zhuyin']; document.querySelector('#hanzipopup-tts').checked = config['tts']; document.querySelector('#hanzipopup-prev').checked = config['prev']; document.querySelector('#hanzipopup-next').checked = config['next']; document.querySelector('#hanzipopup-more').checked = config['more']; infoButtons.forEach(function (button) { document.querySelector(`#hanzipopup-${button.id}`).checked = config[button.id]; }); if (config['ttsvoice']) { document.querySelector("#voiceSelection").value = config['ttsvoice']; } else { config['ttsvoice'] = "zh-CN-XiaoxiaoNeural"; document.querySelector("#voiceSelection").value = "zh-CN-XiaoxiaoNeural"; await setConfig('ttsvoice', config['ttsvoice']); } if (config['more']) { document.querySelector("#hanzipopup-more-info").style.display = "block"; } else { document.querySelector("#hanzipopup-more-info").style.display = "none"; } } function shouldHidePopup() { if (clickedTarget.tagName === "BUTTON") { return false; } return true; } // https://github.com/cschiller/zhongwen async function showPopup(html, elem, x, y, looseWidth) { let config = await getConfig(); if (!x || !y) { x = y = 0; } let popup = document.getElementById('zhongwen-window'); if (!popup) { popup = document.createElement('div'); popup.setAttribute('id', 'zhongwen-window'); document.body.appendChild(popup); } popup.style.width = 'auto'; popup.style.height = 'auto'; popup.style.maxWidth = (looseWidth ? '' : '600px'); popup.className = `background-${config.popupColor} tonecolor-${config.toneColorScheme}`; popup.innerHTML = html; if (elem) { popup.style.top = '-1000px'; popup.style.left = '0px'; popup.style.display = ''; let pW = popup.offsetWidth; let pH = popup.offsetHeight; if (pW <= 0) { pW = 200; } if (pH <= 0) { pH = 0; let j = 0; while ((j = html.indexOf('<br/>', j)) !== -1) { j += 5; pH += 22; } pH += 25; } if (altView === 1) { x = window.scrollX; y = window.scrollY; } else if (altView === 2) { x = (window.innerWidth - (pW + 20)) + window.scrollX; y = (window.innerHeight - (pH + 20)) + window.scrollY; } else if (elem instanceof window.HTMLOptionElement) { x = 0; y = 0; let p = elem; while (p) { x += p.offsetLeft; y += p.offsetTop; p = p.offsetParent; } if (elem.offsetTop > elem.parentNode.clientHeight) { y -= elem.offsetTop; } if (x + popup.offsetWidth > window.innerWidth) { // too much to the right, go left x -= popup.offsetWidth + 5; if (x < 0) { x = 0; } } else { // use SELECT's width x += elem.parentNode.offsetWidth + 5; } } else { // go left if necessary if (x + pW > window.innerWidth - 20) { x = (window.innerWidth - pW) - 20; if (x < 0) { x = 0; } } // below the mouse let v = 25; // go up if necessary if (y + v + pH > window.innerHeight) { let t = y - pH - 30; if (t >= 0) { y = t; } } else { y += v; } x += window.scrollX; y += window.scrollY; } } else { x += window.scrollX; y += window.scrollY; } // (-1, -1) indicates: leave position unchanged if (x !== -1 && y !== -1) { popup.style.left = x + 'px'; popup.style.top = y + 'px'; popup.style.display = ''; } } async function makeHtml(result, showToneColors) { let config = await getConfig(); let entry; let html = ''; let texts = []; let hanziClass; if (result === null) return ''; for (let i = 0; i < result.data.length; ++i) { entry = result.data[i][0].match(/^([^\s]+?)\s+([^\s]+?)\s+\[(.*?)\]?\s*\/(.+)\//); if (!entry) continue; // Hanzi if (config.simpTrad === 'auto') { let word = result.data[i][1]; hanziClass = 'w-hanzi'; if (config.fontSize === 'small') { hanziClass += '-small'; } html += '<span class="' + hanziClass + '">' + word + '</span> '; } else { hanziClass = 'w-hanzi'; if (config.fontSize === 'small') { hanziClass += '-small'; } html += '<span class="' + hanziClass + '">' + entry[2] + '</span> '; if (entry[1] !== entry[2]) { html += '<span class="' + hanziClass + '">' + entry[1] + '</span> '; } } // Pinyin let pinyinClass = 'w-pinyin'; if (config.fontSize === 'small') { pinyinClass += '-small'; } let p = await pinyinAndZhuyin(entry[3], showToneColors, pinyinClass); html += p[0]; // Zhuyin if (config.zhuyin) { html += '<br>' + p[2]; } // Definition let defClass = 'w-def'; if (config.fontSize === 'small') { defClass += '-small'; } let translation = entry[4].replace(/\//g, ' ◆ '); html += '<br><span class="' + defClass + '">' + translation + '</span><br>'; let addFinalBr = false; // Grammar if (config['grammar'] && result.grammar && result.grammar.index === i) { html += '<br><span class="grammar">Press "g" for grammar and usage notes.</span><br>'; addFinalBr = true; } // Vocab if (config['vocab'] && result.vocab && result.vocab.index === i) { html += '<br><span class="vocab">Press "v" for vocabulary notes.</span><br>'; addFinalBr = true; } if (addFinalBr) { html += '<br>'; } texts[i] = [entry[2], entry[1], p[1], translation, entry[3]]; } if (result.more) { html += '…<br/>'; } savedSearchResults = texts; savedSearchResults.grammar = result.grammar; savedSearchResults.vocab = result.vocab; return html; } function hidePopup() { if(!shouldHidePopup()) { return; } let popup = document.getElementById('zhongwen-window'); if (popup) { popup.style.display = 'none'; popup.textContent = ''; } } // https://github.com/cschiller/zhongwen let tones = { 1: '̄', 2: '́', 3: '̌', 4: '̀', 5: '' }; let utones = { 1: '\u0304', 2: '\u0301', 3: '\u030C', 4: '\u0300', 5: '' }; function parse(s) { return s.match(/([^AEIOU:aeiou]*)([AEIOUaeiou:]+)([^aeiou:]*)([1-5])/); } function tonify(vowels, tone) { let html = ''; let text = ''; if (vowels === 'ou') { html = 'o' + tones[tone] + 'u'; text = 'o' + utones[tone] + 'u'; } else { let tonified = false; for (let i = 0; i < vowels.length; i++) { let c = vowels.charAt(i); html += c; text += c; if (c === 'a' || c === 'e') { html += tones[tone]; text += utones[tone]; tonified = true; } else if (i === vowels.length - 1 && !tonified) { html += tones[tone]; text += utones[tone]; tonified = true; } } html = html.replace(/u:/, 'ü'); text = text.replace(/u:/, '\u00FC'); } return [html, text]; } async function pinyinAndZhuyin(syllables, showToneColors, pinyinClass) { let config = await getConfig(); let text = ''; let html = ''; let zhuyin = ''; let a = syllables.split(/[\s·]+/); for (let i = 0; i < a.length; i++) { let syllable = a[i]; // ',' in pinyin if (syllable === ',') { html += ' ,'; text += ' ,'; continue; } if (i > 0) { html += ' '; text += ' '; zhuyin += ' '; } if (syllable === 'r5') { if (showToneColors) { html += '<span class="' + pinyinClass + ' tone5">r</span>'; } else { html += '<span class="' + pinyinClass + '">r</span>'; } text += 'r'; continue; } if (syllable === 'xx5') { if (showToneColors) { html += '<span class="' + pinyinClass + ' tone5">??</span>'; } else { html += '<span class="' + pinyinClass + '">??</span>'; } text += '??'; continue; } let m = parse(syllable); if (showToneColors) { html += '<span class="' + pinyinClass + ' tone' + m[4] + '">'; } else { html += '<span class="' + pinyinClass + '">'; } let t = tonify(m[2], m[4]); html += m[1] + t[0] + m[3]; html += '</span>'; text += m[1] + t[1] + m[3]; let zhuyinClass = 'w-zhuyin'; if (config.fontSize === 'small') { zhuyinClass += '-small'; } zhuyin += '<span class="tone' + m[4] + ' ' + zhuyinClass + '">' + numericPinyin2Zhuyin(syllable) + '</span>'; } return [html, text, zhuyin]; } const zhuyinTones = ['?', '', '\u02CA', '\u02C7', '\u02CB', '\u30FB']; const pinyinTones = { 1: '\u0304', 2: '\u0301', 3: '\u030C', 4: '\u0300', 5: '' }; const zhuyinMap = { 'a': '\u311a', 'ai': '\u311e', 'an': '\u3122', 'ang': '\u3124', 'ao': '\u3120', 'ba': '\u3105\u311a', 'bai': '\u3105\u311e', 'ban': '\u3105\u3122', 'bang': '\u3105\u3124', 'bao': '\u3105\u3120', 'bei': '\u3105\u311f', 'ben': '\u3105\u3123', 'beng': '\u3105\u3125', 'bi': '\u3105\u3127', 'bian': '\u3105\u3127\u3122', 'biao': '\u3105\u3127\u3120', 'bie': '\u3105\u3127\u311d', 'bin': '\u3105\u3127\u3123', 'bing': '\u3105\u3127\u3125', 'bo': '\u3105\u311b', 'bu': '\u3105\u3128', 'ca': '\u3118\u311a', 'cai': '\u3118\u311e', 'can': '\u3118\u3122', 'cang': '\u3118\u3124', 'cao': '\u3118\u3120', 'ce': '\u3118\u311c', 'cen': '\u3118\u3123', 'ceng': '\u3118\u3125', 'cha': '\u3114\u311a', 'chai': '\u3114\u311e', 'chan': '\u3114\u3122', 'chang': '\u3114\u3124', 'chao': '\u3114\u3120', 'che': '\u3114\u311c', 'chen': '\u3114\u3123', 'cheng': '\u3114\u3125', 'chi': '\u3114', 'chong': '\u3114\u3128\u3125', 'chou': '\u3114\u3121', 'chu': '\u3114\u3128', 'chua': '\u3114\u3128\u311a', 'chuai': '\u3114\u3128\u311e', 'chuan': '\u3114\u3128\u3122', 'chuang': '\u3114\u3128\u3124', 'chui': '\u3114\u3128\u311f', 'chun': '\u3114\u3128\u3123', 'chuo': '\u3114\u3128\u311b', 'ci': '\u3118', 'cong': '\u3118\u3128\u3125', 'cou': '\u3118\u3121', 'cu': '\u3118\u3128', 'cuan': '\u3118\u3128\u3122', 'cui': '\u3118\u3128\u311f', 'cun': '\u3118\u3128\u3123', 'cuo': '\u3118\u3128\u311b', 'da': '\u3109\u311a', 'dai': '\u3109\u311e', 'dan': '\u3109\u3122', 'dang': '\u3109\u3124', 'dao': '\u3109\u3120', 'de': '\u3109\u311c', 'dei': '\u3109\u311f', 'den': '\u3109\u3123', 'deng': '\u3109\u3125', 'di': '\u3109\u3127', 'dian': '\u3109\u3127\u3122', 'diang': '\u3109\u3127\u3124', 'diao': '\u3109\u3127\u3120', 'die': '\u3109\u3127\u311d', 'ding': '\u3109\u3127\u3125', 'diu': '\u3109\u3127\u3121', 'dong': '\u3109\u3128\u3125', 'dou': '\u3109\u3121', 'du': '\u3109\u3128', 'duan': '\u3109\u3128\u3122', 'dui': '\u3109\u3128\u311f', 'dun': '\u3109\u3128\u3123', 'duo': '\u3109\u3128\u311b', 'e': '\u311c', 'ei': '\u311f', 'en': '\u3123', 'er': '\u3126', 'fa': '\u3108\u311a', 'fan': '\u3108\u3122', 'fang': '\u3108\u3124', 'fei': '\u3108\u311f', 'fen': '\u3108\u3123', 'feng': '\u3108\u3125', 'fo': '\u3108\u311b', 'fou': '\u3108\u3121', 'fu': '\u3108\u3128', 'ga': '\u310d\u311a', 'gai': '\u310d\u311e', 'gan': '\u310d\u3122', 'gang': '\u310d\u3124', 'gao': '\u310d\u3120', 'ge': '\u310d\u311c', 'gei': '\u310d\u311f', 'gen': '\u310d\u3123', 'geng': '\u310d\u3125', 'gong': '\u310d\u3128\u3125', 'gou': '\u310d\u3121', 'gu': '\u310d\u3128', 'gua': '\u310d\u3128\u311a', 'guai': '\u310d\u3128\u311e', 'guan': '\u310d\u3128\u3122', 'guang': '\u310d\u3128\u3124', 'gui': '\u310d\u3128\u311f', 'gun': '\u310d\u3128\u3123', 'guo': '\u310d\u3128\u311b', 'ha': '\u310f\u311a', 'hai': '\u310f\u311e', 'han': '\u310f\u3122', 'hang': '\u310f\u3124', 'hao': '\u310f\u3120', 'he': '\u310f\u311c', 'hei': '\u310f\u311f', 'hen': '\u310f\u3123', 'heng': '\u310f\u3125', 'hong': '\u310f\u3128\u3125', 'hou': '\u310f\u3121', 'hu': '\u310f\u3128', 'hua': '\u310f\u3128\u311a', 'huai': '\u310f\u3128\u311e', 'huan': '\u310f\u3128\u3122', 'huang': '\u310f\u3128\u3124', 'hui': '\u310f\u3128\u311f', 'hun': '\u310f\u3128\u3123', 'huo': '\u310f\u3128\u311b', 'ji': '\u3110\u3127', 'jia': '\u3110\u3127\u311a', 'jian': '\u3110\u3127\u3122', 'jiang': '\u3110\u3127\u3124', 'jiao': '\u3110\u3127\u3120', 'jie': '\u3110\u3127\u311d', 'jin': '\u3110\u3127\u3123', 'jing': '\u3110\u3127\u3125', 'jiong': '\u3110\u3129\u3125', 'jiu': '\u3110\u3127\u3121', 'ju': '\u3110\u3129', 'juan': '\u3110\u3129\u3122', 'jue': '\u3110\u3129\u311d', 'jun': '\u3110\u3129\u3123', 'ka': '\u310e\u311a', 'kai': '\u310e\u311e', 'kan': '\u310e\u3122', 'kang': '\u310e\u3124', 'kao': '\u310e\u3120', 'ke': '\u310e\u311c', 'ken': '\u310e\u3123', 'keng': '\u310e\u3125', 'kong': '\u310e\u3128\u3125', 'kou': '\u310e\u3121', 'ku': '\u310e\u3128', 'kua': '\u310e\u3128\u311a', 'kuai': '\u310e\u3128\u311e', 'kuan': '\u310e\u3128\u3122', 'kuang': '\u310e\u3128\u3124', 'kui': '\u310e\u3128\u311f', 'kun': '\u310e\u3128\u3123', 'kuo': '\u310e\u3128\u311b', 'la': '\u310c\u311a', 'lai': '\u310c\u311e', 'lan': '\u310c\u3122', 'lang': '\u310c\u3124', 'lao': '\u310c\u3120', 'le': '\u310c\u311c', 'lei': '\u310c\u311f', 'leng': '\u310c\u3125', 'li': '\u310c\u3127', 'lia': '\u310c\u3127\u311a', 'lian': '\u310c\u3127\u3122', 'liang': '\u310c\u3127\u3124', 'liao': '\u310c\u3127\u3120', 'lie': '\u310c\u3127\u311d', 'lin': '\u310c\u3127\u3123', 'ling': '\u310c\u3127\u3125', 'liu': '\u310c\u3127\u3121', 'lo': '\u310c\u311b', 'long': '\u310c\u3128\u3125', 'lou': '\u310c\u3121', 'lu': '\u310c\u3128', 'lu:': '\u310c\u3129', 'luan': '\u310c\u3128\u3123', 'lu:e': '\u310c\u3129\u311d', 'lun': '\u310c\u3129', 'lu:n': '\u310c\u3129\u3123', 'luo': '\u310c\u3129\u3123', 'ma': '\u3107\u311a', 'mai': '\u3107\u311e', 'man': '\u3107\u3122', 'mang': '\u3107\u3124', 'mao': '\u3107\u3120', 'me': '\u3107\u311c', 'mei': '\u3107\u311f', 'men': '\u3107\u3123', 'meng': '\u3107\u3125', 'mi': '\u3107\u3127', 'mian': '\u3107\u3127\u3122', 'miao': '\u3107\u3127\u3120', 'mie': '\u3107\u3127\u311d', 'min': '\u3107\u3127\u3123', 'ming': '\u3107\u3127\u3125', 'miu': '\u3107\u3127\u3121', 'mo': '\u3107\u311b', 'mou': '\u3107\u3121', 'mu': '\u3107\u3128', 'na': '\u310b\u311a', 'nai': '\u310b\u311e', 'nan': '\u310b\u3122', 'nang': '\u310b\u3124', 'nao': '\u310b\u3120', 'ne': '\u310b\u311c', 'nei': '\u310b\u311f', 'nen': '\u310b\u3123', 'neng': '\u310b\u3125', 'ni': '\u310b\u3127', 'nia': '\u310b\u3127\u311a', 'nian': '\u310b\u3127\u3122', 'niang': '\u310b\u3127\u3124', 'niao': '\u310b\u3127\u3120', 'nie': '\u310b\u3127\u311d', 'nin': '\u310b\u3127\u3123', 'ning': '\u310b\u3127\u3125', 'niu': '\u310b\u3127\u3121', 'nong': '\u310b\u3128\u3125', 'nou': '\u310b\u3121', 'nu': '\u310b\u3128', 'nu:': '\u310b\u3129', 'nuan': '\u310b\u3128\u3123', 'nu:e': '\u310b\u3129\u311d', 'nun': '\u310b\u3129', 'nuo': '\u310b\u3129\u311d', 'ou': '\u3121', 'pa': '\u3106\u311a', 'pai': '\u3106\u311e', 'pan': '\u3106\u3122', 'pang': '\u3106\u3124', 'pao': '\u3106\u3120', 'pei': '\u3106\u311f', 'pen': '\u3106\u3123', 'peng': '\u3106\u3125', 'pi': '\u3106\u3127', 'pian': '\u3106\u3127\u3122', 'piao': '\u3106\u3127\u3120', 'pie': '\u3106\u3127\u311d', 'pin': '\u3106\u3127\u3123', 'ping': '\u3106\u3127\u3125', 'po': '\u3106\u311b', 'pou': '\u3106\u3121', 'pu': '\u3106\u3128', 'qi': '\u3111\u3127', 'qia': '\u3111\u3127\u311a', 'qian': '\u3111\u3127\u3122', 'qiang': '\u3111\u3127\u3124', 'qiao': '\u3111\u3127\u3120', 'qie': '\u3111\u3127\u311d', 'qin': '\u3111\u3127\u3123', 'qing': '\u3111\u3127\u3125', 'qiong': '\u3111\u3129\u3125', 'qiu': '\u3111\u3127\u3121', 'qu': '\u3111\u3129', 'quan': '\u3111\u3129\u3122', 'que': '\u3111\u3129\u311d', 'qun': '\u3111\u3129\u3123', 'ran': '\u3116\u3122', 'rang': '\u3116\u3124', 'rao': '\u3116\u3120', 're': '\u3116\u311c', 'ren': '\u3116\u3123', 'reng': '\u3116\u3125', 'ri': '\u3116', 'rong': '\u3116\u3128\u3125', 'rou': '\u3116\u3121', 'ru': '\u3116\u3128', 'ruan': '\u3116\u3128\u3122', 'rui': '\u3116\u3128\u311f', 'run': '\u3116\u3128\u3123', 'ruo': '\u3116\u3128\u311b', 'sa': '\u3119\u311a', 'sai': '\u3119\u311e', 'san': '\u3119\u3122', 'sang': '\u3119\u3124', 'sao': '\u3119\u3120', 'se': '\u3119\u311c', 'sei': '\u3119\u311f', 'sen': '\u3119\u3123', 'seng': '\u3119\u3125', 'sha': '\u3115\u311a', 'shai': '\u3115\u311e', 'shan': '\u3115\u3122', 'shang': '\u3115\u3124', 'shao': '\u3115\u3120', 'she': '\u3115\u311c', 'shei': '\u3115\u311f', 'shen': '\u3115\u3123', 'sheng': '\u3115\u3125', 'shi': '\u3115', 'shong': '\u3115\u3128\u3125', 'shou': '\u3115\u3121', 'shu': '\u3115\u3128', 'shua': '\u3115\u3128\u311a', 'shuai': '\u3115\u3128\u311e', 'shuan': '\u3115\u3128\u3122', 'shuang': '\u3115\u3128\u3124', 'shui': '\u3115\u3128\u311f', 'shun': '\u3115\u3128\u3123', 'shuo': '\u3115\u3128\u311b', 'si': '\u3119', 'song': '\u3119\u3128\u3125', 'sou': '\u3119\u3121', 'su': '\u3119\u3128', 'suan': '\u3119\u3128\u3122', 'sui': '\u3119\u3128\u311f', 'sun': '\u3119\u3128\u3123', 'suo': '\u3119\u3128\u311b', 'ta': '\u310a\u311a', 'tai': '\u310a\u311e', 'tan': '\u310a\u3122', 'tang': '\u310a\u3124', 'tao': '\u310a\u3120', 'te': '\u310a\u311c', 'teng': '\u310a\u3125', 'ti': '\u310a\u3127', 'tian': '\u310a\u3127\u3122', 'tiao': '\u310a\u3127\u3120', 'tie': '\u310a\u3127\u311d', 'ting': '\u310a\u3127\u3125', 'tong': '\u310a\u3128\u3125', 'tou': '\u310a\u3121', 'tu': '\u310a\u3128', 'tuan': '\u310a\u3128\u3122', 'tui': '\u310a\u3128\u311f', 'tun': '\u310a\u3128\u3123', 'tuo': '\u310a\u3128\u311b', 'wa': '\u3128\u311a', 'wai': '\u3128\u311e', 'wan': '\u3128\u3122', 'wang': '\u3128\u3124', 'wei': '\u3128\u311f', 'wen': '\u3128\u3123', 'weng': '\u3128\u3125', 'wo': '\u3128\u311b', 'wu': '\u3128', 'xi': '\u3112\u3127', 'xia': '\u3112\u3127\u311a', 'xian': '\u3112\u3127\u3122', 'xiang': '\u3112\u3127\u3124', 'xiao': '\u3112\u3127\u3120', 'xie': '\u3112\u3127\u311d', 'xin': '\u3112\u3127\u3123', 'xing': '\u3112\u3127\u3125', 'xiong': '\u3112\u3129\u3125', 'xiu': '\u3112\u3127\u3121', 'xu': '\u3112\u3129', 'xuan': '\u3112\u3129\u3122', 'xue': '\u3112\u3129\u311d', 'xun': '\u3112\u3129\u3123', 'ya': '\u3127\u311a', 'yan': '\u3127\u3122', 'yang': '\u3127\u3124', 'yao': '\u3127\u3120', 'ye': '\u3127\u311d', 'yi': '\u3127', 'yin': '\u3127\u3123', 'ying': '\u3127\u3125', 'yong': '\u3129\u3125', 'you': '\u3127\u3121', 'yu': '\u3129', 'yuan': '\u3129\u3122', 'yue': '\u3129\u311d', 'yun': '\u3129\u3123', 'za': '\u3117\u311a', 'zai': '\u3117\u311e', 'zan': '\u3117\u3122', 'zang': '\u3117\u3124', 'zao': '\u3117\u3120', 'ze': '\u3117\u311c', 'zei': '\u3117\u311f', 'zen': '\u3117\u3123', 'zeng': '\u3117\u3125', 'zha': '\u3113\u311a', 'zhai': '\u3113\u311e', 'zhan': '\u3113\u3122', 'zhang': '\u3113\u3124', 'zhao': '\u3113\u3120', 'zhe': '\u3113\u311c', 'zhei': '\u3113\u311f', 'zhen': '\u3113\u3123', 'zheng': '\u3113\u3125', 'zhi': '\u3113', 'zhong': '\u3113\u3128\u3125', 'zhou': '\u3113\u3121', 'zhu': '\u3113\u3128', 'zhua': '\u3113\u3128\u311a', 'zhuai': '\u3113\u3128\u311e', 'zhuan': '\u3113\u3128\u3122', 'zhuang': '\u3113\u3128\u3124', 'zhui': '\u3113\u3128\u311f', 'zhun': '\u3113\u3128\u3123', 'zhuo': '\u3113\u3128\u311b', 'zi': '\u3117', 'zong': '\u3117\u3128\u3125', 'zou': '\u3117\u3121', 'zu': '\u3117\u3128', 'zuan': '\u3117\u3128\u3122', 'zui': '\u3117\u3128\u311f', 'zun': '\u3117\u3128\u3123', 'zuo': '\u3117\u3128\u311b' }; globalThis.numericPinyin2Zhuyin = function (syllable) { return zhuyinMap[syllable.substring(0, syllable.length - 1).toLowerCase()] + zhuyinTones[syllable[syllable.length - 1]] + '</span>'; }; globalThis.accentedPinyin2Zhuyin = function (syllable) { let lowerCased = syllable.toLowerCase(); let key = lowerCased; let tone = 5; for (let i = 1; i <= 4; i++) { let idx = lowerCased.indexOf(pinyinTones[i]); if (idx > 0) { key = lowerCased.substring(0, idx); if (idx < lowerCased.length - 1) { key += lowerCased.substring(idx + 1); } tone = i; break; } } return zhuyinMap[key] + zhuyinTones[tone]; }; /* [email protected], license MIT */ 'use strict';(function(z,G){"object"===typeof exports&&"undefined"!==typeof module?G(exports):"function"===typeof define&&define.amd?define(["exports"],G):(z="undefined"!==typeof globalThis?globalThis:z||self,G(z.unzipit={}))})(this,function(z){function G(a){return a.arrayBuffer?a.arrayBuffer():new Promise((b,c)=>{const e=new FileReader;e.addEventListener("loadend",()=>{b(e.result)});e.addEventListener("error",c);e.readAsArrayBuffer(a)})}async function na(a){a=await G(a);return new Uint8Array(a)} function aa(a){return"undefined"!==typeof Blob&&a instanceof Blob}function I(a){return"undefined"!==typeof SharedArrayBuffer&&a instanceof SharedArrayBuffer}function R(a,b){var c=a.length;if(b<=c)return a;b=new Uint8Array(Math.max(c<<1,b));b.set(a,0);return b}function oa(a,b,c,e,d,h){for(var k=ba,f=ca,l=0;l<c;){var n=a[f(e,d)&b];d+=n&15;var u=n>>>4;if(15>=u)h[l]=u,l++;else{var x=n=0;16==u?(x=3+k(e,d,2),d+=2,n=h[l-1]):17==u?(x=3+k(e,d,3),d+=3):18==u&&(x=11+k(e,d,7),d+=7);for(u=l+x;l<u;)h[l]=n,l++}}return d} function da(a,b,c,e){for(var d=0,h=0,k=e.length>>>1;h<c;){var f=a[h+b];e[h<<1]=0;e[(h<<1)+1]=f;f>d&&(d=f);h++}for(;h<k;)e[h<<1]=0,e[(h<<1)+1]=0,h++;return d}function J(a,b){var c=a.length,e,d;var h=g.bl_count;for(d=0;d<=b;d++)h[d]=0;for(d=1;d<c;d+=2)h[a[d]]++;d=g.next_code;var k=0;h[0]=0;for(e=1;e<=b;e++)k=k+h[e-1]<<1,d[e]=k;for(b=0;b<c;b+=2)h=a[b+1],0!=h&&(a[b]=d[h],d[h]++)}function K(a,b,c){for(var e=a.length,d=g.rev15,h=0;h<e;h+=2)if(0!=a[h+1]){var k=a[h+1],f=h>>1<<4|k,l=b-k;k=a[h]<<l;for(l=k+ (1<<l);k!=l;)c[d[k]>>>15-b]=f,k++}}function ea(a,b){for(var c=g.rev15,e=15-b,d=0;d<a.length;d+=2)a[d]=c[a[d]<<b-a[d+1]]>>>e}function ba(a,b,c){return(a[b>>>3]|a[(b>>>3)+1]<<8)>>>(b&7)&(1<<c)-1}function pa(a,b,c){return(a[b>>>3]|a[(b>>>3)+1]<<8|a[(b>>>3)+2]<<16)>>>(b&7)&(1<<c)-1}function ca(a,b){return(a[b>>>3]|a[(b>>>3)+1]<<8|a[(b>>>3)+2]<<16)>>>(b&7)}function qa(a){D.push(a.target);S();const {id:b,error:c,data:e}=a.data;a=N.get(b);N.delete(b);c?a.reject(c):a.resolve(e)}function T(a){return new Promise((b, c)=>{const e=new Worker(a);e.onmessage=d=>{"start"===d.data?(e.onerror=void 0,e.onmessage=void 0,b(e)):c(Error(`unexpected message: ${d.data}`))};e.onerror=c})}async function ra(){if(0===D.length&&U<y.numWorkers){++U;try{const a=await V.createWorker(y.workerURL);O.push(a);D.push(a);V.addEventListener(a,qa)}catch(a){P=!1}}return D.pop()}async function S(){if(0!==B.length){if(y.useWorkers&&P){var a=await ra();if(P){if(a){if(0===B.length){D.push(a);S();return}const {id:E,src:W,uncompressedSize:X,type:Y, resolve:L,reject:sa}=B.shift();N.set(E,{id:E,resolve:L,reject:sa});a.postMessage({type:"inflate",data:{id:E,type:Y,src:W,uncompressedSize:X}},[])}return}}for(;B.length;){const {src:E,uncompressedSize:W,type:X,resolve:Y}=B.shift();a=E;aa(E)&&(a=await na(E));{var b=a;a=X;var c=Y;const L=new Uint8Array(W);var e=void 0,d=void 0,h,k=L,f=Uint8Array;if(3==b[0]&&0==b[1])k||new f(0);else{var l=pa,n=ba,u=oa,x=ca,A=null==k;A&&(k=new f(b.length>>>2<<3));for(var C=0,t=0,r=h=0,m=0;0==C;){C=l(b,m,1);var q=l(b,m+ 1,2);m+=3;if(0==q)0!=(m&7)&&(m+=8-(m&7)),m=(m>>>3)+4,q=b[m-4]|b[m-3]<<8,A&&(k=R(k,r+q)),k.set(new f(b.buffer,b.byteOffset+m,q),r),m=m+q<<3,r+=q;else{A&&(k=R(k,r+131072));1==q&&(d=g.flmap,e=g.fdmap,t=511,h=31);if(2==q){q=n(b,m,5)+257;h=n(b,m+5,5)+1;e=n(b,m+10,4)+4;m+=14;for(d=0;38>d;d+=2)g.itree[d]=0,g.itree[d+1]=0;t=1;for(d=0;d<e;d++){var p=n(b,m+3*d,3);g.itree[(g.ordr[d]<<1)+1]=p;p>t&&(t=p)}m+=3*e;J(g.itree,t);K(g.itree,t,g.imap);d=g.lmap;e=g.dmap;m=u(g.imap,(1<<t)-1,q+h,b,m,g.ttree);p=da(g.ttree, 0,q,g.ltree);t=(1<<p)-1;q=da(g.ttree,q,h,g.dtree);h=(1<<q)-1;J(g.ltree,p);K(g.ltree,p,d);J(g.dtree,q);K(g.dtree,q,e)}for(;;)if(q=d[x(b,m)&t],m+=q&15,p=q>>>4,0==p>>>8)k[r++]=p;else if(256==p)break;else{q=r+p-254;264<p&&(p=g.ldef[p-257],q=r+(p>>>3)+n(b,m,p&7),m+=p&7);p=e[x(b,m)&h];m+=p&15;p=g.ddef[p>>>4];var Q=(p>>>4)+l(b,m,p&15);m+=p&15;for(A&&(k=R(k,r+131072));r<q;)k[r]=k[r++-Q],k[r]=k[r++-Q],k[r]=k[r++-Q],k[r]=k[r++-Q];r=q}}}k.length==r||k.slice(0,r)}c(a?new Blob([L],{type:a}):L.buffer)}}}}function fa(a, b,c){return new Promise((e,d)=>{B.push({src:a,uncompressedSize:b,type:c,resolve:e,reject:d,id:ta++});S()})}async function ua(){for(const a of O)await V.terminate(a);O.splice(0,O.length);D.splice(0,D.length);B.splice(0,B.length);N.clear();U=0;P=!0}async function H(a,b,c){return await a.read(b,c)}async function Z(a,b,c,e){return a.sliceAsBlob?await a.sliceAsBlob(b,c,e):await a.read(b,c)}function v(a,b){return a[b]+256*a[b+1]}function w(a,b){return a[b]+256*a[b+1]+65536*a[b+2]+16777216*a[b+3]}function F(a, b){return w(a,b)+4294967296*w(a,b+4)}function M(a,b){I(a.buffer)&&(a=new Uint8Array(a));return va.decode(a)}async function wa(a,b){var c=Math.min(65557,b);b-=c;var e=await H(a,b,c);for(c-=22;0<=c;--c){if(101010256!==w(e,c))continue;var d=new Uint8Array(e.buffer,e.byteOffset+c,e.byteLength-c);e=v(d,4);if(0!==e)throw Error(`multi-volume zip files are not supported. This is volume: ${e}`);e=v(d,10);const k=w(d,12),f=w(d,16);var h=v(d,20);const l=d.length-22;if(h!==l)throw Error(`invalid comment length. expected: ${l}, actual: ${h}`); d=new Uint8Array(d.buffer,d.byteOffset+22,h);h=M(d);return 65535===e||4294967295===f?await xa(a,b+c,h,d):await ha(a,f,k,e,h,d)}throw Error("could not find end of central directory. maybe not zip file");}async function xa(a,b,c,e){b=await H(a,b-20,20);if(117853008!==w(b,0))throw Error("invalid zip64 end of central directory locator signature");b=F(b,8);var d=await H(a,b,56);if(101075792!==w(d,0))throw Error("invalid zip64 end of central directory record signature");b=F(d,32);const h=F(d,40);d=F(d, 48);return ha(a,d,h,b,c,e)}async function ha(a,b,c,e,d,h){let k=0;b=await H(a,b,c);c=[];for(let A=0;A<e;++A){var f=b.subarray(k,k+46),l=w(f,0);if(33639248!==l)throw Error(`invalid central directory file header signature: 0x${l.toString(16)}`);f={versionMadeBy:v(f,4),versionNeededToExtract:v(f,6),generalPurposeBitFlag:v(f,8),compressionMethod:v(f,10),lastModFileTime:v(f,12),lastModFileDate:v(f,14),crc32:w(f,16),compressedSize:w(f,20),uncompressedSize:w(f,24),fileNameLength:v(f,28),extraFieldLength:v(f, 30),fileCommentLength:v(f,32),internalFileAttributes:v(f,36),externalFileAttributes:w(f,38),relativeOffsetOfLocalHeader:w(f,42)};if(f.generalPurposeBitFlag&64)throw Error("strong encryption is not supported");k+=46;l=b.subarray(k,k+f.fileNameLength+f.extraFieldLength+f.fileCommentLength);f.nameBytes=l.slice(0,f.fileNameLength);f.name=M(f.nameBytes);var n=f.fileNameLength+f.extraFieldLength;const C=l.slice(f.fileNameLength,n);f.extraFields=[];for(var u=0;u<C.length-3;){const t=v(C,u+0);var x=v(C,u+ 2);u+=4;x=u+x;if(x>C.length)throw Error("extra field length exceeds extra field buffer size");f.extraFields.push({id:t,data:C.slice(u,x)});u=x}f.commentBytes=l.slice(n,n+f.fileCommentLength);f.comment=M(f.commentBytes);k+=l.length;if(4294967295===f.uncompressedSize||4294967295===f.compressedSize||4294967295===f.relativeOffsetOfLocalHeader){l=f.extraFields.find(t=>1===t.id);if(!l)throw Error("expected zip64 extended information extra field");l=l.data;n=0;if(4294967295===f.uncompressedSize){if(n+8> l.length)throw Error("zip64 extended information extra field does not include uncompressed size");f.uncompressedSize=F(l,n);n+=8}if(4294967295===f.compressedSize){if(n+8>l.length)throw Error("zip64 extended information extra field does not include compressed size");f.compressedSize=F(l,n);n+=8}if(4294967295===f.relativeOffsetOfLocalHeader){if(n+8>l.length)throw Error("zip64 extended information extra field does not include relative header offset");f.relativeOffsetOfLocalHeader=F(l,n);n+=8}}if(l=f.extraFields.find(t=> 28789===t.id&&6<=t.data.length&&1===t.data[0]&&w(t.data,1),ya.unsigned(f.nameBytes)))f.fileName=M(l.data.slice(5));if(0===f.compressionMethod&&(l=f.uncompressedSize,0!==(f.generalPurposeBitFlag&1)&&(l+=12),f.compressedSize!==l))throw Error(`compressed size mismatch for stored file: ${f.compressedSize} != ${l}`);c.push(f)}return{zip:{comment:d,commentBytes:h},entries:c.map(A=>new za(a,A))}}async function ia(a,b){if(b.generalPurposeBitFlag&1)throw Error("encrypted entries not supported");var c=await H(a, b.relativeOffsetOfLocalHeader,30);a=await a.getLength();var e=w(c,0);if(67324752!==e)throw Error(`invalid local file header signature: 0x${e.toString(16)}`);e=v(c,26);var d=v(c,28);c=b.relativeOffsetOfLocalHeader+c.length+e+d;if(0===b.compressionMethod)e=!1;else if(8===b.compressionMethod)e=!0;else throw Error(`unsupported compression method: ${b.compressionMethod}`);d=c+b.compressedSize;if(0!==b.compressedSize&&d>a)throw Error(`file data overflows file bounds: ${c} + ${b.compressedSize} > ${a}`); return{decompress:e,fileDataStart:c}}async function Aa(a,b){const {decompress:c,fileDataStart:e}=await ia(a,b);if(!c)return b=await H(a,e,b.compressedSize),0===b.byteOffset&&b.byteLength===b.buffer.byteLength?b.buffer:b.slice().buffer;a=await Z(a,e,b.compressedSize);return await fa(a,b.uncompressedSize)}async function Ba(a,b,c){const {decompress:e,fileDataStart:d}=await ia(a,b);if(!e)return b=await Z(a,d,b.compressedSize,c),aa(b)?b:new Blob([I(b.buffer)?new Uint8Array(b):b],{type:c});a=await Z(a, d,b.compressedSize);return await fa(a,b.uncompressedSize,c)}async function ja(a){if("undefined"!==typeof Blob&&a instanceof Blob)a=new ka(a);else if(a instanceof ArrayBuffer||a&&a.buffer&&a.buffer instanceof ArrayBuffer)a=new la(a);else if(I(a)||I(a.buffer))a=new la(a);else if("string"===typeof a){var b=await fetch(a);if(!b.ok)throw Error(`failed http request ${a}, status: ${b.status}: ${b.statusText}`);a=await b.blob();a=new ka(a)}else if("function"!==typeof a.getLength||"function"!==typeof a.read)throw Error("unsupported source type"); b=await a.getLength();if(b>Number.MAX_SAFE_INTEGER)throw Error(`file too large. size: ${b}. Only file sizes up 4503599627370496 bytes are supported`);return await wa(a,b)}const Ca="undefined"!==typeof process&&process.versions&&"undefined"!==typeof process.versions.node&&"undefined"===typeof process.versions.electron;class la{constructor(a){this.typedArray=a instanceof ArrayBuffer||I(a)?new Uint8Array(a):new Uint8Array(a.buffer,a.byteOffset,a.byteLength)}async getLength(){return this.typedArray.byteLength}async read(a, b){return new Uint8Array(this.typedArray.buffer,this.typedArray.byteOffset+a,b)}}class ka{constructor(a){this.blob=a}async getLength(){return this.blob.size}async read(a,b){a=this.blob.slice(a,a+b);a=await G(a);return new Uint8Array(a)}async sliceAsBlob(a,b,c=""){return this.blob.slice(a,a+b,c)}}class Da{constructor(a){this.url=a}async getLength(){if(void 0===this.length){const a=await fetch(this.url,{method:"HEAD"});if(!a.ok)throw Error(`failed http request ${this.url}, status: ${a.status}: ${a.statusText}`); this.length=parseInt(a.headers.get("content-length"));if(Number.isNaN(this.length))throw Error("could not get length");}return this.length}async read(a,b){if(0===b)return new Uint8Array(0);const c=await fetch(this.url,{headers:{Range:`bytes=${a}-${a+b-1}`}});if(!c.ok)throw Error(`failed http request ${this.url}, status: ${c.status} offset: ${a} size: ${b}: ${c.statusText}`);a=await c.arrayBuffer();return new Uint8Array(a)}}const g=function(){var a=Uint16Array,b=Uint32Array;return{next_code:new a(16), bl_count:new a(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new a(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new b(32),flmap:new a(512), fltree:[],fdmap:new a(32),fdtree:[],lmap:new a(32768),ltree:[],ttree:[],dmap:new a(32768),dtree:[],imap:new a(512),itree:[],rev15:new a(32768),lhst:new b(286),dhst:new b(30),ihst:new b(19),lits:new b(15E3),strt:new a(65536),prev:new a(32768)}}();(function(){function a(e,d,h){for(;0!=d--;)e.push(0,h)}for(var b=0;32768>b;b++){var c=b;c=(c&2863311530)>>>1|(c&1431655765)<<1;c=(c&3435973836)>>>2|(c&858993459)<<2;c=(c&4042322160)>>>4|(c&252645135)<<4;c=(c&4278255360)>>>8|(c&16711935)<<8;g.rev15[b]=(c>>> 16|c<<16)>>>17}for(b=0;32>b;b++)g.ldef[b]=g.of0[b]<<3|g.exb[b],g.ddef[b]=g.df0[b]<<4|g.dxb[b];a(g.fltree,144,8);a(g.fltree,112,9);a(g.fltree,24,7);a(g.fltree,8,8);J(g.fltree,9);K(g.fltree,9,g.flmap);ea(g.fltree,9);a(g.fdtree,32,5);J(g.fdtree,5);K(g.fdtree,5,g.fdmap);ea(g.fdtree,5);a(g.itree,19,0);a(g.ltree,286,0);a(g.dtree,30,0);a(g.ttree,320,0)})();const ma={table:function(){for(var a=new Uint32Array(256),b=0;256>b;b++){for(var c=b,e=0;8>e;e++)c=c&1?3988292384^c>>>1:c>>>1;a[b]=c}return a}(),update:function(a, b,c,e){for(var d=0;d<e;d++)a=ma.table[(a^b[c+d])&255]^a>>>8;return a},crc:function(a,b,c){return ma.update(4294967295,a,b,c)^4294967295}},y={numWorkers:1,workerURL:"",useWorkers:!1};let ta=0,U=0,P=!0;const O=[],D=[],B=[],N=new Map,V=function(){if(Ca){const {Worker:a}=module.require?module.require("worker_threads"):{};return{async createWorker(b){return new a(b)},addEventListener(b,c){b.on("message",e=>{c({target:b,data:e})})},async terminate(b){await b.terminate()}}}return{async createWorker(a){try{return await T(a)}catch(c){console.warn("could not load worker:", a)}let b;try{const c=await fetch(a,{mode:"cors"});if(!c.ok)throw Error(`could not load: ${a}`);b=await c.text();a=URL.createObjectURL(new Blob([b],{type:"application/javascript"}));const e=await T(a);y.workerURL=a;return e}catch(c){console.warn("could not load worker via fetch:",a)}if(void 0!==b)try{a=`data:application/javascript;base64,${btoa(b)}`;const c=await T(a);y.workerURL=a;return c}catch(c){console.warn("could not load worker via dataURI")}console.warn("workers will not be used");throw Error("can not start workers"); },addEventListener(a,b){a.addEventListener("message",b)},async terminate(a){a.terminate()}}}();class za{constructor(a,b){this._reader=a;this._rawEntry=b;this.name=b.name;this.nameBytes=b.nameBytes;this.size=b.uncompressedSize;this.compressedSize=b.compressedSize;this.comment=b.comment;this.commentBytes=b.commentBytes;this.compressionMethod=b.compressionMethod;a=b.lastModFileDate;var c=b.lastModFileTime;this.lastModDate=new Date((a>>9&127)+1980,(a>>5&15)-1,a&31,c>>11&31,c>>5&63,2*(c&31),0);this.isDirectory= 0===b.uncompressedSize&&b.name.endsWith("/");this.encrypted=!!(b.generalPurposeBitFlag&1);this.externalFileAttributes=b.externalFileAttributes;this.versionMadeBy=b.versionMadeBy}async blob(a="application/octet-stream"){return await Ba(this._reader,this._rawEntry,a)}async arrayBuffer(){return await Aa(this._reader,this._rawEntry)}async text(){const a=await this.arrayBuffer();return M(new Uint8Array(a))}async json(){const a=await this.text();return JSON.parse(a)}}const ya={unsigned(){return 0}},va= new TextDecoder;z.HTTPRangeReader=Da;z.cleanup=function(){ua()};z.setOptions=function(a){y.workerURL=a.workerURL||y.workerURL;a.workerURL&&(y.useWorkers=!0);y.useWorkers=void 0!==a.useWorkers?a.useWorkers:y.useWorkers;y.numWorkers=a.numWorkers||y.numWorkers};z.unzip=async function(a){const {zip:b,entries:c}=await ja(a);return{zip:b,entries:Object.fromEntries(c.map(e=>[e.name,e]))}};z.unzipRaw=ja;Object.defineProperty(z,"__esModule",{value:!0})}); /* [email protected], license MIT */ /* Hanzipopup - A Chinese-English Pop-Up Dictionary UserScript Copyright (C) 2024 krmanik https://github.com/krmanik/hanzipopup */ async function setupHanzipopup() { await loadVals(); await loadDict(); let config = await getConfig(); if (config["enable"]) { enable = true; document.querySelector("#hanzi-popup-enable-btn > button").style.background = "#33b249"; enableTab(); } } setupHanzipopup();