@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
package/types.ts CHANGED
@@ -1,14 +1,131 @@
1
- import { type HonoOptions } from "hono/hono-base"
2
- import { type BlankEnv } from "hono/types"
1
+ import { type HttpBindings } from '@hono/node-server'
2
+ import { type Context } from 'hono'
3
+ import { type HonoOptions } from 'hono/hono-base'
4
+ import { type BlankInput, type BlankEnv } from 'hono/types'
5
+ import {
6
+ BlockFieldInstance,
7
+ BlockInstance,
8
+ FieldInstance,
9
+ } from './utils/define.ts'
10
+
11
+ export type PrimitiveField = {
12
+ name: string
13
+ label: string
14
+ type: 'text' | 'slug' | 'markdown' | 'image'
15
+ }
16
+
17
+ export type BlockField = {
18
+ name: string
19
+ label: string
20
+ type: 'blocks'
21
+ children: Record<string, Field | Block>
22
+ }
23
+
24
+ export type Field = PrimitiveField | BlockField
3
25
 
4
26
  export type Block = {
5
27
  name: string
6
28
  label: string
7
29
  type: string
8
- fields?: Block[]
30
+ fields: Record<string, Field | Block>
31
+ }
32
+
33
+ export type Structure = Record<string, BlockDefStructure>
34
+
35
+ // --- Field & block definitions ---
36
+ type FieldType = 'text' | 'slug' | 'markdown' | 'image'
37
+
38
+ interface BaseField {
39
+ label: string
40
+ type: FieldType
41
+ description?: string
42
+ }
43
+
44
+ interface TextField extends BaseField {
45
+ type: 'text' | 'slug' | 'markdown'
46
+ }
47
+
48
+ interface TextFieldStructure extends TextField {
49
+ instanceOf: typeof FieldInstance
50
+ }
51
+
52
+ interface ImageField extends BaseField {
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>
65
+ }
66
+
67
+ export interface BlocksFieldDefStructure extends BlocksFieldDef {
68
+ instanceOf: typeof BlockFieldInstance
69
+ }
70
+
71
+ export type FieldDef = TextField | ImageField
72
+ export type FieldDefStructure = TextFieldStructure | ImageFieldStructure
73
+
74
+ export interface BlockDef {
75
+ label: string
76
+ type: string
77
+ fields: Record<string, FieldDefStructure | BlocksFieldDefStructure>
78
+ description?: string
79
+ }
80
+
81
+ export interface BlockDefStructure extends BlockDef {
82
+ instanceOf: typeof BlockInstance
83
+ }
84
+
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 = {
101
+ id: number
102
+ created_at: string
103
+ updated_at: string
104
+ name: string
105
+ label: string
106
+ sort_order: number
107
+ value: string | null
108
+ options: any
109
+ status: 'enabled' | 'disabled'
110
+ parent_id: number | null
111
+ depth: number
112
+ }
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>
9
126
  }
10
127
 
11
- export type Structure = Block[]
128
+ export type DBFieldResult = DBPrimitiveFieldResult & DBBlockFieldResult
12
129
 
