ეს სკრიპტი არ უნდა იყოს პირდაპირ დაინსტალირებული. ეს ბიბლიოთეკაა, სხვა სკრიპტებისთვის უნდა ჩართეთ მეტა-დირექტივაში // @require https://update.greasyfork.org/scripts/534395/1581905/anki.js
.
;const {
addAnki, getAnkiFormValue,
anki, ankiSave,
queryAnki,
PushAnkiBeforeSaveHook, PushAnkiAfterSaveHook,
PushExpandAnkiRichButton,
PushExpandAnkiInputButton,
PushHookAnkiStyle, PushHookAnkiHtml, PushHookAnkiClose, PushHookAnkiDidRender, PushShowFn, PushHookAnkiChange
} = (() => {
let ankiHost = GM_getValue('ankiHost', 'http://127.0.0.1:8765');
let richTexts = [];
let existsNoteId = 0;
const setExistsNoteId = (id) => {
existsNoteId = id;
const update = document.querySelector('#force-update');
if (id > 0) {
update.parentElement.style.display = 'block';
} else {
update.parentElement.style.display = 'none';
update.checked = false;
}
}
const spellIconsTtf = GM_getResourceURL('spell-icons-ttf');
const spellIconsWoff = GM_getResourceURL('spell-icons-woff');
const spellCss = GM_getResourceText("spell-css")
.replace('chrome-extension://__MSG_@@extension_id__/fg/font/spell-icons.ttf', spellIconsTtf)
.replace('chrome-extension://__MSG_@@extension_id__/fg/font/spell-icons.woff', spellIconsWoff);
const select2Css = GM_getResourceText("select2-css");
const frameCss = GM_getResourceText("frame-css");
const diagStyle = GM_getResourceText('diag-style');
const beforeSaveHookFns = [], afterSaveHookFns = [];
function PushAnkiBeforeSaveHook(...call) {
beforeSaveHookFns.push(...call);
}
function PushAnkiAfterSaveHook(...call) {
afterSaveHookFns.push(...call);
}
PushIconAction && PushIconAction({
name: 'anki',
icon: 'icon-anki',
image: GM_getResourceURL('icon-anki'),
trigger: (t) => {
addAnki(getSelectionElement(), tapKeyboard).catch(res => console.log(res));
}
});
async function queryAnki(expression) {
let {result, error} = await anki('findNotes', {
query: expression
})
if (error) {
throw error;
}
if (result.length < 1) {
return null
}
const res = await anki('notesInfo', {
notes: result
})
if (res.error) {
throw res.error;
}
return res.result;
}
function getSearchType(ev, type = null) {
const value = ev.target.parentElement.previousElementSibling.value.trim();
const field = ev.target.parentElement.parentElement.querySelector('.field-name').value;
const deck = document.querySelector('#deckName').value;
const sel = document.createElement('select');
const inputs = ev.target.parentElement.previousElementSibling;
sel.name = inputs.name;
sel.className = inputs.className;
const precision = `deck:${deck} "${field}:${value}"`;
const str = value.split(' ');
const vague = str.length > 1 ? str.map(v => `${field}:*${v}*`).join(' ') : `${field}:*${value}*`;
const deckVague = `deck:${deck} ` + vague;
if (type !== null) {
return [vague, deckVague, precision, value][type];
}
const searchType = GM_getValue('searchType', 0);
const m = {};
const nbsp = ' '.repeat(5);
const options = [
[vague, `模糊不指定组牌查询: ${nbsp}${vague}`],
[deckVague, `模糊指定组牌查询: ${nbsp}${deckVague}`],
[precision, `精确查询: ${nbsp}${precision}`],
[value, `自定义查询: ${nbsp}${value}`],
].map((v, i) => {
if (i === searchType) {
const vv = v[1].split(':')[0];
v[1] = v[1].replace(vv, vv + ' (默认)');
}
v[0] = htmlSpecial(v[0]);
m[v[0]] = i;
return v;
});
return {options, m}
}
const contextMenuFns = {
'anki-search': async (ev) => {
ev.preventDefault();
const sel = document.createElement('select');
const inputs = ev.target.parentElement.previousElementSibling;
sel.name = inputs.name;
sel.className = inputs.className;
const {options, m} = getSearchType(ev);
sel.innerHTML = buildOption(options, m[GM_getValue('searchType', 0)], 0, 1);
inputs.parentElement.replaceChild(sel, inputs);
sel.focus();
const fn = () => {
GM_setValue('searchType', m[htmlSpecial(sel.value)]);
searchAnki(ev, sel.value, inputs, sel);
sel.removeEventListener('blur', fn);
sel.removeEventListener('change', fn);
};
sel.addEventListener('blur', fn)
sel.addEventListener('change', fn)
},
'action-copy': async (ev) => {
ev.preventDefault();
const ele = ev.target.parentElement.previousElementSibling.querySelector('.spell-content');
const item = new ClipboardItem({
'text/html': new Blob([ele.innerHTML], {type: 'text/html'}),
'text/plain': new Blob([ele.innerHTML], {type: 'text/plain'}),
})
await navigator.clipboard.write([item]).catch(console.log)
}
}
const clickFns = {
'card-delete': async () => {
if (confirm('确定删除么?')) {
const {error} = await anki('deleteNotes', {notes: [existsNoteId]});
if (error) {
Swal.showValidationMessage(error);
return
}
setExistsNoteId(0);
}
},
'anki-search': (ev) => {
const express = getSearchType(ev, GM_getValue('searchType', 0));
const inputs = ev.target.parentElement.previousElementSibling;
searchAnki(ev, express, inputs);
},
'word-wrap-first': (ev) => {
const ed = ev.target.parentElement.previousElementSibling.querySelector('.spell-content');
const b = ed.ownerDocument.createElement('br');
ed.children.length > 0 ? ed.insertBefore(b, ed.children[0]) : ed.innerHTML = `<div><br></div>${ed.innerHTML}`;
ed.focus();
},
'word-wrap-last': (ev) => {
const edt = ev.target.parentElement.previousElementSibling.querySelector('.spell-content');
const br = edt.ownerDocument.createElement('br');
edt.appendChild(br);
},
'upperlowercase': (ev) => {
const input = ev.target.parentElement.previousElementSibling;
if (input.value === '') {
return
}
const stats = input.dataset.stats;
switch (stats) {
case 'upper':
input.value = input.dataset.value;
input.dataset.stats = '';
break
case 'lower':
input.value = input.value.toUpperCase();
input.dataset.stats = 'upper';
break
default:
input.dataset.value = input.value;
input.value = input.value.toLowerCase();
input.dataset.stats = 'lower';
break
}
},
'lemmatizer': (ev) => {
const inputs = ev.target.parentElement.previousElementSibling;
const words = inputs.value.split(' ');
const word = inputs.value.split(' ')[0].toLowerCase();
if (word === '') {
return
}
const origin = lemmatizer.only_lemmas_withPos(word);
if (origin.length < 1) {
return
}
const last = words.length > 1 ? (' ' + words.slice(1).join(' ')) : '';
if (origin.length === 1) {
inputs.value = origin[0][0] + last;
return
}
let wait = origin[0][0];
[...origin].splice(1).map(v => wait = v[0] === origin[0][0] ? wait : v[0]);
if (wait === origin[0][0]) {
inputs.value = origin[0][0] + last
return;
}
const all = origin.map(v => v[0] + last).join(' ');
const ops = [...origin.map(v => [v[0] + last, `${v[1]}:${v[0]} ${last}`]), [all, all]];
const options = buildOption(ops, '', 0, 1);
const sel = document.createElement('select');
sel.name = inputs.name;
sel.className = inputs.className;
sel.innerHTML = options;
inputs.parentElement.replaceChild(sel, inputs);
sel.focus();
sel.onblur = () => {
inputs.value = sel.value;
sel.parentElement.replaceChild(inputs, sel);
}
},
'text-clean': (ev) => {
ev.target.parentElement.previousElementSibling.querySelector('.spell-content').innerHTML = '';
},
'paste-html': async (ev) => {
ev.target.parentElement.previousElementSibling.querySelector('.spell-content').focus();
await tapKeyboard('ctrl v');
},
'action-switch-text': (ev) => {
const el = ev.target.parentElement.previousElementSibling.querySelector('.spell-content');
if (el.tagName === 'DIV') {
const text = el.innerHTML
el.outerHTML = `<textarea class="${el.className}">${text}</textarea>`;
ev.target.title = '切换为富文本'
} else {
const text = el.value
el.outerHTML = `<div class="${el.className}" contenteditable="true">${text}</div>`;
ev.target.title = '切换为textarea'
}
},
'minus': (ev) => {
ev.target.parentElement.parentElement.parentElement.removeChild(ev.target.parentElement.parentElement);
},
"action-copy": async (ev) => {
const ele = ev.target.parentElement.previousElementSibling.querySelector('.spell-content');
const html = await checkAndStoreMedia(ele.innerHTML);
const item = new ClipboardItem({
'text/html': new Blob([html], {type: 'text/html'}),
'text/plain': new Blob([html], {type: 'text/plain'}),
})
await navigator.clipboard.write([item]).catch(console.log)
},
};
async function searchAnki(ev, queryStr, inputs, sels = null) {
const field = ev.target.parentElement.parentElement.querySelector('.field-name').value;
let result;
try {
result = await queryAnki(queryStr);
if (!result) {
setExistsNoteId(0);
sels && sels.parentElement.replaceChild(inputs, sels);
return
}
} catch (e) {
Swal.showValidationMessage(e);
return
}
if (result.length === 1) {
if (sels && sels.parentElement) {
sels.parentElement.replaceChild(inputs, sels);
}
await showAnkiCard(result[0]);
return
}
const sel = document.createElement('select');
sel.name = inputs.name;
sel.className = inputs.className;
const values = {};
const options = result.map(v => {
values[v.fields[field].value] = v;
return [v.fields[field].value, v.fields[field].value];
});
sel.innerHTML = buildOption(options, '', 0, 1);
const ele = (sels && sels.parentElement) ? sels : inputs;
if (!ele || !ele.parentElement) {
return
}
ele.parentElement.replaceChild(sel, ele);
sel.focus();
const changeFn = async () => {
inputs.value = sel.value;
await showAnkiCard(values[sel.value]);
}
const blurFn = async () => {
sel.removeEventListener('change', changeFn);
inputs.value = sel.value;
sel.parentElement.replaceChild(inputs, sel);
await showAnkiCard(values[sel.value]);
};
sel.addEventListener('change', changeFn);
sel.addEventListener('blur', blurFn);
await showAnkiCard(result[0]);
}
const showFns = [];
function PushShowFn(...fns) {
showFns.push(...fns);
}
async function showAnkiCard(result) {
setExistsNoteId(result.noteId);
$('#tags').val(result.tags).trigger('change');
const res = await anki('cardsInfo', {cards: [result.cards[0]]});
if (res.error) {
console.log(res.error);
}
if (res.result.length > 0) {
document.querySelector('#deckName').value = res.result[0].deckName;
}
document.querySelector('#model').value = result.modelName;
const sentenceInput = document.querySelector('#sentence_field');
const sentence = sentenceInput.value;
const fields = {
[sentence]: sentenceInput,
};
[...document.querySelectorAll('#shadowFields input.field-name')].map(input => fields[input.value] = input);
for (const k of Object.keys(result.fields)) {
if (!fields.hasOwnProperty(k)) {
continue;
}
const v = result.fields[k].value;
if (fields[k].nextElementSibling.tagName === 'SELECT') {
continue;
}
if (fields[k].nextElementSibling.tagName === 'INPUT') {
fields[k].nextElementSibling.value = v;
continue;
}
const div = document.createElement('div');
div.innerHTML = v;
for (const img of [...div.querySelectorAll('img')]) {
if (!img.src) {
continue;
}
const srcs = (new URL(img.src)).pathname.split('/');
const src = srcs[srcs.length - 1];
let suffix = 'png';
const name = src.split('.');
suffix = name.length > 1 ? name[1] : suffix;
const {result, error} = await anki('retrieveMediaFile', {'filename': src});
if (error) {
console.log(error);
continue
}
if (!result) {
continue;
}
img.dataset.fileName = src;
img.src = `data:image/${suffix};base64,` + result;
}
fields[k].parentElement.querySelector('.spell-content').innerHTML = div.innerHTML;
}
showFns.forEach(fn => fn(result, res));
}
function buildInput(rawStr = false, field = '', value = '', checked = false) {
const li = document.createElement('div');
const checkeds = checked ? 'checked' : '';
li.className = 'form-item'
li.innerHTML = createHtml(`
<input name="shadow-form-field[]" placeholder="字段名" value="${field}" class="swal2-input field-name">
<input name="shadow-form-value[]" value="${value}" placeholder="字段值" class="swal2-input field-value">
<div class="field-operate">
<button class="minus">➖</button>
<input type="radio" title="选中赋值" ${checkeds} name="shadow-form-defaut[]">
<button class="lemmatizer" title="lemmatize查找单词原型">📟</button>
<button class="anki-search" title="search anki 左健搜索 右键选择搜索模式">🔍</button>
<button class="upperlowercase" title="大小写转换">🔡</button>
${inputButtons.join('\n')} ${inputButtonFields[field] ? inputButtonFields[field].join('\n') : ''}
</div>
`);
if (rawStr) {
return li.outerHTML
}
document.querySelector('#shadowFields ol').appendChild(li)
}
const inputButtons = [], inputButtonFields = {}, buttonFields = {}, buttons = [];
function PushButtonFn(type, className, button, clickFn, field = '', contextMenuFn = null) {
if (!className) {
return
}
const fields = type === 'input' ? inputButtonFields : buttonFields;
const pushButtons = type === 'input' ? inputButtons : buttons;
if (field) {
fields[field] ? fields[field].push(button) : fields[field] = [button];
} else {
button && pushButtons.push(button);
}
if (clickFn) {
const fn = clickFns[className];
clickFns[className] = fn ? (ev) => clickFn(ev, fn) : clickFn;
}
if (contextMenuFn) {
const fn = contextMenuFns[className];
contextMenuFns[className] = fn ? (ev) => contextMenuFn(ev, fn) : contextMenuFn;
}
}
function PushExpandAnkiInputButton(className, button, clickFn, field = '', contextMenuFn = null) {
PushButtonFn('input', className, button, clickFn, field, contextMenuFn)
}
function PushExpandAnkiRichButton(className, button, clickFn, field = '', contextMenuFn = null) {
PushButtonFn('rich', className, button, clickFn, field, contextMenuFn)
}
function buildTextarea(rawStr = false, field = '', value = '', checked = false) {
const li = document.createElement('div');
const checkeds = checked ? 'checked' : '';
const richText = spell();
li.className = 'form-item'
li.innerHTML = createHtml(`
<input name="shadow-form-field[]" placeholder="字段名" value="${field}" class="swal2-input field-name">
<div class="wait-replace"></div>
<div class="field-operate">
<button class="minus">➖</button>
<input type="radio" title="选中赋值" ${checkeds} name="shadow-form-defaut[]">
<button class="paste-html" title="粘贴">✍️</button>
<button class="text-clean" title="清空">🧹</button>
<button class="action-copy" title="复制innerHTML 左键处理图片 右键不处理">⭕</button>
<button class="action-switch-text" title="切换为textrea">🖺</button>
<button class="word-wrap-first" title="在首行换行">🔼</button>
<button class="word-wrap-last" title="在最后换行">🔽</button>
${buttons.join('\n')} ${buttonFields[field] ? buttonFields[field].join('\n') : ''}
</div>
`);
const editor = richText.querySelector('.spell-content');
if (rawStr) {
richTexts.push((ele) => {
editor.innerHTML = value;
enableImageResizeInDiv(editor);
ele.parentElement.replaceChild(richText, ele);
})
return li.outerHTML
}
li.removeChild(li.querySelector('.wait-replace'));
enableImageResizeInDiv(editor);
editor.innerHTML = value;
li.insertBefore(richText, li.querySelector('.field-operate'));
document.querySelector('#shadowFields ol').appendChild(li);
}
const base64Reg = /(data:(.*?)\/(.*?);base64,(.*?)?)[^0-9a-zA-Z=\/+]/i;
async function fetchImg(html) {
const div = document.createElement('div');
div.innerHTML = html;
for (const img of div.querySelectorAll('img')) {
if (img.dataset.hasOwnProperty('fileName') && img.dataset.fileName) {
img.src = img.dataset.fileName;
continue;
}
const prefix = GM_getValue('proxyPrefix', '')
if (img.src.indexOf('http') === 0) {
const name = img.src.split('/').pop().split('&')[0];
const {error: err} = await anki('storeMediaFile', {
filename: name,
url: prefix ? (prefix + encodeURIComponent(img.src)) : img.src,
deleteExisting: false,
})
if (err) {
throw err
}
img.src = name
}
}
return div.innerHTML
}
async function checkAndStoreMedia(text) {
text = await fetchImg(text);
while (true) {
const r = base64Reg.exec(text);
if (!r) {
break
}
const sha = sha1(base64ToUint8Array(r[4]));
const file = 'paste-' + sha + '.' + r[3];
const {error: err} = await anki("storeMediaFile", {
filename: file,
data: r[4],
deleteExisting: false,
}
)
if (err) {
throw err;
}
text = text.replace(r[1], file);
}
return text
}
function anki(action, params = {}) {
return new Promise(async (resolve, reject) => {
await GM_xmlhttpRequest({
method: 'POST',
url: ankiHost,
data: JSON.stringify({action, params, version: 6}),
headers: {
"Content-Type": "application/json"
},
onload: (res) => {
resolve(JSON.parse(res.responseText));
},
onerror: reject,
})
})
}
let enableSentence, sentenceNum, sentenceBackup;
const styles = [], htmls = [], closeFns = [], didRenderFns = [], changeFns = {
".sentence-format-setting": (ev) => {
document.querySelector('.sentence-format').style.display = ev.target.checked ? 'block' : 'none';
},
"#auto-sentence": (ev) => {
document.querySelector('.sample-sentence').style.display = ev.target.checked ? 'grid' : 'none';
enableSentence = ev.target.checked
},
"#sentence_num": (ev) => {
const {wordFormat, sentenceFormat} = sentenceFormatFn();
const {sentence, offset, word,} = sentenceBackup;
const num = parseInt(ev.target.value);
document.querySelector('.sample-sentence .spell-content').innerHTML = cutSentence(word, offset, sentence, num, wordFormat, sentenceFormat);
sentenceNum = num
}
};
function PushHookAnkiClose(fn) {
fn && closeFns.push(fn)
}
function PushHookAnkiDidRender(fn) {
fn && didRenderFns.push(fn)
}
function PushHookAnkiChange(selector, fn) {
if (!selector || !fn) {
return;
}
const fnn = changeFns[selector];
changeFns[selector] = fnn ? (ev) => {
fn(ev, fnn)
} : fn;
}
function PushHookAnkiStyle(style) {
style && styles.push(style)
}
function PushHookAnkiHtml(htmlFn) {
htmlFn && htmls.push(htmlFn)
}
function sentenceFormatFn() {
let wordFormat = decodeHtmlSpecial(document.querySelector('.sentence_bold').value);
if (!wordFormat) {
wordFormat = '<b>{$bold}</b>';
}
let sentenceFormat = decodeHtmlSpecial(document.querySelector('.sentence_format').value);
if (!sentenceFormat) {
sentenceFormat = '<div>{$sentence}</div>'
}
return {
wordFormat, sentenceFormat
}
}
async function addAnki(value = '') {
sentenceBackup = calSentence();
let deckNames, models;
existsNoteId = 0;
if (typeof value === 'string') {
value = value.trim();
}
try {
const {result: deck} = await anki('deckNames');
const {result: modelss} = await anki('modelNames');
deckNames = deck;
models = modelss;
} catch (e) {
console.log(e);
deckNames = [];
models = [];
setTimeout(() => {
Swal.showValidationMessage('无法获取anki的数据,请检查ankiconnect是否启动或者重新设置地址再点🔨');
}, 1000);
}
const model = GM_getValue('model', '问答题');
let modelFields = GM_getValue('modelFields-' + model, [[1, '正面', true], [2, '背面', false]]);
const deckName = GM_getValue('deckName', '');
enableSentence = GM_getValue('enableSentence', true);
const sentenceField = GM_getValue('sentenceField', '句子');
sentenceNum = GM_getValue('sentenceNum', 1);
const lastValues = {ankiHost, model, deckName,}
const deckNameOptions = buildOption(deckNames, deckName);
const modelOptions = buildOption(models, model);
const sentenceHtml = `<div class="wait-replace"></div>
<div class="field-operate">
<button class="paste-html" title="粘贴">✍️</button>
<button class="text-clean" title="清空">🧹</button>
<button class="action-copy" title="复制innerHTML">⭕</button>
<button class="action-switch-text" title="切换为textrea">🖺</button>
${buttons.join('\n')} ${buttonFields[sentenceField].join('\n')}
</div>`
const fieldFn = ['', buildInput, buildTextarea];
const changeFn = ev => {
for (const selector of Object.keys(changeFns)) {
if (ev.target.matches(selector)) {
changeFns[selector](ev);
return;
}
}
if (ev.target.id !== 'model' && ev.target.id !== 'ankiHost') {
return
}
const filed = ev.target.id === 'model' ? ev.target.value : ev.target.parentElement.nextElementSibling.nextElementSibling.querySelector('#model').value;
if (filed === '') {
return;
}
const modelField = GM_getValue('modelFields-' + filed, [[1, '正面', false], [2, '背面', false]]);
document.querySelector('#shadowFields ol').innerHTML = '';
if (modelField.length > 0) {
modelField.forEach(v => {
let t = value
if (value instanceof HTMLElement) {
t = v[0] === 2 ? value.innerHTML : htmlSpecial(value.innerText.trim());
}
fieldFn[v[0]](false, v[1], v[2] ? t : '', v[2]);
})
}
}
document.addEventListener('change', changeFn);
const clickFn = async ev => {
if (ev.target.id === 'shadowAddField') {
const type = parseInt(document.getElementById('shadowField').value);
fieldFn[type]()
return
}
const className = ev.target.className;
if (className === 'hammer') {
ankiHost = ev.target.parentElement.previousElementSibling.value;
GM_setValue('ankiHost', ankiHost);
try {
const {result: deck} = await anki('deckNames');
const {result: modelss} = await anki('modelNames');
deckNames = deck;
models = modelss;
ev.target.parentElement.parentElement.nextElementSibling.querySelector('#deckName').innerHTML = buildOption(deckNames, deckName);
ev.target.parentElement.parentElement.nextElementSibling.nextElementSibling.querySelector('#model').innerHTML = buildOption(models, model);
Swal.resetValidationMessage();
} catch (e) {
Swal.showValidationMessage('无法获取anki的数据,请检查ankiconnect是否启动或者重新设置地址再点🔨');
console.log(e);
}
return
}
clickFns.hasOwnProperty(className) && clickFns[className] && clickFns[className](ev);
}
document.addEventListener('click', clickFn);
const contextMenuFn = (ev) => {
contextMenuFns.hasOwnProperty(ev.target.className) && contextMenuFns[ev.target.className](ev);
};
document.addEventListener('contextmenu', contextMenuFn);
const sentenceBold = GM_getValue('sentence_bold', '');
const sentenceFormat = GM_getValue('sentence_format', '')
let ol = '';
if (modelFields.length > 0) {
ol = modelFields.map(v => {
let t = value
if (value instanceof HTMLElement) {
t = v[0] === 2 ? value.innerHTML : htmlSpecial(value.innerText.trim());
}
return fieldFn[v[0]](true, v[1], v[2] ? t : '', v[2])
}).join('\n')
}
const hookStyles = styles.length > 0 ? `<style>${styles.filter(v => v !== '').join('\n')}</style>` : '';
const style = `<style>${select2Css} ${frameCss} ${spellCss} ${diagStyle} </style> ${hookStyles}`;
const ankiHtml = `${style}
<div class="form-item">
<label for="ankiHost" class="form-label">ankiConnect监听地址</label>
<input id="ankiHost" value="${ankiHost}" placeholder="ankiConnector监听地址" class="swal2-input">
<div class="field-operate">
<button class="hammer">🔨</button>
</div>
</div>
<div class="form-item">
<label for="deckName" class="form-label">牌组</label>
<select id="deckName" class="swal2-select">${deckNameOptions}</select>
</div>
<div class="form-item">
<label for="model" class="form-label">模板</label>
<select id="model" class="swal2-select">${modelOptions}</select>
</div>
<div class="form-item">
<label for="tags" class="form-label">标签</label>
<select class="swal2-select js-example-basic-multiple js-states form-control" id="tags"></select>
</div>
<div class="form-item">
<label for="auto-sentence" class="form-label">自动提取句子</label>
<input type="checkbox" ${enableSentence ? 'checked' : ''} class="swal2-checkbox" name="auto-sentence" id="auto-sentence">
</div>
<div class="form-item">
<label for="shadowField" class="form-label">字段格式</label>
<select id="shadowField" class="swal2-select">
<option value="1">文本</option>
<option value="2">富文本</option>
</select>
<button class="btn-add-field" id="shadowAddField">➕</button>
</div>
<div class="form-item" id="shadowFields">
<ol>${ol}</ol>
</div>
<div class="form-item sample-sentence">
<label class="form-label">句子</label>
<div class="sentence_setting">
<label for="sentence_field" class="form-label">字段</label>
<input type="text" value="${sentenceField}" id="sentence_field" placeholder="句子字段" class="swal2-input sentence_field" name="sentence_field" >
<label class="form-label" for="sentence_num">句子数量</label>
<input type="number" min="0" id="sentence_num" value="${sentenceNum}" class="swal2-input sentence_field" placeholder="提取的句子数量">
<input type="checkbox" class="sentence-format-setting swal2-checkbox" title="设置句子加粗和整句格式">
<dd class="sentence-format">
<input type="text" name="sentence_bold" value="${htmlSpecial(sentenceBold)}" class="sentence_bold sentence-format-input" title="加粗格式,默认: <b>{$bold}</b}" placeholder="加粗格式,默认: <b>{$bold}</b}">
<input type="text" value="${htmlSpecial(sentenceFormat)}" name="sentence_format" class="sentence_format sentence-format-input" title="整句格式,默认: <div>{$sentence}</div>" placeholder="整句格式,默认: <div>{$sentence}</div>">
</dd>
${sentenceHtml}
</div>
</div>
<div class="form-item" style="display: none">
<label for="force-update" class="form-label">更新</label>
<input type="checkbox" class="swal2-checkbox" name="update" id="force-update">
<input type="button" class="card-delete" value="删除">
</div>`;
const ankiContainer = document.createElement('div');
ankiContainer.className = 'anki-container';
ankiContainer.innerHTML = createHtml(ankiHtml);
if (htmls.length > 0) {
htmls.map(fn => fn(ankiContainer));
}
await Swal.fire({
didRender: async () => {
const eles = document.querySelectorAll('.wait-replace');
if (eles.length > 0) {
richTexts.forEach((fn, index) => fn(eles[index]))
}
const se = document.querySelector('.sentence_setting .wait-replace');
if (se) {
const editor = spell();
const {wordFormat, sentenceFormat} = sentenceFormatFn();
const {sentence, offset, word,} = sentenceBackup;
editor.querySelector('.spell-content').innerHTML = cutSentence(word, offset, sentence, sentenceNum, wordFormat, sentenceFormat);
se.parentElement.replaceChild(editor, se);
enableImageResizeInDiv(editor.querySelector('.spell-content'))
}
if (!enableSentence) {
document.querySelector('.sample-sentence').style.display = 'none';
}
let {result: tags} = await anki('getTags');
tags = tags.map(v => {
return {id: v, text: v}
});
$('#tags').select2({
tags: true,
placeholder: '选择或输入标签',
data: tags,
tokenSeparators: [',', ' '],
multiple: true,
});
didRenderFns.length > 0 && didRenderFns.forEach(fn => fn());
},
title: "anki制卡",
showCancelButton: true,
width: '55rem',
html: ankiContainer,
focusConfirm: false,
didDestroy: () => {
richTexts = [];
document.removeEventListener('click', clickFn);
document.removeEventListener('change', changeFn);
document.removeEventListener('contextmenu', contextMenuFn);
closeFns.length > 0 && closeFns.map(fn => fn());
},
preConfirm: async () => {
let r;
try {
r = await ankiSave();
} catch (e) {
Swal.showValidationMessage('发生出错:' + e);
return
}
const {res, modelField, form, params} = r;
console.log(form, params, res);
if (res.error !== null) {
Swal.showValidationMessage('发生出错:' + res.error);
return
}
Object.keys(lastValues).forEach(k => {
if (lastValues[k] !== form[k]) {
GM_setValue(k, form[k])
}
});
const {wordFormat, sentenceFormat} = sentenceFormatFn();
[
[enableSentence, 'enableSentence'],
//[sentenceNum, 'sentenceNum'],
[document.querySelector('#sentence_field').value, 'sentenceField'],
[wordFormat, 'sentence_bold'],
[sentenceFormat, 'sentence_format'],
].forEach(v => {
if (v[0] !== GM_getValue(v[1])) {
GM_setValue(v[1], v[0])
}
})
if (modelField.length !== modelFields.length || !modelField.every((v, i) => v === modelFields[i])) {
GM_setValue('modelFields-' + form.model, modelField)
}
Swal.fire({
html: "操作成功",
timer: 500,
});
}
});
}
async function getAnkiFormValue(formFields) {
const form = {}, fields = {}, modelField = [];
formFields.forEach(field => {
form[field] = document.getElementById(field).value;
});
for (const div of [...document.querySelectorAll('#shadowFields > ol > div')]) {
const name = div.children[0].value;
if (name === '') {
continue;
}
modelField.push([
div.children[1].tagName === 'INPUT' ? 1 : 2,
name,
div.children[2].children[1].checked
]);
if (div.children[1].tagName === 'INPUT') {
fields[name] = decodeHtmlSpecial(div.children[1].value);
} else {
const el = div.querySelector('.spell-content');
fields[name] = await checkAndStoreMedia(el.tagName === 'DIV' ? el.innerHTML : el.value)
}
}
if (Object.values(form).map(v => v === '' ? 0 : 1).reduce((p, c) => p + c, 0) < Object.keys(form).length) {
throw '还有参数为空!请检查!';
}
let tags = $('#tags').val();
if (enableSentence) {
const el = document.querySelector('.sentence_setting .spell-content');
fields[document.querySelector('#sentence_field').value] = await checkAndStoreMedia(el.tagName === 'DIV' ? el.innerHTML : el.value);
}
const params = {
"note": {
"deckName": form.deckName,
"modelName": form.model,
"fields": fields,
"tags": tags,
}
}
return {
params,
modelField,
form,
}
}
async function ankiSave(fields = ['ankiHost', 'model', 'deckName'], update = 'updateNote') {
const {params, modelField, form} = await getAnkiFormValue(fields);
let res;
if (existsNoteId > 0 && document.querySelector('#force-update').checked) {
params.note.id = existsNoteId;
beforeSaveHookFns.forEach(fn => {
const note = fn(true, params.note);
params.note = note ? note : params.note;
});
res = await anki(update, params)
} else {
beforeSaveHookFns.forEach(fn => {
const note = fn(false, params.note);
params.note = note ? note : params.note;
});
res = await anki('addNote', params);
}
afterSaveHookFns.forEach(fn => fn(res, params));
if (res.error) {
throw res.error;
}
return {
res, modelField, form, params
}
}
return {
addAnki, getAnkiFormValue, ankiSave,
anki, queryAnki,
PushAnkiBeforeSaveHook, PushAnkiAfterSaveHook, PushExpandAnkiRichButton, PushExpandAnkiInputButton,
PushHookAnkiStyle, PushHookAnkiHtml, PushHookAnkiClose, PushHookAnkiDidRender, PushShowFn, PushHookAnkiChange
};
})();