no todo yet
This commit is contained in:
parent
8f91b676e9
commit
e7f5e8a636
|
|
@ -1 +0,0 @@
|
||||||
registry=https://npm.nose.space
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"configVersion": 1,
|
|
||||||
"workspaces": {
|
|
||||||
"": {
|
|
||||||
"name": "todo",
|
|
||||||
"dependencies": {
|
|
||||||
"@because/forge": "^0.0.1",
|
|
||||||
"@because/hype": "^0.0.2",
|
|
||||||
"@because/toes": "^0.0.5",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/bun": "latest",
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5.9.3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"packages": {
|
|
||||||
"@because/forge": ["@because/forge@0.0.1", "https://npm.nose.space/@because/forge/-/forge-0.0.1.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-QS5CK51gcWma91i4uECWe4HPJeNHcE+Af4SQHOcfEovyzOEa7VOTAjei+jIWr2i+abGWqQCEC9wIuFgPgyr2Bg=="],
|
|
||||||
|
|
||||||
"@because/hype": ["@because/hype@0.0.2", "https://npm.nose.space/@because/hype/-/hype-0.0.2.tgz", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "sha512-fdKeII6USGC1loVVj+tPz086cKz+Bm+XozNee3NOnK4VP+q4yNPP2Fq1Yujw5xeDYE+ZvJn40gKwlngRvmX2hA=="],
|
|
||||||
|
|
||||||
"@because/toes": ["@because/toes@0.0.5", "https://npm.nose.space/@because/toes/-/toes-0.0.5.tgz", { "dependencies": { "@because/forge": "^0.0.1", "@because/hype": "^0.0.2", "commander": "^14.0.2", "diff": "^8.0.3", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5.9.2" }, "bin": { "toes": "src/cli/index.ts" } }, "sha512-YM1VuR1sym7m7pFcaiqnjg6eJUyhJYUH2ROBb+xi+HEXajq46ZL8KDyyCtz7WiHTfrbxcEWGjqyj20a7UppcJg=="],
|
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.8", "https://npm.nose.space/@types/bun/-/bun-1.3.8.tgz", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.1.0", "https://npm.nose.space/@types/node/-/node-25.1.0.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="],
|
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.8", "https://npm.nose.space/bun-types/-/bun-types-1.3.8.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
|
||||||
|
|
||||||
"commander": ["commander@14.0.3", "https://npm.nose.space/commander/-/commander-14.0.3.tgz", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
|
||||||
|
|
||||||
"diff": ["diff@8.0.3", "https://npm.nose.space/diff/-/diff-8.0.3.tgz", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
|
||||||
|
|
||||||
"hono": ["hono@4.11.7", "https://npm.nose.space/hono/-/hono-4.11.7.tgz", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="],
|
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "https://npm.nose.space/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "https://npm.nose.space/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "https://npm.nose.space/undici-types/-/undici-types-7.16.0.tgz", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './src/server'
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"name": "todo",
|
|
||||||
"module": "index.tsx",
|
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"toes": "bun run --watch index.tsx",
|
|
||||||
"start": "bun toes",
|
|
||||||
"dev": "bun run --hot index.tsx"
|
|
||||||
},
|
|
||||||
"toes": {
|
|
||||||
"tool": "TODO",
|
|
||||||
"icon": "✅"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/bun": "latest"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5.9.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@because/forge": "^0.0.1",
|
|
||||||
"@because/hype": "^0.0.2",
|
|
||||||
"@because/toes": "^0.0.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
|
|
@ -1,36 +0,0 @@
|
||||||
import { render, useState } from 'hono/jsx/dom'
|
|
||||||
import { define } from '@because/forge'
|
|
||||||
|
|
||||||
const Wrapper = define({
|
|
||||||
margin: '0 auto',
|
|
||||||
marginTop: 50,
|
|
||||||
width: '50vw',
|
|
||||||
border: '1px solid black',
|
|
||||||
padding: 24,
|
|
||||||
textAlign: 'center'
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
const [count, setCount] = useState(0)
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<h1>It works!</h1>
|
|
||||||
<h2>Count: {count}</h2>
|
|
||||||
<div>
|
|
||||||
<button onClick={() => setCount(c => c + 1)}>+</button>
|
|
||||||
|
|
||||||
<button onClick={() => setCount(c => c && c - 1)}>-</button>
|
|
||||||
</div>
|
|
||||||
</Wrapper>
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Render error:', error)
|
|
||||||
return <><h1>Error</h1><pre>{error instanceof Error ? error : new Error(String(error))}</pre></>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = document.getElementById('root')!
|
|
||||||
render(<App />, root)
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
section {
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 200%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hype {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.3rem 0.8rem;
|
|
||||||
background: linear-gradient(45deg,
|
|
||||||
#ff00ff 0%,
|
|
||||||
#00ffff 33%,
|
|
||||||
#ffff00 66%,
|
|
||||||
#ff00ff 100%);
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: gradientShift 15s ease infinite;
|
|
||||||
color: black;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes gradientShift {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { $ } from 'bun'
|
|
||||||
|
|
||||||
const GIT_HASH = process.env.RENDER_GIT_COMMIT?.slice(0, 7)
|
|
||||||
|| await $`git rev-parse --short HEAD`.text().then(s => s.trim()).catch(() => 'unknown')
|
|
||||||
|
|
||||||
export default () => <>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>hype</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="color-scheme" content="light dark" />
|
|
||||||
|
|
||||||
<link href={`/css/main.css?${GIT_HASH}`} rel="stylesheet" />
|
|
||||||
<script dangerouslySetInnerHTML={{
|
|
||||||
__html: `
|
|
||||||
window.GIT_HASH = '${GIT_HASH}';
|
|
||||||
${(process.env.NODE_ENV !== 'production' || process.env.IS_PULL_REQUEST === 'true') ? 'window.DEBUG = true;' : ''}
|
|
||||||
`
|
|
||||||
}} />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="viewport">
|
|
||||||
<main>
|
|
||||||
<div id="root" />
|
|
||||||
<script src={`/client/app.js?${GIT_HASH}`} type="module" />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
</>
|
|
||||||
|
|
@ -1,366 +0,0 @@
|
||||||
import { Hype } from '@because/hype'
|
|
||||||
import { define, stylesToCSS } from '@because/forge'
|
|
||||||
import { baseStyles, ToolScript, theme } from '@because/toes/tools'
|
|
||||||
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
const APPS_DIR = process.env.APPS_DIR!
|
|
||||||
|
|
||||||
const app = new Hype({ prettyHTML: false })
|
|
||||||
|
|
||||||
const Container = define('TodoContainer', {
|
|
||||||
fontFamily: theme('fonts-sans'),
|
|
||||||
padding: '20px',
|
|
||||||
maxWidth: '800px',
|
|
||||||
margin: '0 auto',
|
|
||||||
color: theme('colors-text'),
|
|
||||||
})
|
|
||||||
|
|
||||||
const Header = define('Header', {
|
|
||||||
marginBottom: '20px',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
})
|
|
||||||
|
|
||||||
const Title = define('Title', {
|
|
||||||
margin: 0,
|
|
||||||
fontSize: '24px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
})
|
|
||||||
|
|
||||||
const AppName = define('AppName', {
|
|
||||||
color: theme('colors-textMuted'),
|
|
||||||
fontSize: '14px',
|
|
||||||
})
|
|
||||||
|
|
||||||
const TodoList = define('TodoList', {
|
|
||||||
listStyle: 'none',
|
|
||||||
padding: 0,
|
|
||||||
margin: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
const TodoSection = define('TodoSection', {
|
|
||||||
marginBottom: '24px',
|
|
||||||
})
|
|
||||||
|
|
||||||
const SectionTitle = define('SectionTitle', {
|
|
||||||
fontSize: '16px',
|
|
||||||
fontWeight: 600,
|
|
||||||
color: theme('colors-textMuted'),
|
|
||||||
marginBottom: '12px',
|
|
||||||
paddingBottom: '8px',
|
|
||||||
borderBottom: `1px solid ${theme('colors-border')}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const TodoItemStyle = define('TodoItem', {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
padding: '8px 0',
|
|
||||||
gap: '10px',
|
|
||||||
selectors: {
|
|
||||||
'& input[type="checkbox"]': {
|
|
||||||
marginTop: '3px',
|
|
||||||
width: '18px',
|
|
||||||
height: '18px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
},
|
|
||||||
'& label': {
|
|
||||||
flex: 1,
|
|
||||||
cursor: 'pointer',
|
|
||||||
lineHeight: '1.5',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const doneClass = 'todo-done'
|
|
||||||
|
|
||||||
const Error = define('Error', {
|
|
||||||
color: theme('colors-error'),
|
|
||||||
padding: '20px',
|
|
||||||
backgroundColor: theme('colors-bgElement'),
|
|
||||||
borderRadius: theme('radius-md'),
|
|
||||||
margin: '20px 0',
|
|
||||||
})
|
|
||||||
|
|
||||||
const successClass = 'msg-success'
|
|
||||||
const errorClass = 'msg-error'
|
|
||||||
|
|
||||||
const SaveButton = define('SaveButton', {
|
|
||||||
base: 'button',
|
|
||||||
backgroundColor: theme('colors-primary'),
|
|
||||||
color: theme('colors-primaryText'),
|
|
||||||
border: 'none',
|
|
||||||
padding: '8px 16px',
|
|
||||||
borderRadius: theme('radius-md'),
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '14px',
|
|
||||||
fontWeight: 500,
|
|
||||||
states: {
|
|
||||||
':hover': {
|
|
||||||
opacity: 0.9,
|
|
||||||
},
|
|
||||||
':disabled': {
|
|
||||||
opacity: 0.5,
|
|
||||||
cursor: 'not-allowed',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const AddForm = define('AddForm', {
|
|
||||||
display: 'flex',
|
|
||||||
gap: '10px',
|
|
||||||
marginBottom: '20px',
|
|
||||||
})
|
|
||||||
|
|
||||||
const AddInput = define('AddInput', {
|
|
||||||
base: 'input',
|
|
||||||
flex: 1,
|
|
||||||
padding: '8px 12px',
|
|
||||||
border: `1px solid ${theme('colors-border')}`,
|
|
||||||
borderRadius: theme('radius-md'),
|
|
||||||
fontSize: '14px',
|
|
||||||
backgroundColor: theme('colors-bg'),
|
|
||||||
color: theme('colors-text'),
|
|
||||||
states: {
|
|
||||||
':focus': {
|
|
||||||
outline: 'none',
|
|
||||||
borderColor: theme('colors-primary'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const todoStyles = `
|
|
||||||
.${doneClass} {
|
|
||||||
color: ${theme('colors-done')};
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
.${successClass} {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: ${theme('radius-md')};
|
|
||||||
margin-bottom: 16px;
|
|
||||||
background-color: ${theme('colors-successBg')};
|
|
||||||
color: ${theme('colors-success')};
|
|
||||||
}
|
|
||||||
.${errorClass} {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: ${theme('radius-md')};
|
|
||||||
margin-bottom: 16px;
|
|
||||||
background-color: ${theme('colors-bgElement')};
|
|
||||||
color: ${theme('colors-error')};
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
interface TodoEntry {
|
|
||||||
text: string
|
|
||||||
done: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ParsedTodo {
|
|
||||||
title: string
|
|
||||||
items: TodoEntry[]
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTodoFile(content: string): ParsedTodo {
|
|
||||||
const lines = content.split('\n')
|
|
||||||
let title = 'TODO'
|
|
||||||
const items: TodoEntry[] = []
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
const trimmed = line.trim()
|
|
||||||
if (!trimmed) continue
|
|
||||||
|
|
||||||
if (trimmed.startsWith('# ')) {
|
|
||||||
title = trimmed.slice(2)
|
|
||||||
} else if (trimmed.startsWith('[x] ') || trimmed.startsWith('[X] ')) {
|
|
||||||
items.push({ text: trimmed.slice(4), done: true })
|
|
||||||
} else if (trimmed.startsWith('[ ] ')) {
|
|
||||||
items.push({ text: trimmed.slice(4), done: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { title, items }
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeTodo(todo: ParsedTodo): string {
|
|
||||||
const lines = [`# ${todo.title}`]
|
|
||||||
for (const item of todo.items) {
|
|
||||||
lines.push(item.done ? `[x] ${item.text}` : `[ ] ${item.text}`)
|
|
||||||
}
|
|
||||||
return lines.join('\n') + '\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get('/ok', c => c.text('ok'))
|
|
||||||
|
|
||||||
app.get('/styles.css', c => c.text(baseStyles + todoStyles + stylesToCSS(), 200, {
|
|
||||||
'Content-Type': 'text/css; charset=utf-8',
|
|
||||||
}))
|
|
||||||
|
|
||||||
app.get('/', async c => {
|
|
||||||
const appName = c.req.query('app')
|
|
||||||
const message = c.req.query('message')
|
|
||||||
const messageType = c.req.query('type') as 'success' | 'error' | undefined
|
|
||||||
|
|
||||||
if (!appName) {
|
|
||||||
return c.html(
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>TODO</title>
|
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ToolScript />
|
|
||||||
<Container>
|
|
||||||
<Header>
|
|
||||||
<Title>TODO</Title>
|
|
||||||
</Header>
|
|
||||||
<Error>Select an app to view its TODO list</Error>
|
|
||||||
</Container>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const todoPath = join(APPS_DIR, appName, 'current', 'TODO.txt')
|
|
||||||
|
|
||||||
let todo: ParsedTodo
|
|
||||||
if (existsSync(todoPath)) {
|
|
||||||
const content = readFileSync(todoPath, 'utf-8')
|
|
||||||
todo = parseTodoFile(content)
|
|
||||||
} else {
|
|
||||||
todo = { title: `${appName} TODO`, items: [] }
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingItems = todo.items.filter(i => !i.done)
|
|
||||||
|
|
||||||
return c.html(
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>{todo.title}</title>
|
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ToolScript />
|
|
||||||
<Container>
|
|
||||||
<Header>
|
|
||||||
<div>
|
|
||||||
<Title>{todo.title}</Title>
|
|
||||||
<AppName>{appName}/TODO.txt</AppName>
|
|
||||||
</div>
|
|
||||||
</Header>
|
|
||||||
|
|
||||||
{message && (
|
|
||||||
<div class={messageType === 'success' ? successClass : errorClass}>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form action="/add" method="post">
|
|
||||||
<input type="hidden" name="app" value={appName} />
|
|
||||||
<AddForm>
|
|
||||||
<AddInput type="text" name="text" placeholder="Add a new todo..." required />
|
|
||||||
<SaveButton type="submit">Add</SaveButton>
|
|
||||||
</AddForm>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{todo.items.length > 0 && (
|
|
||||||
<TodoSection>
|
|
||||||
<SectionTitle>
|
|
||||||
{pendingItems.length === 0 ? 'All done!' : `Pending (${pendingItems.length})`}
|
|
||||||
</SectionTitle>
|
|
||||||
<TodoList>
|
|
||||||
{todo.items.map((item, i) => (
|
|
||||||
<TodoItemStyle key={i}>
|
|
||||||
<form action="/toggle" method="post" style={{ display: 'contents' }}>
|
|
||||||
<input type="hidden" name="app" value={appName} />
|
|
||||||
<input type="hidden" name="index" value={i.toString()} />
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id={`item-${i}`}
|
|
||||||
checked={item.done}
|
|
||||||
onchange="this.form.submit()"
|
|
||||||
/>
|
|
||||||
<label for={`item-${i}`} class={item.done ? doneClass : ''}>
|
|
||||||
{item.text}
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
</TodoItemStyle>
|
|
||||||
))}
|
|
||||||
</TodoList>
|
|
||||||
</TodoSection>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{todo.items.length === 0 && (
|
|
||||||
<TodoSection>
|
|
||||||
<SectionTitle>No todos yet</SectionTitle>
|
|
||||||
<p style={{ color: theme('colors-textMuted') }}>Add your first todo above!</p>
|
|
||||||
</TodoSection>
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/toggle', async c => {
|
|
||||||
const form = await c.req.formData()
|
|
||||||
const appName = form.get('app') as string
|
|
||||||
const index = parseInt(form.get('index') as string, 10)
|
|
||||||
|
|
||||||
const todoPath = join(APPS_DIR, appName, 'current', 'TODO.txt')
|
|
||||||
|
|
||||||
let todo: ParsedTodo
|
|
||||||
if (existsSync(todoPath)) {
|
|
||||||
const content = readFileSync(todoPath, 'utf-8')
|
|
||||||
todo = parseTodoFile(content)
|
|
||||||
} else {
|
|
||||||
return c.redirect(`/?app=${appName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = todo.items[index]
|
|
||||||
if (item) {
|
|
||||||
item.done = !item.done
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
writeFileSync(todoPath, serializeTodo(todo))
|
|
||||||
return c.redirect(`/?app=${appName}`)
|
|
||||||
} catch {
|
|
||||||
return c.redirect(`/?app=${appName}&message=${encodeURIComponent('Failed to save')}&type=error`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/add', async c => {
|
|
||||||
const form = await c.req.formData()
|
|
||||||
const appName = form.get('app') as string
|
|
||||||
const text = (form.get('text') as string).trim()
|
|
||||||
|
|
||||||
if (!text) {
|
|
||||||
return c.redirect(`/?app=${appName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const todoPath = join(APPS_DIR, appName, 'current', 'TODO.txt')
|
|
||||||
|
|
||||||
let todo: ParsedTodo
|
|
||||||
if (existsSync(todoPath)) {
|
|
||||||
const content = readFileSync(todoPath, 'utf-8')
|
|
||||||
todo = parseTodoFile(content)
|
|
||||||
} else {
|
|
||||||
todo = { title: `${appName} TODO`, items: [] }
|
|
||||||
}
|
|
||||||
|
|
||||||
todo.items.push({ text, done: false })
|
|
||||||
|
|
||||||
try {
|
|
||||||
writeFileSync(todoPath, serializeTodo(todo))
|
|
||||||
return c.redirect(`/?app=${appName}`)
|
|
||||||
} catch {
|
|
||||||
return c.redirect(`/?app=${appName}&message=${encodeURIComponent('Failed to add')}&type=error`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default app.defaults
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["ESNext"],
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "Preserve",
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"jsxImportSource": "hono/jsx",
|
|
||||||
"allowJs": true,
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noPropertyAccessFromIndexSignature": false,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"$*": ["src/server/*"],
|
|
||||||
"#*": ["src/client/*"],
|
|
||||||
"@*": ["src/shared/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user