@alstar/studio 0.0.0-beta.1 → 0.0.0-beta.10
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/api-key.ts +73 -0
- package/api/backup.ts +38 -0
- package/api/block.ts +96 -30
- package/api/index.ts +12 -1
- package/api/mcp.ts +53 -0
- package/components/AdminPanel.ts +74 -0
- package/components/Backup.ts +10 -0
- package/components/BlockFieldRenderer.ts +121 -0
- package/components/BlockRenderer.ts +22 -0
- package/components/Entries.ts +19 -11
- package/components/Entry.ts +13 -21
- package/components/FieldRenderer.ts +35 -0
- package/components/Render.ts +46 -0
- package/components/Settings.ts +101 -0
- package/components/{layout.ts → SiteLayout.ts} +22 -17
- package/components/fields/Markdown.ts +44 -0
- package/components/fields/Text.ts +42 -0
- package/components/fields/index.ts +4 -0
- package/components/icons.ts +100 -7
- package/index.ts +66 -34
- package/package.json +8 -8
- package/pages/entry/[id].ts +17 -0
- package/{components → pages}/index.ts +7 -4
- package/pages/settings.ts +10 -0
- package/public/studio/css/admin-panel.css +103 -0
- package/public/studio/css/blocks-field.css +53 -0
- package/public/studio/css/settings.css +24 -0
- package/public/studio/js/markdown-editor.js +34 -0
- package/public/studio/js/sortable-list.js +50 -0
- package/public/studio/main.css +161 -0
- package/public/studio/main.js +9 -0
- package/queries/block-with-children.ts +74 -0
- package/queries/block.ts +289 -0
- package/queries/db-types.ts +15 -0
- package/queries/getBlockTrees-2.ts +0 -0
- package/queries/getBlockTrees.ts +316 -0
- package/queries/getBlocks.ts +214 -0
- package/queries/index.ts +2 -98
- package/queries/structure-types.ts +97 -0
- package/readme.md +205 -0
- package/schemas.ts +15 -54
- package/types.ts +133 -5
- package/utils/buildBlocksTree.ts +4 -4
- package/utils/define.ts +41 -0
- package/utils/file-based-router.ts +11 -2
- package/utils/get-config.ts +8 -9
- package/utils/get-or-create-row.ts +41 -0
- package/utils/html.ts +247 -0
- package/utils/startup-log.ts +10 -0
- package/components/AdminPanel/AdminPanel.css +0 -59
- package/components/AdminPanel/AdminPanel.ts +0 -57
- package/components/Block.ts +0 -116
- package/components/Entry.css +0 -7
- package/components/Field.ts +0 -164
- package/components/Fields.ts +0 -43
- package/public/main.css +0 -92
- package/public/main.js +0 -43
- /package/public/{favicon.svg → studio/favicon.svg} +0 -0
package/components/Entry.ts
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
import { html } from 'hono/html'
|
|
2
2
|
import { query } from '../queries/index.ts'
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import Fields from './Fields.ts'
|
|
6
|
-
import { buildStructurePath } from '../utils/build-structure-path.ts'
|
|
3
|
+
import { studioStructure } from '../index.ts'
|
|
4
|
+
import Render from './Render.ts'
|
|
7
5
|
|
|
8
|
-
export default (props: { entryId: number
|
|
6
|
+
export default (props: { entryId: number }) => {
|
|
9
7
|
const data = query.block({ id: props.entryId?.toString() })
|
|
10
8
|
|
|
11
9
|
if (!data) return html`<p>No entry with id: "${props.entryId}"</p>`
|
|
12
10
|
|
|
13
|
-
const
|
|
14
|
-
(block) => block.name === data.name,
|
|
15
|
-
)
|
|
11
|
+
const structure = studioStructure[data.name]
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
if (!structure) return html`<p>No structure of type: ${data.name}</p>`
|
|
18
14
|
|
|
19
15
|
return html`
|
|
20
|
-
<div id="entry">
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
parentId: props.entryId,
|
|
28
|
-
blockStructure,
|
|
29
|
-
structurePath,
|
|
30
|
-
})}
|
|
31
|
-
</div>
|
|
16
|
+
<div id="entry" class="entry">
|
|
17
|
+
${Render({
|
|
18
|
+
entryId: props.entryId,
|
|
19
|
+
parentId: props.entryId,
|
|
20
|
+
structure: structure,
|
|
21
|
+
name: data.name,
|
|
22
|
+
})}
|
|
32
23
|
</div>
|
|
24
|
+
|
|
33
25
|
`
|
|
34
26
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Field } from './fields/index.ts'
|
|
2
|
+
import type { BlocksFieldDefStructure, FieldDefStructure } from '../types.ts'
|
|
3
|
+
import BlockFieldRenderer from './BlockFieldRenderer.ts'
|
|
4
|
+
|
|
5
|
+
export default (props: {
|
|
6
|
+
entryId: number
|
|
7
|
+
parentId: number
|
|
8
|
+
structure: FieldDefStructure | BlocksFieldDefStructure
|
|
9
|
+
id?: number
|
|
10
|
+
name: string
|
|
11
|
+
}) => {
|
|
12
|
+
const { entryId, parentId, structure, name, id } = props
|
|
13
|
+
|
|
14
|
+
switch (structure.type) {
|
|
15
|
+
case 'text': {
|
|
16
|
+
return Field.Text({ entryId, parentId, name, id, structure })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
case 'slug': {
|
|
20
|
+
return Field.Text({ entryId, parentId, name, id, structure })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
case 'markdown': {
|
|
24
|
+
return Field.Markdown({ entryId, parentId, name, id, structure })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
case 'image': {
|
|
28
|
+
return Field.Text({ entryId, parentId, name, structure, id })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
case 'blocks': {
|
|
32
|
+
return BlockFieldRenderer({ entryId, parentId, name, structure, id })
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { html } from 'hono/html'
|
|
2
|
+
import type { HtmlEscapedString } from 'hono/utils/html'
|
|
3
|
+
import FieldRenderer from './FieldRenderer.ts'
|
|
4
|
+
import BlockFieldRenderer from './BlockFieldRenderer.ts'
|
|
5
|
+
import BlockRenderer from './BlockRenderer.ts'
|
|
6
|
+
import {
|
|
7
|
+
BlockFieldInstance,
|
|
8
|
+
BlockInstance,
|
|
9
|
+
FieldInstance,
|
|
10
|
+
} from '../utils/define.ts'
|
|
11
|
+
import type {
|
|
12
|
+
BlockDefStructure,
|
|
13
|
+
BlocksFieldDefStructure,
|
|
14
|
+
FieldDefStructure,
|
|
15
|
+
} from '../types.ts'
|
|
16
|
+
|
|
17
|
+
export default (props: {
|
|
18
|
+
entryId: number
|
|
19
|
+
parentId: number
|
|
20
|
+
structure: BlockDefStructure | FieldDefStructure | BlocksFieldDefStructure
|
|
21
|
+
id?: number
|
|
22
|
+
name: string
|
|
23
|
+
sortOrder?: number
|
|
24
|
+
}): HtmlEscapedString | Promise<HtmlEscapedString> => {
|
|
25
|
+
const { entryId, parentId, structure, name, id } = props
|
|
26
|
+
|
|
27
|
+
if (!structure) return html`<p>No block</p>`
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
switch (structure.instanceOf) {
|
|
31
|
+
case FieldInstance: {
|
|
32
|
+
return FieldRenderer({ entryId, parentId, id, structure, name })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
case BlockFieldInstance: {
|
|
36
|
+
return BlockFieldRenderer({ entryId, parentId, id, structure, name })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case BlockInstance: {
|
|
40
|
+
return BlockRenderer({ entryId, parentId, structure, id })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return html`<p>Error rendering "${name}"</p>`
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { db } from '@alstar/db'
|
|
2
|
+
import { html } from 'hono/html'
|
|
3
|
+
import { sql } from '../utils/sql.ts'
|
|
4
|
+
import * as icons from './icons.ts'
|
|
5
|
+
import Backup from './Backup.ts'
|
|
6
|
+
|
|
7
|
+
export default () => {
|
|
8
|
+
const apiKeys = db.database
|
|
9
|
+
.prepare(sql`
|
|
10
|
+
select
|
|
11
|
+
*
|
|
12
|
+
from
|
|
13
|
+
api_keys
|
|
14
|
+
`)
|
|
15
|
+
.all()
|
|
16
|
+
|
|
17
|
+
return html`
|
|
18
|
+
<div id="settings" data-signals="{ apiKey: '', copied: false }">
|
|
19
|
+
<!-- <h1>Settings</h1> -->
|
|
20
|
+
<article>
|
|
21
|
+
<header>API Keys</header>
|
|
22
|
+
|
|
23
|
+
<ul>
|
|
24
|
+
${apiKeys.map((apiKey) => {
|
|
25
|
+
return html`<li>
|
|
26
|
+
<p>${apiKey.name}</p>
|
|
27
|
+
<input type="text" disabled value="${apiKey.hint}" />
|
|
28
|
+
<form
|
|
29
|
+
data-on-submit="@delete('/admin/api/api-key', { contentType: 'form' })"
|
|
30
|
+
>
|
|
31
|
+
<button
|
|
32
|
+
data-tooltip="Delete API key"
|
|
33
|
+
data-placement="left"
|
|
34
|
+
type="submit"
|
|
35
|
+
class="ghost"
|
|
36
|
+
>
|
|
37
|
+
${icons.trash}
|
|
38
|
+
</button>
|
|
39
|
+
|
|
40
|
+
<input type="hidden" name="value" value="${apiKey.value}" />
|
|
41
|
+
</form>
|
|
42
|
+
</li>`
|
|
43
|
+
})}
|
|
44
|
+
</ul>
|
|
45
|
+
|
|
46
|
+
<form
|
|
47
|
+
data-on-submit="@post('/admin/api/api-key', { contentType: 'form' })"
|
|
48
|
+
>
|
|
49
|
+
<label for="api_key_name">Generate API Key</label>
|
|
50
|
+
|
|
51
|
+
<input
|
|
52
|
+
data-bind="name"
|
|
53
|
+
type="text"
|
|
54
|
+
name="name"
|
|
55
|
+
id="api_key_name"
|
|
56
|
+
placeholder="Name"
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<button type="submit" class="ghost">Generate key</button>
|
|
60
|
+
</form>
|
|
61
|
+
|
|
62
|
+
<dialog data-attr="{ open: $apiKey !== '' }">
|
|
63
|
+
<article>
|
|
64
|
+
<header>
|
|
65
|
+
<p>API Key</p>
|
|
66
|
+
</header>
|
|
67
|
+
<p>Be sure to save this key, as it wont be shown again.</p>
|
|
68
|
+
|
|
69
|
+
<div style="display: flex; gap: 1rem; align-items: center;">
|
|
70
|
+
<h3 style="margin: 0;">
|
|
71
|
+
<code data-text="$apiKey"></code>
|
|
72
|
+
</h3>
|
|
73
|
+
|
|
74
|
+
<button
|
|
75
|
+
style="display: flex; align-items: center;"
|
|
76
|
+
data-attr="{ id: $apiKey }"
|
|
77
|
+
data-on-click="navigator.clipboard.writeText($apiKey); $copied = true"
|
|
78
|
+
class="ghost"
|
|
79
|
+
aria-label="Copy key to clipboard"
|
|
80
|
+
>
|
|
81
|
+
${icons.clipboard}
|
|
82
|
+
<span style="display: none; margin-left: 0.5rem; color: green;" data-style="{ display: $copied && 'block' }">Copied</span>
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<footer>
|
|
87
|
+
<button
|
|
88
|
+
class="ghost"
|
|
89
|
+
data-on-click="$apiKey = ''; $copied = false; evt.target.closest('dialog')?.close()"
|
|
90
|
+
>
|
|
91
|
+
Close
|
|
92
|
+
</button>
|
|
93
|
+
</footer>
|
|
94
|
+
</article>
|
|
95
|
+
</dialog>
|
|
96
|
+
</article>
|
|
97
|
+
|
|
98
|
+
<!-- {Backup()} -->
|
|
99
|
+
</div>
|
|
100
|
+
`
|
|
101
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import adminPanel from './AdminPanel
|
|
1
|
+
import adminPanel from './AdminPanel.ts'
|
|
2
2
|
import { html } from 'hono/html'
|
|
3
3
|
import { type HtmlEscapedString } from 'hono/utils/html'
|
|
4
|
-
import {
|
|
5
|
-
import { type Structure } from '../types.ts'
|
|
4
|
+
import { studioConfig } from '../index.ts'
|
|
6
5
|
|
|
7
6
|
export default (props: {
|
|
8
7
|
content:
|
|
@@ -10,7 +9,6 @@ export default (props: {
|
|
|
10
9
|
| Promise<string>
|
|
11
10
|
| HtmlEscapedString
|
|
12
11
|
| Promise<HtmlEscapedString>
|
|
13
|
-
structure: Structure
|
|
14
12
|
}) => {
|
|
15
13
|
return html`
|
|
16
14
|
<!DOCTYPE html>
|
|
@@ -18,35 +16,42 @@ export default (props: {
|
|
|
18
16
|
<head>
|
|
19
17
|
<meta charset="UTF-8" />
|
|
20
18
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
21
|
-
<title>
|
|
19
|
+
<title>
|
|
20
|
+
${studioConfig.siteName ? studioConfig.siteName + ' | ' : ''}Alstar
|
|
21
|
+
Studio
|
|
22
|
+
</title>
|
|
22
23
|
|
|
23
|
-
<link
|
|
24
|
-
rel="icon"
|
|
25
|
-
type="image/svg"
|
|
26
|
-
href="${rootdir}/public/favicon.svg"
|
|
27
|
-
/>
|
|
24
|
+
<link rel="icon" type="image/svg" href="/studio/favicon.svg" />
|
|
28
25
|
|
|
29
26
|
<meta name="color-scheme" content="light dark" />
|
|
30
27
|
|
|
31
|
-
<link rel="stylesheet" href="${rootdir}/public/main.css" />
|
|
32
|
-
|
|
33
28
|
<script
|
|
34
29
|
type="module"
|
|
35
30
|
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"
|
|
36
31
|
></script>
|
|
37
|
-
|
|
32
|
+
|
|
33
|
+
<script type="importmap">
|
|
34
|
+
{
|
|
35
|
+
"imports": {
|
|
36
|
+
"@barba/core": "https://esm.sh/@barba/core@2.10.3/dist/barba.mjs",
|
|
37
|
+
"sortablejs": "https://esm.sh/sortablejs@1.15.6/modular/sortable.core.esm.js",
|
|
38
|
+
"ink-mde": "https://esm.sh/ink-mde@0.34.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<script src="/studio/main.js" type="module"></script>
|
|
44
|
+
<link href="/studio/main.css" rel="stylesheet" />
|
|
38
45
|
</head>
|
|
39
46
|
|
|
40
47
|
<body data-barba="wrapper">
|
|
41
|
-
<section>${adminPanel(
|
|
48
|
+
<section style="margin-bottom: 0;">${adminPanel()}</section>
|
|
42
49
|
|
|
43
|
-
<main
|
|
50
|
+
<main>
|
|
44
51
|
<section data-barba="container" data-barba-namespace="default">
|
|
45
52
|
${props.content}
|
|
46
53
|
</section>
|
|
47
54
|
</main>
|
|
48
|
-
|
|
49
|
-
<script src="${rootdir}/public/main.js" type="module"></script>
|
|
50
55
|
</body>
|
|
51
56
|
</html>
|
|
52
57
|
`
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { FieldDefStructure } from '../../types.ts'
|
|
2
|
+
import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
|
|
3
|
+
import { html } from '../../utils/html.ts'
|
|
4
|
+
|
|
5
|
+
export default (props: {
|
|
6
|
+
entryId: number
|
|
7
|
+
parentId: number
|
|
8
|
+
name: string
|
|
9
|
+
id?: number
|
|
10
|
+
structure: FieldDefStructure
|
|
11
|
+
}) => {
|
|
12
|
+
const { entryId, parentId, name, structure, id } = props
|
|
13
|
+
|
|
14
|
+
const data = getOrCreateRow({ parentId, name, field: structure, id })
|
|
15
|
+
|
|
16
|
+
if (!data) return html`<p>No block</p>`
|
|
17
|
+
|
|
18
|
+
return html`
|
|
19
|
+
<form
|
|
20
|
+
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
21
|
+
>
|
|
22
|
+
<hgroup>
|
|
23
|
+
<label for="block-${data.id}">${structure.label}</label>
|
|
24
|
+
<p><small>${structure.description}</small></p>
|
|
25
|
+
</hgroup>
|
|
26
|
+
|
|
27
|
+
<markdown-editor
|
|
28
|
+
data-content="${data.value?.trim()}"
|
|
29
|
+
data-id="${data.id}"
|
|
30
|
+
>
|
|
31
|
+
<!-- <textarea id="block-{data.id}" name="value" class="markdown">
|
|
32
|
+
{data.value}
|
|
33
|
+
</textarea
|
|
34
|
+
> -->
|
|
35
|
+
</markdown-editor>
|
|
36
|
+
|
|
37
|
+
<input type="hidden" name="type" value="${structure.type}" />
|
|
38
|
+
<input type="hidden" name="id" value="${data.id}" />
|
|
39
|
+
<input type="hidden" name="entryId" value="${entryId}" />
|
|
40
|
+
<input type="hidden" name="parentId" value="${parentId}" />
|
|
41
|
+
<input type="hidden" name="name" value="${name}" />
|
|
42
|
+
</form>
|
|
43
|
+
`
|
|
44
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
|
|
2
|
+
import { html } from '../../utils/html.ts'
|
|
3
|
+
import type { FieldDefStructure } from '../../types.ts'
|
|
4
|
+
|
|
5
|
+
export default (props: {
|
|
6
|
+
entryId: number
|
|
7
|
+
parentId: number
|
|
8
|
+
name: string
|
|
9
|
+
id?: number
|
|
10
|
+
structure: FieldDefStructure
|
|
11
|
+
sortOrder?: number
|
|
12
|
+
}) => {
|
|
13
|
+
const { entryId, parentId, name, structure, sortOrder = 0, id } = props
|
|
14
|
+
|
|
15
|
+
const data = getOrCreateRow({ parentId, name, field: structure, sortOrder, id })
|
|
16
|
+
|
|
17
|
+
if (!data) return html`<p>No block</p>`
|
|
18
|
+
|
|
19
|
+
return html`
|
|
20
|
+
<form
|
|
21
|
+
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
22
|
+
>
|
|
23
|
+
<hgroup>
|
|
24
|
+
<label for="block-${data.id}">${structure.label}</label>
|
|
25
|
+
<p><small>${structure.description}</small></p>
|
|
26
|
+
</hgroup>
|
|
27
|
+
|
|
28
|
+
<input
|
|
29
|
+
id="block-${data.id}"
|
|
30
|
+
name="value"
|
|
31
|
+
type="text"
|
|
32
|
+
value="${data.value}"
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<input type="hidden" name="type" value="${structure.type}" />
|
|
36
|
+
<input type="hidden" name="id" value="${data.id}" />
|
|
37
|
+
<input type="hidden" name="entryId" value="${entryId}" />
|
|
38
|
+
<input type="hidden" name="parentId" value="${parentId}" />
|
|
39
|
+
<input type="hidden" name="name" value="${name}" />
|
|
40
|
+
</form>
|
|
41
|
+
`
|
|
42
|
+
}
|
package/components/icons.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { html } from 'hono/html'
|
|
2
2
|
|
|
3
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
|
-
>
|
|
4
|
+
<svg viewBox="0 0 1332 342" fill="none">
|
|
9
5
|
<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.
|
|
11
|
-
fill="
|
|
6
|
+
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.6V98.288C834.752 95.5573 835.605 92.9973 837.312 90.608L897.728 4.59198C899.605 2.03198 902.336 0.751984 905.92 0.751984H997.568C1001.32 0.751984 1004.05 2.03198 1005.76 4.59198L1066.69 90.608C1068.39 92.9973 1069.25 95.5573 1069.25 98.288V335.6C1069.25 339.867 1067.11 342 1062.85 342H969.152C964.885 342 962.752 339.867 962.752 335.6V239.6H941.248V335.6C941.248 339.867 939.115 342 934.848 342H841.152ZM952 150.768C959.339 150.768 965.312 148.464 969.92 143.856C974.528 139.248 976.832 133.36 976.832 126.192C976.832 118.853 974.528 112.88 969.92 108.272C965.312 103.664 959.339 101.36 952 101.36C944.661 101.36 938.688 103.664 934.08 108.272C929.472 112.88 927.168 118.853 927.168 126.192C927.168 133.36 929.472 139.248 934.08 143.856C938.688 148.464 944.661 150.768 952 150.768ZM1097.15 342C1092.89 342 1090.75 339.867 1090.75 335.6V7.15198C1090.75 2.88531 1092.89 0.751984 1097.15 0.751984H1253.57C1258.01 0.751984 1261.42 1.86132 1263.81 4.07999L1322.69 62.448C1324.57 64.1547 1325.76 65.6053 1326.27 66.8C1326.78 67.9946 1327.04 69.9573 1327.04 72.688V142.832C1327.04 145.051 1325.59 147.611 1322.69 150.512L1287.36 185.84L1281.73 190.96L1297.6 214.768L1329.6 276.208C1330.45 277.744 1330.97 279.28 1331.14 280.816C1331.48 282.181 1331.65 283.973 1331.65 286.192V335.6C1331.65 339.867 1329.51 342 1325.25 342H1244.35C1240.6 342 1238.04 340.293 1236.67 336.88L1201.6 246.768H1197.25V335.6C1197.25 339.867 1195.11 342 1190.85 342H1097.15ZM1208.77 139.76C1215.77 139.76 1221.4 137.541 1225.66 133.104C1230.1 128.667 1232.32 123.035 1232.32 116.208C1232.32 109.381 1230.1 103.835 1225.66 99.568C1221.4 95.1307 1215.77 92.912 1208.77 92.912C1201.94 92.912 1196.31 95.1307 1191.87 99.568C1187.61 103.835 1185.47 109.381 1185.47 116.208C1185.47 123.035 1187.61 128.667 1191.87 133.104C1196.31 137.541 1201.94 139.76 1208.77 139.76Z"
|
|
7
|
+
fill="currentcolor"
|
|
12
8
|
/>
|
|
13
9
|
</svg>
|
|
14
10
|
`
|
|
@@ -101,3 +97,100 @@ export const newDocument = html`
|
|
|
101
97
|
/>
|
|
102
98
|
</svg>
|
|
103
99
|
`
|
|
100
|
+
|
|
101
|
+
export const dots = html`
|
|
102
|
+
<svg
|
|
103
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
104
|
+
width="16"
|
|
105
|
+
height="16"
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
>
|
|
108
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
109
|
+
<path
|
|
110
|
+
fill="none"
|
|
111
|
+
stroke="currentColor"
|
|
112
|
+
stroke-linecap="round"
|
|
113
|
+
stroke-linejoin="round"
|
|
114
|
+
stroke-width="1.5"
|
|
115
|
+
d="M6.75 12a.75.75 0 1 1-1.5 0a.75.75 0 0 1 1.5 0m6 0a.75.75 0 1 1-1.5 0a.75.75 0 0 1 1.5 0m6 0a.75.75 0 1 1-1.5 0a.75.75 0 0 1 1.5 0"
|
|
116
|
+
/>
|
|
117
|
+
</svg>
|
|
118
|
+
`
|
|
119
|
+
|
|
120
|
+
export const trash = html`
|
|
121
|
+
<svg
|
|
122
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
123
|
+
width="16"
|
|
124
|
+
height="16"
|
|
125
|
+
viewBox="0 0 24 24"
|
|
126
|
+
>
|
|
127
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
128
|
+
<path
|
|
129
|
+
fill="none"
|
|
130
|
+
stroke="currentColor"
|
|
131
|
+
stroke-linecap="round"
|
|
132
|
+
stroke-linejoin="round"
|
|
133
|
+
stroke-width="1.5"
|
|
134
|
+
d="m14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21q.512.078 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48 48 0 0 0-3.478-.397m-12 .562q.51-.088 1.022-.165m0 0a48 48 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a52 52 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a49 49 0 0 0-7.5 0"
|
|
135
|
+
/>
|
|
136
|
+
</svg>
|
|
137
|
+
`
|
|
138
|
+
|
|
139
|
+
export const x = html`
|
|
140
|
+
<svg
|
|
141
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
142
|
+
width="26"
|
|
143
|
+
height="26"
|
|
144
|
+
viewBox="0 0 24 24"
|
|
145
|
+
>
|
|
146
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
147
|
+
<path
|
|
148
|
+
fill="none"
|
|
149
|
+
stroke="currentColor"
|
|
150
|
+
stroke-linecap="round"
|
|
151
|
+
stroke-linejoin="round"
|
|
152
|
+
stroke-width="1.5"
|
|
153
|
+
d="M6 18L18 6M6 6l12 12"
|
|
154
|
+
/>
|
|
155
|
+
</svg>
|
|
156
|
+
`
|
|
157
|
+
|
|
158
|
+
export const cog = html`<svg
|
|
159
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
160
|
+
width="16"
|
|
161
|
+
height="16"
|
|
162
|
+
viewBox="0 0 24 24"
|
|
163
|
+
>
|
|
164
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
165
|
+
<g
|
|
166
|
+
fill="none"
|
|
167
|
+
stroke="currentColor"
|
|
168
|
+
stroke-linecap="round"
|
|
169
|
+
stroke-linejoin="round"
|
|
170
|
+
stroke-width="1.5"
|
|
171
|
+
>
|
|
172
|
+
<path
|
|
173
|
+
d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93c.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204s.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78c-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107c-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93c-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204s-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78c.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107s.71-.505.78-.929z"
|
|
174
|
+
/>
|
|
175
|
+
<path d="M15 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0" />
|
|
176
|
+
</g>
|
|
177
|
+
</svg>`
|
|
178
|
+
|
|
179
|
+
export const clipboard = html`
|
|
180
|
+
<svg
|
|
181
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
182
|
+
width="16"
|
|
183
|
+
height="16"
|
|
184
|
+
viewBox="0 0 24 24"
|
|
185
|
+
>
|
|
186
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
187
|
+
<path
|
|
188
|
+
fill="none"
|
|
189
|
+
stroke="currentColor"
|
|
190
|
+
stroke-linecap="round"
|
|
191
|
+
stroke-linejoin="round"
|
|
192
|
+
stroke-width="1.5"
|
|
193
|
+
d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0q.083.292.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0q.002-.32.084-.612m7.332 0q.969.073 1.927.184c1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48 48 0 0 1 1.927-.184"
|
|
194
|
+
/>
|
|
195
|
+
</svg>
|
|
196
|
+
`
|
package/index.ts
CHANGED
|
@@ -1,51 +1,73 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
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
3
|
import { serve } from '@hono/node-server'
|
|
13
4
|
import { serveStatic } from '@hono/node-server/serve-static'
|
|
5
|
+
import { createRefresher } from '@alstar/refresher'
|
|
6
|
+
|
|
7
|
+
import * as types from './types.ts'
|
|
14
8
|
import { createStudioTables } from './utils/create-studio-tables.ts'
|
|
15
9
|
import { fileBasedRouter } from './utils/file-based-router.ts'
|
|
10
|
+
import { getConfig } from './utils/get-config.ts'
|
|
11
|
+
import startupLog from './utils/startup-log.ts'
|
|
12
|
+
import { api } from './api/index.ts'
|
|
13
|
+
import mcp from './api/mcp.ts'
|
|
14
|
+
import path from 'path'
|
|
15
|
+
|
|
16
|
+
export let rootdir = './node_modules/@alstar/studio'
|
|
17
|
+
|
|
18
|
+
export let studioStructure: types.Structure = {}
|
|
19
|
+
export let studioConfig: types.StudioConfig = {
|
|
20
|
+
siteName: '',
|
|
21
|
+
port: 3000,
|
|
22
|
+
structure: {},
|
|
23
|
+
}
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
const createStudio = async (config: types.StudioConfig) => {
|
|
26
|
+
const refresher = await createRefresher({ rootdir: '.' })
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
loadDb('./studio.db')
|
|
29
|
+
createStudioTables()
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
// const configFile = await getConfig<types.StudioConfig>()
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
if (config.structure) {
|
|
34
|
+
studioStructure = config.structure
|
|
35
|
+
}
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
studioConfig = { ...studioConfig, ...config }
|
|
28
38
|
|
|
29
|
-
const app = new Hono(
|
|
39
|
+
const app = new Hono(studioConfig.honoConfig)
|
|
30
40
|
|
|
31
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Static folders
|
|
43
|
+
*/
|
|
44
|
+
app.use('*', serveStatic({ root: path.join(rootdir, 'public') }))
|
|
32
45
|
app.use('*', serveStatic({ root: './public' }))
|
|
33
46
|
|
|
34
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Studio API routes
|
|
49
|
+
*/
|
|
50
|
+
app.route('/admin/api', api(studioStructure))
|
|
51
|
+
app.route('/admin/mcp', mcp())
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Studio pages
|
|
55
|
+
*/
|
|
56
|
+
const adminPages = await fileBasedRouter(path.join(rootdir, 'pages'))
|
|
37
57
|
|
|
38
|
-
app.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}),
|
|
45
|
-
)
|
|
46
|
-
})
|
|
58
|
+
if (adminPages) app.route('/admin', adminPages)
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* User pages
|
|
62
|
+
*/
|
|
63
|
+
const pages = await fileBasedRouter('./pages')
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
if (pages) app.route('/', pages)
|
|
66
|
+
|
|
67
|
+
const server = serve({
|
|
68
|
+
fetch: app.fetch,
|
|
69
|
+
port: studioConfig.port,
|
|
70
|
+
})
|
|
49
71
|
|
|
50
72
|
// graceful shutdown
|
|
51
73
|
process.on('SIGINT', () => {
|
|
@@ -62,10 +84,20 @@ const createStudio = async (studioStructure?: types.Structure) => {
|
|
|
62
84
|
})
|
|
63
85
|
})
|
|
64
86
|
|
|
87
|
+
startupLog({ port: studioConfig.port || 3000, refresherPort: refresher.port })
|
|
88
|
+
|
|
65
89
|
return app
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
export
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
export {
|
|
93
|
+
defineConfig,
|
|
94
|
+
defineEntry,
|
|
95
|
+
defineStructure,
|
|
96
|
+
defineBlock,
|
|
97
|
+
defineField,
|
|
98
|
+
defineBlockField,
|
|
99
|
+
} from './utils/define.ts'
|
|
100
|
+
export { type RequestContext } from './types.ts'
|
|
101
|
+
export { createStudio }
|
|
102
|
+
export { html, type HtmlEscapedString } from './utils/html.ts'
|
|
103
|
+
export { query } from './queries/index.ts'
|