Skip to content

Commit 6e56dc0

Browse files
authored
Merge pull request #5 from 360-info/firstrelease
First release
2 parents 42bde64 + 2caa6a1 commit 6e56dc0

31 files changed

+3995
-2
lines changed

.github/workflows/_publish.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
on:
2+
workflow_dispatch:
3+
push:
4+
branches: main
5+
6+
name: Quarto Publish
7+
8+
jobs:
9+
build-deploy:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
steps:
14+
- name: Check out repository
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Quarto
18+
uses: quarto-dev/quarto-actions/setup@v2
19+
20+
- name: Install npm
21+
uses: actions/setup-node@v3
22+
23+
- name: Install npm packages
24+
run: npm install
25+
working-directory: ./docs
26+
27+
- name: Render and Publish
28+
uses: quarto-dev/quarto-actions/publish@v2
29+
path: docs
30+
with:
31+
target: gh-pages
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# quarto backend folder
2+
/.quarto/
3+
4+
# .sverto directory (except rollup.config.js)
5+
/.sverto/import
6+
/.sverto/.sverto-*
7+
!/.sverto/rollup.config.js
8+
9+
# installed npm packages
10+
/node_modules/
11+
12+
# system files
13+
.DS_Store
14+
15+
# lua
16+
/.luarc.json
17+
18+
# rendered documentation site
19+
/docs/_site/
20+
/docs/.quarto/
21+
/docs/.sverto/

.quartoignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
README.md
2+
LICENSE

Circles.svelte

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script>
2+
// let's borrow svelte's fly transitions for the circles that need to be
3+
// created or destroyed
4+
// https://svelte.dev/tutorial/transition
5+
import { fly } from "svelte/transition";
6+
7+
// here we declare `data` as a prop that this component can expect. it has
8+
// a default value in case we don't provide anything
9+
// https://svelte.dev/tutorial/declaring-props
10+
export let data = [5, 15, 10, 12, 14];
11+
12+
// prefix a statement with $: to make it reactive (so it runs every time
13+
// data changes)
14+
// https://svelte.dev/tutorial/reactive-statements
15+
$: console.log("Dataset prop:", data);
16+
17+
</script>
18+
19+
<!-- we use svelte's in/out transitions for entering and leaving dom elements,
20+
and vanilla css transitions for retained elements that change. the
21+
#each block means we create an svg <circle> for each element of data -->
22+
<svg>
23+
{#each data as d, i (i)}
24+
<circle
25+
in:fly="{{y: 100}}" out:fly="{{y: 100}}"
26+
style={"transition: all 1s ease-out"}
27+
cx={15 * i + "%"} cy="50%" r={d}
28+
fill="black"
29+
/>
30+
{/each}
31+
</svg>

README.md

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,80 @@
1-
# (WIP) svelte-quarto
2-
Add Svelte visualisations to your Quarto documents
1+
# Sverto
2+
3+
**Sverto** is an extension for [Quarto](https://quarto.org) that lets you seamlessly write and include [Svelte](https://svelte.dev) components, like charts and other visuals, in your Quarto website.
4+
5+
Your Svelte components can seamlessly react to your ObservableJS code, making it quick and easy to build visuals that animate in response to [user inputs](https://observablehq.com/@observablehq/inputs?collection=@observablehq/inputs) or other changing data in your document.
6+
7+
## 📋 Prerequisites
8+
9+
You'll need to install two things to run Sverto:
10+
11+
- [Quarto](https://quarto.org)
12+
- [Node and the Node Package Manager (npm)](https://nodejs.org)
13+
14+
## ⚙️ Installation
15+
16+
Install the project extension using:
17+
18+
```
19+
quarto use template 360-info/sverto@firstrelease-docs
20+
```
21+
22+
Then run:
23+
24+
```
25+
npm install
26+
```
27+
28+
This will add the extension itself (which includes some project scripts) to the `_extension` folder, as well as a few other files.
29+
30+
> **Note:** Sverto depends on running [project pre-render scripts](https://quarto.org/docs/projects/scripts.html#pre-and-post-render), so you can't currently use it with single documents.
31+
32+
## 🎉 Use
33+
34+
Here's the short way to add Svelte component you've written to a Quarto doc:
35+
36+
1. Add a magic placeholder block to your document with a [Quarto include](https://quarto.org/docs/authoring/includes.html) to the path to your Quarto doc, prefixed with `/.sverto/`. For example:
37+
38+
```
39+
:::{}
40+
{{< include /.sverto/example.qmd >}}
41+
:::
42+
```
43+
44+
2. Import your Svelte component with `Component = import_svelte("Component.svelte")`
45+
3. Add a target block for your visual using `:::` and give it an `#id`
46+
4. Instantiate the Svelte component with `myVisual = Component.default()` using some default props and your target block
47+
5. Update the instantiated component with `myVisual.propName`
48+
6. Render your Quarto website as usual with `quarto render` or `quarto preview`.
49+
50+
**To see this all in practice, check out [`example.qmd`](./example.qmd).**
51+
52+
## 📦 What's in the box?
53+
54+
* [`example.qmd`](./example.qmd): an example Quarto doc that uses a Svelte component
55+
* [`Circles.svelte`](./Circles.svelte): an example Svelte visualisation
56+
* [`package.json`](./package.json): this is used to keep track of the dependencies of your Svelte components. You should add this to version control.
57+
* Once you've run `npm install`, there'll also be a `package-lock.json` and a `.luarc.json`. You should version control these too (although you oughtn't need to edit them manually).
58+
59+
See [`example.qmd`](./example.qmd) to learn how to add Svelte components to your documents and the [Svelte tutorial](https://svelte.dev/tutorial/basics) to learn how to create them.
60+
61+
## 🛍 Use other libraries in your Svelte component
62+
63+
If you want to refer to other JavaScript libraries in your Svelte component (like d3, for example), add them to the project using `npm install package1 [package2 ...]`. For example:
64+
65+
```
66+
npm install d3-scale
67+
```
68+
69+
## Use pre-compiled Svelte components
70+
71+
If you'd prefer to compile your own Svelte components instead of letting this extension do it, you can skip steps 1 and 2 and simply refer to the compiled bundle with, for example, `Component = import("Component.js")` in an OJS block.
72+
73+
> **Note:** you must compile the Svelte component to an ES6 bundle, and you must enable accessors when compiling if you want to be able to update them from OJS. Refer to `_extensions/sverto/rollup.config.js` for guidance on configuring Rollup to do this.
74+
75+
## ❓ Issues
76+
77+
If you have any problems with the extension, please feel free to [create an issue](https://github.com/360-info/sverto)!
78+
79+
Special thanks to [Carlos Scheidegger](https://github.com/cscheid) from [Posit](https://posit.co) for his time and guidance getting Sverto working!
80+

_extensions/sverto/_extension.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
title: Sverto
2+
author: 360info
3+
version: 0.0.1
4+
quarto-version: ">=1.2.0"
5+
contributes:
6+
project:
7+
project:
8+
type: website
9+
pre-render:
10+
- refresh.ts
11+
- create-imports.lua
12+
- compile-imports.ts
13+
format: sverto-html
14+
formats:
15+
html:
16+
filters:
17+
- cleanup-transform.lua
18+
revealjs:
19+
filters:
20+
- cleanup-transform.lua
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- replaces the doc body with just its first block (assumed to be the
2+
-- version of the doc with transformed .svelte -> .js refs)
3+
Pandoc = function(doc)
4+
doc.blocks = doc.blocks[1].content
5+
return doc
6+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// check if _extensions/svero exists or _extensions/360-info/sverto
2+
3+
import * as path from "https://deno.land/std/path/mod.ts";
4+
5+
const thisScript = path.fromFileUrl(import.meta.url);
6+
const rollupConfig = path.join(path.dirname(thisScript), "rollup.config.js");
7+
8+
// call rollup with the config file
9+
const cmd = ["npm", "run", "build", rollupConfig];
10+
const compileStep = Deno.run({ cmd });
11+
await compileStep.status();
12+
13+
// console.log("Svelte compilation + bundling done!");
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
-- return contents of named file
2+
function read_file(name)
3+
local file = io.open(name, "r")
4+
if file == nil then
5+
return ""
6+
end
7+
local contents = file:read("a")
8+
file:close()
9+
return contents
10+
end
11+
12+
-- write content to named file path
13+
function write_file(name, content)
14+
local file = io.open(name, "w")
15+
if file == nil then
16+
return ""
17+
end
18+
file:write(content)
19+
file:close()
20+
return content
21+
end
22+
23+
function append_to_file(name, content)
24+
local file = io.open(name, "a")
25+
if file == nil then
26+
return ""
27+
end
28+
file:write(content)
29+
file:close()
30+
return content
31+
end
32+
33+
-- use mkdir (windows) or mkdir -p (*nix) to create directories
34+
-- from https://stackoverflow.com/a/14425862/3246758
35+
function get_path_sep()
36+
return package.config:sub(1, 1)
37+
end
38+
39+
-- create a folder recursively (mkdir on windows, mkdir -p on *nix)
40+
function create_dir_recursively(path)
41+
local path_separator = get_path_sep()
42+
if path_separator == "\\" or path_separator == "\"" then
43+
os.execute("mkdir " .. path)
44+
else
45+
-- macos/linux
46+
os.execute("mkdir -p " .. path)
47+
end
48+
end
49+
50+
-- path_dir: extract the folder path from a file path
51+
-- from https://stackoverflow.com/a/9102300/3246758
52+
function path_dir(path)
53+
return path:match("(.*".. get_path_sep() ..")") or ""
54+
end
55+
56+
-- TODO -
57+
58+
local preprocess_qmd_filter = {
59+
60+
-- search for `import_svelte("X.svelte")` refs in codeblocks and switch them
61+
-- to `import("X.js")`
62+
CodeBlock = function(block)
63+
if block.classes:includes("{ojs}") then
64+
65+
local svelte_import_syntax =
66+
"import%_svelte%(\"([%w;,/%?:@&=%+%$%-_%.!~%*'%(%)#]+)%.svelte\"%)"
67+
68+
local block_text = block.text
69+
70+
-- get the qmd_path from disk
71+
local current_qmd_path = read_file(".sverto/.sverto-current-qmd-folder")
72+
73+
-- first, extract .svelte paths in import_svelte() statements
74+
for svelte_path in block_text:gmatch(svelte_import_syntax) do
75+
append_to_file(".sverto/.sverto-imports",
76+
current_qmd_path .. svelte_path .. ".svelte\n")
77+
end
78+
79+
-- now change `import_svelte("X.svelte")` refs to `import("X.js")`
80+
-- TODO - neaten up relative paths instead of assuming we're going from
81+
-- /site_libs/quarto-ojs
82+
block.text = block_text:gsub(
83+
svelte_import_syntax,
84+
"import(\"./../../" .. current_qmd_path .. "%1.js\")")
85+
86+
end
87+
return block
88+
end,
89+
90+
-- return the doc as a a whole unchanged...
91+
-- except without the first block (the include statement)
92+
Pandoc = function(doc)
93+
local new_blocks = pandoc.Blocks({})
94+
for i, v in ipairs(doc.blocks) do
95+
if i ~= 1 then
96+
new_blocks:insert(v)
97+
end
98+
end
99+
doc.blocks = new_blocks
100+
return doc
101+
102+
end
103+
}
104+
105+
create_dir_recursively(".sverto/")
106+
107+
-- collect the input qmd paths
108+
in_file_string = os.getenv("QUARTO_PROJECT_INPUT_FILES")
109+
in_files = {}
110+
for in_file in string.gmatch(in_file_string, "([^\n]+)") do
111+
table.insert(in_files, in_file)
112+
end
113+
114+
-- transform each input qmd, saving the transformation in .sverto/[path]
115+
-- (write the identified .svelte files out to a file too!)
116+
for key, qmd_path in ipairs(in_files) do
117+
118+
local doc = pandoc.read(read_file(qmd_path))
119+
120+
-- store the current qmd_path on disk so the filter can access it
121+
write_file(".sverto/.sverto-current-qmd-folder", path_dir(qmd_path))
122+
123+
-- pre-process the qmd, populating `svelte_files` in the process
124+
-- local svelte_files = {}
125+
local transformed_doc = doc:walk(preprocess_qmd_filter)
126+
create_dir_recursively(".sverto/" .. path_dir(qmd_path))
127+
write_file(".sverto/" .. qmd_path, pandoc.write(transformed_doc, "markdown"))
128+
129+
end
130+
131+
-- write the output dir path temporarily (so rollup can use it)
132+
write_file(".sverto/.sverto-outdir", os.getenv("QUARTO_PROJECT_OUTPUT_DIR"))
133+
134+
-- TODO - if there's no {{< import .sverto/file.qmd >}} block, add it?

_extensions/sverto/refresh.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// delete the temporary import block files and the temp render metadata
2+
3+
if (Deno.env.get("QUARTO_PROJECT_RENDER_ALL") == "1") {
4+
try {
5+
await Deno.remove(".sverto/", { recursive: true })
6+
} catch {
7+
8+
}
9+
}
10+

0 commit comments

Comments
 (0)