diff --git a/electron/Index.vue b/electron/Index.vue index 858c52d..9d9af39 100644 --- a/electron/Index.vue +++ b/electron/Index.vue @@ -89,6 +89,12 @@ + + + + {{wordDefinitionLookup}} + + @@ -116,10 +122,12 @@ // import { BetterSqliteStore } from '../learn/store/better-sqlite3'; // import { Sqlite3Store } from '../learn/store/sqlite3'; import CharacterPage from '../site/character_page/character_page.vue'; + import WordDefinition from '../learn/dictionary/WordDefinition.vue'; import {defaultHost, GeneralSettings, nativeRequire} from './common'; import { fixLogs /*SettingsStore, Logs as FSLogs*/ } from './filesystem'; import * as SlimcatImporter from './importer'; import _ from 'lodash'; + import { EventBus } from '../chat/preview/event-bus'; // import Bluebird from 'bluebird'; // import Connection from '../fchat/connection'; // import Notifications from './notifications'; @@ -171,7 +179,7 @@ log.info('init.chat.keytar.load.done'); @Component({ - components: {chat: Chat, modal: Modal, characterPage: CharacterPage, logs: Logs} + components: {chat: Chat, modal: Modal, characterPage: CharacterPage, logs: Logs, 'word-definition': WordDefinition} }) export default class Index extends Vue { showAdvanced = false; @@ -190,6 +198,7 @@ adName = ''; fixCharacters: ReadonlyArray = []; fixCharacter = ''; + wordDefinitionLookup = ''; shouldShowSpinner = false; @@ -242,6 +251,17 @@ @Hook('mounted') onMounted(): void { log.debug('init.chat.mounted'); + + EventBus.$on( + 'word-definition', + (data: any) => { + this.wordDefinitionLookup = data.lookupWord; + + if (!!data.lookupWord) { + (this.$refs.wordDefinitionViewer).show(); + } + } + ); } @@ -311,7 +331,6 @@ dt.connect(); }*/ - } async login(): Promise { diff --git a/electron/Window.vue b/electron/Window.vue index cee2082..859a1cd 100644 --- a/electron/Window.vue +++ b/electron/Window.vue @@ -94,7 +94,7 @@ async mounted(): Promise { log.debug('init.window.mounting'); // top bar devtools - browserWindow.webContents.openDevTools({ mode: 'detach' }); + // browserWindow.webContents.openDevTools({ mode: 'detach' }); updateSupportedLanguages(browserWindow.webContents.session.availableSpellCheckerLanguages); @@ -255,7 +255,7 @@ ); // tab devtools - view.webContents.openDevTools(); + // view.webContents.openDevTools(); // console.log('ADD TAB LANGUAGES', getSafeLanguages(this.settings.spellcheckLang), this.settings.spellcheckLang); view.webContents.session.setSpellCheckerLanguages(getSafeLanguages(this.settings.spellcheckLang)); diff --git a/electron/chat.ts b/electron/chat.ts index 4086039..46a3f31 100644 --- a/electron/chat.ts +++ b/electron/chat.ts @@ -52,8 +52,8 @@ import {Logs, SettingsStore} from './filesystem'; import Notifications from './notifications'; import * as SlimcatImporter from './importer'; import Index from './Index.vue'; -import log from 'electron-log'; -import { DefinitionDictionary } from '../learn/dictionary/definition-dictionary'; // tslint:disable-line: match-default-export-name +import log from 'electron-log'; // tslint:disable-line: match-default-export-name +import { WordPosSearch } from '../learn/dictionary/word-pos-search'; log.debug('init.chat'); @@ -107,7 +107,7 @@ function openIncognito(url: string): void { } const webContents = electron.remote.getCurrentWebContents(); -const wordDef = new DefinitionDictionary(electron.remote.app.getAppPath()); +const wordPosSearch = new WordPosSearch(); webContents.on('context-menu', (_, props) => { const hasText = props.selectionText.trim().length > 0; @@ -185,15 +185,16 @@ webContents.on('context-menu', (_, props) => { click: () => electron.ipcRenderer.send('dictionary-remove', props.selectionText) }, {type: 'separator'}); - if (props.selectionText) { + + const lookupWord = props.selectionText || wordPosSearch.getLastClickedWord(); + + if (lookupWord) { menuTemplate.unshift( { type: 'separator' }, { - label: `Look up '${props.selectionText}'`, + label: `Look up '${lookupWord}'`, click: async() => { - const words = await wordDef.getDefinition(props.selectionText); - - // console.log('WORDS', words); + EventBus.$emit('word-definition', { lookupWord, x: props.x, y: props.y }); } } ); diff --git a/learn/cache-manager.ts b/learn/cache-manager.ts index 7c22e47..a65d820 100644 --- a/learn/cache-manager.ts +++ b/learn/cache-manager.ts @@ -86,8 +86,7 @@ export class CacheManager { this.queue.push(entry); - console.log('Added to queue', entry.name, entry.added.toISOString()); - + // console.log('Added to queue', entry.name, entry.added.toISOString()); // console.log('AddProfileForFetching', name, this.queue.length); } @@ -239,11 +238,11 @@ export class CacheManager { const scheduleNextFetch = () => { this.profileTimer = setTimeout( async() => { - const d = Date.now(); + // const d = Date.now(); const next = this.consumeNextInQueue(); if (next) { - console.log('Next in queue', next.name, (Date.now() - d) / 1000.0); + // console.log('Next in queue', next.name, (Date.now() - d) / 1000.0); try { // tslint:disable-next-line: binary-expression-operand-order @@ -259,7 +258,7 @@ export class CacheManager { this.queue.push(next); // return to queue } - console.log('Completed', next.name, (Date.now() - d) / 1000.0); + // console.log('Completed', next.name, (Date.now() - d) / 1000.0); } scheduleNextFetch(); diff --git a/learn/dictionary/WordDefinition.vue b/learn/dictionary/WordDefinition.vue new file mode 100644 index 0000000..e7dee90 --- /dev/null +++ b/learn/dictionary/WordDefinition.vue @@ -0,0 +1,65 @@ + + + + ({{definition.type}}.) {{definition.definition}} + {{definition.synonyms.join(', ').replace(/_/g, ' ')}} + + + + + + diff --git a/learn/dictionary/definition-dictionary.ts b/learn/dictionary/definition-dictionary.ts index dfbcdc1..e8ca4fe 100644 --- a/learn/dictionary/definition-dictionary.ts +++ b/learn/dictionary/definition-dictionary.ts @@ -13,6 +13,7 @@ import WordNet from './wordnet/wordnet'; export interface Definition { definition: string; synonyms: string[]; + type: string; } @@ -23,8 +24,6 @@ export class DefinitionDictionary { constructor(basePath: string) { const dataDir = path.join(basePath, 'assets', 'wordnet-dictionary'); - console.log('MOO MOO', process.cwd(), dataDir); - // tslint:disable-next-line:ban-ts-ignore // @ts-ignore this.wordnet = new WordNet( @@ -68,7 +67,8 @@ export class DefinitionDictionary { (r) => ( { definition: r.def, - synonyms: r.synonyms + synonyms: r.synonyms, + type: r.pos } as any as Definition ) ); diff --git a/learn/dictionary/word-pos-search.ts b/learn/dictionary/word-pos-search.ts new file mode 100644 index 0000000..7354488 --- /dev/null +++ b/learn/dictionary/word-pos-search.ts @@ -0,0 +1,113 @@ +import _ from 'lodash'; + +export interface FoundWord { + word: string; + start: number; + clickedRect: DOMRect; +} + + +export class WordPosSearch { + private listener?: (e: MouseEvent) => void; + + private lastClicked: FoundWord | null = null; + + constructor() { + this.start(); + } + + + stop() { + if (this.listener) { + document.removeEventListener('contextmenu', this.listener); + } + + delete this.listener; + } + + start() { + this.stop(); + + this.listener = this.generateListener(); + + document.addEventListener( + 'contextmenu', + this.listener + ); + } + + + generateListener(): ((e: MouseEvent) => void) { + return (e: MouseEvent): void => { + try { + if (!e.target) { + return; + } + + this.lastClicked = _.reduce( + (e.target as any).childNodes || [], + (accum: FoundWord | null, node) => (accum ? accum : this.findClickedWord(node as any, e.clientX, e.clientY)), + null + ); + } catch (err) { + console.log('wordpos.event', err); + } + }; + } + + + findClickedWord(parentElt: Element, x: number, y: number): FoundWord | null { + if (!parentElt.textContent) { + return null; + } + + const range = document.createRange(); + const words = parentElt.textContent.split(/[\s\.\<\>\,\?\!\;\:\@\$\*\(\)]/); + + let start = 0; + let end = 0; + + for(let i = 0; i < words.length; i++) { + const word = words[i]; + end = start + word.length; + + try { + range.setStart(parentElt, start); + range.setEnd(parentElt, end); + + // not getBoundingClientRect as word could wrap + const rects = range.getClientRects(); + const clickedRect = this.isClickInRects(x, y, rects); + + if (clickedRect) { + return { word, start, clickedRect }; + } + + start = end + 1; + } catch (e) { + // console.log('MM', 'word', word, 'start', start, 'end', end, 'i', i, words); + // console.error(e); + return null; + } + } + + return null; + } + + isClickInRects(x: number, y: number, rects: DOMRectList): DOMRect | null { + for(let i = 0; i < rects.length; ++i) { + const r = rects[i]; + + if ((r.left < x) && (r.right > x) && (r.top < y) && (r.bottom > y)) { + return r; + } + } + + return null; + } + + + getLastClickedWord(): string | null { + return this.lastClicked ? this.lastClicked.word : null; + } +}
({{definition.type}}.) {{definition.definition}}