mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
wip
This commit is contained in:
+61
-41
@@ -44,50 +44,70 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio
|
||||
} else {
|
||||
exprID = `scope.${value.id}`;
|
||||
}
|
||||
ctx.addIf(`${exprID} != null`);
|
||||
|
||||
if (ctx.escaping) {
|
||||
let protectID;
|
||||
if (value.hasBody) {
|
||||
ctx.rootContext.shouldDefineUtils = true;
|
||||
protectID = ctx.startProtectScope();
|
||||
ctx.addLine(
|
||||
`${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
|
||||
);
|
||||
}
|
||||
if (ctx.parentTextNode) {
|
||||
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
|
||||
} else if (ctx.parentNode) {
|
||||
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
|
||||
} else {
|
||||
let nodeID = ctx.generateID();
|
||||
ctx.rootContext.rootNode = nodeID;
|
||||
ctx.rootContext.parentTextNode = nodeID;
|
||||
ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
|
||||
if (ctx.rootContext.shouldDefineResult) {
|
||||
ctx.addLine(`result = vn${nodeID}`);
|
||||
}
|
||||
}
|
||||
if (value.hasBody) {
|
||||
ctx.stopProtectScope(protectID);
|
||||
}
|
||||
} else {
|
||||
if (ctx.parentTextNode) {
|
||||
ctx.addIf(`${exprID} != null`);
|
||||
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
|
||||
ctx.closeIf();
|
||||
} else if (ctx.parentNode) {
|
||||
ctx.rootContext.shouldDefineUtils = true;
|
||||
if (value.hasBody) {
|
||||
ctx.addLine(
|
||||
`const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
|
||||
);
|
||||
ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
|
||||
} else {
|
||||
ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
|
||||
}
|
||||
}
|
||||
if (node.childNodes.length) {
|
||||
ctx.addElse();
|
||||
qweb._compileChildren(node, ctx);
|
||||
}
|
||||
ctx.addLine(`insertValue(c${ctx.parentNode}, ${exprID})`);
|
||||
} else {
|
||||
ctx.addIf(`${exprID} != null`);
|
||||
|
||||
ctx.closeIf();
|
||||
let nodeID = ctx.generateID();
|
||||
ctx.rootContext.rootNode = nodeID;
|
||||
ctx.rootContext.parentTextNode = nodeID;
|
||||
ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
|
||||
if (ctx.rootContext.shouldDefineResult) {
|
||||
ctx.addLine(`result = vn${nodeID}`);
|
||||
}
|
||||
ctx.closeIf();
|
||||
}
|
||||
// ctx.addIf(`${exprID} != null`);
|
||||
|
||||
// if (ctx.escaping) {
|
||||
// let protectID;
|
||||
// if (value.hasBody) {
|
||||
// ctx.rootContext.shouldDefineUtils = true;
|
||||
// protectID = ctx.startProtectScope();
|
||||
// ctx.addLine(
|
||||
// `${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
|
||||
// );
|
||||
// }
|
||||
// if (ctx.parentTextNode) {
|
||||
// ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
|
||||
// } else if (ctx.parentNode) {
|
||||
// ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
|
||||
// } else {
|
||||
// let nodeID = ctx.generateID();
|
||||
// ctx.rootContext.rootNode = nodeID;
|
||||
// ctx.rootContext.parentTextNode = nodeID;
|
||||
// ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
|
||||
// if (ctx.rootContext.shouldDefineResult) {
|
||||
// ctx.addLine(`result = vn${nodeID}`);
|
||||
// }
|
||||
// }
|
||||
// if (value.hasBody) {
|
||||
// ctx.stopProtectScope(protectID);
|
||||
// }
|
||||
// } else {
|
||||
// ctx.rootContext.shouldDefineUtils = true;
|
||||
// if (value.hasBody) {
|
||||
// ctx.addLine(
|
||||
// `const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
|
||||
// );
|
||||
// ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
|
||||
// } else {
|
||||
// ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
|
||||
// }
|
||||
// }
|
||||
// if (node.childNodes.length) {
|
||||
// ctx.addElse();
|
||||
// qweb._compileChildren(node, ctx);
|
||||
// }
|
||||
|
||||
// ctx.closeIf();
|
||||
}
|
||||
|
||||
QWeb.addDirective({
|
||||
|
||||
@@ -84,6 +84,7 @@ export class CompilationContext {
|
||||
this.code.unshift(" let QWeb = this.constructor;");
|
||||
}
|
||||
if (this.shouldDefineUtils) {
|
||||
this.code.unshift(" let insertValue = utils.insertValue;");
|
||||
this.code.unshift(" let utils = this.constructor.utils;");
|
||||
}
|
||||
return this.code;
|
||||
|
||||
+17
-2
@@ -1,8 +1,9 @@
|
||||
import { EventBus } from "../core/event_bus";
|
||||
import { h, patch, VNode } from "../vdom/index";
|
||||
import { CompilationContext } from "./compilation_context";
|
||||
import { shallowEqual, escape } from "../utils";
|
||||
import { shallowEqual, escape, _Markup } from "../utils";
|
||||
import { addNS } from "../vdom/vdom";
|
||||
import { htmlToVDOM } from "../vdom/html_to_vdom";
|
||||
|
||||
/**
|
||||
* Owl QWeb Engine
|
||||
@@ -89,6 +90,16 @@ function isComponent(obj): boolean {
|
||||
return obj && obj.hasOwnProperty("__owl__");
|
||||
}
|
||||
|
||||
function insertValue(children: any[], value: any) {
|
||||
if (value != null) {
|
||||
if (value instanceof _Markup) {
|
||||
children.push(...htmlToVDOM(value as any));
|
||||
} else {
|
||||
children.push({ text: value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VDomArray extends Array {
|
||||
toString() {
|
||||
return vDomToString(this);
|
||||
@@ -111,6 +122,7 @@ function vDomToString(vdom: VNode[]): string {
|
||||
|
||||
const UTILS: Utils = {
|
||||
zero: Symbol("zero"),
|
||||
insertValue,
|
||||
toClassObj(expr) {
|
||||
const result = {};
|
||||
if (typeof expr === "string") {
|
||||
@@ -599,7 +611,10 @@ export class QWeb extends EventBus {
|
||||
if (!(dName in QWeb.DIRECTIVE_NAMES)) {
|
||||
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
||||
}
|
||||
if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-out" || attrName === "t-raw")) {
|
||||
if (
|
||||
node.tagName !== "t" &&
|
||||
(attrName === "t-esc" || attrName === "t-out" || attrName === "t-raw")
|
||||
) {
|
||||
const tNode = document.implementation.createDocument(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"t",
|
||||
|
||||
+42
-47
@@ -102,68 +102,63 @@ 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;
|
||||
}
|
||||
}
|
||||
export class _Markup extends String {}
|
||||
|
||||
/**
|
||||
* 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 <strong>success</strong>"));
|
||||
* } else {
|
||||
* h = Markup(_t("Things did <strong>not</strong> work out"));
|
||||
* }
|
||||
* qweb.render("some_template", { message: h });
|
||||
*
|
||||
* @example template tag
|
||||
* const escaped = "<some> text";
|
||||
* const asis = Markup`some <b>text</b>`;
|
||||
* const h = Markup`Regular strings get ${escaped} but markup is injected ${asis}`;
|
||||
*/
|
||||
* 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 <strong>success</strong>"));
|
||||
* } else {
|
||||
* h = Markup(_t("Things did <strong>not</strong> work out"));
|
||||
* }
|
||||
* qweb.render("some_template", { message: h });
|
||||
*
|
||||
* @example template tag
|
||||
* const escaped = "<some> text";
|
||||
* const asis = Markup`some <b>text</b>`;
|
||||
* 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) : '';
|
||||
return v ? new _Markup(v) : "";
|
||||
}
|
||||
const elements = [];
|
||||
let i = 0;
|
||||
for(; i < exprs.length; ++i) {
|
||||
elements.push(v[i], escape(exprs[i]));
|
||||
for (; i < exprs.length; ++i) {
|
||||
elements.push(v[i], escape(exprs[i]));
|
||||
}
|
||||
elements.push(v[i]);
|
||||
|
||||
const s = elements.join('');
|
||||
if (!s) { return '' }
|
||||
const s = elements.join("");
|
||||
if (!s) {
|
||||
return "";
|
||||
}
|
||||
return new _Markup(s);
|
||||
}
|
||||
|
||||
|
||||
@@ -3064,14 +3064,14 @@ exports[`t-out (formerly t-raw tests) not escaping 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let insertValue = utils.insertValue;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
}
|
||||
insertValue(c1, _2)
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
@@ -3181,14 +3181,14 @@ exports[`t-out escaping 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let insertValue = utils.insertValue;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
}
|
||||
insertValue(c1, _2)
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
@@ -3197,14 +3197,14 @@ exports[`t-out escaping on a node 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let insertValue = utils.insertValue;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = 'ok';
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
}
|
||||
insertValue(c1, _2)
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
@@ -3213,16 +3213,14 @@ exports[`t-out escaping on a node with a body 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let insertValue = utils.insertValue;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = 'ok';
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
} else {
|
||||
c1.push({text: \`nope\`});
|
||||
}
|
||||
insertValue(c1, _2)
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
@@ -3231,16 +3229,14 @@ exports[`t-out escaping on a node with a body, as a default 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let insertValue = utils.insertValue;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
} else {
|
||||
c1.push({text: \`nope\`});
|
||||
}
|
||||
insertValue(c1, _2)
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
@@ -3249,14 +3245,14 @@ exports[`t-out literal 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let insertValue = utils.insertValue;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = 'ok';
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
}
|
||||
insertValue(c1, _2)
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
@@ -3357,14 +3353,14 @@ exports[`t-out variable 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let insertValue = utils.insertValue;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
}
|
||||
insertValue(c1, _2)
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -150,7 +150,7 @@ describe("error handling", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("t-out", () => {
|
||||
describe.only("t-out", () => {
|
||||
test("literal", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="'ok'"/></span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
@@ -178,7 +178,7 @@ describe("t-out", () => {
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("escaping on a node with a body, as a default", () => {
|
||||
test.only("escaping on a node with a body, as a default", () => {
|
||||
qweb.addTemplate("test", `<span t-out="var">nope</span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>nope</span>");
|
||||
});
|
||||
|
||||
@@ -32,36 +32,35 @@ describe("old t-esc directive", () => {
|
||||
"<span>&lt;ok&gt;abc&lt;/ok&gt;</span>"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("old t-raw directive", () => {
|
||||
test("literal", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="'ok'"/></span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("variable", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "ok" })).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("not escaping", () => {
|
||||
qweb.addTemplate("test", `<div><t t-raw="var"/></div>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok></ok>" })).toBe("<div><ok></ok></div>");
|
||||
});
|
||||
|
||||
test("t-raw and another sibling node", () => {
|
||||
qweb.addTemplate("test", `<span><span>hello</span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok>world</ok>" })).toBe(
|
||||
"<span><span>hello</span><ok>world</ok></span>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-raw with comment", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<p>text<!-- top secret --></p>" })).toBe(
|
||||
"<span><p>text<!-- top secret --></p></span>"
|
||||
);
|
||||
});
|
||||
});
|
||||
test("literal", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="'ok'"/></span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("variable", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "ok" })).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("not escaping", () => {
|
||||
qweb.addTemplate("test", `<div><t t-raw="var"/></div>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok></ok>" })).toBe("<div><ok></ok></div>");
|
||||
});
|
||||
|
||||
test("t-raw and another sibling node", () => {
|
||||
qweb.addTemplate("test", `<span><span>hello</span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok>world</ok>" })).toBe(
|
||||
"<span><span>hello</span><ok>world</ok></span>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-raw with comment", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<p>text<!-- top secret --></p>" })).toBe(
|
||||
"<span><p>text<!-- top secret --></p></span>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user