mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] app, compiler: introduce t-out
t-out automatically escaped content when it is a string not marked with the `markup` function t-out renders the raw content if it is a Block, or if it has been marked with the `markup` funtion. t-esc has been kept since it is safe and is optimized to render text nodes. all t-raw calls are in fact the same as t-out.
This commit is contained in:
committed by
Aaron Bohy
parent
1761af9c24
commit
b902edc1be
@@ -1,5 +1,7 @@
|
||||
import { BDom, multi, text, toggler } from "../blockdom";
|
||||
import { validateProps } from "../component/props_validation";
|
||||
import { Markup } from "../utils";
|
||||
import { html } from "../blockdom/index";
|
||||
|
||||
/**
|
||||
* This file contains utility functions that will be injected in each template,
|
||||
@@ -94,6 +96,29 @@ function shallowEqual(l1: any[], l2: any[]): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Safely outputs `value` as a block depending on the nature of `value`
|
||||
*/
|
||||
export function safeOutput(value: any): ReturnType<typeof toggler> {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
let safeKey;
|
||||
let block;
|
||||
if (value instanceof Markup) {
|
||||
safeKey = `string_safe`;
|
||||
block = html(value as string);
|
||||
} else if (typeof value === "string") {
|
||||
safeKey = "string_unsafe";
|
||||
block = text(value);
|
||||
} else {
|
||||
// Assuming it is a block
|
||||
safeKey = "block_safe";
|
||||
block = value;
|
||||
}
|
||||
return toggler(safeKey, block);
|
||||
}
|
||||
|
||||
export const UTILS = {
|
||||
withDefault,
|
||||
zero: Symbol("zero"),
|
||||
@@ -106,4 +131,5 @@ export const UTILS = {
|
||||
shallowEqual,
|
||||
toNumber,
|
||||
validateProps,
|
||||
safeOutput,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ASTTForEach,
|
||||
ASTTif,
|
||||
ASTTKey,
|
||||
ASTTRaw,
|
||||
ASTTOut,
|
||||
ASTTSet,
|
||||
ASTTranslation,
|
||||
ASTType,
|
||||
@@ -208,7 +208,7 @@ export class CodeGenerator {
|
||||
// define blocks and utility functions
|
||||
this.addLine(`let { text, createBlock, list, multi, html, toggler, component } = bdom;`);
|
||||
this.addLine(
|
||||
`let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;`
|
||||
`let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;`
|
||||
);
|
||||
if (this.shouldDefineAssign) {
|
||||
this.addLine(`let assign = Object.assign;`);
|
||||
@@ -377,8 +377,8 @@ export class CodeGenerator {
|
||||
case ASTType.TEsc:
|
||||
this.compileTEsc(ast, ctx);
|
||||
break;
|
||||
case ASTType.TRaw:
|
||||
this.compileTRaw(ast, ctx);
|
||||
case ASTType.TOut:
|
||||
this.compileTOut(ast, ctx);
|
||||
break;
|
||||
case ASTType.TIf:
|
||||
this.compileTIf(ast, ctx);
|
||||
@@ -640,20 +640,20 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
compileTRaw(ast: ASTTRaw, ctx: Context) {
|
||||
compileTOut(ast: ASTTOut, ctx: Context) {
|
||||
let { block } = ctx;
|
||||
if (block) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
block = this.createBlock(block, "html", ctx);
|
||||
let expr = ast.expr === "0" ? "ctx[zero]" : compileExpr(ast.expr);
|
||||
let expr = ast.expr === "0" ? "ctx[zero]" : `safeOutput(${compileExpr(ast.expr)})`;
|
||||
if (ast.body) {
|
||||
const nextId = BlockDescription.nextBlockId;
|
||||
const subCtx: Context = createContext(ctx);
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
|
||||
expr = `withDefault(${expr}, b${nextId})`;
|
||||
}
|
||||
this.insertBlock(`html(${expr})`, block, ctx);
|
||||
this.insertBlock(`${expr}`, block, ctx);
|
||||
}
|
||||
|
||||
compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode) {
|
||||
|
||||
+22
-16
@@ -11,7 +11,7 @@ export const enum ASTType {
|
||||
TIf,
|
||||
TSet,
|
||||
TCall,
|
||||
TRaw,
|
||||
TOut,
|
||||
TForEach,
|
||||
TKey,
|
||||
TComponent,
|
||||
@@ -62,8 +62,8 @@ export interface ASTTEsc {
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
export interface ASTTRaw {
|
||||
type: ASTType.TRaw;
|
||||
export interface ASTTOut {
|
||||
type: ASTType.TOut;
|
||||
expr: string;
|
||||
body: AST[] | null;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export type AST =
|
||||
| ASTTif
|
||||
| ASTTSet
|
||||
| ASTTCall
|
||||
| ASTTRaw
|
||||
| ASTTOut
|
||||
| ASTTForEach
|
||||
| ASTTKey
|
||||
| ASTComponent
|
||||
@@ -197,7 +197,7 @@ function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
||||
parseTKey(node, ctx) ||
|
||||
parseTTranslation(node, ctx) ||
|
||||
parseTSlot(node, ctx) ||
|
||||
parseTRawNode(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseComponent(node, ctx) ||
|
||||
parseDOMNode(node, ctx) ||
|
||||
parseTSetNode(node, ctx) ||
|
||||
@@ -419,33 +419,39 @@ function parseTEscNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// t-raw
|
||||
// t-out
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function parseTRawNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!node.hasAttribute("t-raw")) {
|
||||
function parseTOutNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!node.hasAttribute("t-out") && !node.hasAttribute("t-raw")) {
|
||||
return null;
|
||||
}
|
||||
const expr = node.getAttribute("t-raw")!;
|
||||
if (node.hasAttribute("t-raw")) {
|
||||
console.warn(
|
||||
`t-raw has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped`
|
||||
);
|
||||
}
|
||||
const expr = (node.getAttribute("t-out") || node.getAttribute("t-raw"))!;
|
||||
node.removeAttribute("t-out");
|
||||
node.removeAttribute("t-raw");
|
||||
|
||||
const tRaw: AST = { type: ASTType.TRaw, expr, body: null };
|
||||
const tOut: AST = { type: ASTType.TOut, expr, body: null };
|
||||
const ref = node.getAttribute("t-ref");
|
||||
node.removeAttribute("t-ref");
|
||||
const ast = parseNode(node, ctx);
|
||||
if (!ast) {
|
||||
return tRaw;
|
||||
return tOut;
|
||||
}
|
||||
if (ast && ast.type === ASTType.DomNode) {
|
||||
tRaw.body = ast.content.length ? ast.content : null;
|
||||
if (ast.type === ASTType.DomNode) {
|
||||
tOut.body = ast.content.length ? ast.content : null;
|
||||
return {
|
||||
...ast,
|
||||
ref,
|
||||
content: [tRaw],
|
||||
content: [tOut],
|
||||
};
|
||||
}
|
||||
|
||||
return tRaw;
|
||||
return tOut;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -504,7 +510,7 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
||||
function hasNoComponent(ast: AST): boolean {
|
||||
switch (ast.type) {
|
||||
case ASTType.TComponent:
|
||||
case ASTType.TRaw:
|
||||
case ASTType.TOut:
|
||||
case ASTType.TCall:
|
||||
case ASTType.TCallBlock:
|
||||
case ASTType.TSlot:
|
||||
|
||||
+1
-2
@@ -11,7 +11,6 @@ import {
|
||||
toggler,
|
||||
} from "./blockdom";
|
||||
import { mainEventHandler } from "./component/handler";
|
||||
import { EventBus, whenReady, loadFile } from "./utils";
|
||||
|
||||
config.shouldNormalizeDom = false;
|
||||
config.mainEventHandler = mainEventHandler;
|
||||
@@ -58,7 +57,7 @@ export { Memo } from "./misc/memo";
|
||||
export { css, xml } from "./tags";
|
||||
export { useState } from "./reactivity";
|
||||
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
|
||||
export const utils = { EventBus, whenReady, loadFile };
|
||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||
|
||||
export {
|
||||
onWillStart,
|
||||
|
||||
+3
-1
@@ -128,7 +128,9 @@ function isTrackable(value: any): boolean {
|
||||
value !== null &&
|
||||
typeof value === "object" &&
|
||||
!(value instanceof Date) &&
|
||||
!(value instanceof Promise)
|
||||
!(value instanceof Promise) &&
|
||||
!(value instanceof String) &&
|
||||
!(value instanceof Number)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,3 +21,18 @@ export async function loadFile(url: string): Promise<string> {
|
||||
}
|
||||
return await result.text();
|
||||
}
|
||||
|
||||
/*
|
||||
* This class just transports the fact that a string is safe
|
||||
* to be injected as HTML. Overriding a JS primitive is quite painful though
|
||||
* so we need to redfine toString and valueOf.
|
||||
*/
|
||||
export class Markup extends String {}
|
||||
|
||||
/*
|
||||
* Marks a value as safe, that is, a value that can be injected as HTML directly.
|
||||
* It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
|
||||
*/
|
||||
export function markup(value: any) {
|
||||
return new Markup(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user