From d57c5530b7ad56a7cc89782b4d186d8fddb55d86 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Sat, 1 Jul 2017 18:27:49 -0700 Subject: view added notes --- ext/bg/js/anki-null.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ext/bg/js/anki-null.js') diff --git a/ext/bg/js/anki-null.js b/ext/bg/js/anki-null.js index 99dc2f30..8dad6915 100644 --- a/ext/bg/js/anki-null.js +++ b/ext/bg/js/anki-null.js @@ -37,4 +37,8 @@ class AnkiNull { getModelFieldNames(modelName) { return Promise.resolve([]); } + + guiBrowse(query) { + return Promise.resolve([]); + } } -- cgit v1.2.3 From b0cdf59bd8dae8f44362b14bdf19b243514e31d3 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Mon, 10 Jul 2017 16:24:31 -0700 Subject: move anki to async --- ext/bg/js/anki-connect.js | 69 +++++++++++++++++------------------------------ ext/bg/js/anki-null.js | 24 ++++++++--------- ext/bg/js/translator.js | 20 +------------- ext/bg/js/util.js | 25 +++++++++++++++++ 4 files changed, 63 insertions(+), 75 deletions(-) (limited to 'ext/bg/js/anki-null.js') diff --git a/ext/bg/js/anki-connect.js b/ext/bg/js/anki-connect.js index 173feefd..a4d8ba3f 100644 --- a/ext/bg/js/anki-connect.js +++ b/ext/bg/js/anki-connect.js @@ -20,69 +20,50 @@ class AnkiConnect { constructor(server) { this.server = server; - this.asyncPools = {}; this.localVersion = 2; - this.remoteVersion = null; + this.remoteVersion = 0; } - addNote(note) { - return this.checkVersion().then(() => this.ankiInvoke('addNote', {note})); + async addNote(note) { + await this.checkVersion(); + return await this.ankiInvoke('addNote', {note}); } - canAddNotes(notes) { - return this.checkVersion().then(() => this.ankiInvoke('canAddNotes', {notes}, 'notes')); + async canAddNotes(notes) { + await this.checkVersion(); + return await this.ankiInvoke('canAddNotes', {notes}); } - getDeckNames() { - return this.checkVersion().then(() => this.ankiInvoke('deckNames', {})); + async getDeckNames() { + await this.checkVersion(); + return await this.ankiInvoke('deckNames'); } - getModelNames() { - return this.checkVersion().then(() => this.ankiInvoke('modelNames', {})); + async getModelNames() { + await this.checkVersion(); + return await this.ankiInvoke('modelNames'); } - getModelFieldNames(modelName) { - return this.checkVersion().then(() => this.ankiInvoke('modelFieldNames', {modelName})); + async getModelFieldNames(modelName) { + await this.checkVersion(); + return await this.ankiInvoke('modelFieldNames', {modelName}); } - guiBrowse(query) { - return this.checkVersion().then(() => this.ankiInvoke('guiBrowse', {query})); + async guiBrowse(query) { + await this.checkVersion(); + return await this.ankiInvoke('guiBrowse', {query}); } - checkVersion() { - if (this.localVersion === this.remoteVersion) { - return Promise.resolve(true); - } - - return this.ankiInvoke('version', {}, null).then(version => { - this.remoteVersion = version; + async checkVersion() { + if (this.remoteVersion < this.localVersion) { + this.remoteVersion = await this.ankiInvoke('version'); if (this.remoteVersion < this.localVersion) { return Promise.reject('extension and plugin versions incompatible'); } - }); + } } - ankiInvoke(action, params, pool) { - return new Promise((resolve, reject) => { - if (pool && this.asyncPools.hasOwnProperty(pool)) { - this.asyncPools[pool].abort(); - } - - const xhr = new XMLHttpRequest(); - xhr.addEventListener('loadend', () => { - if (pool) { - delete this.asyncPools[pool]; - } - - if (xhr.responseText) { - resolve(JSON.parse(xhr.responseText)); - } else { - reject('unable to connect to plugin'); - } - }); - - xhr.open('POST', this.server); - xhr.send(JSON.stringify({action, params})); - }); + ankiInvoke(action, params) { + return jsonRequest(this.server, 'POST', {action, params, version: this.localVersion}); } } diff --git a/ext/bg/js/anki-null.js b/ext/bg/js/anki-null.js index 8dad6915..d82f0e68 100644 --- a/ext/bg/js/anki-null.js +++ b/ext/bg/js/anki-null.js @@ -18,27 +18,27 @@ class AnkiNull { - addNote(note) { - return Promise.reject('unsupported action'); + async addNote(note) { + return null; } - canAddNotes(notes) { - return Promise.resolve([]); + async canAddNotes(notes) { + return []; } - getDeckNames() { - return Promise.resolve([]); + async getDeckNames() { + return []; } - getModelNames() { - return Promise.resolve([]); + async getModelNames() { + return []; } - getModelFieldNames(modelName) { - return Promise.resolve([]); + async getModelFieldNames(modelName) { + return []; } - guiBrowse(query) { - return Promise.resolve([]); + async guiBrowse(query) { + return []; } } diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index aa11ea63..9232e529 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -31,7 +31,7 @@ class Translator { if (!this.deinflector) { const url = chrome.extension.getURL('/bg/lang/deinflect.json'); - const reasons = await Translator.loadRules(url); + const reasons = await jsonRequest(url, 'GET'); this.deinflector = new Deinflector(reasons); } } @@ -124,22 +124,4 @@ class Translator { return definitions; } - - static loadRules(url) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('application/json'); - xhr.addEventListener('load', () => resolve(xhr.responseText)); - xhr.addEventListener('error', () => reject('failed to execute network request')); - xhr.open('GET', url); - xhr.send(); - }).then(responseText => { - try { - return JSON.parse(responseText); - } - catch (e) { - return Promise.reject('invalid JSON response'); - } - }); - } } diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index b8a60217..4f907923 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -428,6 +428,31 @@ function dictFieldFormat(field, definition, mode, options) { return field; } +/* + * JSON + */ + +function jsonRequest(url, action, params) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('application/json'); + xhr.addEventListener('load', () => resolve(xhr.responseText)); + xhr.addEventListener('error', () => reject('failed to execute network request')); + xhr.open(action, url); + if (params) { + xhr.send(JSON.stringify(params)); + } else { + xhr.send(); + } + }).then(responseText => { + try { + return JSON.parse(responseText); + } + catch (e) { + return Promise.reject('invalid JSON response'); + } + }); +} /* * Helpers -- cgit v1.2.3 From 8b50dfe1e9a8be7b8d2a7c69b25bc04babfc1c0c Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Sun, 13 Aug 2017 21:11:10 -0700 Subject: unify files --- ext/bg/background.html | 4 +- ext/bg/js/anki-connect.js | 69 ------------- ext/bg/js/anki-null.js | 44 --------- ext/bg/js/anki.js | 95 ++++++++++++++++++ ext/bg/js/backend.js | 5 +- ext/fg/js/source-element.js | 77 --------------- ext/fg/js/source-range.js | 176 --------------------------------- ext/fg/js/source.js | 236 ++++++++++++++++++++++++++++++++++++++++++++ ext/manifest.json | 3 +- 9 files changed, 336 insertions(+), 373 deletions(-) delete mode 100644 ext/bg/js/anki-connect.js delete mode 100644 ext/bg/js/anki-null.js create mode 100644 ext/bg/js/anki.js delete mode 100644 ext/fg/js/source-element.js delete mode 100644 ext/fg/js/source-range.js create mode 100644 ext/fg/js/source.js (limited to 'ext/bg/js/anki-null.js') diff --git a/ext/bg/background.html b/ext/bg/background.html index fd3b7dd1..90ad9709 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -9,8 +9,7 @@ - - + @@ -20,7 +19,6 @@ - diff --git a/ext/bg/js/anki-connect.js b/ext/bg/js/anki-connect.js deleted file mode 100644 index 80c075fd..00000000 --- a/ext/bg/js/anki-connect.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -class AnkiConnect { - constructor(server) { - this.server = server; - this.localVersion = 2; - this.remoteVersion = 0; - } - - async addNote(note) { - await this.checkVersion(); - return await this.ankiInvoke('addNote', {note}); - } - - async canAddNotes(notes) { - await this.checkVersion(); - return await this.ankiInvoke('canAddNotes', {notes}); - } - - async getDeckNames() { - await this.checkVersion(); - return await this.ankiInvoke('deckNames'); - } - - async getModelNames() { - await this.checkVersion(); - return await this.ankiInvoke('modelNames'); - } - - async getModelFieldNames(modelName) { - await this.checkVersion(); - return await this.ankiInvoke('modelFieldNames', {modelName}); - } - - async guiBrowse(query) { - await this.checkVersion(); - return await this.ankiInvoke('guiBrowse', {query}); - } - - async checkVersion() { - if (this.remoteVersion < this.localVersion) { - this.remoteVersion = await this.ankiInvoke('version'); - if (this.remoteVersion < this.localVersion) { - throw 'extension and plugin versions incompatible'; - } - } - } - - ankiInvoke(action, params) { - return requestJson(this.server, 'POST', {action, params, version: this.localVersion}); - } -} diff --git a/ext/bg/js/anki-null.js b/ext/bg/js/anki-null.js deleted file mode 100644 index d82f0e68..00000000 --- a/ext/bg/js/anki-null.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -class AnkiNull { - async addNote(note) { - return null; - } - - async canAddNotes(notes) { - return []; - } - - async getDeckNames() { - return []; - } - - async getModelNames() { - return []; - } - - async getModelFieldNames(modelName) { - return []; - } - - async guiBrowse(query) { - return []; - } -} diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js new file mode 100644 index 00000000..f0ec4571 --- /dev/null +++ b/ext/bg/js/anki.js @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +class AnkiConnect { + constructor(server) { + this.server = server; + this.localVersion = 2; + this.remoteVersion = 0; + } + + async addNote(note) { + await this.checkVersion(); + return await this.ankiInvoke('addNote', {note}); + } + + async canAddNotes(notes) { + await this.checkVersion(); + return await this.ankiInvoke('canAddNotes', {notes}); + } + + async getDeckNames() { + await this.checkVersion(); + return await this.ankiInvoke('deckNames'); + } + + async getModelNames() { + await this.checkVersion(); + return await this.ankiInvoke('modelNames'); + } + + async getModelFieldNames(modelName) { + await this.checkVersion(); + return await this.ankiInvoke('modelFieldNames', {modelName}); + } + + async guiBrowse(query) { + await this.checkVersion(); + return await this.ankiInvoke('guiBrowse', {query}); + } + + async checkVersion() { + if (this.remoteVersion < this.localVersion) { + this.remoteVersion = await this.ankiInvoke('version'); + if (this.remoteVersion < this.localVersion) { + throw 'extension and plugin versions incompatible'; + } + } + } + + ankiInvoke(action, params) { + return requestJson(this.server, 'POST', {action, params, version: this.localVersion}); + } +} + +class AnkiNull { + async addNote(note) { + return null; + } + + async canAddNotes(notes) { + return []; + } + + async getDeckNames() { + return []; + } + + async getModelNames() { + return []; + } + + async getModelFieldNames(modelName) { + return []; + } + + async guiBrowse(query) { + return []; + } +} diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index f61e9742..e8c9452c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -17,7 +17,7 @@ */ -window.yomichan_backend = new class { +class Backend { constructor() { this.translator = new Translator(); this.anki = new AnkiNull(); @@ -113,6 +113,7 @@ window.yomichan_backend = new class { return true; } -}; +} +window.yomichan_backend = new Backend(); window.yomichan_backend.prepare(); diff --git a/ext/fg/js/source-element.js b/ext/fg/js/source-element.js deleted file mode 100644 index a8101382..00000000 --- a/ext/fg/js/source-element.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -class TextSourceElement { - constructor(element, content='') { - this.element = element; - this.content = content; - } - - clone() { - return new TextSourceElement(this.element, this.content); - } - - text() { - return this.content; - } - - setEndOffset(length) { - switch (this.element.nodeName) { - case 'BUTTON': - this.content = this.element.innerHTML; - break; - case 'IMG': - this.content = this.element.getAttribute('alt'); - break; - default: - this.content = this.element.value; - break; - } - - this.content = this.content || ''; - this.content = this.content.substring(0, length); - - return this.content.length; - } - - setStartOffset(length) { - return 0; - } - - containsPoint(point) { - const rect = this.getRect(); - return point.x >= rect.left && point.x <= rect.right; - } - - getRect() { - return this.element.getBoundingClientRect(); - } - - select() { - // NOP - } - - deselect() { - // NOP - } - - equals(other) { - return other.element === this.element && other.content === this.content; - } -} diff --git a/ext/fg/js/source-range.js b/ext/fg/js/source-range.js deleted file mode 100644 index fa73b0a4..00000000 --- a/ext/fg/js/source-range.js +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2016 Alex Yatskov - * Author: Alex Yatskov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -class TextSourceRange { - constructor(range, content='') { - this.range = range; - this.content = content; - } - - clone() { - return new TextSourceRange(this.range.cloneRange(), this.content); - } - - text() { - return this.content; - } - - setEndOffset(length) { - const state = TextSourceRange.seekForward(this.range.startContainer, this.range.startOffset, length); - this.range.setEnd(state.node, state.offset); - this.content = state.content; - return length - state.remainder; - } - - setStartOffset(length) { - const state = TextSourceRange.seekBackward(this.range.startContainer, this.range.startOffset, length); - this.range.setStart(state.node, state.offset); - this.content = state.content; - return length - state.remainder; - } - - containsPoint(point) { - const rect = this.getPaddedRect(); - return point.x >= rect.left && point.x <= rect.right; - } - - getRect() { - return this.range.getBoundingClientRect(); - } - - getPaddedRect() { - const range = this.range.cloneRange(); - const startOffset = range.startOffset; - const endOffset = range.endOffset; - const node = range.startContainer; - - range.setStart(node, Math.max(0, startOffset - 1)); - range.setEnd(node, Math.min(node.length, endOffset + 1)); - - return range.getBoundingClientRect(); - } - - select() { - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(this.range); - } - - deselect() { - const selection = window.getSelection(); - selection.removeAllRanges(); - } - - equals(other) { - return other.range && other.range.compareBoundaryPoints(Range.START_TO_START, this.range) === 0; - } - - static shouldEnter(node) { - if (node.nodeType !== 1) { - return false; - } - - const skip = ['RT', 'SCRIPT', 'STYLE']; - if (skip.includes(node.nodeName)) { - return false; - } - - const style = window.getComputedStyle(node); - const hidden = - style.visibility === 'hidden' || - style.display === 'none' || - parseFloat(style.fontSize) === 0; - - return !hidden; - } - - static seekForward(node, offset, length) { - const state = {node, offset, remainder: length, content: ''}; - if (!TextSourceRange.seekForwardHelper(node, state)) { - return state; - } - - for (let current = node; current !== null; current = current.parentElement) { - for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) { - if (!TextSourceRange.seekForwardHelper(sibling, state)) { - return state; - } - } - } - - return state; - } - - static seekForwardHelper(node, state) { - if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { - const offset = state.node === node ? state.offset : 0; - const remaining = node.length - offset; - const consumed = Math.min(remaining, state.remainder); - state.content = state.content + node.nodeValue.substring(offset, offset + consumed); - state.node = node; - state.offset = offset + consumed; - state.remainder -= consumed; - } else if (TextSourceRange.shouldEnter(node)) { - for (let i = 0; i < node.childNodes.length; ++i) { - if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) { - break; - } - } - } - - return state.remainder > 0; - } - - static seekBackward(node, offset, length) { - const state = {node, offset, remainder: length, content: ''}; - if (!TextSourceRange.seekBackwardHelper(node, state)) { - return state; - } - - for (let current = node; current !== null; current = current.parentElement) { - for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) { - if (!TextSourceRange.seekBackwardHelper(sibling, state)) { - return state; - } - } - } - - return state; - } - - static seekBackwardHelper(node, state) { - if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { - const offset = state.node === node ? state.offset : node.length; - const remaining = offset; - const consumed = Math.min(remaining, state.remainder); - state.content = node.nodeValue.substring(offset - consumed, offset) + state.content; - state.node = node; - state.offset = offset - consumed; - state.remainder -= consumed; - } else if (TextSourceRange.shouldEnter(node)) { - for (let i = node.childNodes.length - 1; i >= 0; --i) { - if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) { - break; - } - } - } - - return state.remainder > 0; - } -} diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js new file mode 100644 index 00000000..210dda12 --- /dev/null +++ b/ext/fg/js/source.js @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2016 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +class TextSourceRange { + constructor(range, content='') { + this.range = range; + this.content = content; + } + + clone() { + return new TextSourceRange(this.range.cloneRange(), this.content); + } + + text() { + return this.content; + } + + setEndOffset(length) { + const state = TextSourceRange.seekForward(this.range.startContainer, this.range.startOffset, length); + this.range.setEnd(state.node, state.offset); + this.content = state.content; + return length - state.remainder; + } + + setStartOffset(length) { + const state = TextSourceRange.seekBackward(this.range.startContainer, this.range.startOffset, length); + this.range.setStart(state.node, state.offset); + this.content = state.content; + return length - state.remainder; + } + + containsPoint(point) { + const rect = this.getPaddedRect(); + return point.x >= rect.left && point.x <= rect.right; + } + + getRect() { + return this.range.getBoundingClientRect(); + } + + getPaddedRect() { + const range = this.range.cloneRange(); + const startOffset = range.startOffset; + const endOffset = range.endOffset; + const node = range.startContainer; + + range.setStart(node, Math.max(0, startOffset - 1)); + range.setEnd(node, Math.min(node.length, endOffset + 1)); + + return range.getBoundingClientRect(); + } + + select() { + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(this.range); + } + + deselect() { + const selection = window.getSelection(); + selection.removeAllRanges(); + } + + equals(other) { + return other.range && other.range.compareBoundaryPoints(Range.START_TO_START, this.range) === 0; + } + + static shouldEnter(node) { + if (node.nodeType !== 1) { + return false; + } + + const skip = ['RT', 'SCRIPT', 'STYLE']; + if (skip.includes(node.nodeName)) { + return false; + } + + const style = window.getComputedStyle(node); + const hidden = + style.visibility === 'hidden' || + style.display === 'none' || + parseFloat(style.fontSize) === 0; + + return !hidden; + } + + static seekForward(node, offset, length) { + const state = {node, offset, remainder: length, content: ''}; + if (!TextSourceRange.seekForwardHelper(node, state)) { + return state; + } + + for (let current = node; current !== null; current = current.parentElement) { + for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) { + if (!TextSourceRange.seekForwardHelper(sibling, state)) { + return state; + } + } + } + + return state; + } + + static seekForwardHelper(node, state) { + if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { + const offset = state.node === node ? state.offset : 0; + const remaining = node.length - offset; + const consumed = Math.min(remaining, state.remainder); + state.content = state.content + node.nodeValue.substring(offset, offset + consumed); + state.node = node; + state.offset = offset + consumed; + state.remainder -= consumed; + } else if (TextSourceRange.shouldEnter(node)) { + for (let i = 0; i < node.childNodes.length; ++i) { + if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) { + break; + } + } + } + + return state.remainder > 0; + } + + static seekBackward(node, offset, length) { + const state = {node, offset, remainder: length, content: ''}; + if (!TextSourceRange.seekBackwardHelper(node, state)) { + return state; + } + + for (let current = node; current !== null; current = current.parentElement) { + for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) { + if (!TextSourceRange.seekBackwardHelper(sibling, state)) { + return state; + } + } + } + + return state; + } + + static seekBackwardHelper(node, state) { + if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { + const offset = state.node === node ? state.offset : node.length; + const remaining = offset; + const consumed = Math.min(remaining, state.remainder); + state.content = node.nodeValue.substring(offset - consumed, offset) + state.content; + state.node = node; + state.offset = offset - consumed; + state.remainder -= consumed; + } else if (TextSourceRange.shouldEnter(node)) { + for (let i = node.childNodes.length - 1; i >= 0; --i) { + if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) { + break; + } + } + } + + return state.remainder > 0; + } +} + + +class TextSourceElement { + constructor(element, content='') { + this.element = element; + this.content = content; + } + + clone() { + return new TextSourceElement(this.element, this.content); + } + + text() { + return this.content; + } + + setEndOffset(length) { + switch (this.element.nodeName) { + case 'BUTTON': + this.content = this.element.innerHTML; + break; + case 'IMG': + this.content = this.element.getAttribute('alt'); + break; + default: + this.content = this.element.value; + break; + } + + this.content = this.content || ''; + this.content = this.content.substring(0, length); + + return this.content.length; + } + + setStartOffset(length) { + return 0; + } + + containsPoint(point) { + const rect = this.getRect(); + return point.x >= rect.left && point.x <= rect.right; + } + + getRect() { + return this.element.getBoundingClientRect(); + } + + select() { + // NOP + } + + deselect() { + // NOP + } + + equals(other) { + return other.element === this.element && other.content === this.content; + } +} diff --git a/ext/manifest.json b/ext/manifest.json index 48308b17..ee3bd2ac 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -18,8 +18,7 @@ "fg/js/api.js", "fg/js/document.js", "fg/js/popup.js", - "fg/js/source-element.js", - "fg/js/source-range.js", + "fg/js/source.js", "fg/js/util.js", "fg/js/frontend.js" -- cgit v1.2.3