From 2dbb24ea0416cb83185b6f92624bd9b6e937eade Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 21:01:00 -0500 Subject: Improve error messages when Interface server is invalid --- ext/bg/settings.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'ext/bg/settings.html') diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3e06d4b5..8c787aff 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -760,6 +760,13 @@ + +
@@ -771,7 +778,7 @@
- +
-- cgit v1.2.3 From b8326138a3254e82dd42e1517f371287bdfc6705 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 26 Jan 2020 21:00:19 +0200 Subject: add scannable tags for expression and reading --- ext/bg/data/options-schema.json | 7 ++++++- ext/bg/js/options.js | 3 ++- ext/bg/js/settings/main.js | 2 ++ ext/bg/settings.html | 6 +++++- ext/mixed/css/display-dark.css | 1 + ext/mixed/css/display-default.css | 1 + ext/mixed/css/display.css | 4 ++++ ext/mixed/display-templates.html | 1 + ext/mixed/js/display-generator.js | 15 +++++++++++++++ ext/mixed/js/display.js | 7 ++++--- 10 files changed, 41 insertions(+), 6 deletions(-) (limited to 'ext/bg/settings.html') diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index a20a0619..7e12481d 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -290,7 +290,8 @@ "popupNestingMaxDepth", "enablePopupSearch", "enableOnPopupExpressions", - "enableOnSearchPage" + "enableOnSearchPage", + "enableSearchTags" ], "properties": { "middleMouse": { @@ -348,6 +349,10 @@ "enableOnSearchPage": { "type": "boolean", "default": true + }, + "enableSearchTags": { + "type": "boolean", + "default": false } } }, diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index d93862bf..78508059 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -316,7 +316,8 @@ function profileOptionsCreateDefaults() { popupNestingMaxDepth: 0, enablePopupSearch: false, enableOnPopupExpressions: false, - enableOnSearchPage: true + enableOnSearchPage: true, + enableSearchTags: false }, translation: { diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 6e162ffc..4492cd42 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -67,6 +67,7 @@ async function formRead(options) { options.scanning.enablePopupSearch = $('#enable-search-within-first-popup').prop('checked'); options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked'); options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked'); + options.scanning.enableSearchTags = $('#enable-search-tags').prop('checked'); options.scanning.delay = parseInt($('#scan-delay').val(), 10); options.scanning.length = parseInt($('#scan-length').val(), 10); options.scanning.modifier = $('#scan-modifier-key').val(); @@ -142,6 +143,7 @@ async function formWrite(options) { $('#enable-search-within-first-popup').prop('checked', options.scanning.enablePopupSearch); $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions); $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage); + $('#enable-search-tags').prop('checked', options.scanning.enableSearchTags); $('#scan-delay').val(options.scanning.delay); $('#scan-length').val(options.scanning.length); $('#scan-modifier-key').val(options.scanning.modifier); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 8c787aff..77bcc359 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -481,7 +481,7 @@

