@alstar/studio 0.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ import { html } from 'hono/html'
2
+
3
+ export const logo = html`
4
+ <svg
5
+ viewBox="0 0 1289 342"
6
+ fill="none"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ >
9
+ <path
10
+ d="M7.152 342C2.88533 342 0.752001 339.867 0.752001 335.6V98.288C0.752001 95.5573 1.60533 92.9973 3.312 90.608L63.728 4.59198C65.6053 2.03198 68.336 0.751984 71.92 0.751984H163.568C167.323 0.751984 170.053 2.03198 171.76 4.59198L232.688 90.608C234.395 92.9973 235.248 95.5573 235.248 98.288V335.6C235.248 339.867 233.115 342 228.848 342H135.152C130.885 342 128.752 339.867 128.752 335.6V239.6H107.248V335.6C107.248 339.867 105.115 342 100.848 342H7.152ZM118 150.768C125.339 150.768 131.312 148.464 135.92 143.856C140.528 139.248 142.832 133.36 142.832 126.192C142.832 118.853 140.528 112.88 135.92 108.272C131.312 103.664 125.339 101.36 118 101.36C110.661 101.36 104.688 103.664 100.08 108.272C95.472 112.88 93.168 118.853 93.168 126.192C93.168 133.36 95.472 139.248 100.08 143.856C104.688 148.464 110.661 150.768 118 150.768ZM263.152 342C258.885 342 256.752 339.867 256.752 335.6V7.15198C256.752 2.88531 258.885 0.751984 263.152 0.751984H356.848C361.115 0.751984 363.248 2.88531 363.248 7.15198V239.6H406.512C410.779 239.6 412.912 241.733 412.912 246V335.6C412.912 339.867 410.779 342 406.512 342H263.152ZM440.652 342C436.385 342 434.252 339.867 434.252 335.6V263.408C434.252 261.36 434.337 259.739 434.508 258.544C434.849 257.179 435.873 255.984 437.58 254.96L491.852 223.728V210.928L438.348 182.768C435.617 181.232 434.252 178.672 434.252 175.088V59.888C434.252 57.1573 434.423 55.1947 434.764 54C435.105 52.6347 436.3 51.184 438.348 49.648L505.932 4.07999C507.297 3.05598 508.748 2.28797 510.284 1.77597C511.82 1.09331 513.783 0.751984 516.172 0.751984H606.54C610.807 0.751984 612.94 2.88531 612.94 7.15198V82.928C612.94 86.6826 611.66 89.2427 609.1 90.608L542.028 120.048V133.36L609.1 171.76C611.66 172.955 612.94 175.685 612.94 179.952V291.312C612.94 292.677 612.684 294.128 612.172 295.664C611.831 297.029 610.977 298.139 609.612 298.992L529.228 338.928C528.204 339.611 526.924 340.293 525.388 340.976C524.023 341.659 522.657 342 521.292 342H440.652ZM676.748 342C672.481 342 670.348 339.867 670.348 335.6V103.152H640.652C636.385 103.152 634.252 101.019 634.252 96.752V7.15198C634.252 2.88531 636.385 0.751984 640.652 0.751984H807.052C811.319 0.751984 813.452 2.88531 813.452 7.15198V96.752C813.452 101.019 811.319 103.152 807.052 103.152H777.1V335.6C777.1 339.867 774.967 342 770.7 342H676.748ZM841.152 342C836.885 342 834.752 339.867 834.752 335.6V7.15198C834.752 2.88531 836.885 0.751984 841.152 0.751984H1019.33C1023.59 0.751984 1025.73 2.88531 1025.73 7.15198V96.752C1025.73 101.019 1023.59 103.152 1019.33 103.152H941.248V124.4H1019.33C1023.59 124.4 1025.73 126.533 1025.73 130.8V211.952C1025.73 216.219 1023.59 218.352 1019.33 218.352H941.248V239.6H1019.33C1023.59 239.6 1025.73 241.733 1025.73 246V335.6C1025.73 339.867 1023.59 342 1019.33 342H841.152ZM1053.65 342C1049.39 342 1047.25 339.867 1047.25 335.6V7.15198C1047.25 2.88531 1049.39 0.751984 1053.65 0.751984H1210.07C1214.51 0.751984 1217.92 1.86132 1220.31 4.07999L1279.19 62.448C1281.07 64.1547 1282.26 65.6053 1282.77 66.8C1283.28 67.9946 1283.54 69.9573 1283.54 72.688V142.832C1283.54 145.051 1282.09 147.611 1279.19 150.512L1243.86 185.84L1238.23 190.96L1254.1 214.768L1286.1 276.208C1286.95 277.744 1287.47 279.28 1287.64 280.816C1287.98 282.181 1288.15 283.973 1288.15 286.192V335.6C1288.15 339.867 1286.01 342 1281.75 342H1200.85C1197.1 342 1194.54 340.293 1193.17 336.88L1158.1 246.768H1153.75V335.6C1153.75 339.867 1151.61 342 1147.35 342H1053.65ZM1165.27 139.76C1172.27 139.76 1177.9 137.541 1182.16 133.104C1186.6 128.667 1188.82 123.035 1188.82 116.208C1188.82 109.381 1186.6 103.835 1182.16 99.568C1177.9 95.1307 1172.27 92.912 1165.27 92.912C1158.44 92.912 1152.81 95.1307 1148.37 99.568C1144.11 103.835 1141.97 109.381 1141.97 116.208C1141.97 123.035 1144.11 128.667 1148.37 133.104C1152.81 137.541 1158.44 139.76 1165.27 139.76Z"
11
+ fill="currentColor"
12
+ />
13
+ </svg>
14
+ `
15
+
16
+ export const plus = html`
17
+ <svg
18
+ xmlns="http://www.w3.org/2000/svg"
19
+ width="26"
20
+ height="26"
21
+ viewBox="0 0 20 20"
22
+ >
23
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
24
+ <path
25
+ fill="currentColor"
26
+ d="M10.75 6.75a.75.75 0 0 0-1.5 0v2.5h-2.5a.75.75 0 0 0 0 1.5h2.5v2.5a.75.75 0 0 0 1.5 0v-2.5h2.5a.75.75 0 0 0 0-1.5h-2.5z"
27
+ />
28
+ </svg>
29
+ `
30
+
31
+ export const pen = html`
32
+ <svg
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ width="16"
35
+ height="16"
36
+ viewBox="0 0 16 16"
37
+ >
38
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
39
+ <g fill="currentColor">
40
+ <path
41
+ d="M13.488 2.513a1.75 1.75 0 0 0-2.475 0L6.75 6.774a2.8 2.8 0 0 0-.596.892l-.848 2.047a.75.75 0 0 0 .98.98l2.047-.848a2.8 2.8 0 0 0 .892-.596l4.262-4.262a1.75 1.75 0 0 0 0-2.474"
42
+ />
43
+ <path
44
+ d="M4.75 3.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h6.5c.69 0 1.25-.56 1.25-1.25V9A.75.75 0 0 1 14 9v2.25A2.75 2.75 0 0 1 11.25 14h-6.5A2.75 2.75 0 0 1 2 11.25v-6.5A2.75 2.75 0 0 1 4.75 2H7a.75.75 0 0 1 0 1.5z"
45
+ />
46
+ </g>
47
+ </svg>
48
+ `
49
+
50
+ export const bars = html`
51
+ <svg
52
+ xmlns="http://www.w3.org/2000/svg"
53
+ width="26"
54
+ height="26"
55
+ viewBox="0 0 24 24"
56
+ >
57
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
58
+ <path
59
+ fill="none"
60
+ stroke="currentColor"
61
+ stroke-linecap="round"
62
+ stroke-linejoin="round"
63
+ stroke-width="1.5"
64
+ d="M3.75 9h16.5m-16.5 6.75h16.5"
65
+ />
66
+ </svg>
67
+ `
68
+
69
+ export const chevron = html`
70
+ <svg
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ width="26"
73
+ height="26"
74
+ viewBox="0 0 16 16"
75
+ >
76
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
77
+ <path
78
+ fill="currentColor"
79
+ fill-rule="evenodd"
80
+ d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06"
81
+ clip-rule="evenodd"
82
+ />
83
+ </svg>
84
+ `
85
+
86
+ export const newDocument = html`
87
+ <svg
88
+ xmlns="http://www.w3.org/2000/svg"
89
+ width="16"
90
+ height="16"
91
+ viewBox="0 0 24 24"
92
+ >
93
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
94
+ <path
95
+ fill="none"
96
+ stroke="currentColor"
97
+ stroke-linecap="round"
98
+ stroke-linejoin="round"
99
+ stroke-width="1.5"
100
+ d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m3.75 9v6m3-3H9m1.5-12H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9"
101
+ />
102
+ </svg>
103
+ `
@@ -0,0 +1,30 @@
1
+ import { html } from 'hono/html'
2
+ import { structure } from '../index.ts'
3
+
4
+ const codeBlock = html`<code><span style="color: #c678dd;">await</span> <span style="color: #61aeee;">createStudio</span>([
5
+ {
6
+ <span style="color: #d19a66;">name</span>: <span style="color: #98c379;">'entry'</span>,
7
+ <span style="color: #d19a66;">label</span>: <span style="color: #98c379;">'Entry'</span>,
8
+ <span style="color: #d19a66;">type</span>: <span style="color: #98c379;">'entry'</span>,
9
+ <span style="color: #d19a66;">fields</span>: [
10
+ {
11
+ <span style="color: #d19a66;">name</span>: <span style="color: #98c379;">'title'</span>,
12
+ <span style="color: #d19a66;">label</span>: <span style="color: #98c379;">'Title'</span>,
13
+ <span style="color: #d19a66;">type</span>: <span style="color: #98c379;">'text'</span>,
14
+ }
15
+ ]
16
+ }
17
+ ])</code>`
18
+
19
+ export default () => {
20
+ const Discamer = html`
21
+ <div class="disclamer">
22
+ <article>
23
+ <header>No structure found</header>
24
+ <p>The Studio needs to be initialized with a structure:</p>
25
+ <pre>${codeBlock}</pre>
26
+ </article>
27
+ </div>`
28
+
29
+ return html`${!structure.length ? Discamer : ''}`
30
+ }
@@ -0,0 +1,53 @@
1
+ import adminPanel from './AdminPanel/AdminPanel.ts'
2
+ import { html } from 'hono/html'
3
+ import { type HtmlEscapedString } from 'hono/utils/html'
4
+ import { rootdir } from '../index.ts'
5
+ import { type Structure } from '../types.ts'
6
+
7
+ export default (props: {
8
+ content:
9
+ | string
10
+ | Promise<string>
11
+ | HtmlEscapedString
12
+ | Promise<HtmlEscapedString>
13
+ structure: Structure
14
+ }) => {
15
+ return html`
16
+ <!DOCTYPE html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="UTF-8" />
20
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
21
+ <title>Alster CMS</title>
22
+
23
+ <link
24
+ rel="icon"
25
+ type="image/svg"
26
+ href="${rootdir}/public/favicon.svg"
27
+ />
28
+
29
+ <meta name="color-scheme" content="light dark" />
30
+
31
+ <link rel="stylesheet" href="${rootdir}/public/main.css" />
32
+
33
+ <script
34
+ type="module"
35
+ src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"
36
+ ></script>
37
+ <script src="https://unpkg.com/@barba/core@2.10.3/dist/barba.umd.js"></script>
38
+ </head>
39
+
40
+ <body data-barba="wrapper">
41
+ <section>${adminPanel(props.structure)}</section>
42
+
43
+ <main style="height: 100vh; overflow: auto">
44
+ <section data-barba="container" data-barba-namespace="default">
45
+ ${props.content}
46
+ </section>
47
+ </main>
48
+
49
+ <script src="${rootdir}/public/main.js" type="module"></script>
50
+ </body>
51
+ </html>
52
+ `
53
+ }
package/index.ts ADDED
@@ -0,0 +1,71 @@
1
+ import { Hono } from 'hono'
2
+ import { loadDb } from '@alstar/db'
3
+ import { query } from './queries/index.ts'
4
+ import { sectionRoutes } from './api/index.ts'
5
+ import { getConfig } from './utils/get-config.ts'
6
+ import * as types from './types.ts'
7
+
8
+ import Layout from './components/layout.ts'
9
+ import IndexPage from './components/index.ts'
10
+ import Entry from './components/Entry.ts'
11
+
12
+ import { serve } from '@hono/node-server'
13
+ import { serveStatic } from '@hono/node-server/serve-static'
14
+ import { createStudioTables } from './utils/create-studio-tables.ts'
15
+ import { fileBasedRouter } from './utils/file-based-router.ts'
16
+
17
+ export let structure: types.Structure
18
+ export let rootdir = '/node_modules/@alstar/studio'
19
+
20
+ const createStudio = async (studioStructure?: types.Structure) => {
21
+ const config = await getConfig<types.StudioConfig>()
22
+
23
+ structure = studioStructure || []
24
+
25
+ loadDb('./cms.db')
26
+
27
+ createStudioTables()
28
+
29
+ const app = new Hono(config.honoConfig)
30
+
31
+ app.use('*', serveStatic({ root: './' }))
32
+ app.use('*', serveStatic({ root: './public' }))
33
+
34
+ app.route('/', await fileBasedRouter('./pages'))
35
+
36
+ app.route('/admin/api', sectionRoutes(structure))
37
+
38
+ app.get('/admin', (c) => c.html(Layout({ structure, content: IndexPage() })))
39
+ app.get('/admin/entry/:id', (c) => {
40
+ return c.html(
41
+ Layout({
42
+ structure,
43
+ content: Entry({ structure, entryId: c.req.param('id') }),
44
+ }),
45
+ )
46
+ })
47
+
48
+ const server = serve(app)
49
+
50
+ // graceful shutdown
51
+ process.on('SIGINT', () => {
52
+ server.close()
53
+ process.exit(0)
54
+ })
55
+ process.on('SIGTERM', () => {
56
+ server.close((err) => {
57
+ if (err) {
58
+ console.error(err)
59
+ process.exit(1)
60
+ }
61
+ process.exit(0)
62
+ })
63
+ })
64
+
65
+ return app
66
+ }
67
+
68
+ export const defineConfig = (config: types.StudioConfig) => config
69
+ export const defineStructure = (structure: types.Structure) => structure
70
+
71
+ export { createStudio, query }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@alstar/studio",
3
+ "version": "0.0.0-beta.1",
4
+ "type": "module",
5
+ "main": "index.ts",
6
+ "scripts": {
7
+ "dev": "node --watch index.ts"
8
+ },
9
+ "dependencies": {
10
+ "@alstar/db": "workspace:*",
11
+ "@alstar/ui": "workspace:*",
12
+ "@hono/node-server": "^1.18.1",
13
+ "@starfederation/datastar-sdk": "1.0.0-RC.1",
14
+ "hono": "^4.8.12",
15
+ "sortablejs": "^1.15.6"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^24.1.0",
19
+ "prettier": "^3.6.2",
20
+ "prettier-plugin-embed": "^0.5.0",
21
+ "prettier-plugin-sql": "^0.19.2"
22
+ },
23
+ "prettier": {
24
+ "semi": false,
25
+ "singleQuote": true,
26
+ "plugins": [
27
+ "prettier-plugin-embed",
28
+ "prettier-plugin-sql"
29
+ ],
30
+ "embeddedSqlTags": [
31
+ "sql"
32
+ ],
33
+ "language": "sqlite",
34
+ "keywordCase": "lower"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
@@ -0,0 +1,6 @@
1
+ <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path
3
+ d="M89.728 512C83.328 512 80.128 508.8 80.128 502.4V146.432C80.128 142.336 81.408 138.496 83.968 134.912L174.592 5.88797C177.408 2.04797 181.504 0.127975 186.88 0.127975H324.352C329.984 0.127975 334.08 2.04797 336.64 5.88797L428.032 134.912C430.592 138.496 431.872 142.336 431.872 146.432V502.4C431.872 508.8 428.672 512 422.272 512H281.728C275.328 512 272.128 508.8 272.128 502.4V358.4H239.872V502.4C239.872 508.8 236.672 512 230.272 512H89.728ZM256 225.152C267.008 225.152 275.968 221.696 282.88 214.784C289.792 207.872 293.248 199.04 293.248 188.288C293.248 177.28 289.792 168.32 282.88 161.408C275.968 154.496 267.008 151.04 256 151.04C244.992 151.04 236.032 154.496 229.12 161.408C222.208 168.32 218.752 177.28 218.752 188.288C218.752 199.04 222.208 207.872 229.12 214.784C236.032 221.696 244.992 225.152 256 225.152Z"
4
+ fill="#FC2C2C" />
5
+ </svg>
6
+
@@ -0,0 +1,92 @@
1
+ @import './../node_modules/@alstar/ui/red.css';
2
+
3
+ body {
4
+ min-height: 100vh;
5
+ }
6
+
7
+ body {
8
+ padding: 0;
9
+ min-height: inherit;
10
+
11
+ display: grid;
12
+ grid-template-columns: auto 1fr;
13
+ align-content: baseline;
14
+
15
+ > main {
16
+ padding: 40px;
17
+
18
+ section {
19
+ max-width: 900px;
20
+ margin: 0 auto;
21
+ }
22
+ }
23
+ }
24
+
25
+ .entry > .fields > .block {
26
+ padding-block: var(--pico-spacing);
27
+
28
+ > header {
29
+ margin-block: var(--pico-block-spacing-vertical);
30
+ }
31
+ }
32
+
33
+ .block {
34
+ > header {
35
+ margin-bottom: var(--pico-spacing);
36
+ }
37
+
38
+ header {
39
+ display: flex;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+
43
+ background-color: inherit;
44
+
45
+ h6,
46
+ h5,
47
+ fieldset {
48
+ margin-bottom: 0;
49
+ }
50
+
51
+ button {
52
+ padding: 0;
53
+ background-color: white;
54
+
55
+ svg {
56
+ padding: 8px;
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ [data-sortable] {
63
+ > article {
64
+ transition: opacity 200ms;
65
+ }
66
+
67
+ .sortable-ghost {
68
+ opacity: 0.3;
69
+ }
70
+
71
+ /* .sortable-chosen {
72
+ filter: brightness(0.9)
73
+ } */
74
+ }
75
+
76
+ div.ink-mde {
77
+ border: var(--pico-border-width) solid #2a3140;
78
+ border-radius: var(--pico-border-radius);
79
+ outline: 0;
80
+ background-color: var(--pico-background-color);
81
+ box-shadow: var(--pico-box-shadow);
82
+ color: var(--pico-color);
83
+ }
84
+
85
+ .ink-mde-textarea {
86
+ width: 100%;
87
+ }
88
+
89
+ .disclamer {
90
+ display: flex;
91
+ justify-content: center;
92
+ }
package/public/main.js ADDED
@@ -0,0 +1,43 @@
1
+ import Sortable from '../node_modules/sortablejs/modular/sortable.core.esm.js'
2
+ import { ink, wrap } from 'https://esm.sh/ink-mde@0.34.0'
3
+
4
+ barba.init({
5
+ views: [
6
+ {
7
+ namespace: 'default',
8
+ afterEnter() {
9
+ init()
10
+ },
11
+ },
12
+ ],
13
+ })
14
+
15
+ function init() {
16
+ // sortable
17
+ {
18
+ const els = document.querySelectorAll('[data-sortable]')
19
+
20
+ els.forEach((el) => {
21
+ var sortable = Sortable.create(el, {
22
+ delay: 0,
23
+ animation: 250,
24
+ dragClass: 'sortable-drag',
25
+ easing: 'ease-in-out',
26
+ })
27
+ })
28
+ }
29
+
30
+ // ink-mde
31
+ {
32
+ const el = document.querySelector('textarea')
33
+
34
+ if (el) {
35
+ wrap(el, {
36
+ interface: {
37
+ // appearance: InkValues.Appearance.Auto,
38
+ attribution: false,
39
+ },
40
+ })
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,98 @@
1
+ import { db } from '@alstar/db'
2
+ import { sql } from '../utils/sql.ts'
3
+ import { type DBBlock } from '../types.ts'
4
+ import { buildBlockTree } from '../utils/buildBlocksTree.ts'
5
+
6
+ export const blocks = (options: {
7
+ parent: string | number | null
8
+ }): DBBlock[] | null => {
9
+ const q =
10
+ options.parent === null
11
+ ? sql`
12
+ select
13
+ *
14
+ from
15
+ blocks
16
+ where
17
+ parent_block_id is null;
18
+ `
19
+ : sql`
20
+ select
21
+ *
22
+ from
23
+ blocks
24
+ where
25
+ parent_block_id = ?;
26
+ `
27
+
28
+ const transaction = db.database.prepare(q)
29
+
30
+ if (options.parent === null) {
31
+ return transaction.all() as unknown as DBBlock[]
32
+ } else {
33
+ return transaction.all(options.parent) as unknown as DBBlock[]
34
+ }
35
+ }
36
+
37
+ export const block = (
38
+ query: Record<string, string | null>,
39
+ options?: { recursive: boolean },
40
+ ): DBBlock | null => {
41
+ const str = Object.keys(query)
42
+ .map((key) => `${key.replace('parent', 'parent_block_id')} = ?`)
43
+ .join(' AND ')
44
+
45
+ const q = options?.recursive
46
+ ? sql`
47
+ with recursive
48
+ block_hierarchy as (
49
+ -- Anchor member: the root block, depth = 0
50
+ select
51
+ b.*,
52
+ 0 as depth
53
+ from
54
+ blocks b
55
+ where
56
+ b.id = ? -- Replace 5 with your root block ID
57
+ union all
58
+ -- Recursive member: find children and increment depth
59
+ select
60
+ b.*,
61
+ bh.depth + 1 as depth
62
+ from
63
+ blocks b
64
+ inner join block_hierarchy bh on b.parent_block_id = bh.id
65
+ )
66
+ select
67
+ *
68
+ from
69
+ block_hierarchy
70
+ order by
71
+ sort_order;
72
+ `
73
+ : sql`
74
+ select
75
+ *
76
+ from
77
+ blocks
78
+ where
79
+ ${str};
80
+ `
81
+
82
+ const transaction = db.database.prepare(q)
83
+
84
+ try {
85
+ if (options?.recursive) {
86
+ const res = transaction.all(query.id) as unknown as any[]
87
+ return (buildBlockTree(res) as any) || null
88
+ }
89
+ return (
90
+ (transaction.get(...Object.values(query)) as unknown as DBBlock) || null
91
+ )
92
+ } catch (error) {
93
+ console.log('error')
94
+ return null
95
+ }
96
+ }
97
+
98
+ export const query = { blocks, block }
package/schemas.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { sql } from './utils/sql.ts'
2
+
3
+ // export const entriesTable = {
4
+ // tableName: 'entries',
5
+ // columns: sql`
6
+ // title TEXT not null, -- Title of the page
7
+ // slug TEXT not null unique, -- URL slug for the page
8
+ // meta_description TEXT -- Optional meta description for SEO
9
+ // `,
10
+ // }
11
+
12
+ // export const fieldTable = {
13
+ // tableName: 'fields',
14
+ // columns: sql`
15
+ // name TEXT not null, -- Name of the field (e.g., "content", "header", "image")
16
+ // type TEXT not null, -- Field type (e.g., "text", "image", "video")
17
+ // label TEXT not null, -- Field label (e.g., "Text", "Image", "Video")
18
+ // options TEXT -- Additional options or settings (can be a JSON string if needed)
19
+ // `,
20
+ // }
21
+
22
+ // export const entriesFieldsTable = {
23
+ // tableName: 'entry_fields',
24
+ // columns: sql`
25
+ // entry_id INTEGER not null, -- Foreign key to pages
26
+ // field_id INTEGER not null, -- Foreign key to fields
27
+ // position INTEGER, -- Optional: order of the field on the page
28
+ // content TEXT, -- Content of the field (e.g., text, image URL, etc.)
29
+ // foreign key (entry_id) references entries (id),
30
+ // foreign key (field_id) references fields (id)
31
+ // `,
32
+ // }
33
+
34
+ // export const entryTypeTable = {
35
+ // tableName: 'entry_types',
36
+ // columns: sql`
37
+ // name TEXT not null, -- Name of the field (e.g., "content", "header", "image")
38
+ // type TEXT not null, -- Field type (e.g., "text", "image", "video")
39
+ // label TEXT not null, -- Field label (e.g., "Text", "Image", "Video")
40
+ // options TEXT -- Additional options or settings (can be a JSON string if needed)
41
+ // `,
42
+ // }
43
+
44
+ // export const entryEntryTypeTable = {
45
+ // tableName: 'entry_entry_types',
46
+ // columns: sql`
47
+ // entry_id INTEGER not null, -- Foreign key to pages
48
+ // entry_type_id INTEGER not null, -- Foreign key to fields
49
+ // foreign key (entry_id) references entries (id),
50
+ // foreign key (entry_type_id) references entry_types (id)
51
+ // `,
52
+ // }
53
+
54
+ // -- Blocks
55
+ export const blocksTable = {
56
+ tableName: 'blocks',
57
+ columns: sql`
58
+ name TEXT not null,
59
+ label TEXT not null,
60
+ type TEXT not null,
61
+ sort_order INTEGER not null default 0,
62
+ value TEXT,
63
+ options JSON,
64
+ parent_block_id INTEGER,
65
+ foreign key (parent_block_id) references blocks (id)
66
+ `,
67
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ }
package/types.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { type HonoOptions } from "hono/hono-base"
2
+ import { type BlankEnv } from "hono/types"
3
+
4
+ export type Block = {
5
+ name: string
6
+ label: string
7
+ type: string
8
+ fields?: Block[]
9
+ }
10
+
11
+ export type Structure = Block[]
12
+
13
+ export type DBBlock = Block & {
14
+ id: number
15
+ created_at: string
16
+ updated_at: string
17
+ value: string | null
18
+ sort_order: number | null
19
+ parent_block_id: number | null
20
+ options: number | null
21
+ }
22
+
23
+ export type StudioConfig = {
24
+ honoConfig?: HonoOptions<BlankEnv>
25
+ }
@@ -0,0 +1,43 @@
1
+ import { type Block } from '../types.ts'
2
+ import { structure } from '../index.ts'
3
+
4
+ export type StructurePath = (string | number)[]
5
+
6
+ function getTargetPath(
7
+ target: Block,
8
+ path: StructurePath,
9
+ ): number | string | undefined {
10
+ if (!path.length) {
11
+ return structure.findIndex((block) => block.name === target.name)
12
+ }
13
+
14
+ let sub = structure
15
+
16
+ path.forEach((key: number | string) => {
17
+ if (sub) {
18
+ // @ts-ignore
19
+ sub = sub[key]
20
+ }
21
+ })
22
+
23
+ if (Array.isArray(sub)) {
24
+ return sub.findIndex((block) => block.name === target.name)
25
+ }
26
+
27
+ return
28
+ }
29
+
30
+ export const buildStructurePath = (
31
+ target: Block | undefined,
32
+ path: StructurePath = [],
33
+ ) => {
34
+ if (!target) return path
35
+
36
+ const targetPlacement = getTargetPath(target, path)
37
+
38
+ if (targetPlacement !== undefined) {
39
+ path.push(targetPlacement)
40
+ }
41
+
42
+ return path
43
+ }