@alstar/studio 0.0.0-beta.5 → 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.
Files changed (41) hide show
  1. package/api/api-key.ts +74 -0
  2. package/api/block.ts +21 -29
  3. package/api/index.ts +9 -1
  4. package/api/mcp.ts +53 -0
  5. package/bin/alstar.ts +42 -0
  6. package/components/{AdminPanel/AdminPanel.ts → AdminPanel.ts} +22 -27
  7. package/components/Block.ts +51 -112
  8. package/components/Entries.ts +3 -3
  9. package/components/Entry.ts +9 -15
  10. package/components/Settings.ts +98 -0
  11. package/components/fields/Blocks.ts +118 -0
  12. package/components/fields/Text.ts +42 -0
  13. package/components/fields/index.ts +4 -0
  14. package/components/icons.ts +59 -0
  15. package/components/index.ts +1 -1
  16. package/components/layout.ts +2 -2
  17. package/index.ts +33 -14
  18. package/package.json +4 -3
  19. package/public/admin-panel.css +90 -0
  20. package/public/blocks.css +53 -0
  21. package/public/main.css +8 -0
  22. package/public/main.js +4 -0
  23. package/public/settings.css +24 -0
  24. package/queries/block-with-children.ts +74 -0
  25. package/queries/block.ts +29 -40
  26. package/queries/db-types.ts +15 -0
  27. package/queries/getBlockTrees-2.ts +0 -0
  28. package/queries/getBlockTrees.ts +316 -0
  29. package/queries/getBlocks.ts +214 -0
  30. package/queries/index.ts +1 -0
  31. package/queries/structure-types.ts +97 -0
  32. package/schemas.ts +14 -3
  33. package/types.ts +84 -5
  34. package/utils/buildBlocksTree.ts +4 -4
  35. package/utils/define.ts +18 -4
  36. package/utils/get-or-create-row.ts +28 -0
  37. package/utils/startup-log.ts +9 -0
  38. package/components/AdminPanel/AdminPanel.css +0 -78
  39. package/components/Field.ts +0 -168
  40. package/components/Fields.ts +0 -43
  41. /package/{components/Entry.css → public/entry.css} +0 -0
