@alstar/studio 0.0.0-beta.16 → 0.0.0-beta.18
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/block.ts +0 -14
- package/components/AdminPanel.ts +11 -5
- package/components/BlockFieldRenderer.ts +25 -19
- package/components/BlockRenderer.ts +4 -4
- package/components/Entries.ts +1 -1
- package/components/Entry.ts +13 -7
- package/components/FieldRenderer.ts +8 -7
- package/components/LivePreview.ts +37 -0
- package/components/Render.ts +2 -2
- package/components/SiteLayout.ts +1 -4
- package/components/fields/Markdown.ts +10 -3
- package/components/fields/Reference.ts +11 -7
- package/components/fields/Slug.ts +6 -6
- package/components/fields/Text.ts +13 -8
- package/components/icons.ts +3 -0
- package/components/settings/ApiKeys.ts +4 -4
- package/components/settings/Backup.ts +3 -3
- package/components/settings/Users.ts +1 -1
- package/index.ts +11 -10
- package/package.json +4 -5
- package/pages/entry/[id].ts +7 -1
- package/pages/error.ts +7 -6
- package/pages/login.ts +1 -1
- package/pages/register.ts +2 -2
- package/public/studio/css/admin-panel.css +27 -9
- package/public/studio/css/blocks-field.css +25 -0
- package/public/studio/css/entry-page.css +4 -0
- package/public/studio/css/entry.css +35 -0
- package/public/studio/css/field.css +14 -0
- package/public/studio/css/live-preview.css +25 -0
- package/public/studio/css/settings.css +4 -0
- package/public/studio/js/live-preview.js +26 -0
- package/public/studio/js/markdown-editor.js +6 -0
- package/public/studio/js/sortable-list.js +6 -4
- package/public/studio/main.css +11 -13
- package/public/studio/main.js +1 -0
- package/queries/block.ts +127 -105
- package/queries/index.ts +3 -2
- package/types.ts +39 -69
- package/utils/define.ts +3 -1
- package/utils/refresher.ts +56 -0
- package/utils/renderSSE.ts +8 -3
- package/utils/startup-log.ts +4 -4
- package/queries/block-2.ts +0 -339
- package/queries/db-types.ts +0 -15
- package/queries/getBlockTrees-2.ts +0 -71
- package/queries/getBlocks.ts +0 -214
- package/queries/structure-types.ts +0 -97
- package/utils/buildBlocksTree.ts +0 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alstar/studio",
|
|
3
|
-
"version": "0.0.0-beta.
|
|
3
|
+
"version": "0.0.0-beta.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
"node": ">=23.8"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@hono/node-server": "
|
|
13
|
+
"@hono/node-server": "1.18.1",
|
|
14
14
|
"@starfederation/datastar-sdk": "1.0.0-RC.1",
|
|
15
|
-
"hono": "
|
|
15
|
+
"hono": "4.8.12",
|
|
16
16
|
"@alstar/db": "0.0.0-beta.1",
|
|
17
|
-
"@alstar/
|
|
18
|
-
"@alstar/ui": "0.0.0-beta.3"
|
|
17
|
+
"@alstar/ui": "0.0.0-beta.4"
|
|
19
18
|
},
|
|
20
19
|
"devDependencies": {
|
|
21
20
|
"@types/node": "^24.1.0",
|
package/pages/entry/[id].ts
CHANGED
|
@@ -3,6 +3,7 @@ import { defineEntry } from '../../utils/define.ts'
|
|
|
3
3
|
|
|
4
4
|
import SiteLayout from '../../components/SiteLayout.ts'
|
|
5
5
|
import Entry from '../../components/Entry.ts'
|
|
6
|
+
import LivePreview from '../../components/LivePreview.ts'
|
|
6
7
|
|
|
7
8
|
export default defineEntry((c) => {
|
|
8
9
|
const id = c.req.param('id')
|
|
@@ -11,5 +12,10 @@ export default defineEntry((c) => {
|
|
|
11
12
|
return html`<p>Entry page url needs an ID param: "${id}"</p>`
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
return SiteLayout(
|
|
15
|
+
return SiteLayout(
|
|
16
|
+
html`<div class="entry-page">
|
|
17
|
+
${Entry({ entryId: id })}
|
|
18
|
+
${LivePreview({ entryId: id })}</div>
|
|
19
|
+
`
|
|
20
|
+
)
|
|
15
21
|
})
|
package/pages/error.ts
CHANGED
|
@@ -3,12 +3,13 @@ import SiteLayout from "../components/SiteLayout.ts";
|
|
|
3
3
|
import type { HTTPResponseError } from "hono/types";
|
|
4
4
|
|
|
5
5
|
export default ((err?: Error | HTTPResponseError) => {
|
|
6
|
-
|
|
7
6
|
return SiteLayout(html`
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
<div class="container">
|
|
8
|
+
<article style="margin: 100px">
|
|
9
|
+
<header>Something went wrong</header>
|
|
10
|
+
<p>Try again</p>
|
|
11
|
+
<p>${err?.message || '404 - Not found'}</p>
|
|
12
|
+
</article>
|
|
13
|
+
</div>
|
|
13
14
|
`, false)
|
|
14
15
|
})
|
package/pages/login.ts
CHANGED
|
@@ -7,7 +7,7 @@ export default defineEntry(c => {
|
|
|
7
7
|
<div class="login-form">
|
|
8
8
|
<article>
|
|
9
9
|
<header>Login</header>
|
|
10
|
-
<form data-on
|
|
10
|
+
<form data-on:submit="@post('/studio/api/auth/login', { contentType: 'form' })">
|
|
11
11
|
<label for="email">Email</label>
|
|
12
12
|
<input id="email" name="email" type="text" placeholder="Email">
|
|
13
13
|
<label for="password">Password</label>
|
package/pages/register.ts
CHANGED
|
@@ -10,8 +10,8 @@ export default defineEntry((c) => {
|
|
|
10
10
|
<header>Register user</header>
|
|
11
11
|
<form
|
|
12
12
|
data-signals="{ status: 0 }"
|
|
13
|
-
data-on
|
|
14
|
-
data-on
|
|
13
|
+
data-on:submit="@post('/studio/api/auth/register', { contentType: 'form' })"
|
|
14
|
+
data-on:signal-patch="patch.status === 200 && window.location.reload()"
|
|
15
15
|
>
|
|
16
16
|
<label for="email">Email</label>
|
|
17
17
|
<input id="email" name="email" type="text" placeholder="Email" style="width: 100%" />
|
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
.admin-panel {
|
|
2
|
-
/* background: hsla(0, 0%, 0%, 0.1); */
|
|
3
2
|
padding: 40px;
|
|
4
3
|
margin-bottom: 0;
|
|
5
4
|
display: flex;
|
|
6
5
|
flex-direction: column;
|
|
7
6
|
align-items: flex-start;
|
|
8
7
|
|
|
8
|
+
position: relative;
|
|
9
9
|
height: 100%;
|
|
10
10
|
min-height: inherit;
|
|
11
|
-
|
|
12
11
|
min-width: 250px;
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
padding-bottom: 1rem;
|
|
13
|
+
border-right: 1px solid var(--pico-muted-border-color);
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
/* .toggle-button {
|
|
16
|
+
position: absolute;
|
|
17
|
+
top: 0;
|
|
18
|
+
right: 0;
|
|
19
|
+
margin: 0.5rem;
|
|
20
|
+
padding: 0.2rem;
|
|
21
|
+
} */
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
header {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 1rem;
|
|
27
|
+
padding-bottom: 1rem;
|
|
28
|
+
|
|
29
|
+
.title {
|
|
30
|
+
margin: 0;
|
|
31
|
+
|
|
32
|
+
a {
|
|
33
|
+
display: flex;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
svg {
|
|
37
|
+
height: 1.6rem;
|
|
38
|
+
width: auto;
|
|
39
|
+
}
|
|
23
40
|
}
|
|
24
41
|
}
|
|
25
42
|
|
|
43
|
+
|
|
26
44
|
form {
|
|
27
45
|
padding-bottom: 1rem;
|
|
28
46
|
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
.blocks-field {
|
|
2
2
|
> header {
|
|
3
|
+
position: relative;
|
|
3
4
|
display: flex;
|
|
4
5
|
justify-content: space-between;
|
|
5
6
|
align-items: center;
|
|
6
7
|
|
|
8
|
+
position: sticky;
|
|
9
|
+
top: 0;
|
|
10
|
+
z-index: 3;
|
|
11
|
+
background: var(--pico-background-color);
|
|
12
|
+
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
13
|
+
|
|
14
|
+
padding: 1rem;
|
|
15
|
+
|
|
16
|
+
p {
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
> form fieldset {
|
|
8
21
|
button {
|
|
9
22
|
padding-inline: 1rem;
|
|
@@ -11,6 +24,8 @@
|
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
details {
|
|
27
|
+
margin: 0;
|
|
28
|
+
|
|
14
29
|
form {
|
|
15
30
|
display: flex;
|
|
16
31
|
}
|
|
@@ -28,6 +43,16 @@
|
|
|
28
43
|
}
|
|
29
44
|
}
|
|
30
45
|
|
|
46
|
+
> header:after {
|
|
47
|
+
content: '';
|
|
48
|
+
position: absolute;
|
|
49
|
+
background-color: var(--pico-background-color);
|
|
50
|
+
height: 100%;
|
|
51
|
+
width: 200%;
|
|
52
|
+
left: -50%;
|
|
53
|
+
z-index: -1;
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
article {
|
|
32
57
|
> header {
|
|
33
58
|
display: flex;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.entry{
|
|
2
|
+
height: 100vh;
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-rows: auto 1fr;
|
|
5
|
+
|
|
6
|
+
> header {
|
|
7
|
+
background-color: var(--pico-background-color);
|
|
8
|
+
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
9
|
+
padding: 1rem;
|
|
10
|
+
|
|
11
|
+
h1 {
|
|
12
|
+
font-size: 1rem;
|
|
13
|
+
font-weight: normal;
|
|
14
|
+
margin: 0;
|
|
15
|
+
font-family: var(--pico-font-family-monospace);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.content {
|
|
20
|
+
overflow: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
> .fields > .block {
|
|
24
|
+
padding: var(--pico-spacing);
|
|
25
|
+
|
|
26
|
+
> header {
|
|
27
|
+
margin-block: var(--pico-block-spacing-vertical);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.field {
|
|
32
|
+
padding-top: 1rem;
|
|
33
|
+
padding-inline: 1rem;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.live-preview {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
border-left: 1px solid var(--pico-muted-border-color);
|
|
5
|
+
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-rows: auto 1fr;
|
|
8
|
+
|
|
9
|
+
header {
|
|
10
|
+
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
11
|
+
padding: 1rem;
|
|
12
|
+
|
|
13
|
+
h1 {
|
|
14
|
+
font-size: 1rem;
|
|
15
|
+
font-weight: normal;
|
|
16
|
+
margin: 0;
|
|
17
|
+
font-family: var(--pico-font-family-monospace);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
iframe {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class LivePreview extends HTMLElement {
|
|
2
|
+
abortController = new AbortController()
|
|
3
|
+
mutationObserver
|
|
4
|
+
iframe
|
|
5
|
+
|
|
6
|
+
reload() {
|
|
7
|
+
this.iframe.contentWindow.location.reload()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
this.mutationObserver = new MutationObserver(this.reload.bind(this))
|
|
12
|
+
this.mutationObserver.observe(this, { attributes: true })
|
|
13
|
+
|
|
14
|
+
this.iframe = this.querySelector('iframe')
|
|
15
|
+
|
|
16
|
+
window.addEventListener('markdown-editor:update', this.reload.bind(this), { signal: this.abortController.signal })
|
|
17
|
+
window.addEventListener('sortable-list:update', this.reload.bind(this), { signal: this.abortController.signal })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
disconnectedCallback() {
|
|
21
|
+
this.abortController?.abort()
|
|
22
|
+
this.mutationObserver?.disconnect()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
customElements.define('live-preview', LivePreview)
|
|
@@ -11,11 +11,17 @@ class MarkdownEditor extends HTMLElement {
|
|
|
11
11
|
afterUpdate: async (e) => {
|
|
12
12
|
await fetch('/studio/api/value', {
|
|
13
13
|
method: 'PATCH',
|
|
14
|
+
headers: {
|
|
15
|
+
render: 'LivePreview',
|
|
16
|
+
props: this.dataset.entryId
|
|
17
|
+
},
|
|
14
18
|
body: JSON.stringify({
|
|
15
19
|
value: e,
|
|
16
20
|
id: this.dataset.id,
|
|
17
21
|
}),
|
|
18
22
|
})
|
|
23
|
+
|
|
24
|
+
window.dispatchEvent(new CustomEvent('markdown-editor:update'))
|
|
19
25
|
},
|
|
20
26
|
},
|
|
21
27
|
interface: {
|
|
@@ -13,23 +13,25 @@ class SortableList extends HTMLElement {
|
|
|
13
13
|
this.instance = Sortable.create(this, {
|
|
14
14
|
animation: 250,
|
|
15
15
|
handle: `[data-handle-for="${id}"]`,
|
|
16
|
-
onEnd: (e) => {
|
|
16
|
+
onEnd: async (e) => {
|
|
17
17
|
const items = [...e.target.children]
|
|
18
18
|
|
|
19
|
-
items.
|
|
19
|
+
await Promise.all(items.map(async (child, idx) => {
|
|
20
20
|
const searchParams = new URLSearchParams()
|
|
21
21
|
|
|
22
22
|
searchParams.set('id', child.dataset.id)
|
|
23
23
|
searchParams.set('sort-order', idx)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
return fetch(`/studio/api/sort-order?${searchParams.toString()}`, {
|
|
26
26
|
method: 'post',
|
|
27
27
|
})
|
|
28
|
-
})
|
|
28
|
+
}))
|
|
29
29
|
|
|
30
30
|
this.querySelectorAll('[name="sort_order"]').forEach((input, idx) => {
|
|
31
31
|
input.value = idx.toString()
|
|
32
32
|
})
|
|
33
|
+
|
|
34
|
+
window.dispatchEvent(new CustomEvent('sortable-list:update'))
|
|
33
35
|
},
|
|
34
36
|
})
|
|
35
37
|
}
|
package/public/studio/main.css
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
@import 'https://esm.sh/@alstar/ui/red.css';
|
|
2
|
-
/* @import '../node_modules/@alstar/ui/
|
|
2
|
+
/* @import '../node_modules/@alstar/ui/red.css'; */
|
|
3
3
|
@import './css/admin-panel.css';
|
|
4
4
|
@import './css/blocks-field.css';
|
|
5
5
|
@import './css/settings.css';
|
|
6
|
+
@import './css/live-preview.css';
|
|
7
|
+
@import './css/entry-page.css';
|
|
8
|
+
@import './css/entry.css';
|
|
9
|
+
@import './css/field.css';
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
--pico-font-size: 80%;
|
|
13
|
+
}
|
|
6
14
|
|
|
7
15
|
body {
|
|
8
16
|
padding: 0;
|
|
@@ -13,13 +21,11 @@ body {
|
|
|
13
21
|
align-content: baseline;
|
|
14
22
|
|
|
15
23
|
> main {
|
|
16
|
-
padding: 40px;
|
|
17
|
-
height: 100vh;
|
|
18
|
-
overflow: auto;
|
|
19
24
|
width: 100%;
|
|
25
|
+
height: 100vh;
|
|
26
|
+
padding: 0;
|
|
20
27
|
|
|
21
28
|
section {
|
|
22
|
-
max-width: 900px;
|
|
23
29
|
margin: 0 auto;
|
|
24
30
|
}
|
|
25
31
|
}
|
|
@@ -29,14 +35,6 @@ button svg, a svg {
|
|
|
29
35
|
pointer-events: none;
|
|
30
36
|
}
|
|
31
37
|
|
|
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
38
|
.block {
|
|
41
39
|
> header {
|
|
42
40
|
margin-bottom: var(--pico-spacing);
|
package/public/studio/main.js
CHANGED
package/queries/block.ts
CHANGED
|
@@ -2,97 +2,69 @@ import { db } from '@alstar/db'
|
|
|
2
2
|
import { sql } from '../utils/sql.ts'
|
|
3
3
|
import { type DBBlockResult } from '../types.ts'
|
|
4
4
|
|
|
5
|
-
function buildForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
roots.forEach(sortChildren)
|
|
57
|
-
|
|
58
|
-
return roots[0]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function transformBlocksTree(
|
|
62
|
-
block: DBBlockResult,
|
|
63
|
-
isBlocksChild?: boolean,
|
|
64
|
-
): DBBlockResult {
|
|
65
|
-
const fields: Record<string, DBBlockResult> = {}
|
|
66
|
-
let hasFields = false
|
|
67
|
-
|
|
68
|
-
for (const child of block.children ?? []) {
|
|
69
|
-
const transformedChild = transformBlocksTree(
|
|
70
|
-
child,
|
|
71
|
-
child.type === 'blocks',
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
if (!isBlocksChild) {
|
|
75
|
-
hasFields = true
|
|
76
|
-
fields[transformedChild.name] = transformedChild
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if(hasFields) {
|
|
81
|
-
block.fields = fields
|
|
82
|
-
}
|
|
5
|
+
// function buildForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
6
|
+
// const map = new Map<number, DBBlockResult>()
|
|
7
|
+
// const roots: DBBlockResult[] = []
|
|
8
|
+
|
|
9
|
+
// for (const block of blocks) {
|
|
10
|
+
// block.blocks = []
|
|
11
|
+
// map.set(block.id, block)
|
|
12
|
+
// }
|
|
13
|
+
|
|
14
|
+
// for (const block of blocks) {
|
|
15
|
+
// if (block.parent_id === null) {
|
|
16
|
+
// roots.push(block)
|
|
17
|
+
// } else {
|
|
18
|
+
// const parent = map.get(block.parent_id)
|
|
19
|
+
// if (parent) parent.blocks!.push(block)
|
|
20
|
+
// }
|
|
21
|
+
// }
|
|
22
|
+
|
|
23
|
+
// // Sort blocks by sort_order recursively
|
|
24
|
+
// const sortChildren = (node: DBBlockResult) => {
|
|
25
|
+
// node.blocks!.sort((a, b) => a.sort_order - b.sort_order)
|
|
26
|
+
// node.blocks!.forEach(sortChildren)
|
|
27
|
+
// }
|
|
28
|
+
// roots.forEach(sortChildren)
|
|
29
|
+
|
|
30
|
+
// return roots
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// function transformBlocksTree(
|
|
34
|
+
// block: DBBlockResult,
|
|
35
|
+
// isBlocksChild?: boolean,
|
|
36
|
+
// ): DBBlockResult {
|
|
37
|
+
// const fields: Record<string, DBBlockResult> = {}
|
|
38
|
+
// let hasFields = false
|
|
39
|
+
|
|
40
|
+
// for (const child of block.blocks ?? []) {
|
|
41
|
+
// const transformedChild = transformBlocksTree(
|
|
42
|
+
// child,
|
|
43
|
+
// child.type === 'blocks',
|
|
44
|
+
// )
|
|
45
|
+
|
|
46
|
+
// if (!isBlocksChild) {
|
|
47
|
+
// hasFields = true
|
|
48
|
+
// fields[transformedChild.name] = transformedChild
|
|
49
|
+
// }
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// if(hasFields) {
|
|
53
|
+
// block.fields = fields
|
|
54
|
+
// }
|
|
83
55
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
56
|
+
// if (!isBlocksChild) {
|
|
57
|
+
// delete block.blocks
|
|
58
|
+
// } else {
|
|
59
|
+
// delete block.fields
|
|
60
|
+
// }
|
|
89
61
|
|
|
90
|
-
|
|
91
|
-
}
|
|
62
|
+
// return block
|
|
63
|
+
// }
|
|
92
64
|
|
|
93
|
-
function transformForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
94
|
-
|
|
95
|
-
}
|
|
65
|
+
// function transformForest(blocks: DBBlockResult[]): DBBlockResult[] {
|
|
66
|
+
// return blocks.map((block) => transformBlocksTree(block))
|
|
67
|
+
// }
|
|
96
68
|
|
|
97
69
|
function rootQuery(filterSql: string, depthLimit?: number) {
|
|
98
70
|
const depthLimitClause =
|
|
@@ -218,24 +190,72 @@ function buildFilterSql(params: Record<string, any>) {
|
|
|
218
190
|
return { filterSql, sqlParams }
|
|
219
191
|
}
|
|
220
192
|
|
|
221
|
-
export function roots(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
): DBBlockResult[] | [] {
|
|
227
|
-
|
|
193
|
+
// export function roots(
|
|
194
|
+
// params: Record<string, any>,
|
|
195
|
+
// options?: {
|
|
196
|
+
// depth?: number
|
|
197
|
+
// },
|
|
198
|
+
// ): DBBlockResult[] | [] {
|
|
199
|
+
// const { filterSql, sqlParams } = buildFilterSql(params)
|
|
200
|
+
|
|
201
|
+
// const query = rootQuery(filterSql, options?.depth)
|
|
202
|
+
// const rows = db.database
|
|
203
|
+
// .prepare(query)
|
|
204
|
+
// .all(sqlParams) as unknown as DBBlockResult[]
|
|
205
|
+
|
|
206
|
+
// if (!rows.length) return []
|
|
207
|
+
|
|
208
|
+
// const forest = buildForest(rows)
|
|
209
|
+
|
|
210
|
+
// return transformForest(forest)
|
|
211
|
+
// }
|
|
212
|
+
|
|
213
|
+
type DBRow = {
|
|
214
|
+
id: number
|
|
215
|
+
created_at: string
|
|
216
|
+
updated_at: string
|
|
217
|
+
name: string
|
|
218
|
+
label: string
|
|
219
|
+
type: string
|
|
220
|
+
sort_order: number
|
|
221
|
+
value: string | null
|
|
222
|
+
options: string | null
|
|
223
|
+
status: 'enabled' | 'disabled'
|
|
224
|
+
parent_id: number | null
|
|
225
|
+
depth: number
|
|
226
|
+
}
|
|
228
227
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
type TODO = any
|
|
229
|
+
|
|
230
|
+
function buildTree2(items: DBRow[]): TODO | null {
|
|
231
|
+
const map = new Map<number, TODO>();
|
|
233
232
|
|
|
234
|
-
|
|
233
|
+
// First pass: clone items into map
|
|
234
|
+
for (const item of items) {
|
|
235
|
+
map.set(item.id, { ...item });
|
|
236
|
+
}
|
|
235
237
|
|
|
236
|
-
|
|
238
|
+
let root: TODO | null = null;
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
// Second pass: assign blocks to parents
|
|
241
|
+
for (const item of map.values()) {
|
|
242
|
+
if (item.parent_id === null) {
|
|
243
|
+
root = item; // Root node
|
|
244
|
+
} else {
|
|
245
|
+
const parent = map.get(item.parent_id);
|
|
246
|
+
if (parent) {
|
|
247
|
+
if(parent.type === 'blocks') {
|
|
248
|
+
if (!parent.blocks) parent.blocks = [];
|
|
249
|
+
parent.blocks.push(item);
|
|
250
|
+
} else {
|
|
251
|
+
if (!parent.fields) parent.fields = {};
|
|
252
|
+
parent.fields[item.name] = item;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return root;
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
export function root(
|
|
@@ -247,13 +267,15 @@ export function root(
|
|
|
247
267
|
const query = rootQuery(filterSql, options?.depth)
|
|
248
268
|
const rows = db.database
|
|
249
269
|
.prepare(query)
|
|
250
|
-
.all(sqlParams) as unknown as
|
|
270
|
+
.all(sqlParams) as unknown as DBRow[]
|
|
251
271
|
|
|
252
272
|
if (!rows.length) return null
|
|
253
273
|
|
|
254
|
-
const tree = buildTree(rows)
|
|
274
|
+
// const tree = buildTree(rows)
|
|
275
|
+
|
|
276
|
+
const tree = buildTree2(rows)
|
|
255
277
|
|
|
256
|
-
return transformBlocksTree(tree)
|
|
278
|
+
return tree // transformBlocksTree(tree)
|
|
257
279
|
}
|
|
258
280
|
|
|
259
281
|
export function block(params: Record<string, any>) {
|