First word lookup draft
This commit is contained in:
parent
a56b9baed6
commit
00dcc5f571
|
@ -89,6 +89,12 @@
|
|||
</select>
|
||||
</div>
|
||||
</modal>
|
||||
<modal :buttons="false" ref="wordDefinitionViewer" dialogClass="word-definition-viewer">
|
||||
<word-definition :expression="wordDefinitionLookup" ref="characterPage"></word-definition>
|
||||
<template slot="title">
|
||||
{{wordDefinitionLookup}}
|
||||
</template>
|
||||
</modal>
|
||||
<logs ref="logsDialog"></logs>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -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<string> = [];
|
||||
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) {
|
||||
(<Modal>this.$refs.wordDefinitionViewer).show();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -311,7 +331,6 @@
|
|||
|
||||
dt.connect();
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
async login(): Promise<void> {
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
async mounted(): Promise<void> {
|
||||
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));
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<ul>
|
||||
<li v-for="definition in definitions">
|
||||
<p><i>({{definition.type}}.)</i> {{definition.definition}}</p>
|
||||
<small>{{definition.synonyms.join(', ').replace(/_/g, ' ')}}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { Definition, DefinitionDictionary } from './definition-dictionary';
|
||||
import electron from 'electron';
|
||||
import { Component, Prop, Watch } from '@f-list/vue-ts';
|
||||
// import { EventBus } from '../../chat/preview/event-bus';
|
||||
|
||||
@Component({})
|
||||
export default class WordDefinition extends Vue {
|
||||
private wordDef = new DefinitionDictionary(electron.remote.app.getAppPath());
|
||||
|
||||
@Prop
|
||||
readonly expression?: string;
|
||||
|
||||
public definitions: Definition[] = [];
|
||||
|
||||
// @Hook('mounted')
|
||||
// mounted(): void {
|
||||
//
|
||||
// }
|
||||
|
||||
@Watch('expression')
|
||||
async onWordChange(newExpression: string): Promise<void> {
|
||||
this.definitions = await this.wordDef.getDefinition(newExpression);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.word-definition-viewer {
|
||||
ul {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin-left: 0;
|
||||
|
||||
p {
|
||||
margin-bottom: 0.1em;
|
||||
color: var(--input-color);
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
margin-bottom: 1.2em;
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
|
@ -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
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue