- // ==UserScript==
- // @name ParaTranz diff
- // @namespace https://paratranz.cn/users/44232
- // @version 0.11.1
- // @description ParaTranz enhanced
- // @author ooo
- // @match http*://paratranz.cn/*
- // @icon https://paratranz.cn/favicon.png
- // @require https://cdnjs.cloudflare.com/ajax/libs/medium-zoom/1.1.0/medium-zoom.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js
- // @license MIT
- // ==/UserScript==
-
- (async function() {
- 'use strict';
-
- // #region 主要功能函数
-
- // #region 自动跳过空白页 shouldSkip
- function shouldSkip() {
- if (document.querySelector('.string-list .empty-sign') &&
- location.search.match(/(\?|&)page=\d+/g)) {
- document.querySelector('.pagination .page-item a')?.click();
- return true;
- }
- }
- // #endregion
-
- // #region 添加快捷键 addHotkeys
- function addHotkeys() {
- document.addEventListener('keydown', (e) => {
- if (e.ctrlKey && e.shiftKey && e.key === 'V') {
- e.preventDefault();
- mockInput(document.querySelector('.editor-core .original')?.textContent);
- }
- });
- }
- // #endregion
-
- // #region 更多搜索高亮 initDropMark markSearchParams initMarkJS watchContextBtn
- let markSearchParams = () => {};
- const mergeObjects = (obj1, obj2) => {
- const merged = {};
- for (const key of Object.keys(obj1)) {
- merged[key] = [...obj1[key], ...obj2[key]];
- }
- return merged;
- };
- function updMark() {
- const params = new URLSearchParams(location.search);
- const getParams = (type) => {
- return {
- contains: [...params.getAll(type)],
- startsWith: [...params.getAll(`${type}^`)],
- endsWith: [...params.getAll(`${type}$`)],
- match: [...params.getAll(`${type}~`)]
- };
- };
- const texts = getParams('text');
- const originals = getParams('original');
- const translations = getParams('translation');
- const contexts = getParams('context');
-
- const originKeywords = mergeObjects(texts, originals);
- const editingKeywords = mergeObjects(texts, translations);
- const contextKeywords = contexts;
-
- markSearchParams = () => {
- markOrigin(originKeywords);
- markContext(contextKeywords);
- }
-
- if (Object.values(editingKeywords).filter(v => v).length) {
- const dropMark = markEditing(editingKeywords);
- return dropMark;
- }
- }
- let dropLastTextareaMark;
- const initDropMark = () => dropLastTextareaMark = updMark();
-
- let originMark;
- let contextMark;
-
- function initMarkJS() {
- const original = document.querySelector('.editor-core .original');
- originMark = new Mark(original);
-
- original.addEventListener('click', (e) => {
- if (e.target.tagName === 'MARK') {
- const originalElement = e.target.parentElement;
- originalElement.click();
- }
- });
-
- const context = document.querySelector('.context');
- if (context) contextMark = new Mark(context);
- }
-
- function watchContextBtn() {
- const btn = document.querySelector('.float-right a');
- if (!btn) return;
- btn.addEventListener('click', () => {
- const context = document.querySelector('.context');
- if (!context) return;
- removeContextTags();
-
- const original = document.querySelector('.editor-core .original').textContent;
- contextMark = new Mark(context);
- markContext(original);
- });
- }
-
- function mark(target, keywords, options) {
- if (!target) return;
- target.unmark();
-
- const caseSensitive = !document.querySelector('.sidebar .custom-checkbox')?.__vue__.$data.localChecked;
- const flags = caseSensitive ? 'g' : 'ig';
-
- const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
-
- const { contains, startsWith, endsWith, match } = keywords;
-
- const patterns = [
- ...contains.map(keyword => `(${escapeRegExp(keyword)})`),
- ...startsWith.map(keyword => `^(${escapeRegExp(keyword)})`),
- ...endsWith.map(keyword => `(${escapeRegExp(keyword)})$`),
- match
- ].filter(p => p.length).join('|');
-
- if (patterns) {
- const regex = new RegExp(patterns, flags);
- target.markRegExp(regex, {
- acrossElements: true,
- separateWordSearch: false,
- ...options
- });
- }
- }
-
- function markOrigin(keywords) {
- mark(originMark, keywords);
- }
-
- function markContext(originTxt) {
- mark(contextMark, originTxt, { className: 'mark'});
- }
-
- function markEditing(keywords) {
- let textarea = document.querySelector('textarea.translation');
- if (!textarea) return;
- const lastOverlay = document.getElementById('PZSoverlay');
- if (lastOverlay) return;
-
- const overlay = document.createElement('div');
- overlay.id = 'PZSoverlay';
- overlay.className = textarea.className;
- const textareaStyle = window.getComputedStyle(textarea);
- for (let i = 0; i < textareaStyle.length; i++) {
- const property = textareaStyle[i];
- overlay.style[property] = textareaStyle.getPropertyValue(property);
- }
- overlay.style.position = 'absolute';
- overlay.style.pointerEvents = 'none';
- overlay.style.setProperty('background', 'transparent', 'important');
- overlay.style['-webkit-text-fill-color'] = 'transparent';
- overlay.style.overflowY = 'hidden';
- overlay.style.resize = 'none';
-
- textarea.parentNode.appendChild(overlay);
-
- const updOverlay = () => {
- overlay.innerText = textarea.value;
- const fillColor = window.getComputedStyle(textarea).getPropertyValue('-webkit-text-fill-color');
- mark(new Mark(overlay), keywords, {
- each: (m) => {
- m.style['-webkit-text-fill-color'] = fillColor;
- m.style.opacity = .5;
- }
- });
- overlay.style.top = textarea.offsetTop + 'px';
- overlay.style.left = textarea.offsetLeft + 'px';
- overlay.style.width = textarea.offsetWidth + 'px';
- overlay.style.height = textarea.offsetHeight + 'px';
- };
-
- updOverlay();
-
- textarea.addEventListener('input', updOverlay);
-
- const observer = new MutationObserver(updOverlay);
- observer.observe(textarea, { attributes: true });
-
- window.addEventListener('resize', updOverlay);
-
- const cancelOverlay = () => {
- observer.disconnect();
- textarea.removeEventListener('input', updOverlay);
- window.removeEventListener('resize', updOverlay);
- overlay.remove();
- }
- return cancelOverlay;
- }
- // #endregion
-
- // #region 修复原文排版崩坏和<<>> fixOrigin(originElem)
- function fixOrigin(originElem) {
- originElem.innerHTML = originElem.innerHTML
- .replaceAll('<abbr title="noun.>" data-value=">">></abbr>', '>')
- .replaceAll(/<var>(<<[^<]*?>)<\/var>>/g, '<var>$1></var>')
- .replaceAll('<i class="lf" <abbr="" title="noun.>" data-value=">">>>', '')
- .replaceAll('<i class="<abbr" title="noun.“”" data-value="“”">"lf<abbr title="noun.“”" data-value="“”">"</abbr>>>', '')
- .replaceAll('<i class="<abbr" title="noun.“”" data-value="“”">"lf<abbr title="noun.“”" data-value="“”">"</abbr>>', '');
- }
- // #endregion
-
- // #region 还原上下文HTML源码 removeContextTags
- function removeContextTags() {
- const context = document.querySelector('.context');
- if (!context) return;
- context.innerHTML = context.innerHTML.replace(/<a.*?>(.*?)<\/a>/g, '$1').replace(/<(\/?)(li|b|i|u|h\d|span)>/g, '<$1$2>');
- }
- // #endregion
-
- // #region 修复 Ctrl 唤起菜单的<<>> fixTagSelect
- const insertTag = debounce(async (tag) => {
- const textarea = document.querySelector('textarea.translation');
- const startPos = textarea.selectionStart;
- const endPos = textarea.selectionEnd;
- const currentText = textarea.value;
-
- const before = currentText.slice(0, startPos);
- const after = currentText.slice(endPos);
-
- await mockInput(before.slice(0, Math.max(before.length - tag.length + 1, 0)) + tag + after.slice(0, -2)); // -2 去除\n
-
- textarea.selectionStart = startPos + 1;
- textarea.selectionEnd = endPos + 1;
- })
-
- let activeTag = null;
- let modifiedTags = [];
- const tagSelectController = new AbortController();
- const { tagSelectSignal } = tagSelectController;
-
- function tagSelectHandler(e) {
- if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
- activeTag &&= document.querySelector('.list-group-item.tag.active');
- }
- if (e.key === 'Enter') {
- if (!activeTag) return;
- if (!modifiedTags.includes(activeTag)) return;
- e.preventDefault();
- insertTag(activeTag?.textContent);
- activeTag = null;
- }
- }
-
- function updFixedTags() {
-
- const tags = document.querySelectorAll('.list-group-item.tag');
- activeTag = document.querySelector('.list-group-item.tag.active');
- modifiedTags = [];
-
- for (const tag of tags) {
- tag.innerHTML = tag.innerHTML.trim();
- if (tag.innerHTML.startsWith('<<') && !tag.innerHTML.endsWith('>>')) {
- tag.innerHTML += '>';
- modifiedTags.push(tag);
- }
- }
-
- document.addEventListener('keyup', tagSelectHandler, { tagSelectSignal });
-
- }
- // #endregion
-
- // #region 将填充原文移到右边,增加填充原文并保存 tweakButtons
- function tweakButtons() {
- const copyButton = document.querySelector('button.btn-secondary:has(.fa-clone)');
- const rightButtons = document.querySelector('.right .btn-group');
-
- if (rightButtons) {
- if (copyButton) {
- rightButtons.insertBefore(copyButton, rightButtons.firstChild);
- }
- if (document.querySelector('#PZpaste')) return;
- const pasteSave = document.createElement('button');
- rightButtons.appendChild(pasteSave);
- pasteSave.id = 'PZpaste';
- pasteSave.type = 'button';
- pasteSave.classList.add('btn', 'btn-secondary');
- pasteSave.title = '填充原文并保存';
- pasteSave.innerHTML = '<i aria-hidden="true" class="far fa-save"></i>';
- pasteSave.addEventListener('click', async () => {
- await mockInput(document.querySelector('.editor-core .original')?.textContent);
- document.querySelector('.right .btn-primary')?.click();
- });
- }
- }
- // #endregion
-
- // #region 缩略对比差异中过长无差异文本 extractDiff
- function extractDiff() {
- document.querySelectorAll('.diff-wrapper:not(.PZedited)').forEach(wrapper => {
- wrapper.childNodes.forEach(node => {
- if (node.nodeType !== Node.TEXT_NODE || node.length < 200) return;
-
- const text = node.cloneNode();
- const expand = document.createElement('span');
- expand.textContent = `${node.textContent.slice(0, 100)} ... ${node.textContent.slice(-100)}`;
- expand.style.cursor = 'pointer';
- expand.style.background = 'linear-gradient(to right, transparent, #aaf6, transparent)';
- expand.style.borderRadius = '2px';
-
- let time = 0;
- let isMoving = false;
-
- const start = () => {
- time = Date.now()
- isMoving = false;
- }
- const end = () => {
- if (isMoving || Date.now() - time > 500) return;
- expand.replaceWith(text);
- }
-
- expand.addEventListener('mousedown', start);
- expand.addEventListener('mouseup', end);
- expand.addEventListener('mouseleave', () => time = 0);
-
- expand.addEventListener('touchstart', start);
- expand.addEventListener('touchend', end);
- expand.addEventListener('touchcancel', () => time = 0);
- expand.addEventListener('touchmove', () => isMoving = true);
-
- node.replaceWith(expand);
- });
- wrapper.classList.add('PZedited');
- });
- }
- // #endregion
-
- // #region 点击对比差异绿色文字粘贴其中文本 initDiffClick
- function initDiffClick() {
- const addeds = document.querySelectorAll('.diff.added:not(.PZPedited)');
- for (const added of addeds) {
- added.classList.add('PZPedited');
- const text = added.textContent.replaceAll('\\n', '\n');
- added.style.cursor = 'pointer';
- added.addEventListener('click', () => {
- mockInsert(text);
- });
- }
- }
- // #endregion
-
- // #region 快速搜索原文 addCopySearchBtn
- async function addCopySearchBtn() {
- if (document.querySelector('#PZsch')) return;
- const originSch = document.querySelector('.btn-sm');
- if (!originSch) return;
- originSch.insertAdjacentHTML('beforebegin', '<button id="PZsch" type="button" class="btn btn-secondary btn-sm"><i aria-hidden="true" class="far fa-paste"></i></button>');
- const newSch = document.querySelector('#PZsch');
- newSch.addEventListener('click', async () => {
- const original = document.querySelector('.editor-core .original')?.textContent;
- let input = document.querySelector('.search-form input[type=search]');
- if (!input) {
- await (() => new Promise(resolve => resolve(originSch.click())))();
- input = document.querySelector('.search-form input[type=search]');
- }
- const submit = document.querySelector('.search-form button');
- await (() => new Promise(resolve => {
- input.value = original;
- input.dispatchEvent(new Event('input', {
- bubbles: true,
- cancelable: true,
- }));
- resolve();
- }))();
- submit.click();
- });
- }
- // #endregion
-
- // #region 进入下一条时关闭搜索结果 cancelSearchResult
- function cancelSearchResult() {
- const input = document.querySelector('.search-form input[type=search]');
- if (input) document.querySelectorAll('.btn-sm')[1]?.click();
- }
- // #endregion
-
- // #region 搜索结果对比差异 initSearchResultDiff(originTxt)
- function initSearchResultDiff(originTxt) {
- const strings = document.querySelectorAll('.original.mb-1 span:not(:has(+a)');
- if (!strings[0]) return;
-
- const { $diff } = document.querySelector('main').__vue__;
-
- for (const string of strings) {
- const strHTML = string.innerHTML;
- const showDiff = document.createElement('a');
- showDiff.title = '查看差异';
- showDiff.href = '#';
- showDiff.target = '_self';
- showDiff.classList.add('small');
- showDiff.innerHTML = '<i aria-hidden="true" class="far fa-right-left-large"></i>';
-
- string.after(' ', showDiff);
- showDiff.addEventListener('click', function(e) {
- e.preventDefault();
- string.innerHTML = this.isShown ? strHTML : $diff(string.textContent, originTxt);
- this.isShown = !this.isShown;
- });
- }
- }
- // #endregion
-
- // #region 高级搜索空格变+修复 fixAdvSch
- function fixAdvSch() {
- const inputs = document.querySelectorAll('#advancedSearch table input');
- if (!inputs[0]) return;
- const params = new URLSearchParams(location.search);
- const values = [...params.entries()].filter(([key, _]) => /(text|original|translation).?/.test(key)).map(([_, value]) => value.replaceAll(' ', '+'));
- for (const input of inputs) {
- if (values.includes(input.value)) {
- input.value = input.value.replaceAll('+', ' ');
- input.dispatchEvent(new Event('input', {
- bubbles: true,
- cancelable: true,
- }));
- }
- }
- }
- // #endregion
-
- // #region 自动保存全部相同词条 autoSaveAll
- const autoSave = localStorage.getItem('pzdiffautosave');
- function autoSaveAll() {
- const button = document.querySelector('.modal-dialog .btn-primary');
- if (autoSave && button.textContent === '保存全部') button.click();
- }
- // #endregion
-
- // #region 自动填充100%相似译文 autoFill100(suggests, originTxt)
- function autoFill100(suggests, originTxt) {
- if (!suggests[0]) return;
- const getSim = (suggest) => +suggest.querySelector('header span')?.textContent.split('\n')?.[2]?.trim().slice(0, -1);
- const getOriginal = (suggest) => normalizeString(suggest.querySelector('.original')?.firstChild.textContent);
- const getTranslation = (suggest) => suggest.querySelector('.translation').firstChild.textContent;
- for (const suggest of suggests) {
- const sim = getSim(suggest);
- const equalOrigin = [100, 101].includes(sim) || isEqualWithOneCharDifference(originTxt, getOriginal(suggest));
- if (equalOrigin) {
- mockInput(getTranslation(suggest));
- break;
- }
- }
- }
-
- function isEqualWithOneCharDifference(str1, str2) {
- if (str1 === str2) return true;
-
- if (Math.abs(str1.length - str2.length) > 1) return false;
-
- let differences = 0;
- const len1 = str1.length;
- const len2 = str2.length;
- let i = 0, j = 0;
-
- while (i < len1 && j < len2) {
- if (str1[i] !== str2[j]) {
- differences++;
- if (differences > 1) return false;
-
- if (len1 > len2) i++;
- else if (len2 > len1) j++;
- else {
- i++;
- j++;
- }
- } else {
- i++;
- j++;
- }
- }
-
- if (i < len1 || j < len2) differences++;
-
- return differences <= 1;
- }
- // #endregion
-
- // #region 重新排序历史词条 findTextWithin(suggests, originTxt) getDefaultSorted addReSortBtn
- function findTextWithin(suggests, originTxt) {
- if (!suggests[0]) return;
- originTxt = normalizeString(originTxt);
- const getOriginal = (suggest) => normalizeString(suggest.querySelector('.original')?.firstChild.textContent);
- for (const suggest of suggests) {
- if (getOriginal(suggest).includes(originTxt)) {
- suggest.parentNode.prepend(suggest);
- const header = suggest.querySelector('header');
- let headerSpan = header.querySelector('span');
- if (!headerSpan) {
- headerSpan = document.createElement('span');
- header.prepend(headerSpan);
- }
- if (headerSpan.textContent.includes('100%') || headerSpan.textContent.includes('101%')) break;
- headerSpan.textContent = '文本在中';
- break;
- }
- }
- }
-
- const reSortSuggests = (compareFn) => (suggests) => {
- if (!suggests[0]) return;
- const sorted = [...suggests].sort(compareFn);
- const parent = suggests[0].parentNode;
- const frag = document.createDocumentFragment();
- frag.append(...sorted);
- parent.innerHTML = '';
- parent.appendChild(frag);
- };
-
- const reSortSuggestsBySim = reSortSuggests((a, b) => {
- const getSim = (suggest) => {
- const simContainer = suggest.querySelector('header span');
- if (!simContainer) return 102; // 机器翻译参考
- const sim = +simContainer.textContent.split('\n')?.[2]?.trim().slice(0, -1);
- if (!sim) return 102; // 在文本中
- return sim;
- }
- return getSim(b) - getSim(a);
- });
-
- const reSortSuggestsByTime = reSortSuggests((a, b) => {
- const getTimestamp = (suggest) => {
- const time = suggest.querySelector('time')?.dateTime;
- if (!time) return Infinity;
- return +new Date(time);
- }
- return getTimestamp(b) - getTimestamp(a);
- });
-
- const reSortSuggestsByMem = (suggests) => {
- const sortType = localStorage.getItem('pzdiffsort') || 'sim';
- if (sortType === 'sim') {
- reSortSuggestsBySim(suggests);
- } else if (sortType === 'time') {
- reSortSuggestsByTime(suggests);
- }
- };
-
- let defaultSortedSuggests = [];
- const getDefaultSorted = (suggests) => {
- defaultSortedSuggests = suggests;
- };
- function recoverDefaultSort() {
- const parent = document.querySelector('.translation-memory .list');
- if (!parent) return;
- parent.append(...defaultSortedSuggests);
- }
-
- function addReSortBtn() {
- if (document.querySelector('.pzdiffsort')) return;
- const btn = document.createElement('a');
- btn.href = 'javascript:';
- btn.className = 'pzdiffsort';
- const icon = type => {
- const icon = document.createElement('i');
- icon.classList.add('far', `fa-${type}`);
- icon.ariaHidden = true;
- icon.style.cursor = 'pointer';
- return icon;
- }
-
- const simBtn = btn.cloneNode();
- simBtn.title = '按相似度排序';
- simBtn.append(icon('percentage'));
- simBtn.addEventListener('click', () => {
- const suggests = document.querySelectorAll('.string-item');
- reSortSuggestsByTime(suggests);
- localStorage.setItem('pzdiffsort', 'time');
- simBtn.replaceWith(timeBtn);
- });
-
- const timeBtn = btn.cloneNode();
- timeBtn.title = '按时间排序';
- timeBtn.append(icon('history'));
- timeBtn.addEventListener('click', () => {
- recoverDefaultSort();
- localStorage.setItem('pzdiffsort', 'default');
- timeBtn.replaceWith(defaultBtn);
- });
-
- const defaultBtn = btn.cloneNode();
- defaultBtn.title = '默认排序';
- defaultBtn.append(icon('sort-amount-down'));
- defaultBtn.addEventListener('click', () => {
- const suggests = document.querySelectorAll('.string-item');
- reSortSuggestsBySim(suggests);
- localStorage.setItem('pzdiffsort', 'sim');
- defaultBtn.replaceWith(simBtn);
- });
-
- const sortType = localStorage.getItem('pzdiffsort') || 'sim';
- const initBtn = {
- sim: simBtn,
- time: timeBtn,
- default: defaultBtn,
- };
- document.querySelector('.translation-memory .col-auto').after(initBtn[sortType]);
- }
- // #endregion
-
- // #region 初始化自动编辑 initAuto
- async function initAuto() {
- const avatars = await waitForElems('.nav-item.user-info');
- avatars.forEach(async (avatar) => {
- let harvesting = false;
- let translationPattern, skipPattern, userTime;
- avatar.insertAdjacentHTML('afterend', `<li class="nav-item"><a href="javascript:;" target="_self" class="PZpp nav-link" role="button">PP收割机</a></li>`);
- document.querySelectorAll('.PZpp').forEach(btn => btn.addEventListener('click', async (e) => {
- if (location.pathname.split('/')[3] !== 'strings') return;
- harvesting = !harvesting;
- if (harvesting) {
- e.target.style.color = '#dc3545';
- translationPattern = prompt(`请确认译文模板代码,字符串用'包裹;常用代码:
- original(原文)
- oldTrans(现有译文)
- suggest(第1条翻译建议)
- suggestSim(上者匹配度,最大100)`, 'original');
- if (translationPattern === null) return cancel();
- skipPattern = prompt(`请确认跳过条件代码,多个条件用逻辑运算符相连;常用代码:
- original.match(/^(\s|\n|<<.*?>>|<.*?>)*/gm)[0] !== original(跳过并非只包含标签)
- oldTrans(现有译文)
- suggest(第1条翻译建议)
- suggestSim(上者匹配度,最大100)
- context(上下文内容)`, '');
- if (skipPattern === null) return cancel();
- if (skipPattern === '') skipPattern = 'false';
- userTime = prompt('请确认生成译文后等待时间(单位:ms)', '500');
- if (userTime === null) return cancel();
- function cancel() {
- harvesting = false;
- e.target.style.color = '';
- }
- } else {
- e.target.style.color = '';
- return;
- }
-
- const hideAlert = document.createElement('style');
- document.head.appendChild(hideAlert);
- hideAlert.innerHTML = '.alert-success.alert-global{display:none}';
-
- const checkboxs = [...document.querySelectorAll('.right .custom-checkbox')].slice(0, 2);
- const checkboxValues = checkboxs.map(e => e.__vue__.$data.localChecked);
- checkboxs.forEach(e => e.__vue__.$data.localChecked = true);
-
- const print = {
- waiting: () => console.log('%cWAITING...', 'background: #007BFF; color: #282828; font-weight: 900; padding: 0 5px; font-size: 12px; border-radius: 2px'),
- skip: () => console.log('%cSKIP', 'background: #FFC107; color: #282828; font-weight: 900; padding: 0 5px; font-size: 12px; border-radius: 2px'),
- click: () => console.log('%cCLICK', 'background: #20C997; color: #282828; font-weight: 900; padding: 0 5px; font-size: 12px; border-radius: 2px'),
- end: () => console.log('%cTHE END', 'background: #DE065B; color: white; font-weight: 900; padding: 0 5px; font-size: 12px; border-radius: 2px'),
- }
-
- const INTERVAL = 100;
- let interval = INTERVAL;
- let lastInfo = null;
-
- function prepareWait() {
- print.waiting();
- interval = INTERVAL;
- lastInfo = null;
- return true;
- }
-
- function skipOrFin(originElem, nextButton) {
- if (nextString(nextButton)) return false;
- print.skip();
- interval = 50;
- lastInfo = [
- originElem,
- location.search.match(/(?<=(\?|&)page=)\d+/g)?.[0] ?? 1
- ];
- return true;
- }
-
- function nextString(button) {
- if (button.disabled) {
- print.end();
- harvesting = false;
- e.target.style.color = '';
- return true;
- }
- button.click();
- return false;
- }
-
- try {
- while (true) {
- await sleep(interval);
-
- if (lastInfo) {
- const [ lastOrigin, lastPage ] = lastInfo;
- // 已点击翻页,但原文未发生改变
- const skipWaiting = (location.search.match(/(?<=(\?|&)page=)\d+/g)?.[0] ?? 1) !== lastPage
- && document.querySelector('.editor-core .original') === lastOrigin;
- if (skipWaiting && prepareWait()) continue;
- }
-
- const originElem = document.querySelector('.editor-core .original');
- if (!originElem && prepareWait()) continue;
- const nextButton = document.querySelectorAll('.navigation .btn-secondary')[1];
- if (!nextButton && prepareWait()) continue;
-
- const original = originElem.textContent;
- const oldTrans = document.querySelector('textarea.translation').value;
- let suggest = null, suggestSim = 0;
- if (translationPattern.includes('suggest') || skipPattern.includes('suggest')) {
- const suggestEle = (await waitForElems('.translation-memory .string-item .translation, .empty-sign'))[0];
- if (suggestEle.classList.contains('empty-sign')) {
- if (skipOrFin(originElem, nextButton)) continue; else break;
- }
- suggest = suggestEle.textContent;
- suggestSim = +suggestEle.querySelector('header span')?.textContent.split('\n')?.[2]?.trim().slice(0, -1);
- if ((translationPattern.includes('suggestSim') || skipPattern.includes('suggestSim')) && isNaN(suggestSim)) {
- if (skipOrFin(originElem, nextButton)) continue; else break;
- }
- }
- const context = document.querySelector('.context')?.textContent;
-
- if (eval(skipPattern)) {
- if (skipOrFin(originElem, nextButton)) continue; else break;
- }
-
- const translation = eval(translationPattern);
- if (!translation && prepareWait()) continue;
-
- await mockInput(translation);
- await sleep(userTime);
- if (!harvesting) break; // 放在等待后,以便在等待间隔点击取消
-
- const translateButton = document.querySelector('.right .btn-primary');
- if (!translateButton) {
- if (skipOrFin(originElem, nextButton)) continue; else break;
- } else {
- translateButton.click();
- print.click();
- interval = INTERVAL;
- lastInfo = null;
- continue;
- }
- }
- } catch (e) {
- console.error(e);
- alert('出错了!');
- } finally {
- hideAlert.remove();
- checkboxs.forEach((e, i) => { e.__vue__.$data.localChecked = checkboxValues[i] });
- }
-
- }));
- });
- }
- // #endregion
-
- // #endregion
-
- // #region 函数调用逻辑
-
- addHotkeys();
- initAuto();
-
- let stringPageTurned = true;
- async function actByPath(path) {
- if (path.split('/').pop() === 'strings') {
-
- let original;
- let lastOriginHTML = '';
- let toObserve = document.body;
-
- const observer = new MutationObserver((mutations) => {
-
- fixAdvSch();
- if (shouldSkip()) return;
-
- original = document.querySelector('.editor-core .original');
- if (!original) return;
- const originUpded = original.innerHTML !== lastOriginHTML;
- lastOriginHTML = original.innerHTML;
-
- observer.disconnect();
- initDiffClick();
- extractDiff();
-
- const markAll = () => {
- fixOrigin(original);
- removeContextTags();
- markSearchParams();
- markContext(original.textContent);
- };
-
- if (stringPageTurned) {
- if (!originUpded) {
- connectObserve();
- return;
- }
- console.debug('framework loaded');
- initDropMark();
- initMarkJS();
- tweakButtons();
- addCopySearchBtn();
- addReSortBtn();
- watchContextBtn();
- markAll();
- stringPageTurned = false;
- connectObserve();
- return;
- }
-
- if (originUpded) {
- console.debug('origin upded');
- cancelSearchResult();
- markAll();
- }
-
- for (const mutation of mutations) {
- const { addedNodes, removedNodes } = mutation;
- // console.debug({ addedNodes, removedNodes });
- if (addedNodes.length === 1) {
- const node = addedNodes[0];
- if (node.matches?.('.list-group.tags')) {
- updFixedTags();
- continue;
- }
- if (node.matches?.('.string-item a.small')) {
- node.remove();
- continue;
- }
- if (node.matches?.('.modal-backdrop')) {
- autoSaveAll();
- continue;
- }
- } else if (removedNodes.length === 1) {
- const node = removedNodes[0];
- if (mutation.target.classList?.contains('translation-memory')
- && node.classList?.contains('loading')) {
- console.debug('suggests loaded');
- const suggests = document.querySelectorAll('.string-item');
- findTextWithin(suggests, original.textContent);
- getDefaultSorted(suggests);
- initSearchResultDiff(original.textContent);
- autoFill100(suggests, original.textContent);
- reSortSuggestsByMem(suggests);
- continue;
- }
- if (node.matches?.('.list-group.tags')) tagSelectController.abort();
- }
- }
-
- connectObserve();
- });
-
- connectObserve();
- function connectObserve() {
- observer.observe(toObserve, {
- childList: true,
- subtree: true,
- });
- }
-
- return observer;
-
- } else if (path.split('/').at(-2) === 'issues') {
- waitForElems('.text-content p img').then((imgs) => imgs.forEach(mediumZoom));
- } else if (path.split('/').pop() === 'history') {
-
- let observer = new MutationObserver(() => {
- observer.disconnect();
- extractDiff();
- connectObserve();
- });
-
- connectObserve();
- function connectObserve() {
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- });
- }
-
- return observer;
-
- }
- }
- let cancelAct = await actByPath(location.pathname);
- (await waitForElems('main'))[0].__vue__.$router.afterHooks.push(async (to, from) => {
- dropLastTextareaMark?.();
- if (JSON.stringify(to.query) !== JSON.stringify(from.query)) {
- console.debug('query changed');
- if (to.path.split('/').pop() === 'strings') {
- stringPageTurned = true;
- }
- }
- if (to.path === from.path) return;
- tagSelectController.abort();
- cancelAct?.disconnect();
- console.debug('path changed');
- cancelAct = await actByPath(to.path);
- });
-
- // #endregion
-
- // #region 通用工具函数
- function waitForElems(selector) {
- return new Promise(resolve => {
- if (document.querySelector(selector)) {
- return resolve(document.querySelectorAll(selector));
- }
-
- const observer = new MutationObserver(() => {
- if (document.querySelector(selector)) {
- resolve(document.querySelectorAll(selector));
- observer.disconnect();
- }
- });
-
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- });
- }
-
- function sleep(delay) {
- return new Promise((resolve) => setTimeout(resolve, delay));
- }
-
- function mockInput(text) {
- return new Promise((resolve) => {
- const textarea = document.querySelector('textarea.translation');
- if (!textarea) return;
- textarea.value = text;
- textarea.dispatchEvent(new Event('input', {
- bubbles: true,
- cancelable: true,
- }));
- return resolve(0);
- })
- }
-
- function mockInsert(text) {
- const textarea = document.querySelector('textarea.translation');
- if (!textarea) return;
- const startPos = textarea.selectionStart;
- const endPos = textarea.selectionEnd;
- const currentText = textarea.value;
-
- const before = currentText.slice(0, startPos);
- const after = currentText.slice(endPos);
-
- mockInput(before + text + after);
-
- textarea.selectionStart = startPos + text.length;
- textarea.selectionEnd = endPos + text.length;
- }
-
- function debounce(func, timeout = 300) {
- let called = false;
- return (...args) => {
- if (!called) {
- func.apply(this, args);
- called = true;
- setTimeout(() => called = false, timeout);
- }
- };
- }
-
- function normalizeString(str) {
- if (!str) return '';
- return str
- .replace(/[,.;'"-]/g, '')
- .replace(/\s+/g, '')
- .toLowerCase();
- }
- // #endregion
-
- })();