@odoo/owl 2.5.1 → 2.6.0
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/compile_templates.mjs +2499 -0
- package/dist/owl-devtools.zip +0 -0
- package/dist/owl.cjs.js +125 -24
- package/dist/owl.es.js +125 -24
- package/dist/owl.iife.js +125 -24
- package/dist/owl.iife.min.js +1 -1
- package/dist/types/compiler/code_generator.d.ts +12 -6
- package/dist/types/compiler/parser.d.ts +14 -2
- package/dist/types/compiler/standalone/index.d.ts +2 -0
- package/dist/types/compiler/standalone/setup_jsdom.d.ts +1 -0
- package/dist/types/owl.d.ts +3 -3
- package/dist/types/runtime/template_set.d.ts +2 -2
- package/dist/types/version.d.ts +1 -1
- package/package.json +9 -2
- package/tools/compile_owl_templates.mjs +31 -0
package/dist/owl.iife.js
CHANGED
|
@@ -1628,6 +1628,13 @@
|
|
|
1628
1628
|
fibersInError.delete(current);
|
|
1629
1629
|
fibersInError.delete(root);
|
|
1630
1630
|
current.appliedToDom = false;
|
|
1631
|
+
if (current instanceof RootFiber) {
|
|
1632
|
+
// it is possible that this fiber is a fiber that crashed while being
|
|
1633
|
+
// mounted, so the mounted list is possibly corrupted. We restore it to
|
|
1634
|
+
// its normal initial state (which is empty list or a list with a mount
|
|
1635
|
+
// fiber.
|
|
1636
|
+
current.mounted = current instanceof MountFiber ? [current] : [];
|
|
1637
|
+
}
|
|
1631
1638
|
}
|
|
1632
1639
|
return current;
|
|
1633
1640
|
}
|
|
@@ -1744,6 +1751,7 @@
|
|
|
1744
1751
|
const node = this.node;
|
|
1745
1752
|
this.locked = true;
|
|
1746
1753
|
let current = undefined;
|
|
1754
|
+
let mountedFibers = this.mounted;
|
|
1747
1755
|
try {
|
|
1748
1756
|
// Step 1: calling all willPatch lifecycle hooks
|
|
1749
1757
|
for (current of this.willPatch) {
|
|
@@ -1763,7 +1771,6 @@
|
|
|
1763
1771
|
node._patch();
|
|
1764
1772
|
this.locked = false;
|
|
1765
1773
|
// Step 4: calling all mounted lifecycle hooks
|
|
1766
|
-
let mountedFibers = this.mounted;
|
|
1767
1774
|
while ((current = mountedFibers.pop())) {
|
|
1768
1775
|
current = current;
|
|
1769
1776
|
if (current.appliedToDom) {
|
|
@@ -1784,6 +1791,15 @@
|
|
|
1784
1791
|
}
|
|
1785
1792
|
}
|
|
1786
1793
|
catch (e) {
|
|
1794
|
+
// if mountedFibers is not empty, this means that a crash occured while
|
|
1795
|
+
// calling the mounted hooks of some component. So, there may still be
|
|
1796
|
+
// some component that have been mounted, but for which the mounted hooks
|
|
1797
|
+
// have not been called. Here, we remove the willUnmount hooks for these
|
|
1798
|
+
// specific component to prevent a worse situation (willUnmount being
|
|
1799
|
+
// called even though mounted has not been called)
|
|
1800
|
+
for (let fiber of mountedFibers) {
|
|
1801
|
+
fiber.node.willUnmount = [];
|
|
1802
|
+
}
|
|
1787
1803
|
this.locked = false;
|
|
1788
1804
|
node.app.handleError({ fiber: current || this, error: e });
|
|
1789
1805
|
}
|
|
@@ -3725,6 +3741,7 @@
|
|
|
3725
3741
|
index: 0,
|
|
3726
3742
|
forceNewBlock: true,
|
|
3727
3743
|
translate: parentCtx.translate,
|
|
3744
|
+
translationCtx: parentCtx.translationCtx,
|
|
3728
3745
|
tKeyExpr: null,
|
|
3729
3746
|
nameSpace: parentCtx.nameSpace,
|
|
3730
3747
|
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
|
|
@@ -3827,6 +3844,7 @@
|
|
|
3827
3844
|
forceNewBlock: false,
|
|
3828
3845
|
isLast: true,
|
|
3829
3846
|
translate: true,
|
|
3847
|
+
translationCtx: "",
|
|
3830
3848
|
tKeyExpr: null,
|
|
3831
3849
|
});
|
|
3832
3850
|
// define blocks and utility functions
|
|
@@ -3964,9 +3982,9 @@
|
|
|
3964
3982
|
})
|
|
3965
3983
|
.join("");
|
|
3966
3984
|
}
|
|
3967
|
-
translate(str) {
|
|
3985
|
+
translate(str, translationCtx) {
|
|
3968
3986
|
const match = translationRE.exec(str);
|
|
3969
|
-
return match[1] + this.translateFn(match[2]) + match[3];
|
|
3987
|
+
return match[1] + this.translateFn(match[2], translationCtx) + match[3];
|
|
3970
3988
|
}
|
|
3971
3989
|
/**
|
|
3972
3990
|
* @returns the newly created block name, if any
|
|
@@ -4007,7 +4025,9 @@
|
|
|
4007
4025
|
return this.compileTSlot(ast, ctx);
|
|
4008
4026
|
case 16 /* TTranslation */:
|
|
4009
4027
|
return this.compileTTranslation(ast, ctx);
|
|
4010
|
-
case 17 /*
|
|
4028
|
+
case 17 /* TTranslationContext */:
|
|
4029
|
+
return this.compileTTranslationContext(ast, ctx);
|
|
4030
|
+
case 18 /* TPortal */:
|
|
4011
4031
|
return this.compileTPortal(ast, ctx);
|
|
4012
4032
|
}
|
|
4013
4033
|
}
|
|
@@ -4045,7 +4065,7 @@
|
|
|
4045
4065
|
let { block, forceNewBlock } = ctx;
|
|
4046
4066
|
let value = ast.value;
|
|
4047
4067
|
if (value && ctx.translate !== false) {
|
|
4048
|
-
value = this.translate(value);
|
|
4068
|
+
value = this.translate(value, ctx.translationCtx);
|
|
4049
4069
|
}
|
|
4050
4070
|
if (!ctx.inPreTag) {
|
|
4051
4071
|
value = value.replace(whitespaceRE, " ");
|
|
@@ -4080,6 +4100,7 @@
|
|
|
4080
4100
|
return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
|
|
4081
4101
|
}
|
|
4082
4102
|
compileTDomNode(ast, ctx) {
|
|
4103
|
+
var _a;
|
|
4083
4104
|
let { block, forceNewBlock } = ctx;
|
|
4084
4105
|
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
|
|
4085
4106
|
let codeIdx = this.target.code.length;
|
|
@@ -4135,7 +4156,8 @@
|
|
|
4135
4156
|
}
|
|
4136
4157
|
}
|
|
4137
4158
|
else if (this.translatableAttributes.includes(key)) {
|
|
4138
|
-
|
|
4159
|
+
const attrTranslationCtx = ((_a = ast.attrsTranslationCtx) === null || _a === void 0 ? void 0 : _a[key]) || ctx.translationCtx;
|
|
4160
|
+
attrs[key] = this.translateFn(ast.attrs[key], attrTranslationCtx);
|
|
4139
4161
|
}
|
|
4140
4162
|
else {
|
|
4141
4163
|
expr = `"${ast.attrs[key]}"`;
|
|
@@ -4579,7 +4601,7 @@
|
|
|
4579
4601
|
else {
|
|
4580
4602
|
let value;
|
|
4581
4603
|
if (ast.defaultValue) {
|
|
4582
|
-
const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue);
|
|
4604
|
+
const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue, ctx.translationCtx) : ast.defaultValue);
|
|
4583
4605
|
if (ast.value) {
|
|
4584
4606
|
value = `withDefault(${expr}, ${defaultValue})`;
|
|
4585
4607
|
}
|
|
@@ -4613,9 +4635,10 @@
|
|
|
4613
4635
|
* "some-prop" "state" "'some-prop': ctx['state']"
|
|
4614
4636
|
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
|
4615
4637
|
*/
|
|
4616
|
-
formatProp(name, value) {
|
|
4638
|
+
formatProp(name, value, attrsTranslationCtx, translationCtx) {
|
|
4617
4639
|
if (name.endsWith(".translate")) {
|
|
4618
|
-
|
|
4640
|
+
const attrTranslationCtx = (attrsTranslationCtx === null || attrsTranslationCtx === void 0 ? void 0 : attrsTranslationCtx[name]) || translationCtx;
|
|
4641
|
+
value = toStringExpression(this.translateFn(value, attrTranslationCtx));
|
|
4619
4642
|
}
|
|
4620
4643
|
else {
|
|
4621
4644
|
value = this.captureExpression(value);
|
|
@@ -4631,14 +4654,14 @@
|
|
|
4631
4654
|
case "translate":
|
|
4632
4655
|
break;
|
|
4633
4656
|
default:
|
|
4634
|
-
throw new OwlError(
|
|
4657
|
+
throw new OwlError(`Invalid prop suffix: ${suffix}`);
|
|
4635
4658
|
}
|
|
4636
4659
|
}
|
|
4637
4660
|
name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
|
|
4638
4661
|
return `${name}: ${value || undefined}`;
|
|
4639
4662
|
}
|
|
4640
|
-
formatPropObject(obj) {
|
|
4641
|
-
return Object.entries(obj).map(([k, v]) => this.formatProp(k, v));
|
|
4663
|
+
formatPropObject(obj, attrsTranslationCtx, translationCtx) {
|
|
4664
|
+
return Object.entries(obj).map(([k, v]) => this.formatProp(k, v, attrsTranslationCtx, translationCtx));
|
|
4642
4665
|
}
|
|
4643
4666
|
getPropString(props, dynProps) {
|
|
4644
4667
|
let propString = `{${props.join(",")}}`;
|
|
@@ -4651,7 +4674,9 @@
|
|
|
4651
4674
|
let { block } = ctx;
|
|
4652
4675
|
// props
|
|
4653
4676
|
const hasSlotsProp = "slots" in (ast.props || {});
|
|
4654
|
-
const props = ast.props
|
|
4677
|
+
const props = ast.props
|
|
4678
|
+
? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx)
|
|
4679
|
+
: [];
|
|
4655
4680
|
// slots
|
|
4656
4681
|
let slotDef = "";
|
|
4657
4682
|
if (ast.slots) {
|
|
@@ -4674,7 +4699,7 @@
|
|
|
4674
4699
|
params.push(`__scope: "${scope}"`);
|
|
4675
4700
|
}
|
|
4676
4701
|
if (ast.slots[slotName].attrs) {
|
|
4677
|
-
params.push(...this.formatPropObject(ast.slots[slotName].attrs));
|
|
4702
|
+
params.push(...this.formatPropObject(ast.slots[slotName].attrs, ast.slots[slotName].attrsTranslationCtx, ctx.translationCtx));
|
|
4678
4703
|
}
|
|
4679
4704
|
const slotInfo = `{${params.join(", ")}}`;
|
|
4680
4705
|
slotStr.push(`'${slotName}': ${slotInfo}`);
|
|
@@ -4787,7 +4812,9 @@
|
|
|
4787
4812
|
if (isMultiple) {
|
|
4788
4813
|
key = this.generateComponentKey(key);
|
|
4789
4814
|
}
|
|
4790
|
-
const props = ast.attrs
|
|
4815
|
+
const props = ast.attrs
|
|
4816
|
+
? this.formatPropObject(ast.attrs, ast.attrsTranslationCtx, ctx.translationCtx)
|
|
4817
|
+
: [];
|
|
4791
4818
|
const scope = this.getPropString(props, dynProps);
|
|
4792
4819
|
if (ast.defaultContent) {
|
|
4793
4820
|
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
|
@@ -4820,6 +4847,12 @@
|
|
|
4820
4847
|
}
|
|
4821
4848
|
return null;
|
|
4822
4849
|
}
|
|
4850
|
+
compileTTranslationContext(ast, ctx) {
|
|
4851
|
+
if (ast.content) {
|
|
4852
|
+
return this.compileAST(ast.content, Object.assign({}, ctx, { translationCtx: ast.translationCtx }));
|
|
4853
|
+
}
|
|
4854
|
+
return null;
|
|
4855
|
+
}
|
|
4823
4856
|
compileTPortal(ast, ctx) {
|
|
4824
4857
|
if (!this.staticDefs.find((d) => d.id === "Portal")) {
|
|
4825
4858
|
this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
|
|
@@ -4889,6 +4922,7 @@
|
|
|
4889
4922
|
parseTOutNode(node, ctx) ||
|
|
4890
4923
|
parseTKey(node, ctx) ||
|
|
4891
4924
|
parseTTranslation(node, ctx) ||
|
|
4925
|
+
parseTTranslationContext(node, ctx) ||
|
|
4892
4926
|
parseTSlot(node, ctx) ||
|
|
4893
4927
|
parseComponent(node, ctx) ||
|
|
4894
4928
|
parseDOMNode(node, ctx) ||
|
|
@@ -4997,6 +5031,7 @@
|
|
|
4997
5031
|
node.removeAttribute("t-ref");
|
|
4998
5032
|
const nodeAttrsNames = node.getAttributeNames();
|
|
4999
5033
|
let attrs = null;
|
|
5034
|
+
let attrsTranslationCtx = null;
|
|
5000
5035
|
let on = null;
|
|
5001
5036
|
let model = null;
|
|
5002
5037
|
for (let attr of nodeAttrsNames) {
|
|
@@ -5057,6 +5092,11 @@
|
|
|
5057
5092
|
else if (attr === "xmlns") {
|
|
5058
5093
|
ns = value;
|
|
5059
5094
|
}
|
|
5095
|
+
else if (attr.startsWith("t-translation-context-")) {
|
|
5096
|
+
const attrName = attr.slice(22);
|
|
5097
|
+
attrsTranslationCtx = attrsTranslationCtx || {};
|
|
5098
|
+
attrsTranslationCtx[attrName] = value;
|
|
5099
|
+
}
|
|
5060
5100
|
else if (attr !== "t-name") {
|
|
5061
5101
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
|
5062
5102
|
throw new OwlError(`Unknown QWeb directive: '${attr}'`);
|
|
@@ -5078,6 +5118,7 @@
|
|
|
5078
5118
|
tag: tagName,
|
|
5079
5119
|
dynamicTag,
|
|
5080
5120
|
attrs,
|
|
5121
|
+
attrsTranslationCtx,
|
|
5081
5122
|
on,
|
|
5082
5123
|
ref,
|
|
5083
5124
|
content: children,
|
|
@@ -5218,7 +5259,15 @@
|
|
|
5218
5259
|
if (ast && ast.type === 11 /* TComponent */) {
|
|
5219
5260
|
return {
|
|
5220
5261
|
...ast,
|
|
5221
|
-
slots: {
|
|
5262
|
+
slots: {
|
|
5263
|
+
default: {
|
|
5264
|
+
content: tcall,
|
|
5265
|
+
scope: null,
|
|
5266
|
+
on: null,
|
|
5267
|
+
attrs: null,
|
|
5268
|
+
attrsTranslationCtx: null,
|
|
5269
|
+
},
|
|
5270
|
+
},
|
|
5222
5271
|
};
|
|
5223
5272
|
}
|
|
5224
5273
|
}
|
|
@@ -5333,9 +5382,15 @@
|
|
|
5333
5382
|
node.removeAttribute("t-slot-scope");
|
|
5334
5383
|
let on = null;
|
|
5335
5384
|
let props = null;
|
|
5385
|
+
let propsTranslationCtx = null;
|
|
5336
5386
|
for (let name of node.getAttributeNames()) {
|
|
5337
5387
|
const value = node.getAttribute(name);
|
|
5338
|
-
if (name.startsWith("t-")) {
|
|
5388
|
+
if (name.startsWith("t-translation-context-")) {
|
|
5389
|
+
const attrName = name.slice(22);
|
|
5390
|
+
propsTranslationCtx = propsTranslationCtx || {};
|
|
5391
|
+
propsTranslationCtx[attrName] = value;
|
|
5392
|
+
}
|
|
5393
|
+
else if (name.startsWith("t-")) {
|
|
5339
5394
|
if (name.startsWith("t-on-")) {
|
|
5340
5395
|
on = on || {};
|
|
5341
5396
|
on[name.slice(5)] = value;
|
|
@@ -5379,6 +5434,7 @@
|
|
|
5379
5434
|
const slotAst = parseNode(slotNode, ctx);
|
|
5380
5435
|
let on = null;
|
|
5381
5436
|
let attrs = null;
|
|
5437
|
+
let attrsTranslationCtx = null;
|
|
5382
5438
|
let scope = null;
|
|
5383
5439
|
for (let attributeName of slotNode.getAttributeNames()) {
|
|
5384
5440
|
const value = slotNode.getAttribute(attributeName);
|
|
@@ -5386,6 +5442,11 @@
|
|
|
5386
5442
|
scope = value;
|
|
5387
5443
|
continue;
|
|
5388
5444
|
}
|
|
5445
|
+
else if (attributeName.startsWith("t-translation-context-")) {
|
|
5446
|
+
const attrName = attributeName.slice(22);
|
|
5447
|
+
attrsTranslationCtx = attrsTranslationCtx || {};
|
|
5448
|
+
attrsTranslationCtx[attrName] = value;
|
|
5449
|
+
}
|
|
5389
5450
|
else if (attributeName.startsWith("t-on-")) {
|
|
5390
5451
|
on = on || {};
|
|
5391
5452
|
on[attributeName.slice(5)] = value;
|
|
@@ -5396,17 +5457,32 @@
|
|
|
5396
5457
|
}
|
|
5397
5458
|
}
|
|
5398
5459
|
slots = slots || {};
|
|
5399
|
-
slots[name] = { content: slotAst, on, attrs, scope };
|
|
5460
|
+
slots[name] = { content: slotAst, on, attrs, attrsTranslationCtx, scope };
|
|
5400
5461
|
}
|
|
5401
5462
|
// default slot
|
|
5402
5463
|
const defaultContent = parseChildNodes(clone, ctx);
|
|
5403
5464
|
slots = slots || {};
|
|
5404
5465
|
// t-set-slot="default" has priority over content
|
|
5405
5466
|
if (defaultContent && !slots.default) {
|
|
5406
|
-
slots.default = {
|
|
5467
|
+
slots.default = {
|
|
5468
|
+
content: defaultContent,
|
|
5469
|
+
on,
|
|
5470
|
+
attrs: null,
|
|
5471
|
+
attrsTranslationCtx: null,
|
|
5472
|
+
scope: defaultSlotScope,
|
|
5473
|
+
};
|
|
5407
5474
|
}
|
|
5408
5475
|
}
|
|
5409
|
-
return {
|
|
5476
|
+
return {
|
|
5477
|
+
type: 11 /* TComponent */,
|
|
5478
|
+
name,
|
|
5479
|
+
isDynamic,
|
|
5480
|
+
dynamicProps,
|
|
5481
|
+
props,
|
|
5482
|
+
propsTranslationCtx,
|
|
5483
|
+
slots,
|
|
5484
|
+
on,
|
|
5485
|
+
};
|
|
5410
5486
|
}
|
|
5411
5487
|
// -----------------------------------------------------------------------------
|
|
5412
5488
|
// Slots
|
|
@@ -5418,6 +5494,7 @@
|
|
|
5418
5494
|
const name = node.getAttribute("t-slot");
|
|
5419
5495
|
node.removeAttribute("t-slot");
|
|
5420
5496
|
let attrs = null;
|
|
5497
|
+
let attrsTranslationCtx = null;
|
|
5421
5498
|
let on = null;
|
|
5422
5499
|
for (let attributeName of node.getAttributeNames()) {
|
|
5423
5500
|
const value = node.getAttribute(attributeName);
|
|
@@ -5425,6 +5502,11 @@
|
|
|
5425
5502
|
on = on || {};
|
|
5426
5503
|
on[attributeName.slice(5)] = value;
|
|
5427
5504
|
}
|
|
5505
|
+
else if (attributeName.startsWith("t-translation-context-")) {
|
|
5506
|
+
const attrName = attributeName.slice(22);
|
|
5507
|
+
attrsTranslationCtx = attrsTranslationCtx || {};
|
|
5508
|
+
attrsTranslationCtx[attrName] = value;
|
|
5509
|
+
}
|
|
5428
5510
|
else {
|
|
5429
5511
|
attrs = attrs || {};
|
|
5430
5512
|
attrs[attributeName] = value;
|
|
@@ -5434,10 +5516,14 @@
|
|
|
5434
5516
|
type: 14 /* TSlot */,
|
|
5435
5517
|
name,
|
|
5436
5518
|
attrs,
|
|
5519
|
+
attrsTranslationCtx,
|
|
5437
5520
|
on,
|
|
5438
5521
|
defaultContent: parseChildNodes(node, ctx),
|
|
5439
5522
|
};
|
|
5440
5523
|
}
|
|
5524
|
+
// -----------------------------------------------------------------------------
|
|
5525
|
+
// Translation
|
|
5526
|
+
// -----------------------------------------------------------------------------
|
|
5441
5527
|
function parseTTranslation(node, ctx) {
|
|
5442
5528
|
if (node.getAttribute("t-translation") !== "off") {
|
|
5443
5529
|
return null;
|
|
@@ -5449,6 +5535,21 @@
|
|
|
5449
5535
|
};
|
|
5450
5536
|
}
|
|
5451
5537
|
// -----------------------------------------------------------------------------
|
|
5538
|
+
// Translation Context
|
|
5539
|
+
// -----------------------------------------------------------------------------
|
|
5540
|
+
function parseTTranslationContext(node, ctx) {
|
|
5541
|
+
const translationCtx = node.getAttribute("t-translation-context");
|
|
5542
|
+
if (!translationCtx) {
|
|
5543
|
+
return null;
|
|
5544
|
+
}
|
|
5545
|
+
node.removeAttribute("t-translation-context");
|
|
5546
|
+
return {
|
|
5547
|
+
type: 17 /* TTranslationContext */,
|
|
5548
|
+
content: parseNode(node, ctx),
|
|
5549
|
+
translationCtx,
|
|
5550
|
+
};
|
|
5551
|
+
}
|
|
5552
|
+
// -----------------------------------------------------------------------------
|
|
5452
5553
|
// Portal
|
|
5453
5554
|
// -----------------------------------------------------------------------------
|
|
5454
5555
|
function parseTPortal(node, ctx) {
|
|
@@ -5465,7 +5566,7 @@
|
|
|
5465
5566
|
};
|
|
5466
5567
|
}
|
|
5467
5568
|
return {
|
|
5468
|
-
type:
|
|
5569
|
+
type: 18 /* TPortal */,
|
|
5469
5570
|
target,
|
|
5470
5571
|
content,
|
|
5471
5572
|
};
|
|
@@ -5607,7 +5708,7 @@
|
|
|
5607
5708
|
}
|
|
5608
5709
|
|
|
5609
5710
|
// do not modify manually. This file is generated by the release script.
|
|
5610
|
-
const version = "2.
|
|
5711
|
+
const version = "2.6.0";
|
|
5611
5712
|
|
|
5612
5713
|
// -----------------------------------------------------------------------------
|
|
5613
5714
|
// Scheduler
|
|
@@ -6122,8 +6223,8 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|
|
6122
6223
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
6123
6224
|
|
|
6124
6225
|
|
|
6125
|
-
__info__.date = '
|
|
6126
|
-
__info__.hash = '
|
|
6226
|
+
__info__.date = '2025-01-15T10:40:24.184Z';
|
|
6227
|
+
__info__.hash = 'a9be149';
|
|
6127
6228
|
__info__.url = 'https://github.com/odoo/owl';
|
|
6128
6229
|
|
|
6129
6230
|
|