- +
@@ -492,6 +492,10 @@
+
+ +
+
diff --git a/ext/mixed/css/display-dark.css b/ext/mixed/css/display-dark.css index 088fc741..c9cd9f90 100644 --- a/ext/mixed/css/display-dark.css +++ b/ext/mixed/css/display-dark.css @@ -38,6 +38,7 @@ body { background-color: #1e1e1e; color: #d4d4d4; } .tag[data-category=dictionary] { background-color: #9057ad; } .tag[data-category=frequency] { background-color: #489148; } .tag[data-category=partOfSpeech] { background-color: #565656; } +.tag[data-category=search] { background-color: #69696e; } .term-reasons { color: #888888; } diff --git a/ext/mixed/css/display-default.css b/ext/mixed/css/display-default.css index 69141c9d..6eee43c4 100644 --- a/ext/mixed/css/display-default.css +++ b/ext/mixed/css/display-default.css @@ -38,6 +38,7 @@ body { background-color: #ffffff; color: #333333; } .tag[data-category=dictionary] { background-color: #aa66cc; } .tag[data-category=frequency] { background-color: #5cb85c; } .tag[data-category=partOfSpeech] { background-color: #565656; } +.tag[data-category=search] { background-color: #8a8a91; } .term-reasons { color: #777777; } diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index fefd500f..3a66cec3 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -227,6 +227,10 @@ button.action-button { margin-left: 0.375em; } +html:root:not([data-enable-search-tags=true]) .tag[data-category=search] { + display: none; +} + .entry-header2, .entry-header3 { display: inline; diff --git a/ext/mixed/display-templates.html b/ext/mixed/display-templates.html index 6c611be9..6fcf4c74 100644 --- a/ext/mixed/display-templates.html +++ b/ext/mixed/display-templates.html @@ -77,5 +77,6 @@ + diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index b2dc373b..c90e693a 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -111,7 +111,11 @@ class DisplayGenerator { // Fallback termTags = details.termTags; } + const searchQueries = [details.expression, details.reading] + .filter((x) => !!x) + .map((x) => ({query: x})); DisplayGenerator._appendMultiple(tagContainer, this.createTag.bind(this), termTags); + DisplayGenerator._appendMultiple(tagContainer, this.createSearchTag.bind(this), searchQueries); DisplayGenerator._appendMultiple(frequencyContainer, this.createFrequencyTag.bind(this), details.frequencies); return node; @@ -270,6 +274,16 @@ class DisplayGenerator { return node; } + createSearchTag(details) { + const node = DisplayGenerator._instantiateTemplate(this._tagSearchTemplate); + + node.textContent = details.query; + + node.dataset.query = details.query; + + return node; + } + createFrequencyTag(details) { const node = DisplayGenerator._instantiateTemplate(this._tagFrequencyTemplate); @@ -311,6 +325,7 @@ class DisplayGenerator { this._kanjiReadingTemplate = doc.querySelector('#kanji-reading-template'); this._tagTemplate = doc.querySelector('#tag-template'); + this._tagSearchTemplate = doc.querySelector('#tag-search-template'); this._tagFrequencyTemplate = doc.querySelector('#tag-frequency-template'); } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 70ef8ec3..54cda0eb 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -257,6 +257,7 @@ class Display { data.ankiEnabled = `${options.anki.enable}`; data.audioEnabled = `${options.audio.enable}`; data.compactGlossaries = `${options.general.compactGlossaries}`; + data.enableSearchTags = `${options.scanning.enableSearchTags}`; data.debug = `${options.general.debugInfo}`; } @@ -312,9 +313,9 @@ class Display { this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); if (this.options.scanning.enablePopupSearch) { - this.addEventListeners('.term-glossary-item', 'mouseup', this.onGlossaryMouseUp.bind(this)); - this.addEventListeners('.term-glossary-item', 'mousedown', this.onGlossaryMouseDown.bind(this)); - this.addEventListeners('.term-glossary-item', 'mousemove', this.onGlossaryMouseMove.bind(this)); + this.addEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); + this.addEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); + this.addEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this)); } } else { Display.clearEventListeners(this.eventListeners); -- cgit v1.2.3 From 165959ef068905485a044a06bb281109d88d5679 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 8 Feb 2020 20:45:30 -0500 Subject: Move japanese.js into bg --- ext/bg/background.html | 2 +- ext/bg/js/japanese.js | 454 +++++++++++++++++++++++++++++++++++++++++++++++ ext/bg/search.html | 2 +- ext/bg/settings.html | 2 +- ext/mixed/js/japanese.js | 454 ----------------------------------------------- 5 files changed, 457 insertions(+), 457 deletions(-) create mode 100644 ext/bg/js/japanese.js delete mode 100644 ext/mixed/js/japanese.js (limited to 'ext/bg/settings.html') diff --git a/ext/bg/background.html b/ext/bg/background.html index af87eddb..e6d32593 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -31,6 +31,7 @@ + @@ -39,7 +40,6 @@ - diff --git a/ext/bg/js/japanese.js b/ext/bg/js/japanese.js new file mode 100644 index 00000000..0da822d7 --- /dev/null +++ b/ext/bg/js/japanese.js @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2016-2020 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 . + */ + + +const JP_HALFWIDTH_KATAKANA_MAPPING = new Map([ + ['ヲ', 'ヲヺ-'], + ['ァ', 'ァ--'], + ['ィ', 'ィ--'], + ['ゥ', 'ゥ--'], + ['ェ', 'ェ--'], + ['ォ', 'ォ--'], + ['ャ', 'ャ--'], + ['ュ', 'ュ--'], + ['ョ', 'ョ--'], + ['ッ', 'ッ--'], + ['ー', 'ー--'], + ['ア', 'ア--'], + ['イ', 'イ--'], + ['ウ', 'ウヴ-'], + ['エ', 'エ--'], + ['オ', 'オ--'], + ['カ', 'カガ-'], + ['キ', 'キギ-'], + ['ク', 'クグ-'], + ['ケ', 'ケゲ-'], + ['コ', 'コゴ-'], + ['サ', 'サザ-'], + ['シ', 'シジ-'], + ['ス', 'スズ-'], + ['セ', 'セゼ-'], + ['ソ', 'ソゾ-'], + ['タ', 'タダ-'], + ['チ', 'チヂ-'], + ['ツ', 'ツヅ-'], + ['テ', 'テデ-'], + ['ト', 'トド-'], + ['ナ', 'ナ--'], + ['ニ', 'ニ--'], + ['ヌ', 'ヌ--'], + ['ネ', 'ネ--'], + ['ノ', 'ノ--'], + ['ハ', 'ハバパ'], + ['ヒ', 'ヒビピ'], + ['フ', 'フブプ'], + ['ヘ', 'ヘベペ'], + ['ホ', 'ホボポ'], + ['マ', 'マ--'], + ['ミ', 'ミ--'], + ['ム', 'ム--'], + ['メ', 'メ--'], + ['モ', 'モ--'], + ['ヤ', 'ヤ--'], + ['ユ', 'ユ--'], + ['ヨ', 'ヨ--'], + ['ラ', 'ラ--'], + ['リ', 'リ--'], + ['ル', 'ル--'], + ['レ', 'レ--'], + ['ロ', 'ロ--'], + ['ワ', 'ワ--'], + ['ン', 'ン--'] +]); + +const JP_HIRAGANA_RANGE = [0x3040, 0x309f]; +const JP_KATAKANA_RANGE = [0x30a0, 0x30ff]; +const JP_KANA_RANGES = [JP_HIRAGANA_RANGE, JP_KATAKANA_RANGE]; + +const JP_CJK_COMMON_RANGE = [0x4e00, 0x9fff]; +const JP_CJK_RARE_RANGE = [0x3400, 0x4dbf]; +const JP_CJK_RANGES = [JP_CJK_COMMON_RANGE, JP_CJK_RARE_RANGE]; + +const JP_ITERATION_MARK_CHAR_CODE = 0x3005; + +// Japanese character ranges, roughly ordered in order of expected frequency +const JP_JAPANESE_RANGES = [ + JP_HIRAGANA_RANGE, + JP_KATAKANA_RANGE, + + JP_CJK_COMMON_RANGE, + JP_CJK_RARE_RANGE, + + [0xff66, 0xff9f], // Halfwidth katakana + + [0x30fb, 0x30fc], // Katakana punctuation + [0xff61, 0xff65], // Kana punctuation + [0x3000, 0x303f], // CJK punctuation + + [0xff10, 0xff19], // Fullwidth numbers + [0xff21, 0xff3a], // Fullwidth upper case Latin letters + [0xff41, 0xff5a], // Fullwidth lower case Latin letters + + [0xff01, 0xff0f], // Fullwidth punctuation 1 + [0xff1a, 0xff1f], // Fullwidth punctuation 2 + [0xff3b, 0xff3f], // Fullwidth punctuation 3 + [0xff5b, 0xff60], // Fullwidth punctuation 4 + [0xffe0, 0xffee], // Currency markers +]; + + +// Helper functions + +function _jpIsCharCodeInRanges(charCode, ranges) { + for (const [min, max] of ranges) { + if (charCode >= min && charCode <= max) { + return true; + } + } + return false; +} + + +// Character code testing functions + +function jpIsCharCodeKanji(charCode) { + return _jpIsCharCodeInRanges(charCode, JP_CJK_RANGES); +} + +function jpIsCharCodeKana(charCode) { + return _jpIsCharCodeInRanges(charCode, JP_KANA_RANGES); +} + +function jpIsCharCodeJapanese(charCode) { + return _jpIsCharCodeInRanges(charCode, JP_JAPANESE_RANGES); +} + + +// String testing functions + +function jpIsStringEntirelyKana(str) { + if (str.length === 0) { return false; } + for (let i = 0, ii = str.length; i < ii; ++i) { + if (!jpIsCharCodeKana(str.charCodeAt(i))) { + return false; + } + } + return true; +} + +function jpIsStringPartiallyJapanese(str) { + if (str.length === 0) { return false; } + for (let i = 0, ii = str.length; i < ii; ++i) { + if (jpIsCharCodeJapanese(str.charCodeAt(i))) { + return true; + } + } + return false; +} + + +// Conversion functions + +function jpKatakanaToHiragana(text) { + let result = ''; + for (const c of text) { + if (wanakana.isKatakana(c)) { + result += wanakana.toHiragana(c); + } else { + result += c; + } + } + + return result; +} + +function jpHiraganaToKatakana(text) { + let result = ''; + for (const c of text) { + if (wanakana.isHiragana(c)) { + result += wanakana.toKatakana(c); + } else { + result += c; + } + } + + return result; +} + +function jpToRomaji(text) { + return wanakana.toRomaji(text); +} + +function jpConvertReading(expressionFragment, readingFragment, readingMode) { + switch (readingMode) { + case 'hiragana': + return jpKatakanaToHiragana(readingFragment || ''); + case 'katakana': + return jpHiraganaToKatakana(readingFragment || ''); + case 'romaji': + if (readingFragment) { + return jpToRomaji(readingFragment); + } else { + if (jpIsStringEntirelyKana(expressionFragment)) { + return jpToRomaji(expressionFragment); + } + } + return readingFragment; + case 'none': + return null; + default: + return readingFragment; + } +} + +function jpDistributeFurigana(expression, reading) { + const fallback = [{furigana: reading, text: expression}]; + if (!reading) { + return fallback; + } + + let isAmbiguous = false; + const segmentize = (reading, groups) => { + if (groups.length === 0 || isAmbiguous) { + return []; + } + + const group = groups[0]; + if (group.mode === 'kana') { + if (jpKatakanaToHiragana(reading).startsWith(jpKatakanaToHiragana(group.text))) { + const readingLeft = reading.substring(group.text.length); + const segs = segmentize(readingLeft, groups.splice(1)); + if (segs) { + return [{text: group.text}].concat(segs); + } + } + } else { + let foundSegments = null; + for (let i = reading.length; i >= group.text.length; --i) { + const readingUsed = reading.substring(0, i); + const readingLeft = reading.substring(i); + const segs = segmentize(readingLeft, groups.slice(1)); + if (segs) { + if (foundSegments !== null) { + // more than one way to segmentize the tail, mark as ambiguous + isAmbiguous = true; + return null; + } + foundSegments = [{text: group.text, furigana: readingUsed}].concat(segs); + } + // there is only one way to segmentize the last non-kana group + if (groups.length === 1) { + break; + } + } + return foundSegments; + } + }; + + const groups = []; + let modePrev = null; + for (const c of expression) { + const charCode = c.charCodeAt(0); + const modeCurr = jpIsCharCodeKanji(charCode) || charCode === JP_ITERATION_MARK_CHAR_CODE ? 'kanji' : 'kana'; + if (modeCurr === modePrev) { + groups[groups.length - 1].text += c; + } else { + groups.push({mode: modeCurr, text: c}); + modePrev = modeCurr; + } + } + + const segments = segmentize(reading, groups); + if (segments && !isAmbiguous) { + return segments; + } + return fallback; +} + +function jpDistributeFuriganaInflected(expression, reading, source) { + const output = []; + + let stemLength = 0; + const shortest = Math.min(source.length, expression.length); + const sourceHiragana = jpKatakanaToHiragana(source); + const expressionHiragana = jpKatakanaToHiragana(expression); + while (stemLength < shortest && sourceHiragana[stemLength] === expressionHiragana[stemLength]) { + ++stemLength; + } + const offset = source.length - stemLength; + + const stemExpression = source.substring(0, source.length - offset); + const stemReading = reading.substring( + 0, + offset === 0 ? reading.length : reading.length - expression.length + stemLength + ); + for (const segment of jpDistributeFurigana(stemExpression, stemReading)) { + output.push(segment); + } + + if (stemLength !== source.length) { + output.push({text: source.substring(stemLength)}); + } + + return output; +} + +function jpConvertHalfWidthKanaToFullWidth(text, sourceMapping) { + let result = ''; + const ii = text.length; + const hasSourceMapping = Array.isArray(sourceMapping); + + for (let i = 0; i < ii; ++i) { + const c = text[i]; + const mapping = JP_HALFWIDTH_KATAKANA_MAPPING.get(c); + if (typeof mapping !== 'string') { + result += c; + continue; + } + + let index = 0; + switch (text.charCodeAt(i + 1)) { + case 0xff9e: // dakuten + index = 1; + break; + case 0xff9f: // handakuten + index = 2; + break; + } + + let c2 = mapping[index]; + if (index > 0) { + if (c2 === '-') { // invalid + index = 0; + c2 = mapping[0]; + } else { + ++i; + } + } + + if (hasSourceMapping && index > 0) { + index = result.length; + const v = sourceMapping.splice(index + 1, 1)[0]; + sourceMapping[index] += v; + } + result += c2; + } + + return result; +} + +function jpConvertNumericTofullWidth(text) { + let result = ''; + for (let i = 0, ii = text.length; i < ii; ++i) { + let c = text.charCodeAt(i); + if (c >= 0x30 && c <= 0x39) { // ['0', '9'] + c += 0xff10 - 0x30; // 0xff10 = '0' full width + result += String.fromCharCode(c); + } else { + result += text[i]; + } + } + return result; +} + +function jpConvertAlphabeticToKana(text, sourceMapping) { + let part = ''; + let result = ''; + const ii = text.length; + + if (sourceMapping.length === ii) { + sourceMapping.length = ii; + sourceMapping.fill(1); + } + + for (let i = 0; i < ii; ++i) { + // Note: 0x61 is the character code for 'a' + let c = text.charCodeAt(i); + if (c >= 0x41 && c <= 0x5a) { // ['A', 'Z'] + c += (0x61 - 0x41); + } else if (c >= 0x61 && c <= 0x7a) { // ['a', 'z'] + // NOP; c += (0x61 - 0x61); + } else if (c >= 0xff21 && c <= 0xff3a) { // ['A', 'Z'] fullwidth + c += (0x61 - 0xff21); + } else if (c >= 0xff41 && c <= 0xff5a) { // ['a', 'z'] fullwidth + c += (0x61 - 0xff41); + } else if (c === 0x2d || c === 0xff0d) { // '-' or fullwidth dash + c = 0x2d; // '-' + } else { + if (part.length > 0) { + result += jpToHiragana(part, sourceMapping, result.length); + part = ''; + } + result += text[i]; + continue; + } + part += String.fromCharCode(c); + } + + if (part.length > 0) { + result += jpToHiragana(part, sourceMapping, result.length); + } + return result; +} + +function jpToHiragana(text, sourceMapping, sourceMappingStart) { + const result = wanakana.toHiragana(text); + + // Generate source mapping + if (Array.isArray(sourceMapping)) { + if (typeof sourceMappingStart !== 'number') { sourceMappingStart = 0; } + let i = 0; + let resultPos = 0; + const ii = text.length; + while (i < ii) { + // Find smallest matching substring + let iNext = i + 1; + let resultPosNext = result.length; + while (iNext < ii) { + const t = wanakana.toHiragana(text.substring(0, iNext)); + if (t === result.substring(0, t.length)) { + resultPosNext = t.length; + break; + } + ++iNext; + } + + // Merge characters + const removals = iNext - i - 1; + if (removals > 0) { + let sum = 0; + const vs = sourceMapping.splice(sourceMappingStart + 1, removals); + for (const v of vs) { sum += v; } + sourceMapping[sourceMappingStart] += sum; + } + ++sourceMappingStart; + + // Empty elements + const additions = resultPosNext - resultPos - 1; + for (let j = 0; j < additions; ++j) { + sourceMapping.splice(sourceMappingStart, 0, 0); + ++sourceMappingStart; + } + + i = iNext; + resultPos = resultPosNext; + } + } + + return result; +} diff --git a/ext/bg/search.html b/ext/bg/search.html index 74afbb68..bb7ac095 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -75,6 +75,7 @@ + @@ -82,7 +83,6 @@ - diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 77bcc359..3480b124 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1084,12 +1084,12 @@ - + diff --git a/ext/mixed/js/japanese.js b/ext/mixed/js/japanese.js deleted file mode 100644 index 0da822d7..00000000 --- a/ext/mixed/js/japanese.js +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2016-2020 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 . - */ - - -const JP_HALFWIDTH_KATAKANA_MAPPING = new Map([ - ['ヲ', 'ヲヺ-'], - ['ァ', 'ァ--'], - ['ィ', 'ィ--'], - ['ゥ', 'ゥ--'], - ['ェ', 'ェ--'], - ['ォ', 'ォ--'], - ['ャ', 'ャ--'], - ['ュ', 'ュ--'], - ['ョ', 'ョ--'], - ['ッ', 'ッ--'], - ['ー', 'ー--'], - ['ア', 'ア--'], - ['イ', 'イ--'], - ['ウ', 'ウヴ-'], - ['エ', 'エ--'], - ['オ', 'オ--'], - ['カ', 'カガ-'], - ['キ', 'キギ-'], - ['ク', 'クグ-'], - ['ケ', 'ケゲ-'], - ['コ', 'コゴ-'], - ['サ', 'サザ-'], - ['シ', 'シジ-'], - ['ス', 'スズ-'], - ['セ', 'セゼ-'], - ['ソ', 'ソゾ-'], - ['タ', 'タダ-'], - ['チ', 'チヂ-'], - ['ツ', 'ツヅ-'], - ['テ', 'テデ-'], - ['ト', 'トド-'], - ['ナ', 'ナ--'], - ['ニ', 'ニ--'], - ['ヌ', 'ヌ--'], - ['ネ', 'ネ--'], - ['ノ', 'ノ--'], - ['ハ', 'ハバパ'], - ['ヒ', 'ヒビピ'], - ['フ', 'フブプ'], - ['ヘ', 'ヘベペ'], - ['ホ', 'ホボポ'], - ['マ', 'マ--'], - ['ミ', 'ミ--'], - ['ム', 'ム--'], - ['メ', 'メ--'], - ['モ', 'モ--'], - ['ヤ', 'ヤ--'], - ['ユ', 'ユ--'], - ['ヨ', 'ヨ--'], - ['ラ', 'ラ--'], - ['リ', 'リ--'], - ['ル', 'ル--'], - ['レ', 'レ--'], - ['ロ', 'ロ--'], - ['ワ', 'ワ--'], - ['ン', 'ン--'] -]); - -const JP_HIRAGANA_RANGE = [0x3040, 0x309f]; -const JP_KATAKANA_RANGE = [0x30a0, 0x30ff]; -const JP_KANA_RANGES = [JP_HIRAGANA_RANGE, JP_KATAKANA_RANGE]; - -const JP_CJK_COMMON_RANGE = [0x4e00, 0x9fff]; -const JP_CJK_RARE_RANGE = [0x3400, 0x4dbf]; -const JP_CJK_RANGES = [JP_CJK_COMMON_RANGE, JP_CJK_RARE_RANGE]; - -const JP_ITERATION_MARK_CHAR_CODE = 0x3005; - -// Japanese character ranges, roughly ordered in order of expected frequency -const JP_JAPANESE_RANGES = [ - JP_HIRAGANA_RANGE, - JP_KATAKANA_RANGE, - - JP_CJK_COMMON_RANGE, - JP_CJK_RARE_RANGE, - - [0xff66, 0xff9f], // Halfwidth katakana - - [0x30fb, 0x30fc], // Katakana punctuation - [0xff61, 0xff65], // Kana punctuation - [0x3000, 0x303f], // CJK punctuation - - [0xff10, 0xff19], // Fullwidth numbers - [0xff21, 0xff3a], // Fullwidth upper case Latin letters - [0xff41, 0xff5a], // Fullwidth lower case Latin letters - - [0xff01, 0xff0f], // Fullwidth punctuation 1 - [0xff1a, 0xff1f], // Fullwidth punctuation 2 - [0xff3b, 0xff3f], // Fullwidth punctuation 3 - [0xff5b, 0xff60], // Fullwidth punctuation 4 - [0xffe0, 0xffee], // Currency markers -]; - - -// Helper functions - -function _jpIsCharCodeInRanges(charCode, ranges) { - for (const [min, max] of ranges) { - if (charCode >= min && charCode <= max) { - return true; - } - } - return false; -} - - -// Character code testing functions - -function jpIsCharCodeKanji(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_CJK_RANGES); -} - -function jpIsCharCodeKana(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_KANA_RANGES); -} - -function jpIsCharCodeJapanese(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_JAPANESE_RANGES); -} - - -// String testing functions - -function jpIsStringEntirelyKana(str) { - if (str.length === 0) { return false; } - for (let i = 0, ii = str.length; i < ii; ++i) { - if (!jpIsCharCodeKana(str.charCodeAt(i))) { - return false; - } - } - return true; -} - -function jpIsStringPartiallyJapanese(str) { - if (str.length === 0) { return false; } - for (let i = 0, ii = str.length; i < ii; ++i) { - if (jpIsCharCodeJapanese(str.charCodeAt(i))) { - return true; - } - } - return false; -} - - -// Conversion functions - -function jpKatakanaToHiragana(text) { - let result = ''; - for (const c of text) { - if (wanakana.isKatakana(c)) { - result += wanakana.toHiragana(c); - } else { - result += c; - } - } - - return result; -} - -function jpHiraganaToKatakana(text) { - let result = ''; - for (const c of text) { - if (wanakana.isHiragana(c)) { - result += wanakana.toKatakana(c); - } else { - result += c; - } - } - - return result; -} - -function jpToRomaji(text) { - return wanakana.toRomaji(text); -} - -function jpConvertReading(expressionFragment, readingFragment, readingMode) { - switch (readingMode) { - case 'hiragana': - return jpKatakanaToHiragana(readingFragment || ''); - case 'katakana': - return jpHiraganaToKatakana(readingFragment || ''); - case 'romaji': - if (readingFragment) { - return jpToRomaji(readingFragment); - } else { - if (jpIsStringEntirelyKana(expressionFragment)) { - return jpToRomaji(expressionFragment); - } - } - return readingFragment; - case 'none': - return null; - default: - return readingFragment; - } -} - -function jpDistributeFurigana(expression, reading) { - const fallback = [{furigana: reading, text: expression}]; - if (!reading) { - return fallback; - } - - let isAmbiguous = false; - const segmentize = (reading, groups) => { - if (groups.length === 0 || isAmbiguous) { - return []; - } - - const group = groups[0]; - if (group.mode === 'kana') { - if (jpKatakanaToHiragana(reading).startsWith(jpKatakanaToHiragana(group.text))) { - const readingLeft = reading.substring(group.text.length); - const segs = segmentize(readingLeft, groups.splice(1)); - if (segs) { - return [{text: group.text}].concat(segs); - } - } - } else { - let foundSegments = null; - for (let i = reading.length; i >= group.text.length; --i) { - const readingUsed = reading.substring(0, i); - const readingLeft = reading.substring(i); - const segs = segmentize(readingLeft, groups.slice(1)); - if (segs) { - if (foundSegments !== null) { - // more than one way to segmentize the tail, mark as ambiguous - isAmbiguous = true; - return null; - } - foundSegments = [{text: group.text, furigana: readingUsed}].concat(segs); - } - // there is only one way to segmentize the last non-kana group - if (groups.length === 1) { - break; - } - } - return foundSegments; - } - }; - - const groups = []; - let modePrev = null; - for (const c of expression) { - const charCode = c.charCodeAt(0); - const modeCurr = jpIsCharCodeKanji(charCode) || charCode === JP_ITERATION_MARK_CHAR_CODE ? 'kanji' : 'kana'; - if (modeCurr === modePrev) { - groups[groups.length - 1].text += c; - } else { - groups.push({mode: modeCurr, text: c}); - modePrev = modeCurr; - } - } - - const segments = segmentize(reading, groups); - if (segments && !isAmbiguous) { - return segments; - } - return fallback; -} - -function jpDistributeFuriganaInflected(expression, reading, source) { - const output = []; - - let stemLength = 0; - const shortest = Math.min(source.length, expression.length); - const sourceHiragana = jpKatakanaToHiragana(source); - const expressionHiragana = jpKatakanaToHiragana(expression); - while (stemLength < shortest && sourceHiragana[stemLength] === expressionHiragana[stemLength]) { - ++stemLength; - } - const offset = source.length - stemLength; - - const stemExpression = source.substring(0, source.length - offset); - const stemReading = reading.substring( - 0, - offset === 0 ? reading.length : reading.length - expression.length + stemLength - ); - for (const segment of jpDistributeFurigana(stemExpression, stemReading)) { - output.push(segment); - } - - if (stemLength !== source.length) { - output.push({text: source.substring(stemLength)}); - } - - return output; -} - -function jpConvertHalfWidthKanaToFullWidth(text, sourceMapping) { - let result = ''; - const ii = text.length; - const hasSourceMapping = Array.isArray(sourceMapping); - - for (let i = 0; i < ii; ++i) { - const c = text[i]; - const mapping = JP_HALFWIDTH_KATAKANA_MAPPING.get(c); - if (typeof mapping !== 'string') { - result += c; - continue; - } - - let index = 0; - switch (text.charCodeAt(i + 1)) { - case 0xff9e: // dakuten - index = 1; - break; - case 0xff9f: // handakuten - index = 2; - break; - } - - let c2 = mapping[index]; - if (index > 0) { - if (c2 === '-') { // invalid - index = 0; - c2 = mapping[0]; - } else { - ++i; - } - } - - if (hasSourceMapping && index > 0) { - index = result.length; - const v = sourceMapping.splice(index + 1, 1)[0]; - sourceMapping[index] += v; - } - result += c2; - } - - return result; -} - -function jpConvertNumericTofullWidth(text) { - let result = ''; - for (let i = 0, ii = text.length; i < ii; ++i) { - let c = text.charCodeAt(i); - if (c >= 0x30 && c <= 0x39) { // ['0', '9'] - c += 0xff10 - 0x30; // 0xff10 = '0' full width - result += String.fromCharCode(c); - } else { - result += text[i]; - } - } - return result; -} - -function jpConvertAlphabeticToKana(text, sourceMapping) { - let part = ''; - let result = ''; - const ii = text.length; - - if (sourceMapping.length === ii) { - sourceMapping.length = ii; - sourceMapping.fill(1); - } - - for (let i = 0; i < ii; ++i) { - // Note: 0x61 is the character code for 'a' - let c = text.charCodeAt(i); - if (c >= 0x41 && c <= 0x5a) { // ['A', 'Z'] - c += (0x61 - 0x41); - } else if (c >= 0x61 && c <= 0x7a) { // ['a', 'z'] - // NOP; c += (0x61 - 0x61); - } else if (c >= 0xff21 && c <= 0xff3a) { // ['A', 'Z'] fullwidth - c += (0x61 - 0xff21); - } else if (c >= 0xff41 && c <= 0xff5a) { // ['a', 'z'] fullwidth - c += (0x61 - 0xff41); - } else if (c === 0x2d || c === 0xff0d) { // '-' or fullwidth dash - c = 0x2d; // '-' - } else { - if (part.length > 0) { - result += jpToHiragana(part, sourceMapping, result.length); - part = ''; - } - result += text[i]; - continue; - } - part += String.fromCharCode(c); - } - - if (part.length > 0) { - result += jpToHiragana(part, sourceMapping, result.length); - } - return result; -} - -function jpToHiragana(text, sourceMapping, sourceMappingStart) { - const result = wanakana.toHiragana(text); - - // Generate source mapping - if (Array.isArray(sourceMapping)) { - if (typeof sourceMappingStart !== 'number') { sourceMappingStart = 0; } - let i = 0; - let resultPos = 0; - const ii = text.length; - while (i < ii) { - // Find smallest matching substring - let iNext = i + 1; - let resultPosNext = result.length; - while (iNext < ii) { - const t = wanakana.toHiragana(text.substring(0, iNext)); - if (t === result.substring(0, t.length)) { - resultPosNext = t.length; - break; - } - ++iNext; - } - - // Merge characters - const removals = iNext - i - 1; - if (removals > 0) { - let sum = 0; - const vs = sourceMapping.splice(sourceMappingStart + 1, removals); - for (const v of vs) { sum += v; } - sourceMapping[sourceMappingStart] += sum; - } - ++sourceMappingStart; - - // Empty elements - const additions = resultPosNext - resultPos - 1; - for (let j = 0; j < additions; ++j) { - sourceMapping.splice(sourceMappingStart, 0, 0); - ++sourceMappingStart; - } - - i = iNext; - resultPos = resultPosNext; - } - } - - return result; -} -- cgit v1.2.3 From 939ad42dacfeff566497f3c2f8e9c64d59b8168d Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 25 Jan 2020 19:00:36 +0200 Subject: add global clipboard monitor that spawns popups TODO: refactor the search page clipboard monitor and popup clipboard monitor to use a common ClipboardMonitor class --- ext/bg/data/options-schema.json | 5 +++++ ext/bg/js/backend.js | 17 +++++++++++++++++ ext/bg/js/options.js | 1 + ext/bg/js/settings/main.js | 1 + ext/bg/settings.html | 4 ++++ 5 files changed, 28 insertions(+) (limited to 'ext/bg/settings.html') diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index 7e12481d..d6207952 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -79,6 +79,7 @@ "type": "object", "required": [ "enable", + "enableClipboardPopups", "resultOutputMode", "debugInfo", "maxResults", @@ -111,6 +112,10 @@ "type": "boolean", "default": true }, + "enableClipboardPopups": { + "type": "boolean", + "default": false + }, "resultOutputMode": { "type": "string", "enum": ["group", "merge", "split"], diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 3cb9ce1d..407dc965 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -34,6 +34,9 @@ class Backend { this.clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); this.popupWindow = null; + this.clipboardPopupTimerId = null; + this.clipboardInterval = 250; + this.clipboardPreviousText = null; this.apiForwarder = new BackendApiForwarder(); } @@ -122,6 +125,20 @@ class Backend { } else { this.mecab.stopListener(); } + + window.clearInterval(this.clipboardPopupTimerId); + if (options.general.enableClipboardPopups) { + this.clipboardPopupTimerId = setInterval(() => { + this._onApiClipboardGet() + .then((result) => { + if (this.clipboardPreviousText === result) { + return; + } + this._onCommandSearch({mode: 'popup', query: result}); + this.clipboardPreviousText = result; + }); + }, this.clipboardInterval); + } } async getOptionsSchema() { diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 78508059..97032660 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -266,6 +266,7 @@ function profileOptionsCreateDefaults() { return { general: { enable: true, + enableClipboardPopups: false, resultOutputMode: 'group', debugInfo: false, maxResults: 32, diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 4492cd42..ad3459f0 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -28,6 +28,7 @@ function getOptionsFullMutable() { async function formRead(options) { options.general.enable = $('#enable').prop('checked'); + options.general.enableClipboardPopups = $('#enable-clipboard-popups').prop('checked'); options.general.showGuide = $('#show-usage-guide').prop('checked'); options.general.compactTags = $('#compact-tags').prop('checked'); options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3480b124..b0fcec2b 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -134,6 +134,10 @@
+
+ +
+
-- cgit v1.2.3 From 4e59c2d55684b5a0b1d9edc580dd4c43bfc46211 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 9 Feb 2020 21:28:40 +0200 Subject: hide native popup option for firefox mobile --- ext/bg/css/settings.css | 14 ++++++++++++++ ext/bg/settings.html | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'ext/bg/settings.html') diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index 815a88fa..d686e8f8 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -222,6 +222,20 @@ html:root[data-operating-system=openbsd] [data-show-for-operating-system~=openbs display: initial; } +html:root[data-browser=edge] [data-hide-for-browser~=edge], +html:root[data-browser=chrome] [data-hide-for-browser~=chrome], +html:root[data-browser=firefox] [data-hide-for-browser~=firefox], +html:root[data-browser=firefox-mobile] [data-hide-for-browser~=firefox-mobile], +html:root[data-operating-system=mac] [data-hide-for-operating-system~=mac], +html:root[data-operating-system=win] [data-hide-for-operating-system~=win], +html:root[data-operating-system=android] [data-hide-for-operating-system~=android], +html:root[data-operating-system=cros] [data-hide-for-operating-system~=cros], +html:root[data-operating-system=linux] [data-hide-for-operating-system~=linux], +html:root[data-operating-system=openbsd] [data-hide-for-operating-system~=openbsd] { + display: none; +} + + @media screen and (max-width: 740px) { .col-xs-6 { float: none; diff --git a/ext/bg/settings.html b/ext/bg/settings.html index b0fcec2b..57616873 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -134,7 +134,7 @@ -
+
-- cgit v1.2.3 From 6c63a17d669b57fdbc1a5a71daf89c6c95e7d5ef Mon Sep 17 00:00:00 2001 From: siikamiika Date: Thu, 6 Feb 2020 04:00:02 +0200 Subject: query parser html templates --- build_tmpl.sh | 2 - build_tmpl_auto.sh | 16 ------ ext/bg/background.html | 1 - ext/bg/js/api.js | 4 ++ ext/bg/js/backend.js | 6 +++ ext/bg/js/search-query-parser-generator.js | 78 ++++++++++++++++++++++++++++++ ext/bg/js/search-query-parser.js | 60 +++++++---------------- ext/bg/js/templates.js | 55 --------------------- ext/bg/query-parser-templates.html | 12 +++++ ext/bg/search.html | 7 +-- ext/bg/settings.html | 1 - ext/mixed/css/display.css | 4 +- ext/mixed/js/api.js | 4 ++ ext/mixed/js/template-handler.js | 53 ++++++++++++++++++++ tmpl/query-parser.html | 27 ----------- 15 files changed, 180 insertions(+), 150 deletions(-) delete mode 100755 build_tmpl.sh delete mode 100755 build_tmpl_auto.sh create mode 100644 ext/bg/js/search-query-parser-generator.js delete mode 100644 ext/bg/js/templates.js create mode 100644 ext/bg/query-parser-templates.html create mode 100644 ext/mixed/js/template-handler.js delete mode 100644 tmpl/query-parser.html (limited to 'ext/bg/settings.html') diff --git a/build_tmpl.sh b/build_tmpl.sh deleted file mode 100755 index e91f8de8..00000000 --- a/build_tmpl.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -handlebars tmpl/*.html -f ext/bg/js/templates.js diff --git a/build_tmpl_auto.sh b/build_tmpl_auto.sh deleted file mode 100755 index 98065cb7..00000000 --- a/build_tmpl_auto.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -DIRECTORY_TO_OBSERVE="tmpl" -BUILD_SCRIPT="build_tmpl.sh" - -function block_for_change { - inotifywait -e modify,move,create,delete $DIRECTORY_TO_OBSERVE -} - -function build { - bash $BUILD_SCRIPT -} - -build -while block_for_change; do - build -done diff --git a/ext/bg/background.html b/ext/bg/background.html index 11023221..7fd1c477 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -37,7 +37,6 @@ - diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index f4be7a0c..cd6a9d18 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -33,6 +33,10 @@ function apiClipboardGet() { return _apiInvoke('clipboardGet'); } +function apiGetQueryParserTemplatesHtml() { + return _apiInvoke('getQueryParserTemplatesHtml'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 668d1fb7..529055d2 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -567,6 +567,11 @@ class Backend { return await requestText(url, 'GET'); } + async _onApiGetQueryParserTemplatesHtml() { + const url = chrome.runtime.getURL('/bg/query-parser-templates.html'); + return await requestText(url, 'GET'); + } + _onApiGetZoom(params, sender) { if (!sender || !sender.tab) { return Promise.reject(new Error('Invalid tab')); @@ -854,6 +859,7 @@ Backend._messageHandlers = new Map([ ['getEnvironmentInfo', (self, ...args) => self._onApiGetEnvironmentInfo(...args)], ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)], ['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)], + ['getQueryParserTemplatesHtml', (self, ...args) => self._onApiGetQueryParserTemplatesHtml(...args)], ['getZoom', (self, ...args) => self._onApiGetZoom(...args)] ]); diff --git a/ext/bg/js/search-query-parser-generator.js b/ext/bg/js/search-query-parser-generator.js new file mode 100644 index 00000000..67a1ccad --- /dev/null +++ b/ext/bg/js/search-query-parser-generator.js @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 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 QueryParserGenerator { + constructor() { + this._templateHandler = null; + this._initialize(); + } + + async _initialize() { + const html = await apiGetQueryParserTemplatesHtml(); + this._templateHandler = new TemplateHandler(html); + } + + createParseResult(terms, preview=false) { + const fragment = document.createDocumentFragment(); + for (const term of terms) { + const termContainer = this._templateHandler.instantiate(preview ? 'term-preview' : 'term'); + for (const segment of term) { + if (!segment.text.trim()) { continue; } + if (!segment.reading || !segment.reading.trim()) { + termContainer.appendChild(this.createSegmentText(segment.text)); + } else { + termContainer.appendChild(this.createSegment(segment)); + } + } + fragment.appendChild(termContainer); + } + return fragment; + } + + createSegment(segment) { + const segmentContainer = this._templateHandler.instantiate('segment'); + const segmentTextContainer = segmentContainer.querySelector('.query-parser-segment-text'); + const segmentReadingContainer = segmentContainer.querySelector('.query-parser-segment-reading'); + segmentTextContainer.appendChild(this.createSegmentText(segment.text)); + segmentReadingContainer.innerText = segment.reading; + return segmentContainer; + } + + createSegmentText(text) { + const fragment = document.createDocumentFragment(); + for (const chr of text) { + const charContainer = this._templateHandler.instantiate('char'); + charContainer.innerText = chr; + fragment.appendChild(charContainer); + } + return fragment; + } + + createParserSelect(parseResults, selectedParser) { + const selectContainer = this._templateHandler.instantiate('select'); + for (const parseResult of parseResults) { + const optionContainer = this._templateHandler.instantiate('select-option'); + optionContainer.value = parseResult.id; + optionContainer.innerText = parseResult.name; + optionContainer.defaultSelected = selectedParser === parseResult.id; + selectContainer.appendChild(optionContainer); + } + return selectContainer; + } +} diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index e8e6d11f..3a93c7e7 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -19,14 +19,16 @@ class QueryParser extends TextScanner { constructor(search) { - super(document.querySelector('#query-parser'), [], [], []); + super(document.querySelector('#query-parser-content'), [], [], []); this.search = search; this.parseResults = []; this.selectedParser = null; - this.queryParser = document.querySelector('#query-parser'); - this.queryParserSelect = document.querySelector('#query-parser-select'); + this.queryParser = document.querySelector('#query-parser-content'); + this.queryParserSelect = document.querySelector('#query-parser-select-container'); + + this.queryParserGenerator = new QueryParserGenerator(); } onError(error) { @@ -64,7 +66,7 @@ class QueryParser extends TextScanner { const selectedParser = e.target.value; this.selectedParser = selectedParser; apiOptionsSet({parsing: {selectedParser}}, this.search.getOptionsContext()); - this.renderParseResult(this.getParseResult()); + this.renderParseResult(); } getMouseEventListeners() { @@ -113,13 +115,13 @@ class QueryParser extends TextScanner { async setText(text) { this.search.setSpinnerVisible(true); - await this.setPreview(text); + this.setPreview(text); this.parseResults = await this.parseText(text); this.refreshSelectedParser(); this.renderParserSelect(); - await this.renderParseResult(); + this.renderParseResult(); this.search.setSpinnerVisible(false); } @@ -146,57 +148,29 @@ class QueryParser extends TextScanner { return results; } - async setPreview(text) { + setPreview(text) { const previewTerms = []; for (let i = 0, ii = text.length; i < ii; i += 2) { const tempText = text.substring(i, i + 2); - previewTerms.push([{text: tempText.split('')}]); + previewTerms.push([{text: tempText}]); } - this.queryParser.innerHTML = await apiTemplateRender('query-parser.html', { - terms: previewTerms, - preview: true - }); + this.queryParser.textContent = ''; + this.queryParser.appendChild(this.queryParserGenerator.createParseResult(previewTerms, true)); } renderParserSelect() { this.queryParserSelect.innerHTML = ''; if (this.parseResults.length > 1) { - const select = document.createElement('select'); - select.classList.add('form-control'); - for (const parseResult of this.parseResults) { - const option = document.createElement('option'); - option.value = parseResult.id; - option.innerText = parseResult.name; - option.defaultSelected = this.selectedParser === parseResult.id; - select.appendChild(option); - } + const select = this.queryParserGenerator.createParserSelect(this.parseResults, this.selectedParser); select.addEventListener('change', this.onParserChange.bind(this)); this.queryParserSelect.appendChild(select); } } - async renderParseResult() { + renderParseResult() { const parseResult = this.getParseResult(); - if (!parseResult) { - this.queryParser.innerHTML = ''; - return; - } - - this.queryParser.innerHTML = await apiTemplateRender( - 'query-parser.html', - {terms: QueryParser.processParseResultForDisplay(parseResult.parsedText)} - ); - } - - static processParseResultForDisplay(result) { - return result.map((term) => { - return term.filter((part) => part.text.trim()).map((part) => { - return { - text: part.text.split(''), - reading: part.reading, - raw: !part.reading || !part.reading.trim() - }; - }); - }); + this.queryParser.textContent = ''; + if (!parseResult) { return; } + this.queryParser.appendChild(this.queryParserGenerator.createParseResult(parseResult.parsedText)); } } diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js deleted file mode 100644 index 2f65be31..00000000 --- a/ext/bg/js/templates.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() { - var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; -templates['query-parser.html'] = template({"1":function(container,depth0,helpers,partials,data) { - var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - - return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.preview : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data})) != null ? stack1 : "") - + ((stack1 = helpers.each.call(alias1,depth0,{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ""; -},"2":function(container,depth0,helpers,partials,data) { - return ""; -},"4":function(container,depth0,helpers,partials,data) { - return ""; -},"6":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = container.invokePartial(partials.part,depth0,{"name":"part","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"8":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.raw : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.program(12, data, 0),"data":data})) != null ? stack1 : ""); -},"9":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.text : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"10":function(container,depth0,helpers,partials,data) { - return "" - + container.escapeExpression(container.lambda(depth0, depth0)) - + ""; -},"12":function(container,depth0,helpers,partials,data) { - var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - - return "" - + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.text : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "" - + container.escapeExpression(((helper = (helper = helpers.reading || (depth0 != null ? depth0.reading : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"reading","hash":{},"data":data}) : helper))) - + ""; -},"14":function(container,depth0,helpers,partials,data,blockParams,depths) { - var stack1; - - return ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"preview":(depths[1] != null ? depths[1].preview : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) { - var stack1; - - return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.terms : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"main_d": function(fn, props, container, depth0, data, blockParams, depths) { - - var decorators = container.decorators; - - fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn; - fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(8, data, 0, blockParams, depths),"inverse":container.noop,"args":["part"],"data":data}) || fn; - return fn; - } - -,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true}); -})(); \ No newline at end of file diff --git a/ext/bg/query-parser-templates.html b/ext/bg/query-parser-templates.html new file mode 100644 index 00000000..f7f6d76c --- /dev/null +++ b/ext/bg/query-parser-templates.html @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/ext/bg/search.html b/ext/bg/search.html index 10e5aa8e..d6336826 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -48,8 +48,8 @@
-
-
+
+

@@ -78,7 +78,6 @@ - @@ -87,7 +86,9 @@ + + diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 57616873..b048a36c 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1097,7 +1097,6 @@ - diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 62e62243..2714de72 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -127,12 +127,12 @@ html:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation user-select: none; } -#query-parser { +#query-parser-content { margin-top: 0.5em; font-size: 2em; } -#query-parser[data-term-spacing=true] .query-parser-term { +#query-parser-content[data-term-spacing=true] .query-parser-term { margin-right: 0.2em; } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 5ec93b01..0b1e7e4f 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -105,6 +105,10 @@ function apiGetDisplayTemplatesHtml() { return _apiInvoke('getDisplayTemplatesHtml'); } +function apiGetQueryParserTemplatesHtml() { + return _apiInvoke('getQueryParserTemplatesHtml'); +} + function apiGetZoom() { return _apiInvoke('getZoom'); } diff --git a/ext/mixed/js/template-handler.js b/ext/mixed/js/template-handler.js new file mode 100644 index 00000000..86e2414f --- /dev/null +++ b/ext/mixed/js/template-handler.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 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 TemplateHandler { + constructor(html) { + this._templates = new Map(); + this._html = html; + this._doc = null; + + this._initialize(); + } + + _initialize() { + this._doc = new DOMParser().parseFromString(this._html, 'text/html'); + for (const template of this._doc.querySelectorAll('template')) { + this._setTemplate(template); + } + } + + _setTemplate(template) { + const idMatch = template.id.match(/^([a-z-]+)-template$/); + if (!idMatch) { + throw new Error(`Invalid template ID: ${template.id}`); + } + this._templates.set(idMatch[1], template); + } + + instantiate(name) { + const template = this._templates.get(name); + return document.importNode(template.content.firstChild, true); + } + + instantiateFragment(name) { + const template = this._templates.get(name); + return document.importNode(template.content, true); + } +} diff --git a/tmpl/query-parser.html b/tmpl/query-parser.html deleted file mode 100644 index db98b5ff..00000000 --- a/tmpl/query-parser.html +++ /dev/null @@ -1,27 +0,0 @@ -{{~#*inline "term"~}} -{{~#if preview~}} - -{{~else~}} - -{{~/if~}} -{{~#each this~}} -{{> part }} -{{~/each~}} - -{{~/inline~}} - -{{~#*inline "part"~}} -{{~#if raw~}} -{{~#each text~}} -{{this}} -{{~/each~}} -{{~else~}} -{{~#each text~}} -{{this}} -{{~/each~}}{{reading}} -{{~/if~}} -{{~/inline~}} - -{{~#each terms~}} -{{> term preview=../preview }} -{{~/each~}} -- cgit v1.2.3