Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyforks.org/scripts/534396/1581907/ankienhance.js
;(() => {
PushHookAnkiStyle(`
.fetch-sentence-container { display:flex; }
.select-setting,.fetch-add { display:none;}
.fetch-opera { display: grid;}
.fetch-item { margin-top: 1rem; margin-left: 1rem; }
.fetch-box {
display: inline-block;
vertical-align: middle;
margin-left: 0.2rem;
}
.fetch-dd { margin-left: 0rem; }
.fetch-name {width: 7rem;}
.fetch-format {width: 20rem}
.fetch-bold-field {width: 17rem}
.fetch-num { width:3rem}
.moving {
background: transparent;
color: transparent;
border: 1px dashed #ccc;
}
`);
PushHookAnkiDidRender(addOrDelBtn);
['swal2-cancel swal2-styled',
'swal2-confirm swal2-styled',
'swal2-container swal2-center swal2-backdrop-hide'].forEach(className => {
PushExpandAnkiInputButton(className, '', saveFetchItems);
});
const fetchFields = ['fetch-name', 'fetch-field', 'fetch-to-field', 'fetch-selector', 'fetch-parent-selector',
'fetch-exclude-selector', 'fetch-join-selector', 'fetch-join-reverse', 'fetch-format', 'fetch-data-handle', 'fetch-repeat',
'fetch-bold-field', 'fetch-num', 'fetch-active', 'fetch-value-replacement', 'fetch-value-trim',
'fetch-value-replacement-ignore-case'];
const specialFields = ['fetch-selector', 'fetch-parent-selector', 'fetch-bold-field',
'fetch-exclude-selector', 'fetch-join-selector', 'fetch-format', 'fetch-value-replacement',];
function findParent(ele, selector) {
if (!ele || ele.tagName === 'HTML' || ele === document) {
return null
}
if (ele.matches(selector)) {
return ele
}
return findParent(ele.parentElement, selector)
}
function addOrDelBtn() {
const fetchMap = {};
const hadMap = {};
for (const el of document.querySelectorAll('.fetch-sentence-field')) {
let input = el.parentElement.parentElement.querySelector('.field-name,.sentence_field');
hadMap[input.value] = el;
}
for (const ele of document.querySelectorAll('.fetch-to-field')) {
const active = ele.parentElement.parentElement.parentElement.querySelector('.fetch-active').checked;
if (!fetchMap.hasOwnProperty(ele.value)) {
fetchMap[ele.value] = [[active, ele]]
} else {
fetchMap[ele.value].push([active, ele]);
}
}
Object.keys(fetchMap).map(k => {
let active = false;
let title = [];
fetchMap[k].map(v => {
if (v[0]) {
active = true;
}
title.push(v[1].parentElement.parentElement.parentElement.querySelector('.fetch-name').value);
});
[...document.querySelectorAll('input.field-name,input.sentence_field')].filter(input => input.value === k).map(input => {
if (hadMap.hasOwnProperty(k)) {
delete hadMap[k];
}
const btn = input.parentElement.querySelector(`.fetch-sentence-field`);
if (active && btn) {
btn.title = `将提取${title.join(',')}`;
return;
}
if (active && !btn) {
const btn = document.createElement('button');
btn.innerHTML = `⚓`;
btn.className = 'fetch-sentence-field';
btn.title = `将提取${title.join(',')}`;
let op = input.parentElement.querySelector('.field-operate');
if (op) {
op.appendChild(btn);
return;
}
input.parentElement.parentElement.querySelector('.field-operate').appendChild(btn);
return
}
btn && btn.remove();
});
for (const k in hadMap) {
hadMap[k].remove();
}
})
}
function fetchActive(ev) {
const box = ev.target;
const parent = box.parentElement.parentElement;
const inp = parent.querySelector('.fetch-field');
const targetField = parent.querySelector('.fetch-to-field');
addOrDelBtn();
if (!inp.value) {
Swal.showValidationMessage('提取的字段不能为空!');
inp.focus();
box.checked = false;
return
}
if (!targetField.value) {
Swal.showValidationMessage('提取到目标的字段不能为空!');
targetField.focus();
box.checked = false;
}
saveFetchItems();
}
let setting, boldAll = false;
function saveFetchItems() {
const data = [...setting.children].map(item => convertFetchParam(item));
data.length > 0 && GM_setValue('fetch-items', data);
}
function convertFetchParam(item) {
const param = {};
fetchFields.forEach(sel => {
if (['fetch-num', 'fetch-data-handle'].includes(sel)) {
param[sel] = parseInt(item.querySelector(`.${sel}`).value);
return
}
if (['fetch-repeat', 'fetch-active', 'fetch-join-reverse', 'fetch-value-trim', 'fetch-value-replacement-ignore-case'].includes(sel)) {
param[sel] = item.querySelector(`.${sel}`).checked;
return
}
if (specialFields.includes(sel)) {
param[sel] = item.querySelector(`.${sel}`).value;
param[sel] = decodeHtmlSpecial(param[sel]);
return;
}
param[sel] = item.querySelector(`.${sel}`).value.trim();
});
return param
}
function replace(value, param) {
if (!param['fetch-value-replacement'] || !value) {
return value;
}
const arr = decodeHtmlSpecial(param['fetch-value-replacement']).split('@@');
if (arr.length < 1) {
return value
}
return arr.reduce((value, express) => {
const exp = express.split('[=]');
if (exp.length < 1) {
return value;
}
const v = exp.length > 1 ? exp[1] : '';
try {
exp[0] = exp[0].replaceAll(`\\\\`, `\\`);
value = value.replaceAll(new RegExp(exp[0], param['fetch-value-replacement-ignore-case'] ? 'gi' : 'g'), v);
} catch (e) {
console.log(e);
value = value.split(exp[0]).join(v);
}
return value
}, value);
}
function setValue(target, valElement, param, join = null, boldFieldValue = '') {
let joinEle, joinRep;
if (join) {
joinEle = join.joinEle
joinRep = join.joinRep
}
if (!valElement && !joinEle) {
return
}
const joinParam = {
'fetch-value-replacement-ignore-case': true,
'fetch-value-replacement': joinRep
}
const format = param['fetch-format'], way = param['fetch-data-handle'], isRepeat = !param['fetch-repeat'];
let sw = false;
if (!valElement && joinEle) {
valElement = joinEle;
joinEle = null;
sw = true;
}
const setInput = (input, value, isAppend, isRepeat) => {
value = replace(value.innerText, param);
if (param['fetch-value-trim']) {
value = value.trim();
}
if (format) {
let join = '';
if (joinEle) {
join = joinEle.innerText.trim();
join = replace(join, joinParam);
}
value = sw ? format.replaceAll('{$join}', value).replaceAll('{$value}', '') :
format.replaceAll('{$value}', value).replaceAll('{$join}', join);
}
if (param['fetch-value-trim'] && !isRepeat && input.value.includes(value.trim())) {
return;
}
if (!isRepeat && input.value.includes(value)) {
return;
}
input.value = isAppend ? (input.value + value) : value;
}
const setDiv = (div, value, isAppend, isRepeat) => {
let v = value.innerText.trim();
if (!isRepeat && div.innerText.includes(v)) {
return
}
const bold = (sentence) => {
if (!boldFieldValue) {
return sentence;
}
let words, format;
if (Array.isArray(boldFieldValue)) {
words = boldFieldValue[0].split(' ');
format = boldFieldValue[1];
} else {
words = boldFieldValue.split(' ');
}
words = words.sort((a, b) => a.length <= b.length ? 1 : -1);
const formats = format ? format.split('{$bold}').join('\$&') : '<b>\$&</b>';
for (const word of words) {
const l = sentence.length;
sentence = sentence.replaceAllX(word, formats, 'gi');
if (l !== sentence.length && !boldAll) {
break
}
}
return sentence;
}
const set = (di) => {
di.innerHTML = bold(di.innerHTML);
if (di.children.length > 0) {
isAppend ? [...di.children].forEach(v => div.appendChild(v)) : (div.innerHTML = di.innerHTML);
return;
}
div.innerHTML = isAppend ? div.insertAdjacentHTML('afterend', di.innerHTML) : di.innerHTML;
}
if (format) {
let join = '';
if (joinEle) {
join = joinEle.innerText.trim();
join = replace(join, joinParam);
}
const v = sw ?
format.replaceAll('{$join}', value.innerText.trim()).replaceAll('{$value}', '') :
format.replaceAll('{$value}', value.innerText.trim()).replaceAll('{$join}', join);
const di = document.createElement('div');
di.innerHTML = replace(v, param);
if (!isRepeat && div.innerText.includes(di.innerText)) {
return
}
set(di);
return;
}
//joinEle.innerHTML = bold(joinEle.innerHTML);
value.innerHTML = bold(replace(value.innerHTML, param));
if (joinEle) {
joinEle.innerHTML = replace(joinEle.innerHTML, joinParam);
isAppend ? (div.appendChild(joinEle) , div.appendChild(value)) : (div.innerHTML = joinEle.outerHTML + value.outerHTML);
return;
}
isAppend ? div.appendChild(value) : (div.innerHTML = value.outerHTML);
}
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
const value = target.value;
if (way === 3 && value) {
return;
}
setInput(target, valElement, way === 1, isRepeat);
return;
}
setDiv(target, valElement, way === 1, isRepeat)
}
function fetchLimit(eles, num) {
if (num <= 0) {
return eles;
}
return [...eles].slice(0, num)
}
function findELeBySelector(t, sel, el) {
if (!el) {
return null;
}
if (t === 's') {
return el.querySelector(sel);
}
let ele = el;
do {
if (!ele) {
return null;
}
switch (t) {
case 'p':
ele = ele.parentElement;
break;
case 'ns':
ele = ele.nextElementSibling;
break
case 'ps':
ele = ele.previousSibling;
break;
default:
return null
}
if (ele && ele.matches(sel)) {
return ele;
}
} while (ele)
return ele;
}
function findEleByNum(t, num, el) {
if (!el) {
return null;
}
if (num < 1) {
return null
}
let ele = el;
do {
if (!ele) {
return null;
}
switch (t) {
case 'p':
ele = ele.parentElement;
break;
case 'ns':
ele = ele.nextElementSibling;
break
case 'ps':
ele = ele.previousSibling;
break;
default:
return null
}
} while (--num)
return ele;
}
function parseSelector(expression, joinEle) {
let ele = joinEle;
for (const exp of expression.split('%')) {
const arr = exp.split('@').map(v => v.trim());
if (arr.length < 1 || !['s', 'ps', 'p', 'ns'].includes(arr[0])) {
continue
}
ele = isNaN(parseInt(arr[1])) ? findELeBySelector(arr[0], arr.slice(1).join(''), ele) : findEleByNum(arr[0], arr[1], ele)
}
return ele === joinEle ? null : ele;
}
function removeEle(ele, selector) {
if (!selector) {
return
}
ele.querySelectorAll(selector).forEach(el => el.remove());
}
function inputTrim(target, param) {
if (target.tagName !== 'INPUT' && target.tagName !== 'TEXTAREA') {
return;
}
if (!param['fetch-value-trim']) {
return
}
target.value = target.value.trim();
}
function fetchData(item, from, target, param, boldFieldValue) {
from = from.parentElement;
let joinRep, joinSel;
if (param['fetch-join-selector']) {
const joinSelX = param['fetch-join-selector'].split('++');
joinSel = joinSelX[0].split('`');
if (joinSelX.length > 1) {
joinRep = joinSelX[1];
}
}
if (!param['fetch-parent-selector']) {
for (const el of fetchLimit(from.querySelectorAll(param['fetch-selector']), param['fetch-num'])) {
const ele = el.cloneNode(true);
removeEle(ele, param['fetch-exclude-selector']);
setValue(target, ele, param, null, boldFieldValue);
}
return;
}
[...from.querySelectorAll(param['fetch-parent-selector'])].forEach(parent => {
if (param['fetch-join-selector']) {
if (param['fetch-join-reverse']) {
for (let value of fetchLimit(parent.querySelectorAll(param['fetch-selector']), param['fetch-num'])) {
let joinEle = parseSelector(joinSel[0], value);
if (joinEle) {
joinEle = joinEle.cloneNode(true);
if (joinSel.length > 1) {
removeEle(joinEle, joinSel[1]);
}
}
const ele = value.cloneNode(true);
removeEle(ele, param['fetch-exclude-selector']);
setValue(target, ele, param, {joinEle, joinRep}, boldFieldValue);
}
return;
}
for (let joinEle of fetchLimit(parent.querySelectorAll(joinSel[0]), param['fetch-num'])) {
let ele = parseSelector(param['fetch-selector'], joinEle);
if (ele) {
ele = ele.cloneNode(true);
removeEle(ele, param['fetch-exclude-selector'])
}
joinEle = joinEle.cloneNode(true);
if (joinSel.length > 1) {
removeEle(joinEle, joinSel[1]);
}
setValue(target, ele, param, {joinEle, joinRep}, boldFieldValue);
}
return
}
for (const el of fetchLimit(parent.querySelectorAll(param['fetch-selector']), param['fetch-num'])) {
const ele = el.cloneNode(true);
removeEle(ele, param['fetch-exclude-selector']);
setValue(target, ele, param, null, boldFieldValue);
}
})
}
PushExpandAnkiInputButton('fetch-delete', '', (e) => {
e.target.parentElement.parentElement.remove();
});
PushExpandAnkiInputButton('fetch-sentence-field', '', (e) => {
const field = e.target.parentElement.parentElement.querySelector('.sentence_field,.field-name');
for (const toFieldInput of document.querySelectorAll('.fetch-to-field')) {
const item = findParent(toFieldInput, '.fetch-item');
const selector = item.querySelector('.fetch-selector');
const from = item.querySelector('.fetch-field');
if (toFieldInput.value !== field.value || !from.value || !selector.value || !item.querySelector('.fetch-active').checked) {
continue
}
let fromEle = [...document.querySelectorAll('.field-name,.sentence_field')].filter(el => el.value === from.value);
fromEle = fromEle ? findParent(fromEle[0], '.form-item,.sentence_setting').querySelector('.spell-content,.field-value') : null;
if (!fromEle) {
continue;
}
const target = e.target.parentElement.parentElement.querySelector('.spell-content,.field-value');
const param = convertFetchParam(item);
const bold = parseBoldFormat(param);
fetchData(item, fromEle, target, param, bold);
inputTrim(target, param);
}
}, '', (ev) => {
ev.preventDefault();
boldAll = true
ev.target.click();
boldAll = false;
});
function parseBoldFormat(param) {
if (!param['fetch-bold-field']) {
return ''
}
let boldFieldValue = '';
const fields = param['fetch-bold-field'].split('@@');
for (const input of document.querySelectorAll('input.field-name')) {
if (input => input.value === fields[0]) {
const ip = input.nextElementSibling;
if (ip && ip.matches('input.field-value')) {
boldFieldValue = ip.value;
if (fields.length > 1) {
const f = fields[1].split('%%');
if (f.length < 1) {
break;
}
if (f.length === 1 && f[0].includes('{$bold}')) {
boldFieldValue = [boldFieldValue, f[0]];
break;
}
boldFieldValue = [boldFieldValue.split(f[0].replaceAll('`', '')).join(' '), f[1]];
}
break
}
}
}
return boldFieldValue;
}
const mapTitle = {
'fetch-name': '名称,只作为标识',
'fetch-field': '提取的字段',
'fetch-to-field': '提取到目标字段',
'fetch-selector': '提取值的选择器',
'fetch-parent-selector': '父选择器',
'fetch-exclude-selector': '提取值需要排除的选择器',
'fetch-join-selector': '组合选择器',
'fetch-join-reverse': '反转组合选择器',
'fetch-format': '提取的格式,为空为原值,{$join}为组合选择器的值, {$value}为提取的值',
'fetch-data-handle': '提取到后的操作',
'fetch-repeat': '是否去重',
'fetch-bold-field': htmlSpecial('加粗的字段,如有多个值,可以指定分隔符如 正面@@`,`%%<b>{$bold}</b> %%后为格式'),
'fetch-num': '提取的数量,默认0为全部',
'fetch-value-replacement': '提取的值替换,[=]前后分为表示要替换的值和替换值,多个用@@分隔,支持正则 如 去掉·和将。替换为. 为 ·[=]@@。[=].',
'fetch-value-trim': '提取的值去除首尾空白符如空格等',
'fetch-value-replacement-ignore-case': '是否忽略大小写',
'fetch-active': '是否启用这个提取项',
'fetch-delete': '删除此项',
};
const de = {};
Object.keys(mapTitle).forEach(k => {
de[k] = '';
de['fetch-num'] = 0;
de['fetch-repeat'] = true;
de['fetch-active'] = false;
de['fetch-data-handle'] = '1';
de['fetch-value-trim'] = false;
de['fetch-value-replacement-ignore-case'] = false;
});
function buildFetchItem(data = null) {
specialFields.forEach(v => data[v] = htmlSpecial(data[v]));
const div = document.createElement('div');
div.innerHTML = `
<div class="fetch-item" draggable="true">
<span class="fetch-box">
<input class="fetch-name" value="${data['fetch-name']}" title="${mapTitle['fetch-name']}" placeholder="${mapTitle['fetch-name']}">
</span>
<span class="fetch-box">
<dd class="fetch-dd">
<input name="fetch-field" value="${data['fetch-field']}" class="fetch-field" title="${mapTitle['fetch-field']}" placeholder="${mapTitle['fetch-field']}">
<input name="fetch-to-field" value="${data['fetch-to-field']}" class="fetch-to-field" title="${mapTitle['fetch-to-field']}" placeholder="${mapTitle['fetch-to-field']}">
</dd>
<dd class="fetch-dd">
<input name="fetch-selector" value="${data['fetch-selector']}" class="fetch-selector" title="${mapTitle['fetch-selector']}" placeholder="${mapTitle['fetch-selector']}">
<input name="fetch-parent-selector" value="${data['fetch-parent-selector']}" class="fetch-parent-selector" title="${mapTitle['fetch-parent-selector']}" placeholder="${mapTitle['fetch-parent-selector']}">
</dd>
<dd class="fetch-dd">
<input name="fetch-exclude-selector" value="${data['fetch-exclude-selector']}" class="fetch-exclude-selector" title="${mapTitle['fetch-exclude-selector']}" placeholder="${mapTitle['fetch-exclude-selector']}">
<input name="fetch-join-selector" value="${data['fetch-join-selector']}" class="fetch-join-selector" title="${mapTitle['fetch-join-selector']}" placeholder="${mapTitle['fetch-join-selector']}">
<input type="checkbox" ${data['fetch-join-reverse'] ? 'checked' : ''} name="fetch-join-reverse" class="fetch-join-reverse" title="${mapTitle['fetch-join-reverse']}" placeholder="${mapTitle['fetch-join-reverse']}">
</dd>
<dd class="fetch-dd">
<input name="fetch-format" value="${data['fetch-format']}" class="fetch-format" title="${mapTitle['fetch-format']}" placeholder="${mapTitle['fetch-format']}">
<select name="fetch-data-handle" class="fetch-data-handle" title="${mapTitle['fetch-data-handle']}">
${buildOption([['1', '追加'], ['2', '覆盖'], ['3', '不处理']], data['fetch-data-handle'], 0, 1)}
</select>
</dd>
<dd class="fetch-dd">
<input name="fetch-bold-field" value="${data['fetch-bold-field']}" class="fetch-bold-field" title="${mapTitle['fetch-bold-field']}" placeholder="${mapTitle['fetch-bold-field']}">
<input name="fetch-num" step="1" min="0" value="${data['fetch-num']}" class="fetch-num" type="number" title="${mapTitle['fetch-num']}" placeholder="${mapTitle['fetch-num']}">
<input type="checkbox" ${data['fetch-repeat'] ? 'checked' : ''} name="fetch-repeat" class="fetch-repeat" title="${mapTitle['fetch-repeat']}" placeholder="${mapTitle['fetch-repeat']}">
</dd>
<dd class="fetch-dd">
<input name="fetch-value-replacement" value="${data['fetch-value-replacement']}" class="fetch-value-replacement" title="${mapTitle['fetch-value-replacement']}" placeholder="${mapTitle['fetch-value-replacement']}">
<input type="checkbox" ${data['fetch-value-trim'] ? 'checked' : ''} name="fetch-value-trim" class="fetch-value-trim" title="${mapTitle['fetch-value-trim']}" placeholder="${mapTitle['fetch-value-trim']}">
<input type="checkbox" ${data['fetch-value-replacement-ignore-case'] ? 'checked' : ''} name="fetch-value-replacement-ignore-case" class="fetch-value-replacement-ignore-case" title="${mapTitle['fetch-value-replacement-ignore-case']}" placeholder="${mapTitle['fetch-value-replacement-ignore-case']}">
</dd>
</span>
<span class="fetch-box">
<input type="checkbox" ${data['fetch-active'] ? 'checked' : ''} name="fetch-active" class="swal2-checkbox fetch-active" title="${mapTitle['fetch-active']}" placeholder="${mapTitle['fetch-active']}">
<button class="fetch-delete" title="${mapTitle['fetch-delete']}">➖</button>
</span>
</div>
`;
div.querySelector('.fetch-active').addEventListener('change', fetchActive);
return div.children[0];
}
PushExpandAnkiInputButton('fetch-add', '', (e) => {
e.target.parentElement.nextElementSibling.appendChild(buildFetchItem({...de}));
});
PushHookAnkiHtml((ankiContainer) => {
const div = document.createElement('div');
div.className = 'form-item fetch-sentence-container';
div.innerHTML = `
<div class="fetch-opera">
<label for="fetch" class="form-label">提取词典的句子</label>
<input type="checkbox" class="swal2-checkbox" name="fetch" id="fetch" title="显示提取词典设置">
<button class="fetch-all" title="一键全部提取">🕸️</button>
<button class="fetch-add" title="添加提取项">➕</button>
</div>
<div class="select-setting"></div>
`;
let fetchItems = GM_getValue('fetch-items', [{...de}]);
fetchItems = fetchItems.length > 0 ? fetchItems : [{...de}];
setting = div.querySelector('.select-setting');
let currentItem;
const startDrag = (e) => {
e.dataTransfer.effectAllowed = 'move';
currentItem = e.target;
currentItem.classList.add('moving');
};
const enterDrag = (e) => {
e.preventDefault();
if (e.target === currentItem || setting.children.length <= 1 || e.target === setting || ![...setting.children].includes(e.target)) {
return
}
let liArray = Array.from(setting.childNodes);
let currentIndex = liArray.indexOf(currentItem);
let targetindex = liArray.indexOf(e.target);
if (currentIndex < targetindex) {
setting.insertBefore(currentItem, e.target.nextElementSibling);
} else {
setting.insertBefore(currentItem, e.target);
}
};
const endDrag = (e) => {
currentItem.classList.remove('moving');
saveFetchItems();
};
const overDrag = (e) => {
e.preventDefault();
};
const turnDrag = (onoff) => {
setting.querySelectorAll('.fetch-item').forEach(item => item.draggable = onoff);
};
setting.addEventListener('mousedown', (ev) => {
if (ev.target.tagName === 'INPUT') {
turnDrag(false);
}
});
setting.addEventListener('mouseup', (ev) => {
if (ev.target.tagName === 'INPUT') {
turnDrag(true);
}
});
setting.addEventListener('dragstart', startDrag);
setting.addEventListener('dragenter', enterDrag);
setting.addEventListener('dragend', endDrag);
setting.addEventListener('dragover', overDrag);
fetchItems.forEach(item => setting.appendChild(buildFetchItem(item)));
[...div.querySelectorAll('.fetch-active')].map(f => f.addEventListener('change', fetchActive));
div.querySelector('.fetch-all').addEventListener('click', () => {
[...document.querySelectorAll('.fetch-sentence-field')].forEach(button => button.click());
});
div.querySelector('#fetch').addEventListener('change', function (e) {
const add = findParent(this, '.fetch-opera').querySelector('.fetch-add');
if (this.checked) {
add.style.display = 'block';
this.parentElement.nextElementSibling.style.display = 'block';
return
}
add.style.display = 'none';
this.parentElement.nextElementSibling.style.display = 'none';
})
const n = ankiContainer.querySelector('#auto-sentence');
n.parentElement.parentElement.insertBefore(div, n.parentElement.nextElementSibling);
});
})()