13
130
  export type DBBlock = Block & {
14
131
  id: number
@@ -16,10 +133,21 @@ export type DBBlock = Block & {
16
133
  updated_at: string
17
134
  value: string | null
18
135
  sort_order: number | null
19
- parent_block_id: number | null
136
+ parent_id: number | null
20
137
  options: number | null
21
138
  }
22
139
 
140
+ export type BlockStatus = 'enabled' | 'disabled'
141
+
23
142
  export type StudioConfig = {
143
+ siteName?: string
24
144
  honoConfig?: HonoOptions<BlankEnv>
145
+ port?: number
146
+ structure: Structure
25
147
  }
148
+
149
+ export type RequestContext = Context<
150
+ { Bindings: HttpBindings },
151
+ string,
152
+ BlankInput
153
+ >
@@ -6,7 +6,7 @@ type Block = {
6
6
  sort_order: number
7
7
  value: string | null
8
8
  options: any // JSON-parsed if necessary
9
- parent_block_id: number | null
9
+ parent_id: number | null
10
10
  depth: number
11
11
  // ... you can add other fields if needed
12
12
  }
@@ -26,13 +26,13 @@ export function buildBlockTree(blocks: Block[]): BlockWithChildren {
26
26
  for (const block of blocks) {
27
27
  const current = blockMap.get(block.id)!
28
28
 
29
- if (block.parent_block_id != null) {
30
- const parent = blockMap.get(block.parent_block_id)
29
+ if (block.parent_id != null) {
30
+ const parent = blockMap.get(block.parent_id)
31
31
  if (parent) {
32
32
  parent.fields.push(current)
33
33
  } else {
34
34
  console.warn(
35
- `Parent with id ${block.parent_block_id} not found for block ${block.id}`,
35
+ `Parent with id ${block.parent_id} not found for block ${block.id}`,
36
36
  )
37
37
  }
38
38
  } else {
@@ -0,0 +1,41 @@
1
+ import * as types from '../types.ts'
2
+ import { type HtmlEscapedString } from './html.ts'
3
+
4
+ export const defineConfig = (config: types.StudioConfig) => config
5
+
6
+ export const defineEntry = (
7
+ fn: (
8
+ c: types.RequestContext,
9
+ ) => HtmlEscapedString | Promise<HtmlEscapedString>,
10
+ ) => fn
11
+
12
+ export const FieldInstance = Symbol('field')
13
+ export const BlockFieldInstance = Symbol('blockfield')
14
+ export const BlockInstance = Symbol('block')
15
+
16
+ // --- Identity helpers (preserve literal types) ---
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
+ }
31
+ }
32
+
33
+ export function defineBlock(block: types.BlockDef): types.BlockDefStructure {
34
+ return { ...block, instanceOf: BlockInstance }
35
+ }
36
+
37
+ export function defineStructure(
38
+ structure: Record<string, types.BlockDefStructure>,
39
+ ) {
40
+ return structure
41
+ }
@@ -3,13 +3,22 @@ import path from 'node:path'
3
3
  import { Hono } from 'hono'
4
4
  import { type HtmlEscapedString } from 'hono/utils/html'
5
5
 
6
- type Page<C> = (c?: C) => HtmlEscapedString | Promise<HtmlEscapedString>
6
+ export type Page<C> = (c?: C) => HtmlEscapedString | Promise<HtmlEscapedString>
7
7
 
8
8
  export const fileBasedRouter = async (rootdir: string) => {
9
9
  const router = new Hono()
10
10
 
11
11
  const root = path.resolve(rootdir)
12
- const dirs = await fs.readdir(root, { recursive: true })
12
+
13
+ let dirs
14
+
15
+ try {
16
+ dirs = await fs.readdir(root, { recursive: true })
17
+ } catch (error) {
18
+ console.log('No files in:', root)
19
+ return
20
+ }
21
+
13
22
  const files = dirs.filter((dir) => path.extname(dir))
14
23
 
15
24
  await Promise.all(
@@ -3,15 +3,6 @@ import path from 'node:path'
3
3
 
4
4
  const CONFIG_FILE_NAME = 'alstar.config.ts'
5
5
 
6
- async function fileExists(filepath: string) {
7
- // does the file exist?
8
- try {
9
- await fs.stat(filepath)
10
- } catch (error) {
11
- return {}
12
- }
13
- }
14
-
15
6
  export const getConfig = async <P>(): Promise<P> => {
16
7
  const root = path.resolve('./')
17
8
 
@@ -24,3 +15,11 @@ export const getConfig = async <P>(): Promise<P> => {
24
15
 
25
16
  return config as P
26
17
  }
18
+
19
+ async function fileExists(filepath: string) {
20
+ try {
21
+ return await fs.stat(filepath)
22
+ } catch (error) {
23
+ return null
24
+ }
25
+ }
@@ -0,0 +1,41 @@
1
+ import { db } from '@alstar/db'
2
+ import { query } from '../queries/index.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
+ }
23
+
24
+ const data = query.block({
25
+ parent_id: parentId?.toString() || null,
26
+ name: name,
27
+ sort_order: sortOrder.toString(),
28
+ })
29
+
30
+ if (data) return data
31
+
32
+ const change = db.insertInto('blocks', {
33
+ name: name?.toString(),
34
+ label: field.label?.toString(),
35
+ type: field.type?.toString(),
36
+ sort_order: sortOrder,
37
+ parent_id: parentId,
38
+ })
39
+
40
+ return query.block({ id: change.lastInsertRowid.toString() })
41
+ }
package/utils/html.ts ADDED
@@ -0,0 +1,247 @@
1
+ export const HtmlEscapedCallbackPhase = {
2
+ Stringify: 1,
3
+ BeforeStream: 2,
4
+ Stream: 3,
5
+ } as const
6
+ type HtmlEscapedCallbackOpts = {
7
+ buffer?: [string]
8
+ phase: (typeof HtmlEscapedCallbackPhase)[keyof typeof HtmlEscapedCallbackPhase]
9
+ context: Readonly<object> // An object unique to each JSX tree. This object is used as the WeakMap key.
10
+ }
11
+ export type HtmlEscapedCallback = (
12
+ opts: HtmlEscapedCallbackOpts,
13
+ ) => Promise<string> | undefined
14
+ export type HtmlEscaped = {
15
+ isEscaped: true
16
+ callbacks?: HtmlEscapedCallback[]
17
+ }
18
+ export type HtmlEscapedString = string & HtmlEscaped
19
+
20
+ /**
21
+ * StringBuffer contains string and Promise<string> alternately
22
+ * The length of the array will be odd, the odd numbered element will be a string,
23
+ * and the even numbered element will be a Promise<string>.
24
+ * When concatenating into a single string, it must be processed from the tail.
25
+ * @example
26
+ * [
27
+ * 'framework.',
28
+ * Promise.resolve('ultra fast'),
29
+ * 'a ',
30
+ * Promise.resolve('is '),
31
+ * 'Hono',
32
+ * ]
33
+ */
34
+ export type StringBuffer = (string | Promise<string>)[]
35
+ export type StringBufferWithCallbacks = StringBuffer & {
36
+ callbacks: HtmlEscapedCallback[]
37
+ }
38
+
39
+ export const raw = (
40
+ value: unknown,
41
+ callbacks?: HtmlEscapedCallback[],
42
+ ): HtmlEscapedString => {
43
+ const escapedString = new String(value) as HtmlEscapedString
44
+ escapedString.isEscaped = true
45
+ escapedString.callbacks = callbacks
46
+
47
+ return escapedString
48
+ }
49
+
50
+ // The `escapeToBuffer` implementation is based on code from the MIT licensed `react-dom` package.
51
+ // https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/server/escapeTextForBrowser.js
52
+
53
+ const escapeRe = /[&<>'"]/
54
+
55
+ export const stringBufferToString = async (
56
+ buffer: StringBuffer,
57
+ callbacks: HtmlEscapedCallback[] | undefined,
58
+ ): Promise<HtmlEscapedString> => {
59
+ let str = ''
60
+ callbacks ||= []
61
+ const resolvedBuffer = await Promise.all(buffer)
62
+ for (let i = resolvedBuffer.length - 1; ; i--) {
63
+ str += resolvedBuffer[i]
64
+ i--
65
+ if (i < 0) {
66
+ break
67
+ }
68
+
69
+ let r = resolvedBuffer[i]
70
+ if (typeof r === 'object') {
71
+ callbacks.push(...((r as HtmlEscapedString).callbacks || []))
72
+ }
73
+
74
+ const isEscaped = (r as HtmlEscapedString).isEscaped
75
+ r = await (typeof r === 'object' ? (r as HtmlEscapedString).toString() : r)
76
+ if (typeof r === 'object') {
77
+ callbacks.push(...((r as HtmlEscapedString).callbacks || []))
78
+ }
79
+
80
+ if ((r as HtmlEscapedString).isEscaped ?? isEscaped) {
81
+ str += r
82
+ } else {
83
+ const buf = [str]
84
+ escapeToBuffer(r, buf)
85
+ str = buf[0]
86
+ }
87
+ }
88
+
89
+ return raw(str, callbacks)
90
+ }
91
+
92
+ export const escapeToBuffer = (str: string, buffer: StringBuffer): void => {
93
+ const match = str.search(escapeRe)
94
+ if (match === -1) {
95
+ buffer[0] += str
96
+ return
97
+ }
98
+
99
+ let escape
100
+ let index
101
+ let lastIndex = 0
102
+
103
+ for (index = match; index < str.length; index++) {
104
+ switch (str.charCodeAt(index)) {
105
+ case 34: // "
106
+ escape = '&quot;'
107
+ break
108
+ case 39: // '
109
+ escape = '&#39;'
110
+ break
111
+ case 38: // &
112
+ escape = '&amp;'
113
+ break
114
+ case 60: // <
115
+ escape = '&lt;'
116
+ break
117
+ case 62: // >
118
+ escape = '&gt;'
119
+ break
120
+ default:
121
+ continue
122
+ }
123
+
124
+ buffer[0] += str.substring(lastIndex, index) + escape
125
+ lastIndex = index + 1
126
+ }
127
+
128
+ buffer[0] += str.substring(lastIndex, index)
129
+ }
130
+
131
+ export const resolveCallbackSync = (
132
+ str: string | HtmlEscapedString,
133
+ ): string => {
134
+ const callbacks = (str as HtmlEscapedString)
135
+ .callbacks as HtmlEscapedCallback[]
136
+ if (!callbacks?.length) {
137
+ return str
138
+ }
139
+ const buffer: [string] = [str]
140
+ const context = {}
141
+
142
+ callbacks.forEach((c) =>
143
+ c({ phase: HtmlEscapedCallbackPhase.Stringify, buffer, context }),
144
+ )
145
+
146
+ return buffer[0]
147
+ }
148
+
149
+ export const resolveCallback = async (
150
+ str: string | HtmlEscapedString | Promise<string>,
151
+ phase: (typeof HtmlEscapedCallbackPhase)[keyof typeof HtmlEscapedCallbackPhase],
152
+ preserveCallbacks: boolean,
153
+ context: object,
154
+ buffer?: [string],
155
+ ): Promise<string> => {
156
+ if (typeof str === 'object' && !(str instanceof String)) {
157
+ if (!((str as unknown) instanceof Promise)) {
158
+ str = (str as unknown as string).toString() // HtmlEscapedString object to string
159
+ }
160
+ if ((str as string | Promise<string>) instanceof Promise) {
161
+ str = await (str as unknown as Promise<string>)
162
+ }
163
+ }
164
+
165
+ const callbacks = (str as HtmlEscapedString)
166
+ .callbacks as HtmlEscapedCallback[]
167
+ if (!callbacks?.length) {
168
+ return Promise.resolve(str)
169
+ }
170
+ if (buffer) {
171
+ buffer[0] += str
172
+ } else {
173
+ buffer = [str as string]
174
+ }
175
+
176
+ const resStr = Promise.all(
177
+ callbacks.map((c) => c({ phase, buffer, context })),
178
+ ).then((res) =>
179
+ Promise.all(
180
+ res
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ .filter<string>(Boolean as any)
183
+ .map((str) => resolveCallback(str, phase, false, context, buffer)),
184
+ ).then(() => (buffer as [string])[0]),
185
+ )
186
+
187
+ if (preserveCallbacks) {
188
+ return raw(await resStr, callbacks)
189
+ } else {
190
+ return resStr
191
+ }
192
+ }
193
+
194
+ export const html = (
195
+ strings: TemplateStringsArray,
196
+ ...values: unknown[]
197
+ ): HtmlEscapedString | Promise<HtmlEscapedString> => {
198
+ const buffer: StringBufferWithCallbacks = [''] as StringBufferWithCallbacks
199
+
200
+ for (let i = 0, len = strings.length - 1; i < len; i++) {
201
+ buffer[0] += strings[i]
202
+
203
+ const children = Array.isArray(values[i])
204
+ ? (values[i] as Array<unknown>).flat(Infinity)
205
+ : [values[i]]
206
+ for (let i = 0, len = children.length; i < len; i++) {
207
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
+ const child = children[i] as any
209
+ if (typeof child === 'string') {
210
+ escapeToBuffer(child, buffer)
211
+ } else if (typeof child === 'number') {
212
+ ;(buffer[0] as string) += child
213
+ } else if (
214
+ typeof child === 'boolean' ||
215
+ child === null ||
216
+ child === undefined
217
+ ) {
218
+ continue
219
+ } else if (
220
+ typeof child === 'object' &&
221
+ (child as HtmlEscaped).isEscaped
222
+ ) {
223
+ if ((child as HtmlEscapedString).callbacks) {
224
+ buffer.unshift('', child)
225
+ } else {
226
+ const tmp = child.toString()
227
+ if (tmp instanceof Promise) {
228
+ buffer.unshift('', tmp)
229
+ } else {
230
+ buffer[0] += tmp
231
+ }
232
+ }
233
+ } else if (child instanceof Promise) {
234
+ buffer.unshift('', child)
235
+ } else {
236
+ escapeToBuffer(child.toString(), buffer)
237
+ }
238
+ }
239
+ }
240
+ buffer[0] += strings.at(-1) as string
241
+
242
+ return buffer.length === 1
243
+ ? 'callbacks' in buffer
244
+ ? raw(resolveCallbackSync(raw(buffer[0], buffer.callbacks)))
245
+ : raw(buffer[0])
246
+ : stringBufferToString(buffer, buffer.callbacks)
247
+ }
@@ -0,0 +1,10 @@
1
+ import packageJSON from '../package.json' with { type: 'json' }
2
+
3
+ export default ({ port, refresherPort }: { port: number, refresherPort: number }) => {
4
+ console.log('\x1b[32m%s\x1b[0m', '╭───────────────────────╮')
5
+ console.log('\x1b[32m%s\x1b[0m', '│ Alstar Studio │')
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:${port} │`)
8
+ console.log('\x1b[32m%s\x1b[0m', `│ Refresher port: ${refresherPort} │`)
9
+ console.log('\x1b[32m%s\x1b[0m', '╰───────────────────────╯')
10
+ }
@@ -1,59 +0,0 @@
1
- .admin-panel {
2
- /* background: hsla(0, 0%, 0%, 0.1); */
3
- padding: 40px;
4
- padding-right: 0;
5
-
6
- height: 100%;
7
- min-height: inherit;
8
-
9
- min-width: 200px;
10
-
11
- > h1 {
12
- padding-bottom: 1rem;
13
-
14
- a {
15
- display: flex;
16
- }
17
-
18
- svg {
19
- height: 1.6rem;
20
- }
21
- }
22
-
23
- form {
24
- padding-bottom: 1rem;
25
-
26
- button {
27
- margin: 10px 0px 20px;
28
- }
29
- }
30
-
31
- #entries ul {
32
- padding: 0;
33
-
34
- > li {
35
- margin-bottom: 0px;
36
- border-radius: 8px;
37
- display: flex;
38
- justify-content: space-between;
39
- align-items: stretch;
40
- /* align-items: center; */
41
- list-style: none;
42
- margin-inline: -1rem;
43
-
44
- a {
45
- text-decoration: none;
46
- width: 100%;
47
- padding: 0.5rem 1rem;
48
- }
49
-
50
- button {
51
- border-radius: 7px;
52
-
53
- svg {
54
- margin: 0.5rem 1rem;
55
- }
56
- }
57
- }
58
- }
59
- }
@@ -1,57 +0,0 @@
1
- import { html } from 'hono/html'
2
- import type { Structure } from '../../types.ts'
3
- import { logo } from '../icons.ts'
4
- import { rootdir } from '../../index.ts'
5
- import Entries from '../Entries.ts'
6
- import * as icons from '../icons.ts'
7
-
8
- export default (structure: Structure) => {
9
- return html`
10
- <link
11
- rel="stylesheet"
12
- href="${rootdir}/components/AdminPanel/AdminPanel.css"
13
- />
14
-
15
- <div class="admin-panel">
16
- <h1>
17
- <a href="/admin" aria-label="Go to dashboard"> ${logo} </a>
18
- </h1>
19
-
20
- <aside>
21
- <form
22
- data-on-submit="@post('/admin/api/block', { contentType: 'form' })"
23
- >
24
- <!-- <select name="type">
25
- ${structure.map(
26
- (block) => html`
27
- <option value="${block.type}">
28
- ${block.label}
29
- </option>
30
- `,
31
- )}
32
- </select>
33
- <input type="submit" value="Create" /> -->
34
-
35
- <!-- TODO: currently only handles a single entry type -->
36
- ${structure.length
37
- ? html`<input
38
- type="hidden"
39
- name="type"
40
- value="${structure[0].type}"
41
- />
42
- <button
43
- class="ghost"
44
- style="padding: 10px; margin: 0 -13px; display: flex;"
45
- data-tooltip="New entry"
46
- data-placement="right"
47
- >
48
- ${icons.newDocument}
49
- </button>`
50
- : ''}
51
- </form>
52
- </aside>
53
-
54
- ${Entries()}
55
- </div>
56
- `
57
- }