Pop-up dictionary browser extension for language learning. Successor to Yomichan. (PERSONAL FORK)
at lambda-fork/main 514 lines 14 kB view raw
1/* 2 * Copyright (C) 2023-2025 Yomitan Authors 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18import type * as Anki from './anki'; 19import type * as AnkiNoteBuilder from './anki-note-builder'; 20import type * as Audio from './audio'; 21import type * as AudioDownloader from './audio-downloader'; 22import type * as Backend from './backend'; 23import type * as Core from './core'; 24import type * as Dictionary from './dictionary'; 25import type * as DictionaryDatabase from './dictionary-database'; 26import type * as DictionaryImporter from './dictionary-importer'; 27import type * as Environment from './environment'; 28import type * as Extension from './extension'; 29import type * as Language from './language'; 30import type * as Log from './log'; 31import type * as Settings from './settings'; 32import type * as SettingsModifications from './settings-modifications'; 33import type * as Translation from './translation'; 34import type * as Translator from './translator'; 35import type {ApiMessageNoFrameIdAny as ApplicationApiMessageNoFrameIdAny} from './application'; 36import type { 37 ApiMap as BaseApiMap, 38 ApiMapInit as BaseApiMapInit, 39 ApiHandler as BaseApiHandler, 40 ApiParams as BaseApiParams, 41 ApiReturn as BaseApiReturn, 42 ApiNames as BaseApiNames, 43 ApiParam as BaseApiParam, 44 ApiParamNames as BaseApiParamNames, 45 ApiParamsAny as BaseApiParamsAny, 46} from './api-map'; 47 48export type FindTermsDetails = { 49 matchType?: Translation.FindTermsMatchType; 50 deinflect?: boolean; 51 primaryReading?: string; 52}; 53 54export type ParseTextResultItem = { 55 id: string; 56 source: 'scanning-parser' | 'mecab'; 57 dictionary: null | string; 58 index: number; 59 content: ParseTextLine[]; 60}; 61 62export type ParseTextSegment = { 63 text: string; 64 reading: string; 65 headwords?: { 66 term: string; 67 reading: string; 68 sources: Dictionary.TermSource[]; 69 }[][]; 70}; 71 72export type ParseTextLine = ParseTextSegment[]; 73 74export type InjectAnkiNoteMediaTermDefinitionDetails = { 75 type: 'term'; 76 term: string; 77 reading: string; 78}; 79 80export type InjectAnkiNoteMediaKanjiDefinitionDetails = { 81 type: 'kanji'; 82 character: string; 83}; 84 85export type InjectAnkiNoteMediaDefinitionDetails = InjectAnkiNoteMediaTermDefinitionDetails | InjectAnkiNoteMediaKanjiDefinitionDetails; 86 87export type InjectAnkiNoteMediaAudioDetails = AnkiNoteBuilder.AudioMediaOptions; 88 89export type InjectAnkiNoteMediaScreenshotDetails = { 90 tabId: number; 91 frameId: number; 92 format: Settings.AnkiScreenshotFormat; 93 quality: number; 94}; 95 96export type InjectAnkiNoteMediaClipboardDetails = { 97 image: boolean; 98 text: boolean; 99}; 100 101export type InjectAnkiNoteMediaDictionaryMediaDetails = { 102 dictionary: string; 103 path: string; 104}; 105 106export type InjectAnkiNoteDictionaryMediaResult = { 107 dictionary: string; 108 path: string; 109 fileName: string | null; 110}; 111 112export type GetMediaDetailsTarget = { 113 path: string; 114 dictionary: string; 115}; 116 117export type GetTermFrequenciesDetailsTermReadingListItem = { 118 term: string; 119 reading: string | null; 120}; 121 122type ApiSurface = { 123 applicationReady: { 124 params: void; 125 return: void; 126 }; 127 optionsGet: { 128 params: { 129 optionsContext: Settings.OptionsContext; 130 }; 131 return: Settings.ProfileOptions; 132 }; 133 optionsGetFull: { 134 params: void; 135 return: Settings.Options; 136 }; 137 termsFind: { 138 params: { 139 text: string; 140 details: FindTermsDetails; 141 optionsContext: Settings.OptionsContext; 142 }; 143 return: { 144 dictionaryEntries: Dictionary.TermDictionaryEntry[]; 145 originalTextLength: number; 146 }; 147 }; 148 parseText: { 149 params: { 150 text: string | string[]; 151 optionsContext: Settings.OptionsContext; 152 scanLength: number; 153 useInternalParser: boolean; 154 useMecabParser: boolean; 155 }; 156 return: ParseTextResultItem[]; 157 }; 158 kanjiFind: { 159 params: { 160 text: string; 161 optionsContext: Settings.OptionsContext; 162 }; 163 return: Dictionary.KanjiDictionaryEntry[]; 164 }; 165 isAnkiConnected: { 166 params: void; 167 return: boolean; 168 }; 169 getAnkiConnectVersion: { 170 params: void; 171 return: number | null; 172 }; 173 addAnkiNote: { 174 params: { 175 note: Anki.Note; 176 }; 177 return: Anki.NoteId | null; 178 }; 179 updateAnkiNote: { 180 params: { 181 noteWithId: Anki.NoteWithId; 182 }; 183 return: null; 184 }; 185 getAnkiNoteInfo: { 186 params: { 187 notes: Anki.Note[]; 188 fetchAdditionalInfo: boolean; 189 }; 190 return: Anki.NoteInfoWrapper[]; 191 }; 192 injectAnkiNoteMedia: { 193 params: { 194 timestamp: number; 195 definitionDetails: InjectAnkiNoteMediaDefinitionDetails; 196 audioDetails: InjectAnkiNoteMediaAudioDetails | null; 197 screenshotDetails: InjectAnkiNoteMediaScreenshotDetails | null; 198 clipboardDetails: InjectAnkiNoteMediaClipboardDetails | null; 199 dictionaryMediaDetails: InjectAnkiNoteMediaDictionaryMediaDetails[]; 200 }; 201 return: { 202 screenshotFileName: string | null; 203 clipboardImageFileName: string | null; 204 clipboardText: string | null; 205 audioFileName: string | null; 206 dictionaryMedia: InjectAnkiNoteDictionaryMediaResult[]; 207 errors: Core.SerializedError[]; 208 }; 209 }; 210 viewNotes: { 211 params: { 212 noteIds: Anki.NoteId[]; 213 mode: Settings.AnkiNoteGuiMode; 214 allowFallback: boolean; 215 }; 216 return: Settings.AnkiNoteGuiMode; 217 }; 218 suspendAnkiCardsForNote: { 219 params: { 220 noteId: Anki.NoteId; 221 }; 222 return: number; 223 }; 224 getTermAudioInfoList: { 225 params: { 226 source: Audio.AudioSourceInfo; 227 term: string; 228 reading: string; 229 languageSummary: Language.LanguageSummary; 230 }; 231 return: AudioDownloader.Info[]; 232 }; 233 commandExec: { 234 params: { 235 command: string; 236 params?: Core.SerializableObject; 237 }; 238 return: boolean; 239 }; 240 sendMessageToFrame: { 241 params: { 242 frameId: number; 243 message: ApplicationApiMessageNoFrameIdAny; 244 }; 245 return: boolean; 246 }; 247 broadcastTab: { 248 params: { 249 message: ApplicationApiMessageNoFrameIdAny; 250 }; 251 return: boolean; 252 }; 253 frameInformationGet: { 254 params: void; 255 return: Extension.ContentOrigin; 256 }; 257 injectStylesheet: { 258 params: { 259 type: 'file' | 'code'; 260 value: string; 261 }; 262 return: void; 263 }; 264 getStylesheetContent: { 265 params: { 266 url: string; 267 }; 268 return: string; 269 }; 270 getEnvironmentInfo: { 271 params: void; 272 return: Environment.Info; 273 }; 274 clipboardGet: { 275 params: void; 276 return: string; 277 }; 278 getZoom: { 279 params: void; 280 return: { 281 zoomFactor: number; 282 }; 283 }; 284 getDefaultAnkiFieldTemplates: { 285 params: void; 286 return: string; 287 }; 288 getDictionaryInfo: { 289 params: void; 290 return: DictionaryImporter.Summary[]; 291 }; 292 purgeDatabase: { 293 params: void; 294 return: void; 295 }; 296 getMedia: { 297 params: { 298 targets: GetMediaDetailsTarget[]; 299 }; 300 return: DictionaryDatabase.MediaDataStringContent[]; 301 }; 302 logGenericErrorBackend: { 303 params: { 304 error: Core.SerializedError; 305 level: Log.LogLevel; 306 context: Log.LogContext | undefined; 307 }; 308 return: void; 309 }; 310 logIndicatorClear: { 311 params: void; 312 return: void; 313 }; 314 modifySettings: { 315 params: { 316 targets: SettingsModifications.ScopedModification[]; 317 source: string; 318 }; 319 return: Core.Response<SettingsModifications.ModificationResult>[]; 320 }; 321 getSettings: { 322 params: { 323 targets: SettingsModifications.ScopedRead[]; 324 }; 325 return: Core.Response<SettingsModifications.ModificationResult>[]; 326 }; 327 setAllSettings: { 328 params: { 329 value: Settings.Options; 330 source: string; 331 }; 332 return: void; 333 }; 334 getOrCreateSearchPopup: { 335 params: { 336 focus?: boolean | 'ifCreated'; 337 text?: string; 338 }; 339 return: { 340 tabId: number | null; 341 windowId: number; 342 }; 343 }; 344 isTabSearchPopup: { 345 params: { 346 tabId: number; 347 }; 348 return: boolean; 349 }; 350 triggerDatabaseUpdated: { 351 params: { 352 type: Backend.DatabaseUpdateType; 353 cause: Backend.DatabaseUpdateCause; 354 }; 355 return: void; 356 }; 357 testMecab: { 358 params: void; 359 return: true; 360 }; 361 testYomitanApi: { 362 params: { 363 url: string; 364 }; 365 return: true; 366 }; 367 isTextLookupWorthy: { 368 params: { 369 text: string; 370 language: string; 371 }; 372 return: boolean; 373 }; 374 getTermFrequencies: { 375 params: { 376 termReadingList: GetTermFrequenciesDetailsTermReadingListItem[]; 377 dictionaries: string[]; 378 }; 379 return: Translator.TermFrequencySimple[]; 380 }; 381 findAnkiNotes: { 382 params: { 383 query: string; 384 }; 385 return: Anki.NoteId[]; 386 }; 387 openCrossFramePort: { 388 params: { 389 targetTabId: number; 390 targetFrameId: number; 391 }; 392 return: { 393 targetTabId: number; 394 targetFrameId: number; 395 }; 396 }; 397 requestBackendReadySignal: { 398 params: void; 399 return: boolean; 400 }; 401 getLanguageSummaries: { 402 params: void; 403 return: Language.LanguageSummary[]; 404 }; 405 heartbeat: { 406 params: void; 407 return: void; 408 }; 409 forceSync: { 410 params: void; 411 return: void; 412 }; 413}; 414 415type ApiExtraArgs = [sender: chrome.runtime.MessageSender]; 416 417export type ApiNames = BaseApiNames<ApiSurface>; 418 419export type ApiMap = BaseApiMap<ApiSurface, ApiExtraArgs>; 420 421export type ApiMapInit = BaseApiMapInit<ApiSurface, ApiExtraArgs>; 422 423export type ApiHandler<TName extends ApiNames> = BaseApiHandler<ApiSurface[TName], ApiExtraArgs>; 424 425export type ApiHandlerNoExtraArgs<TName extends ApiNames> = BaseApiHandler<ApiSurface[TName], []>; 426 427export type ApiParams<TName extends ApiNames> = BaseApiParams<ApiSurface[TName]>; 428 429export type ApiParam<TName extends ApiNames, TParamName extends BaseApiParamNames<ApiSurface[TName]>> = BaseApiParam<ApiSurface[TName], TParamName>; 430 431export type ApiReturn<TName extends ApiNames> = BaseApiReturn<ApiSurface[TName]>; 432 433export type ApiParamsAny = BaseApiParamsAny<ApiSurface>; 434 435export type ApiMessageAny = {[name in ApiNames]: ApiMessage<name>}[ApiNames]; 436 437type ApiMessage<TName extends ApiNames> = { 438 action: TName; 439 params: ApiParams<TName>; 440}; 441 442// postMessage API (i.e., API endpoints called via postMessage, either through ServiceWorker on Chrome or a MessageChannel port on Firefox) 443 444type PmApiSurface = { 445 drawMedia: { 446 params: { 447 requests: DrawMediaRequest[]; 448 }; 449 return: void; 450 }; 451 connectToDatabaseWorker: { 452 params: void; 453 return: void; 454 }; 455 drawBufferToCanvases: { 456 params: { 457 buffer: ArrayBuffer; 458 width: number; 459 height: number; 460 canvasIndexes: number[]; 461 generation: number; 462 }; 463 return: void; 464 }; 465 drawDecodedImageToCanvases: { 466 params: { 467 decodedImage: VideoFrame | ImageBitmap; 468 canvasIndexes: number[]; 469 generation: number; 470 }; 471 return: void; 472 }; 473 registerOffscreenPort: { 474 params: void; 475 return: void; 476 }; 477 registerDatabasePort: { 478 params: void; 479 return: void; 480 }; 481}; 482 483type DrawMediaRequest = { 484 path: string; 485 dictionary: string; 486 canvas: OffscreenCanvas; 487}; 488 489type PmApiExtraArgs = [ports: readonly MessagePort[] | null]; 490 491export type PmApiNames = BaseApiNames<PmApiSurface>; 492 493export type PmApiMap = BaseApiMap<PmApiSurface, PmApiExtraArgs>; 494 495export type PmApiMapInit = BaseApiMapInit<PmApiSurface, PmApiExtraArgs>; 496 497export type PmApiHandler<TName extends PmApiNames> = BaseApiHandler<PmApiSurface[TName], PmApiExtraArgs>; 498 499export type PmApiHandlerNoExtraArgs<TName extends PmApiNames> = BaseApiHandler<PmApiSurface[TName], []>; 500 501export type PmApiParams<TName extends PmApiNames> = BaseApiParams<PmApiSurface[TName]>; 502 503export type PmApiParam<TName extends PmApiNames, TParamName extends BaseApiParamNames<PmApiSurface[TName]>> = BaseApiParam<PmApiSurface[TName], TParamName>; 504 505export type PmApiReturn<TName extends PmApiNames> = BaseApiReturn<PmApiSurface[TName]>; 506 507export type PmApiParamsAny = BaseApiParamsAny<PmApiSurface>; 508 509export type PmApiMessageAny = {[name in PmApiNames]: PmApiMessage<name>}[PmApiNames]; 510 511type PmApiMessage<TName extends PmApiNames> = { 512 action: TName; 513 params: PmApiParams<TName>; 514};