Compare commits
130 Commits
7e171abfb8
...
7baa6a3b96
Author | SHA1 | Date | |
---|---|---|---|
7baa6a3b96 | |||
f3174c5cd6 | |||
7e4555052a | |||
c1dc42d4e1 | |||
3c6e75b6c0 | |||
b2c9128ceb | |||
04cdb98158 | |||
cfdc3df103 | |||
396d8498d0 | |||
d35c93c7aa | |||
23f74ff054 | |||
e2447b29c8 | |||
3a33c86a35 | |||
0fc2c8da79 | |||
bc3775f77d | |||
3e22083182 | |||
9a77843747 | |||
7bd93049f6 | |||
00e88a66aa | |||
ba87778c72 | |||
777e9684d8 | |||
8886447348 | |||
23fac0acc0 | |||
3bd67749e2 | |||
c8b1b75567 | |||
e2fff3a30d | |||
d5e553852b | |||
082487fab7 | |||
3a50e68071 | |||
fe49235647 | |||
257b6b1447 | |||
06ca188aed | |||
fb778f78af | |||
74e93c509f | |||
cb30f18461 | |||
073f0fe61e | |||
bde68d9369 | |||
c442d26e91 | |||
66c1e96d22 | |||
f1e36861d1 | |||
df1582b143 | |||
a45c99532b | |||
391838c98e | |||
786b20e9df | |||
e97bec2273 | |||
dbe87450f2 | |||
5bd7e28690 | |||
f5722220ae | |||
2bee714197 | |||
704e877af0 | |||
32b762be3c | |||
b4ee791a29 | |||
69c6c50ccc | |||
c66166b6ae | |||
4a514b3a50 | |||
0554dab12c | |||
6eaaeba81d | |||
6cefb70b31 | |||
410f2e71ae | |||
a4794dd38c | |||
59032499d1 | |||
c8078e8829 | |||
a89f289bcb | |||
739bb7b180 | |||
1610888f4a | |||
ace455645d | |||
71b152db4b | |||
011564a200 | |||
6ffe308bde | |||
103721fc9b | |||
cc7c2b9b87 | |||
9867712165 | |||
3656b3dcab | |||
43af5b5686 | |||
8b994ce249 | |||
550ae2e35c | |||
6287f6aab4 | |||
14e1f7d9a4 | |||
ffe13d2c6d | |||
3a8266a3b6 | |||
4e4633ab1a | |||
917b614136 | |||
dfc2d60853 | |||
f78a8b1619 | |||
138ad3cd2e | |||
face13b207 | |||
6d3a99c63c | |||
85f773d39d | |||
d4c1e991d0 | |||
84326d430a | |||
885fedc8fb | |||
3782570338 | |||
bb3f71d15e | |||
00cea89b53 | |||
85c02f44a9 | |||
5d6be689d2 | |||
5fca652767 | |||
66731d270b | |||
6ba92558c5 | |||
8c3a1884ae | |||
d2c19667fc | |||
d94f5119f7 | |||
385c72fbdd | |||
f4780c5b9c | |||
259d97940d | |||
9c0a5812f1 | |||
ab15488f91 | |||
b197dc8c37 | |||
3eb6393b42 | |||
2e53cab03f | |||
c87ba03d30 | |||
3a0e90c990 | |||
66920b93a3 | |||
d864c9f7b4 | |||
f91c0eda8a | |||
4606f2719d | |||
0df804736b | |||
815b896cc0 | |||
54477bd12d | |||
21fdf408ca | |||
b93d68b4fa | |||
ad9c9b4db7 | |||
7d8ccd3a01 | |||
8150fb0afb | |||
28efa0fa62 | |||
62383d28f9 | |||
cc94d487e1 | |||
965fb51598 | |||
16e16a033b | |||
963d9e63f0 |
159
.drone.yml
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: kubernetes
|
||||||
|
name: staging
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
|
||||||
|
# STAGING!!!!
|
||||||
|
steps:
|
||||||
|
- name: build-wasm
|
||||||
|
image: golang:1.17.8-alpine
|
||||||
|
volumes:
|
||||||
|
- name: build-staging
|
||||||
|
path: /drone/src/build
|
||||||
|
environment:
|
||||||
|
APIURL: https://api.quenten.nl/api/testing
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- mkdir ./build/web
|
||||||
|
- GOARCH=wasm GOOS=js go build -o ./build/web/app.wasm -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||||
|
- name: build-server
|
||||||
|
image: golang:1.17.8-alpine
|
||||||
|
volumes:
|
||||||
|
- name: build-staging
|
||||||
|
path: /drone/src/build
|
||||||
|
environment:
|
||||||
|
APIURL: https://api.quenten.nl/api/testing
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- go build -o ./build/app -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||||
|
- name: build-publish-image
|
||||||
|
image: plugins/docker
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- name: build-staging
|
||||||
|
path: /drone/src/build
|
||||||
|
settings:
|
||||||
|
cache_from:
|
||||||
|
- "dutchellie/proper-website-2:dev"
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
repo: dutchellie/proper-website-2
|
||||||
|
tags:
|
||||||
|
- dev-${DRONE_COMMIT_SHA:0:8}
|
||||||
|
depends_on:
|
||||||
|
- build-wasm
|
||||||
|
- build-server
|
||||||
|
- name: deploy-staging
|
||||||
|
image: pelotech/drone-helm3
|
||||||
|
settings:
|
||||||
|
mode: upgrade
|
||||||
|
chart: .drone/helm/chart
|
||||||
|
namespace: drone-staging
|
||||||
|
release: newsite-staging
|
||||||
|
skip_tls_verify: true
|
||||||
|
values_files:
|
||||||
|
- .drone/helm/staging-val.yaml
|
||||||
|
values:
|
||||||
|
- "image=dutchellie/proper-website-2:dev-${DRONE_COMMIT_SHA:0:8}"
|
||||||
|
kube_api_server:
|
||||||
|
from_secret: staging_api_server
|
||||||
|
kube_token:
|
||||||
|
from_secret: staging_kube_token
|
||||||
|
kube_certificate:
|
||||||
|
from_secret: staging_kube_certificate
|
||||||
|
kube_service_account: drone-deploy
|
||||||
|
dry_run: false
|
||||||
|
depends_on:
|
||||||
|
- build-publish-image
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: build-staging
|
||||||
|
temp: {}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
kind: pipeline
|
||||||
|
type: kubernetes
|
||||||
|
name: production
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- promote
|
||||||
|
target:
|
||||||
|
- production
|
||||||
|
|
||||||
|
# PRODUCTION!!!!
|
||||||
|
steps:
|
||||||
|
- name: build-wasm
|
||||||
|
image: golang:1.17.8-alpine
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /drone/src/build
|
||||||
|
environment:
|
||||||
|
APIURL: https://api.quenten.nl/api
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- mkdir ./build/web
|
||||||
|
- GOARCH=wasm GOOS=js go build -o ./build/web/app.wasm -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||||
|
- name: build-server
|
||||||
|
image: golang:1.17.8-alpine
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /drone/src/build
|
||||||
|
environment:
|
||||||
|
APIURL: https://api.quenten.nl/api
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- go build -o ./build/app -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||||
|
- name: build-publish-image
|
||||||
|
image: plugins/docker
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /drone/src/build
|
||||||
|
settings:
|
||||||
|
cache_from:
|
||||||
|
- "dutchellie/proper-website-2:latest"
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
repo: dutchellie/proper-website-2
|
||||||
|
tags:
|
||||||
|
- latest-${DRONE_COMMIT_SHA:0:8}
|
||||||
|
depends_on:
|
||||||
|
- build-wasm
|
||||||
|
- build-server
|
||||||
|
- name: deploy-production
|
||||||
|
image: pelotech/drone-helm3
|
||||||
|
settings:
|
||||||
|
mode: upgrade
|
||||||
|
chart: .drone/helm/chart
|
||||||
|
namespace: drone-production
|
||||||
|
release: newsite-production
|
||||||
|
skip_tls_verify: true
|
||||||
|
values_files:
|
||||||
|
- .drone/helm/prod-val.yaml
|
||||||
|
values:
|
||||||
|
- "image=dutchellie/proper-website-2:latest-${DRONE_COMMIT_SHA:0:8}"
|
||||||
|
kube_api_server:
|
||||||
|
from_secret: prod_api_server
|
||||||
|
kube_token:
|
||||||
|
from_secret: prod_kube_token
|
||||||
|
kube_certificate:
|
||||||
|
from_secret: prod_kube_certificate
|
||||||
|
kube_service_account: drone-deploy
|
||||||
|
dry_run: false
|
||||||
|
depends_on:
|
||||||
|
- build-publish-image
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
temp: {}
|
3
.drone/helm/chart/Chart.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: newsite
|
||||||
|
version: v0.0.1
|
39
.drone/helm/chart/templates/deployment.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.name }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
{{- if .Values.annotations }}
|
||||||
|
annotations: {{ toYaml .Values.annotations | nindent 4}}
|
||||||
|
{{- end}}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ .Values.name }}
|
||||||
|
release: {{ .Values.release }}
|
||||||
|
replicas: {{ .Values.replicas }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- if .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- range $key, $value := .Values.controller.podAnnotations }}
|
||||||
|
{{ $key }}: {{ $value | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
app: {{ .Values.name }}
|
||||||
|
release: {{ .Values.release }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: {{ .Values.containerName }}
|
||||||
|
image: {{ .Values.image }}
|
||||||
|
imagePullPolicy: {{ .Values.imagePullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8000
|
||||||
|
{{- if .Values.containerEnv }}
|
||||||
|
env: {{ toYaml .Values.containerEnv | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
|
||||||
|
|
36
.drone/helm/chart/templates/ingress.yaml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.ingress.name }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.tls }}
|
||||||
|
- hosts:
|
||||||
|
{{- range .hosts }}
|
||||||
|
- {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range .paths }}
|
||||||
|
- path: {{ .path }}
|
||||||
|
pathType: {{ .pathType }}
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ $.Values.service.name }}
|
||||||
|
port:
|
||||||
|
number: 8000
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
18
.drone/helm/chart/templates/service.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.service.name }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
{{- with .Values.service.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: {{ .Values.name }}
|
||||||
|
release: {{ .Values.release }}
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
name: http
|
||||||
|
port: 8000
|
||||||
|
targetPort: 8000
|
28
.drone/helm/chart/values.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: newsite-deployment
|
||||||
|
annotations: {}
|
||||||
|
release: latest
|
||||||
|
replicas: 1
|
||||||
|
podAnnotations: {}
|
||||||
|
containerName: newsite
|
||||||
|
image: dutchellie/proper-website-2:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
containerEnv: []
|
||||||
|
|
||||||
|
service:
|
||||||
|
name: newsite-service
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
name: newsite-ingress
|
||||||
|
annotations: {}
|
||||||
|
className: nginx
|
||||||
|
tls: []
|
||||||
|
# tls:
|
||||||
|
# - hosts:
|
||||||
|
# - example.com
|
||||||
|
# secretName: example-tls
|
||||||
|
hosts:
|
||||||
|
- host: example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
23
.drone/helm/prod-val.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: newsite-prod
|
||||||
|
containerEnv:
|
||||||
|
- name: APIURL
|
||||||
|
value: https://api.quenten.nl/api
|
||||||
|
service:
|
||||||
|
name: newsite-prod
|
||||||
|
ingress:
|
||||||
|
name: newsite-prod
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
external-dns.alpha.kubernetes.io/hostname: "quenten.nl"
|
||||||
|
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||||
|
add_header Content-Security-Policy "frame-ancestors 'self' https://forestofunix.xyz";
|
||||||
|
proxy_hide_header X-Frame-Options ;
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- quenten.nl
|
||||||
|
secretName: newsite-tls
|
||||||
|
hosts:
|
||||||
|
- host: quenten.nl
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
23
.drone/helm/staging-val.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: newsite-staging
|
||||||
|
containerEnv:
|
||||||
|
- name: APIURL
|
||||||
|
value: https://api.quenten.nl/api/testing
|
||||||
|
service:
|
||||||
|
name: newsite-staging
|
||||||
|
ingress:
|
||||||
|
name: newsite-staging
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||||
|
external-dns.alpha.kubernetes.io/hostname: "staging.quenten.nl"
|
||||||
|
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||||
|
add_header Content-Security-Policy "frame-ancestors 'self' https://forestofunix.xyz";
|
||||||
|
proxy_hide_header X-Frame-Options ;
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- staging.quenten.nl
|
||||||
|
secretName: newsite-staging-tls
|
||||||
|
hosts:
|
||||||
|
- host: staging.quenten.nl
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
0
.drone/helm/values.yaml
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.vscode
|
||||||
|
app
|
||||||
|
web/*.wasm
|
||||||
|
staticsite
|
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#FROM golang:1.17.8-alpine AS builder
|
||||||
|
#ARG APIURL
|
||||||
|
#WORKDIR /project
|
||||||
|
#ADD . /project/
|
||||||
|
#RUN go mod tidy
|
||||||
|
#RUN GOARCH=wasm GOOS=js go build -o web/app.wasm -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||||
|
#RUN go build -o app -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
WORKDIR /root
|
||||||
|
RUN mkdir ./web
|
||||||
|
COPY ./web ./web
|
||||||
|
COPY ./build/web/app.wasm ./web/
|
||||||
|
COPY ./build/app ./
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["./app"]
|
26
Makefile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
APIURL_prod := https://api.quenten.nl/api
|
||||||
|
APIURL_staging := https://api.quenten.nl/api/testing
|
||||||
|
|
||||||
|
build:
|
||||||
|
GOARCH=wasm GOOS=js go build -o web/app.wasm -ldflags="-X 'main.ApiURL=${APIURL_staging}'" ./src
|
||||||
|
go build -o app -ldflags="-X 'main.ApiURL=${APIURL_staging}'" ./src
|
||||||
|
cp -r web staticsite/
|
||||||
|
|
||||||
|
build-new:
|
||||||
|
GOARCH=wasm GOOS=js go build -o web/app.wasm ./src
|
||||||
|
go build -o app ./src
|
||||||
|
cp -r web staticsite/
|
||||||
|
|
||||||
|
build-prod:
|
||||||
|
GOARCH=wasm GOOS=js go build -o web/app.wasm -ldflags="-X 'main.ApiURL=${APIURL_prod}'" ./src
|
||||||
|
go build -o app -ldflags="-X 'main.ApiURL=${APIURL_prod}'" ./src
|
||||||
|
cp -r web staticsite/
|
||||||
|
|
||||||
|
run: build
|
||||||
|
./app
|
||||||
|
|
||||||
|
run-new: build-new
|
||||||
|
APIURL=${APIURL_staging} ./app
|
||||||
|
|
||||||
|
run-prod: build-prod
|
||||||
|
./app
|
33
README.md
@ -1,3 +1,34 @@
|
|||||||
# proper-website-2
|
# proper-website-2
|
||||||
|
|
||||||
Truly proper website
|
[](https://drone.dutchellie.nl/DutchEllie/proper-website-2)
|
||||||
|
|
||||||
|
A truly proper website this time™
|
||||||
|
|
||||||
|
**TODO**:
|
||||||
|
- Change domain to quenten.nl and staging.quenten.nl
|
||||||
|
- Dynamically make domains for other branches
|
||||||
|
- Make a generic page component, so that you don't have to add the standard panels every time (such as navbar, leftbar, header, etc)
|
||||||
|
|
||||||
|
App notes:
|
||||||
|
- leftbar (under nav) is default always the same.
|
||||||
|
This is in a standardized "page" component.
|
||||||
|
it can be changed by specifying it when creating the "page" component, or maybe in a prerender/onnav (or both).
|
||||||
|
Structure will be:
|
||||||
|
Generic page component IS IMPLEMENTED BY "specific page" WHERE OnNav/OnPreRender specify its details WHICH IS CREATED with its own "newXPage()" function. See https://github.com/maxence-charriere/go-app/blob/master/docs/src/actions-page.go for example.
|
||||||
|
- "main content" is an array of app.UI elements.
|
||||||
|
This can also be further abstracted using "block" components (the ones with the cool color)
|
||||||
|
- THOSE BLOCK COMPONENTS' CONTENT can be set manually on creation, OR by passing HTML/MarkDown (not decided yet)
|
||||||
|
|
||||||
|
This website will be done with this:
|
||||||
|
- Backend written in Golang
|
||||||
|
- Templating with Go
|
||||||
|
- Database connection for comments and such
|
||||||
|
- Markdown article writing
|
||||||
|
|
||||||
|
It will be done in the following order:
|
||||||
|
- First get up a working simple proof of concept
|
||||||
|
- Then make the comments better
|
||||||
|
- Database better
|
||||||
|
- Layout
|
||||||
|
- Markdown article writing
|
||||||
|
- Layout
|
73
comments.go.old
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.home.dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *application) Comment(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case "POST":
|
||||||
|
var comment entity.Comment
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
a.WriteError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(body, &comment)
|
||||||
|
if err != nil {
|
||||||
|
a.WriteError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
comment.PostDate = time.Now()
|
||||||
|
|
||||||
|
if comment.Name == "" || comment.Message == "" {
|
||||||
|
a.WriteError(w, http.StatusBadRequest, "one or more fields empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = a.collection.InsertOne(context.Background(), comment)
|
||||||
|
if err != nil {
|
||||||
|
a.WriteError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(200)
|
||||||
|
return
|
||||||
|
case "GET":
|
||||||
|
comments := make([]entity.Comment, 0)
|
||||||
|
filter := bson.D{}
|
||||||
|
sort := options.Find()
|
||||||
|
sort.SetSort(bson.D{{"time", -1}})
|
||||||
|
cur, err := a.collection.Find(context.Background(), filter, sort)
|
||||||
|
if err != nil {
|
||||||
|
a.WriteError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = cur.All(context.Background(), &comments)
|
||||||
|
if err != nil {
|
||||||
|
a.WriteError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsondata, err := json.Marshal(comments)
|
||||||
|
if err != nil {
|
||||||
|
a.WriteError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write(jsondata)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *application) WriteError(w http.ResponseWriter, code int, err string) {
|
||||||
|
w.WriteHeader(code)
|
||||||
|
w.Write([]byte(err))
|
||||||
|
}
|
15
entity/blogpost.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Blogpost contains the name, date and such of a blogpost
|
||||||
|
// The actual post itself is hosted somewhere else in the form of an (html) document
|
||||||
|
// This is put in the path field
|
||||||
|
|
||||||
|
type BlogPost struct {
|
||||||
|
ID string `json:"id,omitempty" bson:"_id,omitempty"`
|
||||||
|
Title string `json:"title" bson:"title"`
|
||||||
|
Author string `json:"author" bson:"author"`
|
||||||
|
Path string `json:"path" bson:"path"`
|
||||||
|
PostDate time.Time `json:"time" bson:"time"`
|
||||||
|
}
|
12
entity/comment.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
ID string `json:"id,omitempty" bson:"_id,omitempty"`
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
Website string `json:"website,omitempty" bson:"website,omitempty"`
|
||||||
|
Email string `json:"email,omitempty" bson:"email,omitempty"`
|
||||||
|
Message string `json:"message" bson:"message"`
|
||||||
|
PostDate time.Time `json:"time" bson:"time"`
|
||||||
|
}
|
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module dutchellie.nl/DutchEllie/proper-website-2
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/handlers v1.5.1
|
||||||
|
github.com/maxence-charriere/go-app/v9 v9.3.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
)
|
28
go.sum
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||||
|
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||||
|
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||||
|
github.com/maxence-charriere/go-app/v9 v9.3.3 h1:vo+1oohWMfTQ0S3eg9JOjuFEy1n3bVNgeDi4eHNfMzA=
|
||||||
|
github.com/maxence-charriere/go-app/v9 v9.3.3/go.mod h1:zo0n1kh4OMKn7P+MrTUUi7QwUMU2HOfHsZ293TITtxI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||||
|
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
30
src/aboutpage.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AboutPage struct {
|
||||||
|
app.Compo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAboutPage() *AboutPage {
|
||||||
|
return &AboutPage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AboutPage) Render() app.UI {
|
||||||
|
return newPage().
|
||||||
|
Title("About me").
|
||||||
|
LeftBar(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("left").
|
||||||
|
Class("leftbarblock").
|
||||||
|
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||||
|
).
|
||||||
|
Main(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
Src("/web/blocks/pages/about.html"),
|
||||||
|
)
|
||||||
|
}
|
86
src/block.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type htmlBlock struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Iclass string
|
||||||
|
Isrc string // HTML document source
|
||||||
|
Iid string
|
||||||
|
|
||||||
|
// TODO: implement invisibility for other background functions
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHTMLBlock() *htmlBlock {
|
||||||
|
return &htmlBlock{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *htmlBlock) ID(v string) *htmlBlock {
|
||||||
|
b.Iid = v
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *htmlBlock) Class(v string) *htmlBlock {
|
||||||
|
b.Iclass = app.AppendClass(b.Iclass, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *htmlBlock) Src(v string) *htmlBlock {
|
||||||
|
b.Isrc = v
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *htmlBlock) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Class("block").
|
||||||
|
Class(b.Iclass).
|
||||||
|
Body(
|
||||||
|
newRemoteHTMLDoc().
|
||||||
|
Src(b.Isrc),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================
|
||||||
|
// UI element block
|
||||||
|
// ==================
|
||||||
|
|
||||||
|
type uiBlock struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Iclass string
|
||||||
|
Iui []app.UI
|
||||||
|
Iid string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUIBlock() *uiBlock {
|
||||||
|
return &uiBlock{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *uiBlock) ID(v string) *uiBlock {
|
||||||
|
b.Iid = v
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *uiBlock) Class(v string) *uiBlock {
|
||||||
|
b.Iclass = app.AppendClass(b.Iclass, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *uiBlock) UI(v ...app.UI) *uiBlock {
|
||||||
|
b.Iui = app.FilterUIElems(v...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *uiBlock) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Class("block").
|
||||||
|
Class(b.Iclass).
|
||||||
|
Body(
|
||||||
|
app.Range(b.Iui).Slice(func(i int) app.UI {
|
||||||
|
return b.Iui[i]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
74
src/bloglinks.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type blogLinks struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
blogposts []entity.BlogPost
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blogLinks) OnMount(ctx app.Context) {
|
||||||
|
b.LoadPosts(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blogLinks) Render() app.UI {
|
||||||
|
return newUIBlock().
|
||||||
|
Class("left").
|
||||||
|
Class("leftbarblock").
|
||||||
|
Class("blogpost-bar").
|
||||||
|
UI(
|
||||||
|
app.P().
|
||||||
|
Class("p-h3").
|
||||||
|
Style("margin-left", "10px").
|
||||||
|
Style("margin-top", "10px").
|
||||||
|
Style("text-decoration", "underline").
|
||||||
|
Text("Posts"),
|
||||||
|
app.Range(b.blogposts).Slice(func(i int) app.UI {
|
||||||
|
return app.P().
|
||||||
|
Class("blogpost-titles").
|
||||||
|
Style("cursor", "pointer").
|
||||||
|
Text(b.blogposts[i].Title).
|
||||||
|
OnClick(func(ctx app.Context, e app.Event) {
|
||||||
|
//e.PreventDefault()
|
||||||
|
|
||||||
|
// Calling the update-blogpost action defined in the blogpage.go file.
|
||||||
|
// There it's updated
|
||||||
|
ctx.NewAction("update-blogpost", app.T("blogpost", b.blogposts[i].Path))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blogLinks) LoadPosts(ctx app.Context) {
|
||||||
|
// TODO: maybe you can put this in a localbrowser storage?
|
||||||
|
url := ApiURL + "/blogpost"
|
||||||
|
ctx.Async(func() {
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
jsondata, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Dispatch(func(ctx app.Context) {
|
||||||
|
err = json.Unmarshal(jsondata, &b.blogposts)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
54
src/blogpage.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BlogPage struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
display bool
|
||||||
|
currentPostPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: write the backend API for this
|
||||||
|
// TODO: Find a proper way of rendering blog posts in markdown
|
||||||
|
// Backend ideas: In the DB, create an entry for each post and a link to where the html file is located!
|
||||||
|
// That way, I don't have to parse and render markdown!!
|
||||||
|
|
||||||
|
// Layout, the leftbar contains the blogpost links and the mainbar contains the post itself!
|
||||||
|
// Function: After pressing a link for a blog post, that blog post ID gets put in the state instead of the URL
|
||||||
|
|
||||||
|
func NewBlogPage() *BlogPage {
|
||||||
|
return &BlogPage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BlogPage) OnMount(ctx app.Context) {
|
||||||
|
ctx.Handle("update-blogpost", b.onUpdateBlogPost)
|
||||||
|
b.display = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BlogPage) Render() app.UI {
|
||||||
|
return newPage().
|
||||||
|
Title("Blog").
|
||||||
|
LeftBar(
|
||||||
|
&blogLinks{},
|
||||||
|
).
|
||||||
|
Main(
|
||||||
|
app.If(b.display,
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
Src(b.currentPostPath),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BlogPage) onUpdateBlogPost(ctx app.Context, a app.Action) {
|
||||||
|
fmt.Printf("Called the update-blogpost ActionHandler\n")
|
||||||
|
blogpost := a.Tags.Get("blogpost")
|
||||||
|
b.currentPostPath = blogpost
|
||||||
|
b.display = true
|
||||||
|
}
|
15
src/emptypage.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
|
||||||
|
type EmptyPage struct {
|
||||||
|
app.Compo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEmptyPage() *EmptyPage {
|
||||||
|
return &EmptyPage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EmptyPage) Render() app.UI {
|
||||||
|
return app.Head().Body()
|
||||||
|
}
|
137
src/galaxiespage.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GalaxiesPage struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
gnuOver bool
|
||||||
|
mousex, mousey int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGalaxiesPage() *GalaxiesPage {
|
||||||
|
return &GalaxiesPage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *GalaxiesPage) Render() app.UI {
|
||||||
|
return newPage().
|
||||||
|
Title("Galaxies").
|
||||||
|
LeftBar(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("left").
|
||||||
|
Class("leftbarblock").
|
||||||
|
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||||
|
).
|
||||||
|
Main(
|
||||||
|
/*
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
Src("/web/blocks/pages/galaxies.html"),
|
||||||
|
*/
|
||||||
|
newUIBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
UI(
|
||||||
|
app.Div().
|
||||||
|
Body(
|
||||||
|
app.P().
|
||||||
|
Class("p-h1").
|
||||||
|
Text("Galaxies"),
|
||||||
|
app.P().
|
||||||
|
Class("content-text").
|
||||||
|
Text(`Here you can find some really really really cool pages that I found on the internet.
|
||||||
|
Some of these are blogs or even blogposts I found, but the ones on top are special!
|
||||||
|
They're the websites of friends of mine! Please visit them, because they worked really hard
|
||||||
|
on their websites as well!`),
|
||||||
|
app.Div().
|
||||||
|
Body(
|
||||||
|
app.P().
|
||||||
|
Class("p-h2").
|
||||||
|
Class("mt-20").
|
||||||
|
Class("mb-10").
|
||||||
|
Class("bold").
|
||||||
|
Text("My friends!"),
|
||||||
|
app.Ul().
|
||||||
|
Body(
|
||||||
|
app.Li().
|
||||||
|
Body(
|
||||||
|
newLinkWithStatus().
|
||||||
|
Link("https://forestofunix.xyz").
|
||||||
|
LinkText("Forest of Unix").
|
||||||
|
Text("A website made by Sebastiaan. A massive Linux fanboy, runs Gentoo on his ThinkPad. Absolutely based. His website is written in Lisp, that's why it's often offline. That was the inspiration for the online/offline status text."),
|
||||||
|
),
|
||||||
|
app.Li().
|
||||||
|
Body(
|
||||||
|
newLinkWithStatus().
|
||||||
|
Link("https://nymphali.neocities.org").
|
||||||
|
LinkText("Nymphali").
|
||||||
|
Text("The website made by ■■■■■■, whoops Nymphali. They have an awesome minimalist website that's just lovely."),
|
||||||
|
),
|
||||||
|
app.Li().
|
||||||
|
Body(
|
||||||
|
newLinkWithStatus().
|
||||||
|
Link("https://kristypixel.neocities.org").
|
||||||
|
LinkText("Kristypixel").
|
||||||
|
Text("Website made by Kristy. Very cute website, I love it! Keep up the awesome work!"),
|
||||||
|
),
|
||||||
|
app.Li().
|
||||||
|
Body(
|
||||||
|
newLinkWithStatus().
|
||||||
|
Link("https://leftonred.neocities.org").
|
||||||
|
LinkText("Left on Red").
|
||||||
|
Text("Kyu made this website, he's a friend of mine as well! Still very new, but their dark mode design is very cool!"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Body(
|
||||||
|
app.P().
|
||||||
|
Class("p-h2").
|
||||||
|
Class("mt-20").
|
||||||
|
Class("mb-10").
|
||||||
|
Class("bold").
|
||||||
|
Text("Neat webspaces"),
|
||||||
|
app.P().
|
||||||
|
Class("m-t5").
|
||||||
|
Style("margin-left", "10px").
|
||||||
|
Text("Just very neat websites I found and causes I support. Not necessarily by people I know. I just wanted to share them here!"),
|
||||||
|
app.Ul().
|
||||||
|
Body(
|
||||||
|
app.Li().
|
||||||
|
Body(
|
||||||
|
newLinkWithStatus().
|
||||||
|
Link("https://evillious.ylimegirl.com").
|
||||||
|
LinkText("Evillious Chronicles fan guide").
|
||||||
|
Text("A VERY cool website made by Ylimegirl! They wrote a whole website dedicated to Evillious Chronicles, which is a super good Japanese light novel and vocaloid series!! Definitely look it up!"),
|
||||||
|
),
|
||||||
|
app.Li().
|
||||||
|
Body(
|
||||||
|
newLinkWithStatus().
|
||||||
|
Link("https://www.gnu.org").
|
||||||
|
LinkBody(
|
||||||
|
newTextWithTooltip().
|
||||||
|
Text("The GNU Project").
|
||||||
|
Tooltip(
|
||||||
|
app.Img().
|
||||||
|
Src("/web/static/images/gnu-head-sm.png").
|
||||||
|
Width(129).
|
||||||
|
Height(122).
|
||||||
|
Alt("GNU"),
|
||||||
|
),
|
||||||
|
).
|
||||||
|
Text("The official website of the GNU project. They advocate for free/libre software. This is not to be confused with 'open source' software. I highly recommend you read about them and their efforts."),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (f *GalaxiesPage) onMouseOverGnu(ctx app.Context, e app.Event) {
|
||||||
|
|
||||||
|
}*/
|
338
src/guestbook.go
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type guestbook struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
comments []entity.Comment
|
||||||
|
|
||||||
|
name string
|
||||||
|
email string
|
||||||
|
website string
|
||||||
|
message string
|
||||||
|
|
||||||
|
lastHash [32]byte
|
||||||
|
gbModalOpen bool
|
||||||
|
OnSubmit func(
|
||||||
|
ctx app.Context,
|
||||||
|
name string,
|
||||||
|
email string,
|
||||||
|
website string,
|
||||||
|
message string,
|
||||||
|
) // Handler to implement which calls the api
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: The comments are loaded like 2 or 3 times every time the page is loaded...
|
||||||
|
func (g *guestbook) OnMount(ctx app.Context) {
|
||||||
|
ctx.Handle("guestbook-loadcomments", g.onHandleLoadComments)
|
||||||
|
ctx.NewAction("guestbook-loadcomments")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (g *guestbook) OnNav(ctx app.Context) {
|
||||||
|
g.LoadComments(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *guestbook) OnUpdate(ctx app.Context) {
|
||||||
|
g.LoadComments(ctx)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func (g guestbook) Render() app.UI {
|
||||||
|
return app.Div().Body(
|
||||||
|
app.Form().
|
||||||
|
Class("guestbook-form").
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
Class("input-groups").
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
Class("fr").
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
Class("input-group").
|
||||||
|
Class("input-group-name").
|
||||||
|
Body(
|
||||||
|
app.Label().
|
||||||
|
For("name").
|
||||||
|
Text("Name:"),
|
||||||
|
app.Input().
|
||||||
|
Type("text").
|
||||||
|
Name("name").
|
||||||
|
Class("input").
|
||||||
|
Required(true).
|
||||||
|
OnChange(g.ValueTo(&g.name)),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Class("input-group").
|
||||||
|
Class("input-group-email").
|
||||||
|
Body(
|
||||||
|
app.Label().
|
||||||
|
For("email").
|
||||||
|
Text("Email: (optional)"),
|
||||||
|
app.Input().
|
||||||
|
Type("text").
|
||||||
|
Name("email").
|
||||||
|
Class("input").
|
||||||
|
Required(false).
|
||||||
|
OnChange(g.ValueTo(&g.email)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Class("input-group").
|
||||||
|
Class("input-group-website").
|
||||||
|
Body(
|
||||||
|
app.Label().
|
||||||
|
For("website").
|
||||||
|
Text("Website: (optional)"),
|
||||||
|
app.Input().
|
||||||
|
Type("text").
|
||||||
|
Name("website").
|
||||||
|
Class("input").
|
||||||
|
Required(false).
|
||||||
|
OnChange(g.ValueTo(&g.website)),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Class("input-group").
|
||||||
|
Class("input-group-message").
|
||||||
|
Body(
|
||||||
|
app.Label().
|
||||||
|
For("message").
|
||||||
|
Text("Message:"),
|
||||||
|
app.Textarea().
|
||||||
|
Name("message").
|
||||||
|
Class("input").
|
||||||
|
Rows(5).
|
||||||
|
Cols(30).
|
||||||
|
Required(true).
|
||||||
|
OnChange(g.ValueTo(&g.message)),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Class("submit-field").
|
||||||
|
Body(
|
||||||
|
app.Input().
|
||||||
|
Type("submit").
|
||||||
|
Value("Send!"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).OnSubmit(func(ctx app.Context, e app.Event) {
|
||||||
|
// This was to prevent the page from reloading
|
||||||
|
e.PreventDefault()
|
||||||
|
if g.name == "" || g.message == "" {
|
||||||
|
fmt.Printf("Error: one or more field(s) are empty. For now unhandled\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(g.name) > 40 || len(g.message) > 360 {
|
||||||
|
fmt.Printf("Error: Your message is too long fucker\n")
|
||||||
|
g.gbModalOpen = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.OnSubmit(ctx, g.name, g.email, g.website, g.message)
|
||||||
|
g.clear()
|
||||||
|
ctx.NewAction("guestbook-loadcomments")
|
||||||
|
//g.LoadComments(ctx)
|
||||||
|
}),
|
||||||
|
app.If(
|
||||||
|
g.gbModalOpen,
|
||||||
|
&guestbookAlertModal{
|
||||||
|
OnClose: func() {
|
||||||
|
g.gbModalOpen = false
|
||||||
|
g.Update()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
app.Div().Body(
|
||||||
|
app.Range(g.comments).Slice(func(i int) app.UI {
|
||||||
|
return &guestbookComment{
|
||||||
|
Comment: g.comments[i],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *guestbook) SmartLoadComments(ctx app.Context) {
|
||||||
|
{
|
||||||
|
// Get the comments quickly to at least render something
|
||||||
|
tmpjsondata := make([]byte, 0)
|
||||||
|
err := ctx.LocalStorage().Get("comments", &tmpjsondata)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Dispatch(func(ctx app.Context) {
|
||||||
|
err = json.Unmarshal(tmpjsondata, &g.comments)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.Update()
|
||||||
|
}
|
||||||
|
var lasthash []byte
|
||||||
|
err := ctx.LocalStorage().Get("lasthash", &lasthash)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lasthash == nil {
|
||||||
|
fmt.Printf("Program thinks lasthash is empty\n")
|
||||||
|
g.LoadComments(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url := ApiURL + "/commenthash"
|
||||||
|
ctx.Async(func() {
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
hash, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("hash: %v\n", hash)
|
||||||
|
//fmt.Printf("lasthash: %v\n", lasthash)
|
||||||
|
|
||||||
|
// If the hash is different, aka there was an update in the comments
|
||||||
|
if !bytes.Equal(hash, lasthash) {
|
||||||
|
fmt.Printf("Hash calculation is different\n")
|
||||||
|
g.LoadComments(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the hash is not different, then there is no need to load new comments
|
||||||
|
jsondata := make([]byte, 0)
|
||||||
|
err = ctx.LocalStorage().Get("comments", &jsondata)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//fmt.Printf("jsondata: %v\n", jsondata)
|
||||||
|
ctx.Dispatch(func(ctx app.Context) {
|
||||||
|
err = json.Unmarshal(jsondata, &g.comments)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *guestbook) LoadComments(ctx app.Context) {
|
||||||
|
// TODO: maybe you can put this in a localbrowser storage?
|
||||||
|
//fmt.Printf("Called LoadComments()\n")
|
||||||
|
url := ApiURL + "/comment"
|
||||||
|
ctx.Async(func() {
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
jsondata, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Dispatch(func(ctx app.Context) {
|
||||||
|
err = json.Unmarshal(jsondata, &g.comments)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.LocalStorage().Set("comments", jsondata)
|
||||||
|
// Calculating the hash
|
||||||
|
//fmt.Printf("Calculating the hash from LoadComments\n")
|
||||||
|
hash := sha256.Sum256(jsondata)
|
||||||
|
//fmt.Printf("hash fresh from calculation: %v\n", hash)
|
||||||
|
//g.lastHash = hash
|
||||||
|
err = ctx.LocalStorage().Set("lasthash", []byte(fmt.Sprintf("%x\n", hash)))
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *guestbook) clear() {
|
||||||
|
g.name = ""
|
||||||
|
g.message = ""
|
||||||
|
g.website = ""
|
||||||
|
g.email = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *guestbook) onHandleLoadComments(ctx app.Context, a app.Action) {
|
||||||
|
g.SmartLoadComments(ctx)
|
||||||
|
g.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
type guestbookAlertModal struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
PreviousAttempts int
|
||||||
|
OnClose func() // For when we close the modal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *guestbookAlertModal) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Class("gb-modal").
|
||||||
|
ID("gbModal").
|
||||||
|
OnClick(func(ctx app.Context, e app.Event) {
|
||||||
|
g.OnClose()
|
||||||
|
}).
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
Class("gb-modal-content").
|
||||||
|
Body(
|
||||||
|
app.Span().Class("close").Text("X").
|
||||||
|
OnClick(func(ctx app.Context, e app.Event) {
|
||||||
|
//modal := app.Window().GetElementByID("gbModal")
|
||||||
|
//modal.Set("style", "none")
|
||||||
|
g.OnClose()
|
||||||
|
}),
|
||||||
|
app.P().Text("Your name must be <= 40 and your message must be <= 360 characters"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type guestbookComment struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Comment entity.Comment
|
||||||
|
time string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *guestbookComment) Render() app.UI {
|
||||||
|
c.time = c.Comment.PostDate.Format(time.RFC1123)
|
||||||
|
return app.Div().Body(
|
||||||
|
app.Div().Class().Body(
|
||||||
|
app.P().Text(c.Comment.Name).Class("name"),
|
||||||
|
app.P().Text(c.time).Class("date"),
|
||||||
|
).Class("comment-header"),
|
||||||
|
app.Div().Class("comment-message").Body(
|
||||||
|
app.P().Text(c.Comment.Message),
|
||||||
|
),
|
||||||
|
).Class("comment")
|
||||||
|
}
|
16
src/header.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
app.Compo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Class("header").
|
||||||
|
Body(
|
||||||
|
app.Text("Internetica Galactica"),
|
||||||
|
//&updater{},
|
||||||
|
)
|
||||||
|
}
|
76
src/homepage.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ApiURL string
|
||||||
|
)
|
||||||
|
|
||||||
|
type Homepage struct {
|
||||||
|
app.Compo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHomepage() *Homepage {
|
||||||
|
return &Homepage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Homepage) Render() app.UI {
|
||||||
|
return newPage().
|
||||||
|
Title("Homepage").
|
||||||
|
LeftBar(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("left").
|
||||||
|
Class("leftbarblock").
|
||||||
|
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||||
|
).
|
||||||
|
Main(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
Src("/web/blocks/pages/intro.html"),
|
||||||
|
newUIBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
UI(
|
||||||
|
&guestbook{
|
||||||
|
OnSubmit: func(ctx app.Context, name, email, website, message string) {
|
||||||
|
var comment entity.Comment
|
||||||
|
comment.Name = name
|
||||||
|
comment.Email = email
|
||||||
|
comment.Website = website
|
||||||
|
comment.Message = message
|
||||||
|
|
||||||
|
jsondata, err := json.Marshal(comment)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url := ApiURL + "/comment"
|
||||||
|
|
||||||
|
// This is not Async'ed, because otherwise you run into a race
|
||||||
|
// condition where you reload the comments before the server had time
|
||||||
|
// to process the request!
|
||||||
|
{
|
||||||
|
req, err := http.Post(url, "application/json", bytes.NewBuffer(jsondata))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.StatusCode == 200 {
|
||||||
|
p.Update()
|
||||||
|
}
|
||||||
|
defer req.Body.Close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
75
src/html-doc.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type htmlDoc struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Ihtml string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHTMLDoc() *htmlDoc {
|
||||||
|
return &htmlDoc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *htmlDoc) HTML(v string) *htmlDoc {
|
||||||
|
h.Ihtml = fmt.Sprintf("<div>%s</div>", v)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *htmlDoc) Render() app.UI {
|
||||||
|
return app.Raw(h.Ihtml)
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteHTMLDoc struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Isrc string
|
||||||
|
|
||||||
|
html htmlContent
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoteHTMLDoc() *remoteHTMLDoc {
|
||||||
|
return &remoteHTMLDoc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *remoteHTMLDoc) Src(v string) *remoteHTMLDoc {
|
||||||
|
h.Isrc = v
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *remoteHTMLDoc) OnMount(ctx app.Context) {
|
||||||
|
h.load(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *remoteHTMLDoc) OnNav(ctx app.Context) {
|
||||||
|
h.load(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *remoteHTMLDoc) load(ctx app.Context) {
|
||||||
|
src := h.Isrc
|
||||||
|
ctx.ObserveState(htmlState(src)).
|
||||||
|
While(func() bool {
|
||||||
|
return src == h.Isrc
|
||||||
|
}).
|
||||||
|
OnChange(func() {
|
||||||
|
|
||||||
|
}).
|
||||||
|
Value(&h.html)
|
||||||
|
|
||||||
|
ctx.NewAction(getHTML, app.T("path", h.Isrc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *remoteHTMLDoc) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Body(
|
||||||
|
app.If(h.html.Status == loaded,
|
||||||
|
newHTMLDoc().
|
||||||
|
HTML(h.html.Data),
|
||||||
|
).Else(),
|
||||||
|
)
|
||||||
|
}
|
63
src/html.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getHTML = "/html/get"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleGetHTML(ctx app.Context, a app.Action) {
|
||||||
|
path := a.Tags.Get("path")
|
||||||
|
if path == "" {
|
||||||
|
app.Log(errors.New("getting html failed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state := htmlState(path)
|
||||||
|
|
||||||
|
var ht htmlContent
|
||||||
|
ctx.GetState(state, &ht)
|
||||||
|
switch ht.Status {
|
||||||
|
case loading, loaded:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ht.Status = loading
|
||||||
|
ht.Error = nil
|
||||||
|
ctx.SetState(state, ht)
|
||||||
|
|
||||||
|
res, err := get(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
ht.Status = loadingErr
|
||||||
|
ht.Error = err
|
||||||
|
ctx.SetState(state, ht)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ht.Status = loaded
|
||||||
|
ht.Data = string(res)
|
||||||
|
ctx.SetState(state, ht)
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlState(src string) string {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
type htmlContent struct {
|
||||||
|
Status status
|
||||||
|
Error error
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
type status int
|
||||||
|
|
||||||
|
const (
|
||||||
|
neverLoaded status = iota
|
||||||
|
loading
|
||||||
|
loadingErr
|
||||||
|
loaded
|
||||||
|
)
|
41
src/http.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
func get(ctx app.Context, path string) ([]byte, error) {
|
||||||
|
url := path
|
||||||
|
if !strings.HasPrefix(url, "http") {
|
||||||
|
u := ctx.Page().URL()
|
||||||
|
u.Path = path
|
||||||
|
url = u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error at getting html page\n")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
// Which means either client or server error
|
||||||
|
if res.StatusCode >= 400 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
103
src/main.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
//type application struct {
|
||||||
|
// client *mongo.Client
|
||||||
|
// database *mongo.Database
|
||||||
|
// collection *mongo.Collection
|
||||||
|
//}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
homepage := NewHomepage()
|
||||||
|
aboutpage := NewAboutPage()
|
||||||
|
galaxiespage := NewGalaxiesPage()
|
||||||
|
undertalePage := NewUndertalePage()
|
||||||
|
musicPage := NewMusicPage()
|
||||||
|
app.Route("/", homepage)
|
||||||
|
app.Route("/about", aboutpage)
|
||||||
|
app.Route("/galaxies", galaxiespage)
|
||||||
|
app.Route("/undertale", undertalePage)
|
||||||
|
app.Route("/blog", NewBlogPage())
|
||||||
|
app.Route("/music", musicPage)
|
||||||
|
|
||||||
|
app.Handle(getHTML, handleGetHTML)
|
||||||
|
|
||||||
|
// This is executed on the client side only.
|
||||||
|
// It handles client side stuff
|
||||||
|
// It exits immediately on the server side
|
||||||
|
app.RunWhenOnBrowser()
|
||||||
|
|
||||||
|
icon := &app.Icon{
|
||||||
|
Default: "/web/static/images/icon-small.png",
|
||||||
|
Large: "/web/static/images/icon.png",
|
||||||
|
}
|
||||||
|
handler := &app.Handler{
|
||||||
|
Name: "Internetica Galactica",
|
||||||
|
Icon: *icon,
|
||||||
|
BackgroundColor: "#362730",
|
||||||
|
ThemeColor: "#362730",
|
||||||
|
LoadingLabel: "Internetica Galactica",
|
||||||
|
Title: "Internetica Galactica",
|
||||||
|
Description: "A 1990's style PWA!",
|
||||||
|
Author: "Quenten",
|
||||||
|
Keywords: []string{
|
||||||
|
"Based website",
|
||||||
|
"Cool website",
|
||||||
|
"PWA",
|
||||||
|
"Programming",
|
||||||
|
"Go", "Golang",
|
||||||
|
"Webassembly", "WASM",
|
||||||
|
"DutchEllie", "Quenten",
|
||||||
|
},
|
||||||
|
Styles: []string{
|
||||||
|
"/web/static/style.css",
|
||||||
|
"/web/static/adreena.css",
|
||||||
|
"/web/static/anisha.css",
|
||||||
|
"/web/static/havakana.css",
|
||||||
|
"/web/static/form.css",
|
||||||
|
},
|
||||||
|
CacheableResources: []string{
|
||||||
|
// Images
|
||||||
|
"/web/static/images/email3.gif",
|
||||||
|
"/web/static/images/rin-len1.webp",
|
||||||
|
"/web/static/images/background_star.gif",
|
||||||
|
"/web/static/images/kanata-1.gif",
|
||||||
|
"/web/static/images/rin-1.gif",
|
||||||
|
"/web/static/images/rin-2.gif",
|
||||||
|
"/web/static/images/gnu-head-sm.png",
|
||||||
|
// Pages
|
||||||
|
"/web/blocks/pages/about.html",
|
||||||
|
"/web/blocks/pages/intro.html",
|
||||||
|
"/web/blocks/snippets/bannerpanel.html",
|
||||||
|
// Music
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/Tokusya-Seizon%20Wonder-la-der%21%21.mp3",
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/kegarenaki-barajuuji.mp3",
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/error-towa.mp3",
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/diamond-city-lights-lazulight.opus",
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/tsunami-finana.opus",
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/%E7%A5%9E%E3%81%A3%E3%81%BD%E3%81%84%E3%81%AA.m4a",
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/Servant%20of%20Evil%20with%20English%20Sub%20-%20%E6%82%AA%E3%83%8E%E5%8F%AC%E4%BD%BF%20-%20Kagamine%20Len%20-%20HQ.m4a",
|
||||||
|
"https://music-website.s3.nl-ams.scw.cloud/%E3%83%94%E3%83%8E%E3%82%AD%E3%82%AA%E3%83%94%E3%83%BC%20-%20%E3%81%8D%E3%81%BF%E3%82%82%E6%82%AA%E3%81%84%E4%BA%BA%E3%81%A7%E3%82%88%E3%81%8B%E3%81%A3%E3%81%9F%20feat.%20%E5%88%9D%E9%9F%B3%E3%83%9F%E3%82%AF%20_%20I%27m%20glad%20you%27re%20evil%20too.m4a",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.GenerateStaticWebsite("./staticsite", handler)
|
||||||
|
compressed := handlers.CompressHandlerLevel(handler, gzip.BestSpeed)
|
||||||
|
http.Handle("/", compressed)
|
||||||
|
if os.Getenv("GEN_STATIC_SITE") == "true" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(":8000", nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
110
src/menu.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
|
||||||
|
type menu struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Iclass string
|
||||||
|
updateAvailable bool
|
||||||
|
|
||||||
|
IpaneWidth int
|
||||||
|
OnClickButton func(page string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMenu() *menu {
|
||||||
|
return &menu{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) PaneWidth(px int) *menu {
|
||||||
|
if px > 0 {
|
||||||
|
m.IpaneWidth = px
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) Class(v string) *menu {
|
||||||
|
m.Iclass = app.AppendClass(m.Iclass, v)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) OnAppUpdate(ctx app.Context) {
|
||||||
|
m.updateAvailable = ctx.AppUpdateAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Class("block").
|
||||||
|
// Class("leftbarblock-nop").
|
||||||
|
Class("navbar").
|
||||||
|
Body(
|
||||||
|
app.Ul().Body(
|
||||||
|
newMenuLink().
|
||||||
|
Link("/").
|
||||||
|
Text("Home"),
|
||||||
|
newMenuLink().
|
||||||
|
Link("/about").
|
||||||
|
Text("About"),
|
||||||
|
newMenuLink().
|
||||||
|
Link("/galaxies").
|
||||||
|
Text("Galaxies"),
|
||||||
|
newMenuLink().
|
||||||
|
Link("/music").
|
||||||
|
Text("Music"),
|
||||||
|
// Disabled for now since there are none anyway
|
||||||
|
app.Li().
|
||||||
|
Body(
|
||||||
|
app.A().Href("/blog").Text("Blog"),
|
||||||
|
).Style("display", "none"),
|
||||||
|
),
|
||||||
|
app.If(m.updateAvailable,
|
||||||
|
app.Div().Body(
|
||||||
|
app.Img().
|
||||||
|
Src("/web/static/images/hot1.gif").
|
||||||
|
Class("update-img"),
|
||||||
|
app.Span().
|
||||||
|
Text("Update available! Click here to update!").
|
||||||
|
Class("update-text"),
|
||||||
|
).
|
||||||
|
Class("update-div").
|
||||||
|
OnClick(m.onUpdateClick),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) onUpdateClick(ctx app.Context, e app.Event) {
|
||||||
|
ctx.Reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
type menuLink struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
IText string
|
||||||
|
ILink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMenuLink() *menuLink {
|
||||||
|
return &menuLink{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menuLink) Text(v string) *menuLink {
|
||||||
|
m.IText = v
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menuLink) Link(v string) *menuLink {
|
||||||
|
m.ILink = v
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menuLink) Render() app.UI {
|
||||||
|
return app.A().
|
||||||
|
Class("menuitem-link").
|
||||||
|
Href(m.ILink).
|
||||||
|
Body(app.Div().
|
||||||
|
Class("menuitem").
|
||||||
|
Body(app.Span().
|
||||||
|
Class("menuitem-text").
|
||||||
|
Text(m.IText)),
|
||||||
|
)
|
||||||
|
}
|
213
src/misc.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
//#################################
|
||||||
|
//## ###
|
||||||
|
//## Link with status component ###
|
||||||
|
//## ###
|
||||||
|
//#################################
|
||||||
|
|
||||||
|
type linkWithStatus struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Ilink string
|
||||||
|
IText string
|
||||||
|
ILinkText string
|
||||||
|
ILinkBody app.UI
|
||||||
|
status bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLinkWithStatus() *linkWithStatus {
|
||||||
|
return &linkWithStatus{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *linkWithStatus) Link(s string) *linkWithStatus {
|
||||||
|
f.Ilink = s
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *linkWithStatus) LinkBody(v app.UI) *linkWithStatus {
|
||||||
|
f.ILinkBody = v
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *linkWithStatus) LinkText(s string) *linkWithStatus {
|
||||||
|
return f.LinkBody(app.Text(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *linkWithStatus) Text(s string) *linkWithStatus {
|
||||||
|
f.IText = s
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *linkWithStatus) checkStatus(ctx app.Context) {
|
||||||
|
ctx.Async(func() {
|
||||||
|
data := struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
}{
|
||||||
|
Url: f.Ilink,
|
||||||
|
}
|
||||||
|
jsondata, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", ApiURL+"/checkonline", bytes.NewBuffer(jsondata))
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonresp := struct {
|
||||||
|
Status bool `json:"status"`
|
||||||
|
}{}
|
||||||
|
err = json.Unmarshal(body, &jsonresp)
|
||||||
|
if err != nil {
|
||||||
|
app.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Dispatch(func(ctx app.Context) {
|
||||||
|
f.status = jsonresp.Status
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *linkWithStatus) OnNav(ctx app.Context) {
|
||||||
|
f.checkStatus(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *linkWithStatus) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
Style("display", "flex").
|
||||||
|
Style("gap", "20px").
|
||||||
|
Body(
|
||||||
|
app.A().
|
||||||
|
Href(f.Ilink).
|
||||||
|
Class("p-h3").
|
||||||
|
Class("m-t5").
|
||||||
|
Body(f.ILinkBody),
|
||||||
|
app.If(f.status,
|
||||||
|
app.P().
|
||||||
|
Style("color", "green").
|
||||||
|
Style("width", "fit-content").
|
||||||
|
Style("margin", "5px 0px 0px 0px").
|
||||||
|
Text("Online"),
|
||||||
|
).Else(
|
||||||
|
app.P().
|
||||||
|
Style("color", "red").
|
||||||
|
Style("width", "fit-content").
|
||||||
|
Style("margin", "5px 0px 0px 0px").
|
||||||
|
Text("Offline"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
app.P().
|
||||||
|
Class("m-t5").
|
||||||
|
Text(f.IText),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//#################################
|
||||||
|
//## ###
|
||||||
|
//## Text with tooltip ###
|
||||||
|
//## ###
|
||||||
|
//#################################
|
||||||
|
|
||||||
|
type textWithTooltip struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
ITooltip app.UI
|
||||||
|
IClass string
|
||||||
|
ITextClass string
|
||||||
|
IText string
|
||||||
|
|
||||||
|
activated bool
|
||||||
|
mousex, mousey int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTextWithTooltip() *textWithTooltip {
|
||||||
|
return &textWithTooltip{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *textWithTooltip) Class(v string) *textWithTooltip {
|
||||||
|
f.IClass = app.AppendClass(f.IClass, v)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *textWithTooltip) TextClass(v string) *textWithTooltip {
|
||||||
|
f.ITextClass = app.AppendClass(f.ITextClass, v)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *textWithTooltip) Text(v string) *textWithTooltip {
|
||||||
|
f.IText = v
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *textWithTooltip) Tooltip(v app.UI) *textWithTooltip {
|
||||||
|
f.ITooltip = v
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *textWithTooltip) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Class(f.IClass).
|
||||||
|
Body(
|
||||||
|
app.Span().
|
||||||
|
Class(f.ITextClass).
|
||||||
|
Text(f.IText).
|
||||||
|
OnMouseOver(func(ctx app.Context, e app.Event) {
|
||||||
|
f.activated = true
|
||||||
|
}).
|
||||||
|
OnMouseMove(func(ctx app.Context, e app.Event) {
|
||||||
|
f.mousex, f.mousey = app.Window().CursorPosition()
|
||||||
|
}).
|
||||||
|
OnMouseOut(func(ctx app.Context, e app.Event) {
|
||||||
|
f.activated = false
|
||||||
|
}),
|
||||||
|
app.If(f.activated,
|
||||||
|
app.Div().
|
||||||
|
Style("position", "fixed").
|
||||||
|
Style("overflow", "hidden").
|
||||||
|
Style("top", fmt.Sprintf("%dpx", f.mousey+20)).
|
||||||
|
Style("left", fmt.Sprintf("%dpx", f.mousex+20)).
|
||||||
|
Body(
|
||||||
|
f.ITooltip,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//#################################
|
||||||
|
//## ###
|
||||||
|
//## Tooltip ###
|
||||||
|
//## ###
|
||||||
|
//#################################
|
||||||
|
|
||||||
|
type toolTip struct {
|
||||||
|
app.Compo
|
||||||
|
}
|
37
src/modal.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
|
||||||
|
// A generic modal to be used on the entire site
|
||||||
|
type Modal struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Title string
|
||||||
|
Body []app.UI // Body of the modal
|
||||||
|
|
||||||
|
OnClose func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modal) Render() app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Class("generic-modal").
|
||||||
|
ID("genericModal").
|
||||||
|
OnClick(func(ctx app.Context, e app.Event) {
|
||||||
|
m.OnClose()
|
||||||
|
}).
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
Class("gb-modal-content").
|
||||||
|
Body(
|
||||||
|
app.Span().Class("close").Text("X").
|
||||||
|
OnClick(func(ctx app.Context, e app.Event) {
|
||||||
|
//modal := app.Window().GetElementByID("gbModal")
|
||||||
|
//modal.Set("style", "none")
|
||||||
|
m.OnClose()
|
||||||
|
}),
|
||||||
|
app.Div().
|
||||||
|
Class("generic-modal-body").
|
||||||
|
Body(m.Body...),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
51
src/music.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Idea for this page:
|
||||||
|
// - Make a navbar on the top for different genres and switch the pages content when clicked
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
|
||||||
|
type MusicPage struct {
|
||||||
|
app.Compo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMusicPage() *MusicPage {
|
||||||
|
return &MusicPage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *MusicPage) Render() app.UI {
|
||||||
|
return newPage().
|
||||||
|
Title("Music!").
|
||||||
|
LeftBar(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("left").
|
||||||
|
Class("leftbarblock").
|
||||||
|
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||||
|
).
|
||||||
|
Main(
|
||||||
|
// Genre navbar above this
|
||||||
|
newUIBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
UI(
|
||||||
|
app.Div().
|
||||||
|
Body(
|
||||||
|
app.P().
|
||||||
|
Class("m-t5").
|
||||||
|
Text(`I am quite picky with my music most of the time. I rarely enjoy an entire album of an artist and most artists for me have only a couple amazing songs.
|
||||||
|
My tastes in music are almost exclusively Japanese songs. Vocaloid is how I began and nowadays I listen to all sorts of Japanese music.
|
||||||
|
Here are some of the songs, artists and albums I like the most.`),
|
||||||
|
app.P().
|
||||||
|
Class("p-h3").
|
||||||
|
Style("color", "red").
|
||||||
|
Text("Warning! Player feature still in beta. Stuff can break and design is most certainly not final at all!"),
|
||||||
|
app.P().
|
||||||
|
Text("Just click one of the songs to play it."),
|
||||||
|
app.P().
|
||||||
|
Class("p-h2").
|
||||||
|
Text("Songs"),
|
||||||
|
newMusicPlayer(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
165
src/musicplayer.go
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
songRepoURL string = "" // URL where the music files are stored. Made a bit like the ApiURL
|
||||||
|
)
|
||||||
|
|
||||||
|
type song struct {
|
||||||
|
ITitle string
|
||||||
|
IID string
|
||||||
|
IURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSong() *song {
|
||||||
|
return &song{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *song) Title(v string) *song {
|
||||||
|
f.ITitle = v
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *song) URL(v string) *song {
|
||||||
|
f.IURL = v
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *song) ID(v string) *song {
|
||||||
|
f.IID = v
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
type musicPlayer struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
songs map[string](*song)
|
||||||
|
currentlySelectedSong string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMusicPlayer() *musicPlayer {
|
||||||
|
return &musicPlayer{
|
||||||
|
songs: make(map[string]*song),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On... handlers
|
||||||
|
|
||||||
|
func (f *musicPlayer) OnMount(ctx app.Context) {
|
||||||
|
ctx.Handle("switchSong", f.handleSwitchSong)
|
||||||
|
// Statically create all the music.
|
||||||
|
// I am not making a database for this shit lmao
|
||||||
|
// Also do not forget to at least set the currentlySelectedSong to the first one added, or at least make it point somewhere
|
||||||
|
// f.songs["ievan-polka"] = newSong().
|
||||||
|
// Title("Ievan Polka - Hatsune Miku").
|
||||||
|
// URL("https://files.catbox.moe/lh229f.mp3").
|
||||||
|
// ID("ievan-polka")
|
||||||
|
f.songs["god-ish"] = newSong().
|
||||||
|
Title("God-ish (神っぽいな) feat. Hatsune Miku - PinocchioP").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/%E7%A5%9E%E3%81%A3%E3%81%BD%E3%81%84%E3%81%AA.m4a").
|
||||||
|
ID("god-ish")
|
||||||
|
f.songs["servant-of-evil"] = newSong().
|
||||||
|
Title("Servant of Evil (悪ノ召使) feat. Kagamine Rin - mothy / AkunoP").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/Servant%20of%20Evil%20with%20English%20Sub%20-%20%E6%82%AA%E3%83%8E%E5%8F%AC%E4%BD%BF%20-%20Kagamine%20Len%20-%20HQ.m4a").
|
||||||
|
ID("servant-of-evil")
|
||||||
|
f.songs["im-glad-youre-evil-too"] = newSong().
|
||||||
|
Title("I'm glad you're evil too (feat. Hatsune Miku) - PinocchioP").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/%E3%83%94%E3%83%8E%E3%82%AD%E3%82%AA%E3%83%94%E3%83%BC%20-%20%E3%81%8D%E3%81%BF%E3%82%82%E6%82%AA%E3%81%84%E4%BA%BA%E3%81%A7%E3%82%88%E3%81%8B%E3%81%A3%E3%81%9F%20feat.%20%E5%88%9D%E9%9F%B3%E3%83%9F%E3%82%AF%20_%20I%27m%20glad%20you%27re%20evil%20too.m4a").
|
||||||
|
ID("im-glad-youre-evil-too")
|
||||||
|
f.songs["tokusya-seizon"] = newSong().
|
||||||
|
Title("Tokusya-Seizon Wonder-la-der!! - Amane Kanata").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/Tokusya-Seizon%20Wonder-la-der%21%21.mp3").
|
||||||
|
ID("tokusya-seizon")
|
||||||
|
f.songs["kegarenaki-barajuuji"] = newSong().
|
||||||
|
Title("Kegarenaki Barajuuji - Ariabl'eyeS").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/kegarenaki-barajuuji.mp3").
|
||||||
|
ID("kegarenaki-barajuuji")
|
||||||
|
f.songs["error-towa"] = newSong().
|
||||||
|
Title("-ERROR (Cover) - Tokoyami Towa").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/error-towa.mp3").
|
||||||
|
ID("error-towa")
|
||||||
|
f.songs["diamond-city-lights"] = newSong().
|
||||||
|
Title("Diamond City Lights - LazuLight").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/diamond-city-lights-lazulight.opus").
|
||||||
|
ID("diamond-city-lights")
|
||||||
|
f.songs["tsunami-finana"] = newSong().
|
||||||
|
Title("TSUNAMI - Finana Ryugu").
|
||||||
|
URL("https://music-website.s3.nl-ams.scw.cloud/tsunami-finana.opus").
|
||||||
|
ID("tsunami-finana")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action handlers
|
||||||
|
|
||||||
|
// Call with a value called "title" to switch to the right song
|
||||||
|
func (f *musicPlayer) handleSwitchSong(ctx app.Context, a app.Action) {
|
||||||
|
title, ok := a.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
app.Log("Error calling handleSwitchSong function. Title value was not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v, ok := f.songs[title]
|
||||||
|
if !ok {
|
||||||
|
app.Log("Error getting song. Song with title does not exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.currentlySelectedSong = v.IID
|
||||||
|
f.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *musicPlayer) Render() app.UI {
|
||||||
|
// Don't forget to handle the possibility of no songs having been added and the currentlySelectedSong to be empty
|
||||||
|
if f.currentlySelectedSong == "" {
|
||||||
|
return app.Div().
|
||||||
|
Body(
|
||||||
|
app.Range(f.songs).Map(func(s string) app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Style("border", "solid 1px red").
|
||||||
|
Style("width", "fit-content").
|
||||||
|
Body(
|
||||||
|
app.Span().
|
||||||
|
Style("text-decoration", "underline").
|
||||||
|
Style("width", "fit-content").
|
||||||
|
Class("finger-hover").
|
||||||
|
Text(f.songs[s].ITitle).
|
||||||
|
OnClick(func(ctx app.Context, e app.Event) {
|
||||||
|
ctx.NewActionWithValue("switchSong", f.songs[s].IID)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return app.Div().Body(
|
||||||
|
app.P().
|
||||||
|
Class("p-h3").
|
||||||
|
Text(fmt.Sprintf("Currently playing: %s", f.songs[f.currentlySelectedSong].ITitle)),
|
||||||
|
app.Audio().
|
||||||
|
Src(f.songs[f.currentlySelectedSong].IURL).
|
||||||
|
Controls(true).
|
||||||
|
AutoPlay(true),
|
||||||
|
// Lots of buttons of songs
|
||||||
|
app.Div().
|
||||||
|
Body(
|
||||||
|
app.Range(f.songs).Map(func(s string) app.UI {
|
||||||
|
return app.Div().
|
||||||
|
Style("border", "solid 1px red").
|
||||||
|
Style("width", "fit-content").
|
||||||
|
Body(
|
||||||
|
app.Span().
|
||||||
|
Style("text-decoration", "underline").
|
||||||
|
Style("width", "fit-content").
|
||||||
|
Class("finger-hover").
|
||||||
|
Text(f.songs[s].ITitle).
|
||||||
|
OnClick(func(ctx app.Context, e app.Event) {
|
||||||
|
ctx.NewActionWithValue("switchSong", f.songs[s].IID)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
32
src/navbar.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dutchellie.nl/DutchEllie/proper-website-2/ui"
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type navbar struct {
|
||||||
|
app.Compo
|
||||||
|
updateAvailable bool
|
||||||
|
|
||||||
|
OnClickButton func(page string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *navbar) OnAppUpdate(ctx app.Context) {
|
||||||
|
n.updateAvailable = ctx.AppUpdateAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *navbar) Render() app.UI {
|
||||||
|
return ui.Menu().
|
||||||
|
PaneWidth(250).
|
||||||
|
Menu(
|
||||||
|
newMenu(),
|
||||||
|
).
|
||||||
|
HamburgerMenu(
|
||||||
|
newMenu(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *navbar) onUpdateClick(ctx app.Context, e app.Event) {
|
||||||
|
ctx.Reload()
|
||||||
|
}
|
101
src/page.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
|
||||||
|
// Page is a generic page. By default it has a header, navbar and a default leftbar
|
||||||
|
type page struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Ititle string
|
||||||
|
/*Description
|
||||||
|
Blah blah
|
||||||
|
etc*/
|
||||||
|
|
||||||
|
IbackgroundClass string
|
||||||
|
Ibackground []app.UI
|
||||||
|
IleftBar []app.UI
|
||||||
|
Imain []app.UI
|
||||||
|
|
||||||
|
hideRightContent bool
|
||||||
|
// TODO: Possibly add "updateavailable" here, so it shows up on every page
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPage() *page {
|
||||||
|
return &page{
|
||||||
|
hideRightContent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) Background(v ...app.UI) *page {
|
||||||
|
p.Ibackground = app.FilterUIElems(v...)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) BackgroundClass(t string) *page {
|
||||||
|
p.IbackgroundClass = app.AppendClass(p.IbackgroundClass, t)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) Title(t string) *page {
|
||||||
|
p.Ititle = t
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) LeftBar(v ...app.UI) *page {
|
||||||
|
p.IleftBar = app.FilterUIElems(v...)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) Main(v ...app.UI) *page {
|
||||||
|
p.Imain = app.FilterUIElems(v...)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) OnMount(ctx app.Context) {
|
||||||
|
ctx.Handle("right-hide", p.hideRight)
|
||||||
|
ctx.Handle("right-show", p.showRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) Render() app.UI {
|
||||||
|
if p.IbackgroundClass == "" {
|
||||||
|
p.IbackgroundClass = app.AppendClass(p.IbackgroundClass, "background")
|
||||||
|
}
|
||||||
|
return app.Div().
|
||||||
|
Class("main").
|
||||||
|
Body(
|
||||||
|
// Header and navbar
|
||||||
|
&header{},
|
||||||
|
app.Div().
|
||||||
|
Class("left").
|
||||||
|
Body(
|
||||||
|
&navbar{},
|
||||||
|
app.Range(p.IleftBar).Slice(func(i int) app.UI {
|
||||||
|
return p.IleftBar[i]
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Style("display", visible(p.hideRightContent)).
|
||||||
|
Class("right").
|
||||||
|
ID("right").
|
||||||
|
Body(
|
||||||
|
app.Range(p.Imain).Slice(func(i int) app.UI {
|
||||||
|
return p.Imain[i]
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func visible(v bool) string {
|
||||||
|
if v {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
return "block"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) hideRight(ctx app.Context, a app.Action) {
|
||||||
|
p.hideRightContent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) showRight(ctx app.Context, a app.Action) {
|
||||||
|
p.hideRightContent = false
|
||||||
|
}
|
32
src/undertale.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
|
||||||
|
type UndertalePage struct {
|
||||||
|
app.Compo
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Autoplay Megalovania
|
||||||
|
|
||||||
|
func NewUndertalePage() *UndertalePage {
|
||||||
|
return &UndertalePage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UndertalePage) Render() app.UI {
|
||||||
|
return newPage().
|
||||||
|
Title("Undertale").
|
||||||
|
BackgroundClass("undertale-bg").
|
||||||
|
Background().
|
||||||
|
LeftBar(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("left").
|
||||||
|
Class("leftbarblock a").
|
||||||
|
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||||
|
).
|
||||||
|
Main(
|
||||||
|
newHTMLBlock().
|
||||||
|
Class("right").
|
||||||
|
Class("contentblock").
|
||||||
|
Src("/web/blocks/pages/undertale.html"),
|
||||||
|
)
|
||||||
|
}
|
49
test-website/base.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="../web/static/style.css">
|
||||||
|
<link rel="stylesheet" href="../web/static/form.css">
|
||||||
|
<link rel="stylesheet" href="static/anisha.css?family=anisha">
|
||||||
|
<link rel="stylesheet" href="static/adreena.css?family=adreena">
|
||||||
|
<link rel="stylesheet" href="static/havakana.css?family=havakana">
|
||||||
|
<title>Index</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="block right content">
|
||||||
|
<form action="" class="guestbook-form">
|
||||||
|
<div class="input-groups">
|
||||||
|
<div class="fr">
|
||||||
|
<div class="input-group input-group-name">
|
||||||
|
<label for="name">Name:</label>
|
||||||
|
<input type="text" name="name" class="input">
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-email">
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="text" name="email" class="input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-website">
|
||||||
|
<label for="website">Website:</label>
|
||||||
|
<input type="text" name="website" class="input">
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-message">
|
||||||
|
<label for="message">Message:</label>
|
||||||
|
<textarea name="message" cols="30" rows="5" class="input"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="submit-field">
|
||||||
|
<input type="submit" value="Send!">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<!-- After you're done, find a way to credit the graphics creators!
|
||||||
|
https://dokodemo.neocities.org/materials/index.html BACKGROUND
|
||||||
|
https://dokodemo.neocities.org/
|
||||||
|
https://neo-neighborhoods.neocities.org/SiliconValley/6603/ COOL SHIT
|
||||||
|
https://fontcity.neocities.org/ FOR THE FONT-->
|
34
test-website/friends.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="static/style.css">
|
||||||
|
<link rel="stylesheet" href="static/anisha.css?family=anisha">
|
||||||
|
<title>Index</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
Internetica Galactica
|
||||||
|
</div>
|
||||||
|
<div class="main">
|
||||||
|
<div class="navbar">
|
||||||
|
<ul>
|
||||||
|
<li><a href="base.html">Home</a></li>
|
||||||
|
<li><a href="friends.html">Friends</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
Dit is eigenlijk 1 van die dingen die steeds kan veranderen
|
||||||
|
Deze doet dat
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<!-- After you're done, find a way to credit the graphics creators!
|
||||||
|
https://dokodemo.neocities.org/materials/index.html BACKGROUND
|
||||||
|
https://dokodemo.neocities.org/
|
||||||
|
https://neo-neighborhoods.neocities.org/SiliconValley/6603/ COOL SHIT
|
||||||
|
https://fontcity.neocities.org/ FOR THE FONT-->
|
1
test-website/static/adreena.css
Normal file
1
test-website/static/anisha.css
Normal file
1
test-website/static/barracuda.css
Normal file
1
test-website/static/havakana.css
Normal file
BIN
test-website/static/images/008.gif
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
test-website/static/images/background_star.gif
Normal file
After Width: | Height: | Size: 3.7 KiB |
1
test-website/static/moon.css
Normal file
1
test-website/static/nikoleta.css
Normal file
73
test-website/static/style.css
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
html {
|
||||||
|
background-image: url(images/background_star.gif);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 900px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
color:aliceblue;
|
||||||
|
font-family: havakana;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
border: 3px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: rgb(252, 230, 255);
|
||||||
|
background-color: rgb(54, 39, 48);
|
||||||
|
font-size: 5em;
|
||||||
|
font-family: anisha;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
border: 3px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: rgb(252, 230, 255);
|
||||||
|
background-color: rgb(54, 39, 48);
|
||||||
|
position: relative;
|
||||||
|
float:left;
|
||||||
|
width: 250px;
|
||||||
|
text-decoration: none;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar a, a:link, a:visited{
|
||||||
|
text-decoration: none;
|
||||||
|
color:rgb(252, 230, 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
border: 3px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: rgb(252, 230, 255);
|
||||||
|
background-color: rgb(54, 39, 48);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
float:right;
|
||||||
|
width: 614px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content p {
|
||||||
|
width: 80%;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-h1 {
|
||||||
|
font-family: anisha;
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-h2 {
|
||||||
|
font-family: adreena;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
176
ui/menu.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IMenu interface {
|
||||||
|
app.UI
|
||||||
|
|
||||||
|
ID(v string) IMenu
|
||||||
|
Class(v string) IMenu
|
||||||
|
PaneWidth(px int) IMenu
|
||||||
|
HamburgerButton(v app.UI) IMenu
|
||||||
|
HamburgerMenu(v ...app.UI) IMenu
|
||||||
|
Menu(v ...app.UI) IMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
func Menu() IMenu {
|
||||||
|
return &menu{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type menu struct {
|
||||||
|
app.Compo
|
||||||
|
|
||||||
|
Iid string
|
||||||
|
Iclass string
|
||||||
|
Ipanewidth int
|
||||||
|
IhamburgerButton app.UI
|
||||||
|
IhamburgerMenu []app.UI
|
||||||
|
Imenu []app.UI
|
||||||
|
|
||||||
|
hideMenu bool
|
||||||
|
showHamburgerMenu bool
|
||||||
|
width int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) ID(v string) IMenu {
|
||||||
|
m.Iid = v
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) Class(v string) IMenu {
|
||||||
|
m.Iclass = app.AppendClass(m.Iclass, v)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) PaneWidth(px int) IMenu {
|
||||||
|
if px > 0 {
|
||||||
|
m.Ipanewidth = px
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) HamburgerButton(v app.UI) IMenu {
|
||||||
|
b := app.FilterUIElems(v)
|
||||||
|
if len(b) != 0 {
|
||||||
|
m.IhamburgerButton = b[0]
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) HamburgerMenu(v ...app.UI) IMenu {
|
||||||
|
m.IhamburgerMenu = app.FilterUIElems(v...)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) Menu(v ...app.UI) IMenu {
|
||||||
|
m.Imenu = app.FilterUIElems(v...)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) OnPreRender(ctx app.Context) {
|
||||||
|
m.refresh(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) OnMount(ctx app.Context) {
|
||||||
|
m.refresh(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) OnResize(ctx app.Context) {
|
||||||
|
m.refresh(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) OnUpdate(ctx app.Context) {
|
||||||
|
m.refresh(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) Render() app.UI {
|
||||||
|
visible := func(v bool) string {
|
||||||
|
if v {
|
||||||
|
return "block"
|
||||||
|
}
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Div().
|
||||||
|
ID(m.Iid).
|
||||||
|
Class(m.Iclass).
|
||||||
|
Style("height", "100%").
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
//Style("display", "flex").
|
||||||
|
Style("display", visible(!m.hideMenu)).
|
||||||
|
Style("width", "100%").
|
||||||
|
Style("height", "100%").
|
||||||
|
Style("overflow", "hidden").
|
||||||
|
Body(
|
||||||
|
app.Div().
|
||||||
|
Style("position", "relative").
|
||||||
|
Style("display", visible(!m.hideMenu)).
|
||||||
|
Style("flex-shrink", "0").
|
||||||
|
Style("flex-basis", pxToString(m.Ipanewidth)).
|
||||||
|
Style("overflow", "hidden").
|
||||||
|
Body(m.Imenu...),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Style("display", visible(m.hideMenu && len(m.IhamburgerMenu) != 0)).
|
||||||
|
Style("position", "absolute").
|
||||||
|
Style("top", "0").
|
||||||
|
Style("left", "0").
|
||||||
|
Style("cursor", "pointer").
|
||||||
|
OnClick(m.onHamburgerButtonClick).
|
||||||
|
Body(
|
||||||
|
app.If(m.IhamburgerButton == nil,
|
||||||
|
app.Div().
|
||||||
|
Class("goapp-shell-hamburger-button-default").
|
||||||
|
Text("☰"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
app.Div().
|
||||||
|
Style("display", visible(m.hideMenu && m.showHamburgerMenu)).
|
||||||
|
Style("position", "relative").
|
||||||
|
// Style("top", "0").
|
||||||
|
// Style("left", "0").
|
||||||
|
// Style("right", "0").
|
||||||
|
Style("width", "100%").
|
||||||
|
Style("height", "100%").
|
||||||
|
Style("overflow", "hidden").
|
||||||
|
OnClick(m.hideHamburgerMenu).
|
||||||
|
Body(m.IhamburgerMenu...),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) refresh(ctx app.Context) {
|
||||||
|
w, _ := app.Window().Size()
|
||||||
|
hideMenu := true
|
||||||
|
if w >= 914 {
|
||||||
|
hideMenu = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if hideMenu != m.hideMenu ||
|
||||||
|
w != m.width {
|
||||||
|
m.hideMenu = hideMenu
|
||||||
|
m.width = w
|
||||||
|
|
||||||
|
ctx.Defer(func(app.Context) {
|
||||||
|
m.ResizeContent()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pxToString(px int) string {
|
||||||
|
return strconv.Itoa(px) + "px"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) onHamburgerButtonClick(ctx app.Context, e app.Event) {
|
||||||
|
m.showHamburgerMenu = true
|
||||||
|
ctx.NewAction("right-hide")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *menu) hideHamburgerMenu(ctx app.Context, e app.Event) {
|
||||||
|
m.showHamburgerMenu = false
|
||||||
|
ctx.NewAction("right-show")
|
||||||
|
}
|
6
web/blocks/blogposts/testpost1.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<p class="p-h1">Test blogpost</p>
|
||||||
|
<p>This is some text under the title</p>
|
||||||
|
<p class="p-h2">Header 2</p>
|
||||||
|
<p class="p-h3">Header 3</p>
|
||||||
|
<p>Regular text here</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus, omnis nesciunt beatae dolorem laudantium quidem? Eos fugiat doloribus, ipsam, suscipit repudiandae culpa laboriosam accusamus alias atque esse rerum, tenetur illum.</p>
|
22
web/blocks/pages/about.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<img src="/web/static/images/rin-1.gif" style="width:100px;position:absolute;top:10px;right:10px;">
|
||||||
|
<p class="content-text">I am a 21 year old computer science student (they/them, he/him, she/her), living and studying in
|
||||||
|
The Netherlands. I like Docker, Kubernetes and Golang!
|
||||||
|
<br>
|
||||||
|
I made this website because I was inspired again by the amazing Neocities pages that I discovered because of my
|
||||||
|
friends.
|
||||||
|
They also have their own pages (you can find them on the Galaxies page, do check them out!) and I just had to get a
|
||||||
|
good website of my own!
|
||||||
|
<br>
|
||||||
|
I am not that great at web development, especially design, but I love trying it regardless!
|
||||||
|
<br><br>
|
||||||
|
To say a bit more about me personally, I love all things computers. From servers to embedded devices! I love the
|
||||||
|
cloud and all that it brings
|
||||||
|
(except for big megacorps, but alright) and it's my goal to work for a big cloud company!
|
||||||
|
<br>
|
||||||
|
Aside from career path ambitions,ボーカロイドはすきです! I love vocaloid and other Japanese music and culture!!
|
||||||
|
I also like Vtubers, especially from Hololive and it's my goal to one day finally understand them in their native
|
||||||
|
language!
|
||||||
|
<br><br>
|
||||||
|
There is a lot more to say in words, but who cares about those! Have a look around my creative digital oasis and see
|
||||||
|
what crazy stuff you can find!
|
||||||
|
</p>
|
65
web/blocks/pages/galaxies.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<p class="p-h1">
|
||||||
|
Galaxies
|
||||||
|
</p>
|
||||||
|
<p class="content-text">
|
||||||
|
Here you can find some really really really cool pages that I found on the internet.
|
||||||
|
Some of these are blogs or even blogposts I found, but the ones on top are special!
|
||||||
|
They're the websites of friends of mine! Please visit them, because they worked really hard
|
||||||
|
on their websites as well!
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<p class="p-h2 mt-20 mb-10 bold">My friends!</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div><a href="https://forestofunix.xyz" class="p-h3 m-t5">Forest of Unix</a>
|
||||||
|
<p class="m-t5">A website by Sebastiaan. A massive Linux fanboy, runs Gentoo on his
|
||||||
|
ThinkPad. Absolutely based.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div><a href="https://nymphali.neocities.org" class="p-h3 m-t5">Nymphali</a>
|
||||||
|
<p class="m-t5">The website made by ■■■■■■, whoops Nymphali. They have an awesome
|
||||||
|
minimalist website that's just lovely.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div><a class="p-h3 m-t5" href="https://kristypixel.neocities.org">Kristy</a>
|
||||||
|
<p class="m-t5">Website made by Kristy. Very cute website, I love it! Keep up the
|
||||||
|
awesome work!</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="p-h2 mt-20 mb-10 bold">Neat webspaces</p>
|
||||||
|
<p class="m-t5" style="margin-left:10px;">Just very neat websites I found. Not necessarily by people I know.
|
||||||
|
I just thought it would be nice to share them here!</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div><a href="https://evillious.ylimegirl.com/" class="p-h3 m-t5">Evillious Chronicles fan guide</a>
|
||||||
|
<p class="m-t5">A VERY cool website made by Ylimegirl! They wrote a whole
|
||||||
|
website dedicated to Evillious Chronicles, which is a super
|
||||||
|
good Japanese light novel and vocaloid series!! Definitely look it up!</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<a href="https://www.gnu.org/" class="p-h3 m-t5">The GNU Project</a>
|
||||||
|
<div style="display:flex; gap:5px;">
|
||||||
|
<div style="flex:70%">
|
||||||
|
<p class="m-t5">
|
||||||
|
The official website of the GNU project.
|
||||||
|
They advocate for free/libre software.
|
||||||
|
This is not to be confused with 'open source' software.
|
||||||
|
I highly recommend you read about them and their efforts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="flex:30%">
|
||||||
|
<img src="/web/static/images/gnu-head-sm.png" alt="GNU" width="129" height="122" >
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
28
web/blocks/pages/intro.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<p class="p-h1">Welcome, internet surfer!</p>
|
||||||
|
<div style="position:absolute; top:10px; right:5px;">
|
||||||
|
<p class="small">Please sign my guestbook</p>
|
||||||
|
<img src="/web/static/images/email3.gif" alt="" style="width:40px; position:absolute; bottom:0px; right:0px;">
|
||||||
|
</div>
|
||||||
|
<img src="/web/static/images/rin-len1.webp" alt="" height="230" style="float:right; margin-bottom: 10px;">
|
||||||
|
<p class="content-text">
|
||||||
|
Welcome to my webspace! Whether you stumbled across this page by accident
|
||||||
|
or were linked here, you're more than welcome! This is my personal project that I like
|
||||||
|
to work on! I was inspired by a couple friends of mine, please do check their webspaces
|
||||||
|
out as well under "Galaxies" on the left side there!
|
||||||
|
If you like this page, there is a lot more, so have a look around! You can also leave a
|
||||||
|
nice message for me in the guestbook! There is no registration (unlike the rest of the "modern"
|
||||||
|
internet) so nothing of that sort!
|
||||||
|
That said, this website is my creative outlet and a way to introduce myself, so be kind please!
|
||||||
|
Also its code is entirely open-source and can be found
|
||||||
|
<a href="https://dutchellie.nl/DutchEllie/proper-website-2">on my personal Gitea instance</a> so if you like that
|
||||||
|
sort
|
||||||
|
of stuff, be my guest it's cool!
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p class="content-text">
|
||||||
|
<img src="/web/static/images/rin-2.gif" alt="Kagamine Rin drawing" style="float:left; width:100px; margin-right: 20px;">
|
||||||
|
There is a lot of stuff I want to add to this website! In fact, there is also a "staging" website, which might
|
||||||
|
contain
|
||||||
|
new features! It can be found at <a href="https://staging.quenten.nl">staging.quenten.nl</a>.
|
||||||
|
Don't worry about the invalid SSL certificate, that's normal!
|
||||||
|
</p>
|
1
web/blocks/pages/undertale.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>Test</p>
|
8
web/blocks/snippets/bannerpanel.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<div style="display: flex; gap:5px; padding:5px" >
|
||||||
|
<div style="flex:50%;">
|
||||||
|
<img src="/web/static/images/kanata-1.gif" alt="Amane Kanata excited!!" style="width:100%" >
|
||||||
|
</div>
|
||||||
|
<div style="flex:50%;">
|
||||||
|
<img src="/web/static/images/kanata-1.gif" alt="Amane Kanata excited!!" style="width:100%" >
|
||||||
|
</div>
|
||||||
|
</div>
|
0
web/blocks/snippets/test.html
Normal file
1
web/static/adreena.css
Normal file
1
web/static/anisha.css
Normal file
46
web/static/form.css
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
.input-groups {
|
||||||
|
display:flex;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-name {
|
||||||
|
width: 50%;
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-email {
|
||||||
|
width: 50%;
|
||||||
|
float:right;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-website {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-message {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 16px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-field {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fr {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
1
web/static/havakana.css
Normal file
BIN
web/static/images/background_star.gif
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
web/static/images/email3.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
web/static/images/gnu-head-sm.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
web/static/images/hot1.gif
Normal file
After Width: | Height: | Size: 384 B |
BIN
web/static/images/icon-small.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
web/static/images/icon.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
web/static/images/kanata-1.gif
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
web/static/images/rin-1.gif
Normal file
After Width: | Height: | Size: 471 KiB |
BIN
web/static/images/rin-2.gif
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
web/static/images/rin-len1.webp
Normal file
After Width: | Height: | Size: 192 KiB |
BIN
web/static/images/ut-bg.webp
Normal file
After Width: | Height: | Size: 45 KiB |
1
web/static/nikoleta.css
Normal file
14
web/static/pages/aboutpage.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<p>I am a 21 year old computer science student, living and studying in The Netherlands. I like Docker, Kubernetes and Golang!
|
||||||
|
<br>
|
||||||
|
I made this website because I was inspired again by the amazing Neocities pages that I discovered because of my friends.
|
||||||
|
They also have their own pages (you can find them on the friends tab, do check them out!) and I just had to get a good website of my own!
|
||||||
|
<br>
|
||||||
|
I am not that great at web development, especially design, but I love trying it regardless!
|
||||||
|
<br><br>
|
||||||
|
To say a bit more about me personally, I love all things computers. From servers to embedded devices! I love the cloud and all that it brings
|
||||||
|
(except for big megacorps, but alright) and it's my goal to work for a big cloud company!
|
||||||
|
<br>
|
||||||
|
Aside from career path ambitions, ボーカロイドはすきです! I love vocaloid and other Japanese music and culture!!
|
||||||
|
I also like Vtubers, especially from Hololive and it's my goal to one day finally understand them in their native language!
|
||||||
|
<br><br>
|
||||||
|
There is a lot more to say in words, but who cares about those! Have a look around my creative digital oasis and see what crazy stuff you can find!</p>
|
429
web/static/style.css
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
html {
|
||||||
|
/* overflow-y: scroll; */
|
||||||
|
margin: 0;
|
||||||
|
height: 99%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
height: 99%;
|
||||||
|
background-image: url(images/background_star.gif);
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
background-image: url(images/background_star.gif);
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.undertale-bg {
|
||||||
|
background-image: url(images/ut-bg.webp);
|
||||||
|
width: 100%;
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
border: 3px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: rgb(252, 230, 255);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
background-color: rgb(54, 39, 48);
|
||||||
|
font-size: 5em;
|
||||||
|
font-family: anisha;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-div {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-div:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finger-hover:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-text {
|
||||||
|
align-self: center;
|
||||||
|
flex: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-img {
|
||||||
|
flex: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
margin-top: 5px;
|
||||||
|
width: 900px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
color:aliceblue;
|
||||||
|
font-family: havakana;
|
||||||
|
font-size: 1.1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
text-decoration: none;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar a, a:link, a:visited{
|
||||||
|
text-decoration: none;
|
||||||
|
color:#fce6ff
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
float:left;
|
||||||
|
max-width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
float:right;
|
||||||
|
max-width: 614px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftbar {
|
||||||
|
border: 3px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: rgb(252, 230, 255);
|
||||||
|
background-color: rgb(54, 39, 48);
|
||||||
|
position: relative;
|
||||||
|
float:left;
|
||||||
|
width: 250px;
|
||||||
|
padding: 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
border: 3px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: rgb(252, 230, 255);
|
||||||
|
background-color: rgb(54, 39, 48);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftbarblock-nop {
|
||||||
|
float:left;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftbarblock {
|
||||||
|
float:left;
|
||||||
|
width: 250px;
|
||||||
|
padding: 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentblock {
|
||||||
|
float:right;
|
||||||
|
width: 614px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
border: 3px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: rgb(252, 230, 255);
|
||||||
|
background-color: rgb(54, 39, 48);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
float:right;
|
||||||
|
width: 614px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-border {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-text {
|
||||||
|
max-width: 75%;
|
||||||
|
width: auto;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blogpost-bar {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.blogpost-titles {
|
||||||
|
margin-left: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgb(252, 230, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
.content p {
|
||||||
|
max-width: 80%;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.guestbook-form-div {
|
||||||
|
margin: 0px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
border: 2px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-color: aliceblue;
|
||||||
|
padding: 0px 0px 5px 0px;
|
||||||
|
background-color: darkseagreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-header {
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
background-color: aquamarine;
|
||||||
|
width: 100%;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
div .name {
|
||||||
|
text-align: left;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
div .date {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-right: 10px;
|
||||||
|
text-align: right;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.comment-header p {
|
||||||
|
width: 80%;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.comment-message p{
|
||||||
|
width: 80%;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-message {
|
||||||
|
color:white;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-modal {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgb(0,0,0);
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-modal-content {
|
||||||
|
background-color: #fefefe;
|
||||||
|
color: black;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover,
|
||||||
|
.close:focus {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-frame {
|
||||||
|
width: 290px;
|
||||||
|
height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
list-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
-ms-zoom: 0.75;
|
||||||
|
-moz-transform: scale(0.75);
|
||||||
|
-moz-transform-origin: 0 0;
|
||||||
|
-o-transform: scale(0.75);
|
||||||
|
-o-transform-origin: 0 0;
|
||||||
|
-webkit-transform: scale(0.75);
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-h1 {
|
||||||
|
font-family: anisha;
|
||||||
|
font-size: 3em;
|
||||||
|
margin-top:5px;
|
||||||
|
margin-bottom:5px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-h3 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 0.8em;
|
||||||
|
width: 80%;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-message {
|
||||||
|
font-family: havakana;
|
||||||
|
margin-top: 0px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulsing {
|
||||||
|
animation-name: pulsing;
|
||||||
|
animation-duration: 0.4s;
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
animation-direction: alternate;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulsing {
|
||||||
|
0% {
|
||||||
|
transform: scale(.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*Margin top 5px*/
|
||||||
|
.m-t5 {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Margin top-bottom 10px*/
|
||||||
|
.m-tb10 {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-20 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-10 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fit {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 914px) {
|
||||||
|
.header {
|
||||||
|
font-size: 10vw;
|
||||||
|
height: fit-content;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftbarblock {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
z-index: 1;
|
||||||
|
max-width: unset;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
height: 100%;
|
||||||
|
display: flexbox;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar ul {
|
||||||
|
padding-inline-end: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuitem {
|
||||||
|
padding: 3.5mm 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
min-width: 65px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border: 0px none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #4974a5;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuitem-text {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuitem-link {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuitem-link a {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|