diff options
Diffstat (limited to 'ext/js/templates/sandbox/template-renderer.js')
-rw-r--r-- | ext/js/templates/sandbox/template-renderer.js | 101 |
1 files changed, 83 insertions, 18 deletions
diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index 8d8a2765..239240b6 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -17,50 +17,78 @@ */ import {Handlebars} from '../../../lib/handlebars.js'; +import {ExtensionError} from '../../core/extension-error.js'; export class TemplateRenderer { constructor() { + /** @type {Map<string, import('handlebars').TemplateDelegate<import('anki-templates').NoteData>>} */ this._cache = new Map(); + /** @type {number} */ this._cacheMaxSize = 5; + /** @type {Map<import('anki-templates').RenderMode, import('template-renderer').DataType>} */ this._dataTypes = new Map(); + /** @type {?((noteData: import('anki-templates').NoteData) => import('template-renderer').SetupCallbackResult)} */ this._renderSetup = null; + /** @type {?((noteData: import('anki-templates').NoteData) => import('template-renderer').CleanupCallbackResult)} */ this._renderCleanup = null; } + /** + * @param {import('template-renderer').HelperFunctionsDescriptor} helpers + */ registerHelpers(helpers) { for (const [name, helper] of helpers) { this._registerHelper(name, helper); } } - registerDataType(name, {modifier=null, composeData=null}) { + /** + * @param {import('anki-templates').RenderMode} name + * @param {import('template-renderer').DataType} details + */ + registerDataType(name, {modifier, composeData}) { this._dataTypes.set(name, {modifier, composeData}); } + /** + * @param {?((noteData: import('anki-templates').NoteData) => import('template-renderer').SetupCallbackResult)} setup + * @param {?((noteData: import('anki-templates').NoteData) => import('template-renderer').CleanupCallbackResult)} cleanup + */ setRenderCallbacks(setup, cleanup) { this._renderSetup = setup; this._renderCleanup = cleanup; } + /** + * @param {string} template + * @param {import('template-renderer').PartialOrCompositeRenderData} data + * @param {import('anki-templates').RenderMode} type + * @returns {import('template-renderer').RenderResult} + */ render(template, data, type) { const instance = this._getTemplateInstance(template); - data = this._getModifiedData(data, void 0, type); - return this._renderTemplate(instance, data); + const modifiedData = this._getModifiedData(data, void 0, type); + return this._renderTemplate(instance, modifiedData); } + /** + * @param {import('template-renderer').RenderMultiItem[]} items + * @returns {import('core').Response<import('template-renderer').RenderResult>[]} + */ renderMulti(items) { + /** @type {import('core').Response<import('template-renderer').RenderResult>[]} */ const results = []; for (const {template, templateItems} of items) { const instance = this._getTemplateInstance(template); for (const {type, commonData, datas} of templateItems) { - for (let data of datas) { + for (const data of datas) { let result; try { - data = this._getModifiedData(data, commonData, type); - result = this._renderTemplate(instance, data); - result = {result}; + const data2 = this._getModifiedData(data, commonData, type); + const renderResult = this._renderTemplate(instance, data2); + result = {result: renderResult}; } catch (error) { - result = {error}; + result = {error: ExtensionError.serialize(error)}; } results.push(result); } @@ -69,29 +97,46 @@ export class TemplateRenderer { return results; } + /** + * @param {import('template-renderer').CompositeRenderData} data + * @param {import('anki-templates').RenderMode} type + * @returns {import('anki-templates').NoteData} + */ getModifiedData(data, type) { return this._getModifiedData(data, void 0, type); } // Private + /** + * @param {string} template + * @returns {import('handlebars').TemplateDelegate<import('anki-templates').NoteData>} + */ _getTemplateInstance(template) { const cache = this._cache; let instance = cache.get(template); if (typeof instance === 'undefined') { this._updateCacheSize(this._cacheMaxSize - 1); - instance = Handlebars.compileAST(template); + instance = /** @type {import('handlebars').TemplateDelegate<import('anki-templates').NoteData>} */ (Handlebars.compileAST(template)); cache.set(template, instance); } return instance; } + /** + * @param {import('handlebars').TemplateDelegate<import('anki-templates').NoteData>} instance + * @param {import('anki-templates').NoteData} data + * @returns {import('template-renderer').RenderResult} + */ _renderTemplate(instance, data) { const renderSetup = this._renderSetup; const renderCleanup = this._renderCleanup; + /** @type {string} */ let result; + /** @type {?import('template-renderer').SetupCallbackResult} */ let additions1; + /** @type {?import('template-renderer').CleanupCallbackResult} */ let additions2; try { additions1 = (typeof renderSetup === 'function' ? renderSetup(data) : null); @@ -99,28 +144,36 @@ export class TemplateRenderer { } finally { additions2 = (typeof renderCleanup === 'function' ? renderCleanup(data) : null); } - return Object.assign({result}, additions1, additions2); + return /** @type {import('template-renderer').RenderResult} */ (Object.assign({result}, additions1, additions2)); } + /** + * @param {import('template-renderer').PartialOrCompositeRenderData} data + * @param {import('anki-note-builder').CommonData|undefined} commonData + * @param {import('anki-templates').RenderMode} type + * @returns {import('anki-templates').NoteData} + * @throws {Error} + */ _getModifiedData(data, commonData, type) { if (typeof type === 'string') { const typeInfo = this._dataTypes.get(type); if (typeof typeInfo !== 'undefined') { if (typeof commonData !== 'undefined') { const {composeData} = typeInfo; - if (typeof composeData === 'function') { - data = composeData(data, commonData); - } + data = composeData(data, commonData); + } else if (typeof data.commonData === 'undefined') { + throw new Error('Incomplete data'); } const {modifier} = typeInfo; - if (typeof modifier === 'function') { - data = modifier(data); - } + return modifier(/** @type {import('template-renderer').CompositeRenderData} */ (data)); } } - return data; + throw new Error(`Invalid type: ${type}`); } + /** + * @param {number} maxSize + */ _updateCacheSize(maxSize) { const cache = this._cache; let removeCount = cache.size - maxSize; @@ -132,9 +185,21 @@ export class TemplateRenderer { } } + /** + * @param {string} name + * @param {import('template-renderer').HelperFunction} helper + */ _registerHelper(name, helper) { + /** + * @this {unknown} + * @param {unknown[]} args + * @returns {unknown} + */ function wrapper(...args) { - return helper(this, ...args); + const argCountM1 = Math.max(0, args.length - 1); + const options = /** @type {import('handlebars').HelperOptions} */ (args[argCountM1]); + args.length = argCountM1; + return helper(args, this, options); } Handlebars.registerHelper(name, wrapper); } |