Compare commits

...

130 Commits

Author SHA1 Message Date
7baa6a3b96 More music lol
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-22 21:58:51 +02:00
f3174c5cd6 Messed up desktop 2022-06-22 13:49:43 +02:00
7e4555052a MOBILE 2022-06-22 13:45:43 +02:00
c1dc42d4e1 Fix mobile kinda 2022-06-22 12:39:36 +02:00
3c6e75b6c0 Add hamburger 2022-06-20 13:33:22 +02:00
b2c9128ceb New tags good 2022-06-20 11:19:15 +02:00
04cdb98158 New tags 2022-06-20 11:17:16 +02:00
cfdc3df103 Mobile scaling 2022-06-20 11:02:53 +02:00
396d8498d0 Okay, I think I know 2022-06-08 20:50:31 +02:00
d35c93c7aa Finally... Music link fingering 😉 2022-06-05 10:12:18 +02:00
23f74ff054 Add update button 2022-05-31 10:22:29 +02:00
e2447b29c8 Merge branch 'main' of dutchellie.nl:DutchEllie/proper-website-2 2022-05-31 09:48:21 +02:00
3a33c86a35 Nah nvm 2022-05-27 21:30:49 +02:00
0fc2c8da79 Possibly maybe.... 2022-05-27 20:51:53 +02:00
bc3775f77d Possibly maybe.... 2022-05-27 20:50:12 +02:00
3e22083182 Made generating static site possible 2022-05-27 20:35:53 +02:00
9a77843747 Go mod tidy 2022-05-23 14:40:48 +02:00
7bd93049f6 Idk what went wrong? 2022-05-23 14:40:22 +02:00
00e88a66aa Added very cool songs 2022-05-14 22:24:14 +02:00
ba87778c72 Disabled CGO 2022-05-14 21:45:04 +02:00
777e9684d8 Update the production pipeline too 2022-05-14 21:22:11 +02:00
8886447348 Forgot the volumes 2022-05-14 21:12:04 +02:00
23fac0acc0 Dependency issue 2022-05-14 21:10:44 +02:00
3bd67749e2 CI update 2022-05-14 21:09:48 +02:00
c8b1b75567 Revert APIURL changes, it's impossible 2022-05-14 20:45:28 +02:00
e2fff3a30d Oh right, after browser 2022-05-14 18:31:43 +02:00
d5e553852b Come on CI 2022-05-14 18:25:45 +02:00
082487fab7 Moved API URL to environment 2022-05-14 18:24:19 +02:00
3a50e68071 [SKIP CI] Readme.md
Signed-off-by: Quenten <personal@quenten.nl>
2022-05-10 08:43:28 +00:00
fe49235647 Updated Badge [SKIP CI] 2022-05-08 19:29:17 +00:00
257b6b1447 Modified API url 2022-05-08 17:11:45 +02:00
06ca188aed Added Kyu's Left on Red 2022-05-01 17:39:01 +02:00
fb778f78af Added ERROR to cache 2022-04-25 14:29:37 +02:00
74e93c509f Added error by towa 2022-04-25 14:27:50 +02:00
cb30f18461 Added a new song 2022-04-20 18:07:52 +02:00
073f0fe61e Changed domain in intro page 2022-04-20 15:25:47 +02:00
bde68d9369 Changed prod domain 2022-04-20 15:23:54 +02:00
c442d26e91 Moved staging domain 2022-04-20 14:52:20 +02:00
66c1e96d22 Added frame options 2022-04-20 14:45:04 +02:00
f1e36861d1 Fixed values 2022-04-20 14:14:52 +02:00
df1582b143 Added CSP 2022-04-20 14:03:46 +02:00
a45c99532b Removed debug messages 2022-04-19 23:06:27 +02:00
391838c98e Added caching song for expensive S3 storage 2022-04-19 22:51:24 +02:00
786b20e9df Added music player beta 2022-04-19 22:30:47 +02:00
e97bec2273 Added sass 2022-04-18 22:20:36 +02:00
dbe87450f2 Added textwithtooltip component 2022-04-18 22:02:51 +02:00
5bd7e28690 Added status check 2022-04-18 16:02:41 +02:00
f5722220ae Updated cache 2022-04-18 13:51:17 +02:00
2bee714197 Added GNU and rewrote page 2022-04-18 13:49:16 +02:00
704e877af0 Hidden blog nav for now 2022-03-27 16:50:48 +02:00
32b762be3c Intermediate loading of comments 2022-03-27 16:39:38 +02:00
b4ee791a29 Added efficient comment loading 2022-03-27 14:06:21 +02:00
69c6c50ccc Guestbook loading still broken 2022-03-25 14:02:14 +01:00
c66166b6ae Blogposts are a thing now 2022-03-25 13:51:25 +01:00
4a514b3a50 Change compression level 2022-03-24 15:55:18 +01:00
0554dab12c Fixed bg 2022-03-24 14:50:36 +01:00
6eaaeba81d Compressing handlergit add . 2022-03-24 14:31:02 +01:00
6cefb70b31 Fixed the API url in makefile [CI SKIP] 2022-03-24 13:36:44 +01:00
410f2e71ae Fixed EOF error 2022-03-24 13:28:28 +01:00
a4794dd38c Started working on blogposts 2022-03-24 13:25:07 +01:00
59032499d1 Added entity 2022-03-24 13:12:57 +01:00
c8078e8829 eeeeh 2022-03-18 22:45:29 +01:00
a89f289bcb Caching 2022-03-16 13:13:14 +01:00
739bb7b180 Fixed some layout 2022-03-16 12:49:52 +01:00
1610888f4a Wait noo 2022-03-15 19:38:46 +01:00
ace455645d God I hate CSS 2022-03-15 19:37:46 +01:00
71b152db4b Fixed 2022-03-15 17:44:29 +01:00
011564a200 This would be really cool 2022-03-15 17:25:42 +01:00
6ffe308bde Todo 2022-03-15 16:47:49 +01:00
103721fc9b Some aligning 2022-03-15 16:44:32 +01:00
cc7c2b9b87 Rewrote guestbook 2022-03-15 16:42:26 +01:00
9867712165 Updater 2022-03-15 14:12:15 +01:00
3656b3dcab Layout and refactoring 2022-03-15 13:22:59 +01:00
43af5b5686 Moved pages 2022-03-15 13:06:52 +01:00
8b994ce249 More unused code 2022-03-15 12:57:12 +01:00
550ae2e35c Removed unused code 2022-03-15 12:56:08 +01:00
6287f6aab4 Major refactor 2022-03-15 12:48:47 +01:00
14e1f7d9a4 App notes 2022-03-15 09:00:19 +01:00
ffe13d2c6d Added pronouns 2022-03-14 21:40:38 +01:00
3a8266a3b6 Added Evillious guide website 2022-03-14 21:37:22 +01:00
4e4633ab1a omg this layout is so goood 2022-03-14 21:10:10 +01:00
917b614136 Fixed css failure 2022-03-14 21:05:03 +01:00
dfc2d60853 New frontpage design 2022-03-14 21:04:30 +01:00
f78a8b1619 Added Kristy 2022-03-14 09:17:20 +01:00
138ad3cd2e Changed friends to galaxies and fixed CSS 2022-03-13 21:34:54 +01:00
face13b207 Testing and normal API 2022-03-13 20:18:07 +01:00
6d3a99c63c I am dumb 2022-03-13 19:37:32 +01:00
85f773d39d Separate staging and production image 2022-03-13 19:34:20 +01:00
d4c1e991d0 Fixed dumb mistake 2022-03-13 18:22:47 +01:00
84326d430a Fixed Go module 2022-03-13 16:56:18 +01:00
885fedc8fb Changed module name 2022-03-13 16:35:31 +01:00
3782570338 Limited message size on client side 2022-03-13 16:17:27 +01:00
bb3f71d15e Added icon and changed name 2022-03-13 15:27:47 +01:00
00cea89b53 I am a massive idiot 2022-03-12 21:41:02 +01:00
85c02f44a9 Remove docker build step from promote 2022-03-12 21:37:05 +01:00
5d6be689d2 Added SHA tags 2022-03-12 21:32:02 +01:00
5fca652767 Removed emphasis 2022-03-12 21:19:17 +01:00
66731d270b Added production 2022-03-12 20:43:21 +01:00
6ba92558c5 Deploy now depends on docker 2022-03-12 20:28:47 +01:00
8c3a1884ae Changed fou link 2022-03-12 20:26:36 +01:00
d2c19667fc Another templating issue 2022-03-12 19:57:43 +01:00
d94f5119f7 Fixed domain 2022-03-12 19:51:40 +01:00
385c72fbdd Removed dry run 2022-03-12 19:45:57 +01:00
f4780c5b9c Alright, another templating issue 2022-03-12 19:40:30 +01:00
259d97940d Templating broken 2022-03-12 19:31:01 +01:00
9c0a5812f1 Added chart specs 2022-03-12 19:27:58 +01:00
ab15488f91 Moved values 2022-03-12 19:26:04 +01:00
b197dc8c37 Let's try this 2022-03-12 19:18:04 +01:00
3eb6393b42 Fix namespace 2022-03-12 19:11:21 +01:00
2e53cab03f Skip TLS verify 2022-03-12 19:10:13 +01:00
c87ba03d30 Drone fix 2022-03-12 18:18:01 +01:00
3a0e90c990 Separated pipelines 2022-03-12 18:15:46 +01:00
66920b93a3 Fixed secrets 2022-03-12 18:00:12 +01:00
d864c9f7b4 First test for drone 2022-03-12 17:47:01 +01:00
f91c0eda8a Add cool badge 2022-03-12 16:32:51 +01:00
4606f2719d Fix drone 2022-03-12 16:28:45 +01:00
0df804736b CI/CD 2022-03-12 16:22:41 +01:00
815b896cc0 Last 2022-03-12 15:52:13 +01:00
54477bd12d Okay this is based 2022-03-07 13:05:33 +01:00
21fdf408ca So many things 2022-03-02 13:38:28 +01:00
b93d68b4fa I am getting so good at this 2022-03-01 19:45:23 +01:00
ad9c9b4db7 Laid the groundwork! 2022-03-01 17:07:33 +01:00
7d8ccd3a01 Ohhhh this is great!! 2022-03-01 15:50:53 +01:00
8150fb0afb updated gitignore 2022-03-01 14:51:57 +01:00
28efa0fa62 Updated gitignore 2022-03-01 14:51:45 +01:00
62383d28f9 Hello world from go-app 2022-03-01 14:36:04 +01:00
cc94d487e1 Bye random packages 2022-03-01 14:28:48 +01:00
965fb51598 Okay nevermind 2022-03-01 14:26:39 +01:00
16e16a033b various things 2022-03-01 12:02:05 +01:00
963d9e63f0 Readme edits 2022-03-01 10:04:28 +01:00
76 changed files with 3360 additions and 1 deletions

159
.drone.yml Normal file
View 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: {}

View File

@ -0,0 +1,3 @@
apiVersion: v2
name: newsite
version: v0.0.1

View 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 }}

View 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 }}

View 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

View 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
View 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

View 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
View File

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.vscode
app
web/*.wasm
staticsite

17
Dockerfile Normal file
View 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
View 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

View File

@ -1,3 +1,34 @@
# proper-website-2 # proper-website-2
Truly proper website [![Build Status](https://drone.dutchellie.nl/api/badges/DutchEllie/proper-website-2/status.svg)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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-->

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
View 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")
}

View 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>

View 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>

View 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>

View 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>

View File

@ -0,0 +1 @@
<p>Test</p>

View 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>

View File

1
web/static/adreena.css Normal file

File diff suppressed because one or more lines are too long

1
web/static/anisha.css Normal file

File diff suppressed because one or more lines are too long

46
web/static/form.css Normal file
View 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

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
web/static/images/hot1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
web/static/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
web/static/images/rin-1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

BIN
web/static/images/rin-2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

1
web/static/nikoleta.css Normal file

File diff suppressed because one or more lines are too long

View 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
View 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;
}
}