chore: fresh restart
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,8 +0,0 @@
|
||||
*.env
|
||||
*_templ.go
|
||||
*_templ.txt
|
||||
node_modules/
|
||||
bin/
|
||||
tmp/
|
||||
.dist/
|
||||
assets/css/uno.css
|
||||
20
.golangci.yml
Normal file
20
.golangci.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
modules-download-mode: readonly
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- errcheck
|
||||
- goimports
|
||||
- gofumpt
|
||||
- revive # golint
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
16
app.go
Normal file
16
app.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package capytalcodecomicverse
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
templates fs.FS
|
||||
}
|
||||
|
||||
func NewApp(templates fs.FS) *App {
|
||||
return &App{
|
||||
templates: templates,
|
||||
}
|
||||
}
|
||||
117
app/app.go
117
app/app.go
@@ -1,117 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/assets"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/configs"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/handlers/pages"
|
||||
devPages "forge.capytal.company/capytalcode/project-comicverse/handlers/pages/dev"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/middleware"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
dev bool
|
||||
port int
|
||||
logger *slog.Logger
|
||||
server *http.Server
|
||||
assets http.Handler
|
||||
}
|
||||
|
||||
type AppOpts struct {
|
||||
Dev *bool
|
||||
Port *int
|
||||
Assets http.Handler
|
||||
}
|
||||
|
||||
func NewApp(opts ...AppOpts) *App {
|
||||
if len(opts) == 0 {
|
||||
opts[0] = AppOpts{}
|
||||
}
|
||||
|
||||
if opts[0].Dev == nil {
|
||||
d := false
|
||||
opts[0].Dev = &d
|
||||
}
|
||||
|
||||
if opts[0].Port == nil {
|
||||
d := 8080
|
||||
opts[0].Port = &d
|
||||
}
|
||||
|
||||
app := &App{
|
||||
dev: *opts[0].Dev,
|
||||
port: *opts[0].Port,
|
||||
assets: opts[0].Assets,
|
||||
}
|
||||
|
||||
configs.DEVELOPMENT = app.dev
|
||||
|
||||
app.setLogger()
|
||||
app.setServer()
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func (a *App) setLogger() {
|
||||
a.logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug,
|
||||
}))
|
||||
}
|
||||
|
||||
func (a *App) setServer() {
|
||||
r := router.NewRouter()
|
||||
|
||||
r.Use(middleware.NewLoggerMiddleware(a.logger))
|
||||
|
||||
if configs.DEVELOPMENT {
|
||||
a.logger.Info("RUNNING IN DEVELOPMENT MODE")
|
||||
|
||||
r.Use(middleware.DevMiddleware)
|
||||
r.Handle("/_dev", devPages.Routes())
|
||||
} else {
|
||||
r.Use(middleware.CacheMiddleware)
|
||||
}
|
||||
|
||||
if configs.DEVELOPMENT && a.assets != nil {
|
||||
r.Handle("/assets/", a.assets)
|
||||
} else {
|
||||
r.Handle("/assets/", http.StripPrefix("/assets/", http.FileServerFS(assets.ASSETS)))
|
||||
}
|
||||
|
||||
r.Handle("/", pages.Routes(a.logger))
|
||||
|
||||
srv := http.Server{
|
||||
Addr: fmt.Sprintf(":%v", a.port),
|
||||
Handler: r,
|
||||
}
|
||||
|
||||
a.server = &srv
|
||||
}
|
||||
|
||||
func (a *App) Run() {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
go func() {
|
||||
if err := a.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
a.logger.Error("Listen and serve returned error", slog.String("error", err.Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
a.logger.Info("Gracefully shutting doing server")
|
||||
if err := a.server.Shutdown(context.TODO()); err != nil {
|
||||
a.logger.Error("Server shut down returned an error", slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
a.logger.Info("FINAL")
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"embed"
|
||||
)
|
||||
|
||||
//go:embed css fonts lib
|
||||
var ASSETS embed.FS
|
||||
@@ -1,175 +0,0 @@
|
||||
@font-face {
|
||||
font-family: "Karla";
|
||||
font-style: normal;
|
||||
src: url("/assets/fonts/KarlaVF.woff2") format("woff2"),
|
||||
url("/assets/fonts/KarlaVF.ttf") format("truetype"),
|
||||
url("/assets/fonts/KarlaMedium.otf") format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Karla";
|
||||
font-style: italic;
|
||||
src: url("/assets/fonts/KarlaItalicVF.woff2") format("woff2"),
|
||||
url("/assets/fonts/KarlaItalicVF.ttf") format("truetype"),
|
||||
url("/assets/fonts/KarlaItalicMedium.otf") format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Playfair";
|
||||
font-style: normal;
|
||||
src: url("/assets/fonts/PlayfairRomanVF.woff2") format("woff2"),
|
||||
url("/assets/fonts/PlayfairRomanVF.ttf") format("truetype"),
|
||||
url("/assets/fonts/PlayfairDisplay.otf") format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Playfair";
|
||||
font-style: italic;
|
||||
src: url("/assets/fonts/PlayfairItalicVF.woff2") format("woff2"),
|
||||
url("/assets/fonts/PlayfairItalicVF.ttf") format("truetype"),
|
||||
url("/assets/fonts/PlayfairDisplayItalic.otf") format("opentype");
|
||||
}
|
||||
|
||||
:root * {
|
||||
--theme-accent-hue: var(--user-theme-accent-hue, 280);
|
||||
--theme-accent-saturation: var(--user-theme-accent-saturation, 95%);
|
||||
--theme-accent-10: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 7%);
|
||||
--theme-accent-20: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 10%);
|
||||
--theme-accent-30: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 13%);
|
||||
--theme-accent-40: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 16%);
|
||||
--theme-accent-50: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 19%);
|
||||
--theme-accent-60: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 23%);
|
||||
--theme-accent-70: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 28%);
|
||||
--theme-accent-80: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 38%);
|
||||
--theme-accent-90: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 43%);
|
||||
--theme-accent-100: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 48%);
|
||||
--theme-accent-110: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 71%);
|
||||
--theme-accent-120: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 93%);
|
||||
|
||||
--theme-neutral-hue: var(--user-theme-neutral-hue, 0);
|
||||
--theme-neutral-saturation: var(--user-theme-neutral-saturation, 0%);
|
||||
--theme-neutral-10: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 7%);
|
||||
--theme-neutral-20: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 10%);
|
||||
--theme-neutral-30: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 13%);
|
||||
--theme-neutral-40: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 16%);
|
||||
--theme-neutral-50: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 19%);
|
||||
--theme-neutral-60: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 23%);
|
||||
--theme-neutral-70: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 28%);
|
||||
--theme-neutral-80: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 38%);
|
||||
--theme-neutral-90: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 43%);
|
||||
--theme-neutral-100: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 48%);
|
||||
--theme-neutral-110: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 71%);
|
||||
--theme-neutral-120: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 93%);
|
||||
|
||||
--theme-success-hue: var(--user-theme-sucesss-hue, 120);
|
||||
--theme-success-saturation: var(--user-theme-sucesss-saturation, 95%);
|
||||
--theme-success-10: hsl(var(--theme-success-hue), var(--theme-success-saturation), 7%);
|
||||
--theme-success-20: hsl(var(--theme-success-hue), var(--theme-success-saturation), 10%);
|
||||
--theme-success-30: hsl(var(--theme-success-hue), var(--theme-success-saturation), 13%);
|
||||
--theme-success-40: hsl(var(--theme-success-hue), var(--theme-success-saturation), 16%);
|
||||
--theme-success-50: hsl(var(--theme-success-hue), var(--theme-success-saturation), 19%);
|
||||
--theme-success-60: hsl(var(--theme-success-hue), var(--theme-success-saturation), 23%);
|
||||
--theme-success-70: hsl(var(--theme-success-hue), var(--theme-success-saturation), 28%);
|
||||
--theme-success-80: hsl(var(--theme-success-hue), var(--theme-success-saturation), 38%);
|
||||
--theme-success-90: hsl(var(--theme-success-hue), var(--theme-success-saturation), 43%);
|
||||
--theme-success-100: hsl(var(--theme-success-hue), var(--theme-success-saturation), 48%);
|
||||
--theme-success-110: hsl(var(--theme-success-hue), var(--theme-success-saturation), 71%);
|
||||
--theme-success-120: hsl(var(--theme-success-hue), var(--theme-success-saturation), 93%);
|
||||
|
||||
--theme-danger-hue: var(--user-theme-danger-hue, 10);
|
||||
--theme-danger-saturation: var(--user-theme-danger-saturation, 95%);
|
||||
--theme-danger-10: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 7%);
|
||||
--theme-danger-20: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 10%);
|
||||
--theme-danger-30: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 13%);
|
||||
--theme-danger-40: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 16%);
|
||||
--theme-danger-50: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 19%);
|
||||
--theme-danger-60: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 23%);
|
||||
--theme-danger-70: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 28%);
|
||||
--theme-danger-80: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 38%);
|
||||
--theme-danger-90: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 43%);
|
||||
--theme-danger-100: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 48%);
|
||||
--theme-danger-110: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 71%);
|
||||
--theme-danger-120: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 93%);
|
||||
|
||||
--theme-warn-hue: var(--user-theme-warn-hue, 60);
|
||||
--theme-warn-saturation: var(--user-theme-warn-saturation, 95%);
|
||||
--theme-warn-10: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 7%);
|
||||
--theme-warn-20: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 10%);
|
||||
--theme-warn-30: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 13%);
|
||||
--theme-warn-40: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 16%);
|
||||
--theme-warn-50: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 19%);
|
||||
--theme-warn-60: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 23%);
|
||||
--theme-warn-70: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 28%);
|
||||
--theme-warn-80: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 38%);
|
||||
--theme-warn-90: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 43%);
|
||||
--theme-warn-100: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 48%);
|
||||
--theme-warn-110: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 71%);
|
||||
--theme-warn-120: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 93%);
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root * {
|
||||
--theme-accent-10: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 99%);
|
||||
--theme-accent-20: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 98%);
|
||||
--theme-accent-30: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 94%);
|
||||
--theme-accent-40: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 91%);
|
||||
--theme-accent-50: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 88%);
|
||||
--theme-accent-60: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 85%);
|
||||
--theme-accent-70: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 81%);
|
||||
--theme-accent-80: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 73%);
|
||||
--theme-accent-90: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 55%);
|
||||
--theme-accent-100: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 51%);
|
||||
--theme-accent-110: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 39%);
|
||||
--theme-accent-120: hsl(var(--theme-accent-hue), var(--theme-accent-saturation), 13%);
|
||||
|
||||
--theme-neutral-10: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 99%);
|
||||
--theme-neutral-20: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 98%);
|
||||
--theme-neutral-30: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 94%);
|
||||
--theme-neutral-40: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 91%);
|
||||
--theme-neutral-50: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 88%);
|
||||
--theme-neutral-60: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 85%);
|
||||
--theme-neutral-70: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 81%);
|
||||
--theme-neutral-80: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 73%);
|
||||
--theme-neutral-90: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 55%);
|
||||
--theme-neutral-100: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 51%);
|
||||
--theme-neutral-110: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 39%);
|
||||
--theme-neutral-120: hsl(var(--theme-neutral-hue), var(--theme-neutral-saturation), 13%);
|
||||
|
||||
--theme-success-10: hsl(var(--theme-success-hue), var(--theme-success-saturation), 99%);
|
||||
--theme-success-20: hsl(var(--theme-success-hue), var(--theme-success-saturation), 98%);
|
||||
--theme-success-30: hsl(var(--theme-success-hue), var(--theme-success-saturation), 94%);
|
||||
--theme-success-40: hsl(var(--theme-success-hue), var(--theme-success-saturation), 91%);
|
||||
--theme-success-50: hsl(var(--theme-success-hue), var(--theme-success-saturation), 88%);
|
||||
--theme-success-60: hsl(var(--theme-success-hue), var(--theme-success-saturation), 85%);
|
||||
--theme-success-70: hsl(var(--theme-success-hue), var(--theme-success-saturation), 81%);
|
||||
--theme-success-80: hsl(var(--theme-success-hue), var(--theme-success-saturation), 73%);
|
||||
--theme-success-90: hsl(var(--theme-success-hue), var(--theme-success-saturation), 55%);
|
||||
--theme-success-100: hsl(var(--theme-success-hue), var(--theme-success-saturation), 51%);
|
||||
--theme-success-110: hsl(var(--theme-success-hue), var(--theme-success-saturation), 39%);
|
||||
--theme-success-120: hsl(var(--theme-success-hue), var(--theme-success-saturation), 13%);
|
||||
|
||||
--theme-danger-10: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 99%);
|
||||
--theme-danger-20: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 98%);
|
||||
--theme-danger-30: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 94%);
|
||||
--theme-danger-40: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 91%);
|
||||
--theme-danger-50: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 88%);
|
||||
--theme-danger-60: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 85%);
|
||||
--theme-danger-70: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 81%);
|
||||
--theme-danger-80: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 73%);
|
||||
--theme-danger-90: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 55%);
|
||||
--theme-danger-100: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 51%);
|
||||
--theme-danger-110: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 39%);
|
||||
--theme-danger-120: hsl(var(--theme-danger-hue), var(--theme-danger-saturation), 13%);
|
||||
|
||||
--theme-warn-10: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 99%);
|
||||
--theme-warn-20: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 98%);
|
||||
--theme-warn-30: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 94%);
|
||||
--theme-warn-40: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 91%);
|
||||
--theme-warn-50: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 88%);
|
||||
--theme-warn-60: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 85%);
|
||||
--theme-warn-70: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 81%);
|
||||
--theme-warn-80: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 73%);
|
||||
--theme-warn-90: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 55%);
|
||||
--theme-warn-100: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 51%);
|
||||
--theme-warn-110: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 39%);
|
||||
--theme-warn-120: hsl(var(--theme-warn-hue), var(--theme-warn-saturation), 13%);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,93 +0,0 @@
|
||||
Copyright 2019 The Karla Project Authors (https://github.com/googlefonts/karla)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2005–2023 The Playfair Project Authors (https://github.com/clauseggers/Playfair)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,18 +0,0 @@
|
||||
|
||||
# Fonts used
|
||||
|
||||
## Karla
|
||||
|
||||
This project uses the [Karla font](https://github.com/googlefonts/karla/tree/main),
|
||||
created by [Jonathan Pinhorn](http://twitter.com/jonpinhorn_type) and released through
|
||||
Google Webfonts in 2012. Karla font is released under the SIL Open Font License, Version 1.1,
|
||||
which a copy can be found [locally](./LICENSE-Karla), [on GitHub](https://github.com/googlefonts/karla/blob/main/OFL.txt)
|
||||
and [on the OFL's site](https://scripts.sil.org/OFL).
|
||||
|
||||
## Playfair Roman, Playfair Display & Playfair Italic
|
||||
|
||||
This project uses the [Playfair font family, version 2.1](https://github.com/clauseggers/Playfair),
|
||||
created by [Claus Eggers Sørensen](http://forthehearts.net/) and released under the
|
||||
SIL Open Font License Version 1.1, which a copy can be found [locally](./LICENSE-Playfair),
|
||||
[on GitHub](https://github.com/clauseggers/Playfair/blob/master/OFL.txt) and
|
||||
[on the OFL's site](https://scripts.sil.org/OFL)
|
||||
@@ -1,3 +0,0 @@
|
||||
import * as hello from './hello.js';
|
||||
|
||||
hello.hello();
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Says hello!
|
||||
*/
|
||||
export function hello() {
|
||||
console.log('hello world');
|
||||
}
|
||||
239
assets/lib/htmx.d.ts
vendored
239
assets/lib/htmx.d.ts
vendored
@@ -1,239 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
|
||||
/**
|
||||
* @copyright Big Sky Software 2024
|
||||
* @license 0BSD
|
||||
* @copyright Big Sky Software 2024
|
||||
* @author Big Sky Software <https://github.com/bigskysoftware>
|
||||
*
|
||||
* This source code is copied from HTMX's GitHub repository, located at
|
||||
* https://github.com/bigskysoftware/htmx/blob/master/dist/htmx.esm.js.
|
||||
*
|
||||
* This source code and the original are licensed under the Zero-Clause BSD license,
|
||||
* which a copy is available in the original [GitHub](https://github.com/bigskysoftware/htmx/blob/master/LICENSE)
|
||||
* and here below:
|
||||
*
|
||||
* Zero-Clause BSD
|
||||
* =============
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for
|
||||
* any purpose with or without fee is hereby granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
||||
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
||||
* FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
||||
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
export default htmx;
|
||||
export type HttpVerb = "get" | "head" | "post" | "put" | "delete" | "connect" | "options" | "trace" | "patch";
|
||||
export type SwapOptions = {
|
||||
select?: string;
|
||||
selectOOB?: string;
|
||||
eventInfo?: any;
|
||||
anchor?: string;
|
||||
contextElement?: Element;
|
||||
afterSwapCallback?: swapCallback;
|
||||
afterSettleCallback?: swapCallback;
|
||||
};
|
||||
export type swapCallback = () => any;
|
||||
export type HtmxSwapStyle = "innerHTML" | "outerHTML" | "beforebegin" | "afterbegin" | "beforeend" | "afterend" | "delete" | "none" | string;
|
||||
export type HtmxSwapSpecification = {
|
||||
swapStyle: HtmxSwapStyle;
|
||||
swapDelay: number;
|
||||
settleDelay: number;
|
||||
transition?: boolean;
|
||||
ignoreTitle?: boolean;
|
||||
head?: string;
|
||||
scroll?: "top" | "bottom";
|
||||
scrollTarget?: string;
|
||||
show?: string;
|
||||
showTarget?: string;
|
||||
focusScroll?: boolean;
|
||||
};
|
||||
export type ConditionalFunction = ((this: Node, evt: Event) => boolean) & {
|
||||
source: string;
|
||||
};
|
||||
export type HtmxTriggerSpecification = {
|
||||
trigger: string;
|
||||
pollInterval?: number;
|
||||
eventFilter?: ConditionalFunction;
|
||||
changed?: boolean;
|
||||
once?: boolean;
|
||||
consume?: boolean;
|
||||
delay?: number;
|
||||
from?: string;
|
||||
target?: string;
|
||||
throttle?: number;
|
||||
queue?: string;
|
||||
root?: string;
|
||||
threshold?: string;
|
||||
};
|
||||
export type HtmxElementValidationError = {
|
||||
elt: Element;
|
||||
message: string;
|
||||
validity: ValidityState;
|
||||
};
|
||||
export type HtmxHeaderSpecification = Record<string, string>;
|
||||
export type HtmxAjaxHelperContext = {
|
||||
source?: Element | string;
|
||||
event?: Event;
|
||||
handler?: HtmxAjaxHandler;
|
||||
target?: Element | string;
|
||||
swap?: HtmxSwapStyle;
|
||||
values?: any | FormData;
|
||||
headers?: Record<string, string>;
|
||||
select?: string;
|
||||
};
|
||||
export type HtmxRequestConfig = {
|
||||
boosted: boolean;
|
||||
useUrlParams: boolean;
|
||||
formData: FormData;
|
||||
/**
|
||||
* formData proxy
|
||||
*/
|
||||
parameters: any;
|
||||
unfilteredFormData: FormData;
|
||||
/**
|
||||
* unfilteredFormData proxy
|
||||
*/
|
||||
unfilteredParameters: any;
|
||||
headers: HtmxHeaderSpecification;
|
||||
target: Element;
|
||||
verb: HttpVerb;
|
||||
errors: HtmxElementValidationError[];
|
||||
withCredentials: boolean;
|
||||
timeout: number;
|
||||
path: string;
|
||||
triggeringEvent: Event;
|
||||
};
|
||||
export type HtmxResponseInfo = {
|
||||
xhr: XMLHttpRequest;
|
||||
target: Element;
|
||||
requestConfig: HtmxRequestConfig;
|
||||
etc: HtmxAjaxEtc;
|
||||
boosted: boolean;
|
||||
select: string;
|
||||
pathInfo: {
|
||||
requestPath: string;
|
||||
finalRequestPath: string;
|
||||
responsePath: string | null;
|
||||
anchor: string;
|
||||
};
|
||||
failed?: boolean;
|
||||
successful?: boolean;
|
||||
keepIndicators?: boolean;
|
||||
};
|
||||
export type HtmxAjaxEtc = {
|
||||
returnPromise?: boolean;
|
||||
handler?: HtmxAjaxHandler;
|
||||
select?: string;
|
||||
targetOverride?: Element;
|
||||
swapOverride?: HtmxSwapStyle;
|
||||
headers?: Record<string, string>;
|
||||
values?: any | FormData;
|
||||
credentials?: boolean;
|
||||
timeout?: number;
|
||||
};
|
||||
export type HtmxResponseHandlingConfig = {
|
||||
code?: string;
|
||||
swap: boolean;
|
||||
error?: boolean;
|
||||
ignoreTitle?: boolean;
|
||||
select?: string;
|
||||
target?: string;
|
||||
swapOverride?: string;
|
||||
event?: string;
|
||||
};
|
||||
export type HtmxBeforeSwapDetails = HtmxResponseInfo & {
|
||||
shouldSwap: boolean;
|
||||
serverResponse: any;
|
||||
isError: boolean;
|
||||
ignoreTitle: boolean;
|
||||
selectOverride: string;
|
||||
swapOverride: string;
|
||||
};
|
||||
export type HtmxAjaxHandler = (elt: Element, responseInfo: HtmxResponseInfo) => any;
|
||||
export type HtmxSettleTask = (() => void);
|
||||
export type HtmxSettleInfo = {
|
||||
tasks: HtmxSettleTask[];
|
||||
elts: Element[];
|
||||
title?: string;
|
||||
};
|
||||
export type HtmxExtension = {
|
||||
init: (api: any) => void;
|
||||
onEvent: (name: string, event: Event | CustomEvent) => boolean;
|
||||
transformResponse: (text: string, xhr: XMLHttpRequest, elt: Element) => string;
|
||||
isInlineSwap: (swapStyle: HtmxSwapStyle) => boolean;
|
||||
handleSwap: (swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean | Node[];
|
||||
encodeParameters: (xhr: XMLHttpRequest, parameters: FormData, elt: Node) => any | string | null;
|
||||
getSelectors: () => string[] | null;
|
||||
};
|
||||
declare namespace htmx {
|
||||
let onLoad: (callback: (elt: Node) => void) => EventListener;
|
||||
let process: (elt: Element | string) => void;
|
||||
let on: (arg1: EventTarget | string, arg2: string | EventListener, arg3?: EventListener | any | boolean, arg4?: any | boolean) => EventListener;
|
||||
let off: (arg1: EventTarget | string, arg2: string | EventListener, arg3?: EventListener) => EventListener;
|
||||
let trigger: (elt: EventTarget | string, eventName: string, detail?: any | undefined) => boolean;
|
||||
let ajax: (verb: HttpVerb, path: string, context: Element | string | HtmxAjaxHelperContext) => Promise<void>;
|
||||
let find: (eltOrSelector: ParentNode | string, selector?: string) => Element | null;
|
||||
let findAll: (eltOrSelector: ParentNode | string, selector?: string) => NodeListOf<Element>;
|
||||
let closest: (elt: Element | string, selector: string) => Element | null;
|
||||
function values(elt: Element, type: HttpVerb): any;
|
||||
let remove: (elt: Node, delay?: number) => void;
|
||||
let addClass: (elt: Element | string, clazz: string, delay?: number) => void;
|
||||
let removeClass: (node: Node | string, clazz: string, delay?: number) => void;
|
||||
let toggleClass: (elt: Element | string, clazz: string) => void;
|
||||
let takeClass: (elt: Node | string, clazz: string) => void;
|
||||
let swap: (target: string | Element, content: string, swapSpec: HtmxSwapSpecification, swapOptions?: SwapOptions) => void;
|
||||
let defineExtension: (name: string, extension: HtmxExtension) => void;
|
||||
let removeExtension: (name: string) => void;
|
||||
let logAll: () => void;
|
||||
let logNone: () => void;
|
||||
let logger: any;
|
||||
namespace config {
|
||||
let historyEnabled: boolean;
|
||||
let historyCacheSize: number;
|
||||
let refreshOnHistoryMiss: boolean;
|
||||
let defaultSwapStyle: HtmxSwapStyle;
|
||||
let defaultSwapDelay: number;
|
||||
let defaultSettleDelay: number;
|
||||
let includeIndicatorStyles: boolean;
|
||||
let indicatorClass: string;
|
||||
let requestClass: string;
|
||||
let addedClass: string;
|
||||
let settlingClass: string;
|
||||
let swappingClass: string;
|
||||
let allowEval: boolean;
|
||||
let allowScriptTags: boolean;
|
||||
let inlineScriptNonce: string;
|
||||
let inlineStyleNonce: string;
|
||||
let attributesToSettle: string[];
|
||||
let withCredentials: boolean;
|
||||
let timeout: number;
|
||||
let wsReconnectDelay: "full-jitter" | ((retryCount: number) => number);
|
||||
let wsBinaryType: BinaryType;
|
||||
let disableSelector: string;
|
||||
let scrollBehavior: "auto" | "instant" | "smooth";
|
||||
let defaultFocusScroll: boolean;
|
||||
let getCacheBusterParam: boolean;
|
||||
let globalViewTransitions: boolean;
|
||||
let methodsThatUseUrlParams: (HttpVerb)[];
|
||||
let selfRequestsOnly: boolean;
|
||||
let ignoreTitle: boolean;
|
||||
let scrollIntoViewOnBoost: boolean;
|
||||
let triggerSpecsCache: any | null;
|
||||
let disableInheritance: boolean;
|
||||
let responseHandling: HtmxResponseHandlingConfig[];
|
||||
let allowNestedOobSwaps: boolean;
|
||||
}
|
||||
let parseInterval: (str: string) => number | undefined;
|
||||
let _: (str: string) => any;
|
||||
let version: string;
|
||||
}
|
||||
5409
assets/lib/htmx.js
5409
assets/lib/htmx.js
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
package configs
|
||||
|
||||
const (
|
||||
APP_NAME = "Comicverse"
|
||||
APP_VERSION = "0.0.0"
|
||||
)
|
||||
|
||||
var DEVELOPMENT = false
|
||||
240
eslint.config.js
240
eslint.config.js
@@ -1,240 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
import jsdoc from 'eslint-plugin-jsdoc';
|
||||
// @ts-expect-error eslint-plugin-jsdoc does not have type definitions
|
||||
import json from 'eslint-plugin-json';
|
||||
// @ts-expect-error eslint-plugin-import does not have type definitions
|
||||
import imports from 'eslint-plugin-import';
|
||||
import perfectionist from 'eslint-plugin-perfectionist';
|
||||
import unicorn from 'eslint-plugin-unicorn';
|
||||
import wc from 'eslint-plugin-wc';
|
||||
import globals from 'globals';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import ts from 'typescript-eslint';
|
||||
|
||||
/**
|
||||
* @typedef {Readonly<import('eslint').Linter.Config>} Config
|
||||
*/
|
||||
|
||||
/** @type {Config[]} */
|
||||
const config = [
|
||||
// Ignores
|
||||
{
|
||||
ignores: [
|
||||
'node_modules',
|
||||
'package-lock.json',
|
||||
'package.json',
|
||||
'tsconfig.json',
|
||||
'.vscode',
|
||||
'dist',
|
||||
],
|
||||
},
|
||||
// Logic plugins
|
||||
js.configs.recommended,
|
||||
(/** @type {Config} */ (ts.configs.eslintRecommended)),
|
||||
...(/** @type {Config[]} */ (ts.configs.strictTypeChecked)),
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
// @ts-expect-error import.meta.dirname is not defined but works in NodeJS
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
(/** @type {Config} */ (imports.flatConfigs.recommended)),
|
||||
{
|
||||
plugins: { wc: wc },
|
||||
rules: { ...wc.configs.recommended.rules },
|
||||
},
|
||||
{
|
||||
plugins: { unicorn: unicorn },
|
||||
},
|
||||
jsdoc.configs['flat/recommended-typescript-flavor'],
|
||||
|
||||
// Stylistic plugins
|
||||
(/** @type {Config} */ (stylistic.configs.customize({
|
||||
arrowParens: false,
|
||||
braceStyle: 'stroustrup',
|
||||
commaDangle: 'always-multiline',
|
||||
indent: 'tab',
|
||||
jsx: false,
|
||||
quoteProps: 'consistent',
|
||||
quotes: 'single',
|
||||
semi: true,
|
||||
}))),
|
||||
(/** @type {Config} */ (perfectionist.configs['recommended-natural'])),
|
||||
|
||||
// Custom config
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
globals: {
|
||||
...globals.builtin,
|
||||
...globals.es2022,
|
||||
...globals.browser,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
},
|
||||
},
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
// Imports
|
||||
'import/exports-last': 'error',
|
||||
'import/extensions': ['error', 'always', { ignorePackages: true }],
|
||||
'import/first': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-absolute-path': 'error',
|
||||
'import/no-amd': 'error',
|
||||
'import/no-commonjs': 'error',
|
||||
'import/no-cycle': 'error',
|
||||
'import/no-default-export': 'error',
|
||||
'import/no-deprecated': 'error',
|
||||
'import/no-dynamic-require': 'error',
|
||||
'import/no-import-module-exports': 'error',
|
||||
'import/no-nodejs-modules': 'error',
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-relative-parent-imports': 'error',
|
||||
'import/no-restricted-paths': 'error',
|
||||
'import/no-unassigned-import': 'error',
|
||||
'import/no-unused-modules': 'error',
|
||||
'import/no-useless-path-segments': 'error',
|
||||
'import/no-webpack-loader-syntax': 'error',
|
||||
'import/order': 'off',
|
||||
// JSDoc
|
||||
'jsdoc/check-indentation': 'warn',
|
||||
|
||||
'jsdoc/check-line-alignment': 'warn',
|
||||
'jsdoc/check-syntax': 'warn',
|
||||
'jsdoc/check-template-names': 'warn',
|
||||
'jsdoc/convert-to-jsdoc-comments': 'warn',
|
||||
'jsdoc/informative-docs': 'warn',
|
||||
'jsdoc/no-bad-blocks': 'warn',
|
||||
'jsdoc/no-blank-block-descriptions': 'warn',
|
||||
'jsdoc/no-blank-blocks': 'warn',
|
||||
'jsdoc/require-asterisk-prefix': 'warn',
|
||||
'jsdoc/require-description': 'warn',
|
||||
'jsdoc/require-description-complete-sentence': 'warn',
|
||||
'jsdoc/require-hyphen-before-param-description': 'warn',
|
||||
'jsdoc/require-template': 'warn',
|
||||
'jsdoc/require-throws': 'warn',
|
||||
'jsdoc/sort-tags': 'warn',
|
||||
|
||||
// Globals
|
||||
'no-restricted-globals': ['error'].concat(CONFUSING_BROWSER_GLOBALS()),
|
||||
|
||||
// Unicorn
|
||||
'unicorn/better-regex': 'error',
|
||||
'unicorn/custom-error-definition': 'error',
|
||||
'unicorn/no-keyword-prefix': 'error',
|
||||
'unicorn/no-unused-properties': 'error',
|
||||
'unicorn/prefer-json-parse-buffer': 'error',
|
||||
'unicorn/require-post-message-target-origin': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// Overrides
|
||||
{
|
||||
files: ['*.config.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'import/no-default-export': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// Additional file types
|
||||
{
|
||||
files: ['**/*.json'],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
...json.configs['recommended'],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* This list was copied from Facebook's create-react-app's "confusing-browser-globals" package,
|
||||
* which is licensed under the MIT license.
|
||||
*
|
||||
* The original source code of this list is available on GitHub:
|
||||
* https://github.com/facebook/create-react-app/blob/dd420a6d25d037decd7b81175626dfca817437ff/packages/confusing-browser-globals/index.js.
|
||||
*
|
||||
* The original LICENSE file can be found here:
|
||||
* https://github.com/facebook/create-react-app/blob/dd420a6d25d037decd7b81175626dfca817437ff/packages/confusing-browser-globals/LICENSE.
|
||||
* @copyright 2015-present, Facebook, Inc
|
||||
* @license MIT
|
||||
* @returns {string[]} - The globals.
|
||||
* @author Facebook
|
||||
*/
|
||||
function CONFUSING_BROWSER_GLOBALS() {
|
||||
return [
|
||||
'addEventListener',
|
||||
'blur',
|
||||
'close',
|
||||
'closed',
|
||||
'confirm',
|
||||
'defaultStatus',
|
||||
'defaultstatus',
|
||||
'event',
|
||||
'external',
|
||||
'find',
|
||||
'focus',
|
||||
'frameElement',
|
||||
'frames',
|
||||
'history',
|
||||
'innerHeight',
|
||||
'innerWidth',
|
||||
'length',
|
||||
'location',
|
||||
'locationbar',
|
||||
'menubar',
|
||||
'moveBy',
|
||||
'moveTo',
|
||||
'name',
|
||||
'onblur',
|
||||
'onerror',
|
||||
'onfocus',
|
||||
'onload',
|
||||
'onresize',
|
||||
'onunload',
|
||||
'open',
|
||||
'opener',
|
||||
'opera',
|
||||
'outerHeight',
|
||||
'outerWidth',
|
||||
'pageXOffset',
|
||||
'pageYOffset',
|
||||
'parent',
|
||||
'print',
|
||||
'removeEventListener',
|
||||
'resizeBy',
|
||||
'resizeTo',
|
||||
'screen',
|
||||
'screenLeft',
|
||||
'screenTop',
|
||||
'screenX',
|
||||
'screenY',
|
||||
'scroll',
|
||||
'scrollbars',
|
||||
'scrollBy',
|
||||
'scrollTo',
|
||||
'scrollX',
|
||||
'scrollY',
|
||||
'self',
|
||||
'status',
|
||||
'statusbar',
|
||||
'stop',
|
||||
'toolbar',
|
||||
'top',
|
||||
];
|
||||
}
|
||||
|
||||
export default config;
|
||||
154
flake.lock
generated
154
flake.lock
generated
@@ -1,81 +1,5 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"templ",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gomod2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"templ",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1722589758,
|
||||
"narHash": "sha256-sbbA8b6Q2vB/t/r1znHawoXLysCyD4L/6n6/RykiSnA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "4e08ca09253ef996bd4c03afa383b23e35fe28a1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1726243404,
|
||||
@@ -92,85 +16,9 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1724322575,
|
||||
"narHash": "sha256-kRYwAdYsaICNb2WYcWtBFG6caSuT0v/vTAyR8ap0IR0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2a02822b466ffb9f1c02d07c5dd6b96d08b56c6b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"templ": "templ"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"templ": {
|
||||
"inputs": {
|
||||
"gitignore": "gitignore",
|
||||
"gomod2nix": "gomod2nix",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"xc": "xc"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1725786353,
|
||||
"narHash": "sha256-lU8aVTw73HX0lNGPyD8Xnvtnr2VFTXv/S6xCVn6Lg74=",
|
||||
"owner": "a-h",
|
||||
"repo": "templ",
|
||||
"rev": "e2511cd57e5ecd28ce6e3d944c87f1e31e20b596",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "a-h",
|
||||
"ref": "v0.2.778",
|
||||
"repo": "templ",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"xc": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": [
|
||||
"templ",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724404748,
|
||||
"narHash": "sha256-p6rXzNiDm2uBvO1MLzC5pJp/0zRNzj/snBzZI0ce62s=",
|
||||
"owner": "joerdav",
|
||||
"repo": "xc",
|
||||
"rev": "960ff9f109d47a19122cfb015721a76e3a0f23a2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "joerdav",
|
||||
"repo": "xc",
|
||||
"type": "github"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
17
flake.nix
17
flake.nix
@@ -2,13 +2,8 @@
|
||||
description = "My development environment";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
templ.url = "github:a-h/templ?ref=v0.2.778";
|
||||
};
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
...
|
||||
} @ inputs: let
|
||||
outputs = {nixpkgs, ...}: let
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
@@ -20,7 +15,6 @@
|
||||
pkgs = import nixpkgs {inherit system;};
|
||||
in
|
||||
f system pkgs);
|
||||
templ = system: inputs.templ.packages.${system}.templ;
|
||||
in {
|
||||
devShells = forAllSystems (system: pkgs: {
|
||||
default = pkgs.mkShell {
|
||||
@@ -28,19 +22,12 @@
|
||||
hardeningDisable = ["all"];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
# Javascript tools
|
||||
eslint_d
|
||||
nodejs_22
|
||||
nodePackages_latest.eslint
|
||||
|
||||
# Go tools
|
||||
go
|
||||
gofumpt
|
||||
golangci-lint
|
||||
golines
|
||||
gofumpt
|
||||
gotools
|
||||
delve
|
||||
(templ system)
|
||||
|
||||
# Sqlite tools
|
||||
sqlite
|
||||
|
||||
2
go.mod
2
go.mod
@@ -1,5 +1,3 @@
|
||||
module forge.capytal.company/capytalcode/project-comicverse
|
||||
|
||||
go 1.22.7
|
||||
|
||||
require github.com/a-h/templ v0.2.778 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,2 +0,0 @@
|
||||
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
|
||||
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router/rerrors"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/cookies"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/forms"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/templates/layouts"
|
||||
)
|
||||
|
||||
type Dashboard struct {
|
||||
Message string `form:"message"`
|
||||
Limit int `form:"limit"`
|
||||
Optional *string `form:"optional"`
|
||||
}
|
||||
|
||||
type DashboardCookie struct {
|
||||
Hello string `cookie:"dashboard-cookie"`
|
||||
Bool bool
|
||||
Test int
|
||||
}
|
||||
|
||||
func (p *Dashboard) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := forms.Unmarshal(r, p); err != nil {
|
||||
forms.RerrUnsmarshal(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
hasCookie := true
|
||||
|
||||
c := DashboardCookie{"hello world", true, 0}
|
||||
if _, err := cookies.UnmarshalIfRequest(r, &c); err != nil {
|
||||
rerrors.InternalError(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
log.Print(hasCookie, c)
|
||||
|
||||
if err := cookies.MarshalToWriter(c, w); err != nil {
|
||||
rerrors.InternalError(err).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
if err := p.Component().Render(r.Context(), w); err != nil {
|
||||
rerrors.InternalError(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
templ (p *Dashboard) Component() {
|
||||
@layouts.Page() {
|
||||
<div class="text-danger-100 font-sans">
|
||||
<p>{ p.Message }</p>
|
||||
<p>{ strconv.Itoa(p.Limit) }</p>
|
||||
if p.Optional != nil {
|
||||
<p>{ *p.Optional }</p>
|
||||
} else {
|
||||
<p>nil</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package dev
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router/rerrors"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/templates/layouts"
|
||||
)
|
||||
|
||||
type Colors struct{}
|
||||
|
||||
func (p *Colors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := p.Component().Render(r.Context(), w); err != nil {
|
||||
rerrors.InternalError(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
templ (p *Colors) Heading() {
|
||||
<script type="module" src="/assets/javascript/pages/devcolors.js" defer></script>
|
||||
}
|
||||
|
||||
templ (p *Colors) Component() {
|
||||
@layouts.Page(layouts.PageInfo{
|
||||
Heading: p.Heading(),
|
||||
}) {
|
||||
<div class="m-10 flex flex-col gap-5 font-sans">
|
||||
<article>
|
||||
<input
|
||||
id="accent-color-hue"
|
||||
class="w-full"
|
||||
type="range"
|
||||
value="260"
|
||||
min="0"
|
||||
max="360"
|
||||
/>
|
||||
<details>
|
||||
<summary class="font-bold">Pallete</summary>
|
||||
<ul class="list-none p-0 flex flex-col gap-3">
|
||||
@templ.Raw(p.html())
|
||||
</ul>
|
||||
</details>
|
||||
</article>
|
||||
<article class="grid grid-cols-3">
|
||||
<section class="bg-neutral-20 p-5">
|
||||
<button>Hello world</button>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Colors) html() string {
|
||||
cs := []string{}
|
||||
for _, c := range colors {
|
||||
ss := []string{"<li class=\"w-full\">" +
|
||||
"<p class=\"mb-0\">" + c + "</p>" +
|
||||
"<ul class=\"flex list-none p-0 w-full\">"}
|
||||
for _, s := range scales {
|
||||
ss = append(ss, fmt.Sprintf("<li "+
|
||||
"style=\"background-color:var(--theme-%s-%s); width: 10%%; height: 3rem;\""+
|
||||
"></li>", c, s))
|
||||
}
|
||||
ss = append(ss, "</ul></li>")
|
||||
|
||||
cs = append(cs, strings.Join(ss, ""))
|
||||
}
|
||||
|
||||
return strings.Join(cs, "")
|
||||
}
|
||||
|
||||
var colors = []string{
|
||||
"accent",
|
||||
"neutral",
|
||||
"danger",
|
||||
"success",
|
||||
"warn",
|
||||
}
|
||||
|
||||
var scales = []string{
|
||||
"10",
|
||||
"20",
|
||||
"30",
|
||||
"40",
|
||||
"50",
|
||||
"60",
|
||||
"70",
|
||||
"80",
|
||||
"90",
|
||||
"100",
|
||||
"110",
|
||||
"120",
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package dev
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router"
|
||||
)
|
||||
|
||||
func Routes() router.Router {
|
||||
r := router.NewRouter()
|
||||
r.Handle("/colors", &Colors{})
|
||||
r.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
_, _ = w.Write([]byte("hello world"))
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router/rerrors"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/templates/layouts"
|
||||
)
|
||||
|
||||
type ErrorPage struct{}
|
||||
|
||||
templ (p ErrorPage) Component(err rerrors.RouteError) {
|
||||
@layouts.Page() {
|
||||
<main>
|
||||
<h1>Error</h1>
|
||||
<p>{ fmt.Sprintf("%#v", err) }</p>
|
||||
for k, v := range err.Info {
|
||||
<p>{ k } { fmt.Sprint(v) } </p>
|
||||
}
|
||||
if err.Endpoint != "" {
|
||||
<a href={ templ.SafeURL(err.Endpoint) }>Retry</a>
|
||||
}
|
||||
</main>
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router/rerrors"
|
||||
)
|
||||
|
||||
func Routes(logger *slog.Logger) router.Router {
|
||||
r := router.NewRouter()
|
||||
|
||||
r.Use(rerrors.NewErrorMiddleware(ErrorPage{}.Component, logger))
|
||||
|
||||
r.Handle("/dashboard", &Dashboard{})
|
||||
|
||||
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
rerrors.NotFound().ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
package cookies
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router/rerrors"
|
||||
)
|
||||
|
||||
type Marshaler interface {
|
||||
MarshalCookie() (*http.Cookie, error)
|
||||
}
|
||||
|
||||
type Unmarshaler interface {
|
||||
UnmarshalCookie(*http.Cookie) error
|
||||
}
|
||||
|
||||
func Marshal(v any) (*http.Cookie, error) {
|
||||
if m, ok := v.(Marshaler); ok {
|
||||
return m.MarshalCookie()
|
||||
}
|
||||
|
||||
c, err := marshalValue(v)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
if err := setCookieProps(c, v); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func MarshalToWriter(v any, w http.ResponseWriter) error {
|
||||
if ck, err := Marshal(v); err != nil {
|
||||
return err
|
||||
} else {
|
||||
http.SetCookie(w, ck)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Unmarshal(c *http.Cookie, v any) error {
|
||||
if m, ok := v.(Unmarshaler); ok {
|
||||
return m.UnmarshalCookie(c)
|
||||
}
|
||||
|
||||
value := c.Value
|
||||
b, err := base64.URLEncoding.DecodeString(value)
|
||||
if err != nil {
|
||||
return errors.Join(ErrDecodeBase64, err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, v); err != nil {
|
||||
return errors.Join(ErrUnmarshal, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnmarshalRequest(r *http.Request, v any) error {
|
||||
name, err := getCookieName(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := r.Cookie(name)
|
||||
if errors.Is(err, http.ErrNoCookie) {
|
||||
return ErrNoCookie{name}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Unmarshal(c, v)
|
||||
}
|
||||
|
||||
func UnmarshalIfRequest(r *http.Request, v any) (bool, error) {
|
||||
if err := UnmarshalRequest(r, v); err != nil {
|
||||
if _, ok := err.(ErrNoCookie); ok {
|
||||
return false, nil
|
||||
} else {
|
||||
return true, err
|
||||
}
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func RerrUnmarshalCookie(err error) rerrors.RouteError {
|
||||
if e, ok := err.(ErrNoCookie); ok {
|
||||
return rerrors.MissingCookies([]string{e.name})
|
||||
} else {
|
||||
return rerrors.InternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func marshalValue(v any) (*http.Cookie, error) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return &http.Cookie{}, errors.Join(ErrMarshal, err)
|
||||
}
|
||||
|
||||
s := base64.URLEncoding.EncodeToString(b)
|
||||
|
||||
return &http.Cookie{
|
||||
Value: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var COOKIE_EXPIRE_VALID_FORMATS = []string{
|
||||
time.DateOnly, time.DateTime,
|
||||
time.RFC1123, time.RFC1123Z,
|
||||
}
|
||||
|
||||
func setCookieProps(c *http.Cookie, v any) error {
|
||||
tag, err := getCookieTag(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Name, err = getCookieName(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tvs := strings.Split(tag, ",")
|
||||
|
||||
if len(tvs) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tvs = tvs[1:]
|
||||
|
||||
for _, tv := range tvs {
|
||||
var k, v string
|
||||
if strings.Contains(tv, "=") {
|
||||
s := strings.Split(tv, "=")
|
||||
k = s[0]
|
||||
v = s[1]
|
||||
} else {
|
||||
k = tv
|
||||
v = ""
|
||||
}
|
||||
|
||||
switch k {
|
||||
case "SECURE":
|
||||
c.Name = "__Secure-" + c.Name
|
||||
c.Secure = true
|
||||
|
||||
case "HOST":
|
||||
c.Name = "__Host" + c.Name
|
||||
c.Secure = true
|
||||
c.Path = "/"
|
||||
|
||||
case "path":
|
||||
c.Path = v
|
||||
|
||||
case "domain":
|
||||
c.Domain = v
|
||||
|
||||
case "httponly":
|
||||
if v == "" {
|
||||
c.HttpOnly = true
|
||||
} else if v, err := strconv.ParseBool(v); err != nil {
|
||||
c.HttpOnly = false
|
||||
} else {
|
||||
c.HttpOnly = v
|
||||
}
|
||||
|
||||
case "samesite":
|
||||
if v == "" {
|
||||
c.SameSite = http.SameSiteDefaultMode
|
||||
} else if v == "strict" {
|
||||
c.SameSite = http.SameSiteStrictMode
|
||||
} else if v == "lax" {
|
||||
c.SameSite = http.SameSiteLaxMode
|
||||
} else {
|
||||
c.SameSite = http.SameSiteNoneMode
|
||||
}
|
||||
case "secure":
|
||||
if v == "" {
|
||||
c.Secure = true
|
||||
} else if v, err := strconv.ParseBool(v); err != nil {
|
||||
c.Secure = false
|
||||
} else {
|
||||
c.Secure = v
|
||||
}
|
||||
|
||||
case "max-age", "age":
|
||||
if v == "" {
|
||||
c.MaxAge = 0
|
||||
} else if v, err := strconv.Atoi(v); err != nil {
|
||||
c.MaxAge = 0
|
||||
} else {
|
||||
c.MaxAge = v
|
||||
}
|
||||
|
||||
case "expires":
|
||||
if v == "" {
|
||||
c.Expires = time.Now()
|
||||
} else if v, err := timeParseMultiple(v, COOKIE_EXPIRE_VALID_FORMATS...); err != nil {
|
||||
c.Expires = time.Now()
|
||||
} else {
|
||||
c.Expires = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCookieName(v any) (name string, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Join(ErrReflectPanic, fmt.Errorf("Panic recovered: %#v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
tag, err := getCookieTag(v)
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
|
||||
tvs := strings.Split(tag, ",")
|
||||
if len(tvs) == 0 {
|
||||
t := reflect.TypeOf(v)
|
||||
name = t.Name()
|
||||
} else {
|
||||
name = tvs[0]
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return name, ErrMissingName
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func getCookieTag(v any) (t string, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Join(ErrReflectPanic, fmt.Errorf("Panic recovered: %#v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
rt := reflect.TypeOf(v)
|
||||
|
||||
if rt.Kind() == reflect.Pointer {
|
||||
rt = rt.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
ft := rt.Field(i)
|
||||
if t := ft.Tag.Get("cookie"); t != "" {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func timeParseMultiple(v string, formats ...string) (time.Time, error) {
|
||||
errs := []error{}
|
||||
for _, f := range formats {
|
||||
t, err := time.Parse(v, f)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
return time.Time{}, errs[len(errs)-1]
|
||||
}
|
||||
|
||||
var (
|
||||
ErrDecodeBase64 = errors.New("Failed to decode base64 string from cookie value")
|
||||
ErrMarshal = errors.New("Failed to marhal JSON value for cookie value")
|
||||
ErrUnmarshal = errors.New("Failed to unmarshal JSON value from cookie value")
|
||||
ErrReflectPanic = errors.New("Reflect panic while trying to get tag from value")
|
||||
ErrMissingName = errors.New("Failed to get name of cookie")
|
||||
)
|
||||
|
||||
type ErrNoCookie struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (e ErrNoCookie) Error() string {
|
||||
return fmt.Sprintf("Cookie \"%s\" missing from request", e.name)
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
package forms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/router/rerrors"
|
||||
)
|
||||
|
||||
type Unmarshaler interface {
|
||||
UnmarshalForm(r *http.Request) error
|
||||
}
|
||||
|
||||
func Unmarshal(r *http.Request, v any) (err error) {
|
||||
if u, ok := v.(Unmarshaler); ok {
|
||||
return u.UnmarshalForm(r)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Join(ErrReflectPanic, fmt.Errorf("Panic recovered: %#v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
rt := rv.Type()
|
||||
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
ft := rt.Field(i)
|
||||
fv := rv.FieldByName(ft.Name)
|
||||
|
||||
log.Print(ft.Name)
|
||||
|
||||
if !fv.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Support embedded fields
|
||||
if ft.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
var tv string
|
||||
if t := ft.Tag.Get("form"); t != "" {
|
||||
tv = t
|
||||
} else if t = ft.Tag.Get("query"); t != "" {
|
||||
tv = t
|
||||
} else {
|
||||
tv = ft.Name
|
||||
}
|
||||
|
||||
tvs := strings.Split(tv, ",")
|
||||
|
||||
name := tvs[0]
|
||||
required := false
|
||||
defaultv := ""
|
||||
|
||||
for _, v := range tvs {
|
||||
if v == "required" {
|
||||
required = true
|
||||
} else if strings.HasPrefix(v, "default=") {
|
||||
defaultv = strings.TrimPrefix(v, "default=")
|
||||
}
|
||||
}
|
||||
|
||||
qv := r.FormValue(name)
|
||||
if qv == "" {
|
||||
if defaultv != "" {
|
||||
qv = defaultv
|
||||
} else if required {
|
||||
return &ErrMissingRequiredValue{name}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := setFieldValue(fv, qv); errors.Is(err, &ErrInvalidValueType{}) {
|
||||
e, _ := err.(*ErrInvalidValueType)
|
||||
e.value = name
|
||||
return e
|
||||
} else if errors.Is(err, &ErrUnsuportedValueType{}) {
|
||||
e, _ := err.(*ErrUnsuportedValueType)
|
||||
e.value = name
|
||||
return e
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RerrUnsmarshal(err error) rerrors.RouteError {
|
||||
if e, ok := err.(*ErrMissingRequiredValue); ok {
|
||||
return rerrors.MissingParameters([]string{e.value})
|
||||
} else if e, ok := err.(*ErrInvalidValueType); ok {
|
||||
return rerrors.BadRequest(e.Error())
|
||||
} else {
|
||||
return rerrors.InternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setFieldValue(rv reflect.Value, v string) error {
|
||||
switch rv.Kind() {
|
||||
|
||||
case reflect.Pointer:
|
||||
return setFieldValue(rv.Elem(), v)
|
||||
|
||||
case reflect.String:
|
||||
rv.SetString(v)
|
||||
|
||||
case reflect.Bool:
|
||||
if cv, err := strconv.ParseBool(v); err != nil {
|
||||
return &ErrInvalidValueType{"bool", err, ""}
|
||||
} else {
|
||||
rv.SetBool(cv)
|
||||
}
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if cv, err := strconv.Atoi(v); err != nil {
|
||||
return &ErrInvalidValueType{"int", err, ""}
|
||||
} else {
|
||||
rv.SetInt(int64(cv))
|
||||
}
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if cv, err := strconv.Atoi(v); err != nil {
|
||||
return &ErrInvalidValueType{"uint", err, ""}
|
||||
} else {
|
||||
rv.SetUint(uint64(cv))
|
||||
}
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if cv, err := strconv.ParseFloat(v, 64); err != nil {
|
||||
return &ErrInvalidValueType{"float64", err, ""}
|
||||
} else {
|
||||
rv.SetFloat(cv)
|
||||
}
|
||||
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
if cv, err := strconv.ParseComplex(v, 128); err != nil {
|
||||
return &ErrInvalidValueType{"complex128", err, ""}
|
||||
} else {
|
||||
rv.SetComplex(cv)
|
||||
}
|
||||
|
||||
// TODO: Support strucys
|
||||
// TODO: Support slices
|
||||
// TODO: Support maps
|
||||
default:
|
||||
return &ErrUnsuportedValueType{
|
||||
[]string{
|
||||
"string",
|
||||
"bool",
|
||||
"int", "int8", "int16", "int32", "int64",
|
||||
"uint", "uint8", "uint16", "uint32", "uint64",
|
||||
"float32", "float64",
|
||||
"complex64", "complex64",
|
||||
},
|
||||
"",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ErrInvalidValueType struct {
|
||||
expected string
|
||||
err error
|
||||
value string
|
||||
}
|
||||
|
||||
func (e ErrInvalidValueType) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Value \"%s\" is a invalid type, expected type \"%s\". Got err: %s",
|
||||
e.value,
|
||||
e.expected,
|
||||
e.err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
type ErrUnsuportedValueType struct {
|
||||
supported []string
|
||||
value string
|
||||
}
|
||||
|
||||
func (e ErrUnsuportedValueType) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Value \"%s\" is a unsupported type, supported types are: \"%s\"",
|
||||
e.value,
|
||||
strings.Join(e.supported, ", "),
|
||||
)
|
||||
}
|
||||
|
||||
type ErrMissingRequiredValue struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (e ErrMissingRequiredValue) Error() string {
|
||||
return fmt.Sprintf("Required value \"%s\" missing from query", e.value)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrParseForm = errors.New("Failed to parse form from body or query parameters")
|
||||
ErrReflectPanic = errors.New("Reflect panic while trying to parse request")
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func CacheMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "max-age=604800, stale-while-revalidate=86400, public")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func DevMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type loggerReponse struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (lr *loggerReponse) WriteHeader(s int) {
|
||||
lr.status = s
|
||||
lr.ResponseWriter.WriteHeader(s)
|
||||
}
|
||||
|
||||
func NewLoggerMiddleware(l *slog.Logger) Middleware {
|
||||
l = l.WithGroup("logger_middleware")
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
id := randHash(5)
|
||||
|
||||
l.Info("NEW REQUEST",
|
||||
slog.String("id", id),
|
||||
slog.String("status", "xxx"),
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
)
|
||||
|
||||
lw := &loggerReponse{w, http.StatusOK}
|
||||
next.ServeHTTP(lw, r)
|
||||
|
||||
if lw.status >= 400 {
|
||||
l.Warn("ERR REQUEST",
|
||||
slog.String("id", id),
|
||||
slog.Int("status", lw.status),
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
l.Info("END REQUEST",
|
||||
slog.String("id", id),
|
||||
slog.Int("status", lw.status),
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const HASH_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
// This is not the most performant function, as a TODO we could
|
||||
// improve based on this Stackoberflow thread:
|
||||
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
|
||||
func randHash(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = HASH_CHARS[rand.Int63()%int64(len(HASH_CHARS))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Middleware = func(next http.Handler) http.Handler
|
||||
|
||||
type MiddlewaredReponse struct {
|
||||
w http.ResponseWriter
|
||||
statuses []int
|
||||
bodyWrites [][]byte
|
||||
}
|
||||
|
||||
func NewMiddlewaredResponse(w http.ResponseWriter) *MiddlewaredReponse {
|
||||
return &MiddlewaredReponse{w, []int{500}, [][]byte{[]byte("")}}
|
||||
}
|
||||
|
||||
func (m *MiddlewaredReponse) WriteHeader(s int) {
|
||||
m.Header().Set("Status", strconv.Itoa(s))
|
||||
m.statuses = append(m.statuses, s)
|
||||
}
|
||||
|
||||
func (m *MiddlewaredReponse) Header() http.Header {
|
||||
return m.w.Header()
|
||||
}
|
||||
|
||||
func (m *MiddlewaredReponse) Write(b []byte) (int, error) {
|
||||
m.bodyWrites = append(m.bodyWrites, b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (m *MiddlewaredReponse) ReallyWriteHeader() (int, error) {
|
||||
status := m.statuses[len(m.statuses)-1]
|
||||
m.w.WriteHeader(status)
|
||||
bytes := 0
|
||||
for _, b := range m.bodyWrites {
|
||||
by, err := m.w.Write(b)
|
||||
if err != nil {
|
||||
return bytes, errors.Join(
|
||||
fmt.Errorf(
|
||||
"Failed to write to response in middleware."+
|
||||
"\nStatuses are %v"+
|
||||
"\nTried to write %v bytes"+
|
||||
"\nTried to write response:\n%s",
|
||||
m.statuses, bytes, string(b),
|
||||
),
|
||||
err,
|
||||
)
|
||||
}
|
||||
bytes += by
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
type multiResponseWriter struct {
|
||||
response http.ResponseWriter
|
||||
writers []io.Writer
|
||||
}
|
||||
|
||||
func MultiResponseWriter(
|
||||
w http.ResponseWriter,
|
||||
writers ...io.Writer,
|
||||
) http.ResponseWriter {
|
||||
if mw, ok := w.(*multiResponseWriter); ok {
|
||||
mw.writers = append(mw.writers, writers...)
|
||||
return mw
|
||||
}
|
||||
|
||||
allWriters := make([]io.Writer, 0, len(writers))
|
||||
for _, iow := range writers {
|
||||
if mw, ok := iow.(*multiResponseWriter); ok {
|
||||
allWriters = append(allWriters, mw.writers...)
|
||||
} else {
|
||||
allWriters = append(allWriters, iow)
|
||||
}
|
||||
}
|
||||
|
||||
return &multiResponseWriter{w, allWriters}
|
||||
}
|
||||
|
||||
func (w *multiResponseWriter) WriteHeader(status int) {
|
||||
w.Header().Set("Status", strconv.Itoa(status))
|
||||
w.response.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (w *multiResponseWriter) Write(p []byte) (int, error) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
for _, w := range w.writers {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if n != len(p) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
return w.response.Write(p)
|
||||
}
|
||||
|
||||
func (w *multiResponseWriter) Header() http.Header {
|
||||
return w.response.Header()
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/middleware"
|
||||
)
|
||||
|
||||
var DefaultRouter = NewRouter()
|
||||
|
||||
func Handle(pattern string, handler http.Handler) {
|
||||
DefaultRouter.Handle(pattern, handler)
|
||||
}
|
||||
|
||||
func HandleFunc(pattern string, handler http.HandlerFunc) {
|
||||
DefaultRouter.HandleFunc(pattern, handler)
|
||||
}
|
||||
|
||||
func Use(m middleware.Middleware) {
|
||||
DefaultRouter.Use(m)
|
||||
}
|
||||
|
||||
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
DefaultRouter.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package rerrors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func BadRequest(reason ...string) RouteError {
|
||||
info := map[string]any{}
|
||||
|
||||
if len(reason) == 1 {
|
||||
info["reason"] = reason[0]
|
||||
} else if len(reason) > 1 {
|
||||
for i, r := range reason {
|
||||
info["reason_"+strconv.Itoa(i)] = r
|
||||
}
|
||||
}
|
||||
|
||||
return NewRouteError(http.StatusBadRequest, "Bad Request", info)
|
||||
}
|
||||
|
||||
func NotFound() RouteError {
|
||||
return NewRouteError(http.StatusNotFound, "Not Found", map[string]any{})
|
||||
}
|
||||
|
||||
func MissingCookies(cookies []string) RouteError {
|
||||
return NewRouteError(http.StatusBadRequest, "Missing cookies", map[string]any{
|
||||
"missing_cookies": cookies,
|
||||
})
|
||||
}
|
||||
|
||||
func MethodNowAllowed(method string, allowedMethods []string) RouteError {
|
||||
return NewRouteError(http.StatusMethodNotAllowed, "Method not allowed", map[string]any{
|
||||
"method": method,
|
||||
"allowed_methods": allowedMethods,
|
||||
})
|
||||
}
|
||||
|
||||
func MissingParameters(params []string) RouteError {
|
||||
return NewRouteError(http.StatusBadRequest, "Missing parameters", map[string]any{
|
||||
"missing_parameters": params,
|
||||
})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package rerrors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func InternalError(errs ...error) RouteError {
|
||||
err := errors.Join(errs...)
|
||||
return NewRouteError(http.StatusInternalServerError, "Internal server error", map[string]any{
|
||||
"errors": err,
|
||||
"errors_desc": err.Error(),
|
||||
})
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package rerrors
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/middleware"
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
const (
|
||||
ERROR_MIDDLEWARE_HEADER = "XX-Error-Middleware"
|
||||
ERROR_VALUE_HEADER = "X-Error-Value"
|
||||
)
|
||||
|
||||
type RouteError struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Error string `json:"error"`
|
||||
Info map[string]any `json:"info"`
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
func NewRouteError(status int, error string, info ...map[string]any) RouteError {
|
||||
rerr := RouteError{StatusCode: status, Error: error}
|
||||
if len(info) > 0 {
|
||||
rerr.Info = info[0]
|
||||
} else {
|
||||
rerr.Info = map[string]any{}
|
||||
}
|
||||
return rerr
|
||||
}
|
||||
|
||||
func (rerr RouteError) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if rerr.StatusCode == 0 {
|
||||
rerr.StatusCode = http.StatusNotImplemented
|
||||
}
|
||||
|
||||
if rerr.Error == "" {
|
||||
rerr.Error = "MISSING ERROR DESCRIPTION"
|
||||
}
|
||||
|
||||
if rerr.Info == nil {
|
||||
rerr.Info = map[string]any{}
|
||||
}
|
||||
|
||||
j, err := json.Marshal(rerr)
|
||||
if err != nil {
|
||||
j, _ = json.Marshal(RouteError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Error: "Failed to marshal error message to JSON",
|
||||
Info: map[string]any{
|
||||
"source_value": fmt.Sprintf("%#v", rerr),
|
||||
"error": err.Error(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if r.Header.Get(ERROR_MIDDLEWARE_HEADER) == "enable" && prefersHtml(r.Header) {
|
||||
q := r.URL.Query()
|
||||
q.Set("error", base64.URLEncoding.EncodeToString(j))
|
||||
r.URL.RawQuery = q.Encode()
|
||||
|
||||
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.WriteHeader(rerr.StatusCode)
|
||||
if _, err = w.Write(j); err != nil {
|
||||
_, _ = w.Write([]byte("Failed to write error JSON string to body"))
|
||||
}
|
||||
}
|
||||
|
||||
type ErrorMiddlewarePage func(err RouteError) templ.Component
|
||||
|
||||
type ErrorDisplayer struct {
|
||||
log *slog.Logger
|
||||
page ErrorMiddlewarePage
|
||||
}
|
||||
|
||||
func (h ErrorDisplayer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
e, err := base64.URLEncoding.DecodeString(r.URL.Query().Get("error"))
|
||||
if err != nil {
|
||||
h.log.Error("Failed to decode \"error\" parameter from error redirect",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.Int("status", 0),
|
||||
slog.String("data", string(e)),
|
||||
)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(
|
||||
fmt.Sprintf("Data %s\nError %s", string(e), err.Error()),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
var rerr RouteError
|
||||
if err := json.Unmarshal(e, &rerr); err != nil {
|
||||
h.log.Error("Failed to decode \"error\" parameter from error redirect",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.Int("status", 0),
|
||||
slog.String("data", string(e)),
|
||||
)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(
|
||||
fmt.Sprintf("Data %s\nError %s", string(e), err.Error()),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
if rerr.Endpoint == "" {
|
||||
q := r.URL.Query()
|
||||
q.Del("error")
|
||||
r.URL.RawQuery = q.Encode()
|
||||
|
||||
rerr.Endpoint = r.URL.String()
|
||||
}
|
||||
|
||||
w.WriteHeader(rerr.StatusCode)
|
||||
if err := h.page(rerr).Render(r.Context(), w); err != nil {
|
||||
_, _ = w.Write(e)
|
||||
}
|
||||
}
|
||||
|
||||
func NewErrorMiddleware(
|
||||
p ErrorMiddlewarePage,
|
||||
l *slog.Logger,
|
||||
notfound ...ErrorMiddlewarePage,
|
||||
) middleware.Middleware {
|
||||
var nf ErrorMiddlewarePage
|
||||
if len(notfound) > 0 {
|
||||
nf = notfound[0]
|
||||
} else {
|
||||
nf = p
|
||||
}
|
||||
|
||||
l = l.WithGroup("error_middleware")
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Header.Set(ERROR_MIDDLEWARE_HEADER, "enable")
|
||||
|
||||
if uerr := r.URL.Query().Get("error"); uerr != "" && prefersHtml(r.Header) {
|
||||
ErrorDisplayer{l, nf}.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func prefersHtml(h http.Header) bool {
|
||||
if h.Get("Accept") == "" {
|
||||
return false
|
||||
}
|
||||
return (strings.Contains(h.Get("Accept"), "text/html") ||
|
||||
strings.Contains(h.Get("Accept"), "application/xhtml+xml") ||
|
||||
strings.Contains(h.Get("Accept"), "application/xml")) &&
|
||||
!strings.Contains(h.Get("Accept"), "application/json")
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/lib/middleware"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
Handle(pattern string, handler http.Handler)
|
||||
HandleFunc(pattern string, handler http.HandlerFunc)
|
||||
|
||||
Use(middleware middleware.Middleware)
|
||||
|
||||
http.Handler
|
||||
}
|
||||
|
||||
type RouterWithRoutes interface {
|
||||
Router
|
||||
Routes() []Route
|
||||
}
|
||||
|
||||
type RouterWithMiddlewares interface {
|
||||
RouterWithRoutes
|
||||
Middlewares() []middleware.Middleware
|
||||
}
|
||||
|
||||
type RouterWithMiddlewaresWrapper interface {
|
||||
RouterWithMiddlewares
|
||||
WrapMiddlewares(ms []middleware.Middleware, h http.Handler) http.Handler
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Path string
|
||||
Method string
|
||||
Host string
|
||||
Handler http.Handler
|
||||
}
|
||||
|
||||
func NewRouter(mux ...*http.ServeMux) Router {
|
||||
var m *http.ServeMux
|
||||
if len(mux) > 0 {
|
||||
m = mux[0]
|
||||
} else {
|
||||
m = http.NewServeMux()
|
||||
}
|
||||
|
||||
return &defaultRouter{
|
||||
m,
|
||||
[]middleware.Middleware{},
|
||||
map[string]Route{},
|
||||
}
|
||||
}
|
||||
|
||||
type defaultRouter struct {
|
||||
mux *http.ServeMux
|
||||
middlewares []middleware.Middleware
|
||||
routes map[string]Route
|
||||
}
|
||||
|
||||
func (r *defaultRouter) Handle(pattern string, h http.Handler) {
|
||||
if sr, ok := h.(Router); ok {
|
||||
r.handleRouter(pattern, sr)
|
||||
} else {
|
||||
r.handle(pattern, h)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *defaultRouter) HandleFunc(pattern string, hf http.HandlerFunc) {
|
||||
r.handle(pattern, hf)
|
||||
}
|
||||
|
||||
func (r *defaultRouter) Use(m middleware.Middleware) {
|
||||
r.middlewares = append(r.middlewares, m)
|
||||
}
|
||||
|
||||
func (r *defaultRouter) Routes() []Route {
|
||||
rs := make([]Route, len(r.routes))
|
||||
i := 0
|
||||
for _, r := range r.routes {
|
||||
rs[i] = r
|
||||
i++
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func (r *defaultRouter) Middlewares() []middleware.Middleware {
|
||||
return r.middlewares
|
||||
}
|
||||
|
||||
func (r defaultRouter) WrapMiddlewares(ms []middleware.Middleware, h http.Handler) http.Handler {
|
||||
hf := h
|
||||
for _, m := range ms {
|
||||
hf = m(hf)
|
||||
}
|
||||
return hf
|
||||
}
|
||||
|
||||
func (r *defaultRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
r.mux.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
func (r defaultRouter) handle(pattern string, hf http.Handler) {
|
||||
m, h, p := r.parsePattern(pattern)
|
||||
rt := Route{
|
||||
Method: m,
|
||||
Host: h,
|
||||
Path: p,
|
||||
Handler: hf,
|
||||
}
|
||||
r.handleRoute(rt)
|
||||
}
|
||||
|
||||
func (r defaultRouter) handleRouter(pattern string, rr Router) {
|
||||
m, h, p := r.parsePattern(pattern)
|
||||
|
||||
rs, ok := rr.(RouterWithRoutes)
|
||||
if !ok {
|
||||
r.handle(p, rr)
|
||||
}
|
||||
|
||||
routes := rs.Routes()
|
||||
middlewares := []middleware.Middleware{}
|
||||
if rw, ok := rs.(RouterWithMiddlewares); ok {
|
||||
middlewares = rw.Middlewares()
|
||||
}
|
||||
|
||||
wrap := r.WrapMiddlewares
|
||||
if rw, ok := rs.(RouterWithMiddlewaresWrapper); ok {
|
||||
wrap = rw.WrapMiddlewares
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
route.Handler = wrap(middlewares, route.Handler)
|
||||
route.Path = path.Join(p, route.Path)
|
||||
|
||||
if m != "" && route.Method != "" && m != route.Method {
|
||||
panic(
|
||||
fmt.Sprintf(
|
||||
"Nested router's route has incompatible method than defined in path %q."+
|
||||
"Router's route method is %q, while path's is %q",
|
||||
p, route.Method, m,
|
||||
),
|
||||
)
|
||||
}
|
||||
if h != "" && route.Host != "" && h != route.Host {
|
||||
panic(
|
||||
fmt.Sprintf(
|
||||
"Nested router's route has incompatible host than defined in path %q."+
|
||||
"Router's route host is %q, while path's is %q",
|
||||
p, route.Host, h,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
r.handleRoute(route)
|
||||
}
|
||||
}
|
||||
|
||||
func (r defaultRouter) handleRoute(rt Route) {
|
||||
if len(r.middlewares) > 0 {
|
||||
rt.Handler = r.WrapMiddlewares(r.middlewares, rt.Handler)
|
||||
}
|
||||
|
||||
if rt.Path == "" || !strings.HasPrefix(rt.Path, "/") {
|
||||
panic(
|
||||
fmt.Sprintf(
|
||||
"INVALID STATE: Path of route (%#v) does not start with a leading slash",
|
||||
rt,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
p := rt.Path
|
||||
if rt.Host != "" {
|
||||
p = fmt.Sprintf("%s%s", rt.Host, p)
|
||||
}
|
||||
if rt.Method != "" {
|
||||
p = fmt.Sprintf("%s %s", rt.Method, p)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(p, "/") {
|
||||
p = fmt.Sprintf("%s/", p)
|
||||
}
|
||||
|
||||
r.routes[p] = rt
|
||||
r.mux.Handle(p, rt.Handler)
|
||||
}
|
||||
|
||||
func (r *defaultRouter) parsePattern(pattern string) (method, host, p string) {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
|
||||
// ServerMux patterns are "[METHOD ][HOST]/[PATH]", so to parsing it, we must
|
||||
// first split it between "[METHOD ][HOST]" and "[PATH]"
|
||||
ps := strings.Split(pattern, "/")
|
||||
|
||||
p = path.Join("/", strings.Join(ps[1:], "/"))
|
||||
|
||||
// path.Join adds a trailing slash, if the original pattern doesn't has one, the parsed
|
||||
// path shouldn't also
|
||||
if !strings.HasSuffix(pattern, "/") {
|
||||
p = strings.TrimSuffix(p, "/")
|
||||
}
|
||||
|
||||
// Since path.Join adds a trailing slash, it can break the {pattern...} syntax.
|
||||
// So we check if it has the suffix "...}/" to see if it ends in "/{pattern...}/"
|
||||
if strings.HasSuffix(p, "...}/") {
|
||||
// If it does, we remove the any possible trailing slash
|
||||
p = strings.TrimSuffix(p, "/")
|
||||
}
|
||||
|
||||
// If "[METHOD ][HOST]" is empty, we just have the path and can send it back
|
||||
if ps[0] == "" {
|
||||
return "", "", p
|
||||
}
|
||||
|
||||
// Split string again, if method is not defined, this will end up being just []string{"[HOST]"}
|
||||
// since there isn't a space before the host. If there is a method defined, this will end up as
|
||||
// []string{"[METHOD]","[HOST]"}, with "[HOST]" being possibly a empty string.
|
||||
mh := strings.Split(ps[0], " ")
|
||||
|
||||
// If slice is of length 1, this means it is []string{"[HOST]"}
|
||||
if len(mh) == 1 {
|
||||
return "", host, p
|
||||
}
|
||||
|
||||
return mh[0], mh[1], p
|
||||
}
|
||||
39
main.go
39
main.go
@@ -1,49 +1,18 @@
|
||||
package main
|
||||
package capytalcodecomicverse
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/app"
|
||||
)
|
||||
|
||||
var (
|
||||
port *int
|
||||
dev *bool
|
||||
debug = flag.Bool("dev", false, "Run the server in debug mode.")
|
||||
port = flag.Int("port", 8080, "Port to be used for the server.")
|
||||
)
|
||||
|
||||
func init() {
|
||||
portEnv := os.Getenv("COMICVERSE_PORT")
|
||||
if portEnv == "" {
|
||||
portEnv = "8080"
|
||||
}
|
||||
p, err := strconv.Atoi(portEnv)
|
||||
if err != nil {
|
||||
p = 8080
|
||||
}
|
||||
port = flag.Int("port", p, "The port to the server to listen on")
|
||||
|
||||
devEnv := os.Getenv("COMICVERSE_DEV")
|
||||
if devEnv == "" {
|
||||
devEnv = "false"
|
||||
}
|
||||
d, err := strconv.ParseBool(devEnv)
|
||||
if err != nil {
|
||||
d = false
|
||||
}
|
||||
dev = flag.Bool("dev", d, "Run the application in development mode")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
app := app.NewApp(app.AppOpts{
|
||||
Port: port,
|
||||
Dev: dev,
|
||||
Assets: http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets"))),
|
||||
})
|
||||
|
||||
app.Run()
|
||||
}
|
||||
|
||||
6700
package-lock.json
generated
6700
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.12.0",
|
||||
"@stylistic/eslint-plugin": "^2.9.0",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jsdoc": "^50.3.2",
|
||||
"eslint-plugin-json": "^4.0.1",
|
||||
"eslint-plugin-perfectionist": "^3.8.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"eslint-plugin-wc": "^2.2.0",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "^4.9.5",
|
||||
"typescript-eslint": "^8.8.1",
|
||||
"unocss": "^0.63.3"
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package layouts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"embed"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/configs"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/assets"
|
||||
)
|
||||
|
||||
type PageInfo struct {
|
||||
Title string
|
||||
Description string
|
||||
Author string
|
||||
Keywords string
|
||||
ThemeColor string
|
||||
Heading templ.Component
|
||||
}
|
||||
|
||||
func pageInfo(info []PageInfo) PageInfo {
|
||||
if len(info) != 0 {
|
||||
return info[0]
|
||||
}
|
||||
return PageInfo{}
|
||||
}
|
||||
|
||||
templ LinkCSSFile(href string, fs embed.FS, file string) {
|
||||
if configs.DEVELOPMENT {
|
||||
<link rel="preload" href={ href } as="style"/>
|
||||
<link rel="stylesheet" href={ href }/>
|
||||
} else if f, err := fs.ReadFile(file); err != nil {
|
||||
<link rel="preload" href={ href } as="style"/>
|
||||
<link rel="stylesheet" href={ href }/>
|
||||
} else {
|
||||
@templ.Raw(fmt.Sprintf("<style>%s</style>", f))
|
||||
}
|
||||
}
|
||||
|
||||
templ Page(i ...PageInfo) {
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
// Page information
|
||||
if pageInfo(i).Title != "" {
|
||||
<title>{ pageInfo(i).Title + " - " + configs.APP_NAME }</title>
|
||||
} else {
|
||||
<title>Comicverse</title>
|
||||
}
|
||||
if pageInfo(i).Author != "" {
|
||||
<meta name="author" content={ pageInfo(i).Author }/>
|
||||
} else {
|
||||
<meta name="author" content={ configs.APP_NAME }/>
|
||||
}
|
||||
if pageInfo(i).Description != "" {
|
||||
<meta name="description" content={ pageInfo(i).Description }/>
|
||||
}
|
||||
<meta name="publisher" content={ configs.APP_NAME }/>
|
||||
// Page configuration
|
||||
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1"/>
|
||||
<meta name="referrer" content="strict-origin-when-cross-origin"/>
|
||||
<meta name="color-scheme" content="dark light"/>
|
||||
if pageInfo(i).ThemeColor != "" {
|
||||
<meta name="theme-color" content={ pageInfo(i).ThemeColor }/>
|
||||
}
|
||||
// Global styles
|
||||
<link rel="preload" href="/assets/fonts/KarlaVF.woff2" as="font"/>
|
||||
<link rel="preload" href="/assets/fonts/KarlaItalicVF.woff2" as="font"/>
|
||||
<link rel="preload" href="/assets/fonts/PlayfairRomanVF.woff2" as="font"/>
|
||||
<link rel="preload" href="/assets/fonts/PlayfairItalicVF.woff2" as="font"/>
|
||||
@LinkCSSFile("/assets/css/theme.css", assets.ASSETS, "css/theme.css")
|
||||
@LinkCSSFile("/assets/css/uno.css", assets.ASSETS, "css/uno.css")
|
||||
// Global scripts
|
||||
<script type="module" src="/assets/lib/entry.js" defer></script>
|
||||
if configs.DEVELOPMENT {
|
||||
<script type="module">
|
||||
import htmx from '/assets/lib/htmx.js'; htmx.logAll(); window.htmx = htmx;
|
||||
</script>
|
||||
} else {
|
||||
<script type="module">
|
||||
import htmx from '/assets/lib/htmx.js'; htmx.logNone(); window.htmx = htmx;
|
||||
</script>
|
||||
}
|
||||
// Additional heading
|
||||
if pageInfo(i).Heading != nil {
|
||||
@pageInfo(i).Heading
|
||||
}
|
||||
</head>
|
||||
<body style="--accent-color:#111">
|
||||
<main class="absolute w-screen min-h-screen top-0 left-0 bg-neutral-10">
|
||||
{ children... }
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
0
templates/templates.go
Normal file
0
templates/templates.go
Normal file
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"alwaysStrict": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Node16",
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"target": "ESNext"
|
||||
},
|
||||
"include": [
|
||||
"./assets/**/*",
|
||||
"./eslint.config.js",
|
||||
"./uno.config.js",
|
||||
"**/*.json"
|
||||
],
|
||||
"exclude": [
|
||||
"./node_modules/**",
|
||||
"./dist"
|
||||
]
|
||||
}
|
||||
115
uno.config.js
115
uno.config.js
@@ -1,115 +0,0 @@
|
||||
import {
|
||||
defineConfig,
|
||||
presetIcons,
|
||||
presetTypography,
|
||||
presetUno,
|
||||
presetWebFonts,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss';
|
||||
|
||||
export default defineConfig({
|
||||
cli: {
|
||||
entry: {
|
||||
outFile: './assets/css/uno.css',
|
||||
patterns: [
|
||||
'./{handlers,templates}/**/*.templ',
|
||||
'./assets/**/*.{js,css,html}',
|
||||
'!./assets/css/uno.css',
|
||||
],
|
||||
},
|
||||
},
|
||||
presets: [
|
||||
presetIcons(),
|
||||
presetTypography(),
|
||||
presetUno({
|
||||
dark: 'media',
|
||||
}),
|
||||
presetWebFonts({
|
||||
fonts: {
|
||||
display: {
|
||||
name: 'Playfair',
|
||||
},
|
||||
sans: {
|
||||
name: 'Karla',
|
||||
},
|
||||
},
|
||||
provider: 'none',
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
accent: {
|
||||
'10': 'var(--theme-accent-10)',
|
||||
'20': 'var(--theme-accent-20)',
|
||||
'30': 'var(--theme-accent-30)',
|
||||
'40': 'var(--theme-accent-40)',
|
||||
'50': 'var(--theme-accent-50)',
|
||||
'60': 'var(--theme-accent-60)',
|
||||
'70': 'var(--theme-accent-70)',
|
||||
'80': 'var(--theme-accent-80)',
|
||||
'90': 'var(--theme-accent-90)',
|
||||
'100': 'var(--theme-accent-100)',
|
||||
'110': 'var(--theme-accent-110)',
|
||||
'120': 'var(--theme-accent-120)',
|
||||
},
|
||||
danger: {
|
||||
'10': 'var(--theme-danger-10)',
|
||||
'20': 'var(--theme-danger-20)',
|
||||
'30': 'var(--theme-danger-30)',
|
||||
'40': 'var(--theme-danger-40)',
|
||||
'50': 'var(--theme-danger-50)',
|
||||
'60': 'var(--theme-danger-60)',
|
||||
'70': 'var(--theme-danger-70)',
|
||||
'80': 'var(--theme-danger-80)',
|
||||
'90': 'var(--theme-danger-90)',
|
||||
'100': 'var(--theme-danger-100)',
|
||||
'110': 'var(--theme-danger-110)',
|
||||
'120': 'var(--theme-danger-120)',
|
||||
},
|
||||
neutral: {
|
||||
'10': 'var(--theme-neutral-10)',
|
||||
'20': 'var(--theme-neutral-20)',
|
||||
'30': 'var(--theme-neutral-30)',
|
||||
'40': 'var(--theme-neutral-40)',
|
||||
'50': 'var(--theme-neutral-50)',
|
||||
'60': 'var(--theme-neutral-60)',
|
||||
'70': 'var(--theme-neutral-70)',
|
||||
'80': 'var(--theme-neutral-80)',
|
||||
'90': 'var(--theme-neutral-90)',
|
||||
'100': 'var(--theme-neutral-100)',
|
||||
'110': 'var(--theme-neutral-110)',
|
||||
'120': 'var(--theme-neutral-120)',
|
||||
},
|
||||
success: {
|
||||
'10': 'var(--theme-success-10)',
|
||||
'20': 'var(--theme-success-20)',
|
||||
'30': 'var(--theme-success-30)',
|
||||
'40': 'var(--theme-success-40)',
|
||||
'50': 'var(--theme-success-50)',
|
||||
'60': 'var(--theme-success-60)',
|
||||
'70': 'var(--theme-success-70)',
|
||||
'80': 'var(--theme-success-80)',
|
||||
'90': 'var(--theme-success-90)',
|
||||
'100': 'var(--theme-success-100)',
|
||||
'110': 'var(--theme-success-110)',
|
||||
'120': 'var(--theme-success-120)',
|
||||
},
|
||||
warn: {
|
||||
'10': 'var(--theme-warn-10)',
|
||||
'20': 'var(--theme-warn-20)',
|
||||
'30': 'var(--theme-warn-30)',
|
||||
'40': 'var(--theme-warn-40)',
|
||||
'50': 'var(--theme-warn-50)',
|
||||
'60': 'var(--theme-warn-60)',
|
||||
'70': 'var(--theme-warn-70)',
|
||||
'80': 'var(--theme-warn-80)',
|
||||
'90': 'var(--theme-warn-90)',
|
||||
'100': 'var(--theme-warn-100)',
|
||||
'110': 'var(--theme-warn-110)',
|
||||
'120': 'var(--theme-warn-120)',
|
||||
},
|
||||
},
|
||||
},
|
||||
transformers: [transformerDirectives(), transformerVariantGroup()],
|
||||
});
|
||||
2
x
2
x
Submodule x updated: f6a044a2b6...90a5169f1b
Reference in New Issue
Block a user