anki extract sentence enhance
This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/534396/1805461/ankienhance.js
;const {ankiFetchClickFn, ankiFetchData, getAnkiFetchParams, arrayDiff, superFetchHook, setEleDrag} = (() => {
PushHookAnkiStyle(GM_getResourceText('extract-sentence'));
PushHookAnkiDidRender(freshBtns);
function changeAddDelBtn(ev, fn) {
fn && fn(ev);
freshBtns();
}
PushHookAnkiChange('#model', changeAddDelBtn);
PushHookAnkiChange('.field-name', changeAddDelBtn);
PushExpandAnkiInputButton('hammer', '', changeAddDelBtn);
PushExpandAnkiInputButton('fetch-all', '', () => {
document.querySelectorAll('.fetch-sentence-field').forEach(button => button.click());
});
PushExpandAnkiInputButton('fetch-delete', '', (e) => {
findParent(e.target, '.fetch-item').remove();
});
PushExpandAnkiInputButton('sequentially-fetch', '', ev => GM_setValue('sequentially-fetch', ev.target.checked));
PushExpandAnkiInputButton('fetch-add', '', (e) => {
findParent(e.target, '.fetch-item').insertAdjacentElement('afterend', actionHelper.buildFetchItem({}));
});
PushExpandAnkiInputButton('fetch-export', '', () => eventFn.export(),
'', evt => {
evt.preventDefault();
eventFn.export(
[...setting.querySelectorAll('.fetch-item:not(.fetch-hidden)')].map(formProcessor.convertFetchParam)
)
});
const importFn = ev => ev.target.parentElement.querySelector('.fetch-file').click();
PushExpandAnkiInputButton('fetch-import', '', importFn, '', ev => {
ev.preventDefault();
ev.target.dataset['new'] = 'true';
importFn(ev);
});
/**
*
* @param a arr
* @param b arr
* @param fn
* @returns {*} a's item not in b
*/
function diff(a, b, fn) {
if (b.length <= 2 || (a.length <= b.length)) {
return a.filter(aa => {
let flag = false
for (const bb of b) {
if (fn(aa, bb)) {
flag = true
break
}
}
return !flag;
})
}
// maybe have some optimization
const hadIndex = new Set();
b.forEach(bb => {
for (const i in a) {
const aa = a[i];
if (fn(aa, bb)) {
hadIndex.add(parseInt(i));
break
}
}
});
return a.filter((aa, i) => !hadIndex.has(i));
}
function objectsEqual(o1, o2) {
return Object.keys(o1).length === Object.keys(o2).length
&& Object.keys(o1).every(p => o1[p] === o2[p])
}
const eventFn = {
contextMenuAction(ev) {
const button = ev.target, input = button.parentElement.parentElement.querySelector('.field-name');
const targetField = input.value.trim(), isText = actionHelper.isTextNode(input.nextElementSibling);
const targetEle = button.parentElement.parentElement.querySelector('.spell-content,.field-value');
const arr = getAnkiFetchParams(targetField, false).filterAndMapX(actionHelper.filterButton(isText));
if (!arr || arr.length < 1) {
return
}
ev.preventDefault();
const sel = document.createElement('select');
const map = {};
const opts = arr.map(v => {
map[v['fetch-name']] = v;
return v['fetch-name'];
});
opts.unshift(['', '选择一个操作']);
sel.innerHTML = buildOption(opts, '', 0, 1);
const fn = (ev) => {
if (sel.value) {
actionHelper.executeAction(map[sel.value], actionHelper.getFromEle(map[sel.value], targetEle), targetEle);
}
const evt = ev.type === 'click' ? 'change' : 'blur';
sel.removeEventListener(evt, fn);
sel.parentElement.replaceChild(button, sel)
};
sel.addEventListener('blur', fn);
sel.addEventListener('change', fn);
button.replaceWith(sel);
},
dragEle: {},
changedEleSelector: '.swal2-popup, #shadowFields > ol, ol .form-item:has( .sentence_setting) :where(.spell), .form-item .spell-content',
export(params = getAnkiFetchParams('', false)) {
const data = JSON.stringify(params);
const current = new Date();
// wtf time format
download(`fetch-rule.${current.getFullYear()}-${current.getMonth() + 1}-${current.getDate()}.${current.getHours()}.${current.getMinutes()}.${current.getSeconds()}.total.${params.length}.rows.json`, data);
},
showProcessor(ev) {
const selector = '.fetch-import,.fetch-export';
if (!ev.target.checked) {
saveFetchItems();
freshBtns();
setting.children[0].classList.add('fetch-hidden');
getFetchItemEles().map(e => e.remove());
ev.target.parentElement.querySelectorAll(selector).forEach(btn => btn.classList.add('fetch-hidden'))
return
}
let fetchItems = GM_getValue('fetch-items', [{}]);
fetchItems.forEach(item => setting.appendChild(actionHelper.buildFetchItem(item)));
if (GM_getValue('fetch-display-type', 1) === 2) {
const arr = Object.groupBy(fetchItems, item => buttonField(item)) ?? [];
const options = [];
const nb = ' '.repeat(6);
Object.keys(arr).forEach(k => {
options.push([k, k, {'data-names': arr[k].map(m => m['fetch-name']).join(',')}]);
arr[k].forEach(m => options.push([m['fetch-name'], nb + m['fetch-name']]));
});
setting.children[0].innerHTML = buildOption(options, options[1][0], 0, 1, 2);
setting.children[0].classList.remove('fetch-hidden');
setting.children.length > 2 && [...setting.children].slice(2).forEach(e => e.classList.add('fetch-hidden'));
}
const fns = [...setting.querySelectorAll('.fetch-replacement-items')].map(el => setEleDrag(el, 'li'));
eventFn.dragEle['replaceItem'] = onOff => fns.forEach(fn => fn(onOff));
eventFn.dragEle.replaceItem(true);
eventFn.dragEle['fetch-item'] = setEleDrag(setting, '.fetch-item');
eventFn.dragEle['fetch-item'](true);
eventFn.dragEle['super-fetch-item'] = setEleDrag(setting, '.super-fetch-item');
eventFn.dragEle['super-fetch-item'](true);
ev.target.parentElement.querySelectorAll(selector).forEach(btn => btn.classList.remove('fetch-hidden'))
},
async importFn(ev) {
const file = ev.target.files[0];
const btn = ev.target.parentElement.querySelector('.fetch-import');
const refresh = btn.dataset?.['new'];
delete btn.dataset?.['new'];
if (!file) {
Swal.showValidationMessage(mapTitle['no file']);
return
}
const items = await file.text().then(JSON.parse);
if (!items || items.length < 1 || !items[0]?.['fetch-name']) {
Swal.showValidationMessage(`can't parse rule file`);
return
}
let newRule = [];
if (refresh) {
newRule = items;
getFetchItemEles().forEach(el => el.remove());
} else {
const hadRule = getAnkiFetchParams('', false);
newRule = diff(items, hadRule, (a, b) => JSON.stringify(a) === JSON.stringify(b));
}
if (newRule.length < 1) {
Swal.showValidationMessage(mapTitle['redundantly import!']);
return
}
const names = [];
newRule.forEach(item => {
const t = actionHelper.buildFetchItem(item);
t.classList.add('fetch-item-specific');
setting.appendChild(t);
names.push([item['fetch-name'], item['fetch-name']]);
});
Swal.showValidationMessage(`已导入${newRule.length}条记录!`);
if (GM_getValue('fetch-display-type', 1) === 2) {
const options = buildOption(names, '', 0, 1);
setting.children[0].insertAdjacentHTML('beforeend', options);
}
},
addTplFn: {
tpl: name => templateHelper.buildTemplateHTML(name, {}),
tplFn: fn => eventFn.addTplFn?.[fn]({}),
replacement(data = {}) {
return actions.handlers.replacement.getReplacementItem(data);
},
fetch(data) {
return actions.handlers.fetch.getFetchItem(data);
}
},
add(ev) {
const el = ev.target.dataset?.target ? findParent(ev.target, ev.target.dataset.target) : ev.target.parentElement;
for (const name of ['tplFn', 'tpl']) {
if (ev.target.dataset?.[name]) {
const t = this.addTplFn[name](ev.target.dataset?.[name]);
el.insertAdjacentElement('afterend', t[0]);
return;
}
}
const em = el.cloneNode(true);
em.querySelectorAll('input,select').forEach(ele => {
const fn = {
INPUT(ele) {
ele.value = '';
if (ele.type === 'checkbox') {
ele.checked = false;
}
},
SELECT(ele) {
ele.value = ele.children[0].value
},
TEXTAREA(ele) {
ele.value = ''
}
};
fn?.[ele.nodeName] && fn[ele.nodeName](ele);
});
el.insertAdjacentElement('afterend', em);
},
copy(ev) {
ev.preventDefault();
const el = ev.target.dataset?.target ? findParent(ev.target, ev.target.dataset.target) : ev.target.parentElement;
el.insertAdjacentElement('afterend', el.cloneNode(true));
},
remove(ev) {
ev.target.dataset?.target ?
findParent(ev.target, ev.target.dataset.target)?.remove()
: ev.target.parentElement.remove();
}
};
PushHookAnkiChange('.fetch-file', ev => eventFn.importFn(ev));
PushExpandAnkiInputButton('fetch-copy', '', (e) => {
const item = findParent(e.target, '.fetch-item');
const copyItem = item.cloneNode(true);
copyItem.querySelector('.fetch-active').checked = false;
item.insertAdjacentElement('afterend', copyItem);
});
PushExpandAnkiInputButton('fetch-sentence-field', '', (ev) => {
ankiFetchClickFn(ev.target);
}, '', evt => eventFn.contextMenuAction(evt));
PushHookAnkiDidRender(() => setting.addEventListener('dblclick', settingItemSwitchDisplay));
PushHookAnkiClose(() => setting.removeEventListener('dblclick', settingItemSwitchDisplay));
function download(filename, text, type = "text/plain") {
// Create an invisible A element
const a = document.createElement("a");
a.style.display = "none";
document.body.appendChild(a);
// Set the HREF to a Blob representation of the data to be downloaded
a.href = window.URL.createObjectURL(
new Blob([text], {type})
);
// Use download attribute to set set desired file name
a.setAttribute("download", filename);
// Trigger the download by simulating click
a.click();
// Cleanup
window.URL.revokeObjectURL(a.href);
a.remove();
}
function settingItemSwitchDisplay(ev) {
if (!ev.target.classList.contains('fetch-item')) {
return
}
const sel = setting.children[0];
const items = [];
const displayType = GM_getValue('fetch-display-type', 1);
setting.querySelectorAll('.fetch-item').forEach(item => {
items.push(item.querySelector('.fetch-name').value);
if (displayType === 1 && item !== ev.target) {
item.classList.add('fetch-hidden');
return
}
item.classList.remove('fetch-hidden');
});
if (displayType === 2) {
sel.classList.add('fetch-hidden');
ev.target.scrollIntoView();
ev.target.classList.add('fetch-item-specific');
GM_setValue('fetch-display-type', 1);
return;
}
const opts = items.map(name => [name, name])
sel.innerHTML = buildOption(opts, ev.target.querySelector('.fetch-name').value, 0, 1)
sel.classList.remove('fetch-hidden');
GM_setValue('fetch-display-type', 2);
}
PushHookAnkiChange('.fetch-item-select', (ev) => {
const fn = (name) => {
const t = setting.querySelector(`.fetch-name[value='${name}']`);
if (!t) {
return
}
findParent(t, '.fetch-item').classList.remove('fetch-hidden');
};
const hidden = () => setting.querySelectorAll('.fetch-item:not(.fetch-hidden)').forEach(e => e.classList.add('fetch-hidden'));
const el = ev.target.querySelector(`option[value='${ev.target.value === '*' ? '\\*' : ev.target.value}']`);
if (el.dataset.hasOwnProperty('names')) {
hidden()
el.dataset.names.split(',').forEach(v => {
fn(v);
})
return
}
hidden();
fn(ev.target.value);
});
// show extract processor
PushHookAnkiChange('#fetch.swal2-checkbox', ev => eventFn.showProcessor(ev));
PushHookAnkiChange('.fetch-active', fetchActive);
['swal2-cancel swal2-styled',
'swal2-confirm swal2-styled',
'swal2-container swal2-center swal2-backdrop-hide'].forEach(className => {
PushExpandAnkiInputButton(className, '', saveFetchItems);
});
function getFetchItemEles() {
return [...setting.children].slice(1);
}
function buttonField(item) {
return item?.['fetch-to-field'] ? item['fetch-to-field'] : item['fetch-field'];
}
function addBtn(input, items) {
const title = items.filterAndMapX(item => item['fetch-active'] ? item['fetch-name'] : false).join(',');
const btn = document.createElement('button');
btn.classList.add('fetch-sentence-field');
btn.title = title ? title + ' ' + mapTitle['right-operate'] : mapTitle['right-operate'];
btn.innerHTML = '⚓';
findParent(input, '.form-item')
.querySelector('.field-operate')
.insertAdjacentElement('beforeend', btn);
}
function freshBtns() {
const items = getAnkiFetchParams() ?? [];
if (items.length < 1) {
return
}
const fetchMap = {};
document.querySelectorAll('.fetch-sentence-field').forEach(el => el.remove());
const generic = [];
items.forEach(item => {
if (item['fetch-field'] === '*') {
generic.push(item);
return
}
const field = buttonField(item);
if (!fetchMap?.[field]) {
fetchMap[field] = [];
}
fetchMap[field].push(item);
});
document.querySelectorAll('.field-name').forEach(input => {
const isText = actionHelper.isTextNode(input.nextElementSibling);
const field = input.value;
if (!fetchMap?.[field] && generic.length < 1) {
return
}
const genericArr = generic.filterAndMapX(actionHelper.filterButton(isText));
if (!fetchMap?.[field]) {
addBtn(input, genericArr);
return;
}
addBtn(input, [...fetchMap[field].filterAndMapX(actionHelper.filterButton(isText)), ...genericArr]);
});
}
function fetchActive(ev) {
freshBtns();
saveFetchItems();
}
let setting;
function saveFetchItems() {
actionHelper.flushElementCache();
const data = getFetchItemEles().map(formProcessor.convertFetchParam);
data.length > 0 && GM_setValue('fetch-items', data);
}
const formProcessor = {
getFormValue(form, param = {}, selector = 'input:not([data-batch] input),select:not([data-batch] select),textarea:not([data-batch] textarea)') {
[...form.querySelectorAll(selector)].forEach(el => {
const k = el.name;
let v = el.value;
if (el.type === 'number' && !el.dataset?.float) {
v = parseInt(v);
}
if (el.type === 'checkbox') {
v = el.checked;
}
param[k] = v;
});
return param;
},
convertFetchParam(item) {
const data = formProcessor.getFormValue(item), t = data['operate-type'];
actions.handlers[t]?.form?.(item, data);
return data;
}
};
const log = GM_getValue('dev', window?.['dev']) ? console.log.bind(window.console) : (...args) => {
};
const mapTitle = {
'no file': '没有文件!',
'fold-or-unfold': '折叠或展开子项',
'handleElement': '处理元素',
'handleElement-desc': '只作用于富文本字段,处理指定选择器对应的元素',
'handle': '处理',
'concatenation': '拼接',
'multiple_child': '子项按组查询(queryAll)',
'redundantly import': '无需导入!',
'super html extract and process processor': '超级html提取加工处理器',
"can't parse rule file": '不能解析规则文件!',
'import': '左键增量导入,右键清空原数据后导入',
'export': '左键全部导出,右键导出显示的记录',
'fetch': '抓取',
'escapeHTML': '转义HTML特殊实体',
'unescapeHTML': '去除HTML特殊实体',
'parseTemplate': '解析模板字符串',
'templateVar': '模板字符串,可以为变量,相当于提取解析模板',
'toUpperCase': '转成大写',
'toLowerCase': '转成小写',
'separator': '分隔符',
'cached': '缓存该值(只查询一次)',
'right-operate': '右键选择执行一个操作',
'keep-parent': '当子项不存在时取父项',
'do-all': '一键执行全部操作',
'replacement': '替换',
'tag': '打标签',
'tag-desc': '只作用于富文本字段上,当有满足指定选择器时,打上对应标签',
'fetch-name': '名称,只作为标识',
'operate-type': '操作类型',
'fetch-field': '提取的字段',
'fetch-to-field': '提取到目标字段',
'sequentially-fetch': '只对抓取操作项有效,尝试按内容顺序抓取,可能有性能及其它不未知问题',
'parent-super-name': '父提取值的标识名',
'fetch-selector': '选择器,多个时,前一个为后一个的父选择器,最后一个为锚选择器',
'is_multiple': '是否有多个',
'value-selector': '值选择器,值可为 parent child doc selector (p|ps|ns)@selector [%(p|ps|ns)@selector1] ...',
'fetch-format': '提取的格式,可以使用{自身标识(即提取的值)或子项标识},为空为时默认为 {自身标识}, ',
'fetch-data-handle': '提取到后的操作',
'fetch-data-type': '提取类型',
'fetch-repeat': '不重复',
'fetch-num': '提取的数量,默认0为全部',
'fetch-value-trim': '提取的值去除首尾空白符如空格等',
'tag-selector': '标签的选择器',
'fetch-tag': '设置的标签',
'fetch-active': '是否启用这个操作项',
'fetch-delete': '删除此项',
'fetch-copy': '复制此项',
'fetch-add': '在此项后台添加一个操作项',
'super-fetch-name': '提取值的唯一标识名,可以作为变量名',
'default-value': '默认值,可使用变量{标识名1[|标识名2]...}',
'replace_target_type': '替换目标类型',
'text': '文本',
'add': '左键添加一个空白项,右键复制当前项',
'innerHTML': 'innerHTML',
'outerHTML': 'outerHTML',
'searchValue': '替换或删除的目标,文本值或选择器',
'replaceValue': '替换的值,当为删除时值为选择器',
'remove element': '删除元素',
'replace_regex_pattern': '正则替换模式如果是正则替换的化,为空则为普通替换',
'cover': '覆盖',
'append': '追加',
'none': '啥都不干',
'htmlElement': '元素',
};
function ankiFetchData(param, target = null, from = null) {
if (!target) {
target = actionHelper.getDestEle(param);
}
if (!from) {
from = actionHelper.getFieldElement(param['fetch-field'], target);
}
if (target && from) {
actions.dispatchAction(param, from, target);
return
}
log(param);
}
function getAnkiFetchParams(targetField = '', activeFilter = true) {
const params = getFetchItemEles().length < 1 ? GM_getValue('fetch-items') : getFetchItemEles().map(formProcessor.convertFetchParam);
if (!params || params.length < 1) {
return;
}
if (!targetField) {
return params;
}
return params.filter(param => {
const field = buttonField(param);
if (activeFilter) {
return param['fetch-active'] && (field === '*' || field === targetField);
}
return field === '*' || field === targetField;
});
}
function ankiFetchClickFn(button) {
const triggerField = button.parentElement.parentElement.querySelector('.field-name').value.trim();
const param = getAnkiFetchParams(triggerField, true);
if (param.length < 1) {
return;
}
const sequence = GM_getValue('sequentially-fetch', false);
if (!sequence) {
param.forEach(item => actionHelper.executeAction(item));
return;
}
const fetchItems = param.filterAndMapX(item => item['operate-type'] === 'fetch' ? item : false);
if (!fetchItems || fetchItems?.length < 1) {
return;
}
const from = actionHelper.getFieldElement(fetchItems[0]['fetch-field']);
[...from.children].forEach(el => fetchItems.forEach(item => actionHelper.executeAction(item, el)));
}
const allowFn = {
htmlSpecial, leftTrim, rightTrim, trims,
checked(value) {
return value ? ' checked ' : '';
},
buildOption,
lang(name) {
return htmlSpecial(mapTitle?.[name] ?? name);
}
};
function leftTrim(s, symbol) {
if (!s || !symbol) {
return s;
}
for (const str of symbol.split('')) {
if (s[0] === str) {
s = s.substring(1);
return s
}
}
return s;
}
function rightTrim(s, symbol) {
if (!s || !symbol) {
return s;
}
for (const str of symbol.split('')) {
if (s.length >= 1 && str === s[s.length - 1]) {
s = s.substring(0, s.length - 1);
return s
}
}
return s;
}
function trims(s, symbol = `('")`) {
return rightTrim(leftTrim(s, symbol), symbol);
}
function getVarVal(vars, express, defaults = '') {
if (!express.includes('.')) {
return vars?.[express] ?? defaults;
}
for (const name of express.split('.')) {
if (!vars?.[name]) {
return defaults;
}
vars = vars[name];
}
return vars;
}
const handleOp = {'append': mapTitle['append'], 'cover': mapTitle['cover'], 'none': mapTitle['none']};
const operations = {fetch: mapTitle['fetch'], handle: mapTitle['handle']};
const opType = {
'text': mapTitle['text'],
'remove element': mapTitle['remove element'],
'innerHTML': mapTitle['innerHTML'],
'outerHTML': mapTitle['outerHTML'],
'toUpperCase': mapTitle['toUpperCase'],
'toLowerCase': mapTitle['toLowerCase'],
'parseTemplate': mapTitle['parseTemplate'],
'escapeHTML': mapTitle['escapeHTML'],
'unescapeHTML': mapTitle['unescapeHTML'],
}, htmlType = {
'text': mapTitle['text'],
'innerHTML': mapTitle['innerHTML'],
'outerHTML': mapTitle['outerHTML'],
'htmlElement': mapTitle['htmlElement'],
};
const templateHelper = {
templateFnHook: {},
templateCache: {},
replaceRex: /\{\{(.*?)}}/g,
createElement(tag, attrs = {}) {
const el = document.createElement(tag);
Object.keys(attrs).forEach(k => el[k] = attrs[k]);
return el;
},
buildTemplateHTML(template, vars = {}, ele = document.createElement('div')) {
let t = this.templateCache?.[template] ?? '';
if (!t) {
t = GM_getResourceText(template) ?? '';
this.templateCache[template] = t;
}
if (!t) {
return t
}
t = t.replace(this.replaceRex, (substring, name) => {
const names = name.split('|');
let val = getVarVal(vars, names[0]);
if (names.length < 2) {
return val;
}
for (let fn of names.splice(1)) {
if (fn === 'lang') {
return allowFn.lang(names[0]);
}
const fns = fn.split('(');
const param = [];
if (fns.length > 1) {
fn = fns[0].trim();
trims(fns[1], ')')
.split(',')
.forEach(a =>
(a = a.trim(), param.push(getVarVal(vars, a, trims(a))))
);
}
if (val?.[fn]) {
val = val[fn](...param);
continue;
}
if (!allowFn?.[fn]) {
return val;
}
val = allowFn[fn](val, ...param);
}
return val;
});
ele.innerHTML = t;
ele.querySelectorAll('template').forEach(tpl => {
const t = tpl.innerHTML;
if (vars?.[t]) {
if (vars[t] instanceof Node) {
tpl.replaceWith(vars[t]);
} else if (Array.isArray(vars[t]) || vars[t] instanceof NodeList) {
tpl.replaceWith(...vars[t]);
}
return;
}
const name = t.split('|');
if (name.length < 2) {
tpl.replaceWith(this.buildTemplateHTML(name[0]))
return
}
tpl.replaceWith(this.buildTemplateHTML(name[0], name[1] === '.' ? vars : getVarVal(vars, name[1], null)));
});
if (this.templateFnHook?.[template]) {
this.templateFnHook[template](ele, vars);
}
return ele.children.length > 1 ? ele : ele.children[0];
},
};
function setEleDrag(ele, selector, config = {}) {
let currentItem;
const turnDrag = onoff => ele.querySelectorAll(selector).forEach(item => item.draggable = onoff)
const evenFn = {
dragstart(e) {
e.dataTransfer.effectAllowed = 'move';
currentItem = e.target;
currentItem.classList.add('moving');
},
dragenter(e) {
e.preventDefault();
const children = [...ele.querySelectorAll(selector)];
if (e.target === currentItem || children.length <= 1) {
return
}
if (!e.target.classList.contains('moving') && !currentItem?.classList?.contains('moving')) {
//log(e.target, currentItem)
return;
}
const cur = children.indexOf(currentItem), tar = children.indexOf(e.target);
if (cur < 0 || tar < 0) {
//log(e.target, tar, '---------', currentItem, cur);
return;
}
e.target.insertAdjacentElement(tar > cur ? 'afterend' : 'beforebegin', currentItem);
},
dragend(e) {
currentItem.classList.remove('moving');
saveFetchItems();
},
dragover(e) {
e.preventDefault();
},
mousedown(ev) {
if (ev.target.tagName === 'INPUT') {
turnDrag(false);
}
},
mouseup(ev) {
if (ev.target.tagName === 'INPUT') {
turnDrag(true);
}
},
...config
}
return on => {
if (on) {
turnDrag(true);
Object.keys(evenFn).forEach(name => ele.addEventListener(name, evenFn[name]));
return
}
Object.keys(evenFn).forEach(v => ele.removeEventListener(v, evenFn[v]));
turnDrag(false);
}
}
PushHookAnkiChange('.replace_target_type', evt => {
const input = evt.target.parentElement.querySelector('.replace_regex_pattern');
if (Object.keys(opType).includes(evt.target.value) && (!input || input.nodeName !== 'INPUT')) {
const inp = document.createElement('input');
inp.type = 'text';
inp.name = 'replace_regex_pattern';
inp.className = 'replace_regex_pattern';
inp.title = mapTitle['replace_regex_pattern'];
inp.placeholder = mapTitle['replace_regex_pattern'];
input.replaceWith(inp);
}
});
PushExpandAnkiRichButton('action-switch-text', '', (evt, fn) => {
fn?.(evt);
actionHelper.flushElementCache();
});
const actions = {
// execute action
dispatchAction(param, from = null, target = null) {
this.handlers?.[param?.['operate-type']]?.action?.(param, from, target);
},
};
const actionHelper = {
executeAction(param, from = null, target = null) {
from = from ? from : this.getFromEle(param);
target = target ? target : this.getDestEle(param);
actions.dispatchAction(param, from, target);
},
elementCache: {},
getEleAndCache(field) {
let el = this.elementCache[field];
if (!el) {
el = this.getFieldElement(field);
this.elementCache[field] = el;
}
return el;
},
getFromEle(item, triggerEle = null) {
const field = item['fetch-field'];
if ('*' === field && triggerEle) {
return triggerEle;
}
return this.getEleAndCache(field, triggerEle);
},
getDestEle(item) {
const field = item?.['fetch-to-field'] ?? item['fetch-field'];
return this.getEleAndCache(field);
},
getFieldElement(name) {
let from = document.querySelector(`:where(.field-name)[value='${name}']`);
return findParent(from, '.form-item,.sentence_setting')?.querySelector('.spell-content,.field-value') ?? null;
},
flushElementCache() {
this.elementCache = {};
},
filterButton(isText) {
return item => {
const type = actions.handlers[item['operate-type']].scope;
if (type !== 'all' && ((isText && type !== 'text')) || (!isText && type === 'text')) {
return false;
}
return item;
}
},
isTextNode(ele) {
return this.textNode.has(ele.nodeName)
},
switchAction(data = {}) {
return e => {
const v = e.target.value;
findParent(e.target, '.fetch-item').querySelector('.fetch-action-container').replaceWith(actions.handlers[v].getTemplate(data));
}
},
parseFetchRule(arr, rule = {}) {
let valid = false;
rule[''] = {};
arr.forEach(item => {
rule[item['super-fetch-name']] = item;
if (!item['value-selector'] && !item['default-value'] && !item['fetch-format']) {
log('value-selector or default value emptied', item);
return;
}
valid = true;
try {
rule[item['parent-super-name']]?.['children'] ?
rule[item['parent-super-name']]['children'].push(item)
: rule[item['parent-super-name']]['children'] = [item];
} catch (e) {
console.log(item, 'error parent name', e);
}
});
if (!valid) {
return null;
}
return rule['']?.children ?? null;
},
buildFetchItem(data = {}) {
data['operate-type'] = data['operate-type'] ?? 'fetch';
const handler = actions.handlers[data['operate-type']];
data['op'] = Object.keys(actions.handlers).map(k => [k, actions.handlers[k].text, {title: actions.handlers[k].desc}]);
data['fetch-operator'] = handler.getTemplate(data);
const div = templateHelper.buildTemplateHTML('fetch-base', data);
div.querySelector('.operate-type').addEventListener('change', actionHelper.switchAction(data));
div.querySelector('.fetch-active').addEventListener('change', fetchActive);
return div;
},
};
PushHookAnkiHtml(ankiContainer => {
const div = templateHelper.buildTemplateHTML('fetch-form', {
'sequentially-fetch': GM_getValue('sequentially-fetch', false),
});
div.className = 'form-item fetch-sentence-container';
setting = div.querySelector('.select-setting');
const ty = new Set(['add', 'remove']);
setting.addEventListener('click', evt => {
if (!evt.target.dataset?.['op'] || !ty.has(evt.target.dataset.op)) {
return
}
if (evt.target.dataset.op === 'remove') {
eventFn.remove(evt);
return;
}
eventFn.add(evt);
});
inputEventSelectors.push('.super-fetch-name,.parent-super-name');
setting.addEventListener('contextmenu', evt => evt.target.dataset.op === 'add' && eventFn.copy(evt));
ankiContainer.querySelector('#autoSentenceField').parentElement.insertAdjacentElement('afterend', div);
});
return {
ankiFetchClickFn,
ankiFetchData,
getAnkiFetchParams,
arrayDiff: diff,
setEleDrag,
superFetchHook: {
eventHook: eventFn, getVarVal,
formProcessor, anchorFn: {
p: el => el.parentElement,
ns: el => el.nextElementSibling,
ps: el => el.previousElementSibling,
}, log,
mapTitle, fetchActions: actions,
fetchActionHelper: actionHelper,
mergeMap: (obj, kv) => Object.keys(kv).forEach(k => obj[k] = kv[k]),
hookLang: lang => Object.keys(lang).forEach(k => mapTitle[k] = lang[k]),
lang: name => allowFn.lang(name),
allowFn, htmlType, handleOp, opType, operations,
buildChildrenHtmlFn: templateHelper,
}
}
})();