@alstar/studio 0.0.0-beta.15 → 0.0.0-beta.18
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.
- package/api/block.ts +0 -14
- package/components/AdminPanel.ts +11 -5
- package/components/BlockFieldRenderer.ts +26 -20
- package/components/BlockRenderer.ts +4 -4
- package/components/Entries.ts +1 -1
- package/components/Entry.ts +13 -7
- package/components/FieldRenderer.ts +14 -8
- package/components/LivePreview.ts +37 -0
- package/components/Render.ts +8 -3
- package/components/SiteLayout.ts +1 -4
- package/components/fields/Markdown.ts +10 -3
- package/components/fields/Reference.ts +71 -0
- package/components/fields/Slug.ts +6 -6
- package/components/fields/Text.ts +13 -8
- package/components/fields/index.ts +2 -1
- package/components/icons.ts +3 -0
- package/components/settings/ApiKeys.ts +4 -4
- package/components/settings/Backup.ts +3 -3
- package/components/settings/Users.ts +1 -1
- package/index.ts +11 -10
- package/package.json +5 -6
- package/pages/entry/[id].ts +7 -1
- package/pages/error.ts +7 -6
- package/pages/login.ts +1 -1
- package/pages/register.ts +2 -2
- package/public/studio/css/admin-panel.css +27 -9
- package/public/studio/css/blocks-field.css +25 -0
- package/public/studio/css/entry-page.css +4 -0
- package/public/studio/css/entry.css +35 -0
- package/public/studio/css/field.css +14 -0
- package/public/studio/css/live-preview.css +25 -0
- package/public/studio/css/settings.css +4 -0
- package/public/studio/js/live-preview.js +26 -0
- package/public/studio/js/markdown-editor.js +6 -0
- package/public/studio/js/sortable-list.js +6 -4
- package/public/studio/main.css +11 -12
- package/public/studio/main.js +1 -0
- package/queries/block.ts +127 -105
- package/queries/index.ts +3 -2
- package/schemas.ts +1 -1
- package/types.ts +51 -75
- package/utils/define.ts +3 -1
- package/utils/get-or-create-row.ts +2 -1
- package/utils/refresher.ts +56 -0
- package/utils/renderSSE.ts +8 -3
- package/utils/startup-log.ts +4 -4
- package/queries/block-2.ts +0 -339
- package/queries/db-types.ts +0 -15
- package/queries/getBlockTrees-2.ts +0 -71
- package/queries/getBlocks.ts +0 -214
- package/queries/structure-types.ts +0 -97
- package/utils/buildBlocksTree.ts +0 -44
package/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { serveStatic } from '@hono/node-server/serve-static'
|
|
|
6
6
|
import { HTTPException } from 'hono/http-exception'
|
|
7
7
|
|
|
8
8
|
import { loadDb } from '@alstar/db'
|
|
9
|
-
import { createRefresher } from '@alstar/refresher'
|
|
10
9
|
|
|
11
10
|
import { createStudioTables } from './utils/create-studio-tables.ts'
|
|
12
11
|
import { fileBasedRouter } from './utils/file-based-router.ts'
|
|
@@ -20,21 +19,20 @@ import auth from './utils/auth.ts'
|
|
|
20
19
|
import ErrorPage from './pages/error.ts'
|
|
21
20
|
|
|
22
21
|
import * as types from './types.ts'
|
|
22
|
+
import { refresher, refreshClient } from './utils/refresher.ts'
|
|
23
23
|
|
|
24
24
|
export let rootdir = './node_modules/@alstar/studio'
|
|
25
25
|
|
|
26
26
|
export let studioStructure: types.Structure = {}
|
|
27
27
|
export let studioConfig: types.StudioConfig = {
|
|
28
28
|
siteName: '',
|
|
29
|
+
honoConfig: {},
|
|
29
30
|
fileBasedRouter: true,
|
|
30
31
|
port: 3000,
|
|
31
32
|
structure: {},
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
const createStudio = async (config: types.
|
|
35
|
-
// const refresher = await createRefresher({ rootdir: ['.', import.meta.dirname] })
|
|
36
|
-
const refresher = await createRefresher({ rootdir: '.' })
|
|
37
|
-
|
|
35
|
+
const createStudio = async (config: types.StudioConfigInput) => {
|
|
38
36
|
loadDb('./studio.db')
|
|
39
37
|
createStudioTables()
|
|
40
38
|
|
|
@@ -48,6 +46,8 @@ const createStudio = async (config: types.StudioConfig) => {
|
|
|
48
46
|
|
|
49
47
|
const app = new Hono(studioConfig.honoConfig)
|
|
50
48
|
|
|
49
|
+
app.get('/refresh', refresher({ root: '.', exclude: '.db' }))
|
|
50
|
+
|
|
51
51
|
/**
|
|
52
52
|
* Static folders
|
|
53
53
|
*/
|
|
@@ -104,8 +104,6 @@ const createStudio = async (config: types.StudioConfig) => {
|
|
|
104
104
|
}),
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
// console.log(app.routes)
|
|
108
|
-
|
|
109
107
|
/**
|
|
110
108
|
* Run server
|
|
111
109
|
*/
|
|
@@ -129,9 +127,12 @@ const createStudio = async (config: types.StudioConfig) => {
|
|
|
129
127
|
})
|
|
130
128
|
})
|
|
131
129
|
|
|
132
|
-
startupLog({ port: studioConfig.port
|
|
130
|
+
startupLog({ port: studioConfig.port })
|
|
133
131
|
|
|
134
|
-
return
|
|
132
|
+
return {
|
|
133
|
+
app,
|
|
134
|
+
refreshClient: refreshClient(studioConfig.port)
|
|
135
|
+
}
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
export {
|
|
@@ -144,6 +145,6 @@ export {
|
|
|
144
145
|
} from './utils/define.ts'
|
|
145
146
|
export { type RequestContext } from './types.ts'
|
|
146
147
|
export { createStudio }
|
|
147
|
-
export { html, type HtmlEscapedString } from './utils/html.ts'
|
|
148
|
+
export { html, raw, type HtmlEscapedString } from './utils/html.ts'
|
|
148
149
|
export { query } from './queries/index.ts'
|
|
149
150
|
export const version = packageJSON.version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alstar/studio",
|
|
3
|
-
"version": "0.0.0-beta.
|
|
3
|
+
"version": "0.0.0-beta.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
"node": ">=23.8"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@hono/node-server": "
|
|
13
|
+
"@hono/node-server": "1.18.1",
|
|
14
14
|
"@starfederation/datastar-sdk": "1.0.0-RC.1",
|
|
15
|
-
"hono": "
|
|
16
|
-
"@alstar/
|
|
17
|
-
"@alstar/
|
|
18
|
-
"@alstar/db": "0.0.0-beta.1"
|
|
15
|
+
"hono": "4.8.12",
|
|
16
|
+
"@alstar/db": "0.0.0-beta.1",
|
|
17
|
+
"@alstar/ui": "0.0.0-beta.4"
|
|
19
18
|
},
|
|
20
19
|
"devDependencies": {
|
|
21
20
|
"@types/node": "^24.1.0",
|
package/pages/entry/[id].ts
CHANGED
|
@@ -3,6 +3,7 @@ import { defineEntry } from '../../utils/define.ts'
|
|
|
3
3
|
|
|
4
4
|
import SiteLayout from '../../components/SiteLayout.ts'
|
|
5
5
|
import Entry from '../../components/Entry.ts'
|
|
6
|
+
import LivePreview from '../../components/LivePreview.ts'
|
|
6
7
|
|
|
7
8
|
export default defineEntry((c) => {
|
|
8
9
|
const id = c.req.param('id')
|
|
@@ -11,5 +12,10 @@ export default defineEntry((c) => {
|
|
|
11
12
|
return html`<p>Entry page url needs an ID param: "${id}"</p>`
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
return SiteLayout(
|
|
15
|
+
return SiteLayout(
|
|
16
|
+
html`<div class="entry-page">
|
|
17
|
+
${Entry({ entryId: id })}
|
|
18
|
+
${LivePreview({ entryId: id })}</div>
|
|
19
|
+
`
|
|
20
|
+
)
|
|
15
21
|
})
|
package/pages/error.ts
CHANGED
|
@@ -3,12 +3,13 @@ import SiteLayout from "../components/SiteLayout.ts";
|
|
|
3
3
|
import type { HTTPResponseError } from "hono/types";
|
|
4
4
|
|
|
5
5
|
export default ((err?: Error | HTTPResponseError) => {
|
|
6
|
-
|
|
7
6
|
return SiteLayout(html`
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
<div class="container">
|
|
8
|
+
<article style="margin: 100px">
|
|
9
|
+
<header>Something went wrong</header>
|
|
10
|
+
<p>Try again</p>
|
|
11
|
+
<p>${err?.message || '404 - Not found'}</p>
|
|
12
|
+
</article>
|
|
13
|
+
</div>
|
|
13
14
|
`, false)
|
|
14
15
|
})
|
package/pages/login.ts
CHANGED
|
@@ -7,7 +7,7 @@ export default defineEntry(c => {
|
|
|
7
7
|
<div class="login-form">
|
|
8
8
|
<article>
|
|
9
9
|
<header>Login</header>
|
|
10
|
-
<form data-on
|
|
10
|
+
<form data-on:submit="@post('/studio/api/auth/login', { contentType: 'form' })">
|
|
11
11
|
<label for="email">Email</label>
|
|
12
12
|
<input id="email" name="email" type="text" placeholder="Email">
|
|
13
13
|
<label for="password">Password</label>
|
package/pages/register.ts
CHANGED
|
@@ -10,8 +10,8 @@ export default defineEntry((c) => {
|
|
|
10
10
|
<header>Register user</header>
|
|
11
11
|
<form
|
|
12
12
|
data-signals="{ status: 0 }"
|
|
13
|
-
data-on
|
|
14
|
-
data-on
|
|
13
|
+
data-on:submit="@post('/studio/api/auth/register', { contentType: 'form' })"
|
|
14
|
+
data-on:signal-patch="patch.status === 200 && window.location.reload()"
|
|
15
15
|
>
|
|
16
16
|
<label for="email">Email</label>
|
|
17
17
|
<input id="email" name="email" type="text" placeholder="Email" style="width: 100%" />
|
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
.admin-panel {
|
|
2
|
-
/* background: hsla(0, 0%, 0%, 0.1); */
|
|
3
2
|
padding: 40px;
|
|
4
3
|
margin-bottom: 0;
|
|
5
4
|
display: flex;
|
|
6
5
|
flex-direction: column;
|
|
7
6
|
align-items: flex-start;
|
|
8
7
|
|
|
8
|
+
position: relative;
|
|
9
9
|
height: 100%;
|
|
10
10
|
min-height: inherit;
|
|
11
|
-
|
|
12
11
|
min-width: 250px;
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
padding-bottom: 1rem;
|
|
13
|
+
border-right: 1px solid var(--pico-muted-border-color);
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
/* .toggle-button {
|
|
16
|
+
position: absolute;
|
|
17
|
+
top: 0;
|
|
18
|
+
right: 0;
|
|
19
|
+
margin: 0.5rem;
|
|
20
|
+
padding: 0.2rem;
|
|
21
|
+
} */
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
header {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 1rem;
|
|
27
|
+
padding-bottom: 1rem;
|
|
28
|
+
|
|
29
|
+
.title {
|
|
30
|
+
margin: 0;
|
|
31
|
+
|
|
32
|
+
a {
|
|
33
|
+
display: flex;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
svg {
|
|
37
|
+
height: 1.6rem;
|
|
38
|
+
width: auto;
|
|
39
|
+
}
|
|
23
40
|
}
|
|
24
41
|
}
|
|
25
42
|
|
|
43
|
+
|
|
26
44
|
form {
|
|
27
45
|
padding-bottom: 1rem;
|
|
28
46
|
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
.blocks-field {
|
|
2
2
|
> header {
|
|
3
|
+
position: relative;
|
|
3
4
|
display: flex;
|
|
4
5
|
justify-content: space-between;
|
|
5
6
|
align-items: center;
|
|
6
7
|
|
|
8
|
+
position: sticky;
|
|
9
|
+
top: 0;
|
|
10
|
+
z-index: 3;
|
|
11
|
+
background: var(--pico-background-color);
|
|
12
|
+
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
13
|
+
|
|
14
|
+
padding: 1rem;
|
|
15
|
+
|
|
16
|
+
p {
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
> form fieldset {
|
|
8
21
|
button {
|
|
9
22
|
padding-inline: 1rem;
|
|
@@ -11,6 +24,8 @@
|
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
details {
|
|
27
|
+
margin: 0;
|
|
28
|
+
|
|
14
29
|
form {
|
|
15
30
|
display: flex;
|
|
16
31
|
}
|
|
@@ -28,6 +43,16 @@
|
|
|
28
43
|
}
|
|
29
44
|
}
|
|
30
45
|
|
|
46
|
+
> header:after {
|
|
47
|
+
content: '';
|
|
48
|
+
position: absolute;
|
|
49
|
+
background-color: var(--pico-background-color);
|
|
50
|
+
height: 100%;
|
|
51
|
+
width: 200%;
|
|
52
|
+
left: -50%;
|
|
53
|
+
z-index: -1;
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
article {
|
|
32
57
|
> header {
|
|
33
58
|
display: flex;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.entry{
|
|
2
|
+
height: 100vh;
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-rows: auto 1fr;
|
|
5
|
+
|
|
6
|
+
> header {
|
|
7
|
+
background-color: var(--pico-background-color);
|
|
8
|
+
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
9
|
+
padding: 1rem;
|
|
10
|
+
|
|
11
|
+
h1 {
|
|
12
|
+
font-size: 1rem;
|
|
13
|
+
font-weight: normal;
|
|
14
|
+
margin: 0;
|
|
15
|
+
font-family: var(--pico-font-family-monospace);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.content {
|
|
20
|
+
overflow: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
> .fields > .block {
|
|
24
|
+
padding: var(--pico-spacing);
|
|
25
|
+
|
|
26
|
+
> header {
|
|
27
|
+
margin-block: var(--pico-block-spacing-vertical);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.field {
|
|
32
|
+
padding-top: 1rem;
|
|
33
|
+
padding-inline: 1rem;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.live-preview {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
border-left: 1px solid var(--pico-muted-border-color);
|
|
5
|
+
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-rows: auto 1fr;
|
|
8
|
+
|
|
9
|
+
header {
|
|
10
|
+
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
11
|
+
padding: 1rem;
|
|
12
|
+
|
|
13
|
+
h1 {
|
|
14
|
+
font-size: 1rem;
|
|
15
|
+
font-weight: normal;
|
|
16
|
+
margin: 0;
|
|
17
|
+
font-family: var(--pico-font-family-monospace);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
iframe {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class LivePreview extends HTMLElement {
|
|
2
|
+
abortController = new AbortController()
|
|
3
|
+
mutationObserver
|
|
4
|
+
iframe
|
|
5
|
+
|
|
6
|
+
reload() {
|
|
7
|
+
this.iframe.contentWindow.location.reload()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
this.mutationObserver = new MutationObserver(this.reload.bind(this))
|
|
12
|
+
this.mutationObserver.observe(this, { attributes: true })
|
|
13
|
+
|
|
14
|
+
this.iframe = this.querySelector('iframe')
|
|
15
|
+
|
|
16
|
+
window.addEventListener('markdown-editor:update', this.reload.bind(this), { signal: this.abortController.signal })
|
|
17
|
+
window.addEventListener('sortable-list:update', this.reload.bind(this), { signal: this.abortController.signal })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
disconnectedCallback() {
|
|
21
|
+
this.abortController?.abort()
|
|
22
|
+
this.mutationObserver?.disconnect()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
customElements.define('live-preview', LivePreview)
|
|
@@ -11,11 +11,17 @@ class MarkdownEditor extends HTMLElement {
|
|
|
11
11
|
afterUpdate: async (e) => {
|
|
12
12
|
await fetch('/studio/api/value', {
|
|
13
13
|
method: 'PATCH',
|
|
14
|
+
headers: {
|
|
15
|
+
render: 'LivePreview',
|
|
16
|
+
props: this.dataset.entryId
|
|
17
|
+
},
|
|
14
18
|
body: JSON.stringify({
|
|
15
19
|
value: e,
|
|
16
20
|
id: this.dataset.id,
|
|
17
21
|
}),
|
|
18
22
|
})
|
|
23
|
+
|
|
24
|
+
window.dispatchEvent(new CustomEvent('markdown-editor:update'))
|
|
19
25
|
},
|
|
20
26
|
},
|
|
21
27
|
interface: {
|
|
@@ -13,23 +13,25 @@ class SortableList extends HTMLElement {
|
|
|
13
13
|
this.instance = Sortable.create(this, {
|
|
14
14
|
animation: 250,
|
|
15
15
|
handle: `[data-handle-for="${id}"]`,
|
|
16
|
-
onEnd: (e) => {
|
|
16
|
+
onEnd: async (e) => {
|
|
17
17
|
const items = [...e.target.children]
|
|
18
18
|
|
|
19
|
-
items.
|
|
19
|
+
await Promise.all(items.map(async (child, idx) => {
|
|
20
20
|
const searchParams = new URLSearchParams()
|
|
21
21
|
|
|
22
22
|
searchParams.set('id', child.dataset.id)
|
|
23
23
|
searchParams.set('sort-order', idx)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
return fetch(`/studio/api/sort-order?${searchParams.toString()}`, {
|
|
26
26
|
method: 'post',
|
|
27
27
|
})
|
|
28
|
-
})
|
|
28
|
+
}))
|
|
29
29
|
|
|
30
30
|
this.querySelectorAll('[name="sort_order"]').forEach((input, idx) => {
|
|
31
31
|
input.value = idx.toString()
|
|
32
32
|
})
|
|
33
|
+
|
|
34
|
+
window.dispatchEvent(new CustomEvent('sortable-list:update'))
|
|
33
35
|
},
|
|
34
36
|
})
|
|
35
37
|
}
|
package/public/studio/main.css
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
@import 'https://esm.sh/@alstar/ui/red.css';
|
|
2
|
+
/* @import '../node_modules/@alstar/ui/red.css'; */
|
|
2
3
|
@import './css/admin-panel.css';
|
|
3
4
|
@import './css/blocks-field.css';
|
|
4
5
|
@import './css/settings.css';
|
|
6
|
+
@import './css/live-preview.css';
|
|
7
|
+
@import './css/entry-page.css';
|
|
8
|
+
@import './css/entry.css';
|
|
9
|
+
@import './css/field.css';
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
--pico-font-size: 80%;
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
body {
|
|
7
16
|
padding: 0;
|
|
@@ -12,13 +21,11 @@ body {
|
|
|
12
21
|
align-content: baseline;
|
|
13
22
|
|
|
14
23
|
> main {
|
|
15
|
-
padding: 40px;
|
|
16
|
-
height: 100vh;
|
|
17
|
-
overflow: auto;
|
|
18
24
|
width: 100%;
|
|
25
|
+
height: 100vh;
|
|
26
|
+
padding: 0;
|
|
19
27
|
|
|
20
28
|
section {
|
|
21
|
-
max-width: 900px;
|
|
22
29
|
margin: 0 auto;
|
|
23
30
|
}
|
|
24
31
|
}
|
|
@@ -28,14 +35,6 @@ button svg, a svg {
|
|
|
28
35
|
pointer-events: none;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
.entry > .fields > .block {
|
|
32
|
-
padding-block: var(--pico-spacing);
|
|
33
|
-
|
|
34
|
-
> header {
|
|
35
|
-
margin-block: var(--pico-block-spacing-vertical);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
38
|
.block {
|
|
40
39
|
> header {
|
|
41
40
|
margin-bottom: var(--pico-spacing);
|