Pop-up dictionary browser extension for language learning. Successor to Yomichan. (PERSONAL FORK)
at lambda-fork/main 128 lines 4.3 kB view raw
1/* 2 * Copyright (C) 2023-2025 Yomitan Authors 3 * Copyright (C) 2021-2022 Yomichan Authors 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19import {isObjectNotArray} from '../core/object-utilities.js'; 20 21/** @type {RegExp} @readonly */ 22const markerPattern = /\{([\p{Letter}\p{Number}_-]+)\}/gu; 23 24/** 25 * Gets the root deck name of a full deck name. If the deck is a root deck, 26 * the same name is returned. Nested decks are separated using '::'. 27 * @param {string} deckName A string of the deck name. 28 * @returns {string} A string corresponding to the name of the root deck. 29 */ 30export function getRootDeckName(deckName) { 31 const index = deckName.indexOf('::'); 32 return index >= 0 ? deckName.substring(0, index) : deckName; 33} 34 35/** 36 * Checks whether or not any marker is contained in a string. 37 * @param {string} string A string to check. 38 * @returns {boolean} `true` if the text contains an Anki field marker, `false` otherwise. 39 */ 40export function stringContainsAnyFieldMarker(string) { 41 const result = markerPattern.test(string); 42 markerPattern.lastIndex = 0; 43 return result; 44} 45 46/** 47 * Gets a list of all markers that are contained in a string. 48 * @param {string} string A string to check. 49 * @returns {string[]} An array of marker strings. 50 */ 51export function getFieldMarkers(string) { 52 const pattern = markerPattern; 53 const markers = []; 54 while (true) { 55 const match = pattern.exec(string); 56 if (match === null) { break; } 57 markers.push(match[1]); 58 } 59 return markers; 60} 61 62/** 63 * Returns a regular expression which can be used to find markers in a string. 64 * @param {boolean} global Whether or not the regular expression should have the global flag. 65 * @returns {RegExp} A new `RegExp` instance. 66 */ 67export function cloneFieldMarkerPattern(global) { 68 return new RegExp(markerPattern.source, global ? 'gu' : 'u'); 69} 70 71/** 72 * Checks whether or not a note object is valid. 73 * @param {import('anki').Note} note A note object to check. 74 * @returns {boolean} `true` if the note is valid, `false` otherwise. 75 */ 76export function isNoteDataValid(note) { 77 if (!isObjectNotArray(note)) { return false; } 78 const {fields, deckName, modelName} = note; 79 return ( 80 typeof deckName === 'string' && deckName.length > 0 && 81 typeof modelName === 'string' && modelName.length > 0 && 82 Object.entries(fields).length > 0 83 ); 84} 85 86export const INVALID_NOTE_ID = -1; 87 88 89/** 90 * @param {string} prefix 91 * @param {string} extension 92 * @param {number} timestamp 93 * @returns {string} 94 */ 95export function generateAnkiNoteMediaFileName(prefix, extension, timestamp) { 96 let fileName = prefix; 97 98 fileName += `_${ankNoteDateToString(new Date(timestamp))}`; 99 fileName += extension; 100 101 fileName = replaceInvalidFileNameCharacters(fileName); 102 103 return fileName; 104} 105 106/** 107 * @param {string} fileName 108 * @returns {string} 109 */ 110function replaceInvalidFileNameCharacters(fileName) { 111 // eslint-disable-next-line no-control-regex 112 return fileName.replace(/[<>:"/\\|?*\u0000-\u001F]/g, '-'); 113} 114 115/** 116 * @param {Date} date 117 * @returns {string} 118 */ 119function ankNoteDateToString(date) { 120 const year = date.getUTCFullYear(); 121 const month = date.getUTCMonth().toString().padStart(2, '0'); 122 const day = date.getUTCDate().toString().padStart(2, '0'); 123 const hours = date.getUTCHours().toString().padStart(2, '0'); 124 const minutes = date.getUTCMinutes().toString().padStart(2, '0'); 125 const seconds = date.getUTCSeconds().toString().padStart(2, '0'); 126 const milliseconds = date.getUTCMilliseconds().toString().padStart(3, '0'); 127 return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}-${milliseconds}`; 128}