Anki support for structured-content (#1786)

* Update how glossary text is formatted

* Update structured content and image generation

* Pass root data to _createStructuredContentGenerator

* Implement media URLs

* Update documentation

* Update options util

* Apply styles to content

* Improve HTML normalization

* Update DatabaseVM.fetch function

* Update test

* Update test data
This commit is contained in:
toasted-nutbread 2021-07-02 22:46:38 -04:00 committed by GitHub
parent a4715935cb
commit ca97e38bd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 257 additions and 33 deletions

View File

@ -84,7 +84,13 @@ class Blob {
}
async function fetch(url2) {
const filePath = url.fileURLToPath(url2);
const extDir = path.join(__dirname, '..', 'ext');
let filePath;
try {
filePath = url.fileURLToPath(url2);
} catch (e) {
filePath = path.resolve(extDir, url2.replace(/^[/\\]/, ''));
}
await Promise.resolve();
const content = fs.readFileSync(filePath, {encoding: null});
return {

View File

@ -645,6 +645,35 @@ Returns an array representing the different pitch categories for a specific term
</details>
### `formatGlossary`
Formats a glossary entry to a HTML content string. This helper handles image and
structured-content generation.
<details>
<summary>Syntax:</summary>
<code>{{#formatGlossary <i>dictionary</i>}}{{{definitionEntry}}}{{/pitchCategories}}</code><br>
* _`@dictionary`_ <br>
The dictionary that the glossary entry belongs to.
* _`@definitionEntry`_ <br>
The definition entry object in raw form.
</details>
<details>
<summary>Example:</summary>
```handlebars
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{/each}}
```
Output:
```html
Here is the content of a gloss, which may include formatted HTML.
```
</details>
## Legacy Helpers
Yomichan has historically used Handlebars templates to generate the HTML used on the search page and results popup.

View File

@ -0,0 +1,17 @@
{{<<<<<<<}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{/each}}
{{=======}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{/each}}
{{>>>>>>>}}
{{<<<<<<<}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{=======}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{#unless @last}} | {{/unless}}{{/each}}
{{>>>>>>>}}
{{<<<<<<<}}
{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}
{{=======}}
{{#each glossary}}<li>{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}</li>{{/each}}
{{>>>>>>>}}

View File

@ -21,11 +21,11 @@
{{~#if only~}}({{#each only}}{{.}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{~#if (op "<=" glossary.length 1)~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{/each}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{/each}}
{{~else if @root.compactGlossaries~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
<ul>{{#each glossary}}<li>{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}</li>{{/each}}</ul>
{{~/if~}}
{{~#set "previousDictionary" dictionary~}}{{~/set~}}
{{/inline}}

View File

@ -461,7 +461,8 @@ class OptionsUtil {
{async: false, update: this._updateVersion9.bind(this)},
{async: true, update: this._updateVersion10.bind(this)},
{async: false, update: this._updateVersion11.bind(this)},
{async: true, update: this._updateVersion12.bind(this)}
{async: true, update: this._updateVersion12.bind(this)},
{async: true, update: this._updateVersion13.bind(this)}
];
}
@ -844,4 +845,11 @@ class OptionsUtil {
}
return options;
}
async _updateVersion13(options) {
// Version 13 changes:
// Handlebars templates updated to use formatGlossary.
await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v13.handlebars');
return options;
}
}

View File

@ -72,12 +72,6 @@ class CssStyleApplier {
element.removeAttribute('style');
}
}
for (const element of elements) {
const {dataset} = element;
for (const key of Object.keys(dataset)) {
delete dataset[key];
}
}
}
// Private

View File

@ -17,14 +17,17 @@
/* globals
* AnkiNoteDataCreator
* CssStyleApplier
* JapaneseUtil
* TemplateRenderer
* TemplateRendererFrameApi
*/
(() => {
(async () => {
const cssStyleApplier = new CssStyleApplier('/data/structured-content-style.json');
await cssStyleApplier.prepare();
const japaneseUtil = new JapaneseUtil(null);
const templateRenderer = new TemplateRenderer(japaneseUtil);
const templateRenderer = new TemplateRenderer(japaneseUtil, cssStyleApplier);
const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil);
templateRenderer.registerDataType('ankiNote', {
modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData),

View File

@ -18,11 +18,13 @@
/* global
* DictionaryDataUtil
* Handlebars
* StructuredContentGenerator
*/
class TemplateRenderer {
constructor(japaneseUtil) {
constructor(japaneseUtil, cssStyleApplier) {
this._japaneseUtil = japaneseUtil;
this._cssStyleApplier = cssStyleApplier;
this._cache = new Map();
this._cacheMaxSize = 5;
this._helpersRegistered = false;
@ -31,6 +33,7 @@ class TemplateRenderer {
this._requirements = null;
this._cleanupCallbacks = null;
this._customData = null;
this._temporaryElement = null;
}
registerDataType(name, {modifier=null, composeData=null}) {
@ -161,7 +164,8 @@ class TemplateRenderer {
['typeof', this._getTypeof.bind(this)],
['join', this._join.bind(this)],
['concat', this._concat.bind(this)],
['pitchCategories', this._pitchCategories.bind(this)]
['pitchCategories', this._pitchCategories.bind(this)],
['formatGlossary', this._formatGlossary.bind(this)]
];
for (const [name, helper] of helpers) {
@ -244,8 +248,12 @@ class TemplateRenderer {
return result;
}
_stringToMultiLineHtml(string) {
return string.split('\n').join('<br>');
}
_multiLine(context, options) {
return options.fn(context).split('\n').join('<br>');
return this._stringToMultiLineHtml(options.fn(context));
}
_sanitizeCssClass(context, options) {
@ -497,4 +505,130 @@ class TemplateRenderer {
}
return [...categories];
}
_getTemporaryElement() {
let element = this._temporaryElement;
if (element === null) {
element = document.createElement('div');
this._temporaryElement = element;
}
return element;
}
_getHtml(node) {
const container = this._getTemporaryElement();
container.appendChild(node);
this._normalizeHtml(container);
const result = container.innerHTML;
container.textContent = '';
return result;
}
_normalizeHtml(root) {
const {ELEMENT_NODE, TEXT_NODE} = Node;
const treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
const elements = [];
const textNodes = [];
while (true) {
const node = treeWalker.nextNode();
if (node === null) { break; }
switch (node.nodeType) {
case ELEMENT_NODE:
elements.push(node);
break;
case TEXT_NODE:
textNodes.push(node);
break;
}
}
this._cssStyleApplier.applyClassStyles(elements);
for (const element of elements) {
const {dataset} = element;
for (const key of Object.keys(dataset)) {
delete dataset[key];
}
}
for (const textNode of textNodes) {
this._replaceNewlines(textNode);
}
}
_replaceNewlines(textNode) {
const parts = textNode.nodeValue.split('\n');
if (parts.length <= 1) { return; }
const {parentNode} = textNode;
if (parentNode === null) { return; }
const fragment = document.createDocumentFragment();
for (let i = 0, ii = parts.length; i < ii; ++i) {
if (i > 0) { fragment.appendChild(document.createElement('br')); }
fragment.appendChild(document.createTextNode(parts[i]));
}
parentNode.replaceChild(fragment, textNode);
}
_getDictionaryMedia(data, dictionary, path) {
const {media} = data;
if (typeof media === 'object' && media !== null && Object.prototype.hasOwnProperty.call(media, 'dictionaryMedia')) {
const {dictionaryMedia} = media;
if (typeof dictionaryMedia === 'object' && dictionaryMedia !== null && Object.prototype.hasOwnProperty.call(dictionaryMedia, dictionary)) {
const dictionaryMedia2 = dictionaryMedia[dictionary];
if (Object.prototype.hasOwnProperty.call(dictionaryMedia2, path)) {
return dictionaryMedia2[path];
}
}
}
return null;
}
_createStructuredContentGenerator(data) {
const mediaLoader = {
loadMedia: async (path, dictionary, onLoad, onUnload) => {
const imageUrl = this._getDictionaryMedia(data, dictionary, path);
if (imageUrl !== null) {
onLoad(imageUrl);
this._cleanupCallbacks.push(() => onUnload(true));
} else {
let set = this._customData.requiredDictionaryMedia;
if (typeof set === 'undefined') {
set = new Set();
this._customData.requiredDictionaryMedia = set;
}
const key = JSON.stringify([dictionary, path]);
if (!set.has(key)) {
set.add(key);
this._requirements.push({
type: 'dictionaryMedia',
dictionary,
path
});
}
}
}
};
return new StructuredContentGenerator(mediaLoader, document);
}
_formatGlossary(context, dictionary, options) {
const data = options.data.root;
const content = options.fn(context);
if (typeof content === 'string') { return this._stringToMultiLineHtml(content); }
if (!(typeof content === 'object' && content !== null)) { return ''; }
switch (content.type) {
case 'image': return this._formatGlossaryImage(content, dictionary, data);
case 'structured-content': return this._formatStructuredContent(content, dictionary, data);
}
return '';
}
_formatGlossaryImage(content, dictionary, data) {
const structuredContentGenerator = this._createStructuredContentGenerator(data);
const node = structuredContentGenerator.createDefinitionImage(content, dictionary);
return this._getHtml(node);
}
_formatStructuredContent(content, dictionary, data) {
const structuredContentGenerator = this._createStructuredContentGenerator(data);
const node = structuredContentGenerator.createStructuredContent(content.content, dictionary);
return node !== null ? this._getHtml(node) : '';
}
}

View File

@ -18,6 +18,8 @@
<script src="/lib/handlebars.min.js"></script>
<script src="/js/data/anki-note-data-creator.js"></script>
<script src="/js/display/structured-content-generator.js"></script>
<script src="/js/dom/css-style-applier.js"></script>
<script src="/js/language/dictionary-data-util.js"></script>
<script src="/js/language/japanese-util.js"></script>
<script src="/js/templates/template-renderer.js"></script>

View File

@ -587,9 +587,9 @@
"frequencies": "",
"furigana": "<ruby>画像<rt>がぞう</rt></ruby>",
"furigana-plain": "画像[がぞう]",
"glossary": "<div style=\"text-align: left;\"><i>(n, Test Dictionary 2)</i> <ul><li>gazou definition 1</li><li>[object Object]</li></ul></div>",
"glossary-brief": "<div style=\"text-align: left;\"><ul><li>gazou definition 1</li><li>[object Object]</li></ul></div>",
"glossary-no-dictionary": "<div style=\"text-align: left;\"><i>(n)</i> <ul><li>gazou definition 1</li><li>[object Object]</li></ul></div>",
"glossary": "<div style=\"text-align: left;\"><i>(n, Test Dictionary 2)</i> <ul><li>gazou definition 1</li><li><a target=\"_blank\" rel=\"noreferrer noopener\" style=\"cursor:inherit;display:inline-block;position:relative;line-height:1;max-width:100%;color:inherit;\"><span style=\"display:inline-block;white-space:nowrap;max-width:100%;max-height:100vh;position:relative;vertical-align:top;line-height:0;overflow:hidden;font-size:1px;width: 350em;\"><span style=\"display:inline-block;width:0;vertical-align:top;font-size:0;padding-top: 100%;\"></span><span style=\"--image:none;position:absolute;left:0;top:0;width:100%;height:100%;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center center;-webkit-mask-mode:alpha;-webkit-mask-size:contain;-webkit-mask-image:var(--image);mask-repeat:no-repeat;mask-position:center center;mask-mode:alpha;mask-size:contain;mask-image:var(--image);background-color:currentColor;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;display:none;\"></span><img alt=\"\" style=\"display:inline-block;vertical-align:top;object-fit:contain;border:none;outline:none;position:absolute;left:0;top:0;width:100%;height:100%;display:none;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;\"><span style=\"position:absolute;left:0;top:0;width:100%;height:100%;display:table;table-layout:fixed;white-space:normal;font-size:initial;line-height:initial;color:initial;\"></span></span><span style=\"display:none;line-height:initial;\">Image</span></a></li></ul></div>",
"glossary-brief": "<div style=\"text-align: left;\"><ul><li>gazou definition 1</li><li><a target=\"_blank\" rel=\"noreferrer noopener\" style=\"cursor:inherit;display:inline-block;position:relative;line-height:1;max-width:100%;color:inherit;\"><span style=\"display:inline-block;white-space:nowrap;max-width:100%;max-height:100vh;position:relative;vertical-align:top;line-height:0;overflow:hidden;font-size:1px;width: 350em;\"><span style=\"display:inline-block;width:0;vertical-align:top;font-size:0;padding-top: 100%;\"></span><span style=\"--image:none;position:absolute;left:0;top:0;width:100%;height:100%;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center center;-webkit-mask-mode:alpha;-webkit-mask-size:contain;-webkit-mask-image:var(--image);mask-repeat:no-repeat;mask-position:center center;mask-mode:alpha;mask-size:contain;mask-image:var(--image);background-color:currentColor;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;display:none;\"></span><img alt=\"\" style=\"display:inline-block;vertical-align:top;object-fit:contain;border:none;outline:none;position:absolute;left:0;top:0;width:100%;height:100%;display:none;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;\"><span style=\"position:absolute;left:0;top:0;width:100%;height:100%;display:table;table-layout:fixed;white-space:normal;font-size:initial;line-height:initial;color:initial;\"></span></span><span style=\"display:none;line-height:initial;\">Image</span></a></li></ul></div>",
"glossary-no-dictionary": "<div style=\"text-align: left;\"><i>(n)</i> <ul><li>gazou definition 1</li><li><a target=\"_blank\" rel=\"noreferrer noopener\" style=\"cursor:inherit;display:inline-block;position:relative;line-height:1;max-width:100%;color:inherit;\"><span style=\"display:inline-block;white-space:nowrap;max-width:100%;max-height:100vh;position:relative;vertical-align:top;line-height:0;overflow:hidden;font-size:1px;width: 350em;\"><span style=\"display:inline-block;width:0;vertical-align:top;font-size:0;padding-top: 100%;\"></span><span style=\"--image:none;position:absolute;left:0;top:0;width:100%;height:100%;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center center;-webkit-mask-mode:alpha;-webkit-mask-size:contain;-webkit-mask-image:var(--image);mask-repeat:no-repeat;mask-position:center center;mask-mode:alpha;mask-size:contain;mask-image:var(--image);background-color:currentColor;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;display:none;\"></span><img alt=\"\" style=\"display:inline-block;vertical-align:top;object-fit:contain;border:none;outline:none;position:absolute;left:0;top:0;width:100%;height:100%;display:none;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;\"><span style=\"position:absolute;left:0;top:0;width:100%;height:100%;display:table;table-layout:fixed;white-space:normal;font-size:initial;line-height:initial;color:initial;\"></span></span><span style=\"display:none;line-height:initial;\">Image</span></a></li></ul></div>",
"part-of-speech": "Noun",
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
@ -1042,9 +1042,9 @@
"frequencies": "",
"furigana": "<ruby>画像<rt>がぞう</rt></ruby>",
"furigana-plain": "画像[がぞう]",
"glossary": "<div style=\"text-align: left;\"><i>(n, Test Dictionary 2)</i> <ul><li>gazou definition 1</li><li>[object Object]</li></ul></div>",
"glossary-brief": "<div style=\"text-align: left;\"><ul><li>gazou definition 1</li><li>[object Object]</li></ul></div>",
"glossary-no-dictionary": "<div style=\"text-align: left;\"><i>(n)</i> <ul><li>gazou definition 1</li><li>[object Object]</li></ul></div>",
"glossary": "<div style=\"text-align: left;\"><i>(n, Test Dictionary 2)</i> <ul><li>gazou definition 1</li><li><a target=\"_blank\" rel=\"noreferrer noopener\" style=\"cursor:inherit;display:inline-block;position:relative;line-height:1;max-width:100%;color:inherit;\"><span style=\"display:inline-block;white-space:nowrap;max-width:100%;max-height:100vh;position:relative;vertical-align:top;line-height:0;overflow:hidden;font-size:1px;width: 350em;\"><span style=\"display:inline-block;width:0;vertical-align:top;font-size:0;padding-top: 100%;\"></span><span style=\"--image:none;position:absolute;left:0;top:0;width:100%;height:100%;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center center;-webkit-mask-mode:alpha;-webkit-mask-size:contain;-webkit-mask-image:var(--image);mask-repeat:no-repeat;mask-position:center center;mask-mode:alpha;mask-size:contain;mask-image:var(--image);background-color:currentColor;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;display:none;\"></span><img alt=\"\" style=\"display:inline-block;vertical-align:top;object-fit:contain;border:none;outline:none;position:absolute;left:0;top:0;width:100%;height:100%;display:none;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;\"><span style=\"position:absolute;left:0;top:0;width:100%;height:100%;display:table;table-layout:fixed;white-space:normal;font-size:initial;line-height:initial;color:initial;\"></span></span><span style=\"display:none;line-height:initial;\">Image</span></a></li></ul></div>",
"glossary-brief": "<div style=\"text-align: left;\"><ul><li>gazou definition 1</li><li><a target=\"_blank\" rel=\"noreferrer noopener\" style=\"cursor:inherit;display:inline-block;position:relative;line-height:1;max-width:100%;color:inherit;\"><span style=\"display:inline-block;white-space:nowrap;max-width:100%;max-height:100vh;position:relative;vertical-align:top;line-height:0;overflow:hidden;font-size:1px;width: 350em;\"><span style=\"display:inline-block;width:0;vertical-align:top;font-size:0;padding-top: 100%;\"></span><span style=\"--image:none;position:absolute;left:0;top:0;width:100%;height:100%;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center center;-webkit-mask-mode:alpha;-webkit-mask-size:contain;-webkit-mask-image:var(--image);mask-repeat:no-repeat;mask-position:center center;mask-mode:alpha;mask-size:contain;mask-image:var(--image);background-color:currentColor;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;display:none;\"></span><img alt=\"\" style=\"display:inline-block;vertical-align:top;object-fit:contain;border:none;outline:none;position:absolute;left:0;top:0;width:100%;height:100%;display:none;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;\"><span style=\"position:absolute;left:0;top:0;width:100%;height:100%;display:table;table-layout:fixed;white-space:normal;font-size:initial;line-height:initial;color:initial;\"></span></span><span style=\"display:none;line-height:initial;\">Image</span></a></li></ul></div>",
"glossary-no-dictionary": "<div style=\"text-align: left;\"><i>(n)</i> <ul><li>gazou definition 1</li><li><a target=\"_blank\" rel=\"noreferrer noopener\" style=\"cursor:inherit;display:inline-block;position:relative;line-height:1;max-width:100%;color:inherit;\"><span style=\"display:inline-block;white-space:nowrap;max-width:100%;max-height:100vh;position:relative;vertical-align:top;line-height:0;overflow:hidden;font-size:1px;width: 350em;\"><span style=\"display:inline-block;width:0;vertical-align:top;font-size:0;padding-top: 100%;\"></span><span style=\"--image:none;position:absolute;left:0;top:0;width:100%;height:100%;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center center;-webkit-mask-mode:alpha;-webkit-mask-size:contain;-webkit-mask-image:var(--image);mask-repeat:no-repeat;mask-position:center center;mask-mode:alpha;mask-size:contain;mask-image:var(--image);background-color:currentColor;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;display:none;\"></span><img alt=\"\" style=\"display:inline-block;vertical-align:top;object-fit:contain;border:none;outline:none;position:absolute;left:0;top:0;width:100%;height:100%;display:none;image-rendering:auto;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:pixelated;image-rendering:crisp-edges;\"><span style=\"position:absolute;left:0;top:0;width:100%;height:100%;display:table;table-layout:fixed;white-space:normal;font-size:initial;line-height:initial;color:initial;\"></span></span><span style=\"display:none;line-height:initial;\">Image</span></a></li></ul></div>",
"part-of-speech": "Noun",
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",

View File

@ -29,9 +29,13 @@ function clone(value) {
async function createVM() {
const dom = new JSDOM();
const {document} = dom.window;
const {Node, NodeFilter, document} = dom.window;
const vm = new TranslatorVM({document});
const vm = new TranslatorVM({
Node,
NodeFilter,
document
});
const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', 'valid-dictionary1');
await vm.prepare(dictionaryDirectory, 'Test Dictionary 2');
@ -39,6 +43,8 @@ async function createVM() {
vm.execute([
'js/data/anki-note-builder.js',
'js/data/anki-util.js',
'js/dom/css-style-applier.js',
'js/display/structured-content-generator.js',
'js/templates/template-renderer.js',
'lib/handlebars.min.js'
]);
@ -46,11 +52,13 @@ async function createVM() {
const [
JapaneseUtil,
TemplateRenderer,
AnkiNoteBuilder
AnkiNoteBuilder,
CssStyleApplier
] = vm.get([
'JapaneseUtil',
'TemplateRenderer',
'AnkiNoteBuilder'
'AnkiNoteBuilder',
'CssStyleApplier'
]);
const ankiNoteDataCreator = vm.ankiNoteDataCreator;
@ -58,7 +66,8 @@ async function createVM() {
constructor() {
this._preparePromise = null;
this._japaneseUtil = new JapaneseUtil(null);
this._templateRenderer = new TemplateRenderer(this._japaneseUtil);
this._cssStyleApplier = new CssStyleApplier('/data/structured-content-style.json');
this._templateRenderer = new TemplateRenderer(this._japaneseUtil, this._cssStyleApplier);
this._templateRenderer.registerDataType('ankiNote', {
modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData),
composeData: (marker, commonData) => ({marker, commonData})
@ -83,7 +92,7 @@ async function createVM() {
}
async _prepareInternal() {
// Empty
await this._cssStyleApplier.prepare();
}
_serializeError(error) {

View File

@ -589,7 +589,7 @@ function createOptionsUpdatedTestData1() {
}
],
profileCurrent: 0,
version: 12,
version: 13,
global: {
database: {
prefixWildcardsSupported: false
@ -655,7 +655,8 @@ async function testFieldTemplatesUpdate(extDir) {
{version: 6, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v6.handlebars')},
{version: 8, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v8.handlebars')},
{version: 10, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v10.handlebars')},
{version: 12, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v12.handlebars')}
{version: 12, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v12.handlebars')},
{version: 13, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v13.handlebars')}
];
const getUpdateAdditions = (startVersion=0) => {
let value = '';
@ -875,11 +876,11 @@ ${getUpdateAdditions()}
{{~#if only~}}({{#each only}}{{.}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{~#if (op "<=" glossary.length 1)~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{/each}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{/each}}
{{~else if @root.compactGlossaries~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
<ul>{{#each glossary}}<li>{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}</li>{{/each}}</ul>
{{~/if~}}
{{~#set "previousDictionary" dictionary~}}{{~/set~}}
{{/inline}}
@ -920,6 +921,27 @@ ${getUpdateAdditions()}
${getUpdateAdditions(7)}
{{~> (lookup . "marker") ~}}`.trimStart()
},
// formatGlossary update
{
oldVersion: 12,
old: `
{{~#if (op "<=" glossary.length 1)~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{/each}}
{{~else if @root.compactGlossaries~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
{{~/if~}}`.trimStart(),
expected: `
{{~#if (op "<=" glossary.length 1)~}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{/each}}
{{~else if @root.compactGlossaries~}}
{{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}</li>{{/each}}</ul>
{{~/if~}}`.trimStart()
}
];