diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eda862..aa20df5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Changelog ### Version 1.3.4 + * Adds the ability to select which dictionary definition you want to import or hear the audio of (#13) * Fixes missing audio files form LanguagePod (#6) ### Version 1.3.3 diff --git a/options/options.html b/options/options.html index 4ceb2f5..7ac5ad7 100644 --- a/options/options.html +++ b/options/options.html @@ -134,7 +134,7 @@

Keymap

- +
@@ -165,6 +165,20 @@

Keymap

+ +
+
+
+
+ +
+
+
+
@@ -236,49 +250,61 @@

Dictionaries

-
+
-
+
+
-
+
+
-
+
-
+
-
+
+
-
+
-
+
-
+
-
+
-
+
+

Installing Epwing

- To install epwing, please download and extract zip file, then run the - install script for your operating system. It doesn't copy any executables to any directories, so you need to keep the folder where it is. - If you accidentally move or delete the folder, just get the zip again, extract to the desired location and re-run the install script. + To install epwing, please download and extract zip file, then run the + install script for your operating system. It doesn't copy any executables to any directories, so you need to + keep the folder where it is. + If you accidentally move or delete the folder, just get the zip again, extract to the desired location and + re-run the install script. -
+
@@ -299,13 +325,13 @@

Keymap

- +
- +
@@ -374,6 +400,7 @@

Keymap

Version 1.3.4

@@ -394,7 +421,9 @@

Version 1.3.1

Version 1.3.0

Version 1.2.2

@@ -433,7 +462,9 @@

Version 1.1.4

Version 1.1.3

Version 1.1.2

@@ -443,7 +474,8 @@

Version 1.1.2

Version 1.1.1

Version 1.1.0

