{() => JSON.stringify(pages(), null, " ")}+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3fa6c04
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# Quot
diff --git a/app/.dockerignore b/app/.dockerignore
new file mode 120000
index 0000000..3e4e48b
--- /dev/null
+++ b/app/.dockerignore
@@ -0,0 +1 @@
+.gitignore
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..6ed48a9
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,2 @@
+.env
+node_modules
diff --git a/app/Dockerfile b/app/Dockerfile
new file mode 100644
index 0000000..8710b6e
--- /dev/null
+++ b/app/Dockerfile
@@ -0,0 +1,5 @@
+FROM node:18.7.0-alpine
+WORKDIR /app
+COPY . /app
+RUN npm ci --production
+CMD ["npm", "start"]
diff --git a/app/compose.yml b/app/compose.yml
new file mode 100644
index 0000000..c713e06
--- /dev/null
+++ b/app/compose.yml
@@ -0,0 +1,10 @@
+services:
+ app:
+ image: kou029w/quot-app
+ build: "."
+ restart: unless-stopped
+ init: true
+ user: ${UID:-1000}:${GID:-1000}
+ ports: ["8080:8080"]
+ volumes:
+ - ".:/app"
diff --git a/app/package-lock.json b/app/package-lock.json
new file mode 100644
index 0000000..61b6f08
--- /dev/null
+++ b/app/package-lock.json
@@ -0,0 +1,189 @@
+{
+ "name": "@quot/app",
+ "version": "0.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@quot/app",
+ "version": "0.0.0",
+ "dependencies": {
+ "@exampledev/new.css": "^1.1.3",
+ "esbuild": "^0.15.5",
+ "esbuild-register": "^3.3.3",
+ "solid-js": "^1.4.8"
+ },
+ "devDependencies": {
+ "@tsconfig/node18-strictest-esm": "^1.0.0",
+ "@types/node": "^18.7.8",
+ "typescript": "^4.7.4"
+ },
+ "engines": {
+ "node": "^18.7.0"
+ }
+ },
+ "node_modules/@exampledev/new.css": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@exampledev/new.css/-/new.css-1.1.3.tgz",
+ "integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w=="
+ },
+ "node_modules/@tsconfig/node18-strictest-esm": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.0.tgz",
+ "integrity": "sha512-4lY2mZXGFaW13OYcz6kwWFussLbIAg5XBlS2h72jzr4mqr/CuFmF04S7hkpBYbw0k/TNQ4tFLx1/j6VpBqr3Tg==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "18.7.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.8.tgz",
+ "integrity": "sha512-/YP55EMK2341JkODUb8DM9O0x1SIz2aBvyF33Uf1c76St3VpsMXEIW0nxuKkq/5cxnbz0RD9cfwNZHEAZQD3ag==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.15.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz",
+ "integrity": "sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg==",
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/linux-loong64": "0.15.5",
+ "esbuild-android-64": "0.15.5",
+ "esbuild-android-arm64": "0.15.5",
+ "esbuild-darwin-64": "0.15.5",
+ "esbuild-darwin-arm64": "0.15.5",
+ "esbuild-freebsd-64": "0.15.5",
+ "esbuild-freebsd-arm64": "0.15.5",
+ "esbuild-linux-32": "0.15.5",
+ "esbuild-linux-64": "0.15.5",
+ "esbuild-linux-arm": "0.15.5",
+ "esbuild-linux-arm64": "0.15.5",
+ "esbuild-linux-mips64le": "0.15.5",
+ "esbuild-linux-ppc64le": "0.15.5",
+ "esbuild-linux-riscv64": "0.15.5",
+ "esbuild-linux-s390x": "0.15.5",
+ "esbuild-netbsd-64": "0.15.5",
+ "esbuild-openbsd-64": "0.15.5",
+ "esbuild-sunos-64": "0.15.5",
+ "esbuild-windows-32": "0.15.5",
+ "esbuild-windows-64": "0.15.5",
+ "esbuild-windows-arm64": "0.15.5"
+ }
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.15.5",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz",
+ "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-register": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.3.3.tgz",
+ "integrity": "sha512-eFHOkutgIMJY5gc8LUp/7c+LLlDqzNi9T6AwCZ2WKKl3HmT+5ef3ZRyPPxDOynInML0fgaC50yszPKfPnjC0NQ==",
+ "peerDependencies": {
+ "esbuild": ">=0.12 <1"
+ }
+ },
+ "node_modules/solid-js": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.4.8.tgz",
+ "integrity": "sha512-XErZdnnYYXF7OwGSUAPcua2y5/ELB/c53zFCpWiEGqxTNoH1iQghzI8EsHJXk06sNn+Z/TGhb8bPDNNGSgimag=="
+ },
+ "node_modules/typescript": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
+ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ }
+ },
+ "dependencies": {
+ "@exampledev/new.css": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@exampledev/new.css/-/new.css-1.1.3.tgz",
+ "integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w=="
+ },
+ "@tsconfig/node18-strictest-esm": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.0.tgz",
+ "integrity": "sha512-4lY2mZXGFaW13OYcz6kwWFussLbIAg5XBlS2h72jzr4mqr/CuFmF04S7hkpBYbw0k/TNQ4tFLx1/j6VpBqr3Tg==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "18.7.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.8.tgz",
+ "integrity": "sha512-/YP55EMK2341JkODUb8DM9O0x1SIz2aBvyF33Uf1c76St3VpsMXEIW0nxuKkq/5cxnbz0RD9cfwNZHEAZQD3ag==",
+ "dev": true
+ },
+ "esbuild": {
+ "version": "0.15.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz",
+ "integrity": "sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg==",
+ "requires": {
+ "@esbuild/linux-loong64": "0.15.5",
+ "esbuild-android-64": "0.15.5",
+ "esbuild-android-arm64": "0.15.5",
+ "esbuild-darwin-64": "0.15.5",
+ "esbuild-darwin-arm64": "0.15.5",
+ "esbuild-freebsd-64": "0.15.5",
+ "esbuild-freebsd-arm64": "0.15.5",
+ "esbuild-linux-32": "0.15.5",
+ "esbuild-linux-64": "0.15.5",
+ "esbuild-linux-arm": "0.15.5",
+ "esbuild-linux-arm64": "0.15.5",
+ "esbuild-linux-mips64le": "0.15.5",
+ "esbuild-linux-ppc64le": "0.15.5",
+ "esbuild-linux-riscv64": "0.15.5",
+ "esbuild-linux-s390x": "0.15.5",
+ "esbuild-netbsd-64": "0.15.5",
+ "esbuild-openbsd-64": "0.15.5",
+ "esbuild-sunos-64": "0.15.5",
+ "esbuild-windows-32": "0.15.5",
+ "esbuild-windows-64": "0.15.5",
+ "esbuild-windows-arm64": "0.15.5"
+ }
+ },
+ "esbuild-linux-64": {
+ "version": "0.15.5",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz",
+ "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==",
+ "optional": true
+ },
+ "esbuild-register": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.3.3.tgz",
+ "integrity": "sha512-eFHOkutgIMJY5gc8LUp/7c+LLlDqzNi9T6AwCZ2WKKl3HmT+5ef3ZRyPPxDOynInML0fgaC50yszPKfPnjC0NQ==",
+ "requires": {}
+ },
+ "solid-js": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.4.8.tgz",
+ "integrity": "sha512-XErZdnnYYXF7OwGSUAPcua2y5/ELB/c53zFCpWiEGqxTNoH1iQghzI8EsHJXk06sNn+Z/TGhb8bPDNNGSgimag=="
+ },
+ "typescript": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
+ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
+ "dev": true
+ }
+ }
+}
diff --git a/app/package.json b/app/package.json
new file mode 100644
index 0000000..523811d
--- /dev/null
+++ b/app/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@quot/app",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "test": "tsc --noEmit",
+ "start": "node -r esbuild-register src/server.ts"
+ },
+ "engines": {
+ "node": "^18.7.0"
+ },
+ "packageManager": "npm@8.15.0",
+ "dependencies": {
+ "@exampledev/new.css": "^1.1.3",
+ "esbuild": "^0.15.5",
+ "esbuild-register": "^3.3.3",
+ "solid-js": "^1.4.8"
+ },
+ "devDependencies": {
+ "@tsconfig/node18-strictest-esm": "^1.0.0",
+ "@types/node": "^18.7.8",
+ "typescript": "^4.7.4"
+ }
+}
diff --git a/app/src/app.tsx b/app/src/app.tsx
new file mode 100644
index 0000000..b960e5d
--- /dev/null
+++ b/app/src/app.tsx
@@ -0,0 +1,38 @@
+import { createSignal } from "solid-js";
+import Index from "./pages/index";
+import Page from "./pages/page";
+
+const routes = {
+ "/": Index,
+};
+
+export default () => {
+ const [pathname, setPathname] = createSignal(
+ document.location.pathname as keyof typeof routes
+ );
+
+ document.body.addEventListener("click", (e) => {
+ if (
+ e.target instanceof HTMLAnchorElement &&
+ e.target.origin === document.location.origin &&
+ e.target.pathname in routes // TODO: support params ... solid router を入れよう
+ ) {
+ e.preventDefault();
+ window.history.pushState({}, "", e.target.href);
+ setPathname(e.target.pathname as keyof typeof routes);
+ }
+ });
+
+ window.addEventListener("popstate", () => {
+ setPathname(document.location.pathname as keyof typeof routes);
+ });
+
+ return () => (
+ <>
+ Quot
+
{() => JSON.stringify(pages(), null, " ")}+
{() => JSON.stringify(page(), null, " ")}+