@odoo/owl 2.2.3 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/owl-devtools.zip +0 -0
- package/dist/owl.cjs.js +116 -114
- package/dist/owl.es.js +116 -114
- package/dist/owl.iife.js +116 -114
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/common/owl_error.d.ts +3 -0
- package/dist/types/compiler/code_generator.d.ts +0 -1
- package/dist/types/owl.d.ts +4 -3
- package/dist/types/runtime/error_handling.d.ts +0 -3
- package/dist/types/runtime/index.d.ts +1 -1
- package/dist/types/runtime/template_helpers.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/owl.iife.js
CHANGED
|
@@ -85,67 +85,6 @@
|
|
|
85
85
|
|
|
86
86
|
// Custom error class that wraps error that happen in the owl lifecycle
|
|
87
87
|
class OwlError extends Error {
|
|
88
|
-
}
|
|
89
|
-
// Maps fibers to thrown errors
|
|
90
|
-
const fibersInError = new WeakMap();
|
|
91
|
-
const nodeErrorHandlers = new WeakMap();
|
|
92
|
-
function _handleError(node, error) {
|
|
93
|
-
if (!node) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
const fiber = node.fiber;
|
|
97
|
-
if (fiber) {
|
|
98
|
-
fibersInError.set(fiber, error);
|
|
99
|
-
}
|
|
100
|
-
const errorHandlers = nodeErrorHandlers.get(node);
|
|
101
|
-
if (errorHandlers) {
|
|
102
|
-
let handled = false;
|
|
103
|
-
// execute in the opposite order
|
|
104
|
-
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
105
|
-
try {
|
|
106
|
-
errorHandlers[i](error);
|
|
107
|
-
handled = true;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
error = e;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (handled) {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return _handleError(node.parent, error);
|
|
119
|
-
}
|
|
120
|
-
function handleError(params) {
|
|
121
|
-
let { error } = params;
|
|
122
|
-
// Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode)
|
|
123
|
-
if (!(error instanceof OwlError)) {
|
|
124
|
-
error = Object.assign(new OwlError(`An error occured in the owl lifecycle (see this Error's "cause" property)`), { cause: error });
|
|
125
|
-
}
|
|
126
|
-
const node = "node" in params ? params.node : params.fiber.node;
|
|
127
|
-
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
128
|
-
if (fiber) {
|
|
129
|
-
// resets the fibers on components if possible. This is important so that
|
|
130
|
-
// new renderings can be properly included in the initial one, if any.
|
|
131
|
-
let current = fiber;
|
|
132
|
-
do {
|
|
133
|
-
current.node.fiber = current;
|
|
134
|
-
current = current.parent;
|
|
135
|
-
} while (current);
|
|
136
|
-
fibersInError.set(fiber.root, error);
|
|
137
|
-
}
|
|
138
|
-
const handled = _handleError(node, error);
|
|
139
|
-
if (!handled) {
|
|
140
|
-
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
141
|
-
try {
|
|
142
|
-
node.app.destroy();
|
|
143
|
-
}
|
|
144
|
-
catch (e) {
|
|
145
|
-
console.error(e);
|
|
146
|
-
}
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
88
|
}
|
|
150
89
|
|
|
151
90
|
const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype;
|
|
@@ -774,12 +713,7 @@
|
|
|
774
713
|
info.push({ type: "child", idx: index });
|
|
775
714
|
el = document.createTextNode("");
|
|
776
715
|
}
|
|
777
|
-
|
|
778
|
-
const ns = attrs.getNamedItem("block-ns");
|
|
779
|
-
if (ns) {
|
|
780
|
-
attrs.removeNamedItem("block-ns");
|
|
781
|
-
currentNS = ns.value;
|
|
782
|
-
}
|
|
716
|
+
currentNS || (currentNS = node.namespaceURI);
|
|
783
717
|
if (!el) {
|
|
784
718
|
el = currentNS
|
|
785
719
|
? document.createElementNS(currentNS, tagName)
|
|
@@ -795,6 +729,7 @@
|
|
|
795
729
|
const fragment = document.createElement("template").content;
|
|
796
730
|
fragment.appendChild(el);
|
|
797
731
|
}
|
|
732
|
+
const attrs = node.attributes;
|
|
798
733
|
for (let i = 0; i < attrs.length; i++) {
|
|
799
734
|
const attrName = attrs[i].name;
|
|
800
735
|
const attrValue = attrs[i].value;
|
|
@@ -1606,6 +1541,68 @@
|
|
|
1606
1541
|
vnode.remove();
|
|
1607
1542
|
}
|
|
1608
1543
|
|
|
1544
|
+
// Maps fibers to thrown errors
|
|
1545
|
+
const fibersInError = new WeakMap();
|
|
1546
|
+
const nodeErrorHandlers = new WeakMap();
|
|
1547
|
+
function _handleError(node, error) {
|
|
1548
|
+
if (!node) {
|
|
1549
|
+
return false;
|
|
1550
|
+
}
|
|
1551
|
+
const fiber = node.fiber;
|
|
1552
|
+
if (fiber) {
|
|
1553
|
+
fibersInError.set(fiber, error);
|
|
1554
|
+
}
|
|
1555
|
+
const errorHandlers = nodeErrorHandlers.get(node);
|
|
1556
|
+
if (errorHandlers) {
|
|
1557
|
+
let handled = false;
|
|
1558
|
+
// execute in the opposite order
|
|
1559
|
+
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
|
1560
|
+
try {
|
|
1561
|
+
errorHandlers[i](error);
|
|
1562
|
+
handled = true;
|
|
1563
|
+
break;
|
|
1564
|
+
}
|
|
1565
|
+
catch (e) {
|
|
1566
|
+
error = e;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
if (handled) {
|
|
1570
|
+
return true;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return _handleError(node.parent, error);
|
|
1574
|
+
}
|
|
1575
|
+
function handleError(params) {
|
|
1576
|
+
let { error } = params;
|
|
1577
|
+
// Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode)
|
|
1578
|
+
if (!(error instanceof OwlError)) {
|
|
1579
|
+
error = Object.assign(new OwlError(`An error occured in the owl lifecycle (see this Error's "cause" property)`), { cause: error });
|
|
1580
|
+
}
|
|
1581
|
+
const node = "node" in params ? params.node : params.fiber.node;
|
|
1582
|
+
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
|
1583
|
+
if (fiber) {
|
|
1584
|
+
// resets the fibers on components if possible. This is important so that
|
|
1585
|
+
// new renderings can be properly included in the initial one, if any.
|
|
1586
|
+
let current = fiber;
|
|
1587
|
+
do {
|
|
1588
|
+
current.node.fiber = current;
|
|
1589
|
+
current = current.parent;
|
|
1590
|
+
} while (current);
|
|
1591
|
+
fibersInError.set(fiber.root, error);
|
|
1592
|
+
}
|
|
1593
|
+
const handled = _handleError(node, error);
|
|
1594
|
+
if (!handled) {
|
|
1595
|
+
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
|
1596
|
+
try {
|
|
1597
|
+
node.app.destroy();
|
|
1598
|
+
}
|
|
1599
|
+
catch (e) {
|
|
1600
|
+
console.error(e);
|
|
1601
|
+
}
|
|
1602
|
+
throw error;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1609
1606
|
function makeChildFiber(node, parent) {
|
|
1610
1607
|
let current = node.fiber;
|
|
1611
1608
|
if (current) {
|
|
@@ -3010,8 +3007,8 @@
|
|
|
3010
3007
|
values = keys;
|
|
3011
3008
|
}
|
|
3012
3009
|
else {
|
|
3013
|
-
values = Object.
|
|
3014
|
-
keys = Object.
|
|
3010
|
+
values = Object.values(collection);
|
|
3011
|
+
keys = Object.keys(collection);
|
|
3015
3012
|
}
|
|
3016
3013
|
}
|
|
3017
3014
|
else {
|
|
@@ -3856,7 +3853,7 @@
|
|
|
3856
3853
|
createBlock(parentBlock, type, ctx) {
|
|
3857
3854
|
const hasRoot = this.target.hasRoot;
|
|
3858
3855
|
const block = new BlockDescription(this.target, type);
|
|
3859
|
-
if (!hasRoot
|
|
3856
|
+
if (!hasRoot) {
|
|
3860
3857
|
this.target.hasRoot = true;
|
|
3861
3858
|
block.isRoot = true;
|
|
3862
3859
|
}
|
|
@@ -3879,7 +3876,7 @@
|
|
|
3879
3876
|
if (ctx.tKeyExpr) {
|
|
3880
3877
|
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
|
|
3881
3878
|
}
|
|
3882
|
-
if (block.isRoot
|
|
3879
|
+
if (block.isRoot) {
|
|
3883
3880
|
if (this.target.on) {
|
|
3884
3881
|
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
|
3885
3882
|
}
|
|
@@ -4055,11 +4052,6 @@
|
|
|
4055
4052
|
}
|
|
4056
4053
|
// attributes
|
|
4057
4054
|
const attrs = {};
|
|
4058
|
-
const nameSpace = ast.ns || ctx.nameSpace;
|
|
4059
|
-
if (nameSpace && isNewBlock) {
|
|
4060
|
-
// specific namespace uri
|
|
4061
|
-
attrs["block-ns"] = nameSpace;
|
|
4062
|
-
}
|
|
4063
4055
|
for (let key in ast.attrs) {
|
|
4064
4056
|
let expr, attrName;
|
|
4065
4057
|
if (key.startsWith("t-attf")) {
|
|
@@ -4175,7 +4167,10 @@
|
|
|
4175
4167
|
const idx = block.insertData(setRefStr, "ref");
|
|
4176
4168
|
attrs["block-ref"] = String(idx);
|
|
4177
4169
|
}
|
|
4178
|
-
const
|
|
4170
|
+
const nameSpace = ast.ns || ctx.nameSpace;
|
|
4171
|
+
const dom = nameSpace
|
|
4172
|
+
? xmlDoc.createElementNS(nameSpace, ast.tag)
|
|
4173
|
+
: xmlDoc.createElement(ast.tag);
|
|
4179
4174
|
for (const [attr, val] of Object.entries(attrs)) {
|
|
4180
4175
|
if (!(attr === "class" && val === "")) {
|
|
4181
4176
|
dom.setAttribute(attr, val);
|
|
@@ -4217,7 +4212,7 @@
|
|
|
4217
4212
|
break;
|
|
4218
4213
|
}
|
|
4219
4214
|
}
|
|
4220
|
-
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
4215
|
+
this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
|
4221
4216
|
}
|
|
4222
4217
|
}
|
|
4223
4218
|
return block.varName;
|
|
@@ -4320,7 +4315,7 @@
|
|
|
4320
4315
|
break;
|
|
4321
4316
|
}
|
|
4322
4317
|
}
|
|
4323
|
-
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
4318
|
+
this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
|
4324
4319
|
}
|
|
4325
4320
|
// note: this part is duplicated from end of compilemulti:
|
|
4326
4321
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
@@ -4349,18 +4344,18 @@
|
|
|
4349
4344
|
}
|
|
4350
4345
|
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
|
4351
4346
|
this.target.indentLevel++;
|
|
4352
|
-
this.addLine(`ctx[\`${ast.elem}\`] = ${
|
|
4347
|
+
this.addLine(`ctx[\`${ast.elem}\`] = ${keys}[${loopVar}];`);
|
|
4353
4348
|
if (!ast.hasNoFirst) {
|
|
4354
4349
|
this.addLine(`ctx[\`${ast.elem}_first\`] = ${loopVar} === 0;`);
|
|
4355
4350
|
}
|
|
4356
4351
|
if (!ast.hasNoLast) {
|
|
4357
|
-
this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${
|
|
4352
|
+
this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${keys}.length - 1;`);
|
|
4358
4353
|
}
|
|
4359
4354
|
if (!ast.hasNoIndex) {
|
|
4360
4355
|
this.addLine(`ctx[\`${ast.elem}_index\`] = ${loopVar};`);
|
|
4361
4356
|
}
|
|
4362
4357
|
if (!ast.hasNoValue) {
|
|
4363
|
-
this.addLine(`ctx[\`${ast.elem}_value\`] = ${
|
|
4358
|
+
this.addLine(`ctx[\`${ast.elem}_value\`] = ${vals}[${loopVar}];`);
|
|
4364
4359
|
}
|
|
4365
4360
|
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
|
4366
4361
|
if (this.dev) {
|
|
@@ -4435,7 +4430,6 @@
|
|
|
4435
4430
|
block,
|
|
4436
4431
|
index,
|
|
4437
4432
|
forceNewBlock: !isTSet,
|
|
4438
|
-
preventRoot: ctx.preventRoot,
|
|
4439
4433
|
isLast: ctx.isLast && i === l - 1,
|
|
4440
4434
|
});
|
|
4441
4435
|
this.compileAST(child, subCtx);
|
|
@@ -4444,21 +4438,19 @@
|
|
|
4444
4438
|
}
|
|
4445
4439
|
}
|
|
4446
4440
|
if (isNewBlock) {
|
|
4447
|
-
if (block.hasDynamicChildren) {
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
break;
|
|
4458
|
-
}
|
|
4441
|
+
if (block.hasDynamicChildren && block.children.length) {
|
|
4442
|
+
const code = this.target.code;
|
|
4443
|
+
const children = block.children.slice();
|
|
4444
|
+
let current = children.shift();
|
|
4445
|
+
for (let i = codeIdx; i < code.length; i++) {
|
|
4446
|
+
if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
|
|
4447
|
+
code[i] = code[i].replace(`const ${current.varName}`, current.varName);
|
|
4448
|
+
current = children.shift();
|
|
4449
|
+
if (!current)
|
|
4450
|
+
break;
|
|
4459
4451
|
}
|
|
4460
|
-
this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
|
|
4461
4452
|
}
|
|
4453
|
+
this.addLine(`let ${block.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
|
4462
4454
|
}
|
|
4463
4455
|
const args = block.children.map((c) => c.varName).join(", ");
|
|
4464
4456
|
this.insertBlock(`multi([${args}])`, block, ctx);
|
|
@@ -4472,24 +4464,23 @@
|
|
|
4472
4464
|
ctxVar = generateId("ctx");
|
|
4473
4465
|
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
|
|
4474
4466
|
}
|
|
4467
|
+
const isDynamic = INTERP_REGEXP.test(ast.name);
|
|
4468
|
+
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
|
|
4469
|
+
if (block && !forceNewBlock) {
|
|
4470
|
+
this.insertAnchor(block);
|
|
4471
|
+
}
|
|
4472
|
+
block = this.createBlock(block, "multi", ctx);
|
|
4475
4473
|
if (ast.body) {
|
|
4476
4474
|
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
|
|
4477
4475
|
this.addLine(`${ctxVar}[isBoundary] = 1;`);
|
|
4478
4476
|
this.helpers.add("isBoundary");
|
|
4479
|
-
const subCtx = createContext(ctx, {
|
|
4477
|
+
const subCtx = createContext(ctx, { ctxVar });
|
|
4480
4478
|
const bl = this.compileMulti({ type: 3 /* Multi */, content: ast.body }, subCtx);
|
|
4481
4479
|
if (bl) {
|
|
4482
4480
|
this.helpers.add("zero");
|
|
4483
4481
|
this.addLine(`${ctxVar}[zero] = ${bl};`);
|
|
4484
4482
|
}
|
|
4485
4483
|
}
|
|
4486
|
-
const isDynamic = INTERP_REGEXP.test(ast.name);
|
|
4487
|
-
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
|
|
4488
|
-
if (block) {
|
|
4489
|
-
if (!forceNewBlock) {
|
|
4490
|
-
this.insertAnchor(block);
|
|
4491
|
-
}
|
|
4492
|
-
}
|
|
4493
4484
|
const key = `key + \`${this.generateComponentKey()}\``;
|
|
4494
4485
|
if (isDynamic) {
|
|
4495
4486
|
const templateVar = generateId("template");
|
|
@@ -4497,7 +4488,6 @@
|
|
|
4497
4488
|
this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
|
|
4498
4489
|
}
|
|
4499
4490
|
this.define(templateVar, subTemplate);
|
|
4500
|
-
block = this.createBlock(block, "multi", ctx);
|
|
4501
4491
|
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
|
|
4502
4492
|
...ctx,
|
|
4503
4493
|
forceNewBlock: !block,
|
|
@@ -4506,7 +4496,6 @@
|
|
|
4506
4496
|
else {
|
|
4507
4497
|
const id = generateId(`callTemplate_`);
|
|
4508
4498
|
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
|
|
4509
|
-
block = this.createBlock(block, "multi", ctx);
|
|
4510
4499
|
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
|
|
4511
4500
|
...ctx,
|
|
4512
4501
|
forceNewBlock: !block,
|
|
@@ -4828,7 +4817,7 @@
|
|
|
4828
4817
|
}
|
|
4829
4818
|
function _parse(xml) {
|
|
4830
4819
|
normalizeXML(xml);
|
|
4831
|
-
const ctx = { inPreTag: false
|
|
4820
|
+
const ctx = { inPreTag: false };
|
|
4832
4821
|
return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
|
|
4833
4822
|
}
|
|
4834
4823
|
function parseNode(node, ctx) {
|
|
@@ -4919,9 +4908,7 @@
|
|
|
4919
4908
|
if (tagName === "pre") {
|
|
4920
4909
|
ctx.inPreTag = true;
|
|
4921
4910
|
}
|
|
4922
|
-
|
|
4923
|
-
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
|
4924
|
-
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
|
4911
|
+
let ns = !ctx.nameSpace && ROOT_SVG_TAGS.has(tagName) ? "http://www.w3.org/2000/svg" : null;
|
|
4925
4912
|
const ref = node.getAttribute("t-ref");
|
|
4926
4913
|
node.removeAttribute("t-ref");
|
|
4927
4914
|
const nodeAttrsNames = node.getAttributeNames();
|
|
@@ -4983,6 +4970,9 @@
|
|
|
4983
4970
|
else if (attr.startsWith("block-")) {
|
|
4984
4971
|
throw new OwlError(`Invalid attribute: '${attr}'`);
|
|
4985
4972
|
}
|
|
4973
|
+
else if (attr === "xmlns") {
|
|
4974
|
+
ns = value;
|
|
4975
|
+
}
|
|
4986
4976
|
else if (attr !== "t-name") {
|
|
4987
4977
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
|
4988
4978
|
throw new OwlError(`Unknown QWeb directive: '${attr}'`);
|
|
@@ -4995,6 +4985,9 @@
|
|
|
4995
4985
|
attrs[attr] = value;
|
|
4996
4986
|
}
|
|
4997
4987
|
}
|
|
4988
|
+
if (ns) {
|
|
4989
|
+
ctx.nameSpace = ns;
|
|
4990
|
+
}
|
|
4998
4991
|
const children = parseChildren(node, ctx);
|
|
4999
4992
|
return {
|
|
5000
4993
|
type: 2 /* DomNode */,
|
|
@@ -5550,11 +5543,20 @@
|
|
|
5550
5543
|
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
|
5551
5544
|
const code = codeGenerator.generateCode();
|
|
5552
5545
|
// template function
|
|
5553
|
-
|
|
5546
|
+
try {
|
|
5547
|
+
return new Function("app, bdom, helpers", code);
|
|
5548
|
+
}
|
|
5549
|
+
catch (originalError) {
|
|
5550
|
+
const { name } = options;
|
|
5551
|
+
const nameStr = name ? `template "${name}"` : "anonymous template";
|
|
5552
|
+
const err = new OwlError(`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`);
|
|
5553
|
+
err.cause = originalError;
|
|
5554
|
+
throw err;
|
|
5555
|
+
}
|
|
5554
5556
|
}
|
|
5555
5557
|
|
|
5556
5558
|
// do not modify manually. This file is generated by the release script.
|
|
5557
|
-
const version = "2.2.
|
|
5559
|
+
const version = "2.2.5";
|
|
5558
5560
|
|
|
5559
5561
|
// -----------------------------------------------------------------------------
|
|
5560
5562
|
// Scheduler
|
|
@@ -6023,8 +6025,8 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
6023
6025
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
6024
6026
|
|
|
6025
6027
|
|
|
6026
|
-
__info__.date = '2023-
|
|
6027
|
-
__info__.hash = '
|
|
6028
|
+
__info__.date = '2023-08-07T10:26:30.557Z';
|
|
6029
|
+
__info__.hash = 'b25e988';
|
|
6028
6030
|
__info__.url = 'https://github.com/odoo/owl';
|
|
6029
6031
|
|
|
6030
6032
|
|