Allow templates to be tested on the settings page

This commit is contained in:
toasted-nutbread 2019-11-09 16:34:39 -05:00
parent 085881d342
commit 184cc4cf28
4 changed files with 156 additions and 13 deletions

View File

@ -37,12 +37,6 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group
padding: 10px; padding: 10px;
} }
#field-templates {
font-family: monospace;
overflow-x: hidden;
white-space: pre;
}
.bottom-links { .bottom-links {
padding-bottom: 1em; padding-bottom: 1em;
} }
@ -136,14 +130,24 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group
} }
#custom-popup-css, #custom-popup-css,
#custom-popup-outer-css { #custom-popup-outer-css,
#field-templates {
width: 100%; width: 100%;
min-height: 34px; min-height: 34px;
line-height: 18px;
height: 96px; height: 96px;
resize: vertical; resize: vertical;
font-family: 'Courier New', Courier, monospace; font-family: 'Courier New', Courier, monospace;
white-space: pre; white-space: pre;
} }
#field-templates {
height: 240px;
border-bottom-left-radius: 0;
}
#field-templates-reset {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.btn-inner-middle { .btn-inner-middle {
vertical-align: middle; vertical-align: middle;

View File

@ -326,7 +326,7 @@ function dictFieldSplit(field) {
return field.length === 0 ? [] : field.split(' '); return field.length === 0 ? [] : field.split(' ');
} }
async function dictFieldFormat(field, definition, mode, options) { async function dictFieldFormat(field, definition, mode, options, exceptions) {
const data = { const data = {
marker: null, marker: null,
definition, definition,
@ -347,6 +347,7 @@ async function dictFieldFormat(field, definition, mode, options) {
try { try {
return await apiTemplateRender(options.anki.fieldTemplates, data, true); return await apiTemplateRender(options.anki.fieldTemplates, data, true);
} catch (e) { } catch (e) {
if (exceptions) { exceptions.push(e); }
return `{${marker}-render-error}`; return `{${marker}-render-error}`;
} }
}); });

View File

@ -134,6 +134,8 @@ async function formWrite(options) {
$('#screenshot-quality').val(options.anki.screenshot.quality); $('#screenshot-quality').val(options.anki.screenshot.quality);
$('#field-templates').val(options.anki.fieldTemplates); $('#field-templates').val(options.anki.fieldTemplates);
onAnkiTemplatesValidateCompile();
try { try {
await ankiDeckAndModelPopulate(options); await ankiDeckAndModelPopulate(options);
} catch (e) { } catch (e) {
@ -144,7 +146,6 @@ async function formWrite(options) {
} }
function formSetupEventListeners() { function formSetupEventListeners() {
$('#field-templates-reset').click(utilAsync(onAnkiFieldTemplatesReset));
$('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(utilAsync(onFormOptionsChanged)); $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(utilAsync(onFormOptionsChanged));
$('.anki-model').change(utilAsync(onAnkiModelChanged)); $('.anki-model').change(utilAsync(onAnkiModelChanged));
} }
@ -202,6 +203,7 @@ async function onReady() {
await audioSettingsInitialize(); await audioSettingsInitialize();
await profileOptionsSetup(); await profileOptionsSetup();
await dictSettingsInitialize(); await dictSettingsInitialize();
ankiTemplatesInitialize();
storageInfoInitialize(); storageInfoInitialize();
@ -607,20 +609,105 @@ async function onAnkiModelChanged(e) {
} }
} }
async function onAnkiFieldTemplatesReset(e) { function onAnkiFieldTemplatesReset(e) {
e.preventDefault();
$('#field-template-reset-modal').modal('show');
}
async function onAnkiFieldTemplatesResetConfirm(e) {
try { try {
e.preventDefault(); e.preventDefault();
$('#field-template-reset-modal').modal('hide');
const optionsContext = getOptionsContext(); const optionsContext = getOptionsContext();
const options = await apiOptionsGet(optionsContext); const options = await apiOptionsGet(optionsContext);
const fieldTemplates = profileOptionsGetDefaultFieldTemplates(); const fieldTemplates = profileOptionsGetDefaultFieldTemplates();
options.anki.fieldTemplates = fieldTemplates; options.anki.fieldTemplates = fieldTemplates;
$('#field-templates').val(fieldTemplates); $('#field-templates').val(fieldTemplates);
onAnkiTemplatesValidateCompile();
await settingsSaveOptions(); await settingsSaveOptions();
} catch (e) { } catch (e) {
ankiErrorShow(e); ankiErrorShow(e);
} }
} }
function ankiTemplatesInitialize() {
const markers = new Set(ankiGetFieldMarkers('terms').concat(ankiGetFieldMarkers('kanji')));
const fragment = ankiGetFieldMarkersHtml(markers);
const list = document.querySelector('#field-templates-list');
list.appendChild(fragment);
for (const node of list.querySelectorAll('.marker-link')) {
node.addEventListener('click', onAnkiTemplateMarkerClicked, false);
}
$('#field-templates').on('change', onAnkiTemplatesValidateCompile);
$('#field-template-render').on('click', onAnkiTemplateRender);
$('#field-templates-reset').on('click', onAnkiFieldTemplatesReset);
$('#field-templates-reset-confirm').on('click', onAnkiFieldTemplatesResetConfirm);
}
const ankiTemplatesValidateGetDefinition = (() => {
let cachedValue = null;
let cachedText = null;
return async (text, optionsContext) => {
if (cachedText !== text) {
const {definitions} = await apiTermsFind(text, optionsContext);
if (definitions.length === 0) { return null; }
cachedValue = definitions[0];
cachedText = text;
}
return cachedValue;
};
})();
async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, invalidateInput) {
const text = document.querySelector('#field-templates-preview-text').value || '';
const exceptions = [];
let result = `No definition found for ${text}`;
try {
const optionsContext = getOptionsContext();
const definition = await ankiTemplatesValidateGetDefinition(text, optionsContext);
if (definition !== null) {
const options = await apiOptionsGet(optionsContext);
result = await dictFieldFormat(field, definition, mode, options, exceptions);
}
} catch (e) {
exceptions.push(e);
}
const hasException = exceptions.length > 0;
infoNode.hidden = !(showSuccessResult || hasException);
infoNode.textContent = hasException ? exceptions.map(e => `${e}`).join('\n') : (showSuccessResult ? result : '');
infoNode.classList.toggle('text-danger', hasException);
if (invalidateInput) {
const input = document.querySelector('#field-templates');
input.classList.toggle('is-invalid', hasException);
}
}
function onAnkiTemplatesValidateCompile() {
const infoNode = document.querySelector('#field-template-compile-result');
ankiTemplatesValidate(infoNode, '{expression}', 'term-kanji', false, true);
}
function onAnkiTemplateMarkerClicked(e) {
e.preventDefault();
document.querySelector('#field-template-render-text').value = `{${e.target.textContent}}`;
}
function onAnkiTemplateRender(e) {
e.preventDefault();
const field = document.querySelector('#field-template-render-text').value;
const infoNode = document.querySelector('#field-template-render-result');
infoNode.hidden = true;
ankiTemplatesValidate(infoNode, field, 'term-kanji', true, false);
}
/* /*
* Storage * Storage

View File

@ -272,7 +272,7 @@
<div class="form-group ignore-form-changes" style="display: none;" id="settings-popup-preview-settings"> <div class="form-group ignore-form-changes" style="display: none;" id="settings-popup-preview-settings">
<label for="settings-popup-preview-text">Popup preview text</label> <label for="settings-popup-preview-text">Popup preview text</label>
<input type="text" id="settings-popup-preview-text" class="form-control" value="読め"> <input type="text" id="settings-popup-preview-text" class="form-control" value="読め" placeholder="Preview text">
</div> </div>
<div class="form-group ignore-form-changes"> <div class="form-group ignore-form-changes">
@ -699,10 +699,60 @@
<p class="help-block"> <p class="help-block">
Fields are formatted using the <a href="https://handlebarsjs.com/" target="_blank" rel="noopener">Handlebars.js</a> template rendering Fields are formatted using the <a href="https://handlebarsjs.com/" target="_blank" rel="noopener">Handlebars.js</a> template rendering
engine. Advanced users can modify these templates for ultimate control of what information gets included in engine. Advanced users can modify these templates for ultimate control of what information gets included in
their Anki cards. If you encounter problems with your changes you can always <a href="#" id="field-templates-reset">reset to default</a> their Anki cards. If you encounter problems with your changes, you can always reset to the default template settings.
template settings.
</p> </p>
<textarea autocomplete="off" spellcheck="false" wrap="soft" class="form-control" rows="10" id="field-templates"></textarea> <textarea autocomplete="off" spellcheck="false" wrap="soft" class="form-control" rows="10" id="field-templates"></textarea>
<div>
<button class="btn btn-danger" id="field-templates-reset">Reset Templates</button>
</div>
<p></p>
<pre id="field-template-compile-result" hidden></pre>
<p>Templates can be tested using the inputs below.</p>
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label for="field-templates-preview-text">Preview text</label>
<input type="text" id="field-templates-preview-text" class="form-control" value="読め" placeholder="Preview text">
</div>
<div class="col-xs-6">
<label for="field-template-render-text">Test field</label>
<div class="input-group">
<div class="input-group-btn">
<button class="btn btn-default" id="field-template-render" title="Test"><span class="glyphicon glyphicon-play"></span></button>
</div>
<input type="text" class="form-control" id="field-template-render-text" value="{expression}" placeholder="{marker}">
<div class="input-group-btn">
<button class="btn btn-default dropdown-toggle" id="field-templates-dropdown" data-toggle="dropdown"><span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right" id="field-templates-list"></ul>
</div>
</div>
</div>
</div>
</div>
<p></p>
<pre id="field-template-render-result" hidden></pre>
</div>
<div class="modal fade" tabindex="-1" role="dialog" id="field-template-reset-modal">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Confirm template reset</h4>
</div>
<div class="modal-body">
Are you sure you want to reset the field templates to the default value?
Any changes you made will be lost.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="field-templates-reset-confirm">Reset Templates</button>
</div>
</div>
</div>
</div> </div>
<template id="anki-field-template"><tr> <template id="anki-field-template"><tr>
@ -777,6 +827,7 @@
<script src="/mixed/lib/wanakana.min.js"></script> <script src="/mixed/lib/wanakana.min.js"></script>
<script src="/mixed/js/extension.js"></script> <script src="/mixed/js/extension.js"></script>
<script src="/mixed/js/japanese.js"></script>
<script src="/bg/js/anki.js"></script> <script src="/bg/js/anki.js"></script>
<script src="/bg/js/api.js"></script> <script src="/bg/js/api.js"></script>