diff --git a/src/utils.ts b/src/utils.ts
index 53ddcbc9..3f222623 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -15,7 +15,7 @@ import { browser } from "./browser";
export function whenReady(fn?: any) {
return new Promise(function (resolve) {
if (document.readyState !== "loading") {
- resolve();
+ (resolve as any)();
} else {
document.addEventListener("DOMContentLoaded", resolve, false);
}
@@ -101,3 +101,69 @@ export function shallowEqual(p1, p2): boolean {
}
return true;
}
+
+const escapeMethod = Symbol('html')
+
+// notable issues:
+// * objects can't be negative in JS, so !!"" -> false but
+// !!(new String) -> true, likewise markup
+// TODO (?)
+// * Markup.join / Markup#join => escapes items and returns a Markup
+// * Markup#replace => automatically escapes the replacements (difficult impl)
+class _Markup extends String {
+ [escapeMethod]() {
+ return this;
+ }
+}
+
+/**
+* Returns a markup object, which acts like a String but is considered safe by
+* `_.escape`, and will therefore be injected as-is (without additional
+* escaping) in templates. Can be used to inject dynamic HTML in templates
+* (where the template itself can't), see first example.
+*
+* Can also be used as a *template tag*, in which case the literal content
+* won't be escaped but the substitutions which are not already markup objects
+* will be.
+*
+* ## WARNINGS:
+* * A markup object is a `String` (boxed) but not a `string` (primitive), they
+* typecheck differently which can be relevant.
+* * To strip out the "markupness", just call `String(markup)`.
+* * Most string operations (e.g. concatenation, `String#replace`, ...) will
+* also strip out markupness
+* * If the input is empty, returns a regular string (that way boolean tests
+* work as expected).
+*
+* @returns a markup object
+*
+* @example regular function
+* let h;
+* if (someTest) {
+* h = Markup(_t("This is a success"));
+* } else {
+* h = Markup(_t("Things did not work out"));
+* }
+* qweb.render("some_template", { message: h });
+*
+* @example template tag
+* const escaped = " text";
+* const asis = Markup`some text`;
+* const h = Markup`Regular strings get ${escaped} but markup is injected ${asis}`;
+*/
+export function Markup(v, ...exprs) {
+ if (!(v instanceof Array)) {
+ return v ? new _Markup(v) : '';
+ }
+ const elements = [];
+ let i = 0;
+ for(; i < exprs.length; ++i) {
+ elements.push(v[i], escape(exprs[i]));
+ }
+ elements.push(v[i]);
+
+ const s = elements.join('');
+ if (!s) { return '' }
+ return new _Markup(s);
+}
+
diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts
index 84a9e1a4..8cc5b5f5 100644
--- a/tests/qweb/qweb.test.ts
+++ b/tests/qweb/qweb.test.ts
@@ -2,6 +2,7 @@ import { QWeb } from "../../src/qweb/index";
import { config } from "../../src/index";
import { nextTick, normalize, renderToDOM, renderToString, trim } from "../helpers";
import { patch } from "../../src/vdom";
+import { Markup } from "../../src/utils";
//------------------------------------------------------------------------------
// Setup and helpers
@@ -246,7 +247,7 @@ describe("t-out (formerly t-raw tests)", () => {
test("not escaping", () => {
qweb.addTemplate("test", `
`);
- expect(renderToString(qweb, "test", { var: "" })).toBe("
");
+ expect(renderToString(qweb, "test", { var: Markup`` })).toBe("
");
});
test("t-raw and another sibling node", () => {