AnkiUtil (#1439)
* Add AnkiUtil * Update AnkiConnect to use AnkiUtil * Use AnkiUtil in AnkiNoteBuilder * Replace containsAnyMarker with AnkiUtil.stringContainsAnyFieldMarker * Add AnkiUtil.getFieldMarkers * Add fieldsObjectContainsMarker to AnkiUtil * Remove unused global * Remove unused parameter: enabled * Add cloneFieldMarkerPattern
This commit is contained in:
parent
0a76de1b44
commit
ae92e0b378
@ -178,6 +178,7 @@
|
||||
"ext/js/comm/clipboard-monitor.js",
|
||||
"ext/js/comm/clipboard-reader.js",
|
||||
"ext/js/comm/mecab.js",
|
||||
"ext/js/data/anki-util.js",
|
||||
"ext/js/data/database.js",
|
||||
"ext/js/data/json-schema.js",
|
||||
"ext/js/data/options-util.js",
|
||||
|
@ -88,6 +88,7 @@
|
||||
|
||||
<script src="/js/comm/api.js"></script>
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/data/permissions-util.js"></script>
|
||||
<script src="/js/input/hotkey-help-controller.js"></script>
|
||||
<script src="/js/input/hotkey-util.js"></script>
|
||||
|
@ -31,6 +31,7 @@
|
||||
<script src="/js/comm/clipboard-monitor.js"></script>
|
||||
<script src="/js/comm/clipboard-reader.js"></script>
|
||||
<script src="/js/comm/mecab.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/data/database.js"></script>
|
||||
<script src="/js/data/json-schema.js"></script>
|
||||
<script src="/js/data/options-util.js"></script>
|
||||
|
@ -64,6 +64,7 @@
|
||||
|
||||
<script src="/js/comm/api.js"></script>
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/data/permissions-util.js"></script>
|
||||
<script src="/js/dom/document-focus-controller.js"></script>
|
||||
<script src="/js/dom/html-template-collection.js"></script>
|
||||
|
@ -15,6 +15,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* global
|
||||
* AnkiUtil
|
||||
*/
|
||||
|
||||
class AnkiConnect {
|
||||
constructor() {
|
||||
this._enabled = false;
|
||||
@ -113,7 +117,7 @@ class AnkiConnect {
|
||||
query = `"deck:${this._escapeQuery(note.deckName)}" `;
|
||||
break;
|
||||
case 'deck-root':
|
||||
query = `"deck:${this._escapeQuery(this.getRootDeckName(note.deckName))}" `;
|
||||
query = `"deck:${this._escapeQuery(AnkiUtil.getRootDeckName(note.deckName))}" `;
|
||||
break;
|
||||
}
|
||||
query += this._fieldsToQuery(note.fields);
|
||||
@ -138,11 +142,6 @@ class AnkiConnect {
|
||||
return await this.findCards(`nid:${noteId}`);
|
||||
}
|
||||
|
||||
getRootDeckName(deckName) {
|
||||
const index = deckName.indexOf('::');
|
||||
return index >= 0 ? deckName.substring(0, index) : deckName;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
async _checkVersion() {
|
||||
|
@ -16,13 +16,14 @@
|
||||
*/
|
||||
|
||||
/* global
|
||||
* AnkiUtil
|
||||
* TemplateRendererProxy
|
||||
*/
|
||||
|
||||
class AnkiNoteBuilder {
|
||||
constructor(enabled) {
|
||||
this._markerPattern = /\{([\w-]+)\}/g;
|
||||
this._templateRenderer = enabled ? new TemplateRendererProxy() : null;
|
||||
constructor() {
|
||||
this._markerPattern = AnkiUtil.cloneFieldMarkerPattern(true);
|
||||
this._templateRenderer = new TemplateRendererProxy();
|
||||
}
|
||||
|
||||
async createNote({
|
||||
@ -46,7 +47,7 @@ class AnkiNoteBuilder {
|
||||
let duplicateScopeCheckChildren = false;
|
||||
if (duplicateScope === 'deck-root') {
|
||||
duplicateScope = 'deck';
|
||||
duplicateScopeDeckName = this.getRootDeckName(deckName);
|
||||
duplicateScopeDeckName = AnkiUtil.getRootDeckName(deckName);
|
||||
duplicateScopeCheckChildren = true;
|
||||
}
|
||||
|
||||
@ -89,27 +90,6 @@ class AnkiNoteBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
containsMarker(fields, marker) {
|
||||
marker = `{${marker}}`;
|
||||
for (const [, fieldValue] of fields) {
|
||||
if (fieldValue.includes(marker)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
containsAnyMarker(field) {
|
||||
const result = this._markerPattern.test(field);
|
||||
this._markerPattern.lastIndex = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
getRootDeckName(deckName) {
|
||||
const index = deckName.indexOf('::');
|
||||
return index >= 0 ? deckName.substring(0, index) : deckName;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
async _formatField(field, data, templates, errors=null) {
|
||||
|
87
ext/js/data/anki-util.js
Normal file
87
ext/js/data/anki-util.js
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Yomichan Authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class has some general utility functions for working with Anki data.
|
||||
*/
|
||||
class AnkiUtil {
|
||||
/**
|
||||
* Gets the root deck name of a full deck name. If the deck is a root deck,
|
||||
* the same name is returned. Nested decks are separated using '::'.
|
||||
* @param deckName A string of the deck name.
|
||||
* @returns A string corresponding to the name of the root deck.
|
||||
*/
|
||||
static getRootDeckName(deckName) {
|
||||
const index = deckName.indexOf('::');
|
||||
return index >= 0 ? deckName.substring(0, index) : deckName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not any marker is contained in a string.
|
||||
* @param string A string to check.
|
||||
* @return `true` if the text contains an Anki field marker, `false` otherwise.
|
||||
*/
|
||||
static stringContainsAnyFieldMarker(string) {
|
||||
const result = this._markerPattern.test(string);
|
||||
this._markerPattern.lastIndex = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all markers that are contained in a string.
|
||||
* @param string A string to check.
|
||||
* @return An array of marker strings.
|
||||
*/
|
||||
static getFieldMarkers(string) {
|
||||
const pattern = this._markerPattern;
|
||||
const markers = [];
|
||||
while (true) {
|
||||
const match = pattern.exec(string);
|
||||
if (match === null) { break; }
|
||||
markers.push(match[1]);
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an object of key-value pairs has a value which contains a specific marker.
|
||||
* @param fieldsObject An object with key-value pairs, where the value corresponds to the field value.
|
||||
* @param marker The marker string to check for, excluding brackets.
|
||||
* @returns `true` if any of the fields contains the marker, `false` otherwise.
|
||||
*/
|
||||
static fieldsObjectContainsMarker(fieldsObject, marker) {
|
||||
marker = `{${marker}}`;
|
||||
for (const [, fieldValue] of fieldsObject) {
|
||||
if (fieldValue.includes(marker)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a regular expression which can be used to find markers in a string.
|
||||
* @param global Whether or not the regular expression should have the global flag.
|
||||
* @returns A new `RegExp` instance.
|
||||
*/
|
||||
static cloneFieldMarkerPattern(global) {
|
||||
return new RegExp(this._markerPattern.source, global ? 'g' : '');
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
AnkiUtil._markerPattern = /\{([\w-]+)\}/g;
|
@ -15,13 +15,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* global
|
||||
* AnkiUtil
|
||||
*/
|
||||
|
||||
class PermissionsUtil {
|
||||
constructor() {
|
||||
this._ankiFieldMarkersRequiringClipboardPermission = new Set([
|
||||
'clipboard-image',
|
||||
'clipboard-text'
|
||||
]);
|
||||
this._ankiMarkerPattern = /\{([\w-]+)\}/g;
|
||||
}
|
||||
|
||||
hasPermissions(permissions) {
|
||||
@ -69,7 +72,7 @@ class PermissionsUtil {
|
||||
}
|
||||
|
||||
getRequiredPermissionsForAnkiFieldValue(fieldValue) {
|
||||
const markers = this._getAnkiFieldMarkers(fieldValue);
|
||||
const markers = AnkiUtil.getFieldMarkers(fieldValue);
|
||||
const markerPermissions = this._ankiFieldMarkersRequiringClipboardPermission;
|
||||
for (const marker of markers) {
|
||||
if (markerPermissions.has(marker)) {
|
||||
@ -99,7 +102,7 @@ class PermissionsUtil {
|
||||
];
|
||||
for (const fields of fieldsList) {
|
||||
for (const fieldValue of Object.values(fields)) {
|
||||
const markers = this._getAnkiFieldMarkers(fieldValue);
|
||||
const markers = AnkiUtil.getFieldMarkers(fieldValue);
|
||||
for (const marker of markers) {
|
||||
if (fieldMarkersRequiringClipboardPermission.has(marker)) {
|
||||
return false;
|
||||
@ -111,16 +114,4 @@ class PermissionsUtil {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_getAnkiFieldMarkers(fieldValue) {
|
||||
const pattern = this._ankiMarkerPattern;
|
||||
const markers = [];
|
||||
let match;
|
||||
while ((match = pattern.exec(fieldValue)) !== null) {
|
||||
markers.push(match[1]);
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
/* global
|
||||
* AnkiNoteBuilder
|
||||
* AnkiUtil
|
||||
* DisplayAudio
|
||||
* DisplayGenerator
|
||||
* DisplayHistory
|
||||
@ -85,7 +86,7 @@ class Display extends EventDispatcher {
|
||||
});
|
||||
this._ankiFieldTemplates = null;
|
||||
this._ankiFieldTemplatesDefault = null;
|
||||
this._ankiNoteBuilder = new AnkiNoteBuilder(true);
|
||||
this._ankiNoteBuilder = new AnkiNoteBuilder();
|
||||
this._updateAdderButtonsPromise = Promise.resolve();
|
||||
this._contentScrollElement = document.querySelector('#content-scroll');
|
||||
this._contentScrollBodyElement = document.querySelector('#content-body');
|
||||
@ -1493,7 +1494,7 @@ class Display extends EventDispatcher {
|
||||
const definitionDetails = this._getDefinitionDetailsForNote(definition);
|
||||
|
||||
let audioDetails = null;
|
||||
if (definitionDetails.type !== 'kanji' && this._ankiNoteBuilder.containsMarker(fields, 'audio')) {
|
||||
if (definitionDetails.type !== 'kanji' && AnkiUtil.fieldsObjectContainsMarker(fields, 'audio')) {
|
||||
const primaryCardAudio = this._displayAudio.getPrimaryCardAudio(definitionDetails.expression, definitionDetails.reading);
|
||||
let preferredAudioIndex = null;
|
||||
let sources2 = sources;
|
||||
@ -1504,11 +1505,11 @@ class Display extends EventDispatcher {
|
||||
audioDetails = {sources: sources2, preferredAudioIndex, customSourceUrl, customSourceType};
|
||||
}
|
||||
|
||||
const screenshotDetails = (this._ankiNoteBuilder.containsMarker(fields, 'screenshot') ? {tabId: this._contentOriginTabId, frameId: this._contentOriginFrameId, format, quality} : null);
|
||||
const screenshotDetails = (AnkiUtil.fieldsObjectContainsMarker(fields, 'screenshot') ? {tabId: this._contentOriginTabId, frameId: this._contentOriginFrameId, format, quality} : null);
|
||||
|
||||
const clipboardDetails = {
|
||||
image: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'),
|
||||
text: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-text')
|
||||
image: AnkiUtil.fieldsObjectContainsMarker(fields, 'clipboard-image'),
|
||||
text: AnkiUtil.fieldsObjectContainsMarker(fields, 'clipboard-text')
|
||||
};
|
||||
|
||||
return await yomichan.api.injectAnkiNoteMedia(
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
/* global
|
||||
* AnkiConnect
|
||||
* AnkiNoteBuilder
|
||||
* AnkiUtil
|
||||
* ObjectPropertyAccessor
|
||||
* SelectorObserver
|
||||
*/
|
||||
@ -26,7 +26,6 @@ class AnkiController {
|
||||
constructor(settingsController) {
|
||||
this._settingsController = settingsController;
|
||||
this._ankiConnect = new AnkiConnect();
|
||||
this._ankiNoteBuilder = new AnkiNoteBuilder(false);
|
||||
this._selectorObserver = new SelectorObserver({
|
||||
selector: '.anki-card',
|
||||
ignoreSelector: null,
|
||||
@ -156,10 +155,6 @@ class AnkiController {
|
||||
return this._settingsController.permissionsUtil.getRequiredPermissionsForAnkiFieldValue(fieldValue);
|
||||
}
|
||||
|
||||
containsAnyMarker(field) {
|
||||
return this._ankiNoteBuilder.containsAnyMarker(field);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
async _onOptionsChanged({options: {anki}}) {
|
||||
@ -439,7 +434,7 @@ class AnkiCardController {
|
||||
|
||||
_validateField(node, index) {
|
||||
let valid = (node.dataset.hasPermissions !== 'false');
|
||||
if (valid && index === 0 && !this._ankiController.containsAnyMarker(node.value)) {
|
||||
if (valid && index === 0 && !AnkiUtil.stringContainsAnyFieldMarker(node.value)) {
|
||||
valid = false;
|
||||
}
|
||||
node.dataset.invalid = `${!valid}`;
|
||||
|
@ -32,7 +32,7 @@ class AnkiTemplatesController {
|
||||
this._renderFieldInput = null;
|
||||
this._renderResult = null;
|
||||
this._fieldTemplateResetModal = null;
|
||||
this._ankiNoteBuilder = new AnkiNoteBuilder(true);
|
||||
this._ankiNoteBuilder = new AnkiNoteBuilder();
|
||||
}
|
||||
|
||||
async prepare() {
|
||||
|
@ -167,6 +167,7 @@
|
||||
|
||||
<script src="/js/comm/api.js"></script>
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/data/permissions-util.js"></script>
|
||||
<script src="/js/dom/document-focus-controller.js"></script>
|
||||
<script src="/js/dom/html-template-collection.js"></script>
|
||||
|
@ -97,6 +97,7 @@
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/comm/frame-endpoint.js"></script>
|
||||
<script src="/js/data/anki-note-builder.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/display/display.js"></script>
|
||||
<script src="/js/display/display-audio.js"></script>
|
||||
<script src="/js/display/display-generator.js"></script>
|
||||
|
@ -83,6 +83,7 @@
|
||||
<script src="/js/comm/clipboard-monitor.js"></script>
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/data/anki-note-builder.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/display/display.js"></script>
|
||||
<script src="/js/display/display-audio.js"></script>
|
||||
<script src="/js/display/display-generator.js"></script>
|
||||
|
@ -1288,6 +1288,7 @@
|
||||
<script src="/js/comm/api.js"></script>
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/data/anki-note-builder.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/data/database.js"></script>
|
||||
<script src="/js/data/json-schema.js"></script>
|
||||
<script src="/js/data/options-util.js"></script>
|
||||
|
@ -3198,6 +3198,7 @@
|
||||
<script src="/js/comm/api.js"></script>
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/data/anki-note-builder.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/data/database.js"></script>
|
||||
<script src="/js/data/json-schema.js"></script>
|
||||
<script src="/js/data/options-util.js"></script>
|
||||
|
@ -28,6 +28,7 @@ self.importScripts(
|
||||
'/js/comm/clipboard-monitor.js',
|
||||
'/js/comm/clipboard-reader.js',
|
||||
'/js/comm/mecab.js',
|
||||
'/js/data/anki-util.js',
|
||||
'/js/data/database.js',
|
||||
'/js/data/json-schema.js',
|
||||
'/js/data/options-util.js',
|
||||
|
@ -320,6 +320,7 @@
|
||||
|
||||
<script src="/js/comm/api.js"></script>
|
||||
<script src="/js/comm/cross-frame-api.js"></script>
|
||||
<script src="/js/data/anki-util.js"></script>
|
||||
<script src="/js/data/database.js"></script>
|
||||
<script src="/js/data/json-schema.js"></script>
|
||||
<script src="/js/data/permissions-util.js"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user