@alstar/studio 0.0.0-beta.4 → 0.0.0-beta.6
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 +74 -0
- package/api/block.ts +39 -21
- package/api/index.ts +9 -1
- package/api/mcp.ts +53 -0
- package/bin/alstar.ts +42 -0
- package/components/{AdminPanel/AdminPanel.ts → AdminPanel.ts} +22 -27
- package/components/Block.ts +51 -112
- package/components/Entries.ts +17 -10
- package/components/Entry.ts +9 -15
- package/components/Settings.ts +98 -0
- package/components/fields/Blocks.ts +118 -0
- package/components/fields/Text.ts +42 -0
- package/components/fields/index.ts +4 -0
- package/components/icons.ts +97 -0
- package/components/index.ts +1 -1
- package/components/layout.ts +8 -5
- package/index.ts +48 -20
- package/package.json +2 -1
- package/public/admin-panel.css +90 -0
- package/public/blocks.css +53 -0
- package/public/main.css +11 -1
- package/public/main.js +4 -0
- package/public/settings.css +24 -0
- package/queries/block-with-children.ts +74 -0
- package/queries/block.ts +287 -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/schemas.ts +15 -54
- package/types.ts +95 -5
- package/utils/buildBlocksTree.ts +4 -4
- package/utils/define.ts +21 -5
- package/utils/file-based-router.ts +9 -1
- package/utils/get-config.ts +8 -9
- package/utils/get-or-create-row.ts +28 -0
- package/utils/startup-log.ts +9 -0
- package/components/AdminPanel/AdminPanel.css +0 -59
- package/components/Field.ts +0 -164
- package/components/Fields.ts +0 -43
- /package/{components/Entry.css → public/entry.css} +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// structure-types.ts
|
|
2
|
+
import { type DBBase } from "./db-types.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract helpers
|
|
6
|
+
*/
|
|
7
|
+
type ArrayElement<T> = T extends readonly (infer U)[] ? U : never;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Field definition shape inferred from defineField(...) (the runtime helper)
|
|
11
|
+
* We keep it generic as "any" shape but with the important properties present
|
|
12
|
+
*/
|
|
13
|
+
type FieldDef = {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly type: string;
|
|
16
|
+
readonly fields?: readonly any[]; // only present when type === 'blocks'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Block definition shape inferred from defineBlock(...) (the runtime helper)
|
|
21
|
+
*/
|
|
22
|
+
type BlockDef = {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly type: string;
|
|
25
|
+
readonly fields?: readonly FieldDef[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Primitive field node (non-'blocks'): DBBase + kept fields but no children
|
|
30
|
+
*/
|
|
31
|
+
type PrimitiveFieldNode<TField extends FieldDef> =
|
|
32
|
+
DBBase & {
|
|
33
|
+
readonly name: TField["name"];
|
|
34
|
+
readonly type: TField["type"];
|
|
35
|
+
// no children (leaf), no nested fields
|
|
36
|
+
readonly children?: [];
|
|
37
|
+
readonly fields?: {}; // empty object for leaf
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* For 'blocks' typed field, we need:
|
|
42
|
+
* - the block node representing the 'blocks' wrapper (has DBBase props)
|
|
43
|
+
* - its 'children' are an array of BlockNodes corresponding to nested block defs supplied in the field's 'fields' array
|
|
44
|
+
* - its 'fields' property is the mapping of its own child-field names (can be empty)
|
|
45
|
+
*/
|
|
46
|
+
type BlocksFieldNode<
|
|
47
|
+
TField extends FieldDef,
|
|
48
|
+
TFieldDefs extends readonly BlockDef[]
|
|
49
|
+
> = DBBase & {
|
|
50
|
+
readonly name: TField["name"]; // e.g. "blocks" or "images"
|
|
51
|
+
readonly type: "blocks"; // literally 'blocks'
|
|
52
|
+
readonly children: BlockNodeFromBlockDefs<TFieldDefs>[]; // children are instances of the nested blocks
|
|
53
|
+
readonly fields: FieldsFromFieldDefs<TFieldDefs[number]["fields"]>; // the blocks-wrapper's own fields mapping (if any)
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the 'fields' object for a set of FieldDef[].
|
|
58
|
+
* Maps each field name -> either PrimitiveFieldNode or BlocksFieldNode recursively.
|
|
59
|
+
*/
|
|
60
|
+
type FieldsFromFieldDefs<TDefs> =
|
|
61
|
+
// If no fields
|
|
62
|
+
TDefs extends readonly any[]
|
|
63
|
+
? {
|
|
64
|
+
// For each field F in TDefs, map F['name'] -> node type
|
|
65
|
+
[F in ArrayElement<TDefs> as F extends { name: infer N extends string } ? N : never]:
|
|
66
|
+
F extends { type: "blocks"; fields: readonly BlockDef[] }
|
|
67
|
+
? BlocksFieldNode<F, F["fields"]>
|
|
68
|
+
: PrimitiveFieldNode<F>;
|
|
69
|
+
}
|
|
70
|
+
: {};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A Block node type for a particular BlockDef.
|
|
74
|
+
* - fields: mapping derived from the block's declared fields
|
|
75
|
+
* - children: by default [], because in our final shape all immediate children are placed under 'fields' of the parent.
|
|
76
|
+
* BUT for nodes that are themselves 'blocks' wrappers (i.e. appear as a Block instance of a nested block def),
|
|
77
|
+
* their 'children' will contain actual child blocks (these are handled via BlocksFieldNode above).
|
|
78
|
+
*/
|
|
79
|
+
export type BlockNode<T extends BlockDef> = DBBase & {
|
|
80
|
+
readonly name: T["name"];
|
|
81
|
+
readonly type: T["type"];
|
|
82
|
+
readonly fields: FieldsFromFieldDefs<T["fields"]>;
|
|
83
|
+
// for regular block nodes, children will usually be [] (top-level parent's children moved into fields)
|
|
84
|
+
readonly children: [];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Construct BlockNode unions for a set of block defs (used when blocks field has multiple block subdefs)
|
|
89
|
+
*/
|
|
90
|
+
type BlockNodeFromBlockDefs<TDefs extends readonly BlockDef[]> =
|
|
91
|
+
ArrayElement<TDefs> extends infer B ? (B extends BlockDef ? BlockNode<B> : never) : never;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The top-level forest return type when you pass a structure: it's an array of BlockNode of any top-level BlockDef
|
|
95
|
+
*/
|
|
96
|
+
export type BlockTreeFromStructure<TStructure extends readonly BlockDef[]> =
|
|
97
|
+
BlockNodeFromBlockDefs<TStructure>;
|
package/schemas.ts
CHANGED
|
@@ -1,56 +1,5 @@
|
|
|
1
1
|
import { sql } from './utils/sql.ts'
|
|
2
2
|
|
|
3
|
-
// export const entriesTable = {
|
|
4
|
-
// tableName: 'entries',
|
|
5
|
-
// columns: sql`
|
|
6
|
-
// title TEXT not null, -- Title of the page
|
|
7
|
-
// slug TEXT not null unique, -- URL slug for the page
|
|
8
|
-
// meta_description TEXT -- Optional meta description for SEO
|
|
9
|
-
// `,
|
|
10
|
-
// }
|
|
11
|
-
|
|
12
|
-
// export const fieldTable = {
|
|
13
|
-
// tableName: 'fields',
|
|
14
|
-
// columns: sql`
|
|
15
|
-
// name TEXT not null, -- Name of the field (e.g., "content", "header", "image")
|
|
16
|
-
// type TEXT not null, -- Field type (e.g., "text", "image", "video")
|
|
17
|
-
// label TEXT not null, -- Field label (e.g., "Text", "Image", "Video")
|
|
18
|
-
// options TEXT -- Additional options or settings (can be a JSON string if needed)
|
|
19
|
-
// `,
|
|
20
|
-
// }
|
|
21
|
-
|
|
22
|
-
// export const entriesFieldsTable = {
|
|
23
|
-
// tableName: 'entry_fields',
|
|
24
|
-
// columns: sql`
|
|
25
|
-
// entry_id INTEGER not null, -- Foreign key to pages
|
|
26
|
-
// field_id INTEGER not null, -- Foreign key to fields
|
|
27
|
-
// position INTEGER, -- Optional: order of the field on the page
|
|
28
|
-
// content TEXT, -- Content of the field (e.g., text, image URL, etc.)
|
|
29
|
-
// foreign key (entry_id) references entries (id),
|
|
30
|
-
// foreign key (field_id) references fields (id)
|
|
31
|
-
// `,
|
|
32
|
-
// }
|
|
33
|
-
|
|
34
|
-
// export const entryTypeTable = {
|
|
35
|
-
// tableName: 'entry_types',
|
|
36
|
-
// columns: sql`
|
|
37
|
-
// name TEXT not null, -- Name of the field (e.g., "content", "header", "image")
|
|
38
|
-
// type TEXT not null, -- Field type (e.g., "text", "image", "video")
|
|
39
|
-
// label TEXT not null, -- Field label (e.g., "Text", "Image", "Video")
|
|
40
|
-
// options TEXT -- Additional options or settings (can be a JSON string if needed)
|
|
41
|
-
// `,
|
|
42
|
-
// }
|
|
43
|
-
|
|
44
|
-
// export const entryEntryTypeTable = {
|
|
45
|
-
// tableName: 'entry_entry_types',
|
|
46
|
-
// columns: sql`
|
|
47
|
-
// entry_id INTEGER not null, -- Foreign key to pages
|
|
48
|
-
// entry_type_id INTEGER not null, -- Foreign key to fields
|
|
49
|
-
// foreign key (entry_id) references entries (id),
|
|
50
|
-
// foreign key (entry_type_id) references entry_types (id)
|
|
51
|
-
// `,
|
|
52
|
-
// }
|
|
53
|
-
|
|
54
3
|
// -- Blocks
|
|
55
4
|
export const blocksTable = {
|
|
56
5
|
tableName: 'blocks',
|
|
@@ -58,10 +7,22 @@ export const blocksTable = {
|
|
|
58
7
|
name TEXT not null,
|
|
59
8
|
label TEXT not null,
|
|
60
9
|
type TEXT not null,
|
|
61
|
-
sort_order INTEGER not null default 0,
|
|
62
10
|
value TEXT,
|
|
63
11
|
options JSON,
|
|
64
|
-
|
|
65
|
-
|
|
12
|
+
status TEXT default 'enabled',
|
|
13
|
+
sort_order INTEGER not null default 0,
|
|
14
|
+
_depth INTEGER,
|
|
15
|
+
parent_id INTEGER,
|
|
16
|
+
foreign key (parent_id) references blocks (id)
|
|
17
|
+
`,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// -- API keys
|
|
21
|
+
export const apiKeysTable = {
|
|
22
|
+
tableName: 'api_keys',
|
|
23
|
+
columns: sql`
|
|
24
|
+
name TEXT not null,
|
|
25
|
+
value TEXT,
|
|
26
|
+
hint TEXT
|
|
66
27
|
`,
|
|
67
28
|
}
|
package/types.ts
CHANGED
|
@@ -1,14 +1,94 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
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
|
+
|
|
6
|
+
export type PrimitiveField = {
|
|
7
|
+
name: string
|
|
8
|
+
label: string
|
|
9
|
+
type: 'text' | 'slug' | 'markdown' | 'image'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type BlockField = {
|
|
13
|
+
name: string
|
|
14
|
+
label: string
|
|
15
|
+
type: 'blocks'
|
|
16
|
+
children: Record<string, Field | Block>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Field = PrimitiveField | BlockField
|
|
3
20
|
|
|
4
21
|
export type Block = {
|
|
5
22
|
name: string
|
|
6
23
|
label: string
|
|
7
24
|
type: string
|
|
8
|
-
fields
|
|
25
|
+
fields: Record<string, Field | Block>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type Structure = Record<string, BlockDef>
|
|
29
|
+
|
|
30
|
+
// --- Field & block definitions ---
|
|
31
|
+
type FieldType = 'text' | 'slug' | 'markdown' | 'blocks' | 'image';
|
|
32
|
+
|
|
33
|
+
interface BaseField {
|
|
34
|
+
label: string;
|
|
35
|
+
type: FieldType;
|
|
36
|
+
description?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface TextField extends BaseField {
|
|
40
|
+
type: 'text' | 'slug' | 'markdown';
|
|
9
41
|
}
|
|
10
42
|
|
|
11
|
-
|
|
43
|
+
interface ImageField extends BaseField {
|
|
44
|
+
type: 'image';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BlocksField extends BaseField {
|
|
48
|
+
type: 'blocks';
|
|
49
|
+
children: Record<string, BlockDef | FieldDef>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type FieldDef = TextField | ImageField | BlocksField;
|
|
53
|
+
|
|
54
|
+
export interface BlockDef {
|
|
55
|
+
label: string;
|
|
56
|
+
type: string;
|
|
57
|
+
fields: Record<string, FieldDef>;
|
|
58
|
+
description?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
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
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type DBBlockResult = {
|
|
77
|
+
id: number
|
|
78
|
+
created_at: string
|
|
79
|
+
updated_at: string
|
|
80
|
+
name: string
|
|
81
|
+
label: string
|
|
82
|
+
type: string
|
|
83
|
+
sort_order: number
|
|
84
|
+
value: string | null
|
|
85
|
+
options: any
|
|
86
|
+
status: string
|
|
87
|
+
parent_id: number | null
|
|
88
|
+
depth: number
|
|
89
|
+
children?: DBBlockResult[]
|
|
90
|
+
fields?: Record<string, DBBlockResult>
|
|
91
|
+
}
|
|
12
92
|
|
|
13
93
|
export type DBBlock = Block & {
|
|
14
94
|
id: number
|
|
@@ -16,10 +96,20 @@ export type DBBlock = Block & {
|
|
|
16
96
|
updated_at: string
|
|
17
97
|
value: string | null
|
|
18
98
|
sort_order: number | null
|
|
19
|
-
|
|
99
|
+
parent_id: number | null
|
|
20
100
|
options: number | null
|
|
21
101
|
}
|
|
22
102
|
|
|
103
|
+
export type BlockStatus = 'enabled' | 'disabled'
|
|
104
|
+
|
|
23
105
|
export type StudioConfig = {
|
|
106
|
+
siteName: string
|
|
24
107
|
honoConfig?: HonoOptions<BlankEnv>
|
|
108
|
+
structure: Structure
|
|
25
109
|
}
|
|
110
|
+
|
|
111
|
+
export type RequestContext = Context<
|
|
112
|
+
{ Bindings: HttpBindings },
|
|
113
|
+
string,
|
|
114
|
+
BlankInput
|
|
115
|
+
>
|
package/utils/buildBlocksTree.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
30
|
-
const parent = blockMap.get(block.
|
|
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.
|
|
35
|
+
`Parent with id ${block.parent_id} not found for block ${block.id}`,
|
|
36
36
|
)
|
|
37
37
|
}
|
|
38
38
|
} else {
|
package/utils/define.ts
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
import { type Context } from 'hono'
|
|
2
1
|
import * as types from '../types.ts'
|
|
3
|
-
import { type HttpBindings } from '@hono/node-server'
|
|
4
|
-
import { type BlankInput } from 'hono/types'
|
|
5
2
|
import { type HtmlEscapedString } from './html.ts'
|
|
6
3
|
|
|
7
4
|
export const defineConfig = (config: types.StudioConfig) => config
|
|
8
|
-
|
|
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
|
+
|
|
9
10
|
export const defineEntry = (
|
|
10
11
|
fn: (
|
|
11
|
-
c:
|
|
12
|
+
c: types.RequestContext,
|
|
12
13
|
) => HtmlEscapedString | Promise<HtmlEscapedString>,
|
|
13
14
|
) => fn
|
|
15
|
+
|
|
16
|
+
// --- Identity helpers (preserve literal types) ---
|
|
17
|
+
export function defineField(field: types.FieldDef) {
|
|
18
|
+
return field
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function defineBlock(block: types.BlockDef) {
|
|
22
|
+
return block
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function defineStructure(
|
|
26
|
+
structure: Record<string, types.BlockDef>,
|
|
27
|
+
) {
|
|
28
|
+
return structure
|
|
29
|
+
}
|
|
@@ -9,7 +9,15 @@ export const fileBasedRouter = async (rootdir: string) => {
|
|
|
9
9
|
const router = new Hono()
|
|
10
10
|
|
|
11
11
|
const root = path.resolve(rootdir)
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
let dirs
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
dirs = await fs.readdir(root, { recursive: true })
|
|
17
|
+
} catch (error) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
const files = dirs.filter((dir) => path.extname(dir))
|
|
14
22
|
|
|
15
23
|
await Promise.all(
|
package/utils/get-config.ts
CHANGED
|
@@ -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 null
|
|
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,28 @@
|
|
|
1
|
+
import { db } from '@alstar/db'
|
|
2
|
+
import { query } from '../queries/index.ts'
|
|
3
|
+
import type { Block, FieldDef } from '../types.ts'
|
|
4
|
+
|
|
5
|
+
export function getOrCreateRow(
|
|
6
|
+
parentId: string | number,
|
|
7
|
+
name: string,
|
|
8
|
+
field: Block | FieldDef,
|
|
9
|
+
sortOrder: number,
|
|
10
|
+
) {
|
|
11
|
+
const data = query.block({
|
|
12
|
+
parent_id: parentId?.toString() || null,
|
|
13
|
+
name: name,
|
|
14
|
+
sort_order: sortOrder.toString(),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
if (data) return data
|
|
18
|
+
|
|
19
|
+
const change = db.insertInto('blocks', {
|
|
20
|
+
name: name?.toString(),
|
|
21
|
+
label: field.label?.toString(),
|
|
22
|
+
type: field.type?.toString(),
|
|
23
|
+
sort_order: sortOrder,
|
|
24
|
+
parent_id: parentId,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return query.block({ id: change.lastInsertRowid.toString() })
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import packageJSON from '../package.json' with { type: 'json' }
|
|
2
|
+
|
|
3
|
+
export default () => {
|
|
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:${3000} │`)
|
|
8
|
+
console.log('\x1b[32m%s\x1b[0m', '╰───────────────────────╯')
|
|
9
|
+
}
|
|
@@ -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
|
-
}
|
package/components/Field.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { html } from 'hono/html'
|
|
2
|
-
import { type Block } from '../types.ts'
|
|
3
|
-
import { type HtmlEscapedString } from 'hono/utils/html'
|
|
4
|
-
import { query } from '../queries/index.ts'
|
|
5
|
-
import { db } from '@alstar/db'
|
|
6
|
-
import BlockComponent from './Block.ts'
|
|
7
|
-
import {
|
|
8
|
-
buildStructurePath,
|
|
9
|
-
type StructurePath,
|
|
10
|
-
} from '../utils/build-structure-path.ts'
|
|
11
|
-
|
|
12
|
-
function getData(parentId: string | number, field: Block, sortOrder: number) {
|
|
13
|
-
const data = query.block({
|
|
14
|
-
parent: parentId?.toString() || null,
|
|
15
|
-
name: field.name,
|
|
16
|
-
sort_order: sortOrder.toString(),
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
if (!data) {
|
|
20
|
-
const change = db.insertInto('blocks', {
|
|
21
|
-
name: field.name?.toString(),
|
|
22
|
-
label: field.label?.toString(),
|
|
23
|
-
type: field.type?.toString(),
|
|
24
|
-
sort_order: sortOrder,
|
|
25
|
-
parent_block_id: parentId,
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
return query.block({ id: change.lastInsertRowid.toString() })
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return data
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const Field = (props: {
|
|
35
|
-
entryId: string | number
|
|
36
|
-
parentId: string | number
|
|
37
|
-
blockStructure: Block
|
|
38
|
-
sortOrder?: number
|
|
39
|
-
structurePath: StructurePath
|
|
40
|
-
}): HtmlEscapedString | Promise<HtmlEscapedString> => {
|
|
41
|
-
const {
|
|
42
|
-
entryId,
|
|
43
|
-
parentId,
|
|
44
|
-
blockStructure,
|
|
45
|
-
sortOrder = 0,
|
|
46
|
-
structurePath,
|
|
47
|
-
} = props
|
|
48
|
-
|
|
49
|
-
const data = getData(parentId, blockStructure, sortOrder)
|
|
50
|
-
|
|
51
|
-
if (!data) return html`<p>No block</p>`
|
|
52
|
-
|
|
53
|
-
const fieldStructurePath = buildStructurePath(blockStructure, structurePath)
|
|
54
|
-
|
|
55
|
-
return html`
|
|
56
|
-
${blockStructure.type === 'slug' &&
|
|
57
|
-
html`
|
|
58
|
-
<form
|
|
59
|
-
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
60
|
-
>
|
|
61
|
-
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
62
|
-
<input
|
|
63
|
-
id="block-${data.id}"
|
|
64
|
-
name="value"
|
|
65
|
-
type="text"
|
|
66
|
-
value="${data.value}"
|
|
67
|
-
/>
|
|
68
|
-
|
|
69
|
-
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
70
|
-
<input type="hidden" name="id" value="${data.id}" />
|
|
71
|
-
<input type="hidden" name="entryId" value="${entryId}" />
|
|
72
|
-
<input type="hidden" name="parentId" value="${parentId}" />
|
|
73
|
-
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
74
|
-
<input
|
|
75
|
-
type="hidden"
|
|
76
|
-
name="path"
|
|
77
|
-
value="${fieldStructurePath.join('.')}"
|
|
78
|
-
/>
|
|
79
|
-
</form>
|
|
80
|
-
`}
|
|
81
|
-
${blockStructure.type === 'text' &&
|
|
82
|
-
html`
|
|
83
|
-
<form
|
|
84
|
-
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
85
|
-
>
|
|
86
|
-
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
87
|
-
<input
|
|
88
|
-
id="block-${data.id}"
|
|
89
|
-
name="value"
|
|
90
|
-
type="text"
|
|
91
|
-
value="${data.value}"
|
|
92
|
-
/>
|
|
93
|
-
|
|
94
|
-
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
95
|
-
<input type="hidden" name="id" value="${data.id}" />
|
|
96
|
-
<input type="hidden" name="entryId" value="${entryId}" />
|
|
97
|
-
<input type="hidden" name="parentId" value="${parentId}" />
|
|
98
|
-
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
99
|
-
<input
|
|
100
|
-
type="hidden"
|
|
101
|
-
name="path"
|
|
102
|
-
value="${fieldStructurePath.join('.')}"
|
|
103
|
-
/>
|
|
104
|
-
</form>
|
|
105
|
-
`}
|
|
106
|
-
${blockStructure.type === 'image' &&
|
|
107
|
-
html`
|
|
108
|
-
<form
|
|
109
|
-
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
110
|
-
>
|
|
111
|
-
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
112
|
-
<input
|
|
113
|
-
id="block-${data.id}"
|
|
114
|
-
name="value"
|
|
115
|
-
type="text"
|
|
116
|
-
value="${data.value}"
|
|
117
|
-
/>
|
|
118
|
-
|
|
119
|
-
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
120
|
-
<input type="hidden" name="id" value="${data.id}" />
|
|
121
|
-
<input type="hidden" name="entryId" value="${entryId}" />
|
|
122
|
-
<input type="hidden" name="parentId" value="${parentId}" />
|
|
123
|
-
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
124
|
-
<input
|
|
125
|
-
type="hidden"
|
|
126
|
-
name="path"
|
|
127
|
-
value="${fieldStructurePath.join('.')}"
|
|
128
|
-
/>
|
|
129
|
-
</form>
|
|
130
|
-
`}
|
|
131
|
-
${blockStructure.type === 'markdown' &&
|
|
132
|
-
html`
|
|
133
|
-
<form
|
|
134
|
-
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
135
|
-
>
|
|
136
|
-
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
137
|
-
|
|
138
|
-
<textarea></textarea>
|
|
139
|
-
|
|
140
|
-
<!-- <input
|
|
141
|
-
id="block-${data.id}"
|
|
142
|
-
name="value"
|
|
143
|
-
type="text"
|
|
144
|
-
value="${data.value}"
|
|
145
|
-
/> -->
|
|
146
|
-
|
|
147
|
-
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
148
|
-
<input type="hidden" name="id" value="${data.id}" />
|
|
149
|
-
<input type="hidden" name="entryId" value="${entryId}" />
|
|
150
|
-
<input type="hidden" name="parentId" value="${parentId}" />
|
|
151
|
-
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
152
|
-
<input
|
|
153
|
-
type="hidden"
|
|
154
|
-
name="path"
|
|
155
|
-
value="${fieldStructurePath.join('.')}"
|
|
156
|
-
/>
|
|
157
|
-
</form>
|
|
158
|
-
`}
|
|
159
|
-
${blockStructure.type === 'blocks' &&
|
|
160
|
-
BlockComponent(entryId, data.id, blockStructure, fieldStructurePath)}
|
|
161
|
-
`
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export default Field
|