mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bc9d34a64 | |||
| b1a3b322ee | |||
| ca688d8640 | |||
| ea9f533536 | |||
| 875ebdcfb0 | |||
| 2e07799250 | |||
| 0d9d21a5c1 | |||
| 836e12b1c5 |
@@ -376,15 +376,16 @@ An important difference should be made with the usual `QWeb` behaviour: Owl
|
|||||||
requires the presence of a `t-key` directive, to be able to properly reconcile
|
requires the presence of a `t-key` directive, to be able to properly reconcile
|
||||||
renderings.
|
renderings.
|
||||||
|
|
||||||
`t-foreach` can iterate on an array (the current item will be the current value)
|
`t-foreach` can iterate on any iterable, and also has special support for objects
|
||||||
or an object (the current item will be the current key).
|
and maps, it will expose the key of the current iteration as the contents of the
|
||||||
|
`t-as`, and the corresponding value with the same name and the suffix `_value`.
|
||||||
|
|
||||||
In addition to the name passed via t-as, `t-foreach` provides a few other
|
In addition to the name passed via t-as, `t-foreach` provides a few other useful
|
||||||
variables for various data points (note: `$as` will be replaced with the name
|
variables (note: `$as` will be replaced with the name passed to `t-as`):
|
||||||
passed to `t-as`):
|
|
||||||
|
|
||||||
- `$as_value`: the current iteration value, identical to `$as` for lists and
|
- `$as_value`: the current iteration value, identical to `$as` for arrays and
|
||||||
integers, but for objects, it provides the value (where `$as` provides the key)
|
other iterables, but for objects and maps, it provides the value (where `$as`
|
||||||
|
provides the key)
|
||||||
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
||||||
- `$as_first`: whether the current item is the first of the iteration
|
- `$as_first`: whether the current item is the first of the iteration
|
||||||
(equivalent to `$as_index == 0`)
|
(equivalent to `$as_index == 0`)
|
||||||
|
|||||||
+99
-67
@@ -122,14 +122,16 @@ function handleError(params) {
|
|||||||
}
|
}
|
||||||
const node = "node" in params ? params.node : params.fiber.node;
|
const node = "node" in params ? params.node : params.fiber.node;
|
||||||
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
||||||
// resets the fibers on components if possible. This is important so that
|
if (fiber) {
|
||||||
// new renderings can be properly included in the initial one, if any.
|
// resets the fibers on components if possible. This is important so that
|
||||||
let current = fiber;
|
// new renderings can be properly included in the initial one, if any.
|
||||||
do {
|
let current = fiber;
|
||||||
current.node.fiber = current;
|
do {
|
||||||
current = current.parent;
|
current.node.fiber = current;
|
||||||
} while (current);
|
current = current.parent;
|
||||||
fibersInError.set(fiber.root, error);
|
} while (current);
|
||||||
|
fibersInError.set(fiber.root, error);
|
||||||
|
}
|
||||||
const handled = _handleError(node, error);
|
const handled = _handleError(node, error);
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
||||||
@@ -311,20 +313,13 @@ function updateClass(val, oldVal) {
|
|||||||
* @returns a batched version of the original callback
|
* @returns a batched version of the original callback
|
||||||
*/
|
*/
|
||||||
function batched(callback) {
|
function batched(callback) {
|
||||||
let called = false;
|
let scheduled = false;
|
||||||
return async () => {
|
return async (...args) => {
|
||||||
// This await blocks all calls to the callback here, then releases them sequentially
|
if (!scheduled) {
|
||||||
// in the next microtick. This line decides the granularity of the batch.
|
scheduled = true;
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
if (!called) {
|
scheduled = false;
|
||||||
called = true;
|
callback(...args);
|
||||||
// wait for all calls in this microtick to fall through before resetting "called"
|
|
||||||
// so that only the first call to the batched function calls the original callback.
|
|
||||||
// Schedule this before calling the callback so that calls to the batched function
|
|
||||||
// within the callback will proceed only after resetting called to false, and have
|
|
||||||
// a chance to execute the callback again
|
|
||||||
Promise.resolve().then(() => (called = false));
|
|
||||||
callback();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1657,8 +1652,7 @@ function cancelFibers(fibers) {
|
|||||||
let node = fiber.node;
|
let node = fiber.node;
|
||||||
fiber.render = throwOnRender;
|
fiber.render = throwOnRender;
|
||||||
if (node.status === 0 /* NEW */) {
|
if (node.status === 0 /* NEW */) {
|
||||||
node.destroy();
|
node.cancel();
|
||||||
delete node.parent.children[node.parentKey];
|
|
||||||
}
|
}
|
||||||
node.fiber = null;
|
node.fiber = null;
|
||||||
if (fiber.bdom) {
|
if (fiber.bdom) {
|
||||||
@@ -2385,6 +2379,9 @@ class ComponentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async render(deep) {
|
async render(deep) {
|
||||||
|
if (this.status >= 2 /* CANCELLED */) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let current = this.fiber;
|
let current = this.fiber;
|
||||||
if (current && (current.root.locked || current.bdom === true)) {
|
if (current && (current.root.locked || current.bdom === true)) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -2410,7 +2407,7 @@ class ComponentNode {
|
|||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
this.app.scheduler.addFiber(fiber);
|
this.app.scheduler.addFiber(fiber);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
if (this.status === 2 /* DESTROYED */) {
|
if (this.status >= 2 /* CANCELLED */) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We only want to actually render the component if the following two
|
// We only want to actually render the component if the following two
|
||||||
@@ -2428,6 +2425,18 @@ class ComponentNode {
|
|||||||
fiber.render();
|
fiber.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cancel() {
|
||||||
|
this._cancel();
|
||||||
|
delete this.parent.children[this.parentKey];
|
||||||
|
this.app.scheduler.scheduleDestroy(this);
|
||||||
|
}
|
||||||
|
_cancel() {
|
||||||
|
this.status = 2 /* CANCELLED */;
|
||||||
|
const children = this.children;
|
||||||
|
for (let childKey in children) {
|
||||||
|
children[childKey]._cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
destroy() {
|
destroy() {
|
||||||
let shouldRemove = this.status === 1 /* MOUNTED */;
|
let shouldRemove = this.status === 1 /* MOUNTED */;
|
||||||
this._destroy();
|
this._destroy();
|
||||||
@@ -2455,7 +2464,7 @@ class ComponentNode {
|
|||||||
this.app.handleError({ error: e, node: this });
|
this.app.handleError({ error: e, node: this });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.status = 2 /* DESTROYED */;
|
this.status = 3 /* DESTROYED */;
|
||||||
}
|
}
|
||||||
async updateAndRender(props, parentFiber) {
|
async updateAndRender(props, parentFiber) {
|
||||||
this.nextProps = props;
|
this.nextProps = props;
|
||||||
@@ -2988,12 +2997,22 @@ function prepareList(collection) {
|
|||||||
keys = collection;
|
keys = collection;
|
||||||
values = collection;
|
values = collection;
|
||||||
}
|
}
|
||||||
else if (collection) {
|
else if (collection instanceof Map) {
|
||||||
values = Object.keys(collection);
|
keys = [...collection.keys()];
|
||||||
keys = Object.values(collection);
|
values = [...collection.values()];
|
||||||
|
}
|
||||||
|
else if (collection && typeof collection === "object") {
|
||||||
|
if (Symbol.iterator in collection) {
|
||||||
|
keys = [...collection];
|
||||||
|
values = keys;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
values = Object.keys(collection);
|
||||||
|
keys = Object.values(collection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new OwlError("Invalid loop expression");
|
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||||
}
|
}
|
||||||
const n = values.length;
|
const n = values.length;
|
||||||
return [keys, values, n, new Array(n)];
|
return [keys, values, n, new Array(n)];
|
||||||
@@ -4820,10 +4839,10 @@ function parseNode(node, ctx) {
|
|||||||
parseTCall(node, ctx) ||
|
parseTCall(node, ctx) ||
|
||||||
parseTCallBlock(node) ||
|
parseTCallBlock(node) ||
|
||||||
parseTEscNode(node, ctx) ||
|
parseTEscNode(node, ctx) ||
|
||||||
|
parseTOutNode(node, ctx) ||
|
||||||
parseTKey(node, ctx) ||
|
parseTKey(node, ctx) ||
|
||||||
parseTTranslation(node, ctx) ||
|
parseTTranslation(node, ctx) ||
|
||||||
parseTSlot(node, ctx) ||
|
parseTSlot(node, ctx) ||
|
||||||
parseTOutNode(node, ctx) ||
|
|
||||||
parseComponent(node, ctx) ||
|
parseComponent(node, ctx) ||
|
||||||
parseDOMNode(node, ctx) ||
|
parseDOMNode(node, ctx) ||
|
||||||
parseTSetNode(node, ctx) ||
|
parseTSetNode(node, ctx) ||
|
||||||
@@ -4936,10 +4955,8 @@ function parseDOMNode(node, ctx) {
|
|||||||
const typeAttr = node.getAttribute("type");
|
const typeAttr = node.getAttribute("type");
|
||||||
const isInput = tagName === "input";
|
const isInput = tagName === "input";
|
||||||
const isSelect = tagName === "select";
|
const isSelect = tagName === "select";
|
||||||
const isTextarea = tagName === "textarea";
|
|
||||||
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
||||||
const isRadioInput = isInput && typeAttr === "radio";
|
const isRadioInput = isInput && typeAttr === "radio";
|
||||||
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
|
||||||
const hasLazyMod = attr.includes(".lazy");
|
const hasLazyMod = attr.includes(".lazy");
|
||||||
const hasNumberMod = attr.includes(".number");
|
const hasNumberMod = attr.includes(".number");
|
||||||
const hasTrimMod = attr.includes(".trim");
|
const hasTrimMod = attr.includes(".trim");
|
||||||
@@ -4951,8 +4968,8 @@ function parseDOMNode(node, ctx) {
|
|||||||
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
||||||
eventType,
|
eventType,
|
||||||
hasDynamicChildren: false,
|
hasDynamicChildren: false,
|
||||||
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
shouldTrim: hasTrimMod,
|
||||||
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
shouldNumberize: hasNumberMod,
|
||||||
};
|
};
|
||||||
if (isSelect) {
|
if (isSelect) {
|
||||||
// don't pollute the original ctx
|
// don't pollute the original ctx
|
||||||
@@ -5015,9 +5032,6 @@ function parseTEscNode(node, ctx) {
|
|||||||
content: [tesc],
|
content: [tesc],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (ast.type === 11 /* TComponent */) {
|
|
||||||
throw new OwlError("t-esc is not supported on Component nodes");
|
|
||||||
}
|
|
||||||
return tesc;
|
return tesc;
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -5459,19 +5473,21 @@ function normalizeTIf(el) {
|
|||||||
*
|
*
|
||||||
* @param el the element containing the tree that should be normalized
|
* @param el the element containing the tree that should be normalized
|
||||||
*/
|
*/
|
||||||
function normalizeTEsc(el) {
|
function normalizeTEscTOut(el) {
|
||||||
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
for (const d of ["t-esc", "t-out"]) {
|
||||||
for (const el of elements) {
|
const elements = [...el.querySelectorAll(`[${d}]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
|
||||||
if (el.childNodes.length) {
|
for (const el of elements) {
|
||||||
throw new OwlError("Cannot have t-esc on a component that already has content");
|
if (el.childNodes.length) {
|
||||||
|
throw new OwlError(`Cannot have ${d} on a component that already has content`);
|
||||||
|
}
|
||||||
|
const value = el.getAttribute(d);
|
||||||
|
el.removeAttribute(d);
|
||||||
|
const t = el.ownerDocument.createElement("t");
|
||||||
|
if (value != null) {
|
||||||
|
t.setAttribute(d, value);
|
||||||
|
}
|
||||||
|
el.appendChild(t);
|
||||||
}
|
}
|
||||||
const value = el.getAttribute("t-esc");
|
|
||||||
el.removeAttribute("t-esc");
|
|
||||||
const t = el.ownerDocument.createElement("t");
|
|
||||||
if (value != null) {
|
|
||||||
t.setAttribute("t-esc", value);
|
|
||||||
}
|
|
||||||
el.appendChild(t);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -5482,7 +5498,7 @@ function normalizeTEsc(el) {
|
|||||||
*/
|
*/
|
||||||
function normalizeXML(el) {
|
function normalizeXML(el) {
|
||||||
normalizeTIf(el);
|
normalizeTIf(el);
|
||||||
normalizeTEsc(el);
|
normalizeTEscTOut(el);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Parses an XML string into an XML document, throwing errors on parser errors
|
* Parses an XML string into an XML document, throwing errors on parser errors
|
||||||
@@ -5535,7 +5551,7 @@ function compile(template, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
const version = "2.1.3";
|
const version = "2.2.3";
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Scheduler
|
// Scheduler
|
||||||
@@ -5545,11 +5561,18 @@ class Scheduler {
|
|||||||
this.tasks = new Set();
|
this.tasks = new Set();
|
||||||
this.frame = 0;
|
this.frame = 0;
|
||||||
this.delayedRenders = [];
|
this.delayedRenders = [];
|
||||||
|
this.cancelledNodes = new Set();
|
||||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||||
}
|
}
|
||||||
addFiber(fiber) {
|
addFiber(fiber) {
|
||||||
this.tasks.add(fiber.root);
|
this.tasks.add(fiber.root);
|
||||||
}
|
}
|
||||||
|
scheduleDestroy(node) {
|
||||||
|
this.cancelledNodes.add(node);
|
||||||
|
if (this.frame === 0) {
|
||||||
|
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Process all current tasks. This only applies to the fibers that are ready.
|
* Process all current tasks. This only applies to the fibers that are ready.
|
||||||
* Other tasks are left unchanged.
|
* Other tasks are left unchanged.
|
||||||
@@ -5559,21 +5582,28 @@ class Scheduler {
|
|||||||
let renders = this.delayedRenders;
|
let renders = this.delayedRenders;
|
||||||
this.delayedRenders = [];
|
this.delayedRenders = [];
|
||||||
for (let f of renders) {
|
for (let f of renders) {
|
||||||
if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
|
if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {
|
||||||
f.render();
|
f.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.frame === 0) {
|
if (this.frame === 0) {
|
||||||
this.frame = this.requestAnimationFrame(() => {
|
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||||
this.frame = 0;
|
}
|
||||||
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
}
|
||||||
for (let task of this.tasks) {
|
processTasks() {
|
||||||
if (task.node.status === 2 /* DESTROYED */) {
|
this.frame = 0;
|
||||||
this.tasks.delete(task);
|
for (let node of this.cancelledNodes) {
|
||||||
}
|
node._destroy();
|
||||||
}
|
}
|
||||||
});
|
this.cancelledNodes.clear();
|
||||||
|
for (let task of this.tasks) {
|
||||||
|
this.processFiber(task);
|
||||||
|
}
|
||||||
|
for (let task of this.tasks) {
|
||||||
|
if (task.node.status === 3 /* DESTROYED */) {
|
||||||
|
this.tasks.delete(task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processFiber(fiber) {
|
processFiber(fiber) {
|
||||||
@@ -5586,7 +5616,7 @@ class Scheduler {
|
|||||||
this.tasks.delete(fiber);
|
this.tasks.delete(fiber);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fiber.node.status === 2 /* DESTROYED */) {
|
if (fiber.node.status === 3 /* DESTROYED */) {
|
||||||
this.tasks.delete(fiber);
|
this.tasks.delete(fiber);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -5678,8 +5708,8 @@ class App extends TemplateSet {
|
|||||||
}
|
}
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.root) {
|
if (this.root) {
|
||||||
this.scheduler.flush();
|
|
||||||
this.root.destroy();
|
this.root.destroy();
|
||||||
|
this.scheduler.processTasks();
|
||||||
}
|
}
|
||||||
window.__OWL_DEVTOOLS__.apps.delete(this);
|
window.__OWL_DEVTOOLS__.apps.delete(this);
|
||||||
}
|
}
|
||||||
@@ -5810,9 +5840,11 @@ function status(component) {
|
|||||||
switch (component.__owl__.status) {
|
switch (component.__owl__.status) {
|
||||||
case 0 /* NEW */:
|
case 0 /* NEW */:
|
||||||
return "new";
|
return "new";
|
||||||
|
case 2 /* CANCELLED */:
|
||||||
|
return "cancelled";
|
||||||
case 1 /* MOUNTED */:
|
case 1 /* MOUNTED */:
|
||||||
return "mounted";
|
return "mounted";
|
||||||
case 2 /* DESTROYED */:
|
case 3 /* DESTROYED */:
|
||||||
return "destroyed";
|
return "destroyed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5952,6 +5984,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
|
|||||||
export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
|
export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
|
||||||
|
|
||||||
|
|
||||||
__info__.date = '2023-06-28T09:17:13.630Z';
|
__info__.date = '2023-07-20T06:05:29.796Z';
|
||||||
__info__.hash = '432ff44';
|
__info__.hash = 'b1a3b32';
|
||||||
__info__.url = 'https://github.com/odoo/owl';
|
__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.1.4",
|
"version": "2.2.3",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.1.4",
|
"version": "2.2.3",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"module": "dist/owl.es.js",
|
"module": "dist/owl.es.js",
|
||||||
|
|||||||
+18
-19
@@ -235,10 +235,10 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
|||||||
parseTCall(node, ctx) ||
|
parseTCall(node, ctx) ||
|
||||||
parseTCallBlock(node, ctx) ||
|
parseTCallBlock(node, ctx) ||
|
||||||
parseTEscNode(node, ctx) ||
|
parseTEscNode(node, ctx) ||
|
||||||
|
parseTOutNode(node, ctx) ||
|
||||||
parseTKey(node, ctx) ||
|
parseTKey(node, ctx) ||
|
||||||
parseTTranslation(node, ctx) ||
|
parseTTranslation(node, ctx) ||
|
||||||
parseTSlot(node, ctx) ||
|
parseTSlot(node, ctx) ||
|
||||||
parseTOutNode(node, ctx) ||
|
|
||||||
parseComponent(node, ctx) ||
|
parseComponent(node, ctx) ||
|
||||||
parseDOMNode(node, ctx) ||
|
parseDOMNode(node, ctx) ||
|
||||||
parseTSetNode(node, ctx) ||
|
parseTSetNode(node, ctx) ||
|
||||||
@@ -444,9 +444,6 @@ function parseTEscNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
content: [tesc],
|
content: [tesc],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (ast.type === ASTType.TComponent) {
|
|
||||||
throw new OwlError("t-esc is not supported on Component nodes");
|
|
||||||
}
|
|
||||||
return tesc;
|
return tesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,21 +938,23 @@ function normalizeTIf(el: Element) {
|
|||||||
*
|
*
|
||||||
* @param el the element containing the tree that should be normalized
|
* @param el the element containing the tree that should be normalized
|
||||||
*/
|
*/
|
||||||
function normalizeTEsc(el: Element) {
|
function normalizeTEscTOut(el: Element) {
|
||||||
const elements = [...el.querySelectorAll("[t-esc]")].filter(
|
for (const d of ["t-esc", "t-out"]) {
|
||||||
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
|
const elements = [...el.querySelectorAll(`[${d}]`)].filter(
|
||||||
);
|
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
|
||||||
for (const el of elements) {
|
);
|
||||||
if (el.childNodes.length) {
|
for (const el of elements) {
|
||||||
throw new OwlError("Cannot have t-esc on a component that already has content");
|
if (el.childNodes.length) {
|
||||||
|
throw new OwlError(`Cannot have ${d} on a component that already has content`);
|
||||||
|
}
|
||||||
|
const value = el.getAttribute(d);
|
||||||
|
el.removeAttribute(d);
|
||||||
|
const t = el.ownerDocument.createElement("t");
|
||||||
|
if (value != null) {
|
||||||
|
t.setAttribute(d, value);
|
||||||
|
}
|
||||||
|
el.appendChild(t);
|
||||||
}
|
}
|
||||||
const value = el.getAttribute("t-esc");
|
|
||||||
el.removeAttribute("t-esc");
|
|
||||||
const t = el.ownerDocument.createElement("t");
|
|
||||||
if (value != null) {
|
|
||||||
t.setAttribute("t-esc", value);
|
|
||||||
}
|
|
||||||
el.appendChild(t);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,7 +966,7 @@ function normalizeTEsc(el: Element) {
|
|||||||
*/
|
*/
|
||||||
function normalizeXML(el: Element) {
|
function normalizeXML(el: Element) {
|
||||||
normalizeTIf(el);
|
normalizeTIf(el);
|
||||||
normalizeTEsc(el);
|
normalizeTEscTOut(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -136,8 +136,8 @@ export class App<
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.root) {
|
if (this.root) {
|
||||||
this.scheduler.flush();
|
|
||||||
this.root.destroy();
|
this.root.destroy();
|
||||||
|
this.scheduler.processTasks();
|
||||||
}
|
}
|
||||||
window.__OWL_DEVTOOLS__.apps.delete(this);
|
window.__OWL_DEVTOOLS__.apps.delete(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,18 +60,26 @@ function withKey(elem: any, k: string) {
|
|||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareList(collection: any): [any[], any[], number, any[]] {
|
function prepareList(collection: unknown): [unknown[], unknown[], number, undefined[]] {
|
||||||
let keys: any[];
|
let keys: unknown[];
|
||||||
let values: any[];
|
let values: unknown[];
|
||||||
|
|
||||||
if (Array.isArray(collection)) {
|
if (Array.isArray(collection)) {
|
||||||
keys = collection;
|
keys = collection;
|
||||||
values = collection;
|
values = collection;
|
||||||
} else if (collection) {
|
} else if (collection instanceof Map) {
|
||||||
values = Object.keys(collection);
|
keys = [...collection.keys()];
|
||||||
keys = Object.values(collection);
|
values = [...collection.values()];
|
||||||
|
} else if (collection && typeof collection === "object") {
|
||||||
|
if (Symbol.iterator in collection) {
|
||||||
|
keys = [...(<Iterable<unknown>>collection)];
|
||||||
|
values = keys;
|
||||||
|
} else {
|
||||||
|
values = Object.keys(collection);
|
||||||
|
keys = Object.values(collection);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new OwlError("Invalid loop expression");
|
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||||
}
|
}
|
||||||
const n = values.length;
|
const n = values.length;
|
||||||
return [keys, values, n, new Array(n)];
|
return [keys, values, n, new Array(n)];
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
export const version = "2.1.4";
|
export const version = "2.2.3";
|
||||||
|
|||||||
@@ -15,6 +15,34 @@ exports[`app App supports env with getters/setters 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
const comp1 = app.createComponent(\`B\`, true, false, false, []);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(\`A\`);
|
||||||
|
if (ctx['state'].value) {
|
||||||
|
b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`B\`);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app can configure an app with props 1`] = `
|
exports[`app can configure an app with props 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
+50
-2
@@ -1,6 +1,14 @@
|
|||||||
import { App, Component, mount, xml } from "../../src";
|
import { App, Component, mount, onWillStart, useState, xml } from "../../src";
|
||||||
import { status } from "../../src/runtime/status";
|
import { status } from "../../src/runtime/status";
|
||||||
import { makeTestFixture, snapshotEverything, nextTick, elem } from "../helpers";
|
import {
|
||||||
|
makeTestFixture,
|
||||||
|
snapshotEverything,
|
||||||
|
nextTick,
|
||||||
|
elem,
|
||||||
|
useLogLifecycle,
|
||||||
|
makeDeferred,
|
||||||
|
nextMicroTick,
|
||||||
|
} from "../helpers";
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
|
|
||||||
@@ -94,4 +102,44 @@ describe("app", () => {
|
|||||||
expect(iframeDoc.contains(div)).toBe(false);
|
expect(iframeDoc.contains(div)).toBe(false);
|
||||||
expect(status(comp)).toBe("destroyed");
|
expect(status(comp)).toBe("destroyed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("app: clear scheduler tasks and destroy cancelled nodes immediately on destroy", async () => {
|
||||||
|
let def = makeDeferred();
|
||||||
|
class B extends Component {
|
||||||
|
static template = xml`B`;
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onWillStart(() => def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class A extends Component {
|
||||||
|
static template = xml`A<t t-if="state.value"><B/></t>`;
|
||||||
|
static components = { B };
|
||||||
|
state = useState({ value: false });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new App(A);
|
||||||
|
const comp = await app.mount(fixture);
|
||||||
|
expect(["A:setup", "A:willStart", "A:willRender", "A:rendered", "A:mounted"]).toBeLogged();
|
||||||
|
|
||||||
|
comp.state.value = true;
|
||||||
|
await nextTick();
|
||||||
|
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged();
|
||||||
|
|
||||||
|
// rerender to force the instantiation of a new B component (and cancelling the first)
|
||||||
|
comp.render();
|
||||||
|
await nextMicroTick();
|
||||||
|
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged();
|
||||||
|
|
||||||
|
app.destroy();
|
||||||
|
expect([
|
||||||
|
"A:willUnmount",
|
||||||
|
"B:willDestroy",
|
||||||
|
"A:willDestroy",
|
||||||
|
"B:willDestroy", // make sure the 2 B instances have been destroyed synchronously
|
||||||
|
]).toBeLogged();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,6 +77,62 @@ exports[`t-foreach iterate on items 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, Map param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, Set param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-foreach iterate, dict param 1`] = `
|
exports[`t-foreach iterate, dict param 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -108,6 +164,62 @@ exports[`t-foreach iterate, dict param 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, generator param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['gen']());;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, iterable param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['map'].values());;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-foreach iterate, position 1`] = `
|
exports[`t-foreach iterate, position 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1569,6 +1569,12 @@ describe("qweb parser", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("component with t-out", async () => {
|
||||||
|
expect(parse(`<MyComponent t-out="someValue"/>`)).toEqual(
|
||||||
|
parse(`<MyComponent><t t-out="someValue"/></MyComponent>`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("component with t-esc and content", async () => {
|
test("component with t-esc and content", async () => {
|
||||||
expect(() => parse(`<MyComponent t-esc="someValue">Some content</MyComponent>`)).toThrow(
|
expect(() => parse(`<MyComponent t-esc="someValue">Some content</MyComponent>`)).toThrow(
|
||||||
"Cannot have t-esc on a component that already has content"
|
"Cannot have t-esc on a component that already has content"
|
||||||
|
|||||||
@@ -105,6 +105,64 @@ describe("t-foreach", () => {
|
|||||||
expect(renderToString(template, context)).toBe(expected);
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("iterate, Map param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 a] [1: 2 b] [2: 3 c] `;
|
||||||
|
const context = {
|
||||||
|
value: new Map([
|
||||||
|
["a", 1],
|
||||||
|
["b", 2],
|
||||||
|
["c", 3],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("iterate, Set param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||||
|
const context = { value: new Set([1, 2, 3]) };
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("iterate, iterable param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="map.values()" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||||
|
const context = {
|
||||||
|
map: new Map([
|
||||||
|
["a", 1],
|
||||||
|
["b", 2],
|
||||||
|
["c", 3],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("iterate, generator param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="gen()" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||||
|
const context = {
|
||||||
|
*gen() {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
yield 3;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
test("does not pollute the rendering context", () => {
|
test("does not pollute the rendering context", () => {
|
||||||
const template = `
|
const template = `
|
||||||
<div>
|
<div>
|
||||||
@@ -193,7 +251,9 @@ describe("t-foreach", () => {
|
|||||||
|
|
||||||
test("throws error if invalid loop expression", () => {
|
test("throws error if invalid loop expression", () => {
|
||||||
const test = `<div><t t-foreach="abc" t-as="item" t-key="item"><span t-key="item_index"/></t></div>`;
|
const test = `<div><t t-foreach="abc" t-as="item" t-key="item"><span t-key="item_index"/></t></div>`;
|
||||||
expect(() => renderToString(test)).toThrow("Invalid loop expression");
|
expect(() => renderToString(test)).toThrow(
|
||||||
|
'Invalid loop expression: "undefined" is not iterable'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-foreach with t-if inside", () => {
|
test("t-foreach with t-if inside", () => {
|
||||||
|
|||||||
+6
-7
@@ -82,6 +82,12 @@ async function startRelease() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
log(`Step ${step++}/${STEPS}: updating package.json...`);
|
||||||
|
await writeFile("package.json", JSON.stringify({...package, version: next}, null, 2) + "\n");
|
||||||
|
await writeFile("package-lock.json", JSON.stringify({...packageLock, version: next}, null, 2) + "\n");
|
||||||
|
await writeFile("./src/version.ts", `// do not modify manually. This file is generated by the release script.\nexport const version = "${next}";\n`);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step ${step++}/${STEPS}: building owl...`);
|
log(`Step ${step++}/${STEPS}: building owl...`);
|
||||||
await execCommand("rm -rf dist/");
|
await execCommand("rm -rf dist/");
|
||||||
@@ -108,13 +114,6 @@ async function startRelease() {
|
|||||||
await execCommand("cd dist && zip -r owl-devtools.zip devtools-chrome devtools-firefox && cd ..");
|
await execCommand("cd dist && zip -r owl-devtools.zip devtools-chrome devtools-firefox && cd ..");
|
||||||
await execCommand("rm -r dist/devtools-chrome dist/devtools-firefox && rm dist/compiler.js");
|
await execCommand("rm -r dist/devtools-chrome dist/devtools-firefox && rm dist/compiler.js");
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
log(`Step ${step++}/${STEPS}: updating package.json...`);
|
|
||||||
await writeFile("package.json", JSON.stringify({...package, version: next}, null, 2) + "\n");
|
|
||||||
await writeFile("package-lock.json", JSON.stringify({...packageLock, version: next}, null, 2) + "\n");
|
|
||||||
await writeFile("./src/version.ts", `// do not modify manually. This file is generated by the release script.\nexport const version = "${next}";\n`);
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step ${step++}/${STEPS}: updating owl on github page...`);
|
log(`Step ${step++}/${STEPS}: updating owl on github page...`);
|
||||||
await fs.copyFileSync("dist/owl.es.js", "docs/owl.js");
|
await fs.copyFileSync("dist/owl.es.js", "docs/owl.js");
|
||||||
|
|||||||
Reference in New Issue
Block a user