🚧 Created first version from the 'home' code snippets
This commit is contained in:
parent
6e2b2a61a5
commit
b5fcd48dbd
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
env:
|
||||
DOCKER_REGISTRY: source.toby3d.me
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://gitea.com/actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: https://gitea.com/docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker BuildX
|
||||
uses: https://gitea.com/docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to registry
|
||||
uses: https://gitea.com/docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKER_REGISTRY }}
|
||||
username: ${{ gitea.repository_owner }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: https://gitea.com/docker/build-push-action@v4
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: "" # See https://gitea.com/gitea/act_runner/issues/119
|
||||
with:
|
||||
context: .
|
||||
file: ./build/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.DOCKER_REGISTRY }}/${{ gitea.repository }}:${{ gitea.ref_name }}
|
|
@ -0,0 +1,28 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
# docker build --rm -f build/Dockerfile -t source.toby3d.me/toby3d/pay .
|
||||
|
||||
# Build
|
||||
FROM golang:alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOFLAGS="-mod=vendor -buildvcs=true"
|
||||
|
||||
COPY go.mod go.sum *.go ./
|
||||
COPY internal ./internal/
|
||||
COPY vendor ./vendor/
|
||||
COPY web ./web/
|
||||
|
||||
RUN go build -o ./pay
|
||||
|
||||
# Run
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /app/pay /pay
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["/pay"]
|
|
@ -0,0 +1,12 @@
|
|||
module source.toby3d.me/toby3d/pay
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require (
|
||||
github.com/caarlos0/env/v10 v10.0.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/valyala/quicktemplate v1.7.0
|
||||
golang.org/x/text v0.14.0
|
||||
)
|
||||
|
||||
require github.com/valyala/bytebufferpool v1.0.0 // indirect
|
|
@ -0,0 +1,27 @@
|
|||
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
|
||||
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
|
||||
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
@ -0,0 +1,78 @@
|
|||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"source.toby3d.me/toby3d/pay/internal/common"
|
||||
"source.toby3d.me/toby3d/pay/internal/domain"
|
||||
paymenthttpdelivery "source.toby3d.me/toby3d/pay/internal/payment/delivery/http"
|
||||
"source.toby3d.me/toby3d/pay/internal/urlutil"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func New(logger *log.Logger, static fs.FS, config domain.Config) *Service {
|
||||
handler := paymenthttpdelivery.NewHandler()
|
||||
fileServer := http.FileServer(http.FS(static))
|
||||
|
||||
return &Service{
|
||||
server: &http.Server{
|
||||
Addr: config.Bind,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch head, _ := urlutil.ShiftPath(r.URL.Path); head {
|
||||
default:
|
||||
parsed, err := strconv.ParseFloat(head, 64)
|
||||
if err != nil {
|
||||
fileServer.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE(toby3d): Take my money? No way!
|
||||
if parsed < 0 {
|
||||
http.Redirect(w, r, fmt.Sprint("/", parsed*-1),
|
||||
http.StatusMovedPermanently)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case "":
|
||||
handler.ServeHTTP(w, r)
|
||||
case "manifest.webmanifest":
|
||||
w.Header().Set(common.HeaderContentType,
|
||||
common.MIMEApplicationManifestJSONCharsetUTF8)
|
||||
fileServer.ServeHTTP(w, r)
|
||||
}
|
||||
}),
|
||||
DisableGeneralOptionsHandler: false,
|
||||
TLSConfig: nil,
|
||||
ReadTimeout: 500 * time.Millisecond,
|
||||
ReadHeaderTimeout: 500 * time.Millisecond,
|
||||
WriteTimeout: 500 * time.Millisecond,
|
||||
IdleTimeout: 0,
|
||||
MaxHeaderBytes: 0,
|
||||
TLSNextProto: nil,
|
||||
ConnState: nil,
|
||||
ErrorLog: logger,
|
||||
BaseContext: nil,
|
||||
ConnContext: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Run() error {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *Service) Stop(ctx context.Context) error {
|
||||
return s.server.Shutdown(ctx)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package common
|
||||
|
||||
const (
|
||||
HeaderAcceptLanguage string = "Accept-Language"
|
||||
HeaderContentLanguage string = "Content-Language"
|
||||
HeaderContentType string = "Content-Type"
|
||||
)
|
||||
|
||||
const (
|
||||
MIMETextHTML string = "text/html"
|
||||
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
|
||||
MIMEApplicationManifestJSON string = "application/manifest+json"
|
||||
MIMEApplicationManifestJSONCharsetUTF8 string = MIMEApplicationManifestJSON + "; " + charsetUTF8
|
||||
)
|
||||
|
||||
const charsetUTF8 string = "charset=UTF-8"
|
|
@ -0,0 +1,10 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaseURL *url.URL `env:"BASE_URL" envDefault:"http://localhost:3000/"`
|
||||
Bind string `env:"BIND" envDefault:":3000"`
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
|
||||
"source.toby3d.me/toby3d/pay/internal/common"
|
||||
"source.toby3d.me/toby3d/pay/internal/urlutil"
|
||||
"source.toby3d.me/toby3d/pay/web/template"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
matcher language.Matcher
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{
|
||||
matcher: language.NewMatcher(append([]language.Tag{language.English},
|
||||
message.DefaultCatalog.Languages()...)),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
tags, _, _ := language.ParseAcceptLanguage(r.Header.Get(common.HeaderAcceptLanguage))
|
||||
tag, _, _ := h.matcher.Match(tags...)
|
||||
|
||||
var amount uint64
|
||||
if head, _ := urlutil.ShiftPath(r.URL.Path); head != "" {
|
||||
parsed, err := strconv.ParseFloat(head, 64)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE(toby3d): Take my money? No way!
|
||||
if parsed < 0 {
|
||||
http.Redirect(w, r, fmt.Sprint("/", parsed*-1), http.StatusMovedPermanently)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
amount = uint64(parsed * 100)
|
||||
}
|
||||
|
||||
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
|
||||
template.WriteTemplate(w, template.NewContext(tag, amount))
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package urlutil
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ShiftPath splits off the first component of p, which will be cleaned of
|
||||
// relative components before processing. head will never contain a slash and
|
||||
// tail will always be a rooted path without trailing slash.
|
||||
//
|
||||
// See: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
|
||||
func ShiftPath(p string) (head, tail string) {
|
||||
p = path.Clean("/" + p)
|
||||
|
||||
i := strings.Index(p[1:], "/") + 1
|
||||
if i <= 0 {
|
||||
return p[1:], "/"
|
||||
}
|
||||
|
||||
return p[1:i], p[i:]
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package urlutil_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"source.toby3d.me/toby3d/pay/internal/urlutil"
|
||||
)
|
||||
|
||||
func TestShiftPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
input, expectHead, expectTail string
|
||||
}{
|
||||
"root": {"/", "", "/"},
|
||||
"file": {"/foo", "foo", "/"},
|
||||
"dir": {"/foo/", "foo", "/"},
|
||||
"dir-file": {"/foo/bar", "foo", "/bar"},
|
||||
"subdir": {"/foo/bar/", "foo", "/bar"},
|
||||
} {
|
||||
name, tc := name, tc
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
head, tail := urlutil.ShiftPath(tc.input)
|
||||
if head != tc.expectHead {
|
||||
t.Errorf("ShiftPath(%s) = '%s', want '%s'", tc.input, head, tc.expectHead)
|
||||
}
|
||||
|
||||
if tail != tc.expectTail {
|
||||
t.Errorf("ShiftPath(%s) = '%s', want '%s'", tc.input, tail, tc.expectTail)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"language": "en",
|
||||
"messages": [
|
||||
{
|
||||
"id": "Donate ${Amount__100} to {Toby3d}",
|
||||
"message": "Donate ${Amount__100} to {Toby3d}",
|
||||
"translation": {
|
||||
"select": {
|
||||
"feature": "plural",
|
||||
"arg": "Amount__100",
|
||||
"cases": {
|
||||
"=0": {
|
||||
"msg": "Donate to {Toby3d}"
|
||||
},
|
||||
"=1": {
|
||||
"msg": "Donate onedollar™ to {Toby3d}"
|
||||
},
|
||||
"other": {
|
||||
"msg": "Donate ${Amount__100} to {Toby3d}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"translatorComment": "Copied from source.",
|
||||
"placeholders": [
|
||||
{
|
||||
"id": "Amount__100",
|
||||
"string": "%[1].2f",
|
||||
"type": "float64",
|
||||
"underlyingType": "float64",
|
||||
"argNum": 1,
|
||||
"expr": "ctx.amount / 100"
|
||||
},
|
||||
{
|
||||
"id": "Toby3d",
|
||||
"string": "%[2]s",
|
||||
"type": "string",
|
||||
"underlyingType": "string",
|
||||
"argNum": 2,
|
||||
"expr": "\"toby3d\""
|
||||
}
|
||||
],
|
||||
"fuzzy": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"language": "en",
|
||||
"messages": [
|
||||
{
|
||||
"id": "Donate ${Amount__100} to {Toby3d}",
|
||||
"message": "Donate ${Amount__100} to {Toby3d}",
|
||||
"translation": {
|
||||
"select": {
|
||||
"feature": "plural",
|
||||
"arg": "Amount__100",
|
||||
"cases": {
|
||||
"=0": {
|
||||
"msg": "Donate to {Toby3d}"
|
||||
},
|
||||
"=1": {
|
||||
"msg": "Donate onedollar™ to {Toby3d}"
|
||||
},
|
||||
"other": {
|
||||
"msg": "Donate ${Amount__100} to {Toby3d}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"translatorComment": "Copied from source.",
|
||||
"placeholders": [
|
||||
{
|
||||
"id": "Amount__100",
|
||||
"string": "%.2[1]f",
|
||||
"type": "float64",
|
||||
"underlyingType": "float64",
|
||||
"argNum": 1,
|
||||
"expr": "ctx.amount / 100"
|
||||
},
|
||||
{
|
||||
"id": "Toby3d",
|
||||
"string": "%[2]s",
|
||||
"type": "string",
|
||||
"underlyingType": "string",
|
||||
"argNum": 2,
|
||||
"expr": "\"toby3d\""
|
||||
}
|
||||
],
|
||||
"fuzzy": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"language": "ru",
|
||||
"messages": [
|
||||
{
|
||||
"id": "Donate ${Amount__100} to {Toby3d}",
|
||||
"message": "Donate ${Amount__100} to {Toby3d}",
|
||||
"translation": {
|
||||
"select": {
|
||||
"feature": "plural",
|
||||
"arg": "Amount__100",
|
||||
"cases": {
|
||||
"=0": {
|
||||
"msg": "Пожертвовать {Toby3d}"
|
||||
},
|
||||
"=1": {
|
||||
"msg": "Пожертвовать долор™ {Toby3d}"
|
||||
},
|
||||
"other": {
|
||||
"msg": "Пожертвовать ${Amount__100} {Toby3d}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"placeholders": [
|
||||
{
|
||||
"id": "Amount__100",
|
||||
"string": "%[1].2f",
|
||||
"type": "float64",
|
||||
"underlyingType": "float64",
|
||||
"argNum": 1,
|
||||
"expr": "ctx.amount / 100"
|
||||
},
|
||||
{
|
||||
"id": "Toby3d",
|
||||
"string": "%[2]s",
|
||||
"type": "string",
|
||||
"underlyingType": "string",
|
||||
"argNum": 2,
|
||||
"expr": "\"toby3d\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"language": "ru",
|
||||
"messages": [
|
||||
{
|
||||
"id": "Donate ${Amount__100} to {Toby3d}",
|
||||
"message": "Donate ${Amount__100} to {Toby3d}",
|
||||
"translation": {
|
||||
"select": {
|
||||
"feature": "plural",
|
||||
"arg": "Amount__100",
|
||||
"cases": {
|
||||
"=0": {
|
||||
"msg": "Пожертвовать {Toby3d}"
|
||||
},
|
||||
"=1": {
|
||||
"msg": "Пожертвовать долор™ {Toby3d}"
|
||||
},
|
||||
"other": {
|
||||
"msg": "Пожертвовать ${Amount__100} {Toby3d}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"placeholders": [
|
||||
{
|
||||
"id": "Amount__100",
|
||||
"string": "%.2[1]f",
|
||||
"type": "float64",
|
||||
"underlyingType": "float64",
|
||||
"argNum": 1,
|
||||
"expr": "ctx.amount / 100"
|
||||
},
|
||||
{
|
||||
"id": "Toby3d",
|
||||
"string": "%[2]s",
|
||||
"type": "string",
|
||||
"underlyingType": "string",
|
||||
"argNum": 2,
|
||||
"expr": "\"toby3d\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"golang.org/x/text/message/catalog"
|
||||
)
|
||||
|
||||
type dictionary struct {
|
||||
index []uint32
|
||||
data string
|
||||
}
|
||||
|
||||
func (d *dictionary) Lookup(key string) (data string, ok bool) {
|
||||
p, ok := messageKeyToIndex[key]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
start, end := d.index[p], d.index[p+1]
|
||||
if start == end {
|
||||
return "", false
|
||||
}
|
||||
return d.data[start:end], true
|
||||
}
|
||||
|
||||
func init() {
|
||||
dict := map[string]catalog.Dictionary{
|
||||
"en": &dictionary{index: enIndex, data: enData},
|
||||
"ru": &dictionary{index: ruIndex, data: ruData},
|
||||
}
|
||||
fallback := language.MustParse("en")
|
||||
cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
message.DefaultCatalog = cat
|
||||
}
|
||||
|
||||
var messageKeyToIndex = map[string]int{
|
||||
"Donate $%.2f to %s": 0,
|
||||
}
|
||||
|
||||
var enIndex = []uint32{ // 2 elements
|
||||
0x00000000, 0x00000053,
|
||||
} // Size: 32 bytes
|
||||
|
||||
const enData string = "" + // Size: 83 bytes
|
||||
"\x14\x01\x81\x01\x02=\x00\x10\x02Donate to %[2]s=\x01\x1d\x02Donate oned" +
|
||||
"ollar™ to %[2]s\x00\x19\x02Donate $%.2[1]f to %[2]s"
|
||||
|
||||
var ruIndex = []uint32{ // 2 elements
|
||||
0x00000000, 0x00000081,
|
||||
} // Size: 32 bytes
|
||||
|
||||
const ruData string = "" + // Size: 129 bytes
|
||||
"\x14\x01\x81\x01\x02=\x00\x1f\x02Пожертвовать %[2]s=\x01-\x02Пожертвоват" +
|
||||
"ь долор™ %[2]s\x00(\x02Пожертвовать $%.2[1]f %[2]s"
|
||||
|
||||
// Total table size 276 bytes (0KiB); checksum: 3BFB37FB
|
|
@ -0,0 +1,90 @@
|
|||
//go:generate go install github.com/valyala/quicktemplate/qtc@master
|
||||
//go:generate qtc -dir=web
|
||||
//go:generate go install golang.org/x/text/cmd/gotext@master
|
||||
//go:generate gotext -srclang=en update -lang=en,ru -out=locales_gen.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"flag"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"syscall"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
|
||||
"source.toby3d.me/toby3d/pay/internal/cmd/pay"
|
||||
"source.toby3d.me/toby3d/pay/internal/domain"
|
||||
)
|
||||
|
||||
//go:embed web/static/*
|
||||
var embedStatic embed.FS
|
||||
|
||||
var static fs.FS = os.DirFS(filepath.Join("web", "static"))
|
||||
|
||||
func main() {
|
||||
logger := log.New(os.Stdout, "pay\t", log.LstdFlags)
|
||||
|
||||
// static, err := fs.Sub(static, filepath.Join("web", "static"))
|
||||
// if err != nil {
|
||||
// logger.Fatalln(err)
|
||||
// }
|
||||
|
||||
cpuProfilePath := flag.String("cpuprofile", "", "set path to saving CPU memory profile")
|
||||
memProfilePath := flag.String("memprofile", "", "set path to saving pprof memory profile")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
config := new(domain.Config)
|
||||
if err := env.ParseWithOptions(config, env.Options{Prefix: "PAY_"}); err != nil {
|
||||
logger.Fatalln(err)
|
||||
}
|
||||
|
||||
app := pay.New(logger, static, *config)
|
||||
|
||||
done := make(chan os.Signal, 1)
|
||||
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
if cpuProfilePath != nil && *cpuProfilePath != "" {
|
||||
cpuProfile, err := os.Create(*cpuProfilePath)
|
||||
if err != nil {
|
||||
logger.Fatalln("could not create CPU profile:", err)
|
||||
}
|
||||
defer cpuProfile.Close()
|
||||
|
||||
if err = pprof.StartCPUProfile(cpuProfile); err != nil {
|
||||
logger.Fatalln("could not start CPU profile:", err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
go app.Run()
|
||||
|
||||
<-done
|
||||
|
||||
if err := app.Stop(context.Background()); err != nil {
|
||||
logger.Fatalln(err)
|
||||
}
|
||||
|
||||
if memProfilePath == nil && *memProfilePath == "" {
|
||||
return
|
||||
}
|
||||
|
||||
memProfile, err := os.Create(*memProfilePath)
|
||||
if err != nil {
|
||||
logger.Fatalln("could not create memory profile:", err)
|
||||
}
|
||||
defer memProfile.Close()
|
||||
|
||||
runtime.GC() // NOTE(toby3d): get up-to-date statistics
|
||||
|
||||
if err = pprof.WriteHeapProfile(memProfile); err != nil {
|
||||
logger.Fatalln("could not write memory profile:", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"dir": "ltr",
|
||||
"lang": "en",
|
||||
"name": "NotDotPay",
|
||||
"short_name": "not.pay",
|
||||
"scope": "/",
|
||||
"icons": [
|
||||
{
|
||||
"sizes": "192x192",
|
||||
"src": "/icon-192.png",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"sizes": "512x512",
|
||||
"src": "/icon-512.png",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"display": "minimal-ui",
|
||||
"orientation": "natural",
|
||||
"start_url": ".",
|
||||
"id": "/",
|
||||
"theme_color": "#000",
|
||||
"related_applications": [
|
||||
{
|
||||
"platform": "play",
|
||||
"url": "https://play.google.com/store/apps/details?id=com.paypal.android.p2pmobile",
|
||||
"id": "com.paypal.android.p2pmobile"
|
||||
}
|
||||
],
|
||||
"prefer_related_applications": false,
|
||||
"background_color": "#fff",
|
||||
"shortcuts": []
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
:root {
|
||||
--ratio: 1.5;
|
||||
--s-5: calc(var(--s-4) / var(--ratio));
|
||||
--s-4: calc(var(--s-3) / var(--ratio));
|
||||
--s-3: calc(var(--s-2) / var(--ratio));
|
||||
--s-2: calc(var(--s-1) / var(--ratio));
|
||||
--s-1: calc(var(--s0) / var(--ratio));
|
||||
--s0: 1rem;
|
||||
--s1: calc(var(--s0) * var(--ratio));
|
||||
--s2: calc(var(--s1) * var(--ratio));
|
||||
--s3: calc(var(--s2) * var(--ratio));
|
||||
--s4: calc(var(--s3) * var(--ratio));
|
||||
--s5: calc(var(--s4) * var(--ratio));
|
||||
--measure: 60ch;
|
||||
/* System font stack, see: https://systemfontstack.com/ */
|
||||
font-family:
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
avenir next,
|
||||
avenir,
|
||||
segoe ui,
|
||||
helvetica neue,
|
||||
helvetica,
|
||||
Cantarell,
|
||||
Ubuntu,
|
||||
roboto,
|
||||
noto,
|
||||
arial,
|
||||
sans-serif;
|
||||
font-size: calc(1rem + 0.5vw);
|
||||
line-height: var(--ratio);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
max-inline-size: var(--measure);
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
header,
|
||||
nav,
|
||||
main,
|
||||
footer {
|
||||
max-inline-size: none;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.button,
|
||||
button {
|
||||
--_background: var(--background, #ccc);
|
||||
--_color: var(--color, #000);
|
||||
/* NOTE(toby3d): reset everything */
|
||||
all: unset;
|
||||
/* NOTE(toby3d): start from scratch */
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
align-items: baseline;
|
||||
appearance: none;
|
||||
background: var(--_background);
|
||||
border-radius: 0.3125em;
|
||||
border: 0;
|
||||
color: var(--_color);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
justify-content: center;
|
||||
min-width: 6.25em;
|
||||
padding: 0.625em 1em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button[href*='liberapay.com'],
|
||||
button[href*='liberapay.com'] {
|
||||
--background: #f6c915;
|
||||
--color: #1a171b;
|
||||
}
|
||||
|
||||
.button[href*='paypal.me'],
|
||||
button[href*='paypal.me'] {
|
||||
--background: #ffd140;
|
||||
--color: #001435;
|
||||
}
|
||||
|
||||
.button .icon,
|
||||
.button svg,
|
||||
button .icon,
|
||||
button svg {
|
||||
margin-inline-end: 0.25rem;
|
||||
height: 0.75em;
|
||||
height: 1cap;
|
||||
width: 0.75em;
|
||||
width: 1cap;
|
||||
}
|
||||
|
||||
/* Button behavior, see: https://bitsofco.de/when-do-the-hover-focus-and-active-pseudo-classes-apply/ */
|
||||
.button:hover,
|
||||
button:hover {
|
||||
/* TODO(toby3d): better color mathing with good WCAG grading */
|
||||
--background: #1d49aa;
|
||||
}
|
||||
|
||||
/* See: https://bitsofco.de/when-is-focus-visible-visible/ */
|
||||
.button:focus-visible,
|
||||
.button:focus,
|
||||
button:focus-visible,
|
||||
button:focus {
|
||||
outline: 4px solid #cbd6ee;
|
||||
}
|
||||
|
||||
/* See: https://bitsofco.de/when-is-focus-visible-visible/ */
|
||||
.button:focus:not(:focus-visible),
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.button:active,
|
||||
button:active {
|
||||
/* TODO(toby3d): better color mathing with good WCAG grading */
|
||||
--background: green;
|
||||
}
|
||||
|
||||
.button:not([href]),
|
||||
button[disabled] {
|
||||
/* TODO(toby3d): better color mathing with good WCAG grading */
|
||||
--background: #6c7589;
|
||||
--color: #d2d5db;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.button:not([href]) .icon,
|
||||
.button:not([href]) svg,
|
||||
button[disabled] .icon,
|
||||
button[disabled] svg {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
/* Cluster */
|
||||
.cluster {
|
||||
--_align: var(--align, flex-start);
|
||||
--_justify: var(--justify, flex-start);
|
||||
--_space: var(--space, var(--s1));
|
||||
align-items: var(--_align);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--_space);
|
||||
justify-content: var(--_justify);
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.list-style-type\:none {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.padding-inline-start\:unset {
|
||||
padding-inline-start: unset;
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
{% package template %}
|
||||
|
||||
{% import (
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
) %}
|
||||
|
||||
{% interface Page {
|
||||
body()
|
||||
dir()
|
||||
lang()
|
||||
t(format message.Reference, v ...any)
|
||||
title()
|
||||
head()
|
||||
} %}
|
||||
|
||||
{% code
|
||||
type Context struct {
|
||||
language language.Tag
|
||||
printer *message.Printer
|
||||
amount float64
|
||||
}
|
||||
|
||||
func NewContext(lang language.Tag, amount uint64) *Context {
|
||||
return &Context{
|
||||
language: lang,
|
||||
printer: message.NewPrinter(lang),
|
||||
amount: float64(amount),
|
||||
}
|
||||
}
|
||||
%}
|
||||
|
||||
{% stripspace %}
|
||||
{% func (ctx Context) head() %}
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="preload stylesheet"{% space %}
|
||||
as="style"{% space %}
|
||||
href="/styles.css"{% space %}
|
||||
type="text/css"{% space %}
|
||||
fetchpriority="high"{% space %}
|
||||
referrerpolicy="no-referrer"{% space %}
|
||||
crossorigin="anonymous">
|
||||
{% endfunc %}
|
||||
|
||||
{% func (ctx Context) body() %}
|
||||
<h1>{%= ctx.title() %}</h1>
|
||||
|
||||
<ul class="[ cluster ][ list-style-type:none padding-inline-start:unset ]">
|
||||
<li>
|
||||
<a class="button"{% space %}
|
||||
rel="noopener noreferrer payment"{% space %}
|
||||
href="https://liberapay.com/toby3d/donate{% if ctx.amount > 0 %}?amount={%f.2 ctx.amount/100 %}¤cy=USD&period=monthly{% endif %}">
|
||||
|
||||
{%= icon("liberapay") %}
|
||||
Liberapay
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button"{% space %}
|
||||
rel="noopener noreferrer payment"{% space %}
|
||||
href="https://www.paypal.me/toby3dKZ{% if ctx.amount > 0 %}/{%f.2 ctx.amount/100 %}USD{% endif %}">
|
||||
|
||||
{%= icon("paypal") %}
|
||||
PayPal
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfunc %}
|
||||
|
||||
{% func (ctx Context) dir() %}
|
||||
{% switch ctx.language %}
|
||||
{% default %}
|
||||
ltr
|
||||
{% case language.Arabic, language.Persian, language.Hebrew, language.Urdu %}
|
||||
rtl
|
||||
{% endswitch %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (ctx Context) lang() %}
|
||||
{% code base, _ := ctx.language.Base() %}
|
||||
{%s base.String() %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (ctx Context) t(format message.Reference, v ...any) %}
|
||||
{%s ctx.printer.Sprintf(format, v...) %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (ctx Context) title() %}
|
||||
{%= ctx.t(`Donate $%.2f to %s`, ctx.amount/100, "toby3d") %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func Template(p Page) %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{%= p.lang() %}" dir="{%= p.dir() %}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{%= p.title() %}</title>
|
||||
{%= p.head() %}
|
||||
</head>
|
||||
<body>
|
||||
{%= p.body() %}
|
||||
</body>
|
||||
</html>
|
||||
{% endfunc %}
|
||||
|
||||
{% func icon(id string) %}
|
||||
{% switch id %}
|
||||
{% case "liberapay" %}
|
||||
<svg class="icon"{% space %}
|
||||
width="32"{% space %}
|
||||
height="32"{% space %}
|
||||
viewBox="0 0 80 80"{% space %}
|
||||
aria-hidden="true"{% space %}
|
||||
focusable="false"{% space %}
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<path fill="#1a171b" d="M25.91 63.04c-3.57 0-6.37-.47-8.41-1.4a9.03 9.03 0 0 1-4.38-3.8 11.15 11.15 0 0 1-1.28-5.55c.03-2.08.32-4.31.87-6.67L22.3 5.57l11.69-1.81L23.5 47.2c-.2.9-.32 1.73-.34 2.49-.03.75.11 1.42.41 2 .3.57.82 1.04 1.55 1.39a8.7 8.7 0 0 0 3.05.68l-2.26 9.28m42.24-24.96c0 3.67-.6 7.03-1.81 10.07a23.94 23.94 0 0 1-5.01 7.88 22.43 22.43 0 0 1-7.7 5.17 25.4 25.4 0 0 1-9.76 1.85c-1.71 0-3.42-.16-5.13-.46l-3.4 13.65H24.19L36.7 24.05a67.6 67.6 0 0 1 6.9-1.62c2.6-.48 5.4-.71 8.42-.71 2.81 0 5.24.42 7.27 1.28a13.6 13.6 0 0 1 5.02 3.5 14.32 14.32 0 0 1 2.9 5.21c.63 1.99.95 4.11.95 6.37M40.78 53.54c.85.2 1.91.3 3.17.3 1.96 0 3.74-.36 5.35-1.09a11.8 11.8 0 0 0 4.11-3.05 14.1 14.1 0 0 0 2.64-4.72c.63-1.83.95-3.86.95-6.07 0-2.16-.48-4-1.44-5.5-.95-1.51-2.61-2.27-4.97-2.27-1.61 0-3.12.15-4.53.46l-5.28 21.94" />
|
||||
</svg>
|
||||
{% case "paypal" %}
|
||||
<svg class="icon"{% space %}
|
||||
width="32"{% space %}
|
||||
height="32"{% space %}
|
||||
viewBox="0 0 154.7 190.5"{% space %}
|
||||
aria-hidden="true"{% space %}
|
||||
focusable="false"{% space %}
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
{% comment %}Left blue{% endcomment %}
|
||||
<path fill="#003087" d="M28 0a5.5 5.5 0 0 0-5.5 4.6L.1 147.2a4.5 4.5 0 0 0 4.4 5.2h33.3l8.3-52.5 9-57.2a5.5 5.5 0 0 1 5.4-4.6h47.8c8.7 0 16.6 2 23.4 5.6C132 19.7 112.4 0 85.3 0z" />
|
||||
{% comment %}Middle blue{% endcomment %}
|
||||
<path fill="#001c64" d="M60.5 38.1a5.5 5.5 0 0 0-5.4 4.6l-9 57.2-8.3 52.5 8.3-52.5a5.5 5.5 0 0 1 5.4-4.6H78a54 54 0 0 0 53.8-51.6 50 50 0 0 0-23.4-5.6z" />
|
||||
{% comment %}Right blue{% endcomment %}
|
||||
<path fill="#0070e0" d="M131.7 43.7a54 54 0 0 1-53.8 51.6H51.5c-2.7 0-5 2-5.4 4.6l-8.3 52.5-5.2 33a4.5 4.5 0 0 0 4.4 5.1h28.7a5.5 5.5 0 0 0 5.4-4.6l7.6-48a5.5 5.5 0 0 1 5.4-4.5H101a54 54 0 0 0 53.2-45.7c3-18.7-6.5-35.6-22.5-44z" />
|
||||
</svg>
|
||||
{% endswitch %}
|
||||
{% endfunc %}
|
||||
{% endstripspace %}
|
|
@ -0,0 +1,517 @@
|
|||
// Code generated by qtc from "template.qtpl". DO NOT EDIT.
|
||||
// See https://github.com/valyala/quicktemplate for details.
|
||||
|
||||
//line web/template/template.qtpl:1
|
||||
package template
|
||||
|
||||
//line web/template/template.qtpl:3
|
||||
import (
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
)
|
||||
|
||||
//line web/template/template.qtpl:8
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line web/template/template.qtpl:8
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line web/template/template.qtpl:8
|
||||
type Page interface {
|
||||
//line web/template/template.qtpl:8
|
||||
body() string
|
||||
//line web/template/template.qtpl:8
|
||||
streambody(qw422016 *qt422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
writebody(qq422016 qtio422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
dir() string
|
||||
//line web/template/template.qtpl:8
|
||||
streamdir(qw422016 *qt422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
writedir(qq422016 qtio422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
lang() string
|
||||
//line web/template/template.qtpl:8
|
||||
streamlang(qw422016 *qt422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
writelang(qq422016 qtio422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
t(format message.Reference, v ...any) string
|
||||
//line web/template/template.qtpl:8
|
||||
streamt(qw422016 *qt422016.Writer, format message.Reference, v ...any)
|
||||
//line web/template/template.qtpl:8
|
||||
writet(qq422016 qtio422016.Writer, format message.Reference, v ...any)
|
||||
//line web/template/template.qtpl:8
|
||||
title() string
|
||||
//line web/template/template.qtpl:8
|
||||
streamtitle(qw422016 *qt422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
writetitle(qq422016 qtio422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
head() string
|
||||
//line web/template/template.qtpl:8
|
||||
streamhead(qw422016 *qt422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
writehead(qq422016 qtio422016.Writer)
|
||||
//line web/template/template.qtpl:8
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:18
|
||||
type Context struct {
|
||||
language language.Tag
|
||||
printer *message.Printer
|
||||
amount float64
|
||||
}
|
||||
|
||||
func NewContext(lang language.Tag, amount uint64) *Context {
|
||||
return &Context{
|
||||
language: lang,
|
||||
printer: message.NewPrinter(lang),
|
||||
amount: float64(amount),
|
||||
}
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:34
|
||||
func (ctx Context) streamhead(qw422016 *qt422016.Writer) {
|
||||
//line web/template/template.qtpl:34
|
||||
qw422016.N().S(`<link rel="icon" href="/favicon.ico" sizes="32x32"><link rel="icon" href="/icon.svg" type="image/svg+xml"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><link rel="manifest" href="/manifest.webmanifest"><link rel="preload stylesheet"`)
|
||||
//line web/template/template.qtpl:39
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:39
|
||||
qw422016.N().S(`as="style"`)
|
||||
//line web/template/template.qtpl:40
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:40
|
||||
qw422016.N().S(`href="/styles.css"`)
|
||||
//line web/template/template.qtpl:41
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:41
|
||||
qw422016.N().S(`type="text/css"`)
|
||||
//line web/template/template.qtpl:42
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:42
|
||||
qw422016.N().S(`fetchpriority="high"`)
|
||||
//line web/template/template.qtpl:43
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:43
|
||||
qw422016.N().S(`referrerpolicy="no-referrer"`)
|
||||
//line web/template/template.qtpl:44
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:44
|
||||
qw422016.N().S(`crossorigin="anonymous">`)
|
||||
//line web/template/template.qtpl:46
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:46
|
||||
func (ctx Context) writehead(qq422016 qtio422016.Writer) {
|
||||
//line web/template/template.qtpl:46
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:46
|
||||
ctx.streamhead(qw422016)
|
||||
//line web/template/template.qtpl:46
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:46
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:46
|
||||
func (ctx Context) head() string {
|
||||
//line web/template/template.qtpl:46
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:46
|
||||
ctx.writehead(qb422016)
|
||||
//line web/template/template.qtpl:46
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:46
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:46
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:46
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:48
|
||||
func (ctx Context) streambody(qw422016 *qt422016.Writer) {
|
||||
//line web/template/template.qtpl:48
|
||||
qw422016.N().S(`<h1>`)
|
||||
//line web/template/template.qtpl:49
|
||||
ctx.streamtitle(qw422016)
|
||||
//line web/template/template.qtpl:49
|
||||
qw422016.N().S(`</h1><ul class="[ cluster ][ list-style-type:none padding-inline-start:unset ]"><li><a class="button"`)
|
||||
//line web/template/template.qtpl:53
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:53
|
||||
qw422016.N().S(`rel="noopener noreferrer payment"`)
|
||||
//line web/template/template.qtpl:54
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:54
|
||||
qw422016.N().S(`href="https://liberapay.com/toby3d/donate`)
|
||||
//line web/template/template.qtpl:55
|
||||
if ctx.amount > 0 {
|
||||
//line web/template/template.qtpl:55
|
||||
qw422016.N().S(`?amount=`)
|
||||
//line web/template/template.qtpl:55
|
||||
qw422016.N().FPrec(ctx.amount/100, 2)
|
||||
//line web/template/template.qtpl:55
|
||||
qw422016.N().S(`¤cy=USD&period=monthly`)
|
||||
//line web/template/template.qtpl:55
|
||||
}
|
||||
//line web/template/template.qtpl:55
|
||||
qw422016.N().S(`">`)
|
||||
//line web/template/template.qtpl:57
|
||||
streamicon(qw422016, "liberapay")
|
||||
//line web/template/template.qtpl:57
|
||||
qw422016.N().S(`Liberapay</a></li><li><a class="button"`)
|
||||
//line web/template/template.qtpl:62
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:62
|
||||
qw422016.N().S(`rel="noopener noreferrer payment"`)
|
||||
//line web/template/template.qtpl:63
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:63
|
||||
qw422016.N().S(`href="https://www.paypal.me/toby3dKZ`)
|
||||
//line web/template/template.qtpl:64
|
||||
if ctx.amount > 0 {
|
||||
//line web/template/template.qtpl:64
|
||||
qw422016.N().S(`/`)
|
||||
//line web/template/template.qtpl:64
|
||||
qw422016.N().FPrec(ctx.amount/100, 2)
|
||||
//line web/template/template.qtpl:64
|
||||
qw422016.N().S(`USD`)
|
||||
//line web/template/template.qtpl:64
|
||||
}
|
||||
//line web/template/template.qtpl:64
|
||||
qw422016.N().S(`">`)
|
||||
//line web/template/template.qtpl:66
|
||||
streamicon(qw422016, "paypal")
|
||||
//line web/template/template.qtpl:66
|
||||
qw422016.N().S(`PayPal</a></span></li></ul>`)
|
||||
//line web/template/template.qtpl:72
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:72
|
||||
func (ctx Context) writebody(qq422016 qtio422016.Writer) {
|
||||
//line web/template/template.qtpl:72
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:72
|
||||
ctx.streambody(qw422016)
|
||||
//line web/template/template.qtpl:72
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:72
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:72
|
||||
func (ctx Context) body() string {
|
||||
//line web/template/template.qtpl:72
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:72
|
||||
ctx.writebody(qb422016)
|
||||
//line web/template/template.qtpl:72
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:72
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:72
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:72
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:74
|
||||
func (ctx Context) streamdir(qw422016 *qt422016.Writer) {
|
||||
//line web/template/template.qtpl:75
|
||||
switch ctx.language {
|
||||
//line web/template/template.qtpl:76
|
||||
default:
|
||||
//line web/template/template.qtpl:76
|
||||
qw422016.N().S(`ltr`)
|
||||
//line web/template/template.qtpl:78
|
||||
case language.Arabic, language.Persian, language.Hebrew, language.Urdu:
|
||||
//line web/template/template.qtpl:78
|
||||
qw422016.N().S(`rtl`)
|
||||
//line web/template/template.qtpl:80
|
||||
}
|
||||
//line web/template/template.qtpl:81
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:81
|
||||
func (ctx Context) writedir(qq422016 qtio422016.Writer) {
|
||||
//line web/template/template.qtpl:81
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:81
|
||||
ctx.streamdir(qw422016)
|
||||
//line web/template/template.qtpl:81
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:81
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:81
|
||||
func (ctx Context) dir() string {
|
||||
//line web/template/template.qtpl:81
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:81
|
||||
ctx.writedir(qb422016)
|
||||
//line web/template/template.qtpl:81
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:81
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:81
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:81
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:83
|
||||
func (ctx Context) streamlang(qw422016 *qt422016.Writer) {
|
||||
//line web/template/template.qtpl:84
|
||||
base, _ := ctx.language.Base()
|
||||
|
||||
//line web/template/template.qtpl:85
|
||||
qw422016.E().S(base.String())
|
||||
//line web/template/template.qtpl:86
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:86
|
||||
func (ctx Context) writelang(qq422016 qtio422016.Writer) {
|
||||
//line web/template/template.qtpl:86
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:86
|
||||
ctx.streamlang(qw422016)
|
||||
//line web/template/template.qtpl:86
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:86
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:86
|
||||
func (ctx Context) lang() string {
|
||||
//line web/template/template.qtpl:86
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:86
|
||||
ctx.writelang(qb422016)
|
||||
//line web/template/template.qtpl:86
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:86
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:86
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:86
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:88
|
||||
func (ctx Context) streamt(qw422016 *qt422016.Writer, format message.Reference, v ...any) {
|
||||
//line web/template/template.qtpl:89
|
||||
qw422016.E().S(ctx.printer.Sprintf(format, v...))
|
||||
//line web/template/template.qtpl:90
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:90
|
||||
func (ctx Context) writet(qq422016 qtio422016.Writer, format message.Reference, v ...any) {
|
||||
//line web/template/template.qtpl:90
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:90
|
||||
ctx.streamt(qw422016, format, v...)
|
||||
//line web/template/template.qtpl:90
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:90
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:90
|
||||
func (ctx Context) t(format message.Reference, v ...any) string {
|
||||
//line web/template/template.qtpl:90
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:90
|
||||
ctx.writet(qb422016, format, v...)
|
||||
//line web/template/template.qtpl:90
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:90
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:90
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:90
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:92
|
||||
func (ctx Context) streamtitle(qw422016 *qt422016.Writer) {
|
||||
//line web/template/template.qtpl:93
|
||||
ctx.streamt(qw422016, `Donate $%.2f to %s`, ctx.amount/100, "toby3d")
|
||||
//line web/template/template.qtpl:94
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:94
|
||||
func (ctx Context) writetitle(qq422016 qtio422016.Writer) {
|
||||
//line web/template/template.qtpl:94
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:94
|
||||
ctx.streamtitle(qw422016)
|
||||
//line web/template/template.qtpl:94
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:94
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:94
|
||||
func (ctx Context) title() string {
|
||||
//line web/template/template.qtpl:94
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:94
|
||||
ctx.writetitle(qb422016)
|
||||
//line web/template/template.qtpl:94
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:94
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:94
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:94
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:96
|
||||
func StreamTemplate(qw422016 *qt422016.Writer, p Page) {
|
||||
//line web/template/template.qtpl:96
|
||||
qw422016.N().S(`<!DOCTYPE html><html lang="`)
|
||||
//line web/template/template.qtpl:98
|
||||
p.streamlang(qw422016)
|
||||
//line web/template/template.qtpl:98
|
||||
qw422016.N().S(`" dir="`)
|
||||
//line web/template/template.qtpl:98
|
||||
p.streamdir(qw422016)
|
||||
//line web/template/template.qtpl:98
|
||||
qw422016.N().S(`"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>`)
|
||||
//line web/template/template.qtpl:102
|
||||
p.streamtitle(qw422016)
|
||||
//line web/template/template.qtpl:102
|
||||
qw422016.N().S(`</title>`)
|
||||
//line web/template/template.qtpl:103
|
||||
p.streamhead(qw422016)
|
||||
//line web/template/template.qtpl:103
|
||||
qw422016.N().S(`</head><body>`)
|
||||
//line web/template/template.qtpl:106
|
||||
p.streambody(qw422016)
|
||||
//line web/template/template.qtpl:106
|
||||
qw422016.N().S(`</body></html>`)
|
||||
//line web/template/template.qtpl:109
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:109
|
||||
func WriteTemplate(qq422016 qtio422016.Writer, p Page) {
|
||||
//line web/template/template.qtpl:109
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:109
|
||||
StreamTemplate(qw422016, p)
|
||||
//line web/template/template.qtpl:109
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:109
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:109
|
||||
func Template(p Page) string {
|
||||
//line web/template/template.qtpl:109
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:109
|
||||
WriteTemplate(qb422016, p)
|
||||
//line web/template/template.qtpl:109
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:109
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:109
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:109
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:111
|
||||
func streamicon(qw422016 *qt422016.Writer, id string) {
|
||||
//line web/template/template.qtpl:112
|
||||
switch id {
|
||||
//line web/template/template.qtpl:113
|
||||
case "liberapay":
|
||||
//line web/template/template.qtpl:113
|
||||
qw422016.N().S(`<svg class="icon"`)
|
||||
//line web/template/template.qtpl:114
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:114
|
||||
qw422016.N().S(`width="32"`)
|
||||
//line web/template/template.qtpl:115
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:115
|
||||
qw422016.N().S(`height="32"`)
|
||||
//line web/template/template.qtpl:116
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:116
|
||||
qw422016.N().S(`viewBox="0 0 80 80"`)
|
||||
//line web/template/template.qtpl:117
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:117
|
||||
qw422016.N().S(`aria-hidden="true"`)
|
||||
//line web/template/template.qtpl:118
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:118
|
||||
qw422016.N().S(`focusable="false"`)
|
||||
//line web/template/template.qtpl:119
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:119
|
||||
qw422016.N().S(`xmlns="http://www.w3.org/2000/svg"><path fill="#1a171b" d="M25.91 63.04c-3.57 0-6.37-.47-8.41-1.4a9.03 9.03 0 0 1-4.38-3.8 11.15 11.15 0 0 1-1.28-5.55c.03-2.08.32-4.31.87-6.67L22.3 5.57l11.69-1.81L23.5 47.2c-.2.9-.32 1.73-.34 2.49-.03.75.11 1.42.41 2 .3.57.82 1.04 1.55 1.39a8.7 8.7 0 0 0 3.05.68l-2.26 9.28m42.24-24.96c0 3.67-.6 7.03-1.81 10.07a23.94 23.94 0 0 1-5.01 7.88 22.43 22.43 0 0 1-7.7 5.17 25.4 25.4 0 0 1-9.76 1.85c-1.71 0-3.42-.16-5.13-.46l-3.4 13.65H24.19L36.7 24.05a67.6 67.6 0 0 1 6.9-1.62c2.6-.48 5.4-.71 8.42-.71 2.81 0 5.24.42 7.27 1.28a13.6 13.6 0 0 1 5.02 3.5 14.32 14.32 0 0 1 2.9 5.21c.63 1.99.95 4.11.95 6.37M40.78 53.54c.85.2 1.91.3 3.17.3 1.96 0 3.74-.36 5.35-1.09a11.8 11.8 0 0 0 4.11-3.05 14.1 14.1 0 0 0 2.64-4.72c.63-1.83.95-3.86.95-6.07 0-2.16-.48-4-1.44-5.5-.95-1.51-2.61-2.27-4.97-2.27-1.61 0-3.12.15-4.53.46l-5.28 21.94" /></svg>`)
|
||||
//line web/template/template.qtpl:124
|
||||
case "paypal":
|
||||
//line web/template/template.qtpl:124
|
||||
qw422016.N().S(`<svg class="icon"`)
|
||||
//line web/template/template.qtpl:125
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:125
|
||||
qw422016.N().S(`width="32"`)
|
||||
//line web/template/template.qtpl:126
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:126
|
||||
qw422016.N().S(`height="32"`)
|
||||
//line web/template/template.qtpl:127
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:127
|
||||
qw422016.N().S(`viewBox="0 0 154.7 190.5"`)
|
||||
//line web/template/template.qtpl:128
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:128
|
||||
qw422016.N().S(`aria-hidden="true"`)
|
||||
//line web/template/template.qtpl:129
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:129
|
||||
qw422016.N().S(`focusable="false"`)
|
||||
//line web/template/template.qtpl:130
|
||||
qw422016.N().S(` `)
|
||||
//line web/template/template.qtpl:130
|
||||
qw422016.N().S(`xmlns="http://www.w3.org/2000/svg">`)
|
||||
//line web/template/template.qtpl:133
|
||||
qw422016.N().S(`<path fill="#003087" d="M28 0a5.5 5.5 0 0 0-5.5 4.6L.1 147.2a4.5 4.5 0 0 0 4.4 5.2h33.3l8.3-52.5 9-57.2a5.5 5.5 0 0 1 5.4-4.6h47.8c8.7 0 16.6 2 23.4 5.6C132 19.7 112.4 0 85.3 0z" />`)
|
||||
//line web/template/template.qtpl:135
|
||||
qw422016.N().S(`<path fill="#001c64" d="M60.5 38.1a5.5 5.5 0 0 0-5.4 4.6l-9 57.2-8.3 52.5 8.3-52.5a5.5 5.5 0 0 1 5.4-4.6H78a54 54 0 0 0 53.8-51.6 50 50 0 0 0-23.4-5.6z" />`)
|
||||
//line web/template/template.qtpl:137
|
||||
qw422016.N().S(`<path fill="#0070e0" d="M131.7 43.7a54 54 0 0 1-53.8 51.6H51.5c-2.7 0-5 2-5.4 4.6l-8.3 52.5-5.2 33a4.5 4.5 0 0 0 4.4 5.1h28.7a5.5 5.5 0 0 0 5.4-4.6l7.6-48a5.5 5.5 0 0 1 5.4-4.5H101a54 54 0 0 0 53.2-45.7c3-18.7-6.5-35.6-22.5-44z" /></svg>`)
|
||||
//line web/template/template.qtpl:140
|
||||
}
|
||||
//line web/template/template.qtpl:141
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:141
|
||||
func writeicon(qq422016 qtio422016.Writer, id string) {
|
||||
//line web/template/template.qtpl:141
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line web/template/template.qtpl:141
|
||||
streamicon(qw422016, id)
|
||||
//line web/template/template.qtpl:141
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line web/template/template.qtpl:141
|
||||
}
|
||||
|
||||
//line web/template/template.qtpl:141
|
||||
func icon(id string) string {
|
||||
//line web/template/template.qtpl:141
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line web/template/template.qtpl:141
|
||||
writeicon(qb422016, id)
|
||||
//line web/template/template.qtpl:141
|
||||
qs422016 := string(qb422016.B)
|
||||
//line web/template/template.qtpl:141
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line web/template/template.qtpl:141
|
||||
return qs422016
|
||||
//line web/template/template.qtpl:141
|
||||
}
|
Loading…
Reference in New Issue