mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[DOC] move reference doc in subfolder
This commit is contained in:
@@ -54,8 +54,8 @@ const app = new App({ qweb: new QWeb() });
|
|||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the counter component is made reactive with the [`useState` hook](doc/hooks.md#usestate).
|
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
|
||||||
Also, all examples here uses the [`xml` helper](doc/tags.md#xml-tag) to define inline templates.
|
Also, all examples here uses the [`xml` helper](doc/reference/tags.md#xml-tag) to define inline templates.
|
||||||
But this is not mandatory, many applications will load templates separately.
|
But this is not mandatory, many applications will load templates separately.
|
||||||
|
|
||||||
More interesting examples can be found on the
|
More interesting examples can be found on the
|
||||||
@@ -93,8 +93,8 @@ A complete documentation for Owl can be found here:
|
|||||||
The most important sections are:
|
The most important sections are:
|
||||||
|
|
||||||
- [Quick Start](doc/quick_start.md)
|
- [Quick Start](doc/quick_start.md)
|
||||||
- [Component](doc/component.md)
|
- [Component](doc/reference/component.md)
|
||||||
- [Hooks](doc/hooks.md)
|
- [Hooks](doc/reference/hooks.md)
|
||||||
|
|
||||||
Found an issue in the documentation? A broken link? Some outdated information?
|
Found an issue in the documentation? A broken link? Some outdated information?
|
||||||
Submit a PR!
|
Submit a PR!
|
||||||
@@ -128,7 +128,7 @@ Owl components in an application are used to define a (dynamic) tree of componen
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Environment:** the root component is special: it is created with an environment,
|
**Environment:** the root component is special: it is created with an environment,
|
||||||
which should contain a [`QWeb` instance](doc/qweb.md). The environment is then automatically
|
which should contain a [`QWeb` instance](doc/reference/qweb.md). The environment is then automatically
|
||||||
propagated to each sub components (and accessible in the `this.env` property).
|
propagated to each sub components (and accessible in the `this.env` property).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@@ -255,7 +255,7 @@ class Parent extends Component {
|
|||||||
In this example, the `OrderLine` component trigger a `add-to-order` event. This
|
In this example, the `OrderLine` component trigger a `add-to-order` event. This
|
||||||
will generate a DOM event which will bubble along the DOM tree. It will then be
|
will generate a DOM event which will bubble along the DOM tree. It will then be
|
||||||
intercepted by the parent component, which will then get the line (from the
|
intercepted by the parent component, which will then get the line (from the
|
||||||
`detail` key) and then increment its quantity. See the section on [event handling](doc/component.md#event-handling)
|
`detail` key) and then increment its quantity. See the section on [event handling](doc/reference/component.md#event-handling)
|
||||||
for more details on how events work.
|
for more details on how events work.
|
||||||
|
|
||||||
Note that this example would have also worked if the `OrderLine` component
|
Note that this example would have also worked if the `OrderLine` component
|
||||||
|
|||||||
+4
-4
@@ -79,7 +79,7 @@ additional tools, we made a lot of effort to make the most of the web platform.
|
|||||||
|
|
||||||
For example, Owl uses the standard `xml` parser that comes with every browser.
|
For example, Owl uses the standard `xml` parser that comes with every browser.
|
||||||
Because of that, Owl did not have to write its own template parser. Another
|
Because of that, Owl did not have to write its own template parser. Another
|
||||||
example is the [`xml`](tags.md#xml-tag) tag helper function, which makes use of
|
example is the [`xml`](reference/tags.md#xml-tag) tag helper function, which makes use of
|
||||||
native template literals to allow in a natural way to write `xml` templates
|
native template literals to allow in a natural way to write `xml` templates
|
||||||
directly in the javascript code. This can be easily integrated with editor
|
directly in the javascript code. This can be easily integrated with editor
|
||||||
plugins to have autocompletion inside the template.
|
plugins to have autocompletion inside the template.
|
||||||
@@ -127,7 +127,7 @@ structured than a template language. Note that the tooling is quite impressive:
|
|||||||
there is a syntax highlighter for jsx here on github!
|
there is a syntax highlighter for jsx here on github!
|
||||||
|
|
||||||
By comparison, here is the equivalent Owl component, written with the
|
By comparison, here is the equivalent Owl component, written with the
|
||||||
[`xml`](tags.md#xml-tag) tag helper:
|
[`xml`](reference/tags.md#xml-tag) tag helper:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class Clock extends Component {
|
class Clock extends Component {
|
||||||
@@ -251,7 +251,7 @@ keeps track of who get data, and retrigger a render when it was changed.
|
|||||||
Owl store is a little bit like a mix of redux and vuex: it has actions (but not
|
Owl store is a little bit like a mix of redux and vuex: it has actions (but not
|
||||||
mutations), and like VueX, it keeps track of the state changes. However, it does
|
mutations), and like VueX, it keeps track of the state changes. However, it does
|
||||||
not notify a component when the state changes. Instead, components need to connect
|
not notify a component when the state changes. Instead, components need to connect
|
||||||
to the store like in redux, with the `useStore` hook (see the [store documentation](store.md#connecting-a-component)).
|
to the store like in redux, with the `useStore` hook (see the [store documentation](reference/store.md#connecting-a-component)).
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const actions = {
|
const actions = {
|
||||||
@@ -314,7 +314,7 @@ This work is based on the new ideas introduced by React hooks.
|
|||||||
|
|
||||||
From the way React and Vue introduce their hooks, it may look like hooks are not
|
From the way React and Vue introduce their hooks, it may look like hooks are not
|
||||||
compatible with class components. However, this is not the case, as shown by
|
compatible with class components. However, this is not the case, as shown by
|
||||||
Owl [hooks](hooks.md). They are inspired by both React and Vue. For example,
|
Owl [hooks](reference/hooks.md). They are inspired by both React and Vue. For example,
|
||||||
the `useState` hook is named after React, but its API is closer to the `reactive`
|
the `useState` hook is named after React, but its API is closer to the `reactive`
|
||||||
Vue hook.
|
Vue hook.
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -50,19 +50,19 @@ Note that for convenience, the `useState` hook is also exported at the root of t
|
|||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
- [Animations](animations.md)
|
- [Animations](reference/animations.md)
|
||||||
- [Component](component.md)
|
- [Component](reference/component.md)
|
||||||
- [Context](context.md)
|
- [Context](reference/context.md)
|
||||||
- [Event Bus](event_bus.md)
|
- [Event Bus](reference/event_bus.md)
|
||||||
- [Hooks](hooks.md)
|
- [Hooks](reference/hooks.md)
|
||||||
- [Misc](misc.md)
|
- [Misc](reference/misc.md)
|
||||||
- [Observer](observer.md)
|
- [Observer](reference/observer.md)
|
||||||
- [QWeb](qweb.md)
|
- [QWeb](reference/qweb.md)
|
||||||
- [Router](router.md)
|
- [Router](reference/router.md)
|
||||||
- [Store](store.md)
|
- [Store](reference/store.md)
|
||||||
- [Tags](tags.md)
|
- [Tags](reference/tags.md)
|
||||||
- [Utils](utils.md)
|
- [Utils](reference/utils.md)
|
||||||
- [Virtual DOM](vdom.md)
|
- [Virtual DOM](architecture/vdom.md)
|
||||||
|
|
||||||
## Learning Resources
|
## Learning Resources
|
||||||
|
|
||||||
|
|||||||
@@ -986,7 +986,7 @@ of the props. Here is how it works in Owl:
|
|||||||
- `props` key is a static key (so, different from `this.props` in a component instance)
|
- `props` key is a static key (so, different from `this.props` in a component instance)
|
||||||
- it is optional: it is ok for a component to not define a `props` key.
|
- it is optional: it is ok for a component to not define a `props` key.
|
||||||
- props are validated whenever a component is created/updated
|
- props are validated whenever a component is created/updated
|
||||||
- props are only validated in `dev` mode (see [tooling page](tooling.md#development-mode))
|
- props are only validated in `dev` mode (see [tooling page](../tooling.md#development-mode))
|
||||||
- if a key does not match the description, an error is thrown
|
- if a key does not match the description, an error is thrown
|
||||||
- it validates keys defined in (static) `props`. Additional keys given by the
|
- it validates keys defined in (static) `props`. Additional keys given by the
|
||||||
parent will cause an error.
|
parent will cause an error.
|
||||||
@@ -121,7 +121,7 @@ Its API is quite simple:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **`render(name, context, extra)`**: renders a template. This returns a `vnode`,
|
- **`render(name, context, extra)`**: renders a template. This returns a `vnode`,
|
||||||
which is a virtual representation of the DOM (see [vdom doc](vdom.md)).
|
which is a virtual representation of the DOM (see [vdom doc](../architecture/vdom.md)).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const vnode = qweb.render("App", component);
|
const vnode = qweb.render("App", component);
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
Tags are very small helpers to make it easy to write inline templates. There is
|
Tags are very small helpers to make it easy to write inline templates. There is
|
||||||
only one currently available tag: `xml`, but we plan to add other tags later,
|
only one currently available tag: `xml`, but we plan to add other tags later,
|
||||||
such as a `css` tag, which will be used to write [single file components](tooling.md#single-file-component).
|
such as a `css` tag, which will be used to write [single file components](../tooling.md#single-file-component).
|
||||||
|
|
||||||
## XML tag
|
## XML tag
|
||||||
|
|
||||||
+2
-2
@@ -64,8 +64,8 @@ It is very useful to group code by feature instead of by type of file. It makes
|
|||||||
it easier to scale application to larger size.
|
it easier to scale application to larger size.
|
||||||
|
|
||||||
To do so, Owl currently has a small helper that makes it easy to define a
|
To do so, Owl currently has a small helper that makes it easy to define a
|
||||||
template inside a javascript (or typescript) file: the [`xml`](tags.md#xml-tag)
|
template inside a javascript (or typescript) file: the [`xml`](reference/tags.md#xml-tag)
|
||||||
helper. With this, a template is automatically registered to [QWeb](qweb.md).
|
helper. With this, a template is automatically registered to [QWeb](reference/qweb.md).
|
||||||
|
|
||||||
This means that the template and the javascript code can be defined in the same
|
This means that the template and the javascript code can be defined in the same
|
||||||
file. It is not currently possible to add css to the same file, but Owl may
|
file. It is not currently possible to add css to the same file, but Owl may
|
||||||
|
|||||||
+135
-78
@@ -6,36 +6,9 @@
|
|||||||
*/
|
*/
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
|
||||||
const LINK_REGEXP = /\[([^\[]+)\]\(([^\)]+)\)/g;
|
//--------------------------------------------------------------------------
|
||||||
const HEADING_REGEXP = /\n(#+\s*)(.*)/g;
|
// Helpers
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
// files to be checked
|
|
||||||
function getFiles(): string[] {
|
|
||||||
const DOCFILES = fs.readdirSync("doc").map(f => `doc/${f}`);
|
|
||||||
const MAINREADME = "README.md";
|
|
||||||
DOCFILES.push("roadmap.md", MAINREADME);
|
|
||||||
return DOCFILES;
|
|
||||||
}
|
|
||||||
|
|
||||||
test("All markdown links work", () => {
|
|
||||||
let linkNumber = 0;
|
|
||||||
let invalidLinkNumber = 0;
|
|
||||||
const files = getFiles();
|
|
||||||
const data = readDocData(files);
|
|
||||||
for (let file of data) {
|
|
||||||
for (let link of file.links) {
|
|
||||||
// DEBUG: uncomment next line
|
|
||||||
// console.warn(`Checking "${link.name}" in "${file.name}"`);
|
|
||||||
linkNumber++;
|
|
||||||
if (!isLinkValid(link, file, data)) {
|
|
||||||
console.warn(`Invalid Link: "${link.name}" in "${file.name}"`);
|
|
||||||
invalidLinkNumber++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expect(invalidLinkNumber).toBe(0);
|
|
||||||
expect(linkNumber).toBeGreaterThan(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
interface MarkDownLink {
|
interface MarkDownLink {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -49,38 +22,130 @@ interface MarkDownSection {
|
|||||||
|
|
||||||
interface FileData {
|
interface FileData {
|
||||||
name: string;
|
name: string;
|
||||||
|
path: string[];
|
||||||
|
fullName: string
|
||||||
links: MarkDownLink[];
|
links: MarkDownLink[];
|
||||||
sections: MarkDownSection[];
|
sections: MarkDownSection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLinkValid(link: MarkDownLink, current: FileData, files: FileData[]): boolean {
|
const LINK_REGEXP = /\[([^\[]+)\]\(([^\)]+)\)/g;
|
||||||
|
const HEADING_REGEXP = /\n(#+\s*)(.*)/g;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function addMardownData(fileData): void {
|
||||||
|
const sep = fileData.path.length > 0 ? '/' : '';
|
||||||
|
const fullName = fileData.path.join('/') + sep + fileData.name;
|
||||||
|
const content = fs.readFileSync(fullName, { encoding: "utf8" });
|
||||||
|
let m;
|
||||||
|
// get links info
|
||||||
|
do {
|
||||||
|
m = LINK_REGEXP.exec(content);
|
||||||
|
if (m) {
|
||||||
|
fileData.links.push({ name: m[0], link: m[2] });
|
||||||
|
}
|
||||||
|
} while (m);
|
||||||
|
// get sections info
|
||||||
|
do {
|
||||||
|
m = HEADING_REGEXP.exec(content);
|
||||||
|
if (m) {
|
||||||
|
fileData.sections.push({ name: m[0], slug: slugify(m[2]) });
|
||||||
|
}
|
||||||
|
} while (m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of FileData corresponding to all files that need to be
|
||||||
|
* validated.
|
||||||
|
*/
|
||||||
|
function getFiles(path: string[] = []): FileData[] {
|
||||||
|
if (path.length === 0) {
|
||||||
|
const baseFiles: FileData[] = [
|
||||||
|
{ name: "README.md", path: [], links: [], sections: [], fullName: "README.md" },
|
||||||
|
{ name: "roadmap.md", path: [], links: [], sections: [], fullName: "roadmap.md" }
|
||||||
|
];
|
||||||
|
const rest = getFiles(["doc"]);
|
||||||
|
const result = baseFiles.concat(rest);
|
||||||
|
result.forEach(addMardownData);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const files = fs.readdirSync(path.join("/"), { withFileTypes: true }).map(f => {
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
return getFiles(path.concat(f.name));
|
||||||
|
}
|
||||||
|
const fullName = path.join('/') + (path.length > 0 ? '/' : '') + f.name;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: f.name,
|
||||||
|
path,
|
||||||
|
links: [],
|
||||||
|
sections: [],
|
||||||
|
fullName
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return Array.prototype.concat(...files);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LOCAL_FILES = ['LICENSE'];
|
||||||
|
export function isLinkValid(link: MarkDownLink, current: FileData, files: FileData[]): boolean {
|
||||||
if (link.link.startsWith("http")) {
|
if (link.link.startsWith("http")) {
|
||||||
// no check on external links
|
// no check on external links
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const parts = link.link.split("#");
|
// Step 1: extract path, name, hash
|
||||||
const currentParts = current.name.split("/");
|
// path = ['doc', 'architecture]
|
||||||
const path = currentParts.length > 1 ? currentParts[0] + "/" : "";
|
// name = 'rendering.md'
|
||||||
const fullName = path + parts[0];
|
// hash = 'blabla' (or '' if no hash)
|
||||||
if (parts.length === 1) {
|
|
||||||
// no # in url
|
const parts = link.link.split('#');
|
||||||
if (parts[0].endsWith(".md")) {
|
const hash = parts[1] || '';
|
||||||
// it is a local md file
|
let name;
|
||||||
if (!files.find(f => f.name === fullName)) {
|
let path;
|
||||||
|
if (parts[0]) {
|
||||||
|
let temp = parts[0].split('/');
|
||||||
|
name = temp[temp.length - 1];
|
||||||
|
temp.splice(-1);
|
||||||
|
path = current.path.slice();
|
||||||
|
for (let elem of temp) {
|
||||||
|
if (elem === '..') {
|
||||||
|
path.splice(-1);
|
||||||
|
} else if (elem !== '.') {
|
||||||
|
path.push(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// there are no file name, so this is a relative link to the current file
|
||||||
|
name = current.name;
|
||||||
|
path = current.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: build normalized link file name
|
||||||
|
const linkFullName = path.join('/') + (path.length > 0 ? '/' : '') + name;
|
||||||
|
|
||||||
|
// Step 3: check link name against white list of local files
|
||||||
|
if (LOCAL_FILES.includes(linkFullName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: check if there is a matching file
|
||||||
|
let target: FileData | undefined = files.find(f => f.fullName === linkFullName);
|
||||||
|
if (!target) {
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const file = parts[0] === "" ? current : files.find(f => f.name === fullName);
|
// Step 5: if necessary, check if there is a corresponding link inside the target
|
||||||
if (!file) {
|
// link name
|
||||||
return false;
|
if (hash) {
|
||||||
|
if (!target.sections.find(s => s.slug === hash)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!file.sections.find(s => s.slug === parts[1])) {
|
|
||||||
return false;
|
return true;
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
|
// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
|
||||||
function slugify(str) {
|
function slugify(str) {
|
||||||
const a = "àáäâãåăæçèéëêǵḧìíïîḿńǹñòóöôœøṕŕßśșțùúüûǘẃẍÿź·_,:;";
|
const a = "àáäâãåăæçèéëêǵḧìíïîḿńǹñòóöôœøṕŕßśșțùúüûǘẃẍÿź·_,:;";
|
||||||
@@ -99,33 +164,25 @@ function slugify(str) {
|
|||||||
.replace(/-+$/, ""); // Trim - from end of text
|
.replace(/-+$/, ""); // Trim - from end of text
|
||||||
}
|
}
|
||||||
|
|
||||||
function readDocData(files: string[]): FileData[] {
|
|
||||||
const result: FileData[] = [];
|
|
||||||
|
|
||||||
for (let file of files) {
|
|
||||||
const fileData: FileData = {
|
|
||||||
name: file,
|
|
||||||
links: [],
|
|
||||||
sections: []
|
|
||||||
};
|
|
||||||
const content = fs.readFileSync(file, { encoding: "utf8" });
|
|
||||||
let m;
|
|
||||||
// get links info
|
|
||||||
do {
|
|
||||||
m = LINK_REGEXP.exec(content);
|
|
||||||
if (m) {
|
|
||||||
fileData.links.push({ name: m[0], link: m[2] });
|
|
||||||
}
|
|
||||||
} while (m);
|
|
||||||
// get sections info
|
|
||||||
do {
|
|
||||||
m = HEADING_REGEXP.exec(content);
|
|
||||||
if (m) {
|
|
||||||
fileData.sections.push({ name: m[0], slug: slugify(m[2]) });
|
|
||||||
}
|
|
||||||
} while (m);
|
|
||||||
|
|
||||||
result.push(fileData);
|
//--------------------------------------------------------------------------
|
||||||
}
|
// Test
|
||||||
return result;
|
//--------------------------------------------------------------------------
|
||||||
}
|
|
||||||
|
test("All markdown links work", () => {
|
||||||
|
let linkNumber = 0;
|
||||||
|
let invalidLinkNumber = 0;
|
||||||
|
const data = getFiles();
|
||||||
|
for (let file of data) {
|
||||||
|
for (let link of file.links) {
|
||||||
|
linkNumber++;
|
||||||
|
if (!isLinkValid(link, file, data)) {
|
||||||
|
console.warn(`Invalid Link: "${link.name}" in "${file.name}"`);
|
||||||
|
invalidLinkNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(invalidLinkNumber).toBe(0);
|
||||||
|
expect(linkNumber).toBeGreaterThan(10);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user