From 439406a5100b24b90bbec0c70c6338101abe735d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Thu, 1 Oct 2015 17:25:40 +0200 Subject: Use codeq.templator() for hints. Support for images. --- js/codeq/comms.js | 4 ++ js/codeq/core.js | 172 +++++++++++++++++++++++++++++++++++++++++++++++++--- js/codeq/hint.js | 8 ++- js/codeq/problem.js | 2 +- 4 files changed, 174 insertions(+), 12 deletions(-) (limited to 'js') diff --git a/js/codeq/comms.js b/js/codeq/comms.js index f416e88..d7d0935 100644 --- a/js/codeq/comms.js +++ b/js/codeq/comms.js @@ -419,6 +419,10 @@ 'getGuiTranslation': function (lang) { return ajaxGet(ajaxResPrefix + lang + '.json'); + }, + + 'getResourceTree': function () { + return ajaxGet(ajaxDataPrefix + 'resources.json'); } }; })(); diff --git a/js/codeq/core.js b/js/codeq/core.js index 29b497c..4b03bfb 100644 --- a/js/codeq/core.js +++ b/js/codeq/core.js @@ -163,8 +163,10 @@ return html.replace(regexpWhiteSpaceBeforeTag, '').split(regexpWhiteSpaceAfterTag).join('>'); }; - var lang = 'en'; // this is overridden in the boot sequence below, if the browser uses a supported language + var lang = 'en', // this is overridden in the boot sequence below, if the browser uses a supported language + resources = {}; // resource tree, loaded from data/resources.json in the boot sequence + // event dispatch var eventListeners = {}, // keyed by event name, value is an array of listeners asyncTimer = null, queuedEvents = [], @@ -194,6 +196,120 @@ }, 0); }; + var tokenize = function (line) { + var result = [], + N = line.length, + i = 0, // character index into line + storedAtom = null, // the previous atom, to decide whether to store a string or an object {key:'', value:''} + atom = [], // currently parsed content + isKeyValue = false, + storeAtom = function () { + if (atom.length == 0) return; + if (storedAtom === null) storedAtom = atom.join(''); + else if (isKeyValue) { + result.push({'key': storedAtom, 'value': atom.join('')}); + storedAtom = null; + isKeyValue = false; + } + else { + result.push(storedAtom); + storedAtom = atom.join(''); + } + atom.length = 0; + }, + c, q; + + while (i < N) { + c = line[i++]; + switch (c) { + // end-of-atom characters + case ' ': + case '\r': + case '\n': + case '\t': + storeAtom(); + break; + + // escape character + case '\\': + if (i < N) atom.push(line[i++]); + break; + + // key-value delimiter character + case '=': + if (storedAtom) { + if (isKeyValue) atom.push(c); + else if (atom.length == 0) isKeyValue = true; // must have been a whitespace before "=" + else { + storeAtom(); + isKeyValue = true; + } + } + else atom.push(c); // "=" is delimiter only before the value, not before the key + break; + + // quoted string + case '"': + case "'": + stringConsume: + while (i < N) { + q = line[i++]; + switch (q) { + case '\\': + if (i < N) atom.push(line[i++]); + break; + case c: + break stringConsume; + default: + atom.push(q); + break; + } + } + break; + + default: + atom.push(c); + break; + } + } + // purge anything in cache + storeAtom(); + if (storedAtom !== null) result.push(storedAtom); + return result; + }; + + var directiveHandlers = { + 'resource': function (code, tokens, templatePath) { + var resourceName = tokens[1], + traversedPath = ['data'], // top-level directory + branch = resources, + candidate = null, + i, fragment; + if (!resourceName) { + codeq.log.error('No resource name provided; path: "' + templatePath.join('/') + '"'); + code.push('_result.push("data/broken.png");\n'); + return; + } + if (branch[resourceName]) candidate = 'data/' + resourceName; // top-level match + for (i = 0; i < templatePath.length; i++) { + fragment = templatePath[i]; + branch = branch[fragment]; + if (!branch) { + codeq.log.error('Resource sub-branch ' + fragment + ' does not exist; resource: "' + resourceName + '", path: "' + templatePath.join('/') + '"'); + code.push('_result.push("data/broken.png");\n'); + break; + } + traversedPath.push(fragment); + if (branch[resourceName]) candidate = traversedPath.join('/') + '/' + resourceName; + } + if (candidate) code.push('_result.push("', candidate, '");\n'); + else { + codeq.log.error('Resource ' + resourceName + ' was not found; path: "' + templatePath.join('/') + '"'); + code.push('_result.push("data/broken.png");\n'); + } + } + }; + window.codeq = { 'jsonize': jsonize, @@ -271,13 +387,14 @@ }); }, - 'templator': function (str, templateName) { + 'templator': function (str, templatePath, templateName) { var f, parts, i, subparts, subpart, src = [ 'var _result = [], echo = function (s) { _result.push(s); };\n' ], - componentName, atoms, j, atom, debugPrefix, s, r; + atoms, j, atom, debugPrefix, s, r, tokens, handler; if (!templateName) templateName = 'unknown template'; debugPrefix = '[' + templateName + ']'; + if (!templatePath) templatePath = []; // remove comments parts = str.split('[%--'); // break on start-of-comment @@ -308,9 +425,25 @@ if (subpart[0] === '=') { // a value reference s = subpart.slice(1); r = regexpWhiteSpaceTrim.exec(s); - src.push('_result.push(this.', r && r[1] || s, ');\n'); + r = r && r[1] || s; + src.push('_result.push(typeof this.', r, ' === \'undefined\' ? \'', r, ' missing\' : this.', r, ');\n'); } - else { // a statement + else if (subpart[0] === '@') { // a directive + tokens = tokenize(subpart.slice(1)); + if (tokens.length === 0) { + codeq.log.error('An empty directive in ' + templateName); + } + else { + handler = directiveHandlers[tokens[0]]; + if (!handler) { + codeq.log.error('An unknown directive in ' + templateName + ': ' + tokens[0]); + } + else { + handler(src, tokens, templatePath); + } + } + } + else { // javascript statement(s) src.push(subpart, '\n'); } } @@ -330,8 +463,22 @@ } return function (args) { - if ((typeof args !== 'object') || (args === null)) args = {}; - return f.apply(args); + var esc = codeq.escapeHtml, + escArgs = {}, + key; + if ((typeof args === 'object') && (args !== null)) { + for (key in args) { + if (!args.hasOwnProperty(key)) continue; + escArgs[key] = esc(args[key]); + } + } + try { + return f.apply(escArgs); + } + catch (e) { + codeq.log.error('Error evaluating template ' + templateName + ' function: ' + e + '\nfunction() {\n' + src.join('') + '\nCreated from template:\n' + str, e); + throw e; + } }; }, @@ -430,9 +577,12 @@ codeq.availableLangs.push(key); } - // the boot chain: must be a sequence of .then() terminated with .done() + // the boot chain: must be a sequence of .then() terminated with a .fail().done() loadGuiTranslations() - .then(function () { + .then(codeq.comms.getResourceTree) + .then(function (resourceTree) { + resources = resourceTree; // save the loaded resource tree to the internal variable + codeq.fire('init'); // tell any interested modules that we are now initialized, perhaps they want to initialize too codeq.setLang(lang || 'en'); // initial language setting, this also translates the GUI // go to login @@ -441,6 +591,10 @@ //For performance reasons, the Tooltip and Popover data-apis are opt-in, meaning you must initialize them yourself. $('[data-toggle="popover"]').popover() }) + .fail(function (e) { + codeq.log.error('CodeQ failed to start: ' + e, e); + alert('CodeQ failed to start: ' + e); + }) .done(); }); })(); diff --git a/js/codeq/hint.js b/js/codeq/hint.js index fc2429a..75df457 100644 --- a/js/codeq/hint.js +++ b/js/codeq/hint.js @@ -20,6 +20,8 @@ hintProblemTr = problemDef.hint, hintCommonTr = commonDef.hint, planDef = problemDef.plan, + templatePath = [problemDef.language, problemDef.group, problemDef.problem], + templateName = templatePath.join('/'), clearHints = function () { var i; @@ -46,14 +48,16 @@ }, processTemplate = function (template, args) { - if (!args) + var fn = codeq.templator(template, templatePath, templateName); + return fn(args); +/* if (!args) return template; return template.replace(/\[%=(\w+)%\]/g, function(match, name) { return args[name].toString() .replace(/&/g, '&') .replace(//g, '>'); - }); + });*/ }, prepareStaticHintContent = function (hintContent, indices, hintId) { diff --git a/js/codeq/problem.js b/js/codeq/problem.js index e3de85a..a875c45 100644 --- a/js/codeq/problem.js +++ b/js/codeq/problem.js @@ -327,7 +327,7 @@ cachedProblem = groupCache[problem]; if (cachedProblem) return Q(cachedProblem); return codeq.comms.getProblemDef(language, group, problem).then(function (rawData) { - var data = processProblemData(rawData); + var data = processProblemData(rawData, language, group, problem); groupCache[problem] = data; return data; }); -- cgit v1.2.1