@@ -0,0 +1,98 @@
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
+
6
+ export default () => {
7
+ const apiKeys = db.database
8
+ .prepare(sql`
9
+ select
10
+ *
11
+ from
12
+ api_keys
13
+ `)
14
+ .all()
15
+
16
+ return html`
17
+ <div id="settings" data-signals="{ apiKey: '', copied: false }">
18
+ <!-- <h1>Settings</h1> -->
19
+ <article>
20
+ <header>API Keys</header>
21
+
22
+ <ul>
23
+ ${apiKeys.map((apiKey) => {
24
+ return html`<li>
25
+ <p>${apiKey.name}</p>
26
+ <input type="text" disabled value="${apiKey.hint}" />
27
+ <form
28
+ data-on-submit="@delete('/admin/api/api-key', { contentType: 'form' })"
29
+ >
30
+ <button
31
+ data-tooltip="Delete API key"
32
+ data-placement="left"
33
+ type="submit"
34
+ class="ghost"
35
+ >
36
+ ${icons.trash}
37
+ </button>
38
+
39
+ <input type="hidden" name="value" value="${apiKey.value}" />
40
+ </form>
41
+ </li>`
42
+ })}
43
+ </ul>
44
+
45
+ <form
46
+ data-on-submit="@post('/admin/api/api-key', { contentType: 'form' })"
47
+ >
48
+ <label for="api_key_name">Generate API Key</label>
49
+
50
+ <input
51
+ data-bind="name"
52
+ type="text"
53
+ name="name"
54
+ id="api_key_name"
55
+ placeholder="Name"
56
+ />
57
+
58
+ <button type="submit" class="ghost">Generate key</button>
59
+ </form>
60
+
61
+ <dialog data-attr="{ open: $apiKey !== '' }">
62
+ <article>
63
+ <header>
64
+ <p>API Key</p>
65
+ </header>
66
+ <p>Be sure to save this key, as it wont be shown again.</p>
67
+
68
+ <div style="display: flex; gap: 1rem; align-items: center;">
69
+ <h3 style="margin: 0;">
70
+ <code data-text="$apiKey"></code>
71
+ </h3>
72
+
73
+ <button
74
+ style="display: flex; align-items: center;"
75
+ data-attr="{ id: $apiKey }"
76
+ data-on-click="navigator.clipboard.writeText($apiKey); $copied = true"
77
+ class="ghost"
78
+ aria-label="Copy key to clipboard"
79
+ >
80
+ ${icons.clipboard}
81
+ <span style="display: none; margin-left: 0.5rem; color: green;" data-style="{ display: $copied && 'block' }">Copied</span>
82
+ </button>
83
+ </div>
84
+
85
+ <footer>
86
+ <button
87
+ class="ghost"
88
+ data-on-click="$apiKey = ''; $copied = false; evt.target.closest('dialog')?.close()"
89
+ >
90
+ Close
91
+ </button>
92
+ </footer>
93
+ </article>
94
+ </dialog>
95
+ </article>
96
+ </div>
97
+ `
98
+ }
@@ -0,0 +1,118 @@
1
+ import { query } from '../../queries/index.ts'
2
+ import type { Field, Block as BlockType, BlocksField } from '../../types.ts'
3
+ import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
4
+ import { html } from '../../utils/html.ts'
5
+ import Block from '../Block.ts'
6
+ import * as icons from '../icons.ts'
7
+
8
+ export default (props: {
9
+ entryId: number
10
+ parentId: number
11
+ name: string
12
+ field: BlocksField
13
+ sortOrder: number
14
+ }) => {
15
+ const { entryId, parentId, name, field, sortOrder = 0 } = props
16
+
17
+ const data = getOrCreateRow(parentId, name, field, sortOrder)
18
+
19
+ if (!data) return html`<p>No block</p>`
20
+
21
+ const entries = Object.entries(field.children) as [
22
+ string,
23
+ BlockType | Field,
24
+ ][]
25
+ const rows = query.blocks({ parent_id: data.id })
26
+
27
+ return html`
28
+ <div class="blocks-field">
29
+ <header>
30
+ <p>${field.label}</p>
31
+
32
+ <details class="dropdown">
33
+ <summary>Add</summary>
34
+ <ul>
35
+ ${entries.map(([name, block]) => {
36
+ return html`
37
+ <li>
38
+ <form
39
+ data-on-submit="@post('/admin/api/new-block', { contentType: 'form' })"
40
+ >
41
+ <button type="submit" class="ghost">${block.label}</button>
42
+ <input type="hidden" name="type" value="${block.type}" />
43
+ <input type="hidden" name="name" value="${name}" />
44
+ <input type="hidden" name="label" value="${block.label}" />
45
+ <input type="hidden" name="parent_id" value="${data.id}" />
46
+ <input type="hidden" name="entry_id" value="${entryId}" />
47
+ <input
48
+ type="hidden"
49
+ name="sort_order"
50
+ value="${rows.length}"
51
+ />
52
+ </form>
53
+ </li>
54
+ `
55
+ })}
56
+ </ul>
57
+ </details>
58
+ </header>
59
+
60
+ <hr style="margin-top: 0;" />
61
+
62
+ <div data-sortable="${data.id}">
63
+ ${rows.map((row, idx) => {
64
+ const [name, struct] = (entries.find(([name]) => name === row.name) ||
65
+ []) as [string, BlockType]
66
+
67
+ if (!name) return html``
68
+
69
+ return html`
70
+ <article>
71
+ <header>
72
+ ${struct.label}
73
+
74
+ <!-- data-tooltip="Move"
75
+ data-placement="top" -->
76
+ <aside>
77
+ <button
78
+ data-handle="${data.id}"
79
+ class="ghost handle text-secondary"
80
+ style="cursor: grab"
81
+ >
82
+ ${icons.bars}
83
+ </button>
84
+
85
+ <form
86
+ data-on-submit="@delete('/admin/api/block', { contentType: 'form' })"
87
+ >
88
+ <button
89
+ type="submit"
90
+ class="ghost text-secondary"
91
+ data-tooltip="Remove"
92
+ data-placement="top"
93
+ aria-label="Delete block"
94
+ >
95
+ ${icons.x}
96
+ </button>
97
+ <input type="hidden" name="id" value="${row.id}" />
98
+ <input type="hidden" name="entry_id" value="${entryId}" />
99
+ </form>
100
+ </aside>
101
+ </header>
102
+
103
+ ${Block({
104
+ entryId,
105
+ parentId: struct.fields ? row.id : data.id,
106
+ blockStructure: struct,
107
+ name: name,
108
+ sortOrder: row.sort_order,
109
+ })}
110
+
111
+ <!-- <pre><code>{JSON.stringify(struct, null, 2)} - {row.sort_order}</code></pre> -->
112
+ </article>
113
+ `
114
+ })}
115
+ </div>
116
+ </div>
117
+ `
118
+ }
@@ -0,0 +1,42 @@
1
+ import type { FieldDef } 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
+ field: FieldDef
10
+ sortOrder: number
11
+ }) => {
12
+ const { entryId, parentId, name, field, sortOrder = 0 } = props
13
+
14
+ const data = getOrCreateRow(parentId, name, field, sortOrder)
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}">${field.label}</label>
24
+ <p><small>${field.description}</small></p>
25
+ </hgroup>
26
+
27
+ <input
28
+ id="block-${data.id}"
29
+ name="value"
30
+ type="text"
31
+ value="${data.value}"
32
+ />
33
+
34
+ <input type="hidden" name="type" value="${field.type}" />
35
+ <input type="hidden" name="id" value="${data.id}" />
36
+ <input type="hidden" name="entryId" value="${entryId}" />
37
+ <input type="hidden" name="parentId" value="${parentId}" />
38
+ <input type="hidden" name="sort_order" value="${sortOrder}" />
39
+ <input type="hidden" name="name" value="${name}" />
40
+ </form>
41
+ `
42
+ }
@@ -0,0 +1,4 @@
1
+ import Text from './Text.ts'
2
+ import Blocks from './Blocks.ts'
3
+
4
+ export const Field = { Text, Blocks }
@@ -135,3 +135,62 @@ export const trash = html`
135
135
  />
136
136
  </svg>
137
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
+ `
@@ -26,5 +26,5 @@ export default () => {
26
26
  </article>
27
27
  </div>`
28
28
 
29
- return html`${!structure.length ? Discamer : ''}`
29
+ return html`${!structure ? Discamer : ''}`
30
30
  }
@@ -1,4 +1,4 @@
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
4
  import { rootdir, studioConfig } from '../index.ts'
@@ -50,7 +50,7 @@ export default (props: {
50
50
  </head>
51
51
 
52
52
  <body data-barba="wrapper">
53
- <section>${adminPanel(props.structure)}</section>
53
+ <section style="margin-bottom: 0;">${adminPanel(props.structure)}</section>
54
54
 
55
55
  <main>
56
56
  <section data-barba="container" data-barba-namespace="default">
package/index.ts CHANGED
@@ -1,33 +1,45 @@
1
1
  import { Hono } from 'hono'
2
2
  import { loadDb } from '@alstar/db'
3
- import { sectionRoutes } from './api/index.ts'
4
- import { getConfig } from './utils/get-config.ts'
5
- import * as types from './types.ts'
3
+ import { serve } from '@hono/node-server'
4
+ import { serveStatic } from '@hono/node-server/serve-static'
5
+ import { createRefresher } from '@alstar/refresher'
6
6
 
7
7
  import Layout from './components/layout.ts'
8
8
  import IndexPage from './components/index.ts'
9
+ import SettingsPage from './components/Settings.ts'
9
10
  import Entry from './components/Entry.ts'
10
11
 
11
- import { serve } from '@hono/node-server'
12
- import { serveStatic } from '@hono/node-server/serve-static'
12
+ import * as types from './types.ts'
13
13
  import { createStudioTables } from './utils/create-studio-tables.ts'
14
14
  import { fileBasedRouter } from './utils/file-based-router.ts'
15
+ import { getConfig } from './utils/get-config.ts'
16
+ import startupLog from './utils/startup-log.ts'
17
+ import { api } from './api/index.ts'
18
+ import mcp from './api/mcp.ts'
15
19
 
16
20
  export let structure: types.Structure
17
21
  export let rootdir = '/node_modules/@alstar/studio'
18
22
 
19
23
  export let studioConfig: types.StudioConfig = {
20
24
  siteName: '',
25
+ structure: {}
21
26
  }
22
27
 
23
- const createStudio = async (studioStructure?: types.Structure) => {
28
+ const createStudio = async (config: types.StudioConfig) => {
29
+ startupLog()
30
+
31
+ createRefresher({ rootdir: '.' })
32
+
24
33
  loadDb('./studio.db')
25
34
  createStudioTables()
26
35
 
27
- const configFile = await getConfig<types.StudioConfig>()
36
+ // const configFile = await getConfig<types.StudioConfig>()
37
+
38
+ if (config.structure) {
39
+ structure = config.structure
40
+ }
28
41
 
29
- structure = studioStructure || []
30
- studioConfig = { ...studioConfig, ...configFile }
42
+ studioConfig = { ...studioConfig, ...config }
31
43
 
32
44
  const app = new Hono(studioConfig.honoConfig)
33
45
 
@@ -35,18 +47,19 @@ const createStudio = async (studioStructure?: types.Structure) => {
35
47
  app.use('*', serveStatic({ root: './public' }))
36
48
 
37
49
  app.get('/admin', (c) => c.html(Layout({ structure, content: IndexPage() })))
38
-
39
- app.route('/admin/api', sectionRoutes(structure))
40
-
50
+ app.get('/admin/settings', (c) => c.html(Layout({ structure, content: SettingsPage() })))
41
51
  app.get('/admin/entry/:id', (c) => {
42
52
  return c.html(
43
53
  Layout({
44
54
  structure,
45
- content: Entry({ structure, entryId: c.req.param('id') }),
55
+ content: Entry({ entryId: parseInt(c.req.param('id')) }),
46
56
  }),
47
57
  )
48
58
  })
49
59
 
60
+ app.route('/admin/api', api(structure))
61
+ app.route('/admin/mcp', mcp())
62
+
50
63
  const pages = await fileBasedRouter('./pages')
51
64
 
52
65
  if (pages) {
@@ -73,7 +86,13 @@ const createStudio = async (studioStructure?: types.Structure) => {
73
86
  return app
74
87
  }
75
88
 
76
- export { defineConfig, defineEntry, defineStructure } from './utils/define.ts'
89
+ export {
90
+ defineConfig,
91
+ defineEntry,
92
+ defineStructure,
93
+ defineBlock,
94
+ defineField,
95
+ } from './utils/define.ts'
77
96
  export { type RequestContext } from './types.ts'
78
97
  export { createStudio }
79
98
  export { html, type HtmlEscapedString } from './utils/html.ts'
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@alstar/studio",
3
- "version": "0.0.0-beta.5",
3
+ "version": "0.0.0-beta.6",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "dependencies": {
7
7
  "@hono/node-server": "^1.18.1",
8
8
  "@starfederation/datastar-sdk": "1.0.0-RC.1",
9
9
  "hono": "^4.8.12",
10
- "@alstar/ui": "0.0.0-beta.1",
11
- "@alstar/db": "0.0.0-beta.1"
10
+ "@alstar/db": "0.0.0-beta.1",
11
+ "@alstar/refresher": "0.0.0-beta.2",
12
+ "@alstar/ui": "0.0.0-beta.1"
12
13
  },
13
14
  "devDependencies": {
14
15
  "@types/node": "^24.1.0",
@@ -0,0 +1,90 @@
1
+ .admin-panel {
2
+ /* background: hsla(0, 0%, 0%, 0.1); */
3
+ padding: 40px;
4
+ margin-bottom: 0;
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: flex-start;
8
+
9
+ height: 100%;
10
+ min-height: inherit;
11
+
12
+ min-width: 250px;
13
+
14
+ > h1 {
15
+ padding-bottom: 1rem;
16
+
17
+ a {
18
+ display: flex;
19
+ }
20
+
21
+ svg {
22
+ height: 1.6rem;
23
+ }
24
+ }
25
+
26
+ form {
27
+ padding-bottom: 1rem;
28
+
29
+ button {
30
+ margin: 10px 0px 20px;
31
+ }
32
+ }
33
+
34
+ #entries {
35
+ width: 100%;
36
+
37
+ ul {
38
+ padding: 0;
39
+ margin-inline: -1rem;
40
+
41
+ form {
42
+ display: flex;
43
+ padding-bottom: 0;
44
+
45
+ button {
46
+ margin: 0;
47
+ }
48
+ }
49
+
50
+ > li {
51
+ margin-bottom: 0px;
52
+ border-radius: 8px;
53
+ display: flex;
54
+ justify-content: space-between;
55
+ align-items: stretch;
56
+ list-style: none;
57
+
58
+ a {
59
+ text-decoration: none;
60
+ width: 100%;
61
+ padding: 0.5rem 1rem;
62
+ }
63
+
64
+ button {
65
+ border-radius: 7px;
66
+ opacity: 0;
67
+ transition: opacity 100px;
68
+
69
+ svg {
70
+ margin: 0.5rem 1rem;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ > footer {
78
+ margin-top: auto;
79
+ }
80
+ }
81
+
82
+ #entries ul {
83
+ > li:hover {
84
+ background-color: var(--pico-form-element-background-color);
85
+
86
+ button {
87
+ opacity: 1;
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,53 @@
1
+ .blocks-field {
2
+ > header {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ align-items: center;
6
+
7
+ > form fieldset {
8
+ button {
9
+ padding-inline: 1rem;
10
+ }
11
+ }
12
+
13
+ details {
14
+ form {
15
+ display: flex;
16
+ }
17
+
18
+ button {
19
+ width: 100%;
20
+ margin-bottom: 0;
21
+ text-align: left;
22
+ }
23
+
24
+ summary + ul {
25
+ right: 0;
26
+ left: auto;
27
+ }
28
+ }
29
+ }
30
+
31
+ article {
32
+ > header {
33
+ display: flex;
34
+ justify-content: space-between;
35
+ align-items: center;
36
+
37
+ aside {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 5px;
41
+ }
42
+
43
+ button {
44
+ margin: 0;
45
+ padding: 0;
46
+
47
+ svg {
48
+ padding: 5px;
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
package/public/main.css CHANGED
@@ -1,5 +1,9 @@
1
1
  /* @import './../node_modules/@alstar/ui/red.css'; */
2
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';
3
7
 
4
8
  body {
5
9
  min-height: 100vh;
@@ -93,3 +97,7 @@ div.ink-mde {
93
97
  display: flex;
94
98
  justify-content: center;
95
99
  }
100
+
101
+ .text-secondary {
102
+ color: var(--pico-secondary);
103
+ }
package/public/main.js CHANGED
@@ -3,6 +3,7 @@ import Sortable from 'sortablejs'
3
3
  // import { wrap } from 'ink-mde'
4
4
 
5
5
  barba.init({
6
+ cacheIgnore: true,
6
7
  views: [
7
8
  {
8
9
  namespace: 'default',
@@ -19,11 +20,14 @@ function init() {
19
20
  const els = document.querySelectorAll('[data-sortable]')
20
21
 
21
22
  els.forEach((el) => {
23
+ const id = el.dataset.sortable
24
+
22
25
  var sortable = Sortable.create(el, {
23
26
  delay: 0,
24
27
  animation: 250,
25
28
  dragClass: 'sortable-drag',
26
29
  easing: 'ease-in-out',
30
+ handle: `[data-handle="${id}"]`,
27
31
  })
28
32
  })
29
33
  }
@@ -0,0 +1,24 @@
1
+ #settings {
2
+ ul {
3
+ padding: 0;
4
+ }
5
+
6
+ li {
7
+ width: 100%;
8
+ display: grid;
9
+ align-items: center;
10
+ grid-template-columns: 15% 1fr auto;
11
+ gap: 1rem;
12
+ border-bottom: 1px solid var(--pico-form-element-border-color);
13
+ margin-bottom: 0;
14
+ padding: 1rem 0;
15
+
16
+ input {
17
+ font-family: var(--pico-font-family-monospace);
18
+ }
19
+
20
+ * {
21
+ margin-bottom: 0;
22
+ }
23
+ }
24
+ }