@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.
Files changed (58) hide show
  1. package/api/api-key.ts +73 -0
  2. package/api/backup.ts +38 -0
  3. package/api/block.ts +96 -30
  4. package/api/index.ts +12 -1
  5. package/api/mcp.ts +53 -0
  6. package/components/AdminPanel.ts +74 -0
  7. package/components/Backup.ts +10 -0
  8. package/components/BlockFieldRenderer.ts +121 -0
  9. package/components/BlockRenderer.ts +22 -0
  10. package/components/Entries.ts +19 -11
  11. package/components/Entry.ts +13 -21
  12. package/components/FieldRenderer.ts +35 -0
  13. package/components/Render.ts +46 -0
  14. package/components/Settings.ts +101 -0
  15. package/components/{layout.ts → SiteLayout.ts} +22 -17
  16. package/components/fields/Markdown.ts +44 -0
  17. package/components/fields/Text.ts +42 -0
  18. package/components/fields/index.ts +4 -0
  19. package/components/icons.ts +100 -7
  20. package/index.ts +66 -34
  21. package/package.json +8 -8
  22. package/pages/entry/[id].ts +17 -0
  23. package/{components → pages}/index.ts +7 -4
  24. package/pages/settings.ts +10 -0
  25. package/public/studio/css/admin-panel.css +103 -0
  26. package/public/studio/css/blocks-field.css +53 -0
  27. package/public/studio/css/settings.css +24 -0
  28. package/public/studio/js/markdown-editor.js +34 -0
  29. package/public/studio/js/sortable-list.js +50 -0
  30. package/public/studio/main.css +161 -0
  31. package/public/studio/main.js +9 -0
  32. package/queries/block-with-children.ts +74 -0
  33. package/queries/block.ts +289 -0
  34. package/queries/db-types.ts +15 -0
  35. package/queries/getBlockTrees-2.ts +0 -0
  36. package/queries/getBlockTrees.ts +316 -0
  37. package/queries/getBlocks.ts +214 -0
  38. package/queries/index.ts +2 -98
  39. package/queries/structure-types.ts +97 -0
  40. package/readme.md +205 -0
  41. package/schemas.ts +15 -54
  42. package/types.ts +133 -5
  43. package/utils/buildBlocksTree.ts +4 -4
  44. package/utils/define.ts +41 -0
  45. package/utils/file-based-router.ts +11 -2
  46. package/utils/get-config.ts +8 -9
  47. package/utils/get-or-create-row.ts +41 -0
  48. package/utils/html.ts +247 -0
  49. package/utils/startup-log.ts +10 -0
  50. package/components/AdminPanel/AdminPanel.css +0 -59
  51. package/components/AdminPanel/AdminPanel.ts +0 -57
  52. package/components/Block.ts +0 -116
  53. package/components/Entry.css +0 -7
  54. package/components/Field.ts +0 -164
  55. package/components/Fields.ts +0 -43
  56. package/public/main.css +0 -92
  57. package/public/main.js +0 -43
  58. /package/public/{favicon.svg → studio/favicon.svg} +0 -0
@@ -1,34 +1,26 @@
1
1
  import { html } from 'hono/html'
2
2
  import { query } from '../queries/index.ts'
3
- import { rootdir } from '../index.ts'
4
- import { type Structure } from '../types.ts'
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 | string; structure: Structure }) => {
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 blockStructure = props.structure.find(
14
- (block) => block.name === data.name,
15
- )
11
+ const structure = studioStructure[data.name]
16
12
 
17
- const structurePath = buildStructurePath(blockStructure)
13
+ if (!structure) return html`<p>No structure of type: ${data.name}</p>`
18
14
 
19
15
  return html`
20
- <div id="entry">
21
- <link rel="stylesheet" href="${rootdir}/components/Entry.css" />
22
-
23
- <div class="entry">
24
- ${blockStructure &&
25
- Fields({
26
- entryId: props.entryId,
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/AdminPanel.ts'
1
+ import adminPanel from './AdminPanel.ts'
2
2
  import { html } from 'hono/html'
3
3
  import { type HtmlEscapedString } from 'hono/utils/html'
4
- import { rootdir } from '../index.ts'
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>Alster CMS</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
- <script src="https://unpkg.com/@barba/core@2.10.3/dist/barba.umd.js"></script>
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(props.structure)}</section>
48
+ <section style="margin-bottom: 0;">${adminPanel()}</section>
42
49
 
43
- <main style="height: 100vh; overflow: auto">
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
+ }
@@ -0,0 +1,4 @@
1
+ import Text from './Text.ts'
2
+ import Markdown from './Markdown.ts'
3
+
4
+ export const Field = { Text, Markdown }
@@ -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.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"
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
- export let structure: types.Structure
18
- export let rootdir = '/node_modules/@alstar/studio'
25
+ const createStudio = async (config: types.StudioConfig) => {
26
+ const refresher = await createRefresher({ rootdir: '.' })
19
27
 
20
- const createStudio = async (studioStructure?: types.Structure) => {
21
- const config = await getConfig<types.StudioConfig>()
28
+ loadDb('./studio.db')
29
+ createStudioTables()
22
30
 
23
- structure = studioStructure || []
31
+ // const configFile = await getConfig<types.StudioConfig>()
24
32
 
25
- loadDb('./cms.db')
33
+ if (config.structure) {
34
+ studioStructure = config.structure
35
+ }
26
36
 
27
- createStudioTables()
37
+ studioConfig = { ...studioConfig, ...config }
28
38
 
29
- const app = new Hono(config.honoConfig)
39
+ const app = new Hono(studioConfig.honoConfig)
30
40
 
31
- app.use('*', serveStatic({ root: './' }))
41
+ /**
42
+ * Static folders
43
+ */
44
+ app.use('*', serveStatic({ root: path.join(rootdir, 'public') }))
32
45
  app.use('*', serveStatic({ root: './public' }))
33
46
 
34
- app.route('/', await fileBasedRouter('./pages'))
47
+ /**
48
+ * Studio API routes
49
+ */
50
+ app.route('/admin/api', api(studioStructure))
51
+ app.route('/admin/mcp', mcp())
35
52
 
36
- app.route('/admin/api', sectionRoutes(structure))
53
+ /**
54
+ * Studio pages
55
+ */
56
+ const adminPages = await fileBasedRouter(path.join(rootdir, 'pages'))
37
57
 
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
- })
58
+ if (adminPages) app.route('/admin', adminPages)
59
+
60
+ /**
61
+ * User pages
62
+ */
63
+ const pages = await fileBasedRouter('./pages')
47
64
 
48
- const server = serve(app)
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 const defineConfig = (config: types.StudioConfig) => config
69
- export const defineStructure = (structure: types.Structure) => structure
70
-
71
- export { createStudio, query }
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'