@korajs/cli 0.1.10 → 0.1.11
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/dist/bin.cjs +52 -8
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +2 -2
- package/dist/chunk-N36PFOSA.js +0 -0
- package/dist/{chunk-AUDUNRYA.js → chunk-SYOFLJLB.js} +33 -4
- package/dist/chunk-SYOFLJLB.js.map +1 -0
- package/dist/{chunk-ZVB4HAB3.js → chunk-VGOOQ56H.js} +22 -7
- package/dist/chunk-VGOOQ56H.js.map +1 -0
- package/dist/create.cjs +52 -8
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +21 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +13 -13
- package/templates/react-basic/src/App.tsx +129 -31
- package/templates/react-basic/src/index.css +281 -0
- package/templates/react-basic/src/main.tsx +2 -0
- package/templates/react-sync/kora.config.ts +1 -1
- package/templates/react-sync/server.ts +13 -2
- package/templates/react-sync/src/App.tsx +132 -32
- package/templates/react-sync/src/index.css +292 -0
- package/templates/react-sync/src/main.tsx +2 -0
- package/templates/react-tailwind/index.html.hbs +12 -0
- package/templates/react-tailwind/kora.config.ts +13 -0
- package/templates/react-tailwind/package.json.hbs +31 -0
- package/templates/react-tailwind/src/App.tsx +212 -0
- package/templates/react-tailwind/src/index.css +7 -0
- package/templates/react-tailwind/src/kora-worker.ts +3 -0
- package/templates/react-tailwind/src/main.tsx +23 -0
- package/templates/react-tailwind/src/schema.ts +15 -0
- package/templates/react-tailwind/tsconfig.json +14 -0
- package/templates/react-tailwind/vite.config.ts +40 -0
- package/templates/react-tailwind-sync/index.html.hbs +12 -0
- package/templates/react-tailwind-sync/kora.config.ts +17 -0
- package/templates/react-tailwind-sync/package.json.hbs +33 -0
- package/templates/react-tailwind-sync/server.ts +22 -0
- package/templates/react-tailwind-sync/src/App.tsx +241 -0
- package/templates/react-tailwind-sync/src/index.css +7 -0
- package/templates/react-tailwind-sync/src/kora-worker.ts +3 -0
- package/templates/react-tailwind-sync/src/main.tsx +29 -0
- package/templates/react-tailwind-sync/src/schema.ts +15 -0
- package/templates/react-tailwind-sync/tsconfig.json +14 -0
- package/templates/react-tailwind-sync/vite.config.ts +40 -0
- package/dist/chunk-AUDUNRYA.js.map +0 -1
- package/dist/chunk-ZVB4HAB3.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -4,7 +4,7 @@ import { KoraError, SchemaDefinition } from '@korajs/core';
|
|
|
4
4
|
declare const PACKAGE_MANAGERS: readonly ["pnpm", "npm", "yarn", "bun"];
|
|
5
5
|
type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
6
6
|
/** Available project templates */
|
|
7
|
-
declare const TEMPLATES: readonly ["react-
|
|
7
|
+
declare const TEMPLATES: readonly ["react-tailwind-sync", "react-tailwind", "react-sync", "react-basic"];
|
|
8
8
|
type TemplateName = (typeof TEMPLATES)[number];
|
|
9
9
|
/** Metadata for a project template */
|
|
10
10
|
interface TemplateInfo {
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { KoraError, SchemaDefinition } from '@korajs/core';
|
|
|
4
4
|
declare const PACKAGE_MANAGERS: readonly ["pnpm", "npm", "yarn", "bun"];
|
|
5
5
|
type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
6
6
|
/** Available project templates */
|
|
7
|
-
declare const TEMPLATES: readonly ["react-
|
|
7
|
+
declare const TEMPLATES: readonly ["react-tailwind-sync", "react-tailwind", "react-sync", "react-basic"];
|
|
8
8
|
type TemplateName = (typeof TEMPLATES)[number];
|
|
9
9
|
/** Metadata for a project template */
|
|
10
10
|
interface TemplateInfo {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korajs/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Kora.js CLI tooling and project scaffolding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -30,16 +30,6 @@
|
|
|
30
30
|
"dist",
|
|
31
31
|
"templates"
|
|
32
32
|
],
|
|
33
|
-
"dependencies": {
|
|
34
|
-
"citty": "^0.1.6",
|
|
35
|
-
"@korajs/core": "0.1.3"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"typescript": "^5.7.3",
|
|
39
|
-
"tsup": "^8.3.6",
|
|
40
|
-
"vitest": "^3.0.4"
|
|
41
|
-
},
|
|
42
|
-
"license": "MIT",
|
|
43
33
|
"scripts": {
|
|
44
34
|
"build": "tsup",
|
|
45
35
|
"dev": "tsup --watch",
|
|
@@ -48,5 +38,15 @@
|
|
|
48
38
|
"typecheck": "tsc --noEmit",
|
|
49
39
|
"lint": "biome check src/ tests/",
|
|
50
40
|
"clean": "rm -rf dist .turbo coverage"
|
|
51
|
-
}
|
|
52
|
-
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@korajs/core": "workspace:*",
|
|
44
|
+
"citty": "^0.1.6"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.7.3",
|
|
48
|
+
"tsup": "^8.3.6",
|
|
49
|
+
"vitest": "^3.0.4"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT"
|
|
52
|
+
}
|
|
@@ -1,48 +1,146 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
1
2
|
import { useQuery, useMutation, useCollection } from '@korajs/react'
|
|
2
3
|
|
|
4
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
5
|
+
|
|
3
6
|
export function App() {
|
|
4
7
|
const todos = useCollection('todos')
|
|
5
|
-
const
|
|
8
|
+
const allTodos = useQuery(todos.orderBy('createdAt', 'desc'))
|
|
6
9
|
const { mutate: addTodo } = useMutation(
|
|
7
10
|
(data: { title: string }) => todos.insert(data)
|
|
8
11
|
)
|
|
9
12
|
const { mutate: toggleTodo } = useMutation(
|
|
10
13
|
(id: string, data: { completed: boolean }) => todos.update(id, data)
|
|
11
14
|
)
|
|
15
|
+
const { mutate: deleteTodo } = useMutation(
|
|
16
|
+
(id: string) => todos.delete(id)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
const [filter, setFilter] = useState<Filter>('all')
|
|
20
|
+
const [input, setInput] = useState('')
|
|
21
|
+
|
|
22
|
+
const activeTodos = allTodos.filter((t) => !t.completed)
|
|
23
|
+
const completedTodos = allTodos.filter((t) => !!t.completed)
|
|
24
|
+
const filteredTodos =
|
|
25
|
+
filter === 'active' ? activeTodos : filter === 'completed' ? completedTodos : allTodos
|
|
26
|
+
|
|
27
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
28
|
+
e.preventDefault()
|
|
29
|
+
const title = input.trim()
|
|
30
|
+
if (title) {
|
|
31
|
+
addTodo({ title })
|
|
32
|
+
setInput('')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const clearCompleted = () => {
|
|
37
|
+
for (const todo of completedTodos) {
|
|
38
|
+
deleteTodo(todo.id)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
12
41
|
|
|
13
42
|
return (
|
|
14
|
-
<div
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<
|
|
43
|
+
<div className="app">
|
|
44
|
+
<div className="header">
|
|
45
|
+
<h1>My Tasks</h1>
|
|
46
|
+
<span className="local-badge">Local-first</span>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div className="stats">
|
|
50
|
+
<div className="stat-card">
|
|
51
|
+
<div className="label">Total</div>
|
|
52
|
+
<div className="value muted">{allTodos.length}</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="stat-card">
|
|
55
|
+
<div className="label">Remaining</div>
|
|
56
|
+
<div className="value warning">{activeTodos.length}</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="stat-card">
|
|
59
|
+
<div className="label">Done</div>
|
|
60
|
+
<div className="value success">{completedTodos.length}</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<form className="add-form" onSubmit={handleSubmit}>
|
|
65
|
+
<input
|
|
66
|
+
type="text"
|
|
67
|
+
value={input}
|
|
68
|
+
onChange={(e) => setInput(e.target.value)}
|
|
69
|
+
placeholder="What needs to be done?"
|
|
70
|
+
/>
|
|
71
|
+
<button type="submit" disabled={!input.trim()}>
|
|
72
|
+
Add
|
|
73
|
+
</button>
|
|
30
74
|
</form>
|
|
31
75
|
|
|
32
|
-
<
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
76
|
+
<div className="filters">
|
|
77
|
+
{(['all', 'active', 'completed'] as const).map((f) => {
|
|
78
|
+
const count = f === 'all' ? allTodos.length : f === 'active' ? activeTodos.length : completedTodos.length
|
|
79
|
+
return (
|
|
80
|
+
<button
|
|
81
|
+
key={f}
|
|
82
|
+
className={`filter-btn ${filter === f ? 'active' : ''}`}
|
|
83
|
+
onClick={() => setFilter(f)}
|
|
84
|
+
>
|
|
85
|
+
{f.charAt(0).toUpperCase() + f.slice(1)}
|
|
86
|
+
<span className="badge">{count}</span>
|
|
87
|
+
</button>
|
|
88
|
+
)
|
|
89
|
+
})}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="todo-list">
|
|
93
|
+
{filteredTodos.length === 0 ? (
|
|
94
|
+
<div className="empty-state">
|
|
95
|
+
{filter === 'all' && 'No tasks yet. Add one above!'}
|
|
96
|
+
{filter === 'active' && 'All caught up! No active tasks.'}
|
|
97
|
+
{filter === 'completed' && 'No completed tasks yet.'}
|
|
98
|
+
</div>
|
|
99
|
+
) : (
|
|
100
|
+
filteredTodos.map((todo) => (
|
|
101
|
+
<div key={todo.id} className="todo-item">
|
|
102
|
+
<button
|
|
103
|
+
className={`toggle ${todo.completed ? 'checked' : ''}`}
|
|
104
|
+
onClick={() => toggleTodo(todo.id, { completed: !todo.completed })}
|
|
105
|
+
>
|
|
106
|
+
{todo.completed ? '\u2713' : ''}
|
|
107
|
+
</button>
|
|
108
|
+
<span className={`title ${todo.completed ? 'done' : ''}`}>
|
|
109
|
+
{String(todo.title)}
|
|
110
|
+
</span>
|
|
111
|
+
{todo.createdAt && (
|
|
112
|
+
<span className="time">{formatTime(Number(todo.createdAt))}</span>
|
|
113
|
+
)}
|
|
114
|
+
<button
|
|
115
|
+
className="delete-btn"
|
|
116
|
+
onClick={() => deleteTodo(todo.id)}
|
|
117
|
+
>
|
|
118
|
+
\u00d7
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
))
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{allTodos.length > 0 && (
|
|
126
|
+
<div className="footer">
|
|
127
|
+
<span>{activeTodos.length} item{activeTodos.length !== 1 ? 's' : ''} left</span>
|
|
128
|
+
{completedTodos.length > 0 && (
|
|
129
|
+
<button onClick={clearCompleted}>Clear completed</button>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
<p className="branding">Powered by Kora — offline-first, real-time sync</p>
|
|
46
135
|
</div>
|
|
47
136
|
)
|
|
48
137
|
}
|
|
138
|
+
|
|
139
|
+
function formatTime(timestamp: number): string {
|
|
140
|
+
const date = new Date(timestamp)
|
|
141
|
+
const now = new Date()
|
|
142
|
+
if (date.toDateString() === now.toDateString()) {
|
|
143
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
144
|
+
}
|
|
145
|
+
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
146
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
*,
|
|
2
|
+
*::before,
|
|
3
|
+
*::after {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
margin: 0;
|
|
6
|
+
padding: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--color-bg: #0a0a0f;
|
|
11
|
+
--color-surface: #111118;
|
|
12
|
+
--color-border: #1e1e2a;
|
|
13
|
+
--color-border-hover: #2a2a3a;
|
|
14
|
+
--color-text: #e4e4e7;
|
|
15
|
+
--color-text-muted: #71717a;
|
|
16
|
+
--color-text-dim: #3f3f46;
|
|
17
|
+
--color-primary: #6366f1;
|
|
18
|
+
--color-primary-hover: #818cf8;
|
|
19
|
+
--color-success: #34d399;
|
|
20
|
+
--color-warning: #fbbf24;
|
|
21
|
+
--color-danger: #f87171;
|
|
22
|
+
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
23
|
+
--radius: 8px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
font-family: var(--font-sans);
|
|
28
|
+
background: var(--color-bg);
|
|
29
|
+
color: var(--color-text);
|
|
30
|
+
line-height: 1.6;
|
|
31
|
+
-webkit-font-smoothing: antialiased;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.app {
|
|
35
|
+
max-width: 640px;
|
|
36
|
+
margin: 0 auto;
|
|
37
|
+
padding: 48px 16px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.header {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
margin-bottom: 32px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.header h1 {
|
|
48
|
+
font-size: 1.5rem;
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.local-badge {
|
|
53
|
+
display: inline-flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 6px;
|
|
56
|
+
padding: 4px 12px;
|
|
57
|
+
border-radius: 999px;
|
|
58
|
+
background: var(--color-surface);
|
|
59
|
+
font-size: 0.8rem;
|
|
60
|
+
color: var(--color-text-muted);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.stats {
|
|
64
|
+
display: grid;
|
|
65
|
+
grid-template-columns: repeat(3, 1fr);
|
|
66
|
+
gap: 12px;
|
|
67
|
+
margin-bottom: 32px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.stat-card {
|
|
71
|
+
background: var(--color-surface);
|
|
72
|
+
border: 1px solid var(--color-border);
|
|
73
|
+
border-radius: var(--radius);
|
|
74
|
+
padding: 16px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.stat-card .label {
|
|
78
|
+
font-size: 0.8rem;
|
|
79
|
+
color: var(--color-text-muted);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.stat-card .value {
|
|
83
|
+
font-size: 1.5rem;
|
|
84
|
+
font-weight: 700;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.stat-card .value.muted { color: var(--color-text-muted); }
|
|
88
|
+
.stat-card .value.warning { color: var(--color-warning); }
|
|
89
|
+
.stat-card .value.success { color: var(--color-success); }
|
|
90
|
+
|
|
91
|
+
.add-form {
|
|
92
|
+
display: flex;
|
|
93
|
+
gap: 12px;
|
|
94
|
+
margin-bottom: 32px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.add-form input {
|
|
98
|
+
flex: 1;
|
|
99
|
+
padding: 10px 16px;
|
|
100
|
+
border: 1px solid var(--color-border);
|
|
101
|
+
border-radius: var(--radius);
|
|
102
|
+
background: var(--color-surface);
|
|
103
|
+
color: var(--color-text);
|
|
104
|
+
font-size: 0.95rem;
|
|
105
|
+
outline: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.add-form input:focus {
|
|
109
|
+
border-color: var(--color-primary);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.add-form input::placeholder {
|
|
113
|
+
color: var(--color-text-muted);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.add-form button {
|
|
117
|
+
padding: 10px 20px;
|
|
118
|
+
border: none;
|
|
119
|
+
border-radius: var(--radius);
|
|
120
|
+
background: var(--color-primary);
|
|
121
|
+
color: white;
|
|
122
|
+
font-size: 0.95rem;
|
|
123
|
+
font-weight: 500;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.add-form button:hover {
|
|
128
|
+
background: var(--color-primary-hover);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.add-form button:disabled {
|
|
132
|
+
opacity: 0.5;
|
|
133
|
+
cursor: not-allowed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.filters {
|
|
137
|
+
display: flex;
|
|
138
|
+
gap: 8px;
|
|
139
|
+
margin-bottom: 24px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.filter-btn {
|
|
143
|
+
padding: 6px 16px;
|
|
144
|
+
border: none;
|
|
145
|
+
border-radius: var(--radius);
|
|
146
|
+
background: var(--color-surface);
|
|
147
|
+
color: var(--color-text-muted);
|
|
148
|
+
font-size: 0.85rem;
|
|
149
|
+
font-weight: 500;
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.filter-btn:hover {
|
|
154
|
+
background: var(--color-border-hover);
|
|
155
|
+
color: var(--color-text);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.filter-btn.active {
|
|
159
|
+
background: var(--color-primary);
|
|
160
|
+
color: white;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.filter-btn .badge {
|
|
164
|
+
display: inline-block;
|
|
165
|
+
margin-left: 6px;
|
|
166
|
+
padding: 1px 6px;
|
|
167
|
+
border-radius: 999px;
|
|
168
|
+
font-size: 0.75rem;
|
|
169
|
+
background: rgba(255, 255, 255, 0.15);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.filter-btn:not(.active) .badge {
|
|
173
|
+
background: var(--color-border);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.todo-list {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: 8px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.todo-item {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 12px;
|
|
186
|
+
padding: 12px 16px;
|
|
187
|
+
border: 1px solid var(--color-border);
|
|
188
|
+
border-radius: var(--radius);
|
|
189
|
+
background: var(--color-surface);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.todo-item:hover {
|
|
193
|
+
border-color: var(--color-border-hover);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.todo-item .toggle {
|
|
197
|
+
background: none;
|
|
198
|
+
border: 2px solid var(--color-text-muted);
|
|
199
|
+
width: 20px;
|
|
200
|
+
height: 20px;
|
|
201
|
+
border-radius: 50%;
|
|
202
|
+
cursor: pointer;
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
justify-content: center;
|
|
206
|
+
color: transparent;
|
|
207
|
+
font-size: 0.7rem;
|
|
208
|
+
flex-shrink: 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.todo-item .toggle.checked {
|
|
212
|
+
border-color: var(--color-success);
|
|
213
|
+
background: var(--color-success);
|
|
214
|
+
color: white;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.todo-item .title {
|
|
218
|
+
flex: 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.todo-item .title.done {
|
|
222
|
+
text-decoration: line-through;
|
|
223
|
+
color: var(--color-text-muted);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.todo-item .time {
|
|
227
|
+
font-size: 0.75rem;
|
|
228
|
+
color: var(--color-text-dim);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.todo-item .delete-btn {
|
|
232
|
+
background: none;
|
|
233
|
+
border: none;
|
|
234
|
+
color: var(--color-text-dim);
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
padding: 4px;
|
|
237
|
+
opacity: 0;
|
|
238
|
+
transition: opacity 0.15s;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.todo-item:hover .delete-btn {
|
|
242
|
+
opacity: 1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.todo-item .delete-btn:hover {
|
|
246
|
+
color: var(--color-danger);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.empty-state {
|
|
250
|
+
padding: 48px 0;
|
|
251
|
+
text-align: center;
|
|
252
|
+
color: var(--color-text-dim);
|
|
253
|
+
border: 1px dashed var(--color-border);
|
|
254
|
+
border-radius: var(--radius);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.footer {
|
|
258
|
+
display: flex;
|
|
259
|
+
justify-content: space-between;
|
|
260
|
+
margin-top: 24px;
|
|
261
|
+
font-size: 0.85rem;
|
|
262
|
+
color: var(--color-text-muted);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.footer button {
|
|
266
|
+
background: none;
|
|
267
|
+
border: none;
|
|
268
|
+
color: var(--color-text-muted);
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.footer button:hover {
|
|
273
|
+
color: var(--color-text);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.branding {
|
|
277
|
+
margin-top: 48px;
|
|
278
|
+
text-align: center;
|
|
279
|
+
font-size: 0.75rem;
|
|
280
|
+
color: var(--color-text-dim);
|
|
281
|
+
}
|
|
@@ -4,12 +4,14 @@ import { StrictMode } from 'react'
|
|
|
4
4
|
import { createRoot } from 'react-dom/client'
|
|
5
5
|
import schema from './schema'
|
|
6
6
|
import { App } from './App'
|
|
7
|
+
import './index.css'
|
|
7
8
|
|
|
8
9
|
const app = createApp({
|
|
9
10
|
schema,
|
|
10
11
|
store: {
|
|
11
12
|
workerUrl: new URL('./kora-worker.ts', import.meta.url),
|
|
12
13
|
},
|
|
14
|
+
devtools: true,
|
|
13
15
|
})
|
|
14
16
|
|
|
15
17
|
createRoot(document.getElementById('root')!).render(
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import { createKoraServer,
|
|
1
|
+
import { createKoraServer, createSqliteServerStore } from '@korajs/server'
|
|
2
|
+
|
|
3
|
+
// SQLite persists data to disk — survives server restarts
|
|
4
|
+
const store = createSqliteServerStore({ filename: './kora-server.db' })
|
|
5
|
+
|
|
6
|
+
// To use PostgreSQL instead:
|
|
7
|
+
// 1. Install: pnpm add postgres
|
|
8
|
+
// 2. Replace the store above with:
|
|
9
|
+
//
|
|
10
|
+
// import { createPostgresServerStore } from '@korajs/server'
|
|
11
|
+
// const store = await createPostgresServerStore({
|
|
12
|
+
// connectionString: 'postgresql://user:password@localhost:5432/mydb',
|
|
13
|
+
// })
|
|
2
14
|
|
|
3
|
-
const store = new MemoryServerStore()
|
|
4
15
|
const server = createKoraServer({
|
|
5
16
|
store,
|
|
6
17
|
port: 3001,
|