diff --git a/src/AnkiImport.ts b/src/AnkiImport.ts index 625cc82..00cdbf8 100644 --- a/src/AnkiImport.ts +++ b/src/AnkiImport.ts @@ -104,14 +104,16 @@ export default class AnkiImport { return Promise.all(promises); } - async downloadAudio(entry: SearchResults) { + async downloadAudio(entry: SearchResults & { selected?: number }) { if (!isDictionaryResult(entry)) return; if (!entry?.data) return; if (await AudioPlayer.isNoAudio(entry)) return; + const selected = entry.selected || 0; + const audioUrl = AudioPlayer.getAudioUrl(entry); - let [, dictionaryForm, reading] = entry.data[0][0].match( + let [, dictionaryForm, reading] = entry.data[selected][0].match( /^(.+?)\s+(?:\[(.*?)\])?\s*\/(.+)\// ); @@ -124,6 +126,7 @@ export default class AnkiImport { } // entry = Contains the work lookup info (kana, kanji, def) + // selected = The index of the entry to look up // word = Highlighted Word // sentence = The sentence containing the highlighted word // sentenceWBlank = Like sentence except the highlighted word is replaced with blanks @@ -131,6 +134,7 @@ export default class AnkiImport { // saveFormat = Token-based save format static async makeTextOptions( entry: SearchResults, + selected: number, word: string, sentence: string, sentenceWithBlank: string, @@ -159,7 +163,9 @@ export default class AnkiImport { // entryData[2] = kana (null if no kanji) // entryData[3] = definition - entryData = entry.data[0][0].match(/^(.+?)\s+(?:\[(.*?)\])?\s*\/(.+)\//); + entryData = entry.data[selected][0].match( + /^(.+?)\s+(?:\[(.*?)\])?\s*\/(.+)\// + ); let [_, dictionaryForm, reading, definition] = entryData; // Does the user want to use the reading in place of kanji for the $d token? diff --git a/src/AudioPlayer.ts b/src/AudioPlayer.ts index 4abf893..8ee60eb 100644 --- a/src/AudioPlayer.ts +++ b/src/AudioPlayer.ts @@ -2,9 +2,10 @@ import Utils from "./Utils"; import {isKanjiResult, SearchResults} from "./interfaces/SearchResults"; export default class AudioPlayer { - static getAudioUrl(entry: SearchResults): string { + static getAudioUrl(entry: SearchResults & { selected?: number }): string { let kanaText; let kanjiText; + const selected = entry.selected || 0; //We have a single kanji selected if (isKanjiResult(entry)) { @@ -18,8 +19,8 @@ export default class AudioPlayer { kanaText = Utils.convertKatakanaToHirigana(kanaText).kana; if (!kanaText) return; - } else if (entry.data[0]) { - let entryData = entry.data[0][0].match( + } else if (entry.data[selected]) { + let entryData = entry.data[selected][0].match( /^(.+?)\s+(?:\[(.*?)\])?\s*\/(.+)\// ); diff --git a/src/background.ts b/src/background.ts index e835a1e..c586c68 100644 --- a/src/background.ts +++ b/src/background.ts @@ -117,6 +117,7 @@ export class RikaiController { async sendToAnki(content) { const { entry, + selected, word, sentence, sentenceWithBlank, @@ -125,6 +126,7 @@ export class RikaiController { } = content; const entryFormat = await AnkiImport.makeTextOptions( entry, + selected || 0, word, sentence, sentenceWithBlank, diff --git a/src/content/index.ts b/src/content/index.ts index ac8c7c4..50541cf 100644 --- a/src/content/index.ts +++ b/src/content/index.ts @@ -1,1106 +1,1376 @@ -import {docImposterDestroy, docRangeFromPoint} from './document'; +import {docImposterDestroy, docRangeFromPoint} from "./document"; import {transformNameEntriesToHtml} from "./entryTransformers"; -import autobind from '../../lib/autobind'; -import defaultConfig, {Config} from '../defaultConfig'; -import Utils from '../Utils'; -import '../../styles/popup.css'; +import autobind from "../../lib/autobind"; +import defaultConfig, {Config} from "../defaultConfig"; +import Utils from "../Utils"; +import "../../styles/popup.css"; import {TextSourceRange} from "./source"; -import {isDictionaryResult, isKanjiResult, SearchResults} from "../interfaces/SearchResults"; +import {isDictionaryResult, isKanjiResult, SearchResults,} from "../interfaces/SearchResults"; type position = { - clientX: number, - clientY: number, - pageX: number, - pageY: number, -} + clientX: number; + clientY: number; + pageX: number; + pageY: number; +}; type tabData = { - selText?: string; - previousRangeOffset?: number; - pos?: position, - previousTarget?: HTMLElement, - previousTextSource?: TextSourceRange, + selText?: string; + previousRangeOffset?: number; + pos?: position; + previousTarget?: HTMLElement; + previousTextSource?: TextSourceRange; }; class Rikai { - private document: Document; - private tabData: tabData = {}; - private lastFound: SearchResults; - private enabled: boolean = false; - private popupId: string = 'rikaichan-window'; - private config: Config; - private keysDown: any[] = []; - - private epwingMode: boolean = false; - private epwingTotalHits: number = 0; - private epwingCurrentHit: number = 0; - private epwingPreviousHit: number = 0; - private epwingResults: any[] = []; - - private sanseidoFallback: number = 0; - private sanseidoMode: boolean = false; - private abortController: AbortController = new AbortController(); - - private word: string; - private sentence: string; - private sentenceWithBlank: string; - - constructor(document: Document) { - autobind(this); - - this.document = document; - this.config = defaultConfig; + private document: Document; + private tabData: tabData = {}; + private lastFound: SearchResults; + private enabled: boolean = false; + private popupId: string = "rikaichan-window"; + private config: Config; + private keysDown: any[] = []; + + private epwingMode: boolean = false; + private epwingTotalHits: number = 0; + private epwingCurrentHit: number = 0; + private epwingPreviousHit: number = 0; + private epwingResults: any[] = []; + + private sanseidoFallback: number = 0; + private sanseidoMode: boolean = false; + private abortController: AbortController = new AbortController(); + + private selectedEntry: number = 0; + private totalEntries: number = 1; + private word: string; + private sentence: string; + private sentenceWithBlank: string; + + private lastPopupPosition: { left: number; top: number }; + + constructor(document: Document) { + autobind(this); + + this.document = document; + this.config = defaultConfig; + } + + enable(): void { + if (this.enabled) return; + + browser.storage.sync + .get("config") + .then((config: Storage & { config: Config }) => { + this.config = config.config || defaultConfig; + this.document.documentElement.removeChild(this.getPopup()); + }); + + this.document.addEventListener("mousemove", this.onMouseMove); + this.document.addEventListener("mousedown", this.onMouseDown); + this.document.addEventListener("keydown", (event: KeyboardEvent) => { + const shouldStop = this.onKeyDown(event); + if (shouldStop === true) { + event.stopPropagation(); + event.preventDefault(); + } + }); + this.document.addEventListener("keyup", this.onKeyUp); + + browser.storage.onChanged.addListener((changes, areaSet) => { + if (areaSet !== "sync") return; + if (typeof changes.config === "undefined") return; + + this.config = changes.config.newValue || defaultConfig; + this.document.documentElement.removeChild(this.getPopup()); + }); + + this.createPopup(); + + this.enabled = true; + } + + disable(): void { + this.document.removeEventListener("mousemove", this.onMouseMove); + this.document.removeEventListener("mousedown", this.onMouseDown); + this.document.removeEventListener("keydown", (event: KeyboardEvent) => { + const shouldStop = this.onKeyDown(event); + if (shouldStop === true) { + event.stopPropagation(); + event.preventDefault(); + } + }); + + this.document.removeEventListener("keyup", this.onKeyUp); + + if (this.hasPopup()) { + this.document.documentElement.removeChild(this.getPopup()); } - enable(): void { - if (this.enabled) return; - - browser.storage.sync.get('config').then((config: Storage & {config: Config}) => { - this.config = config.config || defaultConfig; - this.document.documentElement.removeChild(this.getPopup()); - }); - - this.document.addEventListener('mousemove', this.onMouseMove); - this.document.addEventListener('mousedown', this.onMouseDown); - this.document.addEventListener('keydown', (event: KeyboardEvent) => { - const shouldStop = this.onKeyDown(event); - if (shouldStop === true) { - event.stopPropagation(); - event.preventDefault(); - } - }); - this.document.addEventListener('keyup', this.onKeyUp); - - browser.storage.onChanged.addListener((changes, areaSet) => { - if (areaSet !== 'sync') return; - if (typeof changes.config === 'undefined') return; - - this.config = changes.config.newValue || defaultConfig; - this.document.documentElement.removeChild(this.getPopup()); + this.enabled = false; + } + + onKeyDown(event: KeyboardEvent): boolean { + if (event.altKey || event.metaKey || event.ctrlKey) return; + if (event.shiftKey && event.keyCode !== 16) return; + if (this.keysDown[event.keyCode]) return; + if (!this.isVisible()) return; + + const { pos } = this.tabData; + + this.keysDown[event.keyCode] = 1; + + switch (event.keyCode) { + case this.config.keymap.playAudio: + return this.playAudio(); + case this.config.keymap.sendToAnki: + return this.sendToAnki(); + case this.config.keymap.selectNextDictionary: + this.sendRequest("selectNextDictionary").then(() => { + setTimeout(() => { + this.searchAt({ x: pos.clientX, y: pos.clientY }, this.tabData); + }, 1); }); + return; + case this.config.keymap.toggleSanseidoMode: + browser.storage.local.set({ sanseidoMode: !this.sanseidoMode }); + setTimeout(() => { + this.searchAt({ x: pos.clientX, y: pos.clientY }, this.tabData); + }, 5); + return true; - this.createPopup(); - - this.enabled = true; - } - - disable(): void { - this.document.removeEventListener('mousemove', this.onMouseMove); - this.document.removeEventListener('mousedown', this.onMouseDown); - this.document.removeEventListener('keydown', (event: KeyboardEvent) => { - const shouldStop = this.onKeyDown(event); - if (shouldStop === true) { - event.stopPropagation(); - event.preventDefault(); - } - }); + case this.config.keymap.toggleEpwingMode: + const originalEpwing = this.epwingMode; + browser.storage.local.set({ epwingMode: !this.epwingMode }); + setTimeout(() => { + if (this.epwingMode !== originalEpwing) { + this.searchAt({ x: pos.clientX, y: pos.clientY }, this.tabData); + } + }, 5); + return true; - this.document.removeEventListener('keyup', this.onKeyUp); + case this.config.keymap.epwingNextEntry: + return this.showNextEpwingEntry(); + case this.config.keymap.epwingPreviousEntry: + return this.showPreviousEpwingEntry(); - if (this.hasPopup()) { - this.document.documentElement.removeChild(this.getPopup()); + case this.config.keymap.nextDefinition: + this.selectedEntry++; + if (this.selectedEntry + 1 > this.totalEntries) { + this.selectedEntry = 0; } - this.enabled = false; - } + this.renderPopup(); + return true; - onKeyDown(event: KeyboardEvent): boolean { - if ((event.altKey) || (event.metaKey) || (event.ctrlKey)) return; - if ((event.shiftKey) && (event.keyCode !== 16)) return; - if (this.keysDown[event.keyCode]) return; - if (!this.isVisible()) return; - - const { pos } = this.tabData; - - this.keysDown[event.keyCode] = 1; - - switch (event.keyCode) { - case this.config.keymap.playAudio: - return this.playAudio(); - case this.config.keymap.sendToAnki: - return this.sendToAnki(); - case this.config.keymap.selectNextDictionary: - this.sendRequest('selectNextDictionary').then(() => { - setTimeout(() => { - this.searchAt({x: pos.clientX, y: pos.clientY}, this.tabData); - }, 1); - }); - return; - case this.config.keymap.toggleSanseidoMode: - browser.storage.local.set({ sanseidoMode: !this.sanseidoMode }); - setTimeout(() => { - this.searchAt({x: pos.clientX, y: pos.clientY}, this.tabData); - }, 5); - return true; - - case this.config.keymap.toggleEpwingMode: - const originalEpwing = this.epwingMode; - browser.storage.local.set({ epwingMode: !this.epwingMode }); - setTimeout(() => { - if (this.epwingMode !== originalEpwing) { - this.searchAt({x: pos.clientX, y: pos.clientY}, this.tabData); - } - }, 5); - return true; - - case this.config.keymap.epwingNextEntry: - return this.showNextEpwingEntry(); - case this.config.keymap.epwingPreviousEntry: - return this.showPreviousEpwingEntry(); + case this.config.keymap.previousDefinition: + this.selectedEntry--; + if (this.selectedEntry < 0) { + this.selectedEntry = this.totalEntries - 1; } - return false; - } - - onKeyUp(event: KeyboardEvent) { - if (this.keysDown[event.keyCode]) this.keysDown[event.keyCode] = 0; - } - - onMouseDown(): void { - this.clear(); + this.renderPopup(); + return true; } - async onMouseMove(event: MouseEvent) { - const tabData = this.tabData; + return false; + } - if (event.buttons !== 0) return; + onKeyUp(event: KeyboardEvent) { + if (this.keysDown[event.keyCode]) this.keysDown[event.keyCode] = 0; + } - let distance = null; - if (tabData.pos) { - const distanceX = tabData.pos.clientX - event.clientX; - const distanceY = tabData.pos.clientY - event.clientY; - distance = Math.sqrt((distanceX * distanceX) + (distanceY * distanceY)); - } + onMouseDown(): void { + this.clear(); + } - if (event.target === tabData.previousTarget && distance && distance <= 4) { - return; - } + async onMouseMove(event: MouseEvent) { + const tabData = this.tabData; - tabData.previousTarget = event.target; - tabData.pos = {clientX: event.clientX, clientY: event.clientY, pageX: event.pageX, pageY: event.pageY}; + if (event.buttons !== 0) return; - return setTimeout(() => { - this.searchAt({x: event.clientX, y: event.clientY}, tabData, event); - }, 1); + let distance = null; + if (tabData.pos) { + const distanceX = tabData.pos.clientX - event.clientX; + const distanceY = tabData.pos.clientY - event.clientY; + distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY); } - async searchAt(point: { x: number, y: number }, tabData: tabData, event?: MouseEvent) { - const textSource = docRangeFromPoint(point); - - if (!textSource || !textSource.range || typeof textSource.range.startContainer.textContent === 'undefined') { - this.clear(); - return; - } - - if (event && event.target === tabData.previousTarget && textSource.equals(tabData.previousTextSource)) { - return; - } - - let charCode = textSource.range.startContainer.textContent.charCodeAt(textSource.range.startOffset); - if ((isNaN(charCode)) || - ((charCode !== 0x25CB) && - ((charCode < 0x3001) || (charCode > 0x30FF)) && - ((charCode < 0x3400) || (charCode > 0x9FFF)) && - ((charCode < 0xF900) || (charCode > 0xFAFF)) && - ((charCode < 0xFF10) || (charCode > 0xFF9D)))) { - this.clear(); - return -2; - } - - tabData.previousRangeOffset = textSource.range.startOffset; - - tabData.previousTextSource = textSource; - - const textClone = textSource.clone(); - const sentenceClone = textSource.clone(); + if (event.target === tabData.previousTarget && distance && distance <= 4) { + return; + } - textClone.setEndOffset(20); - const text = textClone.text(); + tabData.previousTarget = event.target; + tabData.pos = { + clientX: event.clientX, + clientY: event.clientY, + pageX: event.pageX, + pageY: event.pageY, + }; - sentenceClone.setEndOffsetFromBeginningOfCurrentNode(textSource.range.startContainer.textContent.length + 50); - const sentence = sentenceClone.text(); + return setTimeout(() => { + this.searchAt({ x: event.clientX, y: event.clientY }, tabData, event); + }, 1); + } + + async searchAt( + point: { x: number; y: number }, + tabData: tabData, + event?: MouseEvent + ) { + const textSource = docRangeFromPoint(point); + + if ( + !textSource || + !textSource.range || + typeof textSource.range.startContainer.textContent === "undefined" + ) { + this.clear(); + return; + } - this.showFromText(text, sentence, textSource.range.startOffset,() => { - textClone.setEndOffset(this.word.length); + if ( + event && + event.target === tabData.previousTarget && + textSource.equals(tabData.previousTextSource) + ) { + return; + } - const currentSelection = document.defaultView.getSelection(); - if (currentSelection.toString() !== '' && currentSelection.toString() !== tabData.selText) { - return; - } - textClone.select(); - tabData.selText = textClone.text(); - }, tabData); - }; + let charCode = textSource.range.startContainer.textContent.charCodeAt( + textSource.range.startOffset + ); + if ( + isNaN(charCode) || + (charCode !== 0x25cb && + (charCode < 0x3001 || charCode > 0x30ff) && + (charCode < 0x3400 || charCode > 0x9fff) && + (charCode < 0xf900 || charCode > 0xfaff) && + (charCode < 0xff10 || charCode > 0xff9d)) + ) { + this.clear(); + return -2; + } - getSentenceStuff(rangeOffset: number, sentence: string): {sentence: string, sentenceStartPos: number, startOffset: number} { - let i = rangeOffset; + tabData.previousRangeOffset = textSource.range.startOffset; - let sentenceStartPos; - let sentenceEndPos; + tabData.previousTextSource = textSource; - // Find the last character of the sentence - while (i < sentence.length) { - if (sentence[i] === "。" || sentence[i] === "\n" || sentence[i] === "?" || sentence[i] === "!") { - sentenceEndPos = i; - break; - } - else if (i === (sentence.length - 1)) { - sentenceEndPos = i; - } + const textClone = textSource.clone(); + const sentenceClone = textSource.clone(); - i++; - } + textClone.setEndOffset(20); + const text = textClone.text(); - i = rangeOffset; + sentenceClone.setEndOffsetFromBeginningOfCurrentNode( + textSource.range.startContainer.textContent.length + 50 + ); + const sentence = sentenceClone.text(); - // Find the first character of the sentence - while (i >= 0) { - if (sentence[i] === "。" || sentence[i] === "\n" || sentence[i] === "?" || sentence[i] === "!") { - sentenceStartPos = i + 1; - break; - } - else if (i === 0) { - sentenceStartPos = i; - } + this.showFromText( + text, + sentence, + textSource.range.startOffset, + () => { + textClone.setEndOffset(this.word.length); - i--; + const currentSelection = document.defaultView.getSelection(); + if ( + currentSelection.toString() !== "" && + currentSelection.toString() !== tabData.selText + ) { + return; } + textClone.select(); + tabData.selText = textClone.text(); + }, + tabData + ); + } + + getSentenceStuff( + rangeOffset: number, + sentence: string + ): { sentence: string; sentenceStartPos: number; startOffset: number } { + let i = rangeOffset; + + let sentenceStartPos; + let sentenceEndPos; + + // Find the last character of the sentence + while (i < sentence.length) { + if ( + sentence[i] === "。" || + sentence[i] === "\n" || + sentence[i] === "?" || + sentence[i] === "!" + ) { + sentenceEndPos = i; + break; + } else if (i === sentence.length - 1) { + sentenceEndPos = i; + } + + i++; + } - // Extract the sentence - sentence = sentence.substring(sentenceStartPos, sentenceEndPos + 1); - - let startingWhitespaceMatch = sentence.match(/^\s+/); + i = rangeOffset; + + // Find the first character of the sentence + while (i >= 0) { + if ( + sentence[i] === "。" || + sentence[i] === "\n" || + sentence[i] === "?" || + sentence[i] === "!" + ) { + sentenceStartPos = i + 1; + break; + } else if (i === 0) { + sentenceStartPos = i; + } + + i--; + } - // Strip out control characters - sentence = sentence.replace(/[\n\r\t]/g, ''); + // Extract the sentence + sentence = sentence.substring(sentenceStartPos, sentenceEndPos + 1); - let startOffset = 0; + let startingWhitespaceMatch = sentence.match(/^\s+/); - // Adjust offset of selected word according to the number of - // whitespace chars at the beginning of the sentence - if (startingWhitespaceMatch) { - startOffset -= startingWhitespaceMatch[0].length; - } + // Strip out control characters + sentence = sentence.replace(/[\n\r\t]/g, ""); - // Trim - sentence = Rikai.trim(sentence); + let startOffset = 0; - return {sentence, sentenceStartPos, startOffset}; + // Adjust offset of selected word according to the number of + // whitespace chars at the beginning of the sentence + if (startingWhitespaceMatch) { + startOffset -= startingWhitespaceMatch[0].length; } - async showFromText(text: string, sentence: string, rangeOffset: number, highlightFunction, tabData: tabData): Promise { - const sentenceStuff = this.getSentenceStuff(rangeOffset, sentence); - sentence = sentenceStuff.sentence; + // Trim + sentence = Rikai.trim(sentence); - this.sentence = sentence; + return { sentence, sentenceStartPos, startOffset }; + } - if (text.length === 0) { - this.clear(); - return; - } + async showFromText( + text: string, + sentence: string, + rangeOffset: number, + highlightFunction, + tabData: tabData + ): Promise { + const sentenceStuff = this.getSentenceStuff(rangeOffset, sentence); + sentence = sentenceStuff.sentence; - let entry = await this.sendRequest('wordSearch', text); - if (!entry) { - this.clear(); - return; - } + this.sentence = sentence; - if (!entry.matchLen) entry.matchLen = 1; - this.lastFound = entry; - this.word = text.substr(0, entry.matchLen); + if (text.length === 0) { + this.clear(); + return; + } - const wordPositionInString = rangeOffset - 1; - this.sentenceWithBlank = sentence.substr(0, wordPositionInString) + "___" - + sentence.substr(wordPositionInString + entry.matchLen, sentence.length); + let entry = ( + await this.sendRequest("wordSearch", text) + ); - // @TODO: Add config check for "shouldHighlight" - // const shouldHighlight = (!('form' in tabData.previousTarget)); - const shouldHighlight = true; - if (shouldHighlight) { - highlightFunction(entry); - } + if (!entry) { + this.clear(); + return; + } - // @TODO: Add audio playing - // @TODO: Add checks for super sticky + if (!entry.matchLen) entry.matchLen = 1; + this.lastFound = entry; + this.selectedEntry = 0; + if (isDictionaryResult(entry)) { + this.totalEntries = entry.data.length; + } - //Sanseido mode - if (this.sanseidoMode) { - this.sanseidoFallback = 0; - return this.lookupSanseido(); - } + this.word = text.substr(0, entry.matchLen); - if (this.epwingMode) { - let epwingFound = await this.lookupEpwing(); - if (epwingFound === true) { - return; - } - } + const wordPositionInString = rangeOffset - 1; + this.sentenceWithBlank = + sentence.substr(0, wordPositionInString) + + "___" + + sentence.substr(wordPositionInString + entry.matchLen, sentence.length); - this.showPopup(await this.makeHTML(entry), tabData.previousTarget, tabData.pos); + // @TODO: Add config check for "shouldHighlight" + // const shouldHighlight = (!('form' in tabData.previousTarget)); + const shouldHighlight = true; + if (shouldHighlight) { + highlightFunction(entry); } - // Extract the first search term from the highlighted word. - // Returns search term string or null on error. - // forceGetReading - true = force this routine to return the reading of the word - extractSearchTerm (forceGetReading: boolean = false): string { - if (!this.lastFound) { - return null; - } + // @TODO: Add audio playing + // @TODO: Add checks for super sticky - // Get the currently hilited entry - let highlightedEntry = this.lastFound; + //Sanseido mode + if (this.sanseidoMode) { + this.sanseidoFallback = 0; + return this.lookupSanseido(); + } - let searchTerm = ""; + if (this.epwingMode) { + let epwingFound = await this.lookupEpwing(); + if (epwingFound === true) { + return; + } + } - // Get the search term to use - if (isKanjiResult(highlightedEntry)) { - // A single kanji was selected + this.showPopup( + await this.makeHTML(entry), + tabData.previousTarget, + tabData.pos + ); + } + + // Extract the first search term from the highlighted word. + // Returns search term string or null on error. + // forceGetReading - true = force this routine to return the reading of the word + extractSearchTerm(forceGetReading: boolean = false): string { + if (!this.lastFound) { + return null; + } - searchTerm = highlightedEntry.kanji; - } else if (highlightedEntry && highlightedEntry.data[0]) { - // An entire word was selected + // Get the currently hilited entry + let highlightedEntry = this.lastFound; - const entryData = highlightedEntry.data[0][0].match(/^(.+?)\s+(?:\[(.*?)\])?\s*\/(.+)\//); + let searchTerm = ""; - // Example of what data[0][0] looks like (linebreak added by me): - // 乃 [の] /(prt,uk) indicates possessive/verb and adjective nominalizer (nominaliser)/substituting - // for "ga" in subordinate phrases/indicates a confident conclusion/emotional emphasis (sentence end) (fem)/(P)/ - // - // Extract needed data from the hilited entry - // entryData[0] = kanji/kana + kana + definition - // entryData[1] = kanji (or kana if no kanji) - // entryData[2] = kana (null if no kanji) - // entryData[3] = definition + // Get the search term to use + if (isKanjiResult(highlightedEntry)) { + // A single kanji was selected - if (forceGetReading) { - if (entryData[2]) { - searchTerm = entryData[2]; - } - else { - searchTerm = entryData[1]; - } - } - else { - // If the highlighted word is kana, don't use the kanji. - // Example1: if の is highlighted, use の rather than the kanji equivalent (乃) - // Example2: if された is highlighted, use される rather then 為れる - if (entryData[2] && !Utils.containsKanji(this.word)) { - searchTerm = entryData[2]; - } - else { - searchTerm = entryData[1]; - } - } - } - else { - return null; - } + searchTerm = highlightedEntry.kanji; + } else if (highlightedEntry && highlightedEntry.data[0]) { + // An entire word was selected - return searchTerm; + const entryData = highlightedEntry.data[0][0].match( + /^(.+?)\s+(?:\[(.*?)\])?\s*\/(.+)\// + ); - } - - async lookupSanseido() { - let searchTerm; - const {tabData} = this; - - // Determine if we should use the kanji form or kana form when looking up the word - if (this.sanseidoFallback === 0) { - // Get this kanji form if it exists - searchTerm = this.extractSearchTerm(false); - } - else if (this.sanseidoFallback === 1) { - // Get the reading - searchTerm = this.extractSearchTerm(true); - } + // Example of what data[0][0] looks like (linebreak added by me): + // 乃 [の] /(prt,uk) indicates possessive/verb and adjective nominalizer (nominaliser)/substituting + // for "ga" in subordinate phrases/indicates a confident conclusion/emotional emphasis (sentence end) (fem)/(P)/ + // + // Extract needed data from the hilited entry + // entryData[0] = kanji/kana + kana + definition + // entryData[1] = kanji (or kana if no kanji) + // entryData[2] = kana (null if no kanji) + // entryData[3] = definition - if (!searchTerm) { - return; + if (forceGetReading) { + if (entryData[2]) { + searchTerm = entryData[2]; + } else { + searchTerm = entryData[1]; } - - // If the kanji form was requested but it returned the kana form anyway, then update the state - if ((this.sanseidoFallback === 0) && !Utils.containsKanji(searchTerm)) { - this.sanseidoFallback = 1; + } else { + // If the highlighted word is kana, don't use the kanji. + // Example1: if の is highlighted, use の rather than the kanji equivalent (乃) + // Example2: if された is highlighted, use される rather then 為れる + if (entryData[2] && !Utils.containsKanji(this.word)) { + searchTerm = entryData[2]; + } else { + searchTerm = entryData[1]; } - - // Show the loading message to the screen while we fetch the entry page - this.showPopup("Loading...", tabData.previousTarget, tabData.pos); - - this.abortController.abort(); - this.abortController = new AbortController(); - const { signal } = this.abortController; - return fetch(`https://www.sanseido.biz/User/Dic/Index.aspx?TWords=${searchTerm}&st=0&DailyJJ=checkbox`, { signal }) - .then(response => response.text()) - .then(response => this.parseAndDisplaySanseido(response)); + } + } else { + return null; } - enableEpwing(): void { - if (!this.config.epwingDictionaries.length) { - this.showPopup('No Epwing Dictionary Set'); + return searchTerm; + } - browser.storage.local.set({epwingMode: false}); - return; - } + async lookupSanseido() { + let searchTerm; + const { tabData } = this; - this.epwingTotalHits = 0; - this.epwingCurrentHit = 0; - this.epwingPreviousHit = 0; - this.epwingResults = []; + // Determine if we should use the kanji form or kana form when looking up the word + if (this.sanseidoFallback === 0) { + // Get this kanji form if it exists + searchTerm = this.extractSearchTerm(false); + } else if (this.sanseidoFallback === 1) { + // Get the reading + searchTerm = this.extractSearchTerm(true); } - async disableEpwing(): Promise { - this.epwingMode = false; + if (!searchTerm) { + return; } - async lookupEpwing(): Promise { - const searchTerm = this.extractSearchTerm(false); - - if (!searchTerm) { - return false; - } - - let epwingText: string = await this.sendRequest('getEpwingDefinition', searchTerm); - - if (epwingText === "No results found") { - return false; - } + // If the kanji form was requested but it returned the kana form anyway, then update the state + if (this.sanseidoFallback === 0 && !Utils.containsKanji(searchTerm)) { + this.sanseidoFallback = 1; + } - const entryFields = epwingText.split(/{ENTRY: \d+}\n/); - let entryList = []; + // Show the loading message to the screen while we fetch the entry page + this.showPopup("Loading...", tabData.previousTarget, tabData.pos); + + this.abortController.abort(); + this.abortController = new AbortController(); + const { signal } = this.abortController; + return fetch( + `https://www.sanseido.biz/User/Dic/Index.aspx?TWords=${searchTerm}&st=0&DailyJJ=checkbox`, + { signal } + ) + .then((response) => response.text()) + .then((response) => this.parseAndDisplaySanseido(response)); + } + + enableEpwing(): void { + if (!this.config.epwingDictionaries.length) { + this.showPopup("No Epwing Dictionary Set"); + + browser.storage.local.set({ epwingMode: false }); + return; + } - for (let i = 0; i < entryFields.length; ++i) { - const curEntry = entryFields[i]; + this.epwingTotalHits = 0; + this.epwingCurrentHit = 0; + this.epwingPreviousHit = 0; + this.epwingResults = []; + } - if (curEntry.length > 0) { - let isDuplicate = false; + async disableEpwing(): Promise { + this.epwingMode = false; + } - for (let j = 0; j < entryList.length; ++j) { - if (curEntry === entryList[j]) { - isDuplicate = true; - break; - } - } + async lookupEpwing(): Promise { + const searchTerm = this.extractSearchTerm(false); - if (!isDuplicate) { - entryList.push(entryFields[i]); - } - - // If user wants to limit number of entries, check to see if we have enough - // if (rcxConfig.epwingshowallentries && (entryList.length >= rcxConfig.epwingmaxentries)) { - // break; - // } - } - } + if (!searchTerm) { + return false; + } - this.epwingCurrentHit = 0; - this.epwingPreviousHit = 0; - this.epwingTotalHits = entryList.length; - this.epwingResults = entryList; + let epwingText: string = await this.sendRequest( + "getEpwingDefinition", + searchTerm + ); - this.showEpwingDefinition(); - return true; + if (epwingText === "No results found") { + return false; } - showNextEpwingEntry(): boolean { - if (!this.epwingMode || this.epwingTotalHits < 2) { - return false; - } + const entryFields = epwingText.split(/{ENTRY: \d+}\n/); + let entryList = []; - this.epwingCurrentHit++; - if (this.epwingCurrentHit > this.epwingTotalHits - 1) { - this.epwingCurrentHit = 0; - } + for (let i = 0; i < entryFields.length; ++i) { + const curEntry = entryFields[i]; - this.showEpwingDefinition(); - return true; - } + if (curEntry.length > 0) { + let isDuplicate = false; - showPreviousEpwingEntry(): boolean { - if (!this.epwingMode || this.epwingTotalHits < 2) { - return false; + for (let j = 0; j < entryList.length; ++j) { + if (curEntry === entryList[j]) { + isDuplicate = true; + break; + } } - this.epwingCurrentHit--; - if (this.epwingCurrentHit < 0) { - this.epwingCurrentHit = this.epwingTotalHits - 1; + if (!isDuplicate) { + entryList.push(entryFields[i]); } - this.showEpwingDefinition(); - return false; + // If user wants to limit number of entries, check to see if we have enough + // if (rcxConfig.epwingshowallentries && (entryList.length >= rcxConfig.epwingmaxentries)) { + // break; + // } + } } - async showEpwingDefinition() { - if (!isDictionaryResult(this.lastFound)) return; - - const {epwingCurrentHit, epwingResults, tabData} = this; + this.epwingCurrentHit = 0; + this.epwingPreviousHit = 0; + this.epwingTotalHits = entryList.length; + this.epwingResults = entryList; - const epwingDefinitionText = epwingResults[epwingCurrentHit]; - const entry = await this.formatEpwingEntry(epwingDefinitionText, true, true); + this.showEpwingDefinition(); + return true; + } - this.lastFound.data[0][0] = this.lastFound.data[0][0] - .replace(/\/.+\//g, "/" + await this.formatEpwingEntry(epwingDefinitionText) + "/"); - - this.showPopup(entry, tabData.previousTarget, tabData.pos); + showNextEpwingEntry(): boolean { + if (!this.epwingMode || this.epwingTotalHits < 2) { + return false; } - async formatEpwingEntry(entryText, showHeader?: boolean, showEntryNumber?: boolean): Promise { + this.epwingCurrentHit++; + if (this.epwingCurrentHit > this.epwingTotalHits - 1) { + this.epwingCurrentHit = 0; + } - //TODO: Add removing user inputted regex - //TODO: Add "Header" (Color, pitch and so on) - if (showHeader) { - //TODO: Add Frequency Information + this.showEpwingDefinition(); + return true; + } - let entryNumber = ""; - if (showEntryNumber) { - entryNumber = `(${this.epwingCurrentHit + 1} / ${this.epwingTotalHits})`; - } + showPreviousEpwingEntry(): boolean { + if (!this.epwingMode || this.epwingTotalHits < 2) { + return false; + } - //TODO: Add known word indicator - //TODO: Add Showing Conjugation - //TODO: Add showing dictionary title and number - entryText = `${entryNumber}
${entryText}`; - } + this.epwingCurrentHit--; + if (this.epwingCurrentHit < 0) { + this.epwingCurrentHit = this.epwingTotalHits - 1; + } - //TODO: Add Max Lines + this.showEpwingDefinition(); + return false; + } + + async showEpwingDefinition() { + if (!isDictionaryResult(this.lastFound)) return; + + const { epwingCurrentHit, epwingResults, tabData } = this; + + const epwingDefinitionText = epwingResults[epwingCurrentHit]; + const entry = await this.formatEpwingEntry( + epwingDefinitionText, + true, + true + ); + + this.lastFound.data[0][0] = this.lastFound.data[0][0].replace( + /\/.+\//g, + "/" + (await this.formatEpwingEntry(epwingDefinitionText)) + "/" + ); + + this.showPopup(entry, tabData.previousTarget, tabData.pos); + } + + async formatEpwingEntry( + entryText, + showHeader?: boolean, + showEntryNumber?: boolean + ): Promise { + //TODO: Add removing user inputted regex + //TODO: Add "Header" (Color, pitch and so on) + if (showHeader) { + //TODO: Add Frequency Information + + let entryNumber = ""; + if (showEntryNumber) { + entryNumber = `(${this.epwingCurrentHit + 1} / ${ + this.epwingTotalHits + })`; + } + + //TODO: Add known word indicator + //TODO: Add Showing Conjugation + //TODO: Add showing dictionary title and number + entryText = `${entryNumber}
${entryText}`; + } - //TODO: Add "epwingStripNewLines" config - if (false) { - entryText = entryText.replace(/\n/g, " "); - } else { - entryText = entryText.replace(/\n/g, "
"); - } + //TODO: Add Max Lines - return entryText; + //TODO: Add "epwingStripNewLines" config + if (false) { + entryText = entryText.replace(/\n/g, " "); + } else { + entryText = entryText.replace(/\n/g, "
"); } - async parseAndDisplaySanseido(response) { - if (!isDictionaryResult(this.lastFound)) return; - - // Create DOM tree from entry page text - // var domPars = rcxMain.htmlParser(entryPageText); - const {tabData} = this; - const parser = new DOMParser(); - const document = parser.parseFromString(response, 'text/html'); - let domPars = document.body; - - // Get list of div elements - const divList = domPars.getElementsByTagName("div"); - - // Will be set if the entry page actually contains a definition - let entryFound = false; - - // Find the div that contains the definition - for (let divIdx = 0; divIdx < divList.length; divIdx++) { - // Did we reach the div the contains the definition? - if (divList[divIdx].className === "NetDicBody") { - entryFound = true; - - // rcxDebug.echo("Raw definition: " + divList[divIdx].innerHTML); - - // Will contain the final parsed definition text - let defText = ""; - - // A list of all child nodes in the div - const childList = divList[divIdx].childNodes; - - // Set when we need to end the parse - let defFinished = false; - - // Extract the definition from the div's child nodes - for (let nodeIdx = 0; nodeIdx < childList.length && !defFinished; nodeIdx++) { - // Is this a b element? - if (childList[nodeIdx].nodeName.toLowerCase() === "b") { - // How many child nodes does this b element have? - if (childList[nodeIdx].childNodes.length === 1) { - // Check for definition number: [1], [2], ... and add to def - const defNum = childList[nodeIdx].childNodes[0].nodeValue.match(/[([1234567890]+)]/); - - if (defNum) { - defText += "
" + RegExp.$1; - } - else { - // Check for sub-definition number: (1), (2), ... and add to def - const subDefNum = childList[nodeIdx].childNodes[0].nodeValue.match(/(([1234567890]+))/); - - if (subDefNum) { - // Convert sub def number to circled number - defText += Utils.convertIntegerToCircledNumStr(Utils.convertJapNumToInteger(RegExp.$1)); - } - } - } - else // This b element has more than one child node - { - // Check the b children for any spans. A span marks the start - // of non-definition portion, so end the parse. - for (let bIdx = 0; bIdx < childList[nodeIdx].childNodes.length; bIdx++) { - if (childList[nodeIdx].childNodes[bIdx].nodeName.toLowerCase() === "span") { - defFinished = true; - } - } - } - } - - // Have we finished parsing the text? - if (defFinished) { - break; - } - - // If the current element is text, add it to the definition - if ((childList[nodeIdx].nodeName.toLowerCase() === "#text") - && (Rikai.trim(childList[nodeIdx].nodeValue) !== "")) { - defText += childList[nodeIdx].nodeValue; - } - } + return entryText; + } + + async parseAndDisplaySanseido(response) { + if (!isDictionaryResult(this.lastFound)) return; + + // Create DOM tree from entry page text + // var domPars = rcxMain.htmlParser(entryPageText); + const { tabData } = this; + const parser = new DOMParser(); + const document = parser.parseFromString(response, "text/html"); + let domPars = document.body; + + // Get list of div elements + const divList = domPars.getElementsByTagName("div"); + + // Will be set if the entry page actually contains a definition + let entryFound = false; + + // Find the div that contains the definition + for (let divIdx = 0; divIdx < divList.length; divIdx++) { + // Did we reach the div the contains the definition? + if (divList[divIdx].className === "NetDicBody") { + entryFound = true; + + // rcxDebug.echo("Raw definition: " + divList[divIdx].innerHTML); + + // Will contain the final parsed definition text + let defText = ""; + + // A list of all child nodes in the div + const childList = divList[divIdx].childNodes; + + // Set when we need to end the parse + let defFinished = false; + + // Extract the definition from the div's child nodes + for ( + let nodeIdx = 0; + nodeIdx < childList.length && !defFinished; + nodeIdx++ + ) { + // Is this a b element? + if (childList[nodeIdx].nodeName.toLowerCase() === "b") { + // How many child nodes does this b element have? + if (childList[nodeIdx].childNodes.length === 1) { + // Check for definition number: [1], [2], ... and add to def + const defNum = childList[nodeIdx].childNodes[0].nodeValue.match( + /[([1234567890]+)]/ + ); + + if (defNum) { + defText += "
" + RegExp.$1; + } else { + // Check for sub-definition number: (1), (2), ... and add to def + const subDefNum = childList[ + nodeIdx + ].childNodes[0].nodeValue.match( + /(([1234567890]+))/ + ); - // If the definition is blank (search ばかり for example), fallback - if (defText.length === 0) { - // Set to a state that will ensure fallback to default JMDICT popup - this.sanseidoFallback = 1; - entryFound = false; - break; + if (subDefNum) { + // Convert sub def number to circled number + defText += Utils.convertIntegerToCircledNumStr( + Utils.convertJapNumToInteger(RegExp.$1) + ); } - - var jdicCode = ""; - - // Get the part-of-speech and other JDIC codes - this.lastFound.data[0][0].match(/\/(\(.+?\) ).+\//); - - if (RegExp.$1) { - jdicCode = RegExp.$1; + } + } // This b element has more than one child node + else { + // Check the b children for any spans. A span marks the start + // of non-definition portion, so end the parse. + for ( + let bIdx = 0; + bIdx < childList[nodeIdx].childNodes.length; + bIdx++ + ) { + if ( + childList[nodeIdx].childNodes[bIdx].nodeName.toLowerCase() === + "span" + ) { + defFinished = true; } - - // Replace the definition with the one we parsed from sanseido - this.lastFound.data[0][0] = this.lastFound.data[0][0] - .replace(/\/.+\//g, "/" + jdicCode + defText + "/"); - - // Remove all words except for the one we just looked up - this.lastFound.data = [this.lastFound.data[0]]; - - // Prevent the "..." from being displayed at the end of the popup text - this.lastFound.more = false; - - // Show the definition - this.showPopup(await this.makeHTML(this.lastFound), - tabData.previousTarget, tabData.pos); - - // Entry found, stop looking - break; + } } - + } + + // Have we finished parsing the text? + if (defFinished) { + break; + } + + // If the current element is text, add it to the definition + if ( + childList[nodeIdx].nodeName.toLowerCase() === "#text" && + Rikai.trim(childList[nodeIdx].nodeValue) !== "" + ) { + defText += childList[nodeIdx].nodeValue; + } } - // If the entry was not on sanseido, either try to lookup the kana form of the word - // or display default JMDICT popup - if (!entryFound) { - this.sanseidoFallback++; - - if (this.sanseidoFallback < 2) { - // Set a timer to lookup again using the kana form of the word instead - window.setTimeout - ( - () => { - this.lookupSanseido(); - }, 10 - ); - } - else { - // Fallback to the default non-sanseido dictionary that comes with rikaichan (JMDICT) - this.showPopup(await this.makeHTML(this.lastFound), tabData.previousTarget, tabData.pos); - } + // If the definition is blank (search ばかり for example), fallback + if (defText.length === 0) { + // Set to a state that will ensure fallback to default JMDICT popup + this.sanseidoFallback = 1; + entryFound = false; + break; } - } - - static trim(text: string): string { - return text.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); - } - - clear(): void { - this.abortController.abort(); - this.abortController = new AbortController(); - setTimeout(() => { - docImposterDestroy(); - }, 500); - this.clearPopup(); - this.clearHighlight(); - } - clearPopup(): void { - this.getPopup().style.display = 'none'; - } + var jdicCode = ""; - clearHighlight(): void { - const tabData = this.tabData; + // Get the part-of-speech and other JDIC codes + this.lastFound.data[0][0].match(/\/(\(.+?\) ).+\//); - const selection = document.defaultView.getSelection(); - //Changed !selection.isCollapsed to selection.toString() !== '' because of Chrome issue with input text boxes - if (selection && selection.toString() !== '' && tabData.selText !== selection.toString()) { - return; + if (RegExp.$1) { + jdicCode = RegExp.$1; } - if (tabData.previousTextSource) { - tabData.previousTextSource.deselect(); - tabData.previousTextSource = null; - return; - } - } + // Replace the definition with the one we parsed from sanseido + this.lastFound.data[0][0] = this.lastFound.data[0][0].replace( + /\/.+\//g, + "/" + jdicCode + defText + "/" + ); - async sendRequest(type: string, content: any = ''): Promise { - return browser.runtime.sendMessage({type, content}).then(response => { - if (typeof response === 'undefined') { - this.showPopup('If you have the options page for RikaiRebuilt, please close that. Word search' + - ' doesn\'t work properly when the options tab is open'); - return -1; - } + // Remove all words except for the one we just looked up + this.lastFound.data = [this.lastFound.data[0]]; - return response.response; - }); - }; + // Prevent the "..." from being displayed at the end of the popup text + this.lastFound.more = false; - createPopup(): void { - if (this.hasPopup()) return; + // Show the definition + this.showPopup( + await this.makeHTML(this.lastFound), + tabData.previousTarget, + tabData.pos + ); - const popup = this.document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); - popup.setAttribute('id', this.popupId); - popup.setAttribute('style', 'display: none;'); - popup.setAttribute('class', `rikai-${this.config.theme}`); - document.documentElement.appendChild(popup); + // Entry found, stop looking + break; + } } - getPopup(): HTMLElement { - if (!this.hasPopup()) this.createPopup(); - - return this.document.getElementById(this.popupId); + // If the entry was not on sanseido, either try to lookup the kana form of the word + // or display default JMDICT popup + if (!entryFound) { + this.sanseidoFallback++; + + if (this.sanseidoFallback < 2) { + // Set a timer to lookup again using the kana form of the word instead + window.setTimeout(() => { + this.lookupSanseido(); + }, 10); + } else { + // Fallback to the default non-sanseido dictionary that comes with rikaichan (JMDICT) + this.showPopup( + await this.makeHTML(this.lastFound), + tabData.previousTarget, + tabData.pos + ); + } } - - hasPopup(): HTMLElement { - return this.document.getElementById(this.popupId); + } + + static trim(text: string): string { + return text.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); + } + + clear(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + setTimeout(() => { + docImposterDestroy(); + }, 500); + this.clearPopup(); + this.clearHighlight(); + } + + clearPopup(): void { + this.getPopup().style.display = "none"; + } + + clearHighlight(): void { + const tabData = this.tabData; + + const selection = document.defaultView.getSelection(); + //Changed !selection.isCollapsed to selection.toString() !== '' because of Chrome issue with input text boxes + if ( + selection && + selection.toString() !== "" && + tabData.selText !== selection.toString() + ) { + return; } - showPopup(textToShow: string, previousTarget?: HTMLElement, position?: position): void { - let {pageX, pageY, clientX, clientY} = position || {pageX: 10, pageY: 10, clientX: 10, clientY: 10}; - const popup = this.getPopup(); - - popup.innerHTML = `\n${textToShow}`; - popup.style.display = 'block'; - popup.style.maxWidth = '600px'; - - if (previousTarget && (typeof previousTarget !== 'undefined') - && previousTarget.parentNode && (typeof previousTarget.parentNode !== 'undefined')) { - - let width = popup.offsetWidth; - let height = popup.offsetHeight; - - popup.style.top = '-1000px'; - popup.style.left = '0px'; - popup.style.display = ''; - - //TODO: Add alt-views here - //TODO: Stuff for box object and zoom? - //TODO: Check for Option Element? What? - - if (clientX + width > window.innerWidth - 20) { - pageX = window.innerWidth - width - 20; - if (pageX < 0) pageX = 0; - } - - let v = 25; - if (previousTarget.title && previousTarget.title !== '') v += 20; - - if (clientY + v + height > window.innerHeight) { - let t = pageY - height - 30; - if (t >= 0) pageY = t; - } else { - pageY += v; - } - } - - popup.style.left = pageX + 'px'; - popup.style.top = pageY + 'px'; + if (tabData.previousTextSource) { + tabData.previousTextSource.deselect(); + tabData.previousTextSource = null; + return; + } + } + + async sendRequest(type: string, content: any = ""): Promise { + return browser.runtime.sendMessage({ type, content }).then((response) => { + if (typeof response === "undefined") { + this.showPopup( + "If you have the options page for RikaiRebuilt, please close that. Word search" + + " doesn't work properly when the options tab is open" + ); + return -1; + } + + return response.response; + }); + } + + createPopup(): void { + if (this.hasPopup()) return; + + const popup = this.document.createElementNS( + "http://www.w3.org/1999/xhtml", + "div" + ); + popup.setAttribute("id", this.popupId); + popup.setAttribute("style", "display: none;"); + popup.setAttribute("class", `rikai-${this.config.theme}`); + document.documentElement.appendChild(popup); + } + + getPopup(): HTMLElement { + if (!this.hasPopup()) this.createPopup(); + + return this.document.getElementById(this.popupId); + } + + hasPopup(): HTMLElement { + return this.document.getElementById(this.popupId); + } + + async renderPopup() { + return this.showPopup( + await this.makeHTML(this.lastFound), + this.tabData.previousTarget, + this.tabData.pos, + true + ); + } + + showPopup( + textToShow: string, + previousTarget?: HTMLElement, + position?: position, + rerender = false + ): void { + let { pageX, pageY, clientX, clientY } = position || { + pageX: 10, + pageY: 10, + clientX: 10, + clientY: 10, + }; + const popup = this.getPopup(); + + popup.innerHTML = `\n${textToShow}`; + popup.style.display = "block"; + popup.style.maxWidth = "600px"; + + if ( + previousTarget && + typeof previousTarget !== "undefined" && + previousTarget.parentNode && + typeof previousTarget.parentNode !== "undefined" + ) { + let width = popup.offsetWidth; + let height = popup.offsetHeight; + + popup.style.top = "-1000px"; + popup.style.left = "0px"; + popup.style.display = ""; + + //TODO: Add alt-views here + //TODO: Stuff for box object and zoom? + //TODO: Check for Option Element? What? + + if (clientX + width > window.innerWidth - 20) { + pageX = window.innerWidth - width - 20; + if (pageX < 0) pageX = 0; + } + + let v = 25; + if (previousTarget.title && previousTarget.title !== "") v += 20; + + if (clientY + v + height > window.innerHeight) { + let t = pageY - height - 30; + if (t >= 0) pageY = t; + } else { + pageY += v; + } } - async makeHTML(entries: SearchResults) { - let k; - let entry; - let returnValue = []; - let c, s; - let i, j; - - if (entries == null) return ''; - - if (isKanjiResult(entries)) { - let yomi; - let box; - let nums; - - yomi = entries.onkun.replace(/\.([^\u3001]+)/g, '$1'); - if (entries.nanori.length) { - yomi += `
\u540D\u4E57\u308A ${entries.nanori}`; - } - if (entries.bushumei.length) { - yomi += `
\u90E8\u9996\u540D ${entries.bushumei}`; - } + if (rerender) { + pageX = this.lastPopupPosition.left; + pageY = this.lastPopupPosition.top; + } - let kanjiUse = entries.misc['G']; - switch (kanjiUse) { - case 8: - kanjiUse = 'general
use'; - break; - case 9: - kanjiUse = 'name
use'; - break; - default: - kanjiUse = isNaN(kanjiUse) ? '-' : ('grade
' + kanjiUse); - break; - } - box = ` - + popup.style.left = pageX + "px"; + popup.style.top = pageY + "px"; + + this.lastPopupPosition = { left: pageX, top: pageY }; + } + + async makeHTML(entries: SearchResults) { + let k; + let entry; + let returnValue = []; + let c, s; + let i, j; + + if (entries == null) return ""; + + if (isKanjiResult(entries)) { + let yomi; + let box; + let nums; + + yomi = entries.onkun.replace( + /\.([^\u3001]+)/g, + '$1' + ); + if (entries.nanori.length) { + yomi += `
\u540D\u4E57\u308A ${entries.nanori}`; + } + if (entries.bushumei.length) { + yomi += `
\u90E8\u9996\u540D ${entries.bushumei}`; + } + + let kanjiUse = entries.misc["G"]; + switch (kanjiUse) { + case 8: + kanjiUse = "general
use"; + break; + case 9: + kanjiUse = "name
use"; + break; + default: + kanjiUse = isNaN(kanjiUse) ? "-" : "grade
" + kanjiUse; + break; + } + box = `
radical
${entries.radical.charAt(0)} ${entries.radicalNumber}
+ - - + +
radical
${entries.radical.charAt(0)} ${ + entries.radicalNumber + }
${kanjiUse}
freq
${entries.misc['F'] ? entries.misc['F'] : '-'}
strokes
${entries.misc['S']}
freq
${ + entries.misc["F"] ? entries.misc["F"] : "-" + }
strokes
${entries.misc["S"]}
`; - if (this.config.showKanjiComponents) { - k = entries.radical.split('\t'); - box += '' + - '' + - '' + - ''; - j = 1; - for (const radical of entries.radicals) { - k = radical.split('\t'); - c = ' class="k-bbox-' + (j ^= 1); - box += '' + k[0] + '' + - '' + k[2] + '' + - '' + k[3] + ''; - } - box += '
' + k[0] + '' + k[2] + '' + k[3] + '
'; - } - - nums = ''; - j = 0; - - const numList = { - 'H': ['showKanjiHalpern', 'Halpern'], - 'L': ['showKanjiHeisig', 'Heisig'], - 'E': ['showKanjiHenshall', 'Henshall'], - 'DK': ['showKanjiLearnersDictionary', 'Kanji Learners Dictionary'], - 'N': ['showKanjiNelson', 'Nelson'], - 'V': ['showKanjiNewNelson', 'New Nelson'], - 'Y': ['showKanjiPinYin', 'PinYin'], - 'P': ['showKanjiSkipPattern', 'Skip Pattern'], - 'IN': ['showKanjiTurtleAndKana', 'Turtle Kanji & Kana'], - 'I': ['showKanjiTurtleDictionary', 'Turtle Kanji Dictionary'], - 'U': ['showKanjiUnicode', 'Unicode'] - }; - - for (const i in numList) { - const configName = numList[i][0]; - const displayName = numList[i][1]; - - if (this.config[configName]) { - s = entries.misc[i]; // The number - c = ' class="k-mix-td' + (j ^= 1) + '"'; - - if (configName === "showKanjiHeisig") { - const revTkLink = 'http://kanji.koohii.com/study/kanji/' + entries.kanji; - nums += '' + 'Heisig' + '' + '' - + (s ? s : '-') + '' + ''; - } - else { - nums += '' + displayName + '' + (s ? s : '-') + ''; - } - } - } - if (nums.length) nums = '' + nums + '
'; - - returnValue.push('
'); - returnValue.push(box); - returnValue.push('' + entries.kanji + '
'); - if (!this.config.hideDefinitions) returnValue.push('
' + entries.eigo + '
'); - returnValue.push('
' + yomi + '
'); - returnValue.push('
' + nums + '
'); - return returnValue.join(''); + if (this.config.showKanjiComponents) { + k = entries.radical.split("\t"); + box += + '' + + '" + + '" + + '"; + j = 1; + for (const radical of entries.radicals) { + k = radical.split("\t"); + c = ' class="k-bbox-' + (j ^= 1); + box += + "' + + k[0] + + "" + + "' + + k[2] + + "" + + "' + + k[3] + + ""; } - - let translationText = ''; - - if (entries.names) { - return transformNameEntriesToHtml(entries, this.config); - // return this.transformNamesToHtml(entries); - } - - if (entries.title) { - returnValue.push(`
${entries.title}
`); + box += "
' + + k[0] + + "' + + k[2] + + "' + + k[3] + + "
"; + } + + nums = ""; + j = 0; + + const numList = { + H: ["showKanjiHalpern", "Halpern"], + L: ["showKanjiHeisig", "Heisig"], + E: ["showKanjiHenshall", "Henshall"], + DK: ["showKanjiLearnersDictionary", "Kanji Learners Dictionary"], + N: ["showKanjiNelson", "Nelson"], + V: ["showKanjiNewNelson", "New Nelson"], + Y: ["showKanjiPinYin", "PinYin"], + P: ["showKanjiSkipPattern", "Skip Pattern"], + IN: ["showKanjiTurtleAndKana", "Turtle Kanji & Kana"], + I: ["showKanjiTurtleDictionary", "Turtle Kanji Dictionary"], + U: ["showKanjiUnicode", "Unicode"], + }; + + for (const i in numList) { + const configName = numList[i][0]; + const displayName = numList[i][1]; + + if (this.config[configName]) { + s = entries.misc[i]; // The number + c = ' class="k-mix-td' + (j ^= 1) + '"'; + + if (configName === "showKanjiHeisig") { + const revTkLink = + "http://kanji.koohii.com/study/kanji/" + entries.kanji; + nums += + "" + + "Heisig' + + "" + + "' + + (s ? s : "-") + + "" + + ""; + } else { + nums += + "" + + displayName + + "" + + (s ? s : "-") + + ""; + } } + } + if (nums.length) nums = '' + nums + "
"; + + returnValue.push('
'); + returnValue.push(box); + returnValue.push( + '' + entries.kanji + "
" + ); + if (!this.config.hideDefinitions) + returnValue.push('
' + entries.eigo + "
"); + returnValue.push('
' + yomi + "
"); + returnValue.push("
" + nums + "
"); + return `
${returnValue.join("")}
`; + } - for (i = 0; i < entries.data.length; ++i) { - let previousKanji = ''; - let kanaText = ''; - let previousDefinition = ''; + let translationText = ""; - entry = entries.data[i][0].match(/^(.+?)\s+(?:\[(.*?)\])?\s*\/([\S\s]+)\//); - let [ _, kanji, kana, definition ] = entry; - if (!entry) continue; + if (entries.names) { + return transformNameEntriesToHtml(entries, this.config); + } - if (previousDefinition !== definition) { - returnValue.push(translationText); - previousKanji = kanaText = ''; - } - else { - kanaText = translationText.length ? '
' : ''; - } + if (entries.title) { + returnValue.push(`
${entries.title}
`); + } - if (kana) { - if (previousKanji === kanji) kanaText = '\u3001 ' + kana + ''; - else kanaText += '' + kanji + ' ' + kana + ''; - previousKanji = kanji; - } else { - kanaText += '' + kanji + ''; - previousKanji = ''; - } - returnValue.push(kanaText); + type Definition = { + kanji?: string; + kana?: string; + conjugation?: string; + definitions: string[]; + }; + const transformed = entries.data.reduce( + (acc: Definition[], entry): Definition[] => { + const conjugation = entry[1]; + let [_, kanji, kana, definition] = entry[0].match( + /^(.+?)\s+(?:\[(.*?)\])?\s*\/([\S\s]+)\// + ); + + const existing = acc.find( + (def) => + def.kanji === kanji && + def.kana === kana && + def.conjugation === conjugation + ); + if (!existing) { + acc.push({ + kanji, + kana, + definitions: [definition], + conjugation, + }); + } else { + existing.definitions.push(definition); + } - //TODO: Add config usage here - // Add pitch accent right after the reading - if (this.config.showPitchAccent) { - const pitchAccent = await this.sendRequest('getPitch', { expression: kanji, reading: kana }); + return acc; + }, + [] + ); - if (pitchAccent && (pitchAccent.length > 0)) { - returnValue.push(' ' + pitchAccent + ''); - } - } + const definitionTransformer = (definition: string): string => { + if (this.config.hideDefinitions) return ""; - if (entries.data[i][1]) returnValue.push(' (' + entries.data[i][1] + ')'); + definition = definition.replace(/\//g, "; "); - // Add frequency - if (this.config.showFrequency) { - const freqExpression = kanji; - let freqReading = kana; + if (!this.config.showWordTypeIndicator) + definition = definition.replace(/^\([^)]+\)\s*/, ""); + if (!this.config.showPopularWordIndicator) + definition = definition.replace("; (P)", ""); - if (freqReading === null) { - freqReading = freqExpression; - } + definition = definition.replace(/\n/g, "
"); - const freq = await this.getFrequency(freqExpression, freqReading, i === 0); + if (!definition.length) return ""; - if (freq && freq.length > 0) { - const frequencyClass = Rikai.getFrequencyStyle(freq); - returnValue.push(' ' + freq + ''); - } - } + return `${definition}`; + }; - //TODO: Add config usage here - previousDefinition = definition; - if (this.config.hideDefinitions) { - translationText = '
'; - } else { - translationText = previousDefinition.replace(/\//g, '; '); - //TODO: Add config here - if (!this.config.showWordTypeIndicator) translationText = translationText.replace(/^\([^)]+\)\s*/, ''); - if (!this.config.showPopularWordIndicator) translationText = translationText.replace('; (P)', ''); - translationText = translationText.replace(/\n/g, '
'); - translationText = '
' + translationText + '
'; + const transformedEntries = await Promise.all( + transformed.map( + async ({ kana, kanji, definitions, conjugation }, index) => { + let title; + let pitch = ""; + const conjugationString = conjugation + ? `(${entries.data[i][1]})` + : ""; + const definitionString = definitions + .map(definitionTransformer) + .filter((def) => !!def.length) + .join("
"); + + let frequencyString = ""; + if (!kana) { + title = `${kanji}`; + } else { + title = `${kanji} ${kana}`; + } + + if (this.config.showPitchAccent) { + const pitchAccent = await this.sendRequest("getPitch", { + expression: kanji, + reading: kana, + }); + + if (pitchAccent && pitchAccent.length > 0) { + pitch = `${pitchAccent}`; } - } - returnValue.push(translationText); - if (entries.more) returnValue.push('...
'); + } - return returnValue.join(''); - } + if (this.config.showFrequency) { + const freq = await this.getFrequency(kanji, kana || kanji, i === 0); - static getFrequencyStyle(inFreqNum) { - let freqNum = inFreqNum.replace(/_r/g, ""); - - let freqStyle = 'w-freq-rare'; + if (freq?.length) { + const frequencyClass = Rikai.getFrequencyStyle(freq); + frequencyString = ` ${freq}`; + } + } - if (freqNum <= 5000) { - freqStyle = "w-freq-very-common"; - } - else if (freqNum <= 10000) { - freqStyle = "w-freq-common"; - } - else if (freqNum <= 20000) { - freqStyle = "w-freq-uncommon"; + return `
${title} ${pitch} ${conjugationString} ${frequencyString}
${definitionString}
`; } + ) + ); - return freqStyle; + if (entries.more) { + transformedEntries.push(`...
`); } - async getFrequency(inExpression: string, inReading: string, useHighlightedWord: boolean) { - const highlightedWord = this.word; - return this.sendRequest('getFrequency', {inExpression, inReading, useHighlightedWord, highlightedWord}); - } + return transformedEntries.join(""); + } - sendToAnki(): boolean { - const word = this.word; - const sentence = this.sentence; - const sentenceWithBlank = this.sentenceWithBlank; - const entry = this.lastFound; - const pageTitle = window.document.title; - const sourceUrl = window.location.href; + static getFrequencyStyle(inFreqNum) { + let freqNum = inFreqNum.replace(/_r/g, ""); - this.sendRequest('sendToAnki', {word, sentence, sentenceWithBlank, entry, pageTitle, sourceUrl}); - return true; - } + let freqStyle = "w-freq-rare"; - isVisible(): boolean { - const popup = this.getPopup(); - return popup && popup.style.display !== 'none'; + if (freqNum <= 5000) { + freqStyle = "w-freq-very-common"; + } else if (freqNum <= 10000) { + freqStyle = "w-freq-common"; + } else if (freqNum <= 20000) { + freqStyle = "w-freq-uncommon"; } - playAudio(): boolean { - const {lastFound} = this; - - if (!lastFound) return false; - - this.sendRequest('playAudio', lastFound); - return true; - } - - setSanseidoMode(sanseidoMode: boolean): void { - this.sanseidoMode = sanseidoMode || false; - } - - setEpwingMode(epwingMode: boolean): void { - if (epwingMode) { - this.enableEpwing(); - } else { - this.disableEpwing(); - } + return freqStyle; + } + + async getFrequency( + inExpression: string, + inReading: string, + useHighlightedWord: boolean + ) { + const highlightedWord = this.word; + return this.sendRequest("getFrequency", { + inExpression, + inReading, + useHighlightedWord, + highlightedWord, + }); + } + + sendToAnki(): boolean { + const word = this.word; + const sentence = this.sentence; + const sentenceWithBlank = this.sentenceWithBlank; + const entry = this.lastFound; + const pageTitle = window.document.title; + const sourceUrl = window.location.href; + + this.sendRequest("sendToAnki", { + word, + sentence, + sentenceWithBlank, + entry, + pageTitle, + sourceUrl, + selected: this.selectedEntry, + }); + return true; + } + + isVisible(): boolean { + const popup = this.getPopup(); + return popup && popup.style.display !== "none"; + } + + playAudio(): boolean { + const { lastFound } = this; + + if (!lastFound) return false; + + this.sendRequest("playAudio", { + ...lastFound, + selected: this.selectedEntry, + }); + return true; + } + + setSanseidoMode(sanseidoMode: boolean): void { + this.sanseidoMode = sanseidoMode || false; + } + + setEpwingMode(epwingMode: boolean): void { + if (epwingMode) { + this.enableEpwing(); + } else { + this.disableEpwing(); } + } } const rikai = new Rikai(document); -browser.storage.local.get('enabled').then(({enabled}) => { - if (enabled) { - rikai.enable(); - } +browser.storage.local.get("enabled").then(({ enabled }) => { + if (enabled) { + rikai.enable(); + } }); -browser.storage.local.get('sanseidoMode').then(({ sanseidoMode }: Storage & { sanseidoMode: boolean }) => { +browser.storage.local + .get("sanseidoMode") + .then(({ sanseidoMode }: Storage & { sanseidoMode: boolean }) => { rikai.setSanseidoMode(sanseidoMode); -}); + }); -browser.storage.local.get('epwingMode').then(({ epwingMode }: Storage & { epwingMode: boolean }) => { +browser.storage.local + .get("epwingMode") + .then(({ epwingMode }: Storage & { epwingMode: boolean }) => { rikai.setEpwingMode(epwingMode); -}); + }); browser.storage.onChanged.addListener((change, storageArea) => { - if (storageArea !== "local") return; - if (typeof change.enabled !== 'undefined') { - if (change.enabled.newValue === true) { - rikai.enable(); - } else { - rikai.disable(); - } + if (storageArea !== "local") return; + if (typeof change.enabled !== "undefined") { + if (change.enabled.newValue === true) { + rikai.enable(); + } else { + rikai.disable(); } + } - if (typeof change.sanseidoMode !== 'undefined') { - rikai.setSanseidoMode(change.sanseidoMode.newValue); - } + if (typeof change.sanseidoMode !== "undefined") { + rikai.setSanseidoMode(change.sanseidoMode.newValue); + } - if (typeof change.epwingMode !== 'undefined') { - rikai.setEpwingMode(change.epwingMode.newValue); - } + if (typeof change.epwingMode !== "undefined") { + rikai.setEpwingMode(change.epwingMode.newValue); + } }); diff --git a/src/defaultConfig.ts b/src/defaultConfig.ts index afd678b..f997396 100644 --- a/src/defaultConfig.ts +++ b/src/defaultConfig.ts @@ -1,160 +1,180 @@ import {DictionaryDefinition} from "./interfaces/DictionaryDefinition"; export interface Config { - ankiTags: string; - keymap: { playAudio: number; epwingPreviousEntry: number; epwingNextEntry: number; selectNextDictionary: number; toggleSanseidoMode: number; toggleEpwingMode: number; sendToAnki: number }; - showKanjiNewNelson: boolean; - showFrequency: boolean; - showKanjiNelson: boolean; - showPopularWordIndicator: boolean; - epwingDictionaries: { name: string, path: string }[]; - epwingMode?: boolean; - startWithSanseido: boolean; - importEmptyAudio: boolean; - ankiFields: { [key: string]: string }; - showKanjiHalpern: boolean; - maxEntries: number; - showKanjiHenshall: boolean; - theme: string; - hideXRatedEntries: boolean; - showKanjiLearnersDictionary: boolean; - audioVolume: number; - installedDictionaries: DictionaryDefinition[]; - showKanjiTurtleAndKana: boolean; - showKanjiHeisig: boolean; - recommendedDictionaries: DictionaryDefinition[]; - showKanjiUnicode: boolean; - showKanjiPinYin: boolean; - showKanjiComponents: boolean; - startWithEpwing: boolean; - hideDefinitions: boolean; - showWordTypeIndicator: boolean; - openChangelogOnUpdate: boolean; - showKanjiTurtleDictionary: boolean; - nameMax: number; - showPitchAccent: boolean; - showKanjiSkipPattern: boolean; + ankiTags: string; + keymap: { + playAudio: number; + epwingPreviousEntry: number; + epwingNextEntry: number; + selectNextDictionary: number; + toggleSanseidoMode: number; + toggleEpwingMode: number; + sendToAnki: number; + nextDefinition: number; + previousDefinition: number; + }; + showKanjiNewNelson: boolean; + showFrequency: boolean; + showKanjiNelson: boolean; + showPopularWordIndicator: boolean; + epwingDictionaries: { name: string; path: string }[]; + epwingMode?: boolean; + startWithSanseido: boolean; + importEmptyAudio: boolean; + ankiFields: { [key: string]: string }; + showKanjiHalpern: boolean; + maxEntries: number; + showKanjiHenshall: boolean; + theme: string; + hideXRatedEntries: boolean; + showKanjiLearnersDictionary: boolean; + audioVolume: number; + installedDictionaries: DictionaryDefinition[]; + showKanjiTurtleAndKana: boolean; + showKanjiHeisig: boolean; + recommendedDictionaries: DictionaryDefinition[]; + showKanjiUnicode: boolean; + showKanjiPinYin: boolean; + showKanjiComponents: boolean; + startWithEpwing: boolean; + hideDefinitions: boolean; + showWordTypeIndicator: boolean; + openChangelogOnUpdate: boolean; + showKanjiTurtleDictionary: boolean; + nameMax: number; + showPitchAccent: boolean; + showKanjiSkipPattern: boolean; - vnHookClipboardFrequency: number; - vnHookAppendToTop: boolean; - vnAutoScroll: boolean; + vnHookClipboardFrequency: number; + vnHookAppendToTop: boolean; + vnAutoScroll: boolean; } const defaultConfig: Config = { - startWithSanseido: false, - startWithEpwing: false, + startWithSanseido: false, + startWithEpwing: false, - keymap: { - playAudio: 70, - sendToAnki: 82, - selectNextDictionary: 16, - toggleSanseidoMode: 79, + keymap: { + playAudio: 70, + sendToAnki: 82, + selectNextDictionary: 16, + toggleSanseidoMode: 79, - toggleEpwingMode: 80, - epwingPreviousEntry: 219, - epwingNextEntry: 221, - }, - hideDefinitions: false, //hidedef - hideXRatedEntries: false, //hidex - nameMax: 20, //namax - maxEntries: 10, - ankiFields: {}, - showWordTypeIndicator: true, //wpos - showPopularWordIndicator: true, //wpop - ankiTags: '', - importEmptyAudio: true, - openChangelogOnUpdate: false, - showFrequency: false, - showPitchAccent: false, //showpitchaccent - theme: 'blue', - audioVolume: 100, + toggleEpwingMode: 80, + epwingPreviousEntry: 219, + epwingNextEntry: 221, + + nextDefinition: 40, + previousDefinition: 38, + }, + hideDefinitions: false, //hidedef + hideXRatedEntries: false, //hidex + nameMax: 20, //namax + maxEntries: 10, + ankiFields: {}, + showWordTypeIndicator: true, //wpos + showPopularWordIndicator: true, //wpop + ankiTags: "", + importEmptyAudio: true, + openChangelogOnUpdate: false, + showFrequency: false, + showPitchAccent: false, //showpitchaccent + theme: "blue", + audioVolume: 100, - //Definitions for what we can show in the Kanji Dictionary display - showKanjiComponents: true, //kindex-COMP - showKanjiHalpern: true, //kindex-H - showKanjiHeisig: true, //kindex-L - showKanjiHenshall: true, //kindex-E - showKanjiLearnersDictionary: true, //kindex-DK - showKanjiNelson: true, //kindex-N - showKanjiNewNelson: true, //kindex-V - showKanjiPinYin: true, //kindex-Y - showKanjiSkipPattern: true, //kindex-P - showKanjiTurtleAndKana: true, //kindex-IN - showKanjiTurtleDictionary: true, //kindex-I - showKanjiUnicode: true, //kindex-U + //Definitions for what we can show in the Kanji Dictionary display + showKanjiComponents: true, //kindex-COMP + showKanjiHalpern: true, //kindex-H + showKanjiHeisig: true, //kindex-L + showKanjiHenshall: true, //kindex-E + showKanjiLearnersDictionary: true, //kindex-DK + showKanjiNelson: true, //kindex-N + showKanjiNewNelson: true, //kindex-V + showKanjiPinYin: true, //kindex-Y + showKanjiSkipPattern: true, //kindex-P + showKanjiTurtleAndKana: true, //kindex-IN + showKanjiTurtleDictionary: true, //kindex-I + showKanjiUnicode: true, //kindex-U - //Epwing Options - epwingDictionaries: [], + //Epwing Options + epwingDictionaries: [], - installedDictionaries: [], - recommendedDictionaries: [ - { - name: 'Japanese to English Dictionary', - id: 'e82ffa3b-1cd4-4749-b5bf-9a6f64e6890a', - hasType: true, - isNameDictionary: false, - isKanjiDictionary: false, - url: 'https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/english.json' - }, - { - name: 'Japanese Names', - id: '359fe507-7235-4040-8f7b-c5af90e9897d', - hasType: false, - isNameDictionary: true, - isKanjiDictionary: false, - url: 'https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/names.json' - // url: '../../names.json' - }, - { - name: 'Japanese to Dutch Dictionary', - id: 'a544e3ba-51cc-4574-aed5-54e195557e17', - hasType: true, - isNameDictionary: false, - isKanjiDictionary: false, - url: 'https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/dutch.json' - // url: '../../dutch.json' - }, - { - name: 'Japanese to French Dictionary', - id: 'eb8e4ac0-9086-4710-b121-05f2acef5664', - hasType: true, - isNameDictionary: false, - isKanjiDictionary: false, - url: 'https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/french.json' - // url: '../../french.json' - }, - { - name: 'Japanese to German Dictionary', - id: '1d7e1b66-8478-4a7d-8c00-60cb85af772e', - hasType: true, - isNameDictionary: false, - isKanjiDictionary: false, - url: 'https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/german.json' - // url: '../../german.json' - }, - { - name: 'Japanese to Russian Dictionary', - id: '62be5b14-353b-4a25-92d7-341da40fd380', - hasType: true, - isNameDictionary: false, - isKanjiDictionary: false, - url: 'https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/russian.json' - // url: '../../russian.json' - }, - { - name: 'Japanese to Thai Dictionary', - id: 'bef50e55-3d98-438f-801f-70137714be30', - hasType: true, - isNameDictionary: false, - isKanjiDictionary: false, - url: 'https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/thai.json' - // url: '../../thai.json' - }, - ], + installedDictionaries: [], + recommendedDictionaries: [ + { + name: "Japanese to English Dictionary", + id: "e82ffa3b-1cd4-4749-b5bf-9a6f64e6890a", + hasType: true, + isNameDictionary: false, + isKanjiDictionary: false, + url: + "https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/english.json", + }, + { + name: "Japanese Names", + id: "359fe507-7235-4040-8f7b-c5af90e9897d", + hasType: false, + isNameDictionary: true, + isKanjiDictionary: false, + url: + "https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/names.json", + // url: '../../names.json' + }, + { + name: "Japanese to Dutch Dictionary", + id: "a544e3ba-51cc-4574-aed5-54e195557e17", + hasType: true, + isNameDictionary: false, + isKanjiDictionary: false, + url: + "https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/dutch.json", + // url: '../../dutch.json' + }, + { + name: "Japanese to French Dictionary", + id: "eb8e4ac0-9086-4710-b121-05f2acef5664", + hasType: true, + isNameDictionary: false, + isKanjiDictionary: false, + url: + "https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/french.json", + // url: '../../french.json' + }, + { + name: "Japanese to German Dictionary", + id: "1d7e1b66-8478-4a7d-8c00-60cb85af772e", + hasType: true, + isNameDictionary: false, + isKanjiDictionary: false, + url: + "https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/german.json", + // url: '../../german.json' + }, + { + name: "Japanese to Russian Dictionary", + id: "62be5b14-353b-4a25-92d7-341da40fd380", + hasType: true, + isNameDictionary: false, + isKanjiDictionary: false, + url: + "https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/russian.json", + // url: '../../russian.json' + }, + { + name: "Japanese to Thai Dictionary", + id: "bef50e55-3d98-438f-801f-70137714be30", + hasType: true, + isNameDictionary: false, + isKanjiDictionary: false, + url: + "https://raw.githubusercontent.com/Garethp/RikaiRebuilt-dictionaries/master/thai.json", + // url: '../../thai.json' + }, + ], - vnHookClipboardFrequency: 200, - vnHookAppendToTop: false, - vnAutoScroll: true, + vnHookClipboardFrequency: 200, + vnHookAppendToTop: false, + vnAutoScroll: true, }; export default defaultConfig; diff --git a/styles/popup.css b/styles/popup.css index ec7af16..6ae071d 100644 --- a/styles/popup.css +++ b/styles/popup.css @@ -23,11 +23,13 @@ position: absolute; z-index: 7777; border: 1px solid #D0D0D0 !important; - padding: 4px; top: 5px; left: 5px; min-width: 100px; } +#rikaichan-window > div { + padding: 4px; +} /* used for word definitions */ #rikaichan-window .w-kanji { @@ -224,8 +226,11 @@ #rikaichan-window.rikai-blue { background: #5C73B8; } +#rikaichan-window.rikai-blue > div.selected { + background-color: #3E528E; +} #rikaichan-window.rikai-blue, #rikaichan-window.rikai-blue * { - color: #FFFFFF; + color: #ffffff; } #rikaichan-window.rikai-blue .w-kanji { color: #B7E7FF; diff --git a/styles/popup.css.map b/styles/popup.css.map index 1390989..8739314 100644 --- a/styles/popup.css.map +++ b/styles/popup.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["popup.scss"],"names":[],"mappings":"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","file":"popup.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["popup.scss"],"names":[],"mappings":"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","file":"popup.css"} \ No newline at end of file diff --git a/styles/popup.scss b/styles/popup.scss index 782efe6..f9b7f51 100644 --- a/styles/popup.scss +++ b/styles/popup.scss @@ -24,10 +24,13 @@ position: absolute; z-index: 7777; border: 1px solid #D0D0D0 !important; - padding: 4px; top: 5px; left: 5px; min-width: 100px; + + > div { + padding: 4px; + } } /* used for word definitions */ @@ -245,8 +248,14 @@ /** Blue Theme **/ #rikaichan-window.rikai-blue { + > div.selected { + background-color: #3E528E; + + } + background: #5C73B8; - &, * { color: #FFFFFF; } + + &, * { color: #ffffff; } .w-kanji { color: #B7E7FF; } .w-kana { color: #C0FFC0; }