您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
make card
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/534395/1589059/anki.js
- ;const {
- addAnki, getAnkiFormValue,
- anki, ankiSave, showAnkiCard,
- queryAnki, searchAnki, findParent,
- 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 ankTags = new Set();
- 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}
- }
- let searchInput;
- const contextMenuFns = {
- 'anki-tag-search': (ev) => {
- ev.preventDefault();
- const target = ev.target;
- if (!searchInput) {
- searchInput = document.createElement('input');
- searchInput.title = '请输入正面字段名';
- const set = () => {
- const val = searchInput.value.trim();
- if (val) {
- GM_setValue('front-field', val);
- }
- };
- const fn = () => {
- set();
- searchInput.parentElement.replaceChild(target, searchInput);
- target.click();
- }
- searchInput.addEventListener('blur', fn);
- searchInput.addEventListener('keyup', (ev) => {
- if (ev.key === 'Enter') {
- set();
- searchInput.removeEventListener('blur', fn);
- searchInput.parentElement.replaceChild(target, searchInput);
- target.click();
- }
- });
- }
- ev.target.parentElement.replaceChild(searchInput, ev.target);
- },
- '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)
- }
- }
- function focusEle(ele, offset = 0) {
- const s = window.getSelection();
- const r = document.createRange();
- r.setStart(ele, offset);
- r.collapse(true);
- s.removeAllRanges();
- s.addRange(r);
- ele.focus();
- }
- const br = (() => {
- const div = document.createElement('div');
- div.innerHTML = '<br>';
- return div
- })();
- const clickFns = {
- 'hammer': async (ev) => {
- ankiHost = findParent(ev.target, '.form-item').querySelector('#ankiHost').value;
- GM_setValue('ankiHost', ankiHost);
- try {
- const {result: deck} = await anki('deckNames');
- const {result: modelss} = await anki('modelNames');
- deckNames = deck;
- models = modelss;
- findParent(ev.target, '.anki-container').querySelector('#deckName').innerHTML = buildOption(deckNames, deckName);
- findParent(ev.target, '.anki-container').querySelector('#model').innerHTML = buildOption(models, model);
- Swal.resetValidationMessage();
- } catch (e) {
- Swal.showValidationMessage('无法获取anki的数据,请检查ankiconnect是否启动或者重新设置地址再点🔨');
- console.log(e);
- }
- },
- 'btn-add-field shadowAddField': (ev) => {
- const type = parseInt(document.getElementById('shadowField').value);
- fieldFn[type]();
- },
- 'card-delete': async () => {
- if (confirm('确定删除么?')) {
- const {error} = await anki('deleteNotes', {notes: [existsNoteId]});
- if (error) {
- Swal.showValidationMessage(error);
- return
- }
- setExistsNoteId(0);
- }
- },
- 'anki-tag-search': (ev) => {
- const tags = $('#tags');
- if (tags.length < 1) {
- return
- }
- const frontField = GM_getValue('front-field');
- let el;
- if (frontField) {
- for (const front of document.querySelectorAll('.field-name')) {
- if (frontField === front.value) {
- el = front.nextElementSibling;
- break
- }
- }
- }
- if (!el) {
- el = document.querySelector("#shadowFields .field-value");
- }
- const express = tags.val().map(v => `tag:${v}`).join(' ');
- searchAnki(ev, express, el);
- },
- '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 b = br.cloneNode(true);
- ev.target.parentElement.previousElementSibling.querySelector('.spell-content').insertAdjacentElement('afterbegin', b);
- focusEle(b);
- },
- 'word-wrap-last': (ev) => {
- const b = br.cloneNode(true);
- ev.target.parentElement.previousElementSibling.querySelector('.spell-content').insertAdjacentElement('beforeend', b);
- focusEle(b);
- },
- '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);
- }
- function addNewTags(tagsEle, tags) {
- const newTags = [];
- tags.forEach(v => {
- if (!ankTags.has(v)) {
- ankTags.add(v);
- newTags.push([v, v]);
- }
- })
- if (newTags.length > 0) {
- tagsEle.append(buildOption(newTags, '', 0, 1));
- }
- }
- async function showAnkiCard(result) {
- setExistsNoteId(result.noteId);
- const tags = $('#tags');
- addNewTags(tags, result.tags);
- 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 findParent(ele, selector) {
- if (!ele || ele.tagName === 'HTML' || ele === document) {
- return null
- }
- if (ele.matches(selector)) {
- return ele
- }
- return findParent(ele.parentElement, selector)
- }
- const fieldFn = ['', buildInput, buildTextarea];
- 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
- },
- '#model': (ev, value) => {
- fieldChange(ev.target.value, value);
- }
- };
- function fieldChange(field, value) {
- if (field === '') {
- return;
- }
- const modelField = GM_getValue('modelFields-' + field, [[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]);
- })
- }
- }
- 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
- }
- }
- let deckNames, models, deckName, model;
- async function addAnki(value = '') {
- sentenceBackup = calSentence();
- 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 = [];
- const t = setTimeout(() => {
- Swal.showValidationMessage('无法获取anki的数据,请检查ankiconnect是否启动或者重新设置地址再点🔨');
- clearTimeout(t);
- }, 1000);
- }
- model = GM_getValue('model', '问答题');
- let modelFields = GM_getValue('modelFields-' + model, [[1, '正面', true], [2, '背面', false]]);
- 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 changeFn = ev => {
- for (const selector of Object.keys(changeFns)) {
- if (ev.target.matches(selector)) {
- changeFns[selector](ev, value);
- return;
- }
- }
- }
- document.addEventListener('change', changeFn);
- const clickFn = async ev => {
- const className = ev.target.className;
- 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>
- <button class="anki-tag-search" title="左键搜索 右键设置正面字段">🔍</button>
- </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 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 => {
- ankTags.add(v);
- return {id: v, text: v}
- });
- const tag = $('#tags');
- tag.select2({
- tags: true,
- placeholder: '选择或输入标签',
- data: tags,
- tokenSeparators: [',', ' '],
- multiple: true,
- });
- tag.on('change', (ev) => {
- const vals = tag.val();
- document.querySelector('.anki-tag-search').style.display = vals.length > 0 ? 'inline' : 'none';
- })
- 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 '还有参数为空!请检查!';
- }
- const $tags = $('#tags');
- const tags = $tags.val();
- addNewTags($tags, tags);
- 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, findParent,
- anki, queryAnki, showAnkiCard, searchAnki,
- PushAnkiBeforeSaveHook, PushAnkiAfterSaveHook, PushExpandAnkiRichButton, PushExpandAnkiInputButton,
- PushHookAnkiStyle, PushHookAnkiHtml, PushHookAnkiClose, PushHookAnkiDidRender, PushShowFn, PushHookAnkiChange
- };
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址