Brought over most of the changes from go-web-starter-kit.
This commit is contained in:
parent
01fcb964be
commit
8d70b57299
11 changed files with 143 additions and 57 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -30,3 +30,4 @@ coverage/*
|
||||||
config.toml
|
config.toml
|
||||||
public
|
public
|
||||||
*.idx
|
*.idx
|
||||||
|
*.sqlite3
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -48,7 +48,7 @@ tailwind_install:
|
||||||
chmod oug+x tailwindcss-linux-x64
|
chmod oug+x tailwindcss-linux-x64
|
||||||
sudo mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
sudo mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
||||||
|
|
||||||
dev:
|
dev: tailwind site
|
||||||
go tool air -build.stop_on_error "true"
|
go tool air -build.stop_on_error "true"
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
|
|
|
@ -8,19 +8,22 @@
|
||||||
<meta name="description" content="My Go learning project, which is a Twitch support thing." />
|
<meta name="description" content="My Go learning project, which is a Twitch support thing." />
|
||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<!-- all alpine plugins come first to register into alpine:init event -->
|
||||||
|
<script defer src="/js/alpine-intersect.js"></script>
|
||||||
|
<!-- then alpine runs, triggers init, and tada you get no-build plugins -->
|
||||||
<script defer src="/js/alpine.js"></script>
|
<script defer src="/js/alpine.js"></script>
|
||||||
<script src="/js/code.js"></script>
|
<script src="/js/code.js"></script>
|
||||||
<title>ZedShaw.games</title>
|
<title>ZedShaw.games</title>
|
||||||
</head>
|
</head>
|
||||||
<body data-testid="{{.PageId}}">
|
<body id="top" data-testid="{{.PageId}}">
|
||||||
<header>
|
<header class="p-4">
|
||||||
<nav>
|
<nav>
|
||||||
<a id="home" href="/">
|
<a id="home" href="/">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
width="2rem"
|
width="24"
|
||||||
height="2rem"
|
height="24"
|
||||||
viewBox="0 0 2rem 2rem">
|
viewBox="0 0 24 24">
|
||||||
<use href="/icons/home.svg#home" />
|
<use href="/icons/home.svg#home"><use>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a id="live" href="/live/">Live</a>
|
<a id="live" href="/live/">Live</a>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
@import "./theme.css" layer(theme);
|
@import "./theme.css" layer(theme);
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply text-gray-950 dark:text-gray-50;
|
@apply bg-gray-100 text-gray-950 dark:text-gray-50 dark:bg-gray-950;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
@ -10,11 +10,15 @@ main {
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
@apply flex flex-col justify-stretch;
|
@apply flex justify-stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
@apply flex bg-gray-950 *:text-gray-50 *:flex-1 *:text-xl p-6;
|
@apply flex justify-center items-center bg-gray-950 *:text-gray-50 *:flex-1 *:text-xl w-full justify-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > a {
|
||||||
|
@apply flex justify-center items-center pt-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
@ -23,7 +27,7 @@ code {
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
@apply bg-gray-950 rounded-lg border-1 border-gray-300 mb-4 p-1;
|
@apply bg-gray-950 border-1 border-gray-600 mb-4 p-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre > code {
|
pre > code {
|
||||||
|
@ -34,6 +38,10 @@ footer {
|
||||||
@apply bg-gray-950 text-gray-50 text-lg flex p-6;
|
@apply bg-gray-950 text-gray-50 text-lg flex p-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@utility sticky-bottom {
|
||||||
|
@apply w-full sticky bottom-0 left-0;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-6xl mb-2 mt-4;
|
@apply text-6xl mb-2 mt-4;
|
||||||
}
|
}
|
||||||
|
@ -118,6 +126,10 @@ button {
|
||||||
@apply rounded-sm shadow-sm bg-gray-600 text-gray-50 dark:bg-gray-300 dark:text-gray-950 p-3;
|
@apply rounded-sm shadow-sm bg-gray-600 text-gray-50 dark:bg-gray-300 dark:text-gray-950 p-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
@apply border-l-5 border-gray-800 dark:border-black bg-gray-200 text-black dark:bg-gray-700 dark:text-white p-2;
|
||||||
|
}
|
||||||
|
|
||||||
@utility btn-hover {
|
@utility btn-hover {
|
||||||
@apply !bg-gray-900 !text-gray-50;
|
@apply !bg-gray-900 !text-gray-50;
|
||||||
}
|
}
|
||||||
|
@ -171,7 +183,7 @@ shape.video {
|
||||||
}
|
}
|
||||||
|
|
||||||
block {
|
block {
|
||||||
@apply flex flex-col p-4 mb-10 gap-4;
|
@apply flex flex-col p-4 gap-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
bar {
|
bar {
|
||||||
|
|
1
static/js/alpine-intersect.js
Normal file
1
static/js/alpine-intersect.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(()=>{function o(e){e.directive("intersect",e.skipDuringClone((t,{value:i,expression:l,modifiers:n},{evaluateLater:r,cleanup:c})=>{let s=r(l),a={rootMargin:x(n),threshold:f(n)},u=new IntersectionObserver(d=>{d.forEach(h=>{h.isIntersecting!==(i==="leave")&&(s(),n.includes("once")&&u.disconnect())})},a);u.observe(t),c(()=>{u.disconnect()})}))}function f(e){if(e.includes("full"))return .99;if(e.includes("half"))return .5;if(!e.includes("threshold"))return 0;let t=e[e.indexOf("threshold")+1];return t==="100"?1:t==="0"?0:Number(`.${t}`)}function p(e){let t=e.match(/^(-?[0-9]+)(px|%)?$/);return t?t[1]+(t[2]||"px"):void 0}function x(e){let t="margin",i="0px 0px 0px 0px",l=e.indexOf(t);if(l===-1)return i;let n=[];for(let r=1;r<5;r++)n.push(p(e[l+r]||""));return n=n.filter(r=>r!==void 0),n.length?n.join(" ").trim():i}document.addEventListener("alpine:init",()=>{window.Alpine.plugin(o)});})();
|
|
@ -4,7 +4,7 @@ class PaginateTable {
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.headers = [];
|
this.headers = [];
|
||||||
this.search_query=""
|
this.search_query="";
|
||||||
}
|
}
|
||||||
|
|
||||||
async contents() {
|
async contents() {
|
||||||
|
@ -31,19 +31,43 @@ class PaginateTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GetJson {
|
class ForeverScroll {
|
||||||
constructor(url) {
|
constructor(url) {
|
||||||
this.item;
|
this.page = 0;
|
||||||
|
this.items = [];
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
this.end = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async item() {
|
async init() {
|
||||||
const resp = await fetch(`${this.url}`);
|
const resp = await fetch(this.url);
|
||||||
console.assert(resp.status == 200, "failed to get it");
|
console.assert(resp.status == 200, "failed to get it");
|
||||||
|
|
||||||
this.item = await resp.json();
|
const items = await resp.json();
|
||||||
return this.item;
|
if(items) this.items = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
this.page += 1
|
||||||
|
let url = `${this.url}?page=${this.page}`;
|
||||||
|
|
||||||
|
const resp = await fetch(url);
|
||||||
|
console.assert(resp.status == 200, "failed to get it");
|
||||||
|
|
||||||
|
const items = await resp.json();
|
||||||
|
|
||||||
|
if(items) {
|
||||||
|
this.items.push(...items);
|
||||||
|
} else {
|
||||||
|
this.end = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetJson = async (url) => {
|
||||||
|
const resp = await fetch(url);
|
||||||
|
console.assert(resp.status == 200, "failed to get it");
|
||||||
|
return await resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfirmDelete = async (table, obj_id) => {
|
const ConfirmDelete = async (table, obj_id) => {
|
||||||
|
@ -55,3 +79,14 @@ const ConfirmDelete = async (table, obj_id) => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UrlId = () => {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
let parts = url.pathname.split("/");
|
||||||
|
|
||||||
|
if(window.location.href.endsWith("/")) {
|
||||||
|
return parts[parts.length - 2];
|
||||||
|
} else {
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
--color-gray-900: oklch(21% 0.0 264.665);
|
--color-gray-900: oklch(21% 0.0 264.665);
|
||||||
--color-gray-950: oklch(13% 0.0 261.692);
|
--color-gray-950: oklch(13% 0.0 261.692);
|
||||||
--color-black: #000;
|
--color-black: #000;
|
||||||
|
--color-white: #fff;
|
||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
--container-xs: 20rem;
|
--container-xs: 20rem;
|
||||||
--container-sm: 24rem;
|
--container-sm: 24rem;
|
||||||
|
@ -477,9 +478,6 @@
|
||||||
.\!p-4 {
|
.\!p-4 {
|
||||||
padding: calc(var(--spacing) * 4) !important;
|
padding: calc(var(--spacing) * 4) !important;
|
||||||
}
|
}
|
||||||
.p-0 {
|
|
||||||
padding: calc(var(--spacing) * 0);
|
|
||||||
}
|
|
||||||
.p-0\! {
|
.p-0\! {
|
||||||
padding: calc(var(--spacing) * 0) !important;
|
padding: calc(var(--spacing) * 0) !important;
|
||||||
}
|
}
|
||||||
|
@ -640,7 +638,11 @@
|
||||||
}
|
}
|
||||||
@layer theme;
|
@layer theme;
|
||||||
body {
|
body {
|
||||||
|
background-color: var(--color-gray-100);
|
||||||
color: var(--color-gray-950);
|
color: var(--color-gray-950);
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-color: var(--color-gray-950);
|
||||||
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
color: var(--color-gray-50);
|
color: var(--color-gray-50);
|
||||||
}
|
}
|
||||||
|
@ -662,13 +664,15 @@ main {
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
}
|
}
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
background-color: var(--color-gray-950);
|
background-color: var(--color-gray-950);
|
||||||
padding: calc(var(--spacing) * 6);
|
|
||||||
:is(& > *) {
|
:is(& > *) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
@ -680,6 +684,12 @@ nav {
|
||||||
color: var(--color-gray-50);
|
color: var(--color-gray-50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
nav > a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
code {
|
code {
|
||||||
speak-as: literal-punctuation;
|
speak-as: literal-punctuation;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -688,10 +698,9 @@ code {
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
margin-bottom: calc(var(--spacing) * 4);
|
margin-bottom: calc(var(--spacing) * 4);
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
border-style: var(--tw-border-style);
|
border-style: var(--tw-border-style);
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: var(--color-gray-300);
|
border-color: var(--color-gray-600);
|
||||||
background-color: var(--color-gray-950);
|
background-color: var(--color-gray-950);
|
||||||
padding: calc(var(--spacing) * 1);
|
padding: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
|
@ -859,6 +868,23 @@ button {
|
||||||
color: var(--color-gray-950);
|
color: var(--color-gray-950);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
blockquote {
|
||||||
|
border-left-style: var(--tw-border-style);
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-color: var(--color-gray-800);
|
||||||
|
background-color: var(--color-gray-200);
|
||||||
|
padding: calc(var(--spacing) * 2);
|
||||||
|
color: var(--color-black);
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
border-color: var(--color-black);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-color: var(--color-gray-700);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
shape {
|
shape {
|
||||||
display: flex;
|
display: flex;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
|
@ -911,7 +937,6 @@ shape.video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
block {
|
block {
|
||||||
margin-bottom: calc(var(--spacing) * 10);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc(var(--spacing) * 4);
|
gap: calc(var(--spacing) * 4);
|
||||||
|
|
6
tools/pragmas.sql
Normal file
6
tools/pragmas.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
PRAGMA journal_mode = WAL;
|
||||||
|
PRAGMA synchronous = NORMAL;
|
||||||
|
PRAGMA mmap_size = 134217728; -- 128 megabytes
|
||||||
|
PRAGMA journal_size_limig = 67108864; -- 64 megabytes
|
||||||
|
PRAGMA cache_size = 2000;
|
|
@ -1,13 +1,12 @@
|
||||||
<script>
|
|
||||||
let Data = new GetJson("/api/admin/new/table/{{ .Table }}");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1><a href="/admin/table/{{ .Table }}/">«</a>Admin {{ .Table }}</h1>
|
<h1><a href="/admin/table/{{ .Table }}/">«</a>Admin {{ .Table }}</h1>
|
||||||
|
|
||||||
<block x-data="Data">
|
<block x-data="{item: {}}"
|
||||||
|
x-init="item = await GetJson('/api/admin/new/table/{{ .Table }}')">
|
||||||
|
|
||||||
<form method="POST" action="/api/admin/new/table/{{ .Table }}">
|
<form method="POST" action="/api/admin/new/table/{{ .Table }}">
|
||||||
<card>
|
<card>
|
||||||
<top><h2>New {{ .Table }}</h2></top>
|
<top><h2>New {{ .Table }}</h2></top>
|
||||||
|
|
||||||
<middle>
|
<middle>
|
||||||
<template x-for="(value, key) in item">
|
<template x-for="(value, key) in item">
|
||||||
<div>
|
<div>
|
||||||
|
@ -16,6 +15,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</middle>
|
</middle>
|
||||||
|
|
||||||
<bottom>
|
<bottom>
|
||||||
<button type="button"><a href="/admin/table/{{ .Table }}/">Back</a></button>
|
<button type="button"><a href="/admin/table/{{ .Table }}/">Back</a></button>
|
||||||
<button class="hover:btn-alert" type="button">Clear</button>
|
<button class="hover:btn-alert" type="button">Clear</button>
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
<script>
|
|
||||||
let Data = new GetJson("/api/admin/table/{{ .Table }}/{{ .Id }}");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1><a href="/admin/table/{{ .Table }}/">«</a>Admin {{ .Table }}</h1>
|
<h1><a href="/admin/table/{{ .Table }}/">«</a>Admin {{ .Table }}</h1>
|
||||||
|
|
||||||
<block x-data="Data">
|
<block x-data="{item: {}}"
|
||||||
|
x-init="item = await GetJson('/api/admin/table/{{ .Table }}/{{ .Id }}')">
|
||||||
|
|
||||||
<form method="POST" action="/api/admin/table/{{ .Table }}/{{ .Id }}">
|
<form method="POST" action="/api/admin/table/{{ .Table }}/{{ .Id }}">
|
||||||
<card>
|
<card>
|
||||||
<top><h1>{{ .Table }} : {{ .Id }}</h1></top>
|
<top><h1>{{ .Table }} : {{ .Id }}</h1></top>
|
||||||
|
|
|
@ -8,12 +8,16 @@
|
||||||
<meta name="description" content="My Go learning project, which is a Twitch support thing." />
|
<meta name="description" content="My Go learning project, which is a Twitch support thing." />
|
||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<!-- all alpine plugins come first to register into alpine:init event -->
|
||||||
|
<script defer src="/js/alpine-intersect.js"></script>
|
||||||
|
<!-- then alpine runs, triggers init, and tada you get no-build plugins -->
|
||||||
<script defer src="/js/alpine.js"></script>
|
<script defer src="/js/alpine.js"></script>
|
||||||
<script src="/js/code.js"></script>
|
<script src="/js/code.js"></script>
|
||||||
<title>ZedShaw.games</title>
|
<title>ZedShaw.games</title>
|
||||||
</head>
|
</head>
|
||||||
<body data-testid="{{.PageId}}">
|
<body id="top" data-testid="{{.PageId}}">
|
||||||
<header>
|
<header class="p-4">
|
||||||
|
<nav>
|
||||||
<a id="home" href="/">
|
<a id="home" href="/">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="24"
|
||||||
|
@ -27,11 +31,12 @@
|
||||||
<a id="game" href="/game/">Games</a>
|
<a id="game" href="/game/">Games</a>
|
||||||
<a id="register" href="/register/">Register</a>
|
<a id="register" href="/register/">Register</a>
|
||||||
<a id="login" href="/login/">Login</a>
|
<a id="login" href="/login/">Login</a>
|
||||||
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="p-0 min-h-screen dark:bg-gray-900">
|
<main>
|
||||||
{{embed}}
|
{{embed}}
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue