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:
parent
a4715935cb
commit
ca97e38bd2
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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}}
|
||||
{{>>>>>>>}}
|
@ -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}}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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) : '';
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
];
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user