Skip to content

Commit 5924b57

Browse files
authored
Merge branch 'main' into fil/node-tsx
2 parents 69116ae + bb96a2c commit 5924b57

35 files changed

+502
-375
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.DS_Store
2-
.observablehq
32
dist/
3+
docs/.observablehq/cache
44
node_modules/
55
test/output/*-changed.*
66
yarn-error.log

docs/.observablehq/config.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export default {
2+
pages: [
3+
{path: "/index", name: "Overview"},
4+
{path: "/getting-started", name: "Getting started"},
5+
{path: "/markdown", name: "Markdown"},
6+
{path: "/html", name: "HTML"},
7+
{path: "/javascript", name: "JavaScript"},
8+
{path: "/javascript/reactivity", name: "Reactivity"},
9+
{path: "/javascript/display", name: "Display"},
10+
{path: "/javascript/files", name: "Files"},
11+
{path: "/javascript/promises", name: "Promises"},
12+
{path: "/javascript/generators", name: "Generators"},
13+
{path: "/javascript/mutables", name: "Mutables"},
14+
{path: "/javascript/imports", name: "Imports"},
15+
{path: "/javascript/inputs", name: "Inputs"},
16+
{path: "/dot", name: "Dot"},
17+
{path: "/mermaid", name: "Mermaid"},
18+
{path: "/tex", name: "TeX"},
19+
{path: "/loaders", name: "Data loaders"},
20+
{path: "/contributing", name: "Contributing"},
21+
]
22+
};

docs/dot.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Dot
2+
3+
```dot show
4+
digraph G {
5+
a -> b -> c
6+
}
7+
```

docs/getting-started.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Getting started
2+
3+
TODO
4+
5+
## Installing
6+
7+
```
8+
yarn add @observablehq/cli
9+
```
10+
11+
## Preview
12+
13+
```
14+
observable preview
15+
```
16+
17+
## Build
18+
19+
```
20+
observable build
21+
```

docs/html.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# HTML
2+
3+
You can write HTML directly into Observable Markdown. HTML is useful for greater control over layout, say to use CSS grid for a responsive bento box layout in a dashboard, or adding an external stylesheet via a link element. For example, here is an HTML details element:
4+
5+
````html
6+
<details>
7+
<summary>Click me</summary>
8+
This text is not visible by default.
9+
</details>
10+
````
11+
12+
This produces:
13+
14+
<details>
15+
<summary>Click me</summary>
16+
This text is not visible by default.
17+
</details>
18+
19+
In Markdown, blank lines denote separate HTML blocks; be sure to avoid blank lines if you want to treat a chunk of HTML as a single block. For example, write this:
20+
21+
```md
22+
<!-- 👍 one HTML block -->
23+
<ul>
24+
<li>one</li>
25+
<li>two</li>
26+
<li>three</li>
27+
</ul>
28+
```
29+
30+
Don’t write this:
31+
32+
```md
33+
<!-- 👎 three HTML blocks -->
34+
<ul>
35+
36+
<li>one</li>
37+
<li>two</li>
38+
<li>three</li>
39+
40+
</ul>
41+
```
42+
43+
In the latter case, the li elements become top-level and wrapped in a span, rather than children of the ul.

docs/javascript.md

Lines changed: 39 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,72 @@
1-
# JavaScript reference
1+
# JavaScript
22

33
[Observable Markdown](./markdown) supports reactive JavaScript. JavaScript runs on the client, powered by the [Observable Runtime](https://github.com/observablehq/runtime). (In the future, JavaScript may also run during build to support data snapshot generation and server-side rendering.)
44

5-
## Reactivity
5+
In addition to standard Markdown features — headings, formatting, tables, images, and the like — Observable Markdown supports reactive JavaScript that runs on the client. Live JavaScript can be expressed either as [fenced code blocks](#fenced-code-blocks) (<code>```js</code>) or [inline expressions](#inline-expressions) (<code>$\{…}</code>).
66

7-
You may be accustomed to code running sequentially from top to bottom, and manually evaluating code in a notebook; Observable is different: we use [dataflow](https://en.wikipedia.org/wiki/Dataflow_programming), as in a spreadsheet, to *automatically* run code in topological order as determined by [top-level variable](#top-level-variables) references. For example, here we reference variables `x` and `y` even though they are defined in a code block below:
7+
### Fenced code blocks
88

9-
```js show
10-
x + y
11-
```
12-
13-
When code (such as `x + y`) references variables (such as `x` and `y`) defined by other code, the *referencing* code automatically runs after the *defining* code. Since code runs independently of its order on the page, giving you the flexibility to arrange your code however you like.
14-
15-
### Top-level variables
16-
17-
A top-level variable declared in a JavaScript fenced code block can be referenced in another code block or inline expression on the same page. So if you say:
18-
19-
```js show
20-
const x = 1, y = 2;
21-
```
22-
23-
Then you can reference `x` and `y` elsewhere on the page (with values ${x} and ${y}, respectively). Top-level variable declarations are effectively [hoisted](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting); you can reference variables even if the defining code block appears later on the page, and code runs in topological rather than top-down document order. If multiple blocks define top-level variables with the same name, references to these variables will throw a duplicate definition error.
24-
25-
To prevent variables from being visible outside the current block, make them local with a block statement:
9+
JavaScript fenced code blocks are typically used to display content such as charts and inputs. They can also declare top-level variables, say to load data or declare helper functions. An expression code block looks like this (note the lack of semicolon):
2610

27-
```js show
28-
{
29-
const z = 3;
30-
}
11+
````md
12+
```js
13+
1 + 2
3114
```
15+
````
3216

33-
### Promises
17+
This produces:
3418

35-
When code refers to a promise defined in another code block, the referencing code implicitly awaits the promise. Most often, promises are used to load files, fetch data from a remote server, or query a database. As a contrived example, within the block below, `hello` is a promise that resolves via `setTimeout`; if you reference `hello` from another code block or expression, the other code won’t run until the timeout fires and will see `hello` as a string.
36-
37-
```js show
38-
const hello = new Promise((resolve) => {
39-
setTimeout(() => {
40-
resolve("hello");
41-
}, 1000);
42-
});
43-
```
44-
45-
Hello is: ${hello}.
46-
47-
### Generators
48-
49-
When code refers to a [generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) defined in another code block, the referencing code automatically runs each time the generator yields a value. Values that change over time, such as interactive inputs and animation parameters, are often represented as generators. For example, you can use [Observable Inputs](https://github.com/observablehq/inputs) and the built-in [`view` function](#view(input)) to construct a live text input. Try entering your name into the box below:
50-
51-
```js show
52-
const name = view(Inputs.text({label: "Name", placeholder: "Enter your name"}));
19+
```js
20+
1 + 2
5321
```
5422

55-
Name is: ${name}.
56-
57-
The `view` function calls `Generators.input` under the hood, which takes an input element and returns a generator that yields the input’s value whenever it changes. The code above can be written more explicitly as:
58-
59-
```js no-run
60-
const nameInput = Inputs.text({label: "Name", placeholder: "Enter your name"});
61-
const name = Generators.input(nameInput);
23+
If the expression evaluates to a DOM node, the node is displayed. For example, here is an SVG element generated by [Observable Plot](https://observablehq.com/plot):
6224

63-
display(nameInput);
25+
````md
26+
```js
27+
Plot.lineY(aapl, {x: "Date", y: "Close"}).plot({y: {grid: true}})
6428
```
29+
````
6530

66-
As another example, you can use the built-in `Generators.observe` to represent the current pointer coordinates:
31+
This produces:
6732

68-
```js show
69-
const pointer = Generators.observe((notify) => {
70-
const pointermoved = (event) => notify([event.clientX, event.clientY]);
71-
addEventListener("pointermove", pointermoved);
72-
notify([0, 0]);
73-
return () => removeEventListener("pointermove", pointermoved);
74-
});
33+
```js
34+
Plot.lineY(aapl, {x: "Date", y: "Close"}).plot({y: {grid: true}})
7535
```
7636

77-
Pointer is: ${pointer.map(Math.round).join(", ")}.
37+
A program code block looks like this:
7838

79-
#### Mutable(*value*)
80-
81-
Normally, only the code block that declares a top-level variable can define it or assign to it. (This constraint may helpfully encourage you to decouple code.) You can however use the `Mutable` function to declare a mutable generator, allowing other code to mutate the generator’s value. This approach is akin to React’s `useState` hook. For example:
82-
83-
```js show
84-
const count = Mutable(0);
85-
const increment = () => ++count.value;
86-
const reset = () => count.value = 0;
39+
````md
40+
```js
41+
const x = 1 + 2;
8742
```
43+
````
8844

89-
In other code, you can now create buttons to increment and reset the count like so:
90-
91-
```js show
92-
Inputs.button([["Increment", increment], ["Reset", reset]])
45+
```js
46+
const x = 1 + 2;
9347
```
9448

95-
<style type="text/css">
96-
@keyframes flash {
97-
from { background-color: var(--theme-foreground-focus); }
98-
to { background-color: none; }
99-
}
100-
.flash {
101-
animation-name: flash;
102-
animation-duration: 1s;
103-
}
104-
</style>
105-
106-
Count is: ${htl.html`<span class="flash">${count}</span>`}.
107-
108-
Within the defining code block, `count` is a generator and `count.value` can be read and written to as desired; in other code, `count` is the generator’s current value. Other code that references `count` will re-run automatically whenever `count.value` is reassigned — so be careful you don’t cause an infinite loop!
109-
110-
## Displaying content
111-
112-
A JavaScript fenced code block containing an expression will automatically display its value, as will an inline JavaScript expression. You can also manually display elements or inspect values by calling the built-in `display` function.
113-
114-
### display(*value*)
49+
A program code block doesn’t display anything by default, but you can call the built-in [`display` function](./javascript/display) explicitly. The above block defines the top-level variable `x` with a value of ${x}.
11550

116-
If `value` is a DOM node, adds it to the DOM. Otherwise, converts the given `value` to a suitable DOM node and displays that instead. Returns the given `value`.
51+
(A technical note: the parser first attempts to parse the input as an expression; if that fails, it parses it as a program. So, code such as `{foo: 1}` is interpreted as an object literal rather than a block with a labeled statement.)
11752

118-
When `value` is not a DOM node, display will automatically create a suitable corresponding DOM node to display. The exact behavior depends on the input `value`, and whether display is called within a fenced code block or an inline expression. In fenced code blocks, display will use the [Observable Inspector](https://github.com/observablehq/inspector); in inline expressions, display will coerce non-DOM values to strings, and will concatenate values when passed an iterable.
53+
### Inline expressions
11954

120-
You can call display multiple times within the same code block or inline expression to display multiple values. The display will be automatically cleared if the associated code block or inline expression is re-run.
55+
Inline JavaScript expressions interpolate live values into Markdown. They are typically used to display dynamic numbers such as metrics, or to arrange visual elements such as charts into rich HTML layouts.
12156

122-
### view(*input*)
57+
For example, this paragraph simulates rolling a 20-sided dice:
12358

124-
As described above, this function displays the given `input` and then returns its corresponding generator via `Generators.input`. Use this to display an input element while also declaring the input’s current value as a reactive top-level variable.
125-
126-
## Imports
127-
128-
You can import a library from npm like so:
129-
130-
```js show
131-
import confetti from "npm:canvas-confetti";
132-
```
133-
134-
Now you can reference the imported `confetti` anywhere on the page.
135-
136-
```js show
137-
Inputs.button("Throw confetti!", {reduce: () => confetti()})
59+
```md
60+
You rolled ${Math.floor(Math.random() * 20) + 1}.
13861
```
13962

140-
You can also import JavaScript from local ES modules. This allows you to move code out of Markdown and into vanilla JavaScript files that can be shared by multiple pages — or even another application. And you can write tests for your code.
63+
You rolled ${Math.floor(Math.random() * 20) + 1}. Reload the page to re-roll.
14164

142-
## Files
65+
As with code blocks, if an inline expression evaluates to a DOM node, it will be displayed. For example, you can interpolate a sparkline ${Plot.plot({axis: null, margin: 0, width: 80, height: 17, x: {type: "band", round: false}, marks: [Plot.barY(aapl.slice(-15), {x: "Date", y1: 150, y2: "Close", fill: "steelblue"})]})} or even a reactive input ${Inputs.bind(htl.html`<input type=range style="width: 120px;">`, numberInput)} ${number} into prose.
14366

144-
You can load files using the built-in `FileAttachment` function.
145-
146-
```js show
147-
const gistemp = FileAttachment("gistemp.csv").csv({typed: true});
67+
```js
68+
const numberInput = Inputs.input(0);
69+
const number = Generators.input(numberInput);
14870
```
14971

150-
The following type-specific methods are supported: *csv*, *html*, *image*, *json*, *sqlite*, *text*, *tsv*, *xlsx*, *xml*, and *zip*. There are also generic methods: *arrayBuffer*, *blob*, and *url*. Each method returns a promise to the file’s contents (or URL).
151-
152-
We use static analysis to determine which files are used so that we can include only referenced files when building. The `FileAttachment` function accepts only literal strings; code such as `FileAttachment("my" + "file.csv")` or similar dynamic invocation is invalid syntax.
72+
Unlike code blocks, expressions cannot declare top-level variables.

docs/javascript/display.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Display
2+
3+
A JavaScript fenced code block containing an expression will automatically display its value, as will an inline JavaScript expression. You can also manually display elements or inspect values by calling the built-in `display` function.
4+
5+
## display(*value*)
6+
7+
If `value` is a DOM node, adds it to the DOM. Otherwise, converts the given `value` to a suitable DOM node and displays that instead. Returns the given `value`.
8+
9+
When `value` is not a DOM node, display will automatically create a suitable corresponding DOM node to display. The exact behavior depends on the input `value`, and whether display is called within a fenced code block or an inline expression. In fenced code blocks, display will use the [Observable Inspector](https://github.com/observablehq/inspector); in inline expressions, display will coerce non-DOM values to strings, and will concatenate values when passed an iterable.
10+
11+
You can call display multiple times within the same code block or inline expression to display multiple values. The display will be automatically cleared if the associated code block or inline expression is re-run.

docs/javascript/files.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Files
2+
3+
You can load files using the built-in `FileAttachment` function.
4+
5+
```js show
6+
const gistemp = FileAttachment("gistemp.csv").csv({typed: true});
7+
```
8+
9+
${Inputs.table(gistemp)}
10+
11+
The following type-specific methods are supported: *csv*, *html*, *image*, *json*, *sqlite*, *text*, *tsv*, *xlsx*, *xml*, and *zip*. There are also generic methods: *arrayBuffer*, *blob*, and *url*. Each method returns a promise to the file’s contents (or URL).
12+
13+
We use static analysis to determine which files are used so that we can include only referenced files when building. The `FileAttachment` function accepts only literal strings; code such as `FileAttachment("my" + "file.csv")` or similar dynamic invocation is invalid syntax.

docs/javascript/generators.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Generators
2+
3+
When code refers to a [generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) defined in another code block, the referencing code automatically runs each time the generator yields a value. Values that change over time, such as interactive inputs and animation parameters, are often represented as generators. For example, you can use [Observable Inputs](https://github.com/observablehq/inputs) and the built-in [`view` function](./inputs) to construct a live text input. Try entering your name into the box below:
4+
5+
```js show
6+
const name = view(Inputs.text({label: "Name", placeholder: "Enter your name"}));
7+
```
8+
9+
Name is: ${name}.
10+
11+
The `view` function calls `Generators.input` under the hood, which takes an input element and returns a generator that yields the input’s value whenever it changes. The code above can be written more explicitly as:
12+
13+
```js no-run
14+
const nameInput = Inputs.text({label: "Name", placeholder: "Enter your name"});
15+
const name = Generators.input(nameInput);
16+
17+
display(nameInput);
18+
```
19+
20+
As another example, you can use the built-in `Generators.observe` to represent the current pointer coordinates:
21+
22+
```js show
23+
const pointer = Generators.observe((notify) => {
24+
const pointermoved = (event) => notify([event.clientX, event.clientY]);
25+
addEventListener("pointermove", pointermoved);
26+
notify([0, 0]);
27+
return () => removeEventListener("pointermove", pointermoved);
28+
});
29+
```
30+
31+
Pointer is: ${pointer.map(Math.round).join(", ")}.
File renamed without changes.

docs/javascript/imports.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Imports
2+
3+
You can import a library from npm like so:
4+
5+
```js show
6+
import confetti from "npm:canvas-confetti";
7+
```
8+
9+
Now you can reference the imported `confetti` anywhere on the page.
10+
11+
```js show
12+
Inputs.button("Throw confetti!", {reduce: () => confetti()})
13+
```
14+
15+
You can also import JavaScript from local ES modules.
16+
17+
```js no-run
18+
import {foo} from "./foo.js"
19+
```
20+
21+
This allows you to move code out of Markdown and into vanilla JavaScript files that can be shared by multiple pages — or even another application. And you can write tests for your code.
22+
23+
TK There is a [bug](https://github.com/observablehq/cli/issues/115) where npm protocol imports don’t work from local ES modules.

docs/javascript/inputs.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Inputs
2+
3+
As described above, this function displays the given `input` and then returns its corresponding generator via `Generators.input`. Use this to display an input element while also declaring the input’s current value as a reactive top-level variable.
4+
5+
## view(*input*)

0 commit comments

Comments
 (0)