redesign site interface
This commit is contained in:
parent
d26f309cf5
commit
7a1952b100
8
go.mod
8
go.mod
@ -7,6 +7,7 @@ require (
|
||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20250212122300-421ef1d8611c
|
||||
github.com/alexedwards/scs/v2 v2.8.0
|
||||
github.com/coreos/go-oidc/v3 v3.14.1
|
||||
github.com/golang-migrate/migrate/v4 v4.18.3
|
||||
github.com/gorilla/schema v1.4.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/justinas/alice v1.2.0
|
||||
@ -16,4 +17,9 @@ require (
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
)
|
||||
|
||||
require github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
require (
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
)
|
||||
|
14
go.sum
14
go.sum
@ -6,27 +6,41 @@ github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZx
|
||||
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
|
||||
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
|
||||
github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk=
|
||||
github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
|
@ -1,86 +1,882 @@
|
||||
/* html {
|
||||
background: lightgray;
|
||||
} */
|
||||
/* CSS Reset and Base Styles */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* CSS Custom Properties for Theming */
|
||||
:root {
|
||||
/* Light mode colors */
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-danger: #dc2626;
|
||||
--color-danger-hover: #b91c1c;
|
||||
--color-warning: #d97706;
|
||||
|
||||
--color-text: #1f2937;
|
||||
--color-text-muted: #6b7280;
|
||||
--color-background: #ffffff;
|
||||
--color-surface: #f9fafb;
|
||||
--color-border: #e5e7eb;
|
||||
--color-border-light: #f3f4f6;
|
||||
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
|
||||
/* Spacing scale */
|
||||
--space-xs: 0.25rem;
|
||||
--space-sm: 0.5rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2rem;
|
||||
--space-2xl: 3rem;
|
||||
--space-3xl: 4rem;
|
||||
|
||||
/* Typography scale */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: 0.125rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
|
||||
/* Layout */
|
||||
--max-width: 1200px;
|
||||
--header-height: 60px;
|
||||
}
|
||||
|
||||
/* Dark mode colors */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-text: #f9fafb;
|
||||
--color-text-muted: #9ca3af;
|
||||
--color-background: #111827;
|
||||
--color-surface: #1f2937;
|
||||
--color-border: #374151;
|
||||
--color-border-light: #4b5563;
|
||||
}
|
||||
}
|
||||
|
||||
/* Base Typography */
|
||||
body {
|
||||
max-width: 1024px;
|
||||
margin: 1rem auto;
|
||||
padding: 1rem;
|
||||
/* background: white; */
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.5;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body > nav {
|
||||
font-size: var(--text-base);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-background);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body > nav ul {
|
||||
list-style: none;
|
||||
margin: 0 1rem;
|
||||
padding: 0;
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
body > nav li {
|
||||
display: inline-block;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
h1 { font-size: var(--text-3xl); }
|
||||
h2 { font-size: var(--text-2xl); }
|
||||
h3 { font-size: var(--text-xl); }
|
||||
h4 { font-size: var(--text-lg); }
|
||||
|
||||
nav form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
nav button {
|
||||
border: none;
|
||||
background: none;
|
||||
font-family: unset;
|
||||
font-size: unset;
|
||||
/* color: blue; */
|
||||
/* text-decoration: underline; */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
div#dashboard {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
div#dashboard nav {
|
||||
flex: 1 1 25%;
|
||||
/* margin-top: 2rem; */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
div#dashboard > div {
|
||||
flex: 10 1 40%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
div > pre {
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
main nav ul {
|
||||
list-style: none;
|
||||
margin: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
p {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
a {
|
||||
/* color: blue; */
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Layout Components */
|
||||
body > header {
|
||||
background-color: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-lg);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
body > header h1 {
|
||||
margin: 0;
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
body > header h1 a {
|
||||
color: var(--color-text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body > header h1 a:hover {
|
||||
text-decoration: none;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.nav-welcome {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-md);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-links li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border-radius: var(--radius-md);
|
||||
transition: background-color 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
background-color: var(--color-border-light);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--color-surface);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: var(--space-lg);
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* Dashboard Layout */
|
||||
#dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: var(--space-2xl);
|
||||
margin-top: var(--space-xl);
|
||||
}
|
||||
|
||||
#dashboard nav {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#dashboard nav > div {
|
||||
margin-bottom: var(--space-xl);
|
||||
padding: var(--space-lg);
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
#dashboard nav h3 {
|
||||
font-size: var(--text-lg);
|
||||
margin-bottom: var(--space-md);
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
#dashboard nav ul {
|
||||
list-style: none;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
#dashboard nav ul:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#dashboard nav li {
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
#dashboard nav a {
|
||||
display: block;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border-radius: var(--radius-md);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#dashboard nav a:hover {
|
||||
background-color: var(--color-border-light);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
form {
|
||||
background-color: var(--color-surface);
|
||||
padding: var(--space-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--color-border);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.form-group small {
|
||||
display: block;
|
||||
margin-top: var(--space-xs);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-lg);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
fieldset.radio-group {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset.danger-zone {
|
||||
border-color: var(--color-danger);
|
||||
background-color: rgba(220, 38, 38, 0.05);
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0 var(--space-sm);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: var(--space-sm);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="url"],
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: var(--space-md);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
font-size: var(--text-base);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="email"]:focus,
|
||||
input[type="url"]:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Radio buttons */
|
||||
input[type="radio"] {
|
||||
margin-right: var(--space-sm);
|
||||
}
|
||||
|
||||
label:has(input[type="radio"]) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: var(--space-lg);
|
||||
margin-bottom: var(--space-sm);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button,
|
||||
input[type="submit"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
font-size: var(--text-base);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
input[type="submit"]:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
button:active,
|
||||
input[type="submit"]:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
button.danger {
|
||||
background-color: var(--color-danger);
|
||||
}
|
||||
|
||||
button.danger:hover {
|
||||
background-color: var(--color-danger-hover);
|
||||
}
|
||||
|
||||
button.outline {
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
button.outline:hover {
|
||||
background-color: var(--color-surface);
|
||||
border-color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Button variants */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-base);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background-color: var(--color-surface);
|
||||
border-color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
#comments {
|
||||
margin-top: var(--space-2xl);
|
||||
}
|
||||
|
||||
.comment {
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-lg);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--space-md);
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.comment-meta {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
margin: 0 0 var(--space-xs) 0;
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.comment time {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.comment-content p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.comments-list {
|
||||
margin-top: var(--space-lg);
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre {
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-lg);
|
||||
overflow-x: auto;
|
||||
margin-bottom: var(--space-lg);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: var(--text-sm);
|
||||
background-color: var(--color-border-light);
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.code-example {
|
||||
margin: var(--space-lg) 0;
|
||||
}
|
||||
|
||||
.code-example figcaption {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
ul, ol {
|
||||
padding-left: var(--space-xl);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
ul#websites {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul#websites li {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
/* Website cards */
|
||||
.website-card {
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-xl);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.website-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.website-header {
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.website-name {
|
||||
margin: 0 0 var(--space-sm) 0;
|
||||
}
|
||||
|
||||
.website-name a {
|
||||
color: var(--color-text);
|
||||
font-weight: 600;
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
|
||||
.website-url {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.website-actions {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Screen reader only content */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Notices and alerts */
|
||||
.notice,
|
||||
.warning-notice {
|
||||
padding: var(--space-md);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-lg);
|
||||
border-left: 4px solid var(--color-warning);
|
||||
background-color: rgba(217, 119, 6, 0.1);
|
||||
}
|
||||
|
||||
.warning-notice {
|
||||
border-left-color: var(--color-danger);
|
||||
background-color: rgba(220, 38, 38, 0.1);
|
||||
}
|
||||
|
||||
/* Section headers */
|
||||
.section-header {
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.section-header:has(.btn) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.section-header h1 {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.section-description {
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Hero section (index.html) */
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: var(--space-3xl) 0;
|
||||
background: linear-gradient(135deg, var(--color-surface) 0%, var(--color-background) 100%);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.hero-header h1 {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: var(--text-xl);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Instruction sections (dashboard_main.html) */
|
||||
.instruction-overview {
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-lg);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.instruction-method {
|
||||
margin-bottom: var(--space-2xl);
|
||||
padding-bottom: var(--space-2xl);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.instruction-method:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.instruction-step {
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.instruction-step h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.method-note {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-md);
|
||||
margin-top: var(--space-lg);
|
||||
}
|
||||
|
||||
.help-section {
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-xl);
|
||||
margin-top: var(--space-2xl);
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background-color: var(--color-border);
|
||||
margin: var(--space-lg) 0;
|
||||
}
|
||||
|
||||
.htmx-indicator {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in;
|
||||
}
|
||||
|
||||
.htmx-request .htmx-indicator,
|
||||
.htmx-request.htmx-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
:root {
|
||||
--space-lg: 1rem;
|
||||
--space-xl: 1.5rem;
|
||||
--space-2xl: 2rem;
|
||||
}
|
||||
|
||||
body > header {
|
||||
padding: 0 var(--space-md);
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: var(--space-md);
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.nav-welcome {
|
||||
text-align: center;
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
justify-content: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
padding: var(--space-sm);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
main {
|
||||
padding: var(--space-lg) var(--space-md);
|
||||
}
|
||||
|
||||
#dashboard {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
#dashboard nav > div {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
form {
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
h1 { font-size: var(--text-2xl); }
|
||||
h2 { font-size: var(--text-xl); }
|
||||
|
||||
label:has(input[type="radio"]) {
|
||||
display: block;
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: var(--space-2xl) 0;
|
||||
}
|
||||
|
||||
.hero-header h1 {
|
||||
font-size: var(--text-3xl);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.section-header:has(.btn) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.section-header .btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.website-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.website-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: var(--text-xs);
|
||||
padding: var(--space-md);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: var(--text-xs);
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body > header h1 {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="submit"] {
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
}
|
||||
|
||||
/* Focus styles for better accessibility */
|
||||
button:focus-visible,
|
||||
input:focus-visible,
|
||||
textarea:focus-visible,
|
||||
select:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
body > header,
|
||||
nav,
|
||||
footer,
|
||||
button,
|
||||
input[type="submit"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#dashboard {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,31 +41,33 @@ templ commonHeader() {
|
||||
|
||||
templ topNav(data CommonData) {
|
||||
{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }}
|
||||
<nav>
|
||||
<div>
|
||||
<nav aria-label="Site navigation">
|
||||
<div class="nav-welcome">
|
||||
<span>
|
||||
if data.IsAuthenticated {
|
||||
Welcome, { data.CurrentUser.Username }
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="nav-links">
|
||||
if data.IsAuthenticated {
|
||||
<a href="/guestbooks">All Guestbooks</a> |
|
||||
<a href="/websites">My Websites</a> |
|
||||
<a href="/users/settings">Settings</a> |
|
||||
<a href="#" hx-post="/users/logout" hx-headers={ hxHeaders }>Logout</a>
|
||||
<li><a href="/guestbooks">All Guestbooks</a></li>
|
||||
<li><a href="/websites">My Websites</a></li>
|
||||
<li><a href="/users/settings">Settings</a></li>
|
||||
<li><a href="#" hx-post="/users/logout" hx-headers={ hxHeaders }>Logout</a></li>
|
||||
} else {
|
||||
if data.LocalAuthEnabled {
|
||||
<a href="/users/register">Create an Account</a> |
|
||||
<li><a href="/users/register">Create an Account</a></li> |
|
||||
}
|
||||
<a href="/users/login">Login</a>
|
||||
<li><a href="/users/login">Login</a></li>
|
||||
}
|
||||
</div>
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
|
||||
templ commonFooter() {
|
||||
<footer>
|
||||
<p>A <a href="https://32bit.cafe">32bit.cafe</a> Project</p>
|
||||
<p>A <a href="https://32bit.cafe" rel="noopener">32bit.cafe</a> Project</p>
|
||||
</footer>
|
||||
}
|
||||
|
||||
@ -76,18 +78,16 @@ templ base(title string, data CommonData) {
|
||||
<title>{ title } - webweav.ing</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link href="/static/css/classless.min.css" rel="stylesheet"/>
|
||||
<link href="/static/css/style.css" rel="stylesheet"/>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@commonHeader()
|
||||
@topNav(data)
|
||||
<main>
|
||||
<main role="main">
|
||||
if data.Flash != "" {
|
||||
<div class="flash">{ data.Flash }</div>
|
||||
<div class="notice flash">{ data.Flash }</div>
|
||||
}
|
||||
<h1>{ title }</h1>
|
||||
{ children... }
|
||||
</main>
|
||||
@commonFooter()
|
||||
|
@ -92,7 +92,7 @@ func topNav(data CommonData) templ.Component {
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav><div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav aria-label=\"Site navigation\"><div class=\"nav-welcome\"><span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -104,48 +104,48 @@ func topNav(data CommonData) templ.Component {
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 47, Col: 40}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 48, Col: 41}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div><div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span></div><ul class=\"nav-links\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.IsAuthenticated {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<a href=\"/guestbooks\">All Guestbooks</a> | <a href=\"/websites\">My Websites</a> | <a href=\"/users/settings\">Settings</a> | <a href=\"#\" hx-post=\"/users/logout\" hx-headers=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<li><a href=\"/guestbooks\">All Guestbooks</a></li><li><a href=\"/websites\">My Websites</a></li><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"#\" hx-post=\"/users/logout\" hx-headers=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 55, Col: 62}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 57, Col: 66}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Logout</a>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Logout</a></li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
if data.LocalAuthEnabled {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"/users/register\">Create an Account</a> | ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<li><a href=\"/users/register\">Create an Account</a></li>| ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " <a href=\"/users/login\">Login</a>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " <li><a href=\"/users/login\">Login</a></li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></nav>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</ul></nav>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -174,7 +174,7 @@ func commonFooter() templ.Component {
|
||||
templ_7745c5c3_Var5 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><p>A <a href=\"https://32bit.cafe\">32bit.cafe</a> Project</p></footer>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><p>A <a href=\"https://32bit.cafe\" rel=\"noopener\">32bit.cafe</a> Project</p></footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -210,13 +210,13 @@ func base(title string, data CommonData) templ.Component {
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 76, Col: 17}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 78, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/classless.min.css\" rel=\"stylesheet\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -228,19 +228,19 @@ func base(title string, data CommonData) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<main>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<main role=\"main\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Flash != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flash\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"notice flash\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 88, Col: 36}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 89, Col: 43}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -251,28 +251,11 @@ func base(title string, data CommonData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h1>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 90, Col: 15}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</h1>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var6.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</main>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -280,7 +263,7 @@ func base(title string, data CommonData) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -10,14 +10,19 @@ templ GuestbookDashboardCommentsView(title string, data CommonData, website mode
|
||||
<div id="dashboard">
|
||||
@wSidebar(website)
|
||||
<div>
|
||||
<h1>Comments on { website.Name }</h1>
|
||||
<hr/>
|
||||
<section aria-labelledby="comments-management-heading">
|
||||
<header class="section-header">
|
||||
<h1 id="comments-management-heading">Comments on yq</h1>
|
||||
<p class="section-description">Manage, moderate, and organize comments on your guestbook</p>
|
||||
</header>
|
||||
<hr role="separator"/>
|
||||
if len(comments) == 0 {
|
||||
<p>No comments yet!</p>
|
||||
}
|
||||
for _, c := range comments {
|
||||
@GuestbookDashboardCommentView(data, website, c)
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -60,41 +65,48 @@ templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models.
|
||||
}
|
||||
|
||||
templ commentForm(form forms.CommentCreateForm) {
|
||||
<div>
|
||||
<label for="authorname">Name</label>
|
||||
<fieldset>
|
||||
<legend>Leave a comment</legend>
|
||||
<div class="form-group">
|
||||
<label for="authorname">Name <span aria-label="required">*</span></label>
|
||||
{{ error, exists := form.FieldErrors["authorName"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="text" name="authorname" id="authorname"/>
|
||||
<input type="text" name="authorname" id="authorname" required aria-describedby="authorname-help"/>
|
||||
<small id="authorname-help">Your name or handle</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="authoremail">Email (Optional) </label>
|
||||
<div class="form-group">
|
||||
<label for="authoremail">Email (Optional)</label>
|
||||
{{ error, exists = form.FieldErrors["authorEmail"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="text" name="authoremail" id="authoremail"/>
|
||||
<input type="email" name="authoremail" id="authoremail" aria-describedby="authoremail-help"/>
|
||||
<small id="authoremail-help">We won't share your email address, except with the guestbook's owner</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="authorsite">Site Url (Optional) </label>
|
||||
<div class="form-group">
|
||||
<label for="authorsite">Website URL (Optional)</label>
|
||||
{{ error, exists = form.FieldErrors["authorSite"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="text" name="authorsite" id="authorsite"/>
|
||||
<input type="url" name="authorsite" id="authorsite" aria-describedby="authorsite-help"/>
|
||||
<small id="authorsite-help">Link to your website or social profile</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="content">Comment</label>
|
||||
<div class="form-group">
|
||||
<label for="content">Comment <span aria-label="required">*</span></label>
|
||||
{{ error, exists = form.FieldErrors["content"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<textarea name="content" id="content"></textarea>
|
||||
<textarea name="content" id="content" required aria-describedby="content-help"></textarea>
|
||||
<small id="content-help">Share your thoughts, feedback, or just say hello!</small>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit"/>
|
||||
<div class="form-group">
|
||||
<button type="submit">Submit Comment</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
|
||||
templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) {
|
||||
@ -105,26 +117,30 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest
|
||||
<html>
|
||||
<head>
|
||||
<title>{ title }</title>
|
||||
<link href="/static/css/classless.min.css" rel="stylesheet"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link href="/static/css/style.css" rel="stylesheet"/>
|
||||
<script src="/static/js/main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div>
|
||||
<main role="main">
|
||||
<section aria-labelledby="guestbook-heading">
|
||||
<h1>{ website.Name } Guestbook</h1>
|
||||
{ data.Flash }
|
||||
<form action={ templ.URL(postUrl) } method="post">
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
|
||||
@commentForm(form)
|
||||
</form>
|
||||
</div>
|
||||
<div id="comments">
|
||||
</section>
|
||||
<section id="comments" aria-labelledby="comments-heading">
|
||||
<h2 id="comments-heading">Comments</h2>
|
||||
if len(comments) == 0 {
|
||||
<p>No comments yet!</p>
|
||||
}
|
||||
for _, c := range comments {
|
||||
<div>
|
||||
<h3>
|
||||
for i, c := range comments {
|
||||
{{ commentAuthorRole := fmt.Sprintf("comment-author-%d", i+1) }}
|
||||
<article class="comment" role="article" aria-labelledby={ commentAuthorRole }>
|
||||
<header class="comment-header">
|
||||
<h3 id={ commentAuthorRole } class="comment-author">
|
||||
if c.AuthorSite != "" {
|
||||
<a href={ templ.URL(externalUrl(c.AuthorSite)) } target="_blank">{ c.AuthorName }</a>
|
||||
} else {
|
||||
@ -132,12 +148,15 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest
|
||||
}
|
||||
</h3>
|
||||
<time datetime={ c.Created.Format(time.RFC3339) }>{ c.Created.Format("01-02-2006 03:04PM") }</time>
|
||||
</header>
|
||||
<div class="comment-content">
|
||||
<p>
|
||||
{ c.CommentText }
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,13 +2,14 @@ package views
|
||||
|
||||
templ Home(title string, data CommonData) {
|
||||
@base(title, data) {
|
||||
<h2>Welcome</h2>
|
||||
<p>
|
||||
Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>.
|
||||
</p>
|
||||
<p>
|
||||
Note this service is in a pre-alpha state. Your account and data can disappear at any time.
|
||||
</p>
|
||||
<section aria-labelledby="welcome-heading">
|
||||
<h2 id="welcome-heading">Welcome</h2>
|
||||
<p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe" rel="noopener">32-Bit Cafe</a>.</p>
|
||||
<aside class="notice" role="alert" aria-labelledby="service-status">
|
||||
<h3 id="service-status" class="sr-only">Service Status Notice</h3>
|
||||
<p><strong>Important:</strong> This service is in a pre-alpha state. Your account and data can disappear at any time.</p>
|
||||
</aside>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ func Home(title string, data CommonData) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p><p>Note this service is in a pre-alpha state. Your account and data can disappear at any time. </p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section aria-labelledby=\"welcome-heading\"><h2 id=\"welcome-heading\">Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\" rel=\"noopener\">32-Bit Cafe</a>.</p><aside class=\"notice\" role=\"alert\" aria-labelledby=\"service-status\"><h3 id=\"service-status\" class=\"sr-only\">Service Status Notice</h3><p><strong>Important:</strong> This service is in a pre-alpha state. Your account and data can disappear at any time.</p></aside></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -86,8 +86,12 @@ templ UserSettingsView(data CommonData, timezones []string) {
|
||||
{{ user := data.CurrentUser }}
|
||||
@base("User Settings", data) {
|
||||
<div>
|
||||
<section aria-labelledby="user-settings-heading">
|
||||
<form hx-put="/users/settings">
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
|
||||
<fieldset>
|
||||
<legend id="user-settings-heading">User Settings</legend>
|
||||
<div class="form-group">
|
||||
<label>Local Timezone</label>
|
||||
<select name="timezones" id="timezone-select">
|
||||
for _, tz := range timezones {
|
||||
@ -98,8 +102,11 @@ templ UserSettingsView(data CommonData, timezones []string) {
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<input type="submit" value="Submit"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<button type="submit">Save Settings</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
@ -444,20 +444,20 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><section aria-labelledby=\"user-settings-heading\"><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 90, Col: 65}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 91, Col: 66}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"> <label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"><fieldset><legend id=\"user-settings-heading\">User Settings</legend><div class=\"form-group\"><label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -470,7 +470,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 25}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 28}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -483,7 +483,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 48}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 51}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -501,7 +501,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
|
||||
var templ_7745c5c3_Var25 string
|
||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 97, Col: 25}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 28}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -514,7 +514,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
|
||||
var templ_7745c5c3_Var26 string
|
||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 97, Col: 32}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -526,7 +526,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select> <input type=\"submit\" value=\"Submit\"></form></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select></div></fieldset><button type=\"submit\">Save Settings</button></form></section></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -14,38 +14,47 @@ func wUrl(w models.Website) string {
|
||||
templ wSidebar(website models.Website) {
|
||||
{{ dashUrl := wUrl(website) + "/dashboard" }}
|
||||
{{ gbUrl := wUrl(website) + "/guestbook" }}
|
||||
<nav>
|
||||
<nav aria-label="Dashboard navigation">
|
||||
<div>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li>
|
||||
<section aria-labelledby="main-nav-heading">
|
||||
<h3 id="main-nav-heading">Website</h3>
|
||||
<ul role="list">
|
||||
<li><a href={ templ.URL(dashUrl) } aria-current="page">Dashboard</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/settings") }>Settings</a></li>
|
||||
<li><a href={ templ.URL(externalUrl(website.Url.String())) } target="_blank">View Website</a></li>
|
||||
</ul>
|
||||
<h3>Guestbook</h3>
|
||||
<ul>
|
||||
</section>
|
||||
</div>
|
||||
<div>
|
||||
<section aria-labelledby="guestbook-nav-heading">
|
||||
<h3 id="guestbook-nav-heading">Guestbook</h3>
|
||||
<ul role="list">
|
||||
<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
</section>
|
||||
//<ul>
|
||||
//<li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li>
|
||||
//<li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li>
|
||||
</ul>
|
||||
//</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Feeds</h3>
|
||||
<section aria-labelledby="feeds-nav-heading">
|
||||
<h3 id="feeds-nav-heading">Feeds</h3>
|
||||
<p>Coming Soon</p>
|
||||
</section>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Account</h3>
|
||||
<ul>
|
||||
<section aria-labelledby="account-nav-heading">
|
||||
<h3 id="account-nav-heading">Account</h3>
|
||||
<ul role="list">
|
||||
<li><a href="/users/settings">Settings</a></li>
|
||||
<li><a href="/users/privacy">Privacy</a></li>
|
||||
<li><a href="/help">Help</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
@ -83,22 +92,48 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
|
||||
|
||||
templ WebsiteList(title string, data CommonData, websites []models.Website) {
|
||||
@base(title, data) {
|
||||
<div>
|
||||
<a href="/websites/create">Add Website</a>
|
||||
</div>
|
||||
<div>
|
||||
<section aria-labelledby="websites-heading">
|
||||
<header class="section-header">
|
||||
<h1 id="websites-heading">My Websites</h1>
|
||||
<a href="/websites/create" class="btn btn-primary" role="button" aria-label="Create a new website">Add Website</a>
|
||||
</header>
|
||||
<div class="websites-container">
|
||||
if len(websites) == 0 {
|
||||
<p>No Websites yet. <a href="">Register a website.</a></p>
|
||||
<h2>No Websites yet.</h2>
|
||||
<p>Create your first website to get started with webweav.ing tools.</p>
|
||||
<a href="/websites/create" class="btn btn-primary">Create Your First Website</a>
|
||||
} else {
|
||||
<ul id="websites" hx-get="/websites" hx-trigger="newWebsite from:body" hx-swap="outerHTML">
|
||||
<ul
|
||||
id="websites"
|
||||
role="list"
|
||||
aria-label="Your websites"
|
||||
hx-get="/websites"
|
||||
hx-trigger="newWebsite from:body"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
for _, w := range websites {
|
||||
<li>
|
||||
<a href={ templ.URL(wUrl(w) + "/dashboard") }>{ w.Name }</a>
|
||||
<li role="listitem">
|
||||
<article class="website-card">
|
||||
<header class="website-header">
|
||||
<h2 class="website-name">
|
||||
<a href={ templ.URL(wUrl(w) + "/dashboard") } aria-label={ fmt.Sprintf("Manage %s website dashboard", w.Name) }>
|
||||
{ w.Name }
|
||||
</a>
|
||||
</h2>
|
||||
<span class="website-url" aria-label="Website URL">{ w.Url.String() }</span>
|
||||
</header>
|
||||
<div class="website-actions">
|
||||
<a href={ templ.URL(wUrl(w) + "/dashboard") } class="btn btn-outline" aria-label={ fmt.Sprintf("Open %s dashboard", w.Name) }>Dashboard</a>
|
||||
<a href={ templ.URL(wUrl(w) + "/guestbook") } target="_blank" rel="noopener" class="btn btn-outline" aria-label={ fmt.Sprintf("View %s website guestbook", w.Name) }>View Guestbook</a>
|
||||
<a href={ templ.URL(externalUrl(w.Url.String())) } target="_blank" rel="noopener" class="btn btn-outline" aria-label={ fmt.Sprintf("View %s website", w.Name) }>Visit Site</a>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,103 +142,142 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) {
|
||||
<div id="dashboard">
|
||||
@wSidebar(website)
|
||||
<div>
|
||||
<h1>Embed your Guestbook</h1>
|
||||
<p>
|
||||
Upload <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> to your site and include it in your <code>{ `<head>` }</code> tag.
|
||||
</p>
|
||||
<div>
|
||||
//<button>Copy to Clipboard</button>
|
||||
{{ gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) }}
|
||||
<section aria-labelledby="embed-instructions">
|
||||
<h1 id="embed-instructions">Embed your Guestbook</h1>
|
||||
<div class="instruction-overview">
|
||||
<p>There are two ways to add your guestbook to your website: using our JavaScript component (recommended) or with an iframe.</p>
|
||||
</div>
|
||||
<article class="instruction-method">
|
||||
<h2>Method 1: JavaScript Component (Recommended)</h2>
|
||||
<div class="instruction-step">
|
||||
<h3>Step 1: Download and upload the component</h3>
|
||||
<p>Download <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> and upload it to your website's JavaScript folder.</p>
|
||||
</div>
|
||||
<div class="instruction-step">
|
||||
<h3>Step 2: Include in your HTML head</h3>
|
||||
<figure class="code-example">
|
||||
<figcaption>Add this script tag to your <head> section:</figcaption>
|
||||
<pre>
|
||||
<code id="guestbookSnippet">
|
||||
{
|
||||
`<head>
|
||||
<script type="module" src="js/guestbook.js"></script>
|
||||
</head>` }
|
||||
<code id="guestbookSnippet" aria-label="HTML head script tag">
|
||||
<head>
|
||||
<script type="module" src="js/guestbook.js"></script>
|
||||
</head>
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
Then add the custom elements where you want your form and comments to show up
|
||||
</p>
|
||||
{{ gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) }}
|
||||
//<button>Copy to Clipboard</button>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="instruction-step">
|
||||
<h3>Step 3: Add the custom elements</h3>
|
||||
<figure class="code-example">
|
||||
<figcaption>Place these elements where you want your guestbook to appear:</figcaption>
|
||||
<pre>
|
||||
<code>
|
||||
<code aria-label="Custom guestbook elements">
|
||||
{ fmt.Sprintf(`<guestbook-form guestbook="%s"></guestbook-form>
|
||||
<guestbook-comments guestbook="%s"></guestbook-comments>`, gbUrl, gbUrl) }
|
||||
</code>
|
||||
</pre>
|
||||
</figure>
|
||||
</div>
|
||||
<p>
|
||||
If your web host does not allow CORS requests, use an iframe instead
|
||||
</p>
|
||||
<div>
|
||||
</article>
|
||||
<article class="instruction-method">
|
||||
<h2>Method 2: iframe (Alternative)</h2>
|
||||
<details>
|
||||
<summary>Use this method if your web host doesn't allow CORS requests</summary>
|
||||
<div class="instruction-step">
|
||||
<p>If your hosting provider blocks cross-origin requests, you can embed the guestbook using an iframe instead:</p>
|
||||
<figure class="code-example">
|
||||
<figcaption>iframe embedding code:</figcaption>
|
||||
<pre>
|
||||
<code>
|
||||
{ fmt.Sprintf(`<iframe src="%s" title="Guestbook"></iframe>`, gbUrl) }
|
||||
<code aria-label="iframe embedding code">
|
||||
{ fmt.Sprintf(`<iframe src="%s"
|
||||
title="Guestbook"
|
||||
width="100%%"
|
||||
height="600"
|
||||
style="border: 1px solid #ccc; border-radius: 8px;"
|
||||
>
|
||||
<p>Your browser does not support iframes. <a href="%s">View the guestbook directly.</a></p>
|
||||
</iframe>`, gbUrl, gbUrl) }
|
||||
</code>
|
||||
</pre>
|
||||
</figure>
|
||||
<aside role="note" class="method-note">
|
||||
<p><strong>Note:</strong> The iframe method may have styling limitations and won't integrate as seamlessly with your site's design.</p>
|
||||
</aside>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
<aside role="complementary" class="help-section">
|
||||
<h2>Need Help?</h2>
|
||||
<p>If you're having trouble embedding your guestbook, check out our <a href="/help">help documentation</a> or contact support.</p>
|
||||
</aside>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ websiteSettingsForm(data CommonData, website models.Website, form forms.WebsiteSettingsForm) {
|
||||
<h3>Website Settings</h3>
|
||||
<div>
|
||||
<legend id="website-settings-heading">Website Settings</legend>
|
||||
<div class="form-group">
|
||||
{{ err, exists := form.FieldErrors["ws_name"] }}
|
||||
<label for="ws_name">Site Name: </label>
|
||||
<label for="ws_name">Site Name <span aria-label="required">*</span></label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
if form.SiteName != "" {
|
||||
<input type="text" name="ws_name" id="sitename" value={ form.SiteName } required/>
|
||||
<input type="text" name="ws_name" id="sitename" value={ form.SiteName } required aria-describedby="sitename-help"/>
|
||||
} else {
|
||||
<input type="text" name="ws_name" id="sitename" value={ website.Name } required/>
|
||||
<input type="text" name="ws_name" id="sitename" value={ website.Name } required aria-describedby="sitename-help"/>
|
||||
}
|
||||
<small id="sitename-help">The display name for your website</small>
|
||||
</div>
|
||||
<div>
|
||||
<div class="form-group">
|
||||
{{ err, exists = form.FieldErrors["ws_url"] }}
|
||||
<label for="ws_url">Site URL: </label>
|
||||
<label for="ws_url">Site URL <span aria-label="required">*</span></label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
if form.SiteUrl != "" {
|
||||
<input type="text" name="ws_url" id="ws_url" value={ form.SiteUrl } required/>
|
||||
<input type="url" name="ws_url" id="ws_url" value={ form.SiteUrl } required aria-describedby="siteurl-help"/>
|
||||
} else {
|
||||
<input type="text" name="ws_url" id="ws_url" value={ website.Url.String() } required/>
|
||||
<input type="url" name="ws_url" id="ws_url" value={ website.Url.String() } required aria-describedby="siteurl-help"/>
|
||||
}
|
||||
<small id="siteurl-help">The full URL where your website can be accessed</small>
|
||||
</div>
|
||||
<div>
|
||||
{{ err, exists = form.FieldErrors["ws_author"] }}
|
||||
<label for="ws_author">Site Author: </label>
|
||||
<label for="ws_author">Site Author <span aria-label="required">*</span></label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
if form.AuthorName != "" {
|
||||
<input type="text" name="ws_author" id="authorname" value={ form.AuthorName } required/>
|
||||
<input type="text" name="ws_author" id="authorname" value={ form.AuthorName } required aria-describedby="authorname-help"/>
|
||||
} else {
|
||||
<input type="text" name="ws_author" id="authorname" value={ website.AuthorName } required/>
|
||||
<input type="text" name="ws_author" id="authorname" value={ website.AuthorName } required aria-describedby="authorname-help"/>
|
||||
}
|
||||
<small id="authorname-help">Your name or the website owner's name</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ guestbookSettingsForm(data CommonData, website models.Website, gb models.Guestbook, form forms.WebsiteSettingsForm) {
|
||||
<h3>Guestbook Settings</h3>
|
||||
<div>
|
||||
<label>Guestbook Visibility</label>
|
||||
<label for="gb_visible_true">
|
||||
<legend>Guestbook Settings</legend>
|
||||
<div class="form-group">
|
||||
<fieldset class="radio-group">
|
||||
<legend>Guestbook Visibility</legend>
|
||||
<label>
|
||||
<input type="radio" name="gb_visible" id="gb_visible_true" value="true" checked?={ gb.Settings.IsVisible }/>
|
||||
Public
|
||||
</label>
|
||||
<label for="gb_visible_false">
|
||||
<label>
|
||||
<input type="radio" name="gb_visible" id="gb_visible_false" value="false" checked?={ !gb.Settings.IsVisible }/>
|
||||
Private
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div>
|
||||
<label>Guestbook Commenting</label>
|
||||
<select name="gb_commenting" id="gb-commenting">
|
||||
<div class="form-group">
|
||||
<label for="gb-commenting">Guestbook Commenting</label>
|
||||
<select name="gb_commenting" id="gb-commenting" aria-describedby="commenting-help">
|
||||
<option value="true" selected?={ gb.Settings.IsCommentingEnabled }>Enabled</option>
|
||||
<option value="1h">Disabled for 1 Hour</option>
|
||||
<option value="4h">Disabled for 4 Hours</option>
|
||||
@ -217,9 +291,11 @@ templ guestbookSettingsForm(data CommonData, website models.Website, gb models.G
|
||||
{{ localtime := gb.Settings.ReenableCommenting.In(data.CurrentUser.Settings.LocalTimezone) }}
|
||||
<label>Commenting re-enabled on <time value={ localtime.Format(time.RFC3339) }>{ localtime.Format("2 January 2006") } at { localtime.Format("3:04PM MST") }</time></label>
|
||||
}
|
||||
<small id="commenting-help">Control when users can post new comments</small>
|
||||
</div>
|
||||
<div>
|
||||
<label>Enable Widgets</label>
|
||||
<div class="form-group">
|
||||
<fieldset class="radio-group">
|
||||
<legend>Enable Widgets</legend>
|
||||
<label for="gb_remote_true">
|
||||
<input type="radio" name="gb_remote" id="gb_remote_true" value="true" checked?={ gb.Settings.AllowRemoteHostAccess }/>
|
||||
Yes
|
||||
@ -228,6 +304,8 @@ templ guestbookSettingsForm(data CommonData, website models.Website, gb models.G
|
||||
<input type="radio" name="gb_remote" id="gb_remote_false" value="false" checked?={ !gb.Settings.AllowRemoteHostAccess }/>
|
||||
No
|
||||
</label>
|
||||
</fieldset>
|
||||
<small>Allow embedding guestbook on external websites</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -239,27 +317,36 @@ templ SettingsForm(data CommonData, website models.Website, form forms.WebsiteSe
|
||||
{ msg }
|
||||
</p>
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
|
||||
<div id="settings_form">
|
||||
<fieldset>
|
||||
@websiteSettingsForm(data, website, form)
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@guestbookSettingsForm(data, website, gb, form)
|
||||
</div>
|
||||
<input type="submit" value="Submit"/>
|
||||
</fieldset>
|
||||
<button type="submit">Save Settings</button>
|
||||
</form>
|
||||
}
|
||||
|
||||
templ DeleteForm(data CommonData, website models.Website, form forms.WebsiteDeleteForm) {
|
||||
{{ putUrl := fmt.Sprintf("/websites/%s", shortIdToSlug(website.ShortId)) }}
|
||||
<form hx-put={ putUrl } hx-swap="outerHTML">
|
||||
<form hx-put={ putUrl } hx-swap="outerHTML" aria-label="Delete website">
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
|
||||
<h3>Delete Website</h3>
|
||||
<p>Deleting a website is permanent. Be absolutely sure before proceeding.</p>
|
||||
<fieldset class="danger-zone">
|
||||
<legend id="danger-zone-heading">Delete Website</legend>
|
||||
<aside role="alert" class="warning-notice">
|
||||
<p><strong>Warning:</strong> Deleting a website is permanent. Be absolutely sure before proceeding.</p>
|
||||
</aside>
|
||||
{{ err, exists := form.FieldErrors["delete"] }}
|
||||
<label for="delete">Type your site name in the form.</label>
|
||||
<div class="form-group">
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
<input type="text" name="delete" id="delete" required/>
|
||||
<input type="submit" value="Delete" class="danger"/>
|
||||
<label for="delete">Type your site name to confirm deletion</label>
|
||||
<input type="text" name="delete" id="delete" required aria-describedby="delete-help" placeholder={ website.Name }/>
|
||||
<small id="delete-help">Type { website.Name } exactly as shown to confirm deletion</small>
|
||||
</div>
|
||||
<button type="submit" class="danger">Delete Website</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
}
|
||||
|
||||
@ -269,8 +356,12 @@ templ WebsiteDashboardSettings(data CommonData, website models.Website, form for
|
||||
<div id="dashboard">
|
||||
@wSidebar(website)
|
||||
<div>
|
||||
<section aria-labelledby="website-settings-heading">
|
||||
@SettingsForm(data, website, form, "")
|
||||
</section>
|
||||
<section aria-labelledby="danger-zone-heading">
|
||||
@DeleteForm(data, website, forms.WebsiteDeleteForm{})
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user