@alstar/studio 0.0.0-beta.6 → 0.0.0-beta.8

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 (41) hide show
  1. package/api/api-key.ts +1 -2
  2. package/api/backup.ts +38 -0
  3. package/api/block.ts +63 -15
  4. package/api/index.ts +6 -3
  5. package/components/AdminPanel.ts +46 -24
  6. package/components/Backup.ts +10 -0
  7. package/components/{fields/Blocks.ts → BlockFieldRenderer.ts} +33 -30
  8. package/components/BlockRenderer.ts +22 -0
  9. package/components/Entries.ts +3 -2
  10. package/components/Entry.ts +13 -15
  11. package/components/FieldRenderer.ts +35 -0
  12. package/components/Render.ts +46 -0
  13. package/components/Settings.ts +3 -0
  14. package/components/{layout.ts → SiteLayout.ts} +8 -12
  15. package/components/fields/Markdown.ts +44 -0
  16. package/components/fields/Text.ts +10 -10
  17. package/components/fields/index.ts +2 -2
  18. package/index.ts +31 -28
  19. package/package.json +3 -3
  20. package/pages/entry/[id].ts +17 -0
  21. package/{components → pages}/index.ts +7 -4
  22. package/pages/settings.ts +10 -0
  23. package/public/{admin-panel.css → studio/admin-panel.css} +14 -1
  24. package/public/studio/main.css +162 -0
  25. package/public/studio/main.js +10 -0
  26. package/public/studio/markdown-editor.js +34 -0
  27. package/public/studio/sortable-list.js +40 -0
  28. package/queries/block.ts +2 -0
  29. package/types.ts +70 -32
  30. package/utils/define.ts +21 -9
  31. package/utils/file-based-router.ts +1 -0
  32. package/utils/get-or-create-row.ts +20 -7
  33. package/utils/startup-log.ts +2 -2
  34. package/bin/alstar.ts +0 -42
  35. package/components/Block.ts +0 -55
  36. package/public/main.css +0 -103
  37. package/public/main.js +0 -48
  38. /package/public/{blocks.css → studio/blocks.css} +0 -0
  39. /package/public/{entry.css → studio/entry.css} +0 -0
  40. /package/public/{favicon.svg → studio/favicon.svg} +0 -0
  41. /package/public/{settings.css → studio/settings.css} +0 -0
package/types.ts CHANGED
@@ -2,6 +2,11 @@ import { type HttpBindings } from '@hono/node-server'
2
2
  import { type Context } from 'hono'
3
3
  import { type HonoOptions } from 'hono/hono-base'
4
4
  import { type BlankInput, type BlankEnv } from 'hono/types'
5
+ import {
6
+ BlockFieldInstance,
7
+ BlockInstance,
8
+ FieldInstance,
9
+ } from './utils/define.ts'
5
10
 
6
11
  export type PrimitiveField = {
7
12
  name: string
@@ -25,71 +30,103 @@ export type Block = {
25
30
  fields: Record<string, Field | Block>
26
31
  }
27
32
 
28
- export type Structure = Record<string, BlockDef>
33
+ export type Structure = Record<string, BlockDefStructure>
29
34
 
30
35
  // --- Field & block definitions ---
31
- type FieldType = 'text' | 'slug' | 'markdown' | 'blocks' | 'image';
36
+ type FieldType = 'text' | 'slug' | 'markdown' | 'image'
32
37
 
33
38
  interface BaseField {
34
- label: string;
35
- type: FieldType;
39
+ label: string
40
+ type: FieldType
36
41
  description?: string
37
42
  }
38
43
 
39
44
  interface TextField extends BaseField {
40
- type: 'text' | 'slug' | 'markdown';
45
+ type: 'text' | 'slug' | 'markdown'
46
+ }
47
+
48
+ interface TextFieldStructure extends TextField {
49
+ instanceOf: typeof FieldInstance
41
50
  }
42
51
 
43
52
  interface ImageField extends BaseField {
44
- type: 'image';
53
+ type: 'image'
54
+ }
55
+
56
+ interface ImageFieldStructure extends ImageField {
57
+ instanceOf: typeof FieldInstance
58
+ }
59
+
60
+ export interface BlocksFieldDef {
61
+ label: string
62
+ type: 'blocks'
63
+ description?: string
64
+ children: Record<string, BlockDefStructure | FieldDefStructure>
45
65
  }
46
66
 
47
- export interface BlocksField extends BaseField {
48
- type: 'blocks';
49
- children: Record<string, BlockDef | FieldDef>;
67
+ export interface BlocksFieldDefStructure extends BlocksFieldDef {
68
+ instanceOf: typeof BlockFieldInstance
50
69
  }
51
70
 
52
- export type FieldDef = TextField | ImageField | BlocksField;
71
+ export type FieldDef = TextField | ImageField
72
+ export type FieldDefStructure = TextFieldStructure | ImageFieldStructure
53
73
 
54
74
  export interface BlockDef {
55
- label: string;
56
- type: string;
57
- fields: Record<string, FieldDef>;
75
+ label: string
76
+ type: string
77
+ fields: Record<string, FieldDefStructure | BlocksFieldDefStructure>
58
78
  description?: string
59
79
  }
60
80
 
61
- type DBDefaults = {
62
- id: number
63
- created_at: string
64
- updated_at: string
65
- name: string
66
- label: string
67
- // type: string
68
- sort_order: number
69
- value: string
70
- options: string | null
71
- status: 'enabled' | 'disabled'
72
- parent_id: number | null
73
- depth: number
81
+ export interface BlockDefStructure extends BlockDef {
82
+ instanceOf: typeof BlockInstance
74
83
  }
75
84
 
76
- export type DBBlockResult = {
85
+ // type DBDefaults = {
86
+ // id: number
87
+ // created_at: string
88
+ // updated_at: string
89
+ // name: string
90
+ // label: string
91
+ // // type: string
92
+ // sort_order: number
93
+ // value: string
94
+ // options: string | null
95
+ // status: 'enabled' | 'disabled'
96
+ // parent_id: number | null
97
+ // depth: number
98
+ // }
99
+
100
+ type BaseDBResult = {
77
101
  id: number
78
102
  created_at: string
79
103
  updated_at: string
80
104
  name: string
81
105
  label: string
82
- type: string
83
106
  sort_order: number
84
107
  value: string | null
85
108
  options: any
86
- status: string
109
+ status: 'enabled' | 'disabled'
87
110
  parent_id: number | null
88
111
  depth: number
89
- children?: DBBlockResult[]
90
- fields?: Record<string, DBBlockResult>
91
112
  }
92
113
 
114
+ export type DBPrimitiveFieldResult = BaseDBResult & {
115
+ type: FieldDef
116
+ }
117
+
118
+ export type DBBlockFieldResult = BaseDBResult & {
119
+ type: 'blocks'
120
+ children: DBBlockResult[]
121
+ }
122
+
123
+ export type DBBlockResult = BaseDBResult & {
124
+ type: string
125
+ fields: Record<string, DBFieldResult>
126
+ }
127
+
128
+ export type DBFieldResult = DBPrimitiveFieldResult & DBBlockFieldResult
129
+
93
130
  export type DBBlock = Block & {
94
131
  id: number
95
132
  created_at: string
@@ -103,8 +140,9 @@ export type DBBlock = Block & {
103
140
  export type BlockStatus = 'enabled' | 'disabled'
104
141
 
105
142
  export type StudioConfig = {
106
- siteName: string
143
+ siteName?: string
107
144
  honoConfig?: HonoOptions<BlankEnv>
145
+ port?: number
108
146
  structure: Structure
109
147
  }
110
148
 
package/utils/define.ts CHANGED
@@ -3,27 +3,39 @@ import { type HtmlEscapedString } from './html.ts'
3
3
 
4
4
  export const defineConfig = (config: types.StudioConfig) => config
5
5
 
6
- // export const defineStructure = (structure: types.Block[]) => structure
7
- // export const defineField = (field: types.Field) => field
8
- // export const defineBlock = (block: types.Block) => block
9
-
10
6
  export const defineEntry = (
11
7
  fn: (
12
8
  c: types.RequestContext,
13
9
  ) => HtmlEscapedString | Promise<HtmlEscapedString>,
14
10
  ) => fn
15
11
 
12
+ export const FieldInstance = Symbol('field')
13
+ export const BlockFieldInstance = Symbol('blockfield')
14
+ export const BlockInstance = Symbol('block')
15
+
16
16
  // --- Identity helpers (preserve literal types) ---
17
- export function defineField(field: types.FieldDef) {
18
- return field
17
+ export function defineField(field: types.FieldDef): types.FieldDefStructure {
18
+ return {
19
+ ...field,
20
+ instanceOf: FieldInstance,
21
+ }
22
+ }
23
+
24
+ export function defineBlockField(
25
+ field: types.BlocksFieldDef,
26
+ ): types.BlocksFieldDefStructure {
27
+ return {
28
+ ...field,
29
+ instanceOf: BlockFieldInstance,
30
+ }
19
31
  }
20
32
 
21
- export function defineBlock(block: types.BlockDef) {
22
- return block
33
+ export function defineBlock(block: types.BlockDef): types.BlockDefStructure {
34
+ return { ...block, instanceOf: BlockInstance }
23
35
  }
24
36
 
25
37
  export function defineStructure(
26
- structure: Record<string, types.BlockDef>,
38
+ structure: Record<string, types.BlockDefStructure>,
27
39
  ) {
28
40
  return structure
29
41
  }
@@ -15,6 +15,7 @@ export const fileBasedRouter = async (rootdir: string) => {
15
15
  try {
16
16
  dirs = await fs.readdir(root, { recursive: true })
17
17
  } catch (error) {
18
+ console.log('No files in:', root)
18
19
  return
19
20
  }
20
21
 
@@ -1,13 +1,26 @@
1
1
  import { db } from '@alstar/db'
2
2
  import { query } from '../queries/index.ts'
3
- import type { Block, FieldDef } from '../types.ts'
3
+ import type {
4
+ BlockDefStructure,
5
+ BlocksFieldDefStructure,
6
+ FieldDefStructure,
7
+ } from '../types.ts'
8
+
9
+ export function getOrCreateRow(props: {
10
+ parentId: string | number | null
11
+ name: string
12
+ field: BlockDefStructure | BlocksFieldDefStructure | FieldDefStructure
13
+ sortOrder?: number
14
+ id?: number
15
+ }) {
16
+ const { parentId, name, field, sortOrder = 0, id } = props
17
+
18
+ if (id) {
19
+ return query.block({
20
+ id: id?.toString(),
21
+ })
22
+ }
4
23
 
5
- export function getOrCreateRow(
6
- parentId: string | number,
7
- name: string,
8
- field: Block | FieldDef,
9
- sortOrder: number,
10
- ) {
11
24
  const data = query.block({
12
25
  parent_id: parentId?.toString() || null,
13
26
  name: name,
@@ -1,9 +1,9 @@
1
1
  import packageJSON from '../package.json' with { type: 'json' }
2
2
 
3
- export default () => {
3
+ export default ({ port }: { port: number }) => {
4
4
  console.log('\x1b[32m%s\x1b[0m', '╭───────────────────────╮')
5
5
  console.log('\x1b[32m%s\x1b[0m', '│ Alstar Studio │')
6
6
  console.log('\x1b[32m%s\x1b[0m', `│ ${packageJSON.version}${' '.repeat(22 - packageJSON.version.length)}│`)
7
- console.log('\x1b[32m%s\x1b[0m', `│ http://localhost:${3000} │`)
7
+ console.log('\x1b[32m%s\x1b[0m', `│ http://localhost:${port} │`)
8
8
  console.log('\x1b[32m%s\x1b[0m', '╰───────────────────────╯')
9
9
  }
package/bin/alstar.ts DELETED
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env node
2
- import { spawn } from 'node:child_process'
3
- import { fileURLToPath } from 'node:url'
4
- import path from 'node:path'
5
-
6
- // process.removeAllListeners('warning')
7
-
8
- const __filename = fileURLToPath(import.meta.url)
9
-
10
- // Dev-only auto watch (opt out with ALSTAR_NO_WATCH=true)
11
- const isDev =
12
- process.env.NODE_ENV !== 'production' &&
13
- process.env.ALSTAR_NO_WATCH !== 'true'
14
- const alreadyBootstrapped = process.env.ALSTAR_WATCH_BOOTSTRAPPED === '1'
15
-
16
- if (isDev && !alreadyBootstrapped) {
17
- // Start a watcher ON THIS FILE and mark the environment so the child won't re-spawn
18
- const childProcess = spawn(
19
- process.execPath,
20
- ['--watch', __filename, ...process.argv.slice(2)],
21
- // [__filename, ...process.argv.slice(2)],
22
- {
23
- stdio: 'inherit',
24
- env: { ...process.env, ALSTAR_WATCH_BOOTSTRAPPED: '1' },
25
- },
26
- )
27
- // Important: exit this launcher so only the watched child keeps running
28
- process.on('SIGINT', () => {
29
- childProcess.kill('SIGINT')
30
- })
31
-
32
- process.on('SIGTERM', () => {
33
- childProcess.kill('SIGTERM')
34
- })
35
-
36
- process.exit(0)
37
-
38
- }
39
-
40
- // --- Your actual CLI logic below ---
41
- // console.log(import.meta.dirname, path.resolve('.'))
42
- // import("./main.js").then(m => m.default());
@@ -1,55 +0,0 @@
1
- import { html } from 'hono/html'
2
- import type { Block, BlockDef, FieldDef } from '../types.ts'
3
- import { type HtmlEscapedString } from 'hono/utils/html'
4
- import { Field } from './fields/index.ts'
5
-
6
- export default (props: {
7
- entryId: number
8
- parentId: number
9
- blockStructure: BlockDef | Block | FieldDef
10
- name: string
11
- sortOrder?: number
12
- }): HtmlEscapedString | Promise<HtmlEscapedString> => {
13
- const { entryId, parentId, blockStructure, name, sortOrder = 0 } = props
14
-
15
- if (!blockStructure) return html`<p>No block</p>`
16
-
17
- let entries: [string, BlockDef | Block | FieldDef][] = []
18
-
19
- const fieldTypes = ['text', 'image', 'markdown', 'slug']
20
-
21
- try {
22
- if (fieldTypes.includes(blockStructure.type)) {
23
- entries = [[name, blockStructure]]
24
- } else if (blockStructure.type === 'blocks') {
25
- entries = Object.entries(blockStructure.children)
26
- } else if (blockStructure.fields) {
27
- entries = Object.entries(blockStructure.fields)
28
- } else {
29
- console.log(blockStructure)
30
- }
31
- } catch (error) {
32
- console.log(error)
33
- }
34
-
35
- return html`
36
- ${entries.map(([name, field]) => {
37
- switch (field.type) {
38
- case 'text': {
39
- return Field.Text({ entryId, parentId, name, field, sortOrder })
40
- }
41
- case 'slug': {
42
- return Field.Text({ entryId, parentId, name, field, sortOrder })
43
- }
44
- case 'markdown': {
45
- return Field.Text({ entryId, parentId, name, field, sortOrder })
46
- }
47
- case 'image': {
48
- return Field.Text({ entryId, parentId, name, field, sortOrder })
49
- }
50
- case 'blocks': {
51
- return Field.Blocks({ entryId, parentId, name, field, sortOrder })
52
- }
53
- }
54
- })}`
55
- }
package/public/main.css DELETED
@@ -1,103 +0,0 @@
1
- /* @import './../node_modules/@alstar/ui/red.css'; */
2
- @import 'https://esm.sh/@alstar/ui/red.css';
3
- @import './admin-panel.css';
4
- @import './entry.css';
5
- @import './blocks.css';
6
- @import './settings.css';
7
-
8
- body {
9
- min-height: 100vh;
10
- }
11
-
12
- body {
13
- padding: 0;
14
- min-height: inherit;
15
-
16
- display: grid;
17
- grid-template-columns: auto 1fr;
18
- align-content: baseline;
19
-
20
- > main {
21
- padding: 40px;
22
- height: 100vh;
23
- overflow: auto;
24
-
25
- section {
26
- max-width: 900px;
27
- margin: 0 auto;
28
- }
29
- }
30
- }
31
-
32
- .entry > .fields > .block {
33
- padding-block: var(--pico-spacing);
34
-
35
- > header {
36
- margin-block: var(--pico-block-spacing-vertical);
37
- }
38
- }
39
-
40
- .block {
41
- > header {
42
- margin-bottom: var(--pico-spacing);
43
- }
44
-
45
- header {
46
- display: flex;
47
- justify-content: space-between;
48
- align-items: center;
49
-
50
- background-color: inherit;
51
-
52
- h6,
53
- h5,
54
- fieldset {
55
- margin-bottom: 0;
56
- }
57
-
58
- button {
59
- padding: 0;
60
- background-color: white;
61
-
62
- svg {
63
- padding: 8px;
64
- }
65
- }
66
- }
67
- }
68
-
69
- [data-sortable] {
70
- > article {
71
- transition: opacity 200ms;
72
- }
73
-
74
- .sortable-ghost {
75
- opacity: 0.3;
76
- }
77
-
78
- /* .sortable-chosen {
79
- filter: brightness(0.9)
80
- } */
81
- }
82
-
83
- div.ink-mde {
84
- border: var(--pico-border-width) solid #2a3140;
85
- border-radius: var(--pico-border-radius);
86
- outline: 0;
87
- background-color: var(--pico-background-color);
88
- box-shadow: var(--pico-box-shadow);
89
- color: var(--pico-color);
90
- }
91
-
92
- .ink-mde-textarea {
93
- width: 100%;
94
- }
95
-
96
- .disclamer {
97
- display: flex;
98
- justify-content: center;
99
- }
100
-
101
- .text-secondary {
102
- color: var(--pico-secondary);
103
- }
package/public/main.js DELETED
@@ -1,48 +0,0 @@
1
- import barba from '@barba/core'
2
- import Sortable from 'sortablejs'
3
- // import { wrap } from 'ink-mde'
4
-
5
- barba.init({
6
- cacheIgnore: true,
7
- views: [
8
- {
9
- namespace: 'default',
10
- afterEnter() {
11
- init()
12
- },
13
- },
14
- ],
15
- })
16
-
17
- function init() {
18
- // sortable
19
- {
20
- const els = document.querySelectorAll('[data-sortable]')
21
-
22
- els.forEach((el) => {
23
- const id = el.dataset.sortable
24
-
25
- var sortable = Sortable.create(el, {
26
- delay: 0,
27
- animation: 250,
28
- dragClass: 'sortable-drag',
29
- easing: 'ease-in-out',
30
- handle: `[data-handle="${id}"]`,
31
- })
32
- })
33
- }
34
-
35
- // // ink-mde
36
- // {
37
- // const el = document.querySelector('textarea')
38
-
39
- // if (el) {
40
- // wrap(el, {
41
- // interface: {
42
- // // appearance: InkValues.Appearance.Auto,
43
- // attribution: false,
44
- // },
45
- // })
46
- // }
47
- // }
48
- }
File without changes
File without changes
File without changes
File without changes