├── .gitignore
├── .prettierignore
├── LICENSE
├── README.md
├── assets
├── AtkinsonHyperlegible-Bold.woff2
├── AtkinsonHyperlegible-BoldItalic.woff2
├── AtkinsonHyperlegible-Italic.woff2
├── AtkinsonHyperlegible-Regular.woff2
├── SourceCodePro.woff2
├── cohost-leaguemono.woff
├── examples
│ ├── animated-svg-background.toml
│ ├── css-animations.toml
│ ├── default.toml
│ ├── index.json
│ ├── katex-mathml.toml
│ ├── lesscss-files.toml
│ ├── scss-files.toml
│ ├── slideshow.toml
│ ├── svelte-chat-log.toml
│ ├── svelte-templates.toml
│ └── svg-conditional-css.toml
└── no-data.svg
├── flake.lock
├── flake.nix
├── index.html
├── package-lock.json
├── package.json
├── prettier.config.js
├── src
├── cohost-inherited.scss
├── document.ts
├── index.tsx
├── plugins
│ ├── index.ts
│ ├── source
│ │ ├── external-url.tsx
│ │ ├── file-data-url.tsx
│ │ ├── file-data.less
│ │ ├── file-data.tsx
│ │ ├── lesscss.css
│ │ ├── lesscss.tsx
│ │ ├── sass.tsx
│ │ ├── svelte-component.tsx
│ │ ├── svelte-worker.js
│ │ ├── svelte.tsx
│ │ ├── text.css
│ │ └── text.tsx
│ └── transform
│ │ ├── style-inliner.tsx
│ │ ├── svg-to-background.tsx
│ │ ├── svgo.tsx
│ │ ├── to-blob.tsx
│ │ └── to-data-url.tsx
├── storage-context.tsx
├── storage
│ ├── index.ts
│ └── versions
│ │ ├── index.ts
│ │ ├── onv1
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ └── stringify.ts
│ │ ├── v0.ts
│ │ └── v1.ts
├── ui
│ ├── components
│ │ ├── code-editor.css
│ │ ├── code-editor.tsx
│ │ ├── codemirror.tsx
│ │ ├── data-preview.css
│ │ ├── data-preview.tsx
│ │ ├── icons.tsx
│ │ ├── module-graph
│ │ │ ├── auto-layout.ts
│ │ │ ├── consts.ts
│ │ │ ├── eggbug-sleep.svg
│ │ │ ├── eggbug.svg
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── module-node.tsx
│ │ │ ├── output-node.tsx
│ │ │ └── reactflow.tsx
│ │ ├── module-list.css
│ │ ├── module-list.tsx
│ │ ├── module-picker.css
│ │ ├── module-picker.tsx
│ │ ├── module-status.css
│ │ ├── module-status.tsx
│ │ ├── post-preview
│ │ │ ├── basic-renderer.tsx
│ │ │ ├── cohost-renderer.ts
│ │ │ ├── dark-theme-button.css
│ │ │ ├── dark-theme-button.tsx
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── preview.scss
│ │ ├── preview.tsx
│ │ ├── split-panel.scss
│ │ └── split-panel.tsx
│ ├── examples.css
│ ├── examples.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── opt-held.tsx
│ ├── prechoster.scss
│ ├── prechoster.tsx
│ ├── render-context.ts
│ ├── sidebar.css
│ └── sidebar.tsx
└── uikit
│ ├── animation.tsx
│ ├── button-popout.css
│ ├── button-popout.tsx
│ ├── button.css
│ ├── button.tsx
│ ├── checkbox.css
│ ├── checkbox.tsx
│ ├── dir-popover.css
│ ├── dir-popover.tsx
│ ├── form.css
│ ├── form.tsx
│ ├── frame-animation.ts
│ ├── layout-root-context.tsx
│ ├── text-field.css
│ └── text-field.tsx
├── tsconfig.json
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 | /result
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 | package-lock.json
3 | static
4 | README.md
5 | src/cohost-inherited.scss
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 cpsdqs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # prechoster
2 | A graph-based HTML generator to make fancy chosting easier.
3 |
4 | ## Overview
5 | Documents are a directed graph of modules.
6 | Every module is JSON data associated with a plugin implementation.
7 | The plugin implementation evaluates result `Data` and provides it to connected modules on the graph.
8 | Modules have “sends,” which simply input data into other modules in evaluation order,
9 | and “named sends,” which are sort of like side inputs that don’t make sense as regular inputs (e.g. variable definitions).
10 |
11 | Plugins are defined in `src/plugins` (indexed in `src/plugins/index.tsx`) and are composed of a module data interface, a UI component that edits module data, and an evaluation function.
12 |
13 | Do not change module data interfaces in a backwards-incompatible way because people are apparently using this software sometimes!!
14 |
15 | ### Building
16 | in the repository:
17 |
18 | ```sh
19 | npm install
20 | npm run build # or npm run dev
21 | ```
22 |
23 | Look in `dist` for the output.
24 |
25 | ### Browser Support
26 | Major feature gates:
27 |
28 | - script type module
29 | - dialog element
30 |
31 | According to caniuse, this means:
32 |
33 | - Firefox 98
34 | - Safari 15.4
35 | - Chrome 63
36 |
--------------------------------------------------------------------------------
/assets/AtkinsonHyperlegible-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/prechoster/2c85906f9fd625ccb9001453244db4fcea6a900a/assets/AtkinsonHyperlegible-Bold.woff2
--------------------------------------------------------------------------------
/assets/AtkinsonHyperlegible-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/prechoster/2c85906f9fd625ccb9001453244db4fcea6a900a/assets/AtkinsonHyperlegible-BoldItalic.woff2
--------------------------------------------------------------------------------
/assets/AtkinsonHyperlegible-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/prechoster/2c85906f9fd625ccb9001453244db4fcea6a900a/assets/AtkinsonHyperlegible-Italic.woff2
--------------------------------------------------------------------------------
/assets/AtkinsonHyperlegible-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/prechoster/2c85906f9fd625ccb9001453244db4fcea6a900a/assets/AtkinsonHyperlegible-Regular.woff2
--------------------------------------------------------------------------------
/assets/SourceCodePro.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/prechoster/2c85906f9fd625ccb9001453244db4fcea6a900a/assets/SourceCodePro.woff2
--------------------------------------------------------------------------------
/assets/cohost-leaguemono.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/prechoster/2c85906f9fd625ccb9001453244db4fcea6a900a/assets/cohost-leaguemono.woff
--------------------------------------------------------------------------------
/assets/examples/animated-svg-background.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'Animated SVG Background'
3 |
4 | [[modules]]
5 | plugin = 'source.text'
6 | data.contents = '''
7 | '''
10 | data.language = 'html'
11 | sends = [2]
12 |
13 | [[modules]]
14 | plugin = 'source.lesscss'
15 | data.contents = '''
16 | .animated-circle {
17 | transform-origin: 50% 50%;
18 | animation: pulse 2s infinite;
19 | }
20 |
21 | @keyframes pulse {
22 | 0%, 100% {
23 | transform: scale(0.5);
24 | }
25 | 50% {
26 | transform: scale(1);
27 | }
28 | }'''
29 | sends = [2]
30 |
31 | [[modules]]
32 | plugin = 'transform.style-inliner'
33 | data.mode = 'element'
34 | sends = [3]
35 |
36 | [[modules]]
37 | plugin = 'transform.to-data-url'
38 | data.mime = 'image/svg+xml'
39 | namedSends = { '5' = ['svg'] }
40 |
41 | [[modules]]
42 | plugin = 'source.text'
43 | data.contents = '
'
44 | data.language = 'html'
45 | sends = [6]
46 |
47 | [[modules]]
48 | plugin = 'source.lesscss'
49 | data.contents = '''
50 | .svg-container {
51 | width: 200px;
52 | height: 200px;
53 | background: url("@{svg}");
54 | }'''
55 | sends = [6]
56 |
57 | [[modules]]
58 | plugin = 'transform.style-inliner'
59 | data.mode = 'attr'
60 | sends = ['output']
61 |
--------------------------------------------------------------------------------
/assets/examples/css-animations.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'CSS Animations'
3 |
4 | [[modules]]
5 | plugin = 'source.text'
6 | data.language = 'html'
7 | data.contents = '''
8 | ## CSS animations available on Cohost
9 | While you cannot define your own `@keyframes` with inline CSS on Cohost, you can still call existing animations defined elsewhere on the page. Use them wisely…
10 |
11 | ### Always available
12 |
13 |
14 | -
15 |
slideupleft
16 |
17 |
18 | -
19 |
slideupright
20 |
21 |
22 | -
23 |
bounce
24 |
25 |
26 |
27 | -
28 |
go3020080000
29 |
30 |
31 | -
32 |
go901347462
33 |
34 |
35 | -
36 |
go651618207
37 |
38 | (affects layout)
39 |
40 |
41 | -
42 |
go2264125279
43 |
44 |
45 | -
46 |
go463499852
47 | (rotated)
48 |
49 | -
50 |
go1268368563
51 |
52 |
53 |
54 |
55 |
56 |
57 | ### Available only when reduced motion is disabled
58 |
59 |
60 | Reduced motion is currently:
61 |
62 | disabled
63 | enabled
64 |
65 |
66 |
67 |
68 |
69 | -
70 |
spin
71 |
72 |
73 | -
74 |
spin
75 | also spin
76 |
77 | -
78 |
spin
79 |
80 |
81 |
spin is very versatile
82 |
83 |
84 |
85 |
86 |
87 | ### Available only when reduced motion is enabled
88 |
89 | -
90 |
pulse
91 |
92 |
93 |
'''
94 | sends = [2]
95 |
96 | [[modules]]
97 | plugin = 'source.text'
98 | data.contents = '''
99 | ul.demos {
100 | display: grid;
101 | list-style: none;
102 | padding: 0;
103 | gap: 1em;
104 | grid-template-columns: repeat(auto-fit, minmax(10em, 1fr));
105 | }
106 |
107 | ul.demos > li {
108 | display: flex;
109 | flex-direction: column;
110 | gap: 1em;
111 | align-items: center;
112 | padding: 1em;
113 | background: #7772;
114 | border: 1px solid #7777;
115 | border-radius: 0.5em;
116 | }
117 |
118 | .demo-box {
119 | background: gray;
120 | color: white;
121 | width: 3rem;
122 | height: 3rem;
123 | display: grid;
124 | place-content: center;
125 | font-size: 0.7em;
126 | line-height: 1.1;
127 | }
128 |
129 | .reduced-motion-check {
130 | display: inline-block;
131 | overflow: hidden;
132 | font-weight: bold;
133 | height: 1.25em;
134 | position: relative;
135 | line-height: 1;
136 | vertical-align: middle;
137 | }
138 | .reduced-motion-check .a {
139 | display: inline-block;
140 | width: 100%;
141 | position: relative;
142 | transform: translateX(-100%);
143 | animation: spin 0s forwards;
144 | color: #07f;
145 | }
146 | .reduced-motion-check .b {
147 | position: absolute;
148 | left: 100%;
149 | color: #7a0;
150 | }
151 | '''
152 | data.language = 'css'
153 | sends = [2]
154 |
155 | [[modules]]
156 | plugin = 'transform.style-inliner'
157 | data.mode = 'attr'
158 | sends = ['output']
159 |
--------------------------------------------------------------------------------
/assets/examples/default.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = "Style Inlining"
3 |
4 | [[modules]]
5 | plugin = "source.text"
6 | data.contents = '''
7 |
8 | hello world
9 |
10 |
11 |
12 | see Examples and Templates in the
13 |
14 |
25 |
26 | sidebar for more examples!
27 |
'''
28 | data.language = "html"
29 | sends = [2]
30 |
31 | [[modules]]
32 | plugin = "source.text"
33 | data.contents = '''
34 | .a-thing {
35 | color: red;
36 | }
37 |
38 | .toolbar-button {
39 | display: inline-flex;
40 | vertical-align: middle;
41 |
42 | align-items: center;
43 | justify-content: center;
44 | padding: 0 0.1em;
45 |
46 | background: black;
47 | border: 1px solid #fff4;
48 | border-radius: 0.5em;
49 | }
50 | .sidebar-icon {
51 | display: block;
52 | vertical-align: middle;
53 | width: 24px;
54 | aspect-ratio: 1;
55 | background-size: contain;
56 | }'''
57 | data.language = "css"
58 | sends = [2]
59 |
60 | [[modules]]
61 | plugin = "transform.style-inliner"
62 | data.mode = "attr"
63 | sends = [3]
64 |
65 | [[modules]]
66 | plugin = "transform.svg-to-background"
67 | data.useSvgo = true
68 | sends = ["output"]
69 |
--------------------------------------------------------------------------------
/assets/examples/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "default.toml": {
3 | "title": "Style Inlining",
4 | "description": "Inline a CSS stylesheet into HTML."
5 | },
6 | "css-animations.toml": {
7 | "title": "CSS Animations",
8 | "description": "Use predefined animations available on Cohost."
9 | },
10 | "lesscss-files.toml": {
11 | "title": "LessCSS Variables",
12 | "description": "Use LessCSS variables to include files as background images."
13 | },
14 | "scss-files.toml": {
15 | "title": "Sass Modules",
16 | "description": "Use Sass modules to include files as background images."
17 | },
18 | "svelte-templates.toml": {
19 | "title": "Svelte Templating",
20 | "description": "Use Svelte components for various kinds of templating."
21 | },
22 | "svelte-chat-log.toml": {
23 | "title": "Svelte Chat Log",
24 | "description": "Use Svelte to create a simple chat log template."
25 | },
26 | "slideshow.toml": {
27 | "title": "Slideshow",
28 | "description": "Use Svelte and to create a slideshow."
29 | },
30 | "katex-mathml.toml": {
31 | "title": "KaTeX MathML",
32 | "description": "Use KaTeX to easily render TeX markup to MathML."
33 | },
34 | "animated-svg-background.toml": {
35 | "title": "Animated SVG Background",
36 | "description": "Use @keyframes to animate SVG elements and embed them as a background image."
37 | },
38 | "svg-conditional-css.toml": {
39 | "title": "Conditional CSS inside SVG",
40 | "description": "Use SVG backgrounds to trigger CSS animation with a click interaction."
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/assets/examples/katex-mathml.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'KaTeX MathML'
3 |
4 | [[modules]]
5 | plugin = 'source.text'
6 | data.contents = '''
7 | here’s some math:
8 | $$f*g = \int_{-\infty}^\infty f(t)\,g(x-t)\,dt$$
9 |
10 | and some inline math also: $e^{\tau i}=1$
11 |
12 |
13 |
14 | $$
15 | \left[\begin{matrix}
16 | \cos\theta & -\sin\theta \\
17 | \sin\theta & \cos\theta
18 | \end{matrix}\right]
19 | $$
20 |
21 |
'''
22 | data.language = 'html'
23 | namedSends = { '4' = ['text'] }
24 |
25 | [[modules]]
26 | plugin = 'source.text'
27 | data.contents = '''
28 | .spin-container {
29 | text-align: center;
30 | padding: 1em 0;
31 | }
32 |
33 | .spinning {
34 | display: inline-block;
35 | animation: spin 10s infinite linear;
36 | }'''
37 | data.language = 'css'
38 | sends = [2]
39 |
40 | [[modules]]
41 | plugin = 'transform.style-inliner'
42 | data.mode = 'attr'
43 | sends = [3]
44 |
45 | [[modules]]
46 | plugin = 'transform.svg-to-background'
47 | data.useSvgo = true
48 | sends = ['output']
49 |
50 | [[modules]]
51 | plugin = 'source.svelte'
52 | data.contents = '''
53 |
79 | {@html rendered}'''
80 | sends = [2]
81 |
82 | [[modules]]
83 | plugin = 'source.external-url-data'
84 | data.url = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.mjs'
85 | data.type = 'javascript'
86 | namedSends = { '4' = ['katex.js'] }
87 |
--------------------------------------------------------------------------------
/assets/examples/lesscss-files.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'LessCSS Variables'
3 |
4 | [[modules]]
5 | plugin = 'source.text'
6 | data.contents = '''
7 |
8 | check it out →
9 |
'''
10 | data.language = 'html'
11 | sends = [3]
12 |
13 | [[modules]]
14 | plugin = 'source.file-data-url'
15 | data.url = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDE2OCAxODIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgeG1sbnM6c2VyaWY9Imh0dHA6Ly93d3cuc2VyaWYuY29tLyIgc3R5bGU9ImZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEsMCwwLDEsLTQ5LC0zMikiPgogICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuMjU2MTkyLDAsMCwwLjI2OTAzLDg2NS44MzEsMjU4LjAzNikiPgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgtMS4zMDAzMSwwLDAsMS4yODI0LC01MjA0LjQyLC0xNS4xMTc5KSI+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNLTE2MDMsLTIwNC41NzFDLTE1NzguNTgsLTIwNC4yODEgLTE1NjguNjQsLTE4OS41MDIgLTE1NjUuOTMsLTE2Ny41MDIiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjExLjM4cHg7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoLTEuMjk3MywtMC45NTM1ODIsLTAuOTY2OTAyLDEuMjc5NDMsLTQ2MTUuOTksLTgxNS44OSkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTS05MzksLTQ4NkMtOTM5LC00ODYgLTkwMi4wOTcsLTY1OS45MDYgLTc5NiwtNTk3Qy02ODkuOTAzLC01MzQuMDk0IC05MjYuMDg4LC00NjYuNzg5IC05MjksLTQ2OCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6cmdiKDI1LDI1LDI1KTtzdHJva2Utd2lkdGg6OS4zcHg7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoODE2LjY1MywtMjQwLjY5OSwyNDQuMDYxLDgwNS40MDMsLTMyMjYuOTIsLTExMC42OTQpIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLjUyNSwwLjAxMUMwLjQ1MywwLjAxMSAwLjM4OCwwLjAwMSAwLjMyOSwtMC4wMThDMC4yNzEsLTAuMDM4IDAuMjI0LC0wLjA2NyAwLjE5LC0wLjEwNkMwLjE1NSwtMC4xNDQgMC4xMzgsLTAuMTkzIDAuMTM4LC0wLjI1MUMwLjEzOCwtMC4zMSAwLjE1NSwtMC4zNTggMC4xOSwtMC4zOTdDMC4yMjQsLTAuNDM1IDAuMjcxLC0wLjQ2NCAwLjMyOSwtMC40ODNDMC4zODgsLTAuNTAyIDAuNDUzLC0wLjUxMiAwLjUyNSwtMC41MTJDMC41OTcsLTAuNTEyIDAuNjYyLC0wLjUwMiAwLjcyLC0wLjQ4M0MwLjc3OCwtMC40NjQgMC44MjUsLTAuNDM1IDAuODYsLTAuMzk2QzAuODk0LC0wLjM1OCAwLjkxMiwtMC4zMDkgMC45MTIsLTAuMjUxQzAuOTEyLC0wLjE5MyAwLjg5NCwtMC4xNDQgMC44NiwtMC4xMDZDMC44MjUsLTAuMDY3IDAuNzc4LC0wLjAzOCAwLjcyLC0wLjAxOEMwLjY2MiwwLjAwMSAwLjU5NywwLjAxMSAwLjUyNSwwLjAxMVoiIHN0eWxlPSJmaWxsOnJnYigxMzEsMzcsNzkpO2ZpbGwtcnVsZTpub256ZXJvOyIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEuMzAwMzEsMCwwLDEuMjgyNCwtOTMyLjM4MSwtMzA4LjAyKSI+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSItMTY2OS41IiBjeT0iLTc0LjUiIHI9IjE3LjUiIHN0eWxlPSJmaWxsOnJnYigyNSwyNSwyNSk7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjExLjM4cHg7c3Ryb2tlLWxpbmVjYXA6c3F1YXJlO3N0cm9rZS1saW5lam9pbjpyb3VuZDsiLz4KICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxLjMwMDMxLDAsMCwxLjI4MjQsLTgwMS4wNSwtMjg1LjU3OCkiPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iLTE2NjkuNSIgY3k9Ii03NC41IiByPSIxNy41IiBzdHlsZT0iZmlsbDpyZ2IoMjUsMjUsMjUpO3N0cm9rZTpyZ2IoMjUsMjUsMjUpO3N0cm9rZS13aWR0aDoxMS4zOHB4O3N0cm9rZS1saW5lY2FwOnNxdWFyZTtzdHJva2UtbGluZWpvaW46cm91bmQ7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMS42MTc5OSwwLDAsMS41OTU3LC0xMzE2LjYzLDIyMy4yNjMpIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0tOTM5LC00ODZDLTkzOSwtNDg2IC05MDIuMDk3LC02NTkuOTA2IC03OTYsLTU5N0MtNjg5LjkwMywtNTM0LjA5NCAtOTI2LjA4OCwtNDY2Ljc4OSAtOTI5LC00NjgiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjkuMTVweDsiLz4KICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxLjMwMDMxLDAsMCwxLjI4MjQsLTc3NS42MjksMzQuMTM4NikiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTS0xNjAzLC0yMDQuNTcxQy0xNTc4LjU4LC0yMDQuMjgxIC0xNTY4LjY0LC0xODkuNTAyIC0xNTY1LjkzLC0xNjcuNTAyIiBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTpyZ2IoMjUsMjUsMjUpO3N0cm9rZS13aWR0aDoxMS4zOHB4OyIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEuMzAwMzEsMCwwLDEuMjgyNCwtNjYxLjg4OCwtMTMuMzk5OCkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTS0xNjAzLC0yMDQuNTcxQy0xNTc4LjU4LC0yMDQuMjgxIC0xNTY4LjY0LC0xODkuNTAyIC0xNTY1LjkzLC0xNjcuNTAyIiBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTpyZ2IoMjUsMjUsMjUpO3N0cm9rZS13aWR0aDoxMS4zOHB4OyIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEuMjU3NywtMC4zMjU1OTksMC4zMzAxNDcsMS4yNDAzNywtNTc3LjYwMiwtNTc5LjU2KSI+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNLTE2MDMsLTIwNC41NzFDLTE1NzguNTgsLTIwNC4yODEgLTE1NjguNjQsLTE4OS41MDIgLTE1NjUuOTMsLTE2Ny41MDIiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjExLjQ4cHg7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC44NDcyMDMsMC4wNjA3Nzc4LC0wLjA2MTYyNjcsMC44MzU1MzMsLTIwNDguMDcsLTE3MS4zODUpIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0tMTIyNSwtMTIwQy0xMjI0LjkyLC0xMTguODg3IC0xMjEwLjk5LC0xMDUuNTIzIC0xMTk0LC0xMjBDLTExOTMuNDcsLTEyMC4xMzYgLTExNzkuNTMsLTEwNi4yNDEgLTExNjMuMTQsLTExOS4xNCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6cmdiKDI1LDI1LDI1KTtzdHJva2Utd2lkdGg6MTcuMzhweDtzdHJva2UtbGluZWpvaW46cm91bmQ7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo='
16 | namedSends = { '2' = ['eggbug'] }
17 |
18 | [[modules]]
19 | plugin = 'source.lesscss'
20 | data.contents = '''
21 | .a-thing {
22 | background: url(@eggbug);
23 | background-repeat: no-repeat;
24 | background-position: right center;
25 | }'''
26 | sends = [3]
27 |
28 | [[modules]]
29 | plugin = 'transform.style-inliner'
30 | data.mode = 'attr'
31 | sends = ['output']
32 |
--------------------------------------------------------------------------------
/assets/examples/scss-files.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'Sass Modules'
3 |
4 | [[modules]]
5 | plugin = 'source.text'
6 | data.contents = '''
7 |
8 | check it out →
9 |
'''
10 | data.language = 'html'
11 | sends = [2]
12 |
13 | [[modules]]
14 | plugin = 'source.file-data-url'
15 | data.url = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDE2OCAxODIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgeG1sbnM6c2VyaWY9Imh0dHA6Ly93d3cuc2VyaWYuY29tLyIgc3R5bGU9ImZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEsMCwwLDEsLTQ5LC0zMikiPgogICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuMjU2MTkyLDAsMCwwLjI2OTAzLDg2NS44MzEsMjU4LjAzNikiPgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgtMS4zMDAzMSwwLDAsMS4yODI0LC01MjA0LjQyLC0xNS4xMTc5KSI+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNLTE2MDMsLTIwNC41NzFDLTE1NzguNTgsLTIwNC4yODEgLTE1NjguNjQsLTE4OS41MDIgLTE1NjUuOTMsLTE2Ny41MDIiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjExLjM4cHg7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoLTEuMjk3MywtMC45NTM1ODIsLTAuOTY2OTAyLDEuMjc5NDMsLTQ2MTUuOTksLTgxNS44OSkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTS05MzksLTQ4NkMtOTM5LC00ODYgLTkwMi4wOTcsLTY1OS45MDYgLTc5NiwtNTk3Qy02ODkuOTAzLC01MzQuMDk0IC05MjYuMDg4LC00NjYuNzg5IC05MjksLTQ2OCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6cmdiKDI1LDI1LDI1KTtzdHJva2Utd2lkdGg6OS4zcHg7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoODE2LjY1MywtMjQwLjY5OSwyNDQuMDYxLDgwNS40MDMsLTMyMjYuOTIsLTExMC42OTQpIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLjUyNSwwLjAxMUMwLjQ1MywwLjAxMSAwLjM4OCwwLjAwMSAwLjMyOSwtMC4wMThDMC4yNzEsLTAuMDM4IDAuMjI0LC0wLjA2NyAwLjE5LC0wLjEwNkMwLjE1NSwtMC4xNDQgMC4xMzgsLTAuMTkzIDAuMTM4LC0wLjI1MUMwLjEzOCwtMC4zMSAwLjE1NSwtMC4zNTggMC4xOSwtMC4zOTdDMC4yMjQsLTAuNDM1IDAuMjcxLC0wLjQ2NCAwLjMyOSwtMC40ODNDMC4zODgsLTAuNTAyIDAuNDUzLC0wLjUxMiAwLjUyNSwtMC41MTJDMC41OTcsLTAuNTEyIDAuNjYyLC0wLjUwMiAwLjcyLC0wLjQ4M0MwLjc3OCwtMC40NjQgMC44MjUsLTAuNDM1IDAuODYsLTAuMzk2QzAuODk0LC0wLjM1OCAwLjkxMiwtMC4zMDkgMC45MTIsLTAuMjUxQzAuOTEyLC0wLjE5MyAwLjg5NCwtMC4xNDQgMC44NiwtMC4xMDZDMC44MjUsLTAuMDY3IDAuNzc4LC0wLjAzOCAwLjcyLC0wLjAxOEMwLjY2MiwwLjAwMSAwLjU5NywwLjAxMSAwLjUyNSwwLjAxMVoiIHN0eWxlPSJmaWxsOnJnYigxMzEsMzcsNzkpO2ZpbGwtcnVsZTpub256ZXJvOyIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEuMzAwMzEsMCwwLDEuMjgyNCwtOTMyLjM4MSwtMzA4LjAyKSI+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSItMTY2OS41IiBjeT0iLTc0LjUiIHI9IjE3LjUiIHN0eWxlPSJmaWxsOnJnYigyNSwyNSwyNSk7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjExLjM4cHg7c3Ryb2tlLWxpbmVjYXA6c3F1YXJlO3N0cm9rZS1saW5lam9pbjpyb3VuZDsiLz4KICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxLjMwMDMxLDAsMCwxLjI4MjQsLTgwMS4wNSwtMjg1LjU3OCkiPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iLTE2NjkuNSIgY3k9Ii03NC41IiByPSIxNy41IiBzdHlsZT0iZmlsbDpyZ2IoMjUsMjUsMjUpO3N0cm9rZTpyZ2IoMjUsMjUsMjUpO3N0cm9rZS13aWR0aDoxMS4zOHB4O3N0cm9rZS1saW5lY2FwOnNxdWFyZTtzdHJva2UtbGluZWpvaW46cm91bmQ7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMS42MTc5OSwwLDAsMS41OTU3LC0xMzE2LjYzLDIyMy4yNjMpIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0tOTM5LC00ODZDLTkzOSwtNDg2IC05MDIuMDk3LC02NTkuOTA2IC03OTYsLTU5N0MtNjg5LjkwMywtNTM0LjA5NCAtOTI2LjA4OCwtNDY2Ljc4OSAtOTI5LC00NjgiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjkuMTVweDsiLz4KICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxLjMwMDMxLDAsMCwxLjI4MjQsLTc3NS42MjksMzQuMTM4NikiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTS0xNjAzLC0yMDQuNTcxQy0xNTc4LjU4LC0yMDQuMjgxIC0xNTY4LjY0LC0xODkuNTAyIC0xNTY1LjkzLC0xNjcuNTAyIiBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTpyZ2IoMjUsMjUsMjUpO3N0cm9rZS13aWR0aDoxMS4zOHB4OyIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEuMzAwMzEsMCwwLDEuMjgyNCwtNjYxLjg4OCwtMTMuMzk5OCkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTS0xNjAzLC0yMDQuNTcxQy0xNTc4LjU4LC0yMDQuMjgxIC0xNTY4LjY0LC0xODkuNTAyIC0xNTY1LjkzLC0xNjcuNTAyIiBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTpyZ2IoMjUsMjUsMjUpO3N0cm9rZS13aWR0aDoxMS4zOHB4OyIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDEuMjU3NywtMC4zMjU1OTksMC4zMzAxNDcsMS4yNDAzNywtNTc3LjYwMiwtNTc5LjU2KSI+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNLTE2MDMsLTIwNC41NzFDLTE1NzguNTgsLTIwNC4yODEgLTE1NjguNjQsLTE4OS41MDIgLTE1NjUuOTMsLTE2Ny41MDIiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOnJnYigyNSwyNSwyNSk7c3Ryb2tlLXdpZHRoOjExLjQ4cHg7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC44NDcyMDMsMC4wNjA3Nzc4LC0wLjA2MTYyNjcsMC44MzU1MzMsLTIwNDguMDcsLTE3MS4zODUpIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0tMTIyNSwtMTIwQy0xMjI0LjkyLC0xMTguODg3IC0xMjEwLjk5LC0xMDUuNTIzIC0xMTk0LC0xMjBDLTExOTMuNDcsLTEyMC4xMzYgLTExNzkuNTMsLTEwNi4yNDEgLTExNjMuMTQsLTExOS4xNCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6cmdiKDI1LDI1LDI1KTtzdHJva2Utd2lkdGg6MTcuMzhweDtzdHJva2UtbGluZWpvaW46cm91bmQ7Ii8+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo='
16 | namedSends = { '3' = ['eggbug'] }
17 |
18 | [[modules]]
19 | plugin = 'transform.style-inliner'
20 | data.mode = 'attr'
21 | sends = ['output']
22 |
23 | [[modules]]
24 | plugin = 'source.sass'
25 | data.contents = '''
26 | @use "./eggbug";
27 | @use "./mixins.scss";
28 |
29 | .a-thing {
30 | @include mixins.example-mixin(eggbug.$value);
31 | }'''
32 | data.syntax = 'scss'
33 | sends = [2]
34 |
35 | [[modules]]
36 | plugin = 'source.sass-module'
37 | data.contents = '''
38 | @mixin example-mixin($url) {
39 | background: url("#{$url}");
40 | background-repeat: no-repeat;
41 | background-position: right center;
42 | }'''
43 | data.syntax = 'scss'
44 | namedSends = { '3' = ['mixins'] }
45 |
--------------------------------------------------------------------------------
/assets/examples/slideshow.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'Slideshow'
3 |
4 | [[modules]]
5 | plugin = 'source.svelte'
6 | data.contents = '''
7 |
10 |
11 |
12 | slide one
13 |
14 |
15 | slide two
16 |
17 |
18 |
19 | slide three
20 |
21 |
22 |
23 | slide four
24 |
25 |
26 |
27 |
28 |
'''
29 | sends = [3]
30 |
31 | [[modules]]
32 | plugin = 'source.sass'
33 | data.contents = '''
34 | .slideshow-container {
35 | $background-color: #333;
36 | color: white;
37 |
38 | $aspect-ratio: 6 / 4;
39 | $slide-button-width: 2rem;
40 | $slide-button-height: 2rem;
41 | $slide-button-text-size: 2rem;
42 |
43 | position: relative;
44 | aspect-ratio: $aspect-ratio;
45 | background: $background-color;
46 |
47 | .slide {
48 | position: absolute;
49 | left: 0;
50 | right: 0;
51 | bottom: 0;
52 | text-align: right;
53 | }
54 |
55 | .next-slide-button {
56 | display: inline-grid;
57 | place-content: center;
58 | pointer-events: all;
59 | width: $slide-button-width;
60 | height: $slide-button-height;
61 | font-size: 0;
62 | vertical-align: bottom;
63 | cursor: default;
64 |
65 | .text-contents {
66 | font-size: $slide-button-text-size;
67 | }
68 | }
69 |
70 | .fake-prev-slide-button {
71 | position: absolute;
72 | display: inline-grid;
73 | place-content: center;
74 | width: $slide-button-width;
75 | height: $slide-button-height;
76 | right: $slide-button-width;
77 | font-size: $slide-button-text-size;
78 | bottom: 0;
79 | }
80 |
81 | .displace-prev-slide-button {
82 | display: inline-block;
83 | width: $slide-button-width;
84 | }
85 |
86 | .slide-shroud {
87 | text-align: left;
88 | background: $background-color;
89 | position: absolute;
90 | pointer-events: none;
91 | width: 100%;
92 | aspect-ratio: $aspect-ratio;
93 | bottom: 0;
94 | }
95 |
96 | .slide-contents {
97 | position: absolute;
98 | inset: 0;
99 | bottom: $slide-button-height;
100 | pointer-events: all;
101 | }
102 | }'''
103 | data.syntax = 'scss'
104 | sends = [3]
105 |
106 | [[modules]]
107 | plugin = 'source.svelte-component'
108 | data.name = 'Slide'
109 | data.contents = '''
110 |
111 |
112 | ▶
113 |
114 |
115 |
119 | '''
120 | sends = [0]
121 |
122 | [[modules]]
123 | plugin = 'transform.style-inliner'
124 | data.mode = 'attr'
125 | sends = ['output']
126 |
--------------------------------------------------------------------------------
/assets/examples/svelte-chat-log.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'Svelte Chat Log'
3 |
4 | [[modules]]
5 | plugin = 'source.text'
6 | data.contents = '''
7 | eggbug: egg!
8 | bug!
9 | eggbug2: egg bug!'''
10 | data.language = 'text'
11 | namedSends = { '2' = ['chat-log'] }
12 |
13 | [[modules]]
14 | plugin = 'source.lesscss'
15 | data.contents = '''
16 | .sender-picture {
17 | /*
18 | * if you want to actually use this code,
19 | * consider uploading your images to cohost
20 | * and using a cohostcdn URL instead of
21 | * inlining them into the CSS like this.
22 | * you might hit the post size limit (~200kB)
23 | * otherwise!
24 | */
25 | &[data-sender="eggbug"] {
26 | background-image: url("@{eggbug}");
27 | }
28 | &[data-sender="eggbug2"] {
29 | background-image: url("@{eggbug}");
30 | }
31 | }
32 |
33 | .chat-log {
34 | list-style: none;
35 | padding: 0;
36 | }
37 | .chat-message {
38 | margin: 0;
39 | margin-top: 0.5em;
40 | display: flex;
41 | line-height: 1.2;
42 | padding: 0;
43 | @picture-size: 2em;
44 | @gap: 0.5em;
45 | gap: @gap;
46 |
47 | &.is-continuation {
48 | padding-left: @picture-size + @gap;
49 | margin-top: 0;
50 | }
51 |
52 | .sender-picture {
53 | width: @picture-size;
54 | height: @picture-size;
55 | border-radius: 50%;
56 | background-size: cover;
57 | background-position: center;
58 | background-repeat: no-repeat;
59 | flex-shrink: 0;
60 | }
61 |
62 | .sender-header {
63 | font-weight: bold;
64 | }
65 | .message-contents {
66 | line-height: 1.5;
67 | }
68 | }
69 | '''
70 | sends = [4]
71 |
72 | [[modules]]
73 | plugin = 'source.svelte'
74 | data.contents = '''
75 |
109 |
110 | '''
131 | sends = [4]
132 |
133 | [[modules]]
134 | plugin = 'source.file-data-url'
135 | data.url = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYzIiBoZWlnaHQ9IjE2NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48bGluZWFyR3JhZGllbnQgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSIgaWQ9ImEiPjxzdG9wIHN0b3AtY29sb3I9IiNEQUFDOTMiIG9mZnNldD0iMCUiLz48c3RvcCBzdG9wLWNvbG9yPSIjNjQxRDM0IiBvZmZzZXQ9IjEwMCUiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGZpbGw9InVybCgjYSkiIGQ9Ik0wIDBoMTYzdjE2N0gweiIvPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0uNDYyIC0uMDEyKSI+PHBhdGggZD0iTTQxLjg4MiAxMDkuMTI1Yy00Ljg0Mi4wNi02LjgxMyAzLjA5NS03LjM1IDcuNjEzIiBzdHJva2U9IiMxOTE5MTkiIHN0cm9rZS13aWR0aD0iMi4yOTgiLz48cGF0aCBkPSJNNzAuNzMgNjYuMjhzMTkuNTAzLTQxLjE4Ny0xMS4wMzMtNDMuODZjLTMwLjUzNi0yLjY3MSA1LjUzOSA0NS45MSA2LjI5OCA0Ni4wOSIgc3Ryb2tlPSIjMTkxOTE5IiBzdHJva2Utd2lkdGg9IjIuMzM3Ii8+PHBhdGggZD0iTTkxLjgzOCAxMTcuMDJjLTguOTQ4IDIuNzA1LTE3LjQxNiAzLjg1OC0yNS40OSAzLjYyMy03Ljk4Ny0uNC0xNC45Ni0yLjM3NS0yMC43MDYtNi4xMjktNS44MzEtMy41ODctOS44NTUtOS4yNjktMTIuMTE3LTE2Ljc1LTIuMzAxLTcuNjEyLTIuMDYtMTQuNDQyLjc2OC0yMC43ODkgMi43NDMtNi4xNzkgNy40NTMtMTEuNjg2IDEzLjkyLTE2LjMxNiA2LjU5MS00LjY2OCAxNC4yOC04LjQgMjMuMjI3LTExLjEwNiA4Ljk0OC0yLjcwNSAxNy40MTYtMy44NTcgMjUuMzY1LTMuNTg2IDcuOTUuMjcyIDE0LjkyMSAyLjI0NyAyMC43OTIgNS45NjMgNS43MDcgMy42MjQgOS44NTYgOS4yNyAxMi4xMTggMTYuNzUxIDIuMjYyIDcuNDgyIDEuOTM2IDE0LjQ4LS44MDggMjAuNjU5LTIuODI4IDYuMzQ2LTcuNTM4IDExLjg1My0xMy45NjYgMTYuNjEyLTYuNDY3IDQuNjMtMTQuMTU1IDguMzYzLTIzLjEwMyAxMS4wNjhaIiBmaWxsPSIjODMyNTRGIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48ZWxsaXBzZSBzdHJva2U9IiMxOTE5MTkiIHN0cm9rZS13aWR0aD0iMi4yOTgiIGZpbGw9IiMxOTE5MTkiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGN4PSI0NC40NCIgY3k9Ijg4LjkzMiIgcng9IjMuNDciIHJ5PSIzLjU5NCIvPjxlbGxpcHNlIHN0cm9rZT0iIzE5MTkxOSIgc3Ryb2tlLXdpZHRoPSIyLjI5OCIgZmlsbD0iIzE5MTkxOSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY3g9IjY0LjQ2OCIgY3k9IjkyLjUyNiIgcng9IjMuNDciIHJ5PSIzLjU5NCIvPjxwYXRoIGQ9Ik04NS4yMDYgNjUuMTIyczkuMTA2LTQ0LjQzOCAzNS4yODMtMjguMzY0YzI2LjE3OCAxNi4wNzQtMzIuMDk3IDMzLjI3My0zMi44MTUgMzIuOTYzIiBzdHJva2U9IiMxOTE5MTkiIHN0cm9rZS13aWR0aD0iMi4yOTkiLz48cGF0aCBkPSJNODEuNTMgMTE3LjAxM2M0Ljg0My4wNiA2LjgxNCAzLjA5NSA3LjM1MSA3LjYxM005OC44NzYgMTA5LjRjNC44NDIuMDYgNi44MTMgMy4wOTUgNy4zNSA3LjYxMyIgc3Ryb2tlPSIjMTkxOTE5IiBzdHJva2Utd2lkdGg9IjIuMjk4Ii8+PHBhdGggZD0iTTExMS44MDYgMTAzLjY3MWM0LjY5MS0xLjE4NCA3LjM3NSAxLjI0NiA5LjA1NSA1LjQ3OCIgc3Ryb2tlPSIjMTkxOTE5IiBzdHJva2Utd2lkdGg9IjIuMzE4Ii8+PHBhdGggZD0iTTQ4LjIxNyA5OC4xNGMwIC4xNSAxLjY2NyAyLjA3IDQuMDA0LjI5NS4wNy0uMDEzIDEuNzM0IDEuOTc5IDMuOTc4LjQxIiBzdHJva2U9IiMxOTE5MTkiIHN0cm9rZS13aWR0aD0iMi4yOTMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvZz48L2c+PC9zdmc+'
136 | namedSends = { '1' = ['eggbug'] }
137 |
138 | [[modules]]
139 | plugin = 'transform.style-inliner'
140 | data.mode = 'attr'
141 | sends = ['output']
142 |
--------------------------------------------------------------------------------
/assets/examples/svelte-templates.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'Svelte Templating'
3 |
4 | [[modules]]
5 | plugin = 'source.svelte'
6 | data.contents = '''
7 |
11 |
12 |
13 | Hello {textData.toUpperCase()}
14 | '''
15 | sends = [3]
16 |
17 | [[modules]]
18 | plugin = 'source.text'
19 | data.contents = 'world!'
20 | data.language = 'text'
21 | namedSends = { '0' = ['text-data'] }
22 |
23 | [[modules]]
24 | plugin = 'source.svelte-component'
25 | data.name = 'Box'
26 | data.contents = '''
27 |
28 |
29 |
30 |
31 | '''
40 | sends = [0]
41 |
42 | [[modules]]
43 | plugin = 'transform.style-inliner'
44 | data.mode = 'attr'
45 | sends = ['output']
46 |
--------------------------------------------------------------------------------
/assets/examples/svg-conditional-css.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | title = 'Conditional CSS inside SVG'
3 |
4 | [[modules]]
5 | plugin = 'source.text'
6 | data.contents = '''
7 | ### triggering conditional CSS inside SVG backgrounds
8 |
9 | The <details> element here has a size of 200×200 pixels, but when opened, it has a size of 200×201 pixels (because of the .inner-spacer element).
10 |
11 | By using an SVG background that does not have a viewBox and a background-size of 100%, the browser will actually pass the size of the container to the SVG image. So, inside the SVG (module #6 LessCSS), we can check for the case where the container is larger than 200 pixels, and apply a simple animation (or a more complex animation…)
12 |
13 |
14 |
15 |
16 | '''
17 | data.language = 'html'
18 | sends = [2]
19 |
20 | [[modules]]
21 | plugin = 'source.lesscss'
22 | data.contents = '''
23 | .svg-container {
24 | display: inline-block;
25 | background: url("@{svg}") top left no-repeat;
26 | background-size: 100% 100%;
27 |
28 | & > summary {
29 | width: 200px;
30 | height: 200px;
31 | font-size: 0; /* hide the arrow */
32 | }
33 |
34 | .inner-spacer {
35 | height: 1px;
36 | }
37 | }'''
38 | sends = [2]
39 |
40 | [[modules]]
41 | plugin = 'transform.style-inliner'
42 | data.mode = 'attr'
43 | sends = ['output']
44 |
45 | [[modules]]
46 | plugin = 'transform.to-data-url'
47 | data.mime = 'image/svg+xml'
48 | namedSends = { '1' = ['svg'] }
49 |
50 | [[modules]]
51 | plugin = 'source.text'
52 | data.contents = '''
53 | '''
69 | data.language = 'html'
70 | sends = [6]
71 |
72 | [[modules]]
73 | plugin = 'source.lesscss'
74 | data.contents = '''
75 | /*
76 | in several browsers, animation does not work inside
77 | svgs loaded as an image unless you have something
78 | animating constantly.
79 | if you don't need animation, you can remove this to
80 | save resources!
81 | */
82 | .animation-fix {
83 | position: fixed;
84 | top: 0;
85 | left: 0;
86 | width: 1px;
87 | height: 1px;
88 | animation: animation-fix 1s infinite;
89 | }
90 | @keyframes animation-fix {
91 | 100% { transform: rotate(360deg) }
92 | }
93 |
94 | .container {
95 | font-family: sans-serif;
96 | }
97 |
98 | .click-prompt {
99 | position: absolute;
100 | width: 200px;
101 | height: 200px;
102 | padding: 1em;
103 | box-sizing: border-box;
104 | background: gray;
105 | color: white;
106 | transition: all 1s;
107 | }
108 | .contents {
109 | position: relative;
110 | width: 200px;
111 | height: 200px;
112 | padding: 1em;
113 | box-sizing: border-box;
114 | background: #4a0;
115 | color: white;
116 | transition: all 1s;
117 | transform: translateY(100%);
118 | }
119 |
120 | /*
121 | here we define what happens when the container is clicked,
122 | i.e. when it's taller than 200px
123 | */
124 | @media (min-height: 201px) {
125 | .click-prompt {
126 | transform: scale(0);
127 | }
128 | .contents {
129 | transform: none;
130 | }
131 | }'''
132 | sends = [6]
133 |
134 | [[modules]]
135 | plugin = 'transform.style-inliner'
136 | data.mode = 'element'
137 | sends = [3]
138 |
--------------------------------------------------------------------------------
/assets/no-data.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "inputs": {
5 | "systems": "systems"
6 | },
7 | "locked": {
8 | "lastModified": 1710146030,
9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
10 | "owner": "numtide",
11 | "repo": "flake-utils",
12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "numtide",
17 | "repo": "flake-utils",
18 | "type": "github"
19 | }
20 | },
21 | "nixpkgs": {
22 | "locked": {
23 | "lastModified": 1715447595,
24 | "narHash": "sha256-VsVAUQOj/cS1LCOmMjAGeRksXIAdPnFIjCQ0XLkCsT0=",
25 | "owner": "nixos",
26 | "repo": "nixpkgs",
27 | "rev": "062ca2a9370a27a35c524dc82d540e6e9824b652",
28 | "type": "github"
29 | },
30 | "original": {
31 | "owner": "nixos",
32 | "ref": "nixos-unstable",
33 | "repo": "nixpkgs",
34 | "type": "github"
35 | }
36 | },
37 | "root": {
38 | "inputs": {
39 | "flake-utils": "flake-utils",
40 | "nixpkgs": "nixpkgs"
41 | }
42 | },
43 | "systems": {
44 | "locked": {
45 | "lastModified": 1681028828,
46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
47 | "owner": "nix-systems",
48 | "repo": "default",
49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
50 | "type": "github"
51 | },
52 | "original": {
53 | "owner": "nix-systems",
54 | "repo": "default",
55 | "type": "github"
56 | }
57 | }
58 | },
59 | "root": "root",
60 | "version": 7
61 | }
62 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "prechoster";
3 | inputs = {
4 | flake-utils.url = "github:numtide/flake-utils";
5 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
6 | };
7 |
8 | outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
9 |
10 | pkgs = nixpkgs.legacyPackages.${system};
11 |
12 | compatibleSiteRev = "9ceeebd8";
13 | staticUrl = "https://cloudwithlightning.net/random/chostin/static.${compatibleSiteRev}/";
14 |
15 | in
16 | {
17 |
18 | packages.default = pkgs.buildNpmPackage {
19 | name = "prechoster";
20 | src = ./.;
21 | nativeBuildInputs = with pkgs; [ git ];
22 | npmDepsHash = "sha256-hw2QcOcKaTJ42TDoThHpvPRVQ8SgGPAQ6rz+UcQ9z8s=";
23 |
24 | installPhase = ''
25 | runHook preInstall
26 | mkdir $out
27 | cp -r dist/* $out
28 | runHook postInstall
29 | '';
30 |
31 | PRECHOSTER_STATIC = staticUrl;
32 | PRECHOSTER_GIT_COMMIT_HASH = if (self ? rev) then self.rev else "dirty";
33 | };
34 |
35 | devShells.default = pkgs.mkShell {
36 | buildInputs = with pkgs; [ nodejs ];
37 |
38 | PRECHOSTER_STATIC = staticUrl;
39 | };
40 |
41 | });
42 | }
43 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | prechoster
8 |
9 |
10 |
11 |
Application not loaded
12 |
13 | If you can see this, the application Javascript was not executed. This may have
14 | various causes:
15 |
16 |
17 |
20 | - If your network connection is particularly slow, it may still be loading.
21 | -
22 | A network error may have occurred. You can try reloading the
23 | page.
24 |
25 | -
26 | Your web browser may not support features used by the application. Consider
27 | using at least Firefox 98, Safari 15.4, or Chrome 63.
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prechoster",
3 | "version": "0.1.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "fmt": "prettier --write .",
8 | "dev": "vite dev",
9 | "build": "vite build"
10 | },
11 | "homepage": "https://github.com/cpsdqs/prechoster",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "@rollup/plugin-alias": "^4.0.2",
15 | "@rollup/plugin-babel": "^5.3.1",
16 | "@rollup/plugin-commonjs": "^22.0.1",
17 | "@rollup/plugin-html": "^1.0.1",
18 | "@rollup/plugin-json": "^4.1.0",
19 | "@rollup/plugin-node-resolve": "^13.3.0",
20 | "@rollup/plugin-typescript": "^9.0.2",
21 | "@surma/rollup-plugin-off-main-thread": "^2.2.3",
22 | "@types/css-tree": "^1.0.7",
23 | "@types/react": "^18.0.25",
24 | "@types/react-dom": "^18.0.9",
25 | "autoprefixer": "^10.4.14",
26 | "postcss": "^8.4.19",
27 | "postcss-nesting": "^10.2.0",
28 | "prettier": "^2.7.1",
29 | "rollup-plugin-postcss": "^4.0.2",
30 | "rollup-plugin-terser": "^7.0.2",
31 | "typescript": "^4.7.4",
32 | "vite": "^4.5.1"
33 | },
34 | "dependencies": {
35 | "@bramus/specificity": "^2.1.0",
36 | "@codemirror/lang-css": "^6.0.0",
37 | "@codemirror/lang-html": "^6.1.0",
38 | "@codemirror/lang-javascript": "^6.0.2",
39 | "@codemirror/lang-less": "^6.0.1",
40 | "@codemirror/lang-sass": "^6.0.2",
41 | "@codemirror/view": "^6.1.0",
42 | "@ltd/j-toml": "^1.38.0",
43 | "@uiw/codemirror-theme-xcode": "^4.11.4",
44 | "@uiw/react-codemirror": "^4.11.4",
45 | "@vitejs/plugin-react": "^4.0.3",
46 | "base64-js": "^1.5.1",
47 | "css-tree": "^2.1.0",
48 | "events": "^3.3.0",
49 | "idb": "^7.1.1",
50 | "less": "^4.1.3",
51 | "react": "^18.2.0",
52 | "react-dom": "^18.2.0",
53 | "reactflow": "^11.2.0",
54 | "rehype-stringify": "^9.0.3",
55 | "remark-breaks": "^3.0.3",
56 | "remark-gfm": "^3.0.1",
57 | "remark-parse": "^10.0.2",
58 | "remark-rehype": "^10.1.0",
59 | "rollup": "^2.77.0",
60 | "sass": "^1.63.6",
61 | "svelte": "^3.55.1",
62 | "svgo": "^2.8.0",
63 | "unified": "^10.1.2",
64 | "whatwg-url": "^11.0.0"
65 | },
66 | "postcss": {
67 | "plugins": {
68 | "postcss-nesting": {},
69 | "autoprefixer": {}
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | tabWidth: 4,
4 | singleQuote: true,
5 | };
6 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 | import { initStorage, MemoryStorage } from './storage';
3 | import ApplicationFrame from './ui';
4 |
5 | let canInit = true;
6 | {
7 | // check support for