diff options
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/bg/background.html | 2 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 5 | ||||
| -rw-r--r-- | ext/bg/js/handlebars.js | 172 | ||||
| -rw-r--r-- | ext/bg/js/template-renderer.js | 202 | 
4 files changed, 206 insertions, 175 deletions
| diff --git a/ext/bg/background.html b/ext/bg/background.html index d51858a7..f2d01e85 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -34,12 +34,12 @@          <script src="/bg/js/dictionary-importer.js"></script>          <script src="/bg/js/deinflector.js"></script>          <script src="/bg/js/dictionary.js"></script> -        <script src="/bg/js/handlebars.js"></script>          <script src="/bg/js/json-schema.js"></script>          <script src="/bg/js/media-utility.js"></script>          <script src="/bg/js/options.js"></script>          <script src="/bg/js/profile-conditions.js"></script>          <script src="/bg/js/request.js"></script> +        <script src="/bg/js/template-renderer.js"></script>          <script src="/bg/js/text-source-map.js"></script>          <script src="/bg/js/translator.js"></script>          <script src="/bg/js/util.js"></script> diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 0c7a0301..93ba620f 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -27,10 +27,10 @@   * JsonSchema   * Mecab   * ObjectPropertyAccessor + * TemplateRenderer   * Translator   * conditionsTestValue   * dictTermsSort - * handlebarsRenderDynamic   * jp   * optionsLoad   * optionsSave @@ -63,6 +63,7 @@ class Backend {              audioSystem: this.audioSystem,              renderTemplate: this._renderTemplate.bind(this)          }); +        this._templateRenderer = new TemplateRenderer();          const url = (typeof window === 'object' && window !== null ? window.location.href : '');          this.optionsContext = {depth: 0, url}; @@ -1230,7 +1231,7 @@ class Backend {      }      async _renderTemplate(template, data) { -        return handlebarsRenderDynamic(template, data); +        return await this._templateRenderer.render(template, data);      }      _getTemplates(options) { diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js deleted file mode 100644 index 822174e2..00000000 --- a/ext/bg/js/handlebars.js +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2016-2020  Yomichan Authors - * - * 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 <https://www.gnu.org/licenses/>. - */ - -/* global - * Handlebars - * jp - */ - -function handlebarsEscape(text) { -    return Handlebars.Utils.escapeExpression(text); -} - -function handlebarsDumpObject(options) { -    const dump = JSON.stringify(options.fn(this), null, 4); -    return handlebarsEscape(dump); -} - -function handlebarsFurigana(options) { -    const definition = options.fn(this); -    const segs = jp.distributeFurigana(definition.expression, definition.reading); - -    let result = ''; -    for (const seg of segs) { -        if (seg.furigana) { -            result += `<ruby>${seg.text}<rt>${seg.furigana}</rt></ruby>`; -        } else { -            result += seg.text; -        } -    } - -    return result; -} - -function handlebarsFuriganaPlain(options) { -    const definition = options.fn(this); -    const segs = jp.distributeFurigana(definition.expression, definition.reading); - -    let result = ''; -    for (const seg of segs) { -        if (seg.furigana) { -            result += ` ${seg.text}[${seg.furigana}]`; -        } else { -            result += seg.text; -        } -    } - -    return result.trimLeft(); -} - -function handlebarsKanjiLinks(options) { -    let result = ''; -    for (const c of options.fn(this)) { -        if (jp.isCodePointKanji(c.codePointAt(0))) { -            result += `<a href="#" class="kanji-link">${c}</a>`; -        } else { -            result += c; -        } -    } - -    return result; -} - -function handlebarsMultiLine(options) { -    return options.fn(this).split('\n').join('<br>'); -} - -function handlebarsSanitizeCssClass(options) { -    return options.fn(this).replace(/[^_a-z0-9\u00a0-\uffff]/ig, '_'); -} - -function handlebarsRegexReplace(...args) { -    // Usage: -    // {{#regexReplace regex string [flags]}}content{{/regexReplace}} -    // regex: regular expression string -    // string: string to replace -    // flags: optional flags for regular expression -    //   e.g. "i" for case-insensitive, "g" for replace all -    let value = args[args.length - 1].fn(this); -    if (args.length >= 3) { -        try { -            const flags = args.length > 3 ? args[2] : 'g'; -            const regex = new RegExp(args[0], flags); -            value = value.replace(regex, args[1]); -        } catch (e) { -            return `${e}`; -        } -    } -    return value; -} - -function handlebarsRegexMatch(...args) { -    // Usage: -    // {{#regexMatch regex [flags]}}content{{/regexMatch}} -    // regex: regular expression string -    // flags: optional flags for regular expression -    //   e.g. "i" for case-insensitive, "g" for match all -    let value = args[args.length - 1].fn(this); -    if (args.length >= 2) { -        try { -            const flags = args.length > 2 ? args[1] : ''; -            const regex = new RegExp(args[0], flags); -            const parts = []; -            value.replace(regex, (g0) => parts.push(g0)); -            value = parts.join(''); -        } catch (e) { -            return `${e}`; -        } -    } -    return value; -} - -function handlebarsMergeTags(object, isGroupMode, isMergeMode) { -    const tagSources = []; -    if (isGroupMode || isMergeMode) { -        for (const definition of object.definitions) { -            tagSources.push(definition.definitionTags); -        } -    } else { -        tagSources.push(object.definitionTags); -    } - -    const tags = new Set(); -    for (const tagSource of tagSources) { -        for (const tag of tagSource) { -            tags.add(tag.name); -        } -    } - -    return [...tags].join(', '); -} - -function handlebarsRegisterHelpers() { -    if (Handlebars.partials !== Handlebars.templates) { -        Handlebars.partials = Handlebars.templates; -        Handlebars.registerHelper('dumpObject', handlebarsDumpObject); -        Handlebars.registerHelper('furigana', handlebarsFurigana); -        Handlebars.registerHelper('furiganaPlain', handlebarsFuriganaPlain); -        Handlebars.registerHelper('kanjiLinks', handlebarsKanjiLinks); -        Handlebars.registerHelper('multiLine', handlebarsMultiLine); -        Handlebars.registerHelper('sanitizeCssClass', handlebarsSanitizeCssClass); -        Handlebars.registerHelper('regexReplace', handlebarsRegexReplace); -        Handlebars.registerHelper('regexMatch', handlebarsRegexMatch); -        Handlebars.registerHelper('mergeTags', handlebarsMergeTags); -    } -} - -function handlebarsRenderDynamic(template, data) { -    handlebarsRegisterHelpers(); -    const cache = handlebarsRenderDynamic._cache; -    let instance = cache.get(template); -    if (typeof instance === 'undefined') { -        instance = Handlebars.compile(template); -        cache.set(template, instance); -    } - -    return instance(data).trim(); -} -handlebarsRenderDynamic._cache = new Map(); diff --git a/ext/bg/js/template-renderer.js b/ext/bg/js/template-renderer.js new file mode 100644 index 00000000..f4b50c3d --- /dev/null +++ b/ext/bg/js/template-renderer.js @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2016-2020  Yomichan Authors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +/* global + * Handlebars + * jp + */ + +class TemplateRenderer { +    constructor() { +        this._cache = new Map(); +        this._cacheMaxSize = 5; +        this._helpersRegistered = false; +    } + +    async render(template, data) { +        if (!this._helpersRegistered) { +            this._registerHelpers(); +            this._helpersRegistered = true; +        } + +        const cache = this._cache; +        let instance = cache.get(template); +        if (typeof instance === 'undefined') { +            this._updateCacheSize(this._cacheMaxSize - 1); +            instance = Handlebars.compile(template); +            cache.set(template, instance); +        } + +        return instance(data).trim(); +    } + +    // Private + +    _updateCacheSize(maxSize) { +        const cache = this._cache; +        let removeCount = cache.size - maxSize; +        if (removeCount <= 0) { return; } + +        for (const key of cache.keys()) { +            cache.delete(key); +            if (--removeCount <= 0) { break; } +        } +    } + +    _registerHelpers() { +        Handlebars.partials = Handlebars.templates; + +        const helpers = [ +            ['dumpObject',       this._dumpObject.bind(this)], +            ['furigana',         this._furigana.bind(this)], +            ['furiganaPlain',    this._furiganaPlain.bind(this)], +            ['kanjiLinks',       this._kanjiLinks.bind(this)], +            ['multiLine',        this._multiLine.bind(this)], +            ['sanitizeCssClass', this._sanitizeCssClass.bind(this)], +            ['regexReplace',     this._regexReplace.bind(this)], +            ['regexMatch',       this._regexMatch.bind(this)], +            ['mergeTags',        this._mergeTags.bind(this)] +        ]; + +        for (const [name, helper] of helpers) { +            Handlebars.registerHelper(name, helper); +        } +    } + +    _escape(text) { +        return Handlebars.Utils.escapeExpression(text); +    } + +    _dumpObject(options) { +        const dump = JSON.stringify(options.fn(this), null, 4); +        return this._escape(dump); +    } + +    _furigana(options) { +        const definition = options.fn(this); +        const segs = jp.distributeFurigana(definition.expression, definition.reading); + +        let result = ''; +        for (const seg of segs) { +            if (seg.furigana) { +                result += `<ruby>${seg.text}<rt>${seg.furigana}</rt></ruby>`; +            } else { +                result += seg.text; +            } +        } + +        return result; +    } + +    _furiganaPlain(options) { +        const definition = options.fn(this); +        const segs = jp.distributeFurigana(definition.expression, definition.reading); + +        let result = ''; +        for (const seg of segs) { +            if (seg.furigana) { +                result += ` ${seg.text}[${seg.furigana}]`; +            } else { +                result += seg.text; +            } +        } + +        return result.trimLeft(); +    } + +    _kanjiLinks(options) { +        let result = ''; +        for (const c of options.fn(this)) { +            if (jp.isCodePointKanji(c.codePointAt(0))) { +                result += `<a href="#" class="kanji-link">${c}</a>`; +            } else { +                result += c; +            } +        } + +        return result; +    } + +    _multiLine(options) { +        return options.fn(this).split('\n').join('<br>'); +    } + +    _sanitizeCssClass(options) { +        return options.fn(this).replace(/[^_a-z0-9\u00a0-\uffff]/ig, '_'); +    } + +    _regexReplace(...args) { +        // Usage: +        // {{#regexReplace regex string [flags]}}content{{/regexReplace}} +        // regex: regular expression string +        // string: string to replace +        // flags: optional flags for regular expression +        //   e.g. "i" for case-insensitive, "g" for replace all +        let value = args[args.length - 1].fn(this); +        if (args.length >= 3) { +            try { +                const flags = args.length > 3 ? args[2] : 'g'; +                const regex = new RegExp(args[0], flags); +                value = value.replace(regex, args[1]); +            } catch (e) { +                return `${e}`; +            } +        } +        return value; +    } + +    _regexMatch(...args) { +        // Usage: +        // {{#regexMatch regex [flags]}}content{{/regexMatch}} +        // regex: regular expression string +        // flags: optional flags for regular expression +        //   e.g. "i" for case-insensitive, "g" for match all +        let value = args[args.length - 1].fn(this); +        if (args.length >= 2) { +            try { +                const flags = args.length > 2 ? args[1] : ''; +                const regex = new RegExp(args[0], flags); +                const parts = []; +                value.replace(regex, (g0) => parts.push(g0)); +                value = parts.join(''); +            } catch (e) { +                return `${e}`; +            } +        } +        return value; +    } + +    _mergeTags(object, isGroupMode, isMergeMode) { +        const tagSources = []; +        if (isGroupMode || isMergeMode) { +            for (const definition of object.definitions) { +                tagSources.push(definition.definitionTags); +            } +        } else { +            tagSources.push(object.definitionTags); +        } + +        const tags = new Set(); +        for (const tagSource of tagSources) { +            for (const tag of tagSource) { +                tags.add(tag.name); +            } +        } + +        return [...tags].join(', '); +    } +} |