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
|
||||
public
|
||||
*.idx
|
||||
*.sqlite3
|
||||
|
|
2
Makefile
2
Makefile
|
@ -48,7 +48,7 @@ tailwind_install:
|
|||
chmod oug+x tailwindcss-linux-x64
|
||||
sudo mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
||||
|
||||
dev:
|
||||
dev: tailwind site
|
||||
go tool air -build.stop_on_error "true"
|
||||
|
||||
coverage:
|
||||
|
|
|
@ -8,19 +8,22 @@
|
|||
<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="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 src="/js/code.js"></script>
|
||||
<title>ZedShaw.games</title>
|
||||
</head>
|
||||
<body data-testid="{{.PageId}}">
|
||||
<header>
|
||||
<body id="top" data-testid="{{.PageId}}">
|
||||
<header class="p-4">
|
||||
<nav>
|
||||
<a id="home" href="/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="2rem"
|
||||
height="2rem"
|
||||
viewBox="0 0 2rem 2rem">
|
||||
<use href="/icons/home.svg#home" />
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24">
|
||||
<use href="/icons/home.svg#home"><use>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="live" href="/live/">Live</a>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@import "./theme.css" layer(theme);
|
||||
|
||||
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 {
|
||||
|
@ -10,11 +10,15 @@ main {
|
|||
}
|
||||
|
||||
header {
|
||||
@apply flex flex-col justify-stretch;
|
||||
@apply flex justify-stretch;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -23,7 +27,7 @@ code {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -34,6 +38,10 @@ footer {
|
|||
@apply bg-gray-950 text-gray-50 text-lg flex p-6;
|
||||
}
|
||||
|
||||
@utility sticky-bottom {
|
||||
@apply w-full sticky bottom-0 left-0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@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;
|
||||
}
|
||||
|
||||
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 {
|
||||
@apply !bg-gray-900 !text-gray-50;
|
||||
}
|
||||
|
@ -171,7 +183,7 @@ shape.video {
|
|||
}
|
||||
|
||||
block {
|
||||
@apply flex flex-col p-4 mb-10 gap-4;
|
||||
@apply flex flex-col p-4 gap-4;
|
||||
}
|
||||
|
||||
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.url = url;
|
||||
this.headers = [];
|
||||
this.search_query=""
|
||||
this.search_query="";
|
||||
}
|
||||
|
||||
async contents() {
|
||||
|
@ -31,19 +31,43 @@ class PaginateTable {
|
|||
}
|
||||
}
|
||||
|
||||
class GetJson {
|
||||
class ForeverScroll {
|
||||
constructor(url) {
|
||||
this.item;
|
||||
this.page = 0;
|
||||
this.items = [];
|
||||
this.url = url;
|
||||
this.end = false;
|
||||
}
|
||||
|
||||
async item() {
|
||||
const resp = await fetch(`${this.url}`);
|
||||
async init() {
|
||||
const resp = await fetch(this.url);
|
||||
console.assert(resp.status == 200, "failed to get it");
|
||||
|
||||
this.item = await resp.json();
|
||||
return this.item;
|
||||
const items = await resp.json();
|
||||
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) => {
|
||||
|
@ -55,3 +79,14 @@ const ConfirmDelete = async (table, obj_id) => {
|
|||
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-950: oklch(13% 0.0 261.692);
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
--container-xs: 20rem;
|
||||
--container-sm: 24rem;
|
||||
|
@ -477,9 +478,6 @@
|
|||
.\!p-4 {
|
||||
padding: calc(var(--spacing) * 4) !important;
|
||||
}
|
||||
.p-0 {
|
||||
padding: calc(var(--spacing) * 0);
|
||||
}
|
||||
.p-0\! {
|
||||
padding: calc(var(--spacing) * 0) !important;
|
||||
}
|
||||
|
@ -640,7 +638,11 @@
|
|||
}
|
||||
@layer theme;
|
||||
body {
|
||||
background-color: var(--color-gray-100);
|
||||
color: var(--color-gray-950);
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: var(--color-gray-950);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: var(--color-gray-50);
|
||||
}
|
||||
|
@ -662,13 +664,15 @@ main {
|
|||
}
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: space-evenly;
|
||||
background-color: var(--color-gray-950);
|
||||
padding: calc(var(--spacing) * 6);
|
||||
:is(& > *) {
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -680,6 +684,12 @@ nav {
|
|||
color: var(--color-gray-50);
|
||||
}
|
||||
}
|
||||
nav > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
code {
|
||||
speak-as: literal-punctuation;
|
||||
display: inline-block;
|
||||
|
@ -688,10 +698,9 @@ code {
|
|||
}
|
||||
pre {
|
||||
margin-bottom: calc(var(--spacing) * 4);
|
||||
border-radius: var(--radius-lg);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-gray-300);
|
||||
border-color: var(--color-gray-600);
|
||||
background-color: var(--color-gray-950);
|
||||
padding: calc(var(--spacing) * 1);
|
||||
}
|
||||
|
@ -859,6 +868,23 @@ button {
|
|||
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 {
|
||||
display: flex;
|
||||
aspect-ratio: 1 / 1;
|
||||
|
@ -911,7 +937,6 @@ shape.video {
|
|||
width: 100%;
|
||||
}
|
||||
block {
|
||||
margin-bottom: calc(var(--spacing) * 10);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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>
|
||||
|
||||
<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 }}">
|
||||
<card>
|
||||
<top><h2>New {{ .Table }}</h2></top>
|
||||
|
||||
<middle>
|
||||
<template x-for="(value, key) in item">
|
||||
<div>
|
||||
|
@ -16,10 +15,11 @@
|
|||
</div>
|
||||
</template>
|
||||
</middle>
|
||||
|
||||
<bottom>
|
||||
<button type="button"><a href="/admin/table/{{ .Table }}/">Back</a></button>
|
||||
<button class="hover:btn-alert" type="button">Clear</button>
|
||||
<button class="hover:btn-hover" type="submit">Insert</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-hover" type="submit">Insert</button>
|
||||
</bottom>
|
||||
</card>
|
||||
</form>
|
||||
|
|
|
@ -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>
|
||||
|
||||
<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 }}">
|
||||
<card>
|
||||
<top><h1>{{ .Table }} : {{ .Id }}</h1></top>
|
||||
|
|
|
@ -8,30 +8,35 @@
|
|||
<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="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 src="/js/code.js"></script>
|
||||
<title>ZedShaw.games</title>
|
||||
</head>
|
||||
<body data-testid="{{.PageId}}">
|
||||
<header>
|
||||
<a id="home" href="/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24">
|
||||
<use href="/icons/home.svg#home"><use>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="live" href="/live/">Live</a>
|
||||
<a id="stream" href="/stream/">Streams</a>
|
||||
<a id="game" href="/game/">Games</a>
|
||||
<a id="register" href="/register/">Register</a>
|
||||
<a id="login" href="/login/">Login</a>
|
||||
<body id="top" data-testid="{{.PageId}}">
|
||||
<header class="p-4">
|
||||
<nav>
|
||||
<a id="home" href="/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24">
|
||||
<use href="/icons/home.svg#home"><use>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="live" href="/live/">Live</a>
|
||||
<a id="stream" href="/stream/">Streams</a>
|
||||
<a id="game" href="/game/">Games</a>
|
||||
<a id="register" href="/register/">Register</a>
|
||||
<a id="login" href="/login/">Login</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="p-0 min-h-screen dark:bg-gray-900">
|
||||
<main>
|
||||
{{embed}}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="flex-1">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue