Compare commits
42 Commits
edge-2022-
...
master
Author | SHA1 | Date | |
---|---|---|---|
227a8a308a | |||
1c0626aaed | |||
0de79f2cf1 | |||
d676d742c8 | |||
759e04dc25 | |||
d41851cfbc | |||
f60e04ada4 | |||
de745fad87 | |||
717f47cbef | |||
f5a850efd5 | |||
42f578d5a3 | |||
d6ebbfcb20 | |||
14e8d1e9e9 | |||
f614f7bfe9 | |||
3ec08ec6d9 | |||
54df3388e3 | |||
b1a6dbe4db | |||
489ec3a7df | |||
814b43e43d | |||
90d43f46b8 | |||
aa82a91a29 | |||
a314ffa2b1 | |||
f79020c95d | |||
fd95d53702 | |||
4c180c0966 | |||
8f3fc74270 | |||
18eb65218b | |||
5bbd9db71d | |||
2477a7ee20 | |||
38e5bad973 | |||
d9e37e4e2e | |||
5d7b39ac15 | |||
7c59cbacea | |||
fbc6b3ed6a | |||
27ae3e3fed | |||
7951002892 | |||
1c1418e78a | |||
5a62535bce | |||
26b7893c71 | |||
2948165acb | |||
dba4022bf4 | |||
e1e78655bd |
@ -1,8 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
name: default
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: golang
|
|
||||||
commands:
|
|
||||||
- make build
|
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -7,3 +7,15 @@ dist/
|
|||||||
# Test data and logs folders
|
# Test data and logs folders
|
||||||
.data/
|
.data/
|
||||||
.idea/dataSources.xml
|
.idea/dataSources.xml
|
||||||
|
|
||||||
|
# CDN link
|
||||||
|
cdn/
|
||||||
|
cdn
|
||||||
|
cdn_/
|
||||||
|
cdn_
|
||||||
|
|
||||||
|
# Config Link
|
||||||
|
cnf/
|
||||||
|
cnf
|
||||||
|
cnf_/
|
||||||
|
cnf_
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="SwUserDefinedSpecifications">
|
|
||||||
<option name="specTypeByUrl">
|
|
||||||
<map />
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
12
.woodpecker/build.yml
Normal file
12
.woodpecker/build.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
format:
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- files=$(gofmt -l .) && echo "$files" && [ -z "$files" ]
|
||||||
|
build:
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- make build
|
||||||
|
|
17
Makefile
17
Makefile
@ -1,6 +1,7 @@
|
|||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
PRODUCT_NAME := wappcityuni
|
PRODUCT_NAME := wappcityuni
|
||||||
BIN := dist/${PRODUCT_NAME}
|
BIN := dist/${PRODUCT_NAME}
|
||||||
|
DNAME := ${PRODUCT_NAME}_
|
||||||
ENTRY_POINT := ./cmd/${PRODUCT_NAME}
|
ENTRY_POINT := ./cmd/${PRODUCT_NAME}
|
||||||
HASH := $(shell git rev-parse --short HEAD)
|
HASH := $(shell git rev-parse --short HEAD)
|
||||||
COMMIT_DATE := $(shell git show -s --format=%ci ${HASH})
|
COMMIT_DATE := $(shell git show -s --format=%ci ${HASH})
|
||||||
@ -11,9 +12,10 @@ COMP_BIN := go
|
|||||||
|
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
BIN := $(BIN).exe
|
BIN := $(BIN).exe
|
||||||
|
DNAME := $(DNAME).exe
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: build dev test clean deploy
|
.PHONY: build dev test clean deploy d
|
||||||
|
|
||||||
build:
|
build:
|
||||||
mkdir -p dist/
|
mkdir -p dist/
|
||||||
@ -34,4 +36,17 @@ clean:
|
|||||||
deploy: build
|
deploy: build
|
||||||
sudo systemctl stop wappcityuni
|
sudo systemctl stop wappcityuni
|
||||||
sudo cp "${BIN}" /usr/local/bin
|
sudo cp "${BIN}" /usr/local/bin
|
||||||
|
sudo cp *.go.html cnf
|
||||||
|
sudo cp *.go.yml cnf
|
||||||
|
sudo cp *.css cdn
|
||||||
|
sudo cp *.js cdn
|
||||||
sudo systemctl start wappcityuni
|
sudo systemctl start wappcityuni
|
||||||
|
|
||||||
|
d: build
|
||||||
|
sudo systemctl stop wappcityuni_
|
||||||
|
sudo cp "${BIN}" "/usr/local/bin/${DNAME}"
|
||||||
|
sudo cp *.go.html cnf_
|
||||||
|
sudo cp *.go.yml cnf_
|
||||||
|
sudo cp *.css cdn_
|
||||||
|
sudo cp *.js cdn_
|
||||||
|
sudo systemctl start wappcityuni_
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
# Captain ALM Cityuni subdomain WebServer
|
# Captain ALM Cityuni subdomain WebServer
|
||||||
|
|
||||||
[![Build Status](https://ci.mrmelon54.xyz/api/badges/alfred/cityuni-webserver/status.svg)](https://ci.mrmelon54.xyz/alfred/cityuni-webserver)
|
[![Build Status](https://ci.mrmelon54.com/api/badges/alfred/cityuni-webserver/status.svg)](https://ci.mrmelon54.com/alfred/cityuni-webserver)
|
||||||
|
|
||||||
This provides my template and cache supporting web / application server for my city university portfolio subdomain.
|
This provides my template and cache supporting web / application server for my city university portfolio subdomain.
|
||||||
|
|
||||||
|
[Production Server](https://cityuni.captainalm.com/)
|
||||||
|
|
||||||
Maintainer:
|
Maintainer:
|
||||||
[Captain ALM](https://code.mrmelon54.xyz/alfred)
|
[Captain ALM](https://code.mrmelon54.com/alfred)
|
||||||
|
|
||||||
License:
|
License:
|
||||||
[BSD 3-Clause](https://code.mrmelon54.xyz/alfred/GOPackageHeaderServer/src/branch/master/LICENSE)
|
[BSD 3-Clause](https://code.mrmelon54.com/alfred/cityuni-webserver/src/branch/master/LICENSE)
|
||||||
|
358
base.css
Normal file
358
base.css
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
This file is (C) Captain ALM
|
||||||
|
Under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
|
||||||
|
*/
|
||||||
|
*{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Times New Roman", Times, serif;
|
||||||
|
}
|
||||||
|
#st{
|
||||||
|
position: absolute;
|
||||||
|
left: -1080px;
|
||||||
|
top: -1080px;
|
||||||
|
font-size: 1em;
|
||||||
|
visibility: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
main{
|
||||||
|
display: block;
|
||||||
|
padding-top: 70px;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
.no-dec{
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.no-lst-style{
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.centered{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.content, .content > *{
|
||||||
|
word-break: break-word;
|
||||||
|
-ms-word-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.content > p, .content > h1, .content > h2, .content > h3, .content > h4, .content > h5, .content > h6{
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.header{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 0 2px;
|
||||||
|
}
|
||||||
|
.home-button, .sort-button{
|
||||||
|
display: inline-block;
|
||||||
|
width: 84px;
|
||||||
|
height: 66px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.sort-button{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.home-button > div, .sort-button > div{
|
||||||
|
display: inline;
|
||||||
|
font-size: 48px;
|
||||||
|
padding: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.nav{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
.vnav{
|
||||||
|
max-height: none;
|
||||||
|
top: 0;
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.so-pane{
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
position: fixed;
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
.so-pane > form > div{
|
||||||
|
display: table-row;
|
||||||
|
background-color: transparent;
|
||||||
|
box-sizing: inherit;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.so-pane > form > div > div, .so-pane > form > div > span{
|
||||||
|
background-color: transparent;
|
||||||
|
box-sizing: inherit;
|
||||||
|
padding: 2px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.so-pane > form > div > div{
|
||||||
|
display: table-cell;
|
||||||
|
max-width: 50%;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.so-pane > form > div > span{
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.so-pane-full{
|
||||||
|
display: table-caption !important;
|
||||||
|
caption-side: bottom;
|
||||||
|
}
|
||||||
|
.so-pane > form > div > * > *{
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 16px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.so-pane > form > div > div > *{
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.so-pane > form > div > div > label{
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.nav-menu, .sort-menu{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.menu a, .vmenu a{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.menu a, .vmenu a, #st{
|
||||||
|
padding: 24px 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.vmenu li{
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.vmenu a:hover{
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.hmb{
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 32px 20px;
|
||||||
|
}
|
||||||
|
.hmb-line{
|
||||||
|
display: block;
|
||||||
|
height: 2px;
|
||||||
|
position: relative;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
.hmb-line::before, .hmb-line::after{
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
transition: all .1s ease-out;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.hmb-line::before{
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
.hmb-line::after{
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
|
.nav-menu:checked ~ #nav, .nav-open{
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
.nav-menu:checked ~ .hmb .hmb-line, .nav-open-hmb .hmb-line{
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.nav-menu:checked ~ .hmb .hmb-line::before, .nav-open-hmb .hmb-line::before{
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
top:0;
|
||||||
|
}
|
||||||
|
.nav-menu:checked ~ .hmb .hmb-line::after, .nav-open-hmb .hmb-line::after{
|
||||||
|
transform: rotate(45deg);
|
||||||
|
top:0;
|
||||||
|
}
|
||||||
|
.sort-menu:checked ~ .so-pane, .so-pane-open{
|
||||||
|
display: block;
|
||||||
|
z-index: 101;
|
||||||
|
box-sizing: content-box;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
top: 70px;
|
||||||
|
left: 20px;
|
||||||
|
max-height: 100%;
|
||||||
|
width: 230px;
|
||||||
|
height: auto;
|
||||||
|
-webkit-border-bottom-left-radius: 8px;
|
||||||
|
-webkit-border-bottom-right-radius: 8px;
|
||||||
|
-moz-border-radius-bottomleft: 8px;
|
||||||
|
-moz-border-radius-bottomright: 8px;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 2px 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.sort-menu:checked ~ .so-pane > form, .so-pane-open > form{
|
||||||
|
display: table;
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
.main-box > div, footer{
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 3px 0;
|
||||||
|
padding: 2px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
.item-table{
|
||||||
|
display: table;
|
||||||
|
box-sizing: content-box;
|
||||||
|
background-color: transparent;
|
||||||
|
-webkit-border-radius: 32px;
|
||||||
|
-moz-border-radius: 32px;
|
||||||
|
border-radius: 32px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
.flex-col{
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -moz-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-moz-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-moz-box-direction: normal;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
-webkit-align-content: center;
|
||||||
|
align-content: center;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.item-table > div{
|
||||||
|
display: table-row;
|
||||||
|
box-sizing: inherit;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.flex-row, .item-table > div{
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -moz-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: horizontal;
|
||||||
|
-moz-box-orient: horizontal;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-moz-box-direction: normal;
|
||||||
|
-webkit-flex-direction: row;
|
||||||
|
-ms-flex-direction: row;
|
||||||
|
flex-direction: row;
|
||||||
|
-ms-flex-wrap: wrap;
|
||||||
|
-webkit-flex-wrap: wrap;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.item-table > div > div > div{
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.item-table-caption, .so-pane-caption{
|
||||||
|
display: table-caption !important;
|
||||||
|
caption-side: bottom;
|
||||||
|
}
|
||||||
|
.item-table-full, .item-table-360, .item-table-caption, .so-pane-caption{
|
||||||
|
background-color: transparent;
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
.item-table-full{
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.item-table-360{
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 360px;
|
||||||
|
width: 360px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.image-box > a{
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
@media (min-width: 600px){
|
||||||
|
.main-box > div{
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
footer{
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.item-table > div{
|
||||||
|
-ms-flex-wrap: nowrap;
|
||||||
|
-webkit-flex-wrap: nowrap;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.item-table-full{
|
||||||
|
max-width: -webkit-calc(100% - 361px);
|
||||||
|
max-width: -moz-calc(100% - 361px);
|
||||||
|
max-width: calc(100% - 361px);
|
||||||
|
width: -webkit-calc(100% - 361px);
|
||||||
|
width: -moz-calc(100% - 361px);
|
||||||
|
width: calc(100% - 361px);
|
||||||
|
}
|
||||||
|
.image-box > a{
|
||||||
|
border-width: 4px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 680px){
|
||||||
|
.nav{
|
||||||
|
max-height: none;
|
||||||
|
top: 0;
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.vnav{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.menu li{
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.menu a:hover{
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.hmb{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
||||||
|
.item-table-full, .item-table-360 {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.item-table-caption{
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
19
conf/page.go
Normal file
19
conf/page.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type PageYaml struct {
|
||||||
|
PageName string `yaml:"pageName"`
|
||||||
|
PagePath string `yaml:"pagePath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (py PageYaml) GetPagePath() string {
|
||||||
|
toReturn := py.PagePath
|
||||||
|
if !strings.HasSuffix(toReturn, ".go") {
|
||||||
|
toReturn += ".go"
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(toReturn, "/") {
|
||||||
|
toReturn = "/" + toReturn
|
||||||
|
}
|
||||||
|
return toReturn
|
||||||
|
}
|
@ -9,10 +9,13 @@ import (
|
|||||||
|
|
||||||
type ServeYaml struct {
|
type ServeYaml struct {
|
||||||
DataStorage string `yaml:"dataStorage"`
|
DataStorage string `yaml:"dataStorage"`
|
||||||
|
TemplateStorage string `yaml:"templateStorage"`
|
||||||
Domains []string `yaml:"domains"`
|
Domains []string `yaml:"domains"`
|
||||||
RangeSupported bool `yaml:"rangeSupported"`
|
RangeSupported bool `yaml:"rangeSupported"`
|
||||||
EnableGoInfoPage bool `yaml:"enableGoInfoPage"`
|
EnableGoInfoPage bool `yaml:"enableGoInfoPage"`
|
||||||
CacheSettings CacheSettingsYaml `yaml:"cacheSettings"`
|
CacheSettings CacheSettingsYaml `yaml:"cacheSettings"`
|
||||||
|
PageSettings []PageYaml `yaml:"pageSettings"`
|
||||||
|
YmlDataFallback bool `yaml:"ymlDataFallback"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sy ServeYaml) GetDomainString() string {
|
func (sy ServeYaml) GetDomainString() string {
|
||||||
@ -39,3 +42,20 @@ func (sy ServeYaml) GetDataStoragePath() string {
|
|||||||
return sy.DataStorage
|
return sy.DataStorage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sy ServeYaml) GetTemplateStoragePath() string {
|
||||||
|
if sy.TemplateStorage == "" || !filepath.IsAbs(sy.TemplateStorage) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
if sy.TemplateStorage == "" {
|
||||||
|
return wd
|
||||||
|
} else {
|
||||||
|
return path.Join(wd, sy.TemplateStorage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return sy.TemplateStorage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,9 +4,13 @@ listen:
|
|||||||
webMethod: "http"
|
webMethod: "http"
|
||||||
identify: true
|
identify: true
|
||||||
serve:
|
serve:
|
||||||
|
dataStorage: ""
|
||||||
|
domains: []
|
||||||
rangeSupported: true
|
rangeSupported: true
|
||||||
enableGoInfoPage: true
|
enableGoInfoPage: true
|
||||||
cacheSettings:
|
cacheSettings:
|
||||||
|
enableTemplateCaching: false
|
||||||
|
enableTemplateCachePurge: false
|
||||||
enableContentsCaching: true
|
enableContentsCaching: true
|
||||||
enableContentsCachePurge: true
|
enableContentsCachePurge: true
|
||||||
maxAge: 3600
|
maxAge: 3600
|
||||||
|
40
dark.css
Normal file
40
dark.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
This file is (C) Captain ALM
|
||||||
|
Under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
|
||||||
|
*/
|
||||||
|
body{
|
||||||
|
color: #f9f9f9;
|
||||||
|
background-color: #050506;
|
||||||
|
border-color: #696969;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: #b0b0f0;
|
||||||
|
}
|
||||||
|
.header, nav, footer, .so-pane{
|
||||||
|
background-color: #1d1d1e;
|
||||||
|
}
|
||||||
|
.home-button > div, .sort-button > div, .menu a, .vmenu a, .so-pane > form > div > * > *{
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
.home-button:hover, .menu a:hover, .vmenu a:hover, .hmb:hover, .sort-button:hover, .sort-menu:checked ~ .sort-button, .sort-button-active, .so-pane > form > div > span > input, .so-pane > form > div > div > select{
|
||||||
|
background-color: #606061;
|
||||||
|
}
|
||||||
|
.hmb-line, .hmb-line::before, .hmb-line::after{
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
.main-box{
|
||||||
|
background-color: #0f0f0f;
|
||||||
|
}
|
||||||
|
.item-table{
|
||||||
|
background-color: #3f3f3f;
|
||||||
|
border-color: #f5deb3;
|
||||||
|
}
|
||||||
|
.so-pane, .header{
|
||||||
|
border-color: #6f6f6f;
|
||||||
|
}
|
||||||
|
.image-box, .item-heading{
|
||||||
|
background-color: #4f4f4f;
|
||||||
|
}
|
||||||
|
.image-box > a{
|
||||||
|
border-color: #b0b0f0;
|
||||||
|
}
|
@ -13,28 +13,25 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: mediumslateblue;
|
background-color: mediumslateblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
table, th, td {
|
table, th, td {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border: black 1px solid;
|
border: black 1px solid;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
-ms-word-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
table, td {
|
table, td {
|
||||||
background-color: lightgray;
|
background-color: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
background-color: lightsteelblue;
|
background-color: lightsteelblue;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
}
|
}
|
||||||
@ -70,6 +67,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Product Description</th>
|
<th>Product Description</th>
|
||||||
<td>{{ .ProductDescription }}</td>
|
<td>{{ .ProductDescription }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Product License</th>
|
||||||
|
<td>BSD 3-Clause License</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Product Location</th>
|
<th>Product Location</th>
|
||||||
@ -149,6 +150,10 @@
|
|||||||
<th>Memory Page Size</th>
|
<th>Memory Page Size</th>
|
||||||
<td>{{ .PageSize }}</td>
|
<td>{{ .PageSize }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>System Time</th>
|
||||||
|
<td>{{ .CurrentTime }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
206
index.go.html
Normal file
206
index.go.html
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
<!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"/>
|
||||||
|
<meta name="description" content="Captain ALM's City University Portfolio">
|
||||||
|
<meta name="keywords" content="CaptainALM Captain_ALM Captain ALM portfolio Alfred Manville projects programming hacking cracking city uni cityuni cuol City University of London mycityuni">
|
||||||
|
<title>City University Portfolio</title>
|
||||||
|
<link rel="stylesheet" href="{{ .Data.CSSBaseURL }}"/>
|
||||||
|
{{ if .Light }}
|
||||||
|
<link id="style-theme" rel="stylesheet" href="{{ .Data.CSSLightURL }}"/>
|
||||||
|
{{ else }}
|
||||||
|
<link id="style-theme" rel="stylesheet" href="{{ .Data.CSSDarkURL }}"/>
|
||||||
|
{{ end }}
|
||||||
|
<script type="application/javascript">
|
||||||
|
var TheParameters = "{{ .Parameters }}"
|
||||||
|
var CssLightURL = "{{ .Data.CSSLightURL }}"
|
||||||
|
var CssDarkURL = "{{ .Data.CSSDarkURL }}"
|
||||||
|
var SunImageURL = "{{ .Data.SunImageLocation }}"
|
||||||
|
var MoonImageURL = "{{ .Data.MoonImageLocation }}"
|
||||||
|
var PlayImageURL = "{{ .Data.PlayVideoImageLocation }}"
|
||||||
|
</script>
|
||||||
|
<script type="application/javascript" src="{{ .Data.JScriptURL }}"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
{{ if .Light }}
|
||||||
|
<a href="?light" class="home-button no-dec" title="Home" id="logo"><div><img src="{{ .Data.LogoImageLocation }}" width="64px" alt="⌂"></div></a>
|
||||||
|
<a href="?{{ .Parameters }}" class="home-button no-dec" title="Switch to Dark Mode" id="theme"><div><img id="theme-img" src="{{ .Data.MoonImageLocation }}" width="64px" alt='{{ "{" }}'></div></a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="?" class="home-button no-dec" title="Home" id="logo"><div><img src="{{ .Data.LogoImageLocation }}" width="64px" alt="⌂"></div></a>
|
||||||
|
{{ if eq .Parameters "" }}
|
||||||
|
<a href="?light" class="home-button no-dec" title="Switch to Light Mode" id="theme"><div><img id="theme-img" src="{{ .Data.SunImageLocation }}" width="64px" alt='()'></div></a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="?light&{{ .Parameters }}" class="home-button no-dec" title="Switch to Light Mode" id="theme"><div><img id="theme-img" src="{{ .Data.SunImageLocation }}" width="64px" alt='()'></div></a>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<input class="sort-menu" type="checkbox" id="sort-menu"/>
|
||||||
|
<label class="sort-button no-dec" for="sort-menu" id="sort-menu-button" title="Order and Sort Options"><div><img src="{{ .Data.SortImageLocation }}" width="64px" alt='↓'></div></label>
|
||||||
|
<div class="so-pane" id="so-pane">
|
||||||
|
<form action="?" method="get" id="so-form">
|
||||||
|
{{ if .Light }}
|
||||||
|
<input id="so-theme" type="hidden" name="light" />
|
||||||
|
{{ end }}
|
||||||
|
<div>
|
||||||
|
{{ $sort := 0 }}
|
||||||
|
<div><label class="no-dec" for="so-order">Order by:</label></div>
|
||||||
|
<div><select name="order" id="so-order">
|
||||||
|
{{ if eq .OrderStartDate 0 }}
|
||||||
|
<option value="start">Start Date</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="start" selected>Start Date</option>
|
||||||
|
{{ $sort = .OrderStartDate }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if eq .OrderEndDate 0 }}
|
||||||
|
<option value="end">End Date</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="end" selected>End Date</option>
|
||||||
|
{{ $sort = .OrderEndDate }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if eq .OrderName 0 }}
|
||||||
|
<option value="name">Name</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="name" selected>Name</option>
|
||||||
|
{{ $sort = .OrderName }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if eq .OrderDuration 0 }}
|
||||||
|
<option value="duration">Duration</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="duration" selected>Duration</option>
|
||||||
|
{{ $sort = .OrderDuration }}
|
||||||
|
{{ end }}
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div><label class="no-dec" for="so-sort">Sort:</label></div>
|
||||||
|
<div><select name="sort" id="so-sort">
|
||||||
|
{{ if gt $sort 0 }}
|
||||||
|
<option value="asc" selected>Ascending</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="asc">Ascending</option>
|
||||||
|
{{ end }}
|
||||||
|
{{ if lt $sort 0 }}
|
||||||
|
<option value="desc" selected>Descending</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="desc">Descending</option>
|
||||||
|
{{ end }}
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
<div class="so-pane-full">
|
||||||
|
<span><input id="so-submit" type="submit" value="Commit"></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<input class="nav-menu" type="checkbox" id="nav-menu"/>
|
||||||
|
<label class="hmb" for="nav-menu" title="Navigation Links"><span class="hmb-line"></span></label>
|
||||||
|
<nav class="vnav" id="vnav">
|
||||||
|
<ul class="vmenu no-lst-style" id="vmenu">
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<nav class="nav" id="nav">
|
||||||
|
<ul class="menu no-lst-style" id="menu">
|
||||||
|
{{ range .Data.GetHeaderLabels }}
|
||||||
|
<li><a href="{{ $.Data.GetHeaderLink . }}" class="no-dec" title="{{ . }}">{{ . }}</a></li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main class="main-box flex-col">
|
||||||
|
<div id="about">
|
||||||
|
<div class="item-table flex-col">
|
||||||
|
<div class="item-heading">
|
||||||
|
<div class="item-table-full">
|
||||||
|
<div class="centered"><h1>{{ .Data.About.Title }}</h1></div>
|
||||||
|
</div>
|
||||||
|
<div class="item-table-360">
|
||||||
|
<div class="centered"><h4>Email: <a href="mailto:{{ .Data.About.ContactEmail }}">{{ .Data.About.ContactEmail }}</a></h4></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="item-table-full">
|
||||||
|
<div class="content">{{ .Data.About.GetContent }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-table-360">
|
||||||
|
<div><a href="{{ .Data.About.ImageLocation }}"><img src="{{ .Data.About.ThumbnailLocation }}" alt="{{ .Data.About.ImageAltText }}" title="{{ .Data.About.ImageAltText }}" width="360px"></a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ $c := 0 }}
|
||||||
|
{{ range .GetEntries }}
|
||||||
|
{{ $c = $.CounterPlusPlus }}
|
||||||
|
<div id="entry-{{ $c }}">
|
||||||
|
<script type="application/javascript">
|
||||||
|
CreateEntry({{ $c }}, "{{ .Name }}", "{{ .VideoLocation }}", "{{ .VideoContentType }}", "{{ .GetStartDateHTML }}", "{{ .GetEndDateHTML }}", {{ .GetInt64Duration }})
|
||||||
|
</script>
|
||||||
|
<div class="item-table flex-col">
|
||||||
|
<div class="item-heading">
|
||||||
|
<div class="item-table-full">
|
||||||
|
<div class="centered"><h1>{{ .Name }}</h1></div>
|
||||||
|
</div>
|
||||||
|
<div class="item-table-360">
|
||||||
|
{{ if eq .GetStartDate .GetEndDate }}
|
||||||
|
<div class="centered"><h4>{{ .GetStartDate }}</h4></div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="centered"><h4>{{ .GetStartDate }} - {{ .GetEndDate }}</h4></div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="item-table-full">
|
||||||
|
<div class="content">{{ .GetContent }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-table-360">
|
||||||
|
<div id="video-{{ $c }}">
|
||||||
|
{{ if eq .VideoLocation "" }}
|
||||||
|
<img src="{{ .GetVideoThumbnail $.Data.NoVideoImageLocation }}" alt="No Video" width="360px">
|
||||||
|
{{ else }}
|
||||||
|
{{ if .IsVideoLink }}
|
||||||
|
<a href="{{ .VideoLocation }}">
|
||||||
|
<img src="{{ .GetVideoThumbnail $.Data.PlayVideoImageLocation }}" alt="Play Video" title="Play" width="360px">
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
|
<script type="application/javascript">
|
||||||
|
CreateVideoPlaceholder({{ $c }}, "{{ .GetVideoThumbnail $.Data.PlayVideoImageLocation }}")
|
||||||
|
</script>
|
||||||
|
<noscript>
|
||||||
|
<video controls width="360px">
|
||||||
|
<source src="{{ .VideoLocation }}" type="{{ .VideoContentType }}">
|
||||||
|
<a href="{{ .VideoLocation }}">The Video</a>
|
||||||
|
</video>
|
||||||
|
</noscript>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ if not (eq .GetImageCount 0) }}
|
||||||
|
<div class="item-table-caption">
|
||||||
|
<div class="image-box flex-row">
|
||||||
|
{{ range .GetImages }}
|
||||||
|
<a href="{{ .ImageLocation }}"><img src="{{ .ThumbnailLocation }}" alt="{{ .ImageAltText }}" title="{{ .ImageAltText }}" width="240px"></a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</main>
|
||||||
|
<script type="application/javascript">
|
||||||
|
SetupJS()
|
||||||
|
</script>
|
||||||
|
<footer>
|
||||||
|
<p>
|
||||||
|
Looking for the old static HTML page, here's the <a href="index.html">link</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This page's content is licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">
|
||||||
|
<img src="https://mirrors.creativecommons.org/presskit/buttons/80x15/png/by-nc-nd.png" alt="License" title="License" height="16"></a>.
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
<div id="st"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
370
index.go.yml
Normal file
370
index.go.yml
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
#This file is (C) Captain ALM
|
||||||
|
#Under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
|
||||||
|
cssBaseURL: "resources/assets/base.css"
|
||||||
|
cssDarkURL: "resources/assets/dark.css"
|
||||||
|
cssLightURL: "resources/assets/light.css"
|
||||||
|
jScriptURL: "resources/assets/index.js"
|
||||||
|
noVideoImageLocation: "resources/assets/novideo.png"
|
||||||
|
playVideoImageLocation: "resources/assets/video.png"
|
||||||
|
logoImageLocation: "resources/assets/logo.png"
|
||||||
|
moonImageLocation: "resources/assets/moon.png"
|
||||||
|
sunImageLocation: "resources/assets/sun.png"
|
||||||
|
sortImageLocation: "resources/assets/sort.png"
|
||||||
|
headerLinks:
|
||||||
|
Main Portfolio: "https://portfolio.captainalm.com/"
|
||||||
|
Root Site Home: "https://www.captainalm.com/"
|
||||||
|
LinkedIn: "https://www.linkedin.com/in/alfred-manville/"
|
||||||
|
about:
|
||||||
|
title: "Alfred Manville (Captain ALM)"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
Hello, I'm Alfred Manville (#age# Years Old) and a third year student at City, University of London.
|
||||||
|
I'm a free and open-source developer who enjoys networking my laptops together,
|
||||||
|
writes network software to communicate between them and then tries to break said software.
|
||||||
|
I also have a <a href="https://youtube.com/c/CaptainALM">Youtube Channel</a> which is in the process of being resumed from a hiatus.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
On the programming side, I know Visual Basic .net, C# .net, C, Java, Go, Javascript, C++, Python, Bash,
|
||||||
|
Haskell, Processing and Microsoft Smallbasic (I have also dabbled in Batch).
|
||||||
|
I am currently in the progress of writing infrastructure software in Go, in the past, I wrote a <a href="https://github.com/Captain-ALM/CALM-Console">command console</a>
|
||||||
|
in VB .net for my own pluggable libraries (I created a CMD emulator to get past the school disabling interactive CMD) and some <a href="https://github.com/Captain-ALM/CALMNetLibSamples">
|
||||||
|
network communication applications</a> (Including a peer-to-peer <a href="https://github.com/Captain-ALM/C-ALM-VOIP">VOIP client</a>
|
||||||
|
using NAudio as the audio library and my own network wrapper library, however, it is in need of bug-fixing at the moment).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
My github username is <a href="https://github.com/Captain-ALM">Captain-ALM</a> and has half my public programming projects,
|
||||||
|
the other half is located at: <a href="https://code.mrmelon54.com/alfred">https://code.mrmelon54.com/alfred</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
On the cracking / hacking side, I've used virtual machines a lot (Mainly infrastructure testing
|
||||||
|
but I did at one point try creating and breaking into a test windows domain network I had setup).
|
||||||
|
I've also used VMs to pull perform a PXE boot off a network and analyse the partially deployed image
|
||||||
|
(And this is why the deployment servers should be switched off when unneeded, especially since PXE
|
||||||
|
auto-domain-join would store its credentials in the image).
|
||||||
|
I also played around with <a href="https://github.com/Captain-ALM/rdpccgs-ccgsrdp-">accessing the RDP servers</a>
|
||||||
|
when I was in secondary school (Turns out remote apps is just a glorified application auto-launcher with window size detection).
|
||||||
|
Here is <a href="https://github.com/Captain-ALM/op_ctrl">Operation Control</a> (Stylized: op_ctrl) used for same-computer exploiting
|
||||||
|
by wrapping target applications run by other users (Such as via RDP Remote Apps) and then starting a master server to send arbitrary
|
||||||
|
.net DLLs to execute on the target slaved client.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
I also <a href="https://subsection.captainalm.com/">bake bread</a> (Mostly learnt from my grandma); although this sub-site is still under construction.
|
||||||
|
I also play video-games (Check my Youtube Channel or my <a href="https://www.gog.com/u/Captain_ALM">GOG Profile</a>) and am an expert at using lower-end hardware.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
I used to do Karate (Kyokushin Brown Belt) and I wish I could still fit my bike.
|
||||||
|
Here is my <a href="https://cdn.captainalm.com/download/keys/alfred@captainalm.com.asc">GPG Key</a> for my email address.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
My CV is available in the following formats: ( <a href="https://cdn.captainalm.com/download/cvs/AlfredManvilleCV-2024.docx">DOCX</a> | <a href="https://cdn.captainalm.com/download/cvs/AlfredManvilleCV-2024.pdf">PDF</a> )
|
||||||
|
</p>
|
||||||
|
thumbnailLocation: "resources/assets/imageofyou_t.jpg"
|
||||||
|
imageLocation: "resources/assets/imageofyou.jpg"
|
||||||
|
imageAltText: "Image of me."
|
||||||
|
birthYear: 2002
|
||||||
|
contactEmail: "alfred@captainalm.com"
|
||||||
|
entries:
|
||||||
|
- name: "Bootcamp 2021: Ninjaformer GUI"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
My first programming task at City, concluding the 2 week 2021 Programming Bootcamp,
|
||||||
|
although I have only spent 3 days programming this and was a tad bit too ambitious.
|
||||||
|
(I could have started earlier though)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This Processing project show that I can use arrays, loops, mouse and keyboard interaction and geometric transforms.
|
||||||
|
The project contains a GUI library that I made to create the menu system for what could have been the Ninjaformer game.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Unfortunately, while the code for loading tile, sprite and level information exists (JSON, sprite sheet support);
|
||||||
|
I ran out of time before the submission to actually even start on the game. But you can play around with the main code and build your own GUIs too so...
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Here is the repo: <a href="https://github.com/Captain-ALM/Ninjaformer-Processing">https://github.com/Captain-ALM/Ninjaformer-Processing</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The original video is available here: <a href="resources/stream/vid1.mp4">resources/stream/vid1.mp4</a>
|
||||||
|
</p>
|
||||||
|
startDate: "01/10/2021"
|
||||||
|
endDate: "31/10/2021"
|
||||||
|
videoLocation: "resources/stream/vid-bootcamp.mp4"
|
||||||
|
videoContentType: "video/mp4"
|
||||||
|
videoThumbnailLocation: "resources/assets/bootcamp-vid.png"
|
||||||
|
thumbnailLocations:
|
||||||
|
- "resources/assets/pic1_t.jpg"
|
||||||
|
- "resources/assets/pic2_t.jpg"
|
||||||
|
- "resources/assets/pic3_t.jpg"
|
||||||
|
- "resources/assets/bootcamp-1_t.jpg"
|
||||||
|
- "resources/assets/bootcamp-2_t.jpg"
|
||||||
|
- "resources/assets/bootcamp-3_t.jpg"
|
||||||
|
imageLocations:
|
||||||
|
- "resources/assets/pic1.jpg"
|
||||||
|
- "resources/assets/pic2.jpg"
|
||||||
|
- "resources/assets/pic3.jpg"
|
||||||
|
- "resources/assets/bootcamp-1.jpg"
|
||||||
|
- "resources/assets/bootcamp-2.jpg"
|
||||||
|
- "resources/assets/bootcamp-3.jpg"
|
||||||
|
imageAltTexts:
|
||||||
|
- "Level select screen."
|
||||||
|
- "Empty content interface (Gameplay)."
|
||||||
|
- "Level editor screen."
|
||||||
|
- "Ninjagame source code."
|
||||||
|
- "Button image source code."
|
||||||
|
- "Sprite world source code."
|
||||||
|
- name: "City Game Project 2022: Ninjaformer (Alpha, Beta)"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
My first major project at City (A Java Game), concluding 2.2 Months of programming.
|
||||||
|
This game uses the University provided game library (Which is just JBox2D extended).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Looking back on this, I wish I started earlier (Like January) that way I could have implemented all the features I wanted.
|
||||||
|
This project allows for levels to be designed within the program and allows them to be edited as XML outside the program.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The code is extensible and it is relatively straight forward to implement new features. There are a few bugs that can crop up
|
||||||
|
(Such as sticking to surfaces due to ground body updates) but I already know ways to fix them.
|
||||||
|
This project relies on part of a GUI library I built in it and I had to modify the CityGame library by extending it.
|
||||||
|
The audio and assets were also created by me, although they're a bit amateurish as I'm a computer scientist not an artist!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This game is designed to be a story based game... The tutorial level at the beginning of the game is the dream in which the main
|
||||||
|
character dreams of being a ninja, this allows for the player to learn the controls and basic mechanics of the game.
|
||||||
|
The next level is the training level in which the ninja trains within a monastery.
|
||||||
|
The final level allows the ninja to "complete" the game while exploring a set of caves.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The game contains a mechanism system allowing for the unlocking of portals (Doors) for the character to progress within and between levels.
|
||||||
|
The character contains a set of different stamina types, core stamina which if depleted causes a death, leg stamina which is a multiplier for
|
||||||
|
doing any action that uses legs and arm stamina which is a multiplier for doing any action that uses arms. The game contains a weapon system of
|
||||||
|
throwing stars, swords and throwable liquids.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Here is the repo: <a href="https://github.com/cityteaching/citygame2122-Captain-ALM"><strike><del>Not public due to university anti-plagiarism policy.</del></strike></a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The original video is available here: <a href="resources/stream/vid2.mp4">resources/stream/vid2.mp4</a>
|
||||||
|
</p>
|
||||||
|
startDate: "25/02/2022"
|
||||||
|
endDate: "08/05/2022"
|
||||||
|
videoLocation: "resources/stream/vid-ninjaformer-2022.mp4"
|
||||||
|
videoContentType: "video/mp4"
|
||||||
|
videoThumbnailLocation: "resources/assets/ninjaformer-vid.png"
|
||||||
|
thumbnailLocations:
|
||||||
|
- "resources/assets/pic4_t.jpg"
|
||||||
|
- "resources/assets/pic5_t.jpg"
|
||||||
|
- "resources/assets/pic6_t.jpg"
|
||||||
|
- "resources/assets/ninjaformer-1_t.jpg"
|
||||||
|
- "resources/assets/ninjaformer-2_t.jpg"
|
||||||
|
- "resources/assets/ninjaformer-3_t.jpg"
|
||||||
|
imageLocations:
|
||||||
|
- "resources/assets/pic4.jpg"
|
||||||
|
- "resources/assets/pic5.jpg"
|
||||||
|
- "resources/assets/pic6.jpg"
|
||||||
|
- "resources/assets/ninjaformer-1.jpg"
|
||||||
|
- "resources/assets/ninjaformer-2.jpg"
|
||||||
|
- "resources/assets/ninjaformer-3.jpg"
|
||||||
|
imageAltTexts:
|
||||||
|
- "Cave level."
|
||||||
|
- "Tutorial level."
|
||||||
|
- "Training level (Editor mode)."
|
||||||
|
- "Main Menu screen."
|
||||||
|
- "Pause Menu screen."
|
||||||
|
- "Audio Settings screen."
|
||||||
|
- name: "Global Game Jam January 2022 : Shadow work"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
I may have not done any programming for this (Even though I know C#) but I helped write a good chunk of the background story
|
||||||
|
(I also helped with the level design although it turns out 48 hours is hard to get polished stuff done in, so some stuff had to be axed).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Read about and get the game files from: <a href="https://globalgamejam.org/2022/games/shadow-work-8">https://globalgamejam.org/2022/games/shadow-work-8</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Download the windows executable from: <a href="https://cdn.captainalm.com/download/game/dist/ShadowWorkExecutable.zip">https://cdn.captainalm.com/download/game/dist/ShadowWorkExecutable.zip</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Design Document is available here: <a href="https://cdn.captainalm.com/download/game/design/Shadow-WIP-Story-Info.pdf">https://cdn.captainalm.com/download/game/design/Shadow-WIP-Story-Info.pdf</a>
|
||||||
|
</p>
|
||||||
|
startDate: "20/01/2022"
|
||||||
|
endDate: "30/01/2022"
|
||||||
|
videoLocation: "resources/stream/vid-shadowwork.mp4"
|
||||||
|
videoContentType: "video/mp4"
|
||||||
|
videoThumbnailLocation: "resources/assets/shadowwork-vid.png"
|
||||||
|
thumbnailLocations:
|
||||||
|
- "resources/assets/shadowwork-2_t.jpg"
|
||||||
|
- "resources/assets/shadowwork-3_t.jpg"
|
||||||
|
- "resources/assets/shadowwork-4_t.jpg"
|
||||||
|
- "resources/assets/shadowwork-5_t.jpg"
|
||||||
|
- "resources/assets/shadowwork-6_t.jpg"
|
||||||
|
- "resources/assets/shadowwork-1_t.jpg"
|
||||||
|
imageLocations:
|
||||||
|
- "resources/assets/shadowwork-2.jpg"
|
||||||
|
- "resources/assets/shadowwork-3.jpg"
|
||||||
|
- "resources/assets/shadowwork-4.jpg"
|
||||||
|
- "resources/assets/shadowwork-5.jpg"
|
||||||
|
- "resources/assets/shadowwork-6.jpg"
|
||||||
|
- "resources/assets/shadowwork-1.jpg"
|
||||||
|
imageAltTexts:
|
||||||
|
- "Main Menu screen."
|
||||||
|
- "Controls screen."
|
||||||
|
- "Bedroom (Beginning game area)."
|
||||||
|
- "First Puzzle, duality keys."
|
||||||
|
- "Visualisation of in-game character split."
|
||||||
|
- "End of the game area."
|
||||||
|
- name: "City-University Portfolio"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
This project is what's outputting this page at the moment! The backend is written in Go and there is both custom front-end Javascript and CSS!
|
||||||
|
The pages support theming and the entries can be sorted through Javascript or on the backend through GET parameters.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This project is under the BSD-3-Clause License, so if reusing, you must scrub references to me, the yml file this is written in is under
|
||||||
|
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Find the source code here: <a href="https://code.mrmelon54.com/alfred/cityuni-webserver">https://code.mrmelon54.com/alfred/cityuni-webserver</a>
|
||||||
|
</p>
|
||||||
|
startDate: "13/07/2022"
|
||||||
|
- name: "Python Communicator"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
After learning python for my portfolio development option, I wanted to showcase what I knew in python.
|
||||||
|
I usually make a network application in each of the new programming languages that I learn.
|
||||||
|
For the repo showing me learning python please go to: <a href="https://code.mrmelon54.com/alfred/LearningPy">https://code.mrmelon54.com/alfred/LearningPy</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This project is a message based network communicator written in python and has a module for networking.
|
||||||
|
The project allows the sending of text messages and files over a network.
|
||||||
|
It also comes with a nice twist, in that if you select the pickle protocol, you can inject code into another client by sending a specially crafted message!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Find the source code here: <a href="https://github.com/Captain-ALM/CALMPyNetworker">https://github.com/Captain-ALM/CALMPyNetworker</a>
|
||||||
|
</p>
|
||||||
|
startDate: "10/12/2022"
|
||||||
|
endDate: "10/12/2022"
|
||||||
|
videoLocation: "resources/stream/vid-pycom.mp4"
|
||||||
|
videoContentType: "video/mp4"
|
||||||
|
videoThumbnailLocation: "resources/assets/pycom-vid.png"
|
||||||
|
thumbnailLocations:
|
||||||
|
- "resources/assets/pycom-1_t.jpg"
|
||||||
|
- "resources/assets/pycom-2_t.jpg"
|
||||||
|
- "resources/assets/pycom-3_t.jpg"
|
||||||
|
- "resources/assets/pycom-4_t.jpg"
|
||||||
|
imageLocations:
|
||||||
|
- "resources/assets/pycom-1.jpg"
|
||||||
|
- "resources/assets/pycom-2.jpg"
|
||||||
|
- "resources/assets/pycom-3.jpg"
|
||||||
|
- "resources/assets/pycom-4.jpg"
|
||||||
|
imageAltTexts:
|
||||||
|
- "Text Messaging."
|
||||||
|
- "File Messaging."
|
||||||
|
- "Exploit Testing."
|
||||||
|
- "Failed Exploit Testing."
|
||||||
|
- name: "Group Project - AirVia ATS (AirTicket Sales)"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
This group project was creating a Ticket Sales system for the fictional company AirVia LTD, for this the group had to both design the implementation and then write the code for it.
|
||||||
|
Unfortunately, the project was not finished to a state where all the required features were added in and while all of the backend functionality except for the reports existed,
|
||||||
|
the GUIs to view and control those backends was not available.
|
||||||
|
Examples of this include: Sales, Transactions, Discounts and Reports where no GUIs were created or finished for any of these components.
|
||||||
|
The design of the program was followed with a lot of adaptations (Rather than not being followed at all) and it architecturally made sense with the use of facade implementations;
|
||||||
|
The use of facade allowed for other people to code against an interface while waiting for a controller to be finished which extended the interface.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The database system was handled via an abstraction layer I designed and wrote myself which supports 'locking' a record for keeping consistency when multiple instances of the program are running.
|
||||||
|
This system uses an auxiliary table that has only the primary key column, a record is locked if it does not exist in the auxiliary table (Cannot delete) and is not locked if it does (Cannot insert);
|
||||||
|
This allows for atomic locking and unlocking of the record.
|
||||||
|
The implementation requires the record locked for safe access (Loading, Storing).
|
||||||
|
The abstraction layer makes use of two base classes, one for a single record and one for a table; with tha table one allowing the creation and deletion of the extending table via a schema and name being provided (As seen in the source code).
|
||||||
|
I also developed a backup system for the database that supports any table with the specifically supported data types used in the tables defined in the schema - removing the need to use third-party programs like SQLDump.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In the end, the following features were implemented: Login, Help / Error / Status Bar, Account System + GUI, Blank Types + GUI, Blanks + GUI, Customers + GUI, Discounts, Flexible Discounts, Sales, Transactions, Refunds, Dashboard + Notifications, Database Interfacing + Backup and Rates + GUI.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Find the source code here: <a href="https://github.com/karansambee/IN2018-Team-Project/tree/master">https://github.com/karansambee/IN2018-Team-Project/tree/master</a>
|
||||||
|
</p>
|
||||||
|
startDate: "01/02/2023"
|
||||||
|
endDate: "30/04/2023"
|
||||||
|
videoLocation: "resources/stream/vid-groupproject-2023.mp4"
|
||||||
|
videoContentType: "video/mp4"
|
||||||
|
videoThumbnailLocation: "resources/assets/groupproject-vid.png"
|
||||||
|
thumbnailLocations:
|
||||||
|
- "resources/assets/groupproject-1_t.jpg"
|
||||||
|
- "resources/assets/groupproject-2_t.jpg"
|
||||||
|
- "resources/assets/groupproject-3_t.jpg"
|
||||||
|
- "resources/assets/groupproject-4_t.jpg"
|
||||||
|
- "resources/assets/groupproject-5_t.jpg"
|
||||||
|
- "resources/assets/groupproject-6_t.jpg"
|
||||||
|
- "resources/assets/groupproject-7_t.jpg"
|
||||||
|
- "resources/assets/groupproject-8_t.jpg"
|
||||||
|
- "resources/assets/groupproject-9_t.jpg"
|
||||||
|
- "resources/assets/groupproject-10_t.jpg"
|
||||||
|
imageLocations:
|
||||||
|
- "resources/assets/groupproject-1.png"
|
||||||
|
- "resources/assets/groupproject-2.png"
|
||||||
|
- "resources/assets/groupproject-3.png"
|
||||||
|
- "resources/assets/groupproject-4.png"
|
||||||
|
- "resources/assets/groupproject-5.png"
|
||||||
|
- "resources/assets/groupproject-6.png"
|
||||||
|
- "resources/assets/groupproject-7.png"
|
||||||
|
- "resources/assets/groupproject-8.png"
|
||||||
|
- "resources/assets/groupproject-9.png"
|
||||||
|
- "resources/assets/groupproject-10.png"
|
||||||
|
imageAltTexts:
|
||||||
|
- "Logon Interface."
|
||||||
|
- "Administrator Dashboard Interface."
|
||||||
|
- "Blank Modifier."
|
||||||
|
- "Customer Creator."
|
||||||
|
- "Disabling an Account."
|
||||||
|
- "Rate Creator."
|
||||||
|
- "Blank Type Editor."
|
||||||
|
- "Help on Force Unlocking (Database Manager Interface)."
|
||||||
|
- "Account Editor on a Manager within The Dashboard Interface."
|
||||||
|
- "Part of the Main.java source code Screenshot."
|
||||||
|
- name: "City-University Promotional Video"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
Here, I star in an interview for City, University of London's Promotional Marketing Campaign. Join <a href="https://www.city.ac.uk/">City</a>!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Find the video here: <a href="https://www.youtube.com/watch?v=tOccImgskec">https://www.youtube.com/watch?v=tOccImgskec</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Find the general School of Science and Technology video here: <a href="https://www.youtube.com/watch?v=pkTCf4CWFSY">https://www.youtube.com/watch?v=pkTCf4CWFSY</a>
|
||||||
|
</p>
|
||||||
|
startDate: "05/06/2023"
|
||||||
|
endDate: "05/06/2023"
|
||||||
|
videoLocation: "https://www.youtube.com/watch?v=tOccImgskec"
|
||||||
|
videoContentType: "text/uri-list"
|
||||||
|
videoThumbnailLocation: "resources/assets/citypromo-vid.png"
|
||||||
|
- name: "Decide Quiz - GCloud City"
|
||||||
|
content: >
|
||||||
|
<p>
|
||||||
|
This project was <a href="https://cdn.captainalm.com/download/gcloudcity/CloudApplicationSpecification-AlfredManville-MohammadMasood.docx">designed</a> <a href="https://cdn.captainalm.com/download/gcloudcity/CloudPresentation-AlfredManville-MohammadMasood.pptx">to</a> operate as a Kahoot clone although the front-end never got completed by the other member of the pair (This was written in React).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This was created in a pair as part of the Cloud Computing module at City, University of London. It was built for use using google cloud and therefore
|
||||||
|
uses many Google Cloud Platform Technologies which are listed and shown off in the video.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The communication protocol uses JSON packets passed either through a WebSocket connection or a REST based protocol that polls the server for data while sending the queued packets.
|
||||||
|
The REST connection is used as a fallback system where WebSockets do not work; in addition, a REST connection is made to the master server to first work out which app server has
|
||||||
|
the least load, once found, this is sent back to the client where the client will then attempt a WebSocket connection and, on failure, a REST session is created (A key is returned
|
||||||
|
which is then used as a parameter in subsequent communications). In the event there is no more capacity, a service unavailable error is sent and a new VM will be spun up, in the case
|
||||||
|
that any are left. The source code for this library can be found at <a href="https://github.com/Captain-ALM/gc-c-com">https://github.com/Captain-ALM/gc-c-com</a> and the master server source
|
||||||
|
code can be found at <a href="https://github.com/Captain-ALM/gc-c-master-srv">https://github.com/Captain-ALM/gc-c-master-srv</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The main game loop was supposed to allow user generated quizzes once logged in along-side allowing for public quizzes that could be used and copied by other users.
|
||||||
|
Only logged in users could start games where any user - both logged in or not - could join and answer questions within a set amount of time, with the person answering
|
||||||
|
the fastest getting more points than people who answer later. The system would also have a leaderboard shown at the end of each question; it was also designed to recover
|
||||||
|
from crashes and resume from the last question executed. The source code for the app server can be found at <a href="https://github.com/Captain-ALM/gc-c-app-srv">
|
||||||
|
https://github.com/Captain-ALM/gc-c-app-srv</a> where all this functionality does exist in the backend; the database source code can be found at <a href="https://github.com/Captain-ALM/gc-c-db">https://github.com/Captain-ALM/gc-c-db</a>.
|
||||||
|
</p>
|
||||||
|
startDate: "27/11/2023"
|
||||||
|
endDate: "14/01/2024"
|
||||||
|
videoLocation: "resources/stream/vid-gc-c-v2.mp4"
|
||||||
|
videoContentType: "video/mp4"
|
||||||
|
videoThumbnailLocation: "resources/assets/vid-gc-c-v2.png"
|
369
index.js
Normal file
369
index.js
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
/*
|
||||||
|
This file is (C) Captain ALM
|
||||||
|
Under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
|
||||||
|
*/
|
||||||
|
var EntryData = []
|
||||||
|
var EntryIndices = []
|
||||||
|
var SortOrderStateI = true
|
||||||
|
var SortOrderBStateI = true
|
||||||
|
var SortOrderEnabled = false
|
||||||
|
var SortValue = ""
|
||||||
|
var OrderValue = ""
|
||||||
|
function SetupJS() {
|
||||||
|
SetupIndexArray()
|
||||||
|
SetupJSTheme()
|
||||||
|
SetupJSHSO()
|
||||||
|
SetupJSSOI()
|
||||||
|
SetupJSRSN()
|
||||||
|
}
|
||||||
|
function CreateEntry(id, name, videourl, videotype, start, end, duration) {
|
||||||
|
EntryData[id] = {
|
||||||
|
name: name,
|
||||||
|
videourl: videourl,
|
||||||
|
videotype: videotype,
|
||||||
|
start: Date.parse(start),
|
||||||
|
end: Date.parse(end),
|
||||||
|
duration : parseInt(duration, 10)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function CreateVideoPlaceholder(id,phImageURL) {
|
||||||
|
var imgPH = document.createElement("img")
|
||||||
|
imgPH.src = phImageURL
|
||||||
|
imgPH.id = "play-"+id
|
||||||
|
imgPH.alt = "Play Video"
|
||||||
|
imgPH.title = "Play"
|
||||||
|
imgPH.width = 360
|
||||||
|
imgPH.style.cursor = "pointer"
|
||||||
|
if (document.addEventListener) {
|
||||||
|
imgPH.addEventListener("click", function () {ActivateVideo(id);})
|
||||||
|
} else {
|
||||||
|
imgPH.setAttribute("onclick", "ActivateVideo("+id+");")
|
||||||
|
imgPH.onclick = function () {ActivateVideo(id);}
|
||||||
|
}
|
||||||
|
document.getElementById("video-" + id).appendChild(imgPH)
|
||||||
|
}
|
||||||
|
function ActivateVideo(id) {
|
||||||
|
var holder = document.getElementById("video-" + id)
|
||||||
|
holder.removeChild(document.getElementById("play-"+id))
|
||||||
|
var vid = document.createElement("video")
|
||||||
|
vid.controls = true
|
||||||
|
vid.width = 360
|
||||||
|
var vids = document.createElement("source")
|
||||||
|
vids.src = EntryData[id].videourl
|
||||||
|
vids.type = EntryData[id].videotype
|
||||||
|
var vida = document.createElement("a")
|
||||||
|
vida.href = EntryData[id].videourl
|
||||||
|
vida.innerText = "The Video"
|
||||||
|
vid.appendChild(vids)
|
||||||
|
vid.appendChild(vida)
|
||||||
|
holder.appendChild(vid)
|
||||||
|
if (vid.play) {vid.play();}
|
||||||
|
}
|
||||||
|
function SetupIndexArray() {
|
||||||
|
for (var i = 0; i < EntryData.length; i++) {
|
||||||
|
EntryIndices[i] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SetupJSTheme() {
|
||||||
|
var th = document.getElementById("theme")
|
||||||
|
th.href = "#"
|
||||||
|
new Image().src = MoonImageURL //Preload I hope
|
||||||
|
if (document.addEventListener) {
|
||||||
|
th.addEventListener("click", ToggleTheme)
|
||||||
|
} else {
|
||||||
|
th.setAttribute("onclick", "ToggleTheme();")
|
||||||
|
th.onclick = ToggleTheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ReplaceHistory(url) {
|
||||||
|
var s = true
|
||||||
|
if (window.history) {
|
||||||
|
if (window.history.replaceState) {
|
||||||
|
window.history.replaceState({}, "", url)
|
||||||
|
s = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s) {
|
||||||
|
document.location.href = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ToggleTheme() {
|
||||||
|
var th = document.getElementById("theme")
|
||||||
|
var thimg = document.getElementById("theme-img")
|
||||||
|
var thsty = document.getElementById("style-theme")
|
||||||
|
var logo = document.getElementById("logo")
|
||||||
|
var url = document.location.href
|
||||||
|
url = url.split("#", 1)[0].split('?', 1)[0]
|
||||||
|
if (document.getElementById("so-theme")) {
|
||||||
|
thimg.src = SunImageURL
|
||||||
|
thimg.alt = "()"
|
||||||
|
th.title = "Switch to Light Mode"
|
||||||
|
document.getElementById("so-form").removeChild(document.getElementById("so-theme"))
|
||||||
|
logo.href = "?"
|
||||||
|
ReplaceHistory(url+"?"+TheParameters+"#")
|
||||||
|
thsty.href = CssDarkURL
|
||||||
|
} else {
|
||||||
|
thimg.src = MoonImageURL
|
||||||
|
thimg.alt = "{"
|
||||||
|
th.title = "Switch to Dark Mode"
|
||||||
|
var thi = document.createElement("input")
|
||||||
|
thi.name = "light"
|
||||||
|
thi.type = "hidden"
|
||||||
|
thi.id = "so-theme"
|
||||||
|
document.getElementById("so-form").appendChild(thi)
|
||||||
|
logo.href = "?light"
|
||||||
|
if (TheParameters === "") {
|
||||||
|
ReplaceHistory(url+"?light#")
|
||||||
|
} else {
|
||||||
|
ReplaceHistory(url+"?light&"+TheParameters+"#")
|
||||||
|
}
|
||||||
|
thsty.href = CssLightURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SetupJSHSO() {
|
||||||
|
var pb = document.getElementById("sort-menu-button")
|
||||||
|
var pane = document.getElementById("so-pane")
|
||||||
|
if (document.addEventListener) {
|
||||||
|
document.addEventListener("click", HandleGlobalClick)
|
||||||
|
pb.addEventListener("mouseover", HandleSortOrderBEnter)
|
||||||
|
pb.addEventListener("mouseout", HandleSortOrderBLeave)
|
||||||
|
pane.addEventListener("mouseover", HandleSortOrderEnter)
|
||||||
|
pane.addEventListener("mouseout", HandleSortOrderLeave)
|
||||||
|
} else {
|
||||||
|
document.parentElement.setAttribute("onclick", "HandleGlobalClick();")
|
||||||
|
pb.setAttribute("onmouseover", "HandleSortOrderBEnter();")
|
||||||
|
pb.setAttribute("onmouseout", "HandleSortOrderBLeave();")
|
||||||
|
pane.setAttribute("onmouseover", "HandleSortOrderEnter();")
|
||||||
|
pane.setAttribute("onmouseout", "HandleSortOrderLeave();")
|
||||||
|
document.parentElement.onclick = HandleGlobalClick
|
||||||
|
pb.onmouseover = HandleSortOrderBEnter
|
||||||
|
pb.onmouseout = HandleSortOrderBLeave
|
||||||
|
pane.onmouseover = HandleSortOrderEnter
|
||||||
|
pane.onmouseout = HandleSortOrderLeave
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function HandleGlobalClick() {
|
||||||
|
if (SortOrderStateI && SortOrderBStateI) {document.getElementById("sort-menu").checked = false;}
|
||||||
|
}
|
||||||
|
function HandleSortOrderBEnter() {
|
||||||
|
SortOrderBStateI = false
|
||||||
|
}
|
||||||
|
function HandleSortOrderBLeave(){
|
||||||
|
SortOrderBStateI = true
|
||||||
|
}
|
||||||
|
function HandleSortOrderEnter() {
|
||||||
|
SortOrderStateI = false
|
||||||
|
}
|
||||||
|
function HandleSortOrderLeave(){
|
||||||
|
SortOrderStateI = true
|
||||||
|
}
|
||||||
|
function SetupJSSOI() {
|
||||||
|
var submit = document.getElementById("so-submit")
|
||||||
|
if (submit.parentNode) {submit.parentNode.removeChild(submit);}
|
||||||
|
var oc = document.getElementById("so-order")
|
||||||
|
OrderValue = oc.value
|
||||||
|
var sc = document.getElementById("so-sort")
|
||||||
|
SortValue = sc.value
|
||||||
|
if (document.addEventListener) {
|
||||||
|
oc.addEventListener("change", HandleSortOrderChange)
|
||||||
|
sc.addEventListener("change", HandleSortOrderChange)
|
||||||
|
} else {
|
||||||
|
oc.setAttribute("onchange", "HandleSortOrderChange();")
|
||||||
|
sc.setAttribute("onchange", "HandleSortOrderChange();")
|
||||||
|
oc.onchange = HandleSortOrderChange
|
||||||
|
sc.onchange = HandleSortOrderChange
|
||||||
|
}
|
||||||
|
SortOrderEnabled = true
|
||||||
|
}
|
||||||
|
function HandleSortOrderChange() {
|
||||||
|
if (SortOrderEnabled) {EntrySort(document.getElementById("so-order").value, document.getElementById("so-sort").value);}
|
||||||
|
}
|
||||||
|
function EntrySort(o, s) {
|
||||||
|
var ts = s.toString().toLowerCase()
|
||||||
|
var chg = false
|
||||||
|
if (SortValue !== s) {
|
||||||
|
chg = true
|
||||||
|
SortValue = s
|
||||||
|
}
|
||||||
|
if (chg || OrderValue !== o) {
|
||||||
|
if (ts === "asc" || ts === "ascending") {
|
||||||
|
ts = 1
|
||||||
|
} else {
|
||||||
|
ts = -1
|
||||||
|
}
|
||||||
|
var to = o.toString().toLowerCase()
|
||||||
|
if (to === "start") {
|
||||||
|
if (ts < 0) {
|
||||||
|
EntryIndices = EntryIndices.sort(SortStartD)
|
||||||
|
} else {
|
||||||
|
EntryIndices = EntryIndices.sort(SortStartA)
|
||||||
|
}
|
||||||
|
} else if (to === "end") {
|
||||||
|
if (ts < 0) {
|
||||||
|
EntryIndices = EntryIndices.sort(SortEndD)
|
||||||
|
} else {
|
||||||
|
EntryIndices = EntryIndices.sort(SortEndA)
|
||||||
|
}
|
||||||
|
} else if (to === "name") {
|
||||||
|
if (ts < 0) {
|
||||||
|
EntryIndices = EntryIndices.sort(SortNameD)
|
||||||
|
} else {
|
||||||
|
EntryIndices = EntryIndices.sort(SortNameA)
|
||||||
|
}
|
||||||
|
} else if (to === "duration") {
|
||||||
|
if (ts < 0) {
|
||||||
|
EntryIndices = EntryIndices.sort(SortDurationD)
|
||||||
|
} else {
|
||||||
|
EntryIndices = EntryIndices.sort(SortDurationA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chg = true
|
||||||
|
OrderValue = o
|
||||||
|
}
|
||||||
|
if (chg) {
|
||||||
|
TheParameters = "order="+OrderValue+"&sort="+SortValue
|
||||||
|
var url = document.location.href
|
||||||
|
url = url.split("#", 1)[0].split('?', 1)[0]
|
||||||
|
if (document.getElementById("so-theme")) {
|
||||||
|
ReplaceHistory(url+"?light&"+TheParameters)
|
||||||
|
} else {
|
||||||
|
ReplaceHistory(url+"?"+TheParameters)
|
||||||
|
}
|
||||||
|
for (var i = 0; i < EntryIndices.length; i++) {
|
||||||
|
var tNode = document.getElementById("entry-"+EntryIndices[i])
|
||||||
|
var pNode = tNode.parentNode
|
||||||
|
tNode = pNode.removeChild(tNode)
|
||||||
|
pNode.appendChild(tNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortStartA(a, b) {
|
||||||
|
if (EntryData[a].start < EntryData[b].start) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortStartD(a, b) {
|
||||||
|
if (EntryData[a].start > EntryData[b].start) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortEndA(a, b) {
|
||||||
|
if (EntryData[a].end < EntryData[b].end) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortEndD(a, b) {
|
||||||
|
if (EntryData[a].end > EntryData[b].end) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortNameA(a, b) {
|
||||||
|
if (EntryData[a].name < EntryData[b].name) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortNameD(a, b) {
|
||||||
|
if (EntryData[a].name > EntryData[b].name) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortDurationA(a, b) {
|
||||||
|
if (EntryData[a].duration < EntryData[b].duration) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SortDurationD(a, b) {
|
||||||
|
if (EntryData[a].duration > EntryData[b].duration) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SetupJSRSN() {
|
||||||
|
if (window.addEventListener) {
|
||||||
|
window.addEventListener("resize", PerformNavResize)
|
||||||
|
} else {
|
||||||
|
window.setAttribute("onresize", "PerformNavResize();")
|
||||||
|
window.onresize = PerformNavResize
|
||||||
|
}
|
||||||
|
PerformNavResize()
|
||||||
|
}
|
||||||
|
function PerformNavResize() {
|
||||||
|
var ww = 0
|
||||||
|
if (window.innerWidth && window.innerWidth !== 10) {
|
||||||
|
ww = window.innerWidth
|
||||||
|
} else {
|
||||||
|
var ht = document.getElementsByTagName("html")
|
||||||
|
if (ht && ht.length > 0) {ww = ht[0].clientWidth;}
|
||||||
|
}
|
||||||
|
if (ww > 0) {
|
||||||
|
var maxbarsz = ww - 342;
|
||||||
|
var men = document.getElementById("menu")
|
||||||
|
var vmen = document.getElementById("vmenu")
|
||||||
|
if (men && vmen) {
|
||||||
|
if (ww > 679) {
|
||||||
|
while (vmen.childNodes.length > 0) {InsertBefore(men, vmen.removeChild(vmen.childNodes[vmen.childNodes.length - 1]));}
|
||||||
|
} else {
|
||||||
|
var vmeni
|
||||||
|
var mensz = 0
|
||||||
|
var menc = []
|
||||||
|
var imenc = 0
|
||||||
|
for (vmeni = 0; vmeni < vmen.childNodes.length; vmeni++) {
|
||||||
|
if (vmen.childNodes[vmeni].nodeType === Node.ELEMENT_NODE) {
|
||||||
|
if (mensz+vmen.childNodes[vmeni].clientWidth > maxbarsz) {menc[imenc] = vmen.childNodes[vmeni]; imenc++;}
|
||||||
|
mensz += vmen.childNodes[vmeni].clientWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (menc.length > 0) {
|
||||||
|
for (vmeni = 0; vmeni < menc.length; vmeni++) {vmen.removeChild(menc[vmeni]);}
|
||||||
|
for (vmeni = menc.length - 1; vmeni >= 0; vmeni--) {InsertBefore(men, menc[vmeni]);}
|
||||||
|
} else {
|
||||||
|
for (vmeni = 0; vmeni < men.childNodes.length; vmeni++) {
|
||||||
|
if (men.childNodes[vmeni].nodeType === Node.ELEMENT_NODE) {
|
||||||
|
var mena = GetFirstSubElement(men.childNodes[vmeni], 0)
|
||||||
|
var menaw = GetNavTextWidth(mena.textContent)
|
||||||
|
if (mensz+menaw <= maxbarsz) {menc[imenc] = men.childNodes[vmeni]; imenc++;}
|
||||||
|
mensz += menaw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (vmeni = 0; vmeni < menc.length; vmeni++) {vmen.appendChild(men.removeChild(menc[vmeni]));}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function GetFirstSubElement(t,r) {
|
||||||
|
for (var gfsei = 0; gfsei < t.childNodes.length; gfsei++) {
|
||||||
|
if (t.childNodes[gfsei].nodeType === Node.ELEMENT_NODE) {
|
||||||
|
if (r < 1) {return t.childNodes[gfsei];} else {return GetFirstSubElement(t.childNodes[gfsei], r - 1);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
function InsertBefore(p,c) {
|
||||||
|
if (p.childNodes.length > 0) {p.insertBefore(c, p.childNodes[0]);} else {p.appendChild(c);}
|
||||||
|
}
|
||||||
|
function GetNavTextWidth(s) {
|
||||||
|
var st = document.getElementById("st")
|
||||||
|
if (st) {
|
||||||
|
st.textContent = s
|
||||||
|
var trw = st.clientWidth
|
||||||
|
st.textContent = ""
|
||||||
|
return trw
|
||||||
|
}
|
||||||
|
return 8 * s.length + 32
|
||||||
|
}
|
40
light.css
Normal file
40
light.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
This file is (C) Captain ALM
|
||||||
|
Under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
|
||||||
|
*/
|
||||||
|
body{
|
||||||
|
color: #060606;
|
||||||
|
background-color: #fafaf9;
|
||||||
|
border-color: #969696;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: #4f4fff;
|
||||||
|
}
|
||||||
|
.header, nav, footer, .so-pane{
|
||||||
|
background-color: #e2e2e1;
|
||||||
|
}
|
||||||
|
.home-button > div, .sort-button > div, .menu a, .vmenu a, .so-pane > form > div > * > *{
|
||||||
|
color: #1f1f1f;
|
||||||
|
}
|
||||||
|
.home-button:hover, .menu a:hover, .vmenu a:hover, .hmb:hover, .sort-button:hover, .sort-menu:checked ~ .sort-button, .sort-button-active, .so-pane > form > div > span > input, .so-pane > form > div > div > select{
|
||||||
|
background-color: #9f9f9e;
|
||||||
|
}
|
||||||
|
.hmb-line, .hmb-line::before, .hmb-line::after{
|
||||||
|
background: #1f1f1f;
|
||||||
|
}
|
||||||
|
.main-box{
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
.item-table{
|
||||||
|
background-color: #c0c0c0;
|
||||||
|
border-color: #0a214c;
|
||||||
|
}
|
||||||
|
.so-pane, .header{
|
||||||
|
border-color: #909090;
|
||||||
|
}
|
||||||
|
.image-box, .item-heading{
|
||||||
|
background-color: #b0b0b0;
|
||||||
|
}
|
||||||
|
.image-box > a{
|
||||||
|
border-color: #4f4f0f;
|
||||||
|
}
|
@ -45,6 +45,7 @@ func (gipg *goInfoPage) GetCacheIDExtension(urlParameters url.Values) string {
|
|||||||
|
|
||||||
type goInfoTemplateMarshal struct {
|
type goInfoTemplateMarshal struct {
|
||||||
FullOutput bool
|
FullOutput bool
|
||||||
|
CurrentTime time.Time
|
||||||
RegisteredPages []string
|
RegisteredPages []string
|
||||||
CachedPages []string
|
CachedPages []string
|
||||||
ProcessID int
|
ProcessID int
|
||||||
@ -98,6 +99,7 @@ func (gipg *goInfoPage) GetContents(urlParameters url.Values) (contentType strin
|
|||||||
theBuffer := &io.BufferedWriter{}
|
theBuffer := &io.BufferedWriter{}
|
||||||
err = theTemplate.ExecuteTemplate(theBuffer, templateName, &goInfoTemplateMarshal{
|
err = theTemplate.ExecuteTemplate(theBuffer, templateName, &goInfoTemplateMarshal{
|
||||||
FullOutput: urlParameters.Has("full"),
|
FullOutput: urlParameters.Has("full"),
|
||||||
|
CurrentTime: time.Now(),
|
||||||
RegisteredPages: regPages,
|
RegisteredPages: regPages,
|
||||||
CachedPages: cacPages,
|
CachedPages: cacPages,
|
||||||
ProcessID: os.Getpid(),
|
ProcessID: os.Getpid(),
|
||||||
|
@ -7,13 +7,14 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const indexName = "index.go"
|
||||||
|
|
||||||
type PageHandler struct {
|
type PageHandler struct {
|
||||||
PageContentsCache map[string]*CachedPage
|
PageContentsCache map[string]*CachedPage
|
||||||
PageProviders map[string]PageProvider
|
PageProviders map[string]PageProvider
|
||||||
@ -42,15 +43,24 @@ func NewPageHandler(config conf.ServeYaml) *PageHandler {
|
|||||||
CacheSettings: config.CacheSettings,
|
CacheSettings: config.CacheSettings,
|
||||||
}
|
}
|
||||||
if config.EnableGoInfoPage {
|
if config.EnableGoInfoPage {
|
||||||
toReturn.PageProviders = GetProviders(config.CacheSettings.EnableTemplateCaching, config.DataStorage, toReturn)
|
toReturn.PageProviders = GetProviders(config.CacheSettings.EnableTemplateCaching, config.GetDataStoragePath(), toReturn, config.GetTemplateStoragePath(), config.PageSettings, config.YmlDataFallback)
|
||||||
} else {
|
} else {
|
||||||
toReturn.PageProviders = GetProviders(config.CacheSettings.EnableTemplateCaching, config.DataStorage, nil)
|
toReturn.PageProviders = GetProviders(config.CacheSettings.EnableTemplateCaching, config.GetDataStoragePath(), nil, config.GetTemplateStoragePath(), config.PageSettings, config.YmlDataFallback)
|
||||||
}
|
}
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
actualPagePath := strings.TrimRight(request.URL.Path, "/")
|
actualPagePath := ""
|
||||||
|
if strings.HasSuffix(request.URL.Path, "/") {
|
||||||
|
if strings.HasSuffix(request.URL.Path, ".go/") {
|
||||||
|
actualPagePath = strings.TrimRight(request.URL.Path, "/")
|
||||||
|
} else {
|
||||||
|
actualPagePath = request.URL.Path + indexName
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actualPagePath = request.URL.Path
|
||||||
|
}
|
||||||
|
|
||||||
var currentProvider PageProvider
|
var currentProvider PageProvider
|
||||||
canCache := false
|
canCache := false
|
||||||
@ -64,7 +74,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
|||||||
actualQueries = currentProvider.GetCacheIDExtension(queryValues)
|
actualQueries = currentProvider.GetCacheIDExtension(queryValues)
|
||||||
|
|
||||||
if ph.CacheSettings.EnableContentsCaching {
|
if ph.CacheSettings.EnableContentsCaching {
|
||||||
cached := ph.getPageFromCache(request.URL, actualQueries)
|
cached := ph.getPageFromCache(request.URL.Path, actualQueries)
|
||||||
if cached != nil {
|
if cached != nil {
|
||||||
pageContent = cached.Content
|
pageContent = cached.Content
|
||||||
pageContentType = cached.ContentType
|
pageContentType = cached.ContentType
|
||||||
@ -76,7 +86,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
|||||||
pageContentType, pageContent, canCache = currentProvider.GetContents(queryValues)
|
pageContentType, pageContent, canCache = currentProvider.GetContents(queryValues)
|
||||||
lastMod = currentProvider.GetLastModified()
|
lastMod = currentProvider.GetLastModified()
|
||||||
if pageContentType != "" && canCache && ph.CacheSettings.EnableContentsCaching {
|
if pageContentType != "" && canCache && ph.CacheSettings.EnableContentsCaching {
|
||||||
ph.setPageToCache(request.URL, actualQueries, &CachedPage{
|
ph.setPageToCache(request.URL.Path, actualQueries, &CachedPage{
|
||||||
Content: pageContent,
|
Content: pageContent,
|
||||||
ContentType: pageContentType,
|
ContentType: pageContentType,
|
||||||
LastMod: lastMod,
|
LastMod: lastMod,
|
||||||
@ -143,7 +153,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
ph.PurgeTemplateCache(actualPagePath)
|
ph.PurgeTemplateCache(actualPagePath, request.URL.Path == "/")
|
||||||
ph.PurgeContentsCache(request.URL.Path, actualQueries)
|
ph.PurgeContentsCache(request.URL.Path, actualQueries)
|
||||||
utils.SetNeverCacheHeader(writer.Header())
|
utils.SetNeverCacheHeader(writer.Header())
|
||||||
utils.WriteResponseHeaderCanWriteBody(request.Method, writer, http.StatusOK, "")
|
utils.WriteResponseHeaderCanWriteBody(request.Method, writer, http.StatusOK, "")
|
||||||
@ -167,12 +177,12 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
|||||||
|
|
||||||
func (ph *PageHandler) PurgeContentsCache(path string, query string) {
|
func (ph *PageHandler) PurgeContentsCache(path string, query string) {
|
||||||
if ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge {
|
if ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge {
|
||||||
if path == "" {
|
if path == "/" {
|
||||||
ph.pageContentsCacheRWMutex.Lock()
|
ph.pageContentsCacheRWMutex.Lock()
|
||||||
ph.PageContentsCache = make(map[string]*CachedPage)
|
ph.PageContentsCache = make(map[string]*CachedPage)
|
||||||
ph.pageContentsCacheRWMutex.Unlock()
|
ph.pageContentsCacheRWMutex.Unlock()
|
||||||
} else {
|
} else {
|
||||||
if strings.HasSuffix(path, "/") {
|
if strings.HasSuffix(path, ".go/") {
|
||||||
ph.pageContentsCacheRWMutex.RLock()
|
ph.pageContentsCacheRWMutex.RLock()
|
||||||
toDelete := make([]string, len(ph.PageContentsCache))
|
toDelete := make([]string, len(ph.PageContentsCache))
|
||||||
theSize := 0
|
theSize := 0
|
||||||
@ -189,22 +199,24 @@ func (ph *PageHandler) PurgeContentsCache(path string, query string) {
|
|||||||
delete(ph.PageContentsCache, toDelete[i])
|
delete(ph.PageContentsCache, toDelete[i])
|
||||||
}
|
}
|
||||||
ph.pageContentsCacheRWMutex.Unlock()
|
ph.pageContentsCacheRWMutex.Unlock()
|
||||||
} else {
|
return
|
||||||
ph.pageContentsCacheRWMutex.Lock()
|
} else if strings.HasSuffix(path, "/") {
|
||||||
if query == "" {
|
path += indexName
|
||||||
delete(ph.PageContentsCache, path)
|
|
||||||
} else {
|
|
||||||
delete(ph.PageContentsCache, path+"?"+query)
|
|
||||||
}
|
|
||||||
ph.pageContentsCacheRWMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
ph.pageContentsCacheRWMutex.Lock()
|
||||||
|
if query == "" {
|
||||||
|
delete(ph.PageContentsCache, path)
|
||||||
|
} else {
|
||||||
|
delete(ph.PageContentsCache, path+"?"+query)
|
||||||
|
}
|
||||||
|
ph.pageContentsCacheRWMutex.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PageHandler) PurgeTemplateCache(path string) {
|
func (ph *PageHandler) PurgeTemplateCache(path string, all bool) {
|
||||||
if ph.CacheSettings.EnableTemplateCaching && ph.CacheSettings.EnableTemplateCachePurge {
|
if ph.CacheSettings.EnableTemplateCaching && ph.CacheSettings.EnableTemplateCachePurge {
|
||||||
if path == "" {
|
if all {
|
||||||
for _, pageProvider := range ph.PageProviders {
|
for _, pageProvider := range ph.PageProviders {
|
||||||
pageProvider.PurgeTemplate()
|
pageProvider.PurgeTemplate()
|
||||||
}
|
}
|
||||||
@ -215,36 +227,39 @@ func (ph *PageHandler) PurgeTemplateCache(path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (ph *PageHandler) getPageFromCache(urlIn *url.URL, cleanedQueries string) *CachedPage {
|
func (ph *PageHandler) getPageFromCache(pathIn string, cleanedQueries string) *CachedPage {
|
||||||
ph.pageContentsCacheRWMutex.RLock()
|
ph.pageContentsCacheRWMutex.RLock()
|
||||||
defer ph.pageContentsCacheRWMutex.RUnlock()
|
defer ph.pageContentsCacheRWMutex.RUnlock()
|
||||||
if strings.HasSuffix(urlIn.Path, "/") {
|
if strings.HasSuffix(pathIn, ".go/") {
|
||||||
return ph.PageContentsCache[strings.TrimRight(urlIn.Path, "/")]
|
return ph.PageContentsCache[strings.TrimRight(pathIn, "/")]
|
||||||
|
} else if strings.HasSuffix(pathIn, "/") {
|
||||||
|
pathIn += indexName
|
||||||
|
}
|
||||||
|
if cleanedQueries == "" {
|
||||||
|
return ph.PageContentsCache[pathIn]
|
||||||
} else {
|
} else {
|
||||||
if cleanedQueries == "" {
|
return ph.PageContentsCache[pathIn+"?"+cleanedQueries]
|
||||||
return ph.PageContentsCache[urlIn.Path]
|
|
||||||
} else {
|
|
||||||
return ph.PageContentsCache[urlIn.Path+"?"+cleanedQueries]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PageHandler) setPageToCache(urlIn *url.URL, cleanedQueries string, newPage *CachedPage) {
|
func (ph *PageHandler) setPageToCache(pathIn string, cleanedQueries string, newPage *CachedPage) {
|
||||||
ph.pageContentsCacheRWMutex.Lock()
|
ph.pageContentsCacheRWMutex.Lock()
|
||||||
defer ph.pageContentsCacheRWMutex.Unlock()
|
defer ph.pageContentsCacheRWMutex.Unlock()
|
||||||
if strings.HasSuffix(urlIn.Path, "/") {
|
if strings.HasSuffix(pathIn, ".go/") {
|
||||||
ph.PageContentsCache[strings.TrimRight(urlIn.Path, "/")] = newPage
|
ph.PageContentsCache[strings.TrimRight(pathIn, "/")] = newPage
|
||||||
|
return
|
||||||
|
} else if strings.HasSuffix(pathIn, "/") {
|
||||||
|
pathIn += indexName
|
||||||
|
}
|
||||||
|
if cleanedQueries == "" {
|
||||||
|
ph.PageContentsCache[pathIn] = newPage
|
||||||
} else {
|
} else {
|
||||||
if cleanedQueries == "" {
|
ph.PageContentsCache[pathIn+"?"+cleanedQueries] = newPage
|
||||||
ph.PageContentsCache[urlIn.Path] = newPage
|
|
||||||
} else {
|
|
||||||
ph.PageContentsCache[urlIn.Path+"?"+cleanedQueries] = newPage
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PageHandler) getAllowedMethodsForPath(pathIn string) []string {
|
func (ph *PageHandler) getAllowedMethodsForPath(pathIn string) []string {
|
||||||
if strings.HasSuffix(pathIn, "/") {
|
if pathIn == "/" || strings.HasSuffix(pathIn, ".go/") {
|
||||||
if (ph.CacheSettings.EnableTemplateCaching && ph.CacheSettings.EnableTemplateCachePurge) ||
|
if (ph.CacheSettings.EnableTemplateCaching && ph.CacheSettings.EnableTemplateCachePurge) ||
|
||||||
(ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge) {
|
(ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge) {
|
||||||
return []string{http.MethodHead, http.MethodGet, http.MethodOptions, http.MethodDelete}
|
return []string{http.MethodHead, http.MethodGet, http.MethodOptions, http.MethodDelete}
|
||||||
@ -271,6 +286,9 @@ func (ph *PageHandler) GetRegisteredPages() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PageHandler) GetCachedPages() []string {
|
func (ph *PageHandler) GetCachedPages() []string {
|
||||||
|
if ph.pageContentsCacheRWMutex == nil {
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
ph.pageContentsCacheRWMutex.RLock()
|
ph.pageContentsCacheRWMutex.RLock()
|
||||||
defer ph.pageContentsCacheRWMutex.RUnlock()
|
defer ph.pageContentsCacheRWMutex.RUnlock()
|
||||||
pages := make([]string, len(ph.PageContentsCache))
|
pages := make([]string, len(ph.PageContentsCache))
|
||||||
@ -283,6 +301,9 @@ func (ph *PageHandler) GetCachedPages() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PageHandler) GetNumberOfCachedPages() int {
|
func (ph *PageHandler) GetNumberOfCachedPages() int {
|
||||||
|
if ph.pageContentsCacheRWMutex == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
ph.pageContentsCacheRWMutex.RLock()
|
ph.pageContentsCacheRWMutex.RLock()
|
||||||
defer ph.pageContentsCacheRWMutex.RUnlock()
|
defer ph.pageContentsCacheRWMutex.RUnlock()
|
||||||
return len(ph.PageContentsCache)
|
return len(ph.PageContentsCache)
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
package pageHandler
|
package pageHandler
|
||||||
|
|
||||||
import "golang.captainalm.com/cityuni-webserver/pageHandler/pages/index"
|
import (
|
||||||
|
"golang.captainalm.com/cityuni-webserver/conf"
|
||||||
|
"golang.captainalm.com/cityuni-webserver/pageHandler/pages/index"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
var providers map[string]PageProvider
|
var providers map[string]PageProvider
|
||||||
|
|
||||||
func GetProviders(cacheTemplates bool, dataStorage string, pageHandler *PageHandler) map[string]PageProvider {
|
func GetProviders(cacheTemplates bool, dataStorage string, pageHandler *PageHandler, templateStorage string, pageSettings []conf.PageYaml, ymlDataFallback bool) map[string]PageProvider {
|
||||||
if providers == nil {
|
if providers == nil {
|
||||||
providers = make(map[string]PageProvider)
|
providers = make(map[string]PageProvider)
|
||||||
if pageHandler != nil {
|
if pageHandler != nil {
|
||||||
infoPage := newGoInfoPage(pageHandler, dataStorage, cacheTemplates)
|
infoPage := newGoInfoPage(pageHandler, dataStorage, cacheTemplates)
|
||||||
providers[infoPage.GetPath()] = infoPage //Go Information Page
|
providers[infoPage.GetPath()] = infoPage //Go Information Page
|
||||||
}
|
}
|
||||||
indexPage := index.NewPage(dataStorage, cacheTemplates)
|
for _, cpg := range pageSettings { //Register pages
|
||||||
providers[indexPage.GetPath()] = indexPage
|
if strings.EqualFold(cpg.PageName, index.PageName) {
|
||||||
|
indexPage := index.NewPage(dataStorage, cacheTemplates, templateStorage, cpg.GetPagePath(), ymlDataFallback)
|
||||||
|
providers[indexPage.GetPath()] = indexPage
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return providers
|
return providers
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,23 @@ package index
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AboutYaml struct {
|
type AboutYaml struct {
|
||||||
Title string `yaml:"title"`
|
Title string `yaml:"title"`
|
||||||
Content string `yaml:"content"`
|
Content string `yaml:"content"`
|
||||||
ThumbnailLocation string `yaml:"thumbnailLocation"`
|
ThumbnailLocation template.URL `yaml:"thumbnailLocation"`
|
||||||
ImageLocation string `yaml:"imageLocation"`
|
ImageLocation template.URL `yaml:"imageLocation"`
|
||||||
BirthYear int `yaml:"birthYear"`
|
ImageAltText string `yaml:"imageAltText"`
|
||||||
ContactEmail string `yaml:"contactEmail"`
|
BirthYear int `yaml:"birthYear"`
|
||||||
|
ContactEmail string `yaml:"contactEmail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ay AboutYaml) GetContent() template.HTML {
|
func (ay AboutYaml) GetContent() template.HTML {
|
||||||
return template.HTML(ay.Content)
|
return template.HTML(strings.ReplaceAll(strings.ReplaceAll(ay.Content, "#age#", strconv.Itoa(ay.GetAge())), "#birth#", strconv.Itoa(ay.BirthYear)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ay AboutYaml) GetAge() int {
|
func (ay AboutYaml) GetAge() int {
|
||||||
|
@ -1,12 +1,39 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
|
import "html/template"
|
||||||
|
|
||||||
type DataYaml struct {
|
type DataYaml struct {
|
||||||
HomeLink string `yaml:"homeLink"`
|
HeaderLinks map[string]template.URL `yaml:"headerLinks"`
|
||||||
PortfolioLink string `yaml:"portfolioLink"`
|
CSSBaseURL template.URL `yaml:"cssBaseURL"`
|
||||||
CSSBaseURL string `yaml:"cssBaseURL"`
|
CSSLightURL template.URL `yaml:"cssLightURL"`
|
||||||
CSSLightURL string `yaml:"cssLightURL"`
|
CSSDarkURL template.URL `yaml:"cssDarkURL"`
|
||||||
CSSDarkURL string `yaml:"cssDarkURL"`
|
JScriptURL template.URL `yaml:"jScriptURL"`
|
||||||
JScriptURL string `yaml:"jScriptURL"`
|
PlayVideoImageLocation template.URL `yaml:"playVideoImageLocation"`
|
||||||
About AboutYaml `yaml:"about"`
|
NoVideoImageLocation template.URL `yaml:"noVideoImageLocation"`
|
||||||
Entries []EntryYaml `yaml:"entries"`
|
LogoImageLocation template.URL `yaml:"logoImageLocation"`
|
||||||
|
SunImageLocation template.URL `yaml:"sunImageLocation"`
|
||||||
|
MoonImageLocation template.URL `yaml:"moonImageLocation"`
|
||||||
|
SortImageLocation template.URL `yaml:"sortImageLocation"`
|
||||||
|
About AboutYaml `yaml:"about"`
|
||||||
|
Entries []EntryYaml `yaml:"entries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dy DataYaml) GetHeaderLabels() []string {
|
||||||
|
if dy.HeaderLinks == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
toReturn := make([]string, len(dy.HeaderLinks))
|
||||||
|
i := 0
|
||||||
|
for key := range dy.HeaderLinks {
|
||||||
|
toReturn[i] = key
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return toReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dy DataYaml) GetHeaderLink(headerLabel string) template.URL {
|
||||||
|
if dy.HeaderLinks == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return dy.HeaderLinks[headerLabel]
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,55 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"golang.captainalm.com/cityuni-webserver/utils/yaml"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dateFormat = "2006-01-02"
|
const dateFormat = "01/2006"
|
||||||
|
|
||||||
type EntryYaml struct {
|
type EntryYaml struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Content string `yaml:"content"`
|
Content string `yaml:"content"`
|
||||||
StartDate time.Time `yaml:"startDate"`
|
StartDate yaml.DateType `yaml:"startDate"`
|
||||||
EndDate time.Time `yaml:"endDate"`
|
EndDate yaml.DateType `yaml:"endDate"`
|
||||||
VideoLocation string `yaml:"videoLocation"`
|
VideoLocation template.URL `yaml:"videoLocation"`
|
||||||
VideoContentType string `yaml:"videoContentType"`
|
VideoContentType string `yaml:"videoContentType"`
|
||||||
ThumbnailLocations []string `yaml:"thumbnailLocations"`
|
ThumbnailLocations []template.URL `yaml:"thumbnailLocations"`
|
||||||
ImageLocations []string `yaml:"imageLocations"`
|
ImageLocations []template.URL `yaml:"imageLocations"`
|
||||||
|
ImageAltTexts []string `yaml:"imageAltTexts"`
|
||||||
|
VideoThumbnailLocation template.URL `yaml:"videoThumbnailLocation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageReference struct {
|
||||||
|
ThumbnailLocation template.URL
|
||||||
|
ImageLocation template.URL
|
||||||
|
ImageAltText string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ey EntryYaml) GetVideoThumbnail(usual template.URL) template.URL {
|
||||||
|
if ey.VideoThumbnailLocation == "" {
|
||||||
|
return usual
|
||||||
|
} else {
|
||||||
|
return ey.VideoThumbnailLocation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ey EntryYaml) IsVideoLink() bool {
|
||||||
|
return strings.EqualFold(ey.VideoContentType, "text/uri-list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ey EntryYaml) GetStartDate() string {
|
func (ey EntryYaml) GetStartDate() string {
|
||||||
return ey.StartDate.Format(dateFormat)
|
return ey.StartDate.Format(dateFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ey EntryYaml) GetStartDateHTML() string {
|
||||||
|
return ey.StartDate.Format(http.TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
func (ey EntryYaml) GetEndDate() string {
|
func (ey EntryYaml) GetEndDate() string {
|
||||||
if ey.EndDate.IsZero() {
|
if ey.EndDate.IsZero() {
|
||||||
return ""
|
return ""
|
||||||
@ -30,11 +58,15 @@ func (ey EntryYaml) GetEndDate() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ey EntryYaml) GetEndDateHTML() string {
|
||||||
|
return ey.GetEndTime().Format(http.TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
func (ey EntryYaml) GetEndTime() time.Time {
|
func (ey EntryYaml) GetEndTime() time.Time {
|
||||||
if ey.EndDate.IsZero() {
|
if ey.EndDate.IsZero() {
|
||||||
return time.Now()
|
return time.Now()
|
||||||
} else {
|
} else {
|
||||||
return ey.EndDate
|
return ey.EndDate.Time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,5 +75,25 @@ func (ey EntryYaml) GetContent() template.HTML {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ey EntryYaml) GetDuration() time.Duration {
|
func (ey EntryYaml) GetDuration() time.Duration {
|
||||||
return ey.GetEndTime().Sub(ey.StartDate).Truncate(time.Second)
|
return ey.GetEndTime().Sub(ey.StartDate.Time).Truncate(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ey EntryYaml) GetInt64Duration() int64 {
|
||||||
|
return int64(ey.GetDuration())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ey EntryYaml) GetImageCount() int {
|
||||||
|
return int(math.Min(math.Min(float64(len(ey.ThumbnailLocations)), float64(len(ey.ImageLocations))), float64(len(ey.ImageAltTexts))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ey EntryYaml) GetImages() []ImageReference {
|
||||||
|
toReturn := make([]ImageReference, ey.GetImageCount())
|
||||||
|
for i := 0; i < len(ey.ThumbnailLocations) && i < len(ey.ImageLocations) && i < len(ey.ImageAltTexts); i++ {
|
||||||
|
toReturn[i] = ImageReference{
|
||||||
|
ThumbnailLocation: ey.ThumbnailLocations[i],
|
||||||
|
ImageLocation: ey.ImageLocations[i],
|
||||||
|
ImageAltText: ey.ImageAltTexts[i],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toReturn
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"golang.captainalm.com/cityuni-webserver/utils/io"
|
"golang.captainalm.com/cityuni-webserver/utils/io"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"html/template"
|
"html/template"
|
||||||
@ -12,24 +13,32 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const PageName = "index"
|
||||||
const templateName = "index.go.html"
|
const templateName = "index.go.html"
|
||||||
const yamlName = "index.go.yml"
|
|
||||||
|
|
||||||
func NewPage(dataStore string, cacheTemplates bool) *Page {
|
func NewPage(dataStore string, cacheTemplates bool, templateStore string, pagePath string, ymlDataFallback bool) *Page {
|
||||||
var ptm *sync.Mutex
|
var ptm *sync.Mutex
|
||||||
|
var sdm *sync.Mutex
|
||||||
if cacheTemplates {
|
if cacheTemplates {
|
||||||
ptm = &sync.Mutex{}
|
ptm = &sync.Mutex{}
|
||||||
|
sdm = &sync.Mutex{}
|
||||||
}
|
}
|
||||||
pageToReturn := &Page{
|
pageToReturn := &Page{
|
||||||
DataStore: dataStore,
|
YMLDataFallback: ymlDataFallback,
|
||||||
StoredDataMutex: &sync.Mutex{},
|
PagePath: pagePath,
|
||||||
|
DataPath: path.Join(dataStore, pagePath),
|
||||||
|
TemplatePath: path.Join(templateStore, templateName),
|
||||||
|
StoredDataMutex: sdm,
|
||||||
PageTemplateMutex: ptm,
|
PageTemplateMutex: ptm,
|
||||||
}
|
}
|
||||||
return pageToReturn
|
return pageToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
DataStore string
|
YMLDataFallback bool
|
||||||
|
PagePath string
|
||||||
|
DataPath string
|
||||||
|
TemplatePath string
|
||||||
StoredDataMutex *sync.Mutex
|
StoredDataMutex *sync.Mutex
|
||||||
StoredData *DataYaml
|
StoredData *DataYaml
|
||||||
LastModifiedData time.Time
|
LastModifiedData time.Time
|
||||||
@ -39,7 +48,7 @@ type Page struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) GetPath() string {
|
func (p *Page) GetPath() string {
|
||||||
return "/index.go"
|
return p.PagePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) GetLastModified() time.Time {
|
func (p *Page) GetLastModified() time.Time {
|
||||||
@ -51,6 +60,17 @@ func (p *Page) GetLastModified() time.Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) GetCacheIDExtension(urlParameters url.Values) string {
|
func (p *Page) GetCacheIDExtension(urlParameters url.Values) string {
|
||||||
|
toReturn := p.getNonThemedCleanQuery(urlParameters)
|
||||||
|
if toReturn != "" {
|
||||||
|
toReturn += "&"
|
||||||
|
}
|
||||||
|
if urlParameters.Has("light") {
|
||||||
|
toReturn += "light"
|
||||||
|
}
|
||||||
|
return strings.TrimRight(toReturn, "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Page) getNonThemedCleanQuery(urlParameters url.Values) string {
|
||||||
toReturn := ""
|
toReturn := ""
|
||||||
if urlParameters.Has("order") {
|
if urlParameters.Has("order") {
|
||||||
if theParameter := strings.ToLower(urlParameters.Get("order")); theParameter == "start" || theParameter == "end" || theParameter == "name" || theParameter == "duration" {
|
if theParameter := strings.ToLower(urlParameters.Get("order")); theParameter == "start" || theParameter == "end" || theParameter == "name" || theParameter == "duration" {
|
||||||
@ -59,12 +79,9 @@ func (p *Page) GetCacheIDExtension(urlParameters url.Values) string {
|
|||||||
}
|
}
|
||||||
if urlParameters.Has("sort") {
|
if urlParameters.Has("sort") {
|
||||||
if theParameter := strings.ToLower(urlParameters.Get("sort")); theParameter == "asc" || theParameter == "ascending" || theParameter == "desc" || theParameter == "descending" {
|
if theParameter := strings.ToLower(urlParameters.Get("sort")); theParameter == "asc" || theParameter == "ascending" || theParameter == "desc" || theParameter == "descending" {
|
||||||
toReturn += "sort=" + theParameter + "&"
|
toReturn += "sort=" + theParameter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if urlParameters.Has("light") {
|
|
||||||
toReturn += "light"
|
|
||||||
}
|
|
||||||
return strings.TrimRight(toReturn, "&")
|
return strings.TrimRight(toReturn, "&")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +95,9 @@ func (p *Page) GetContents(urlParameters url.Values) (contentType string, conten
|
|||||||
return "text/plain", []byte("Cannot Get Data.\r\n" + err.Error()), false
|
return "text/plain", []byte("Cannot Get Data.\r\n" + err.Error()), false
|
||||||
}
|
}
|
||||||
theMarshal := &Marshal{
|
theMarshal := &Marshal{
|
||||||
Data: *theData,
|
Data: *theData,
|
||||||
Light: urlParameters.Has("light"),
|
Light: urlParameters.Has("light"),
|
||||||
|
Parameters: template.URL(p.getNonThemedCleanQuery(urlParameters)),
|
||||||
}
|
}
|
||||||
switch strings.ToLower(urlParameters.Get("order")) {
|
switch strings.ToLower(urlParameters.Get("order")) {
|
||||||
case "end":
|
case "end":
|
||||||
@ -118,16 +136,12 @@ func (p *Page) getPageTemplate() (*template.Template, error) {
|
|||||||
defer p.PageTemplateMutex.Unlock()
|
defer p.PageTemplateMutex.Unlock()
|
||||||
}
|
}
|
||||||
if p.PageTemplate == nil {
|
if p.PageTemplate == nil {
|
||||||
thePath := templateName
|
stat, err := os.Stat(p.TemplatePath)
|
||||||
if p.DataStore != "" {
|
|
||||||
thePath = path.Join(p.DataStore, thePath)
|
|
||||||
}
|
|
||||||
stat, err := os.Stat(thePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p.LastModifiedTemplate = stat.ModTime()
|
p.LastModifiedTemplate = stat.ModTime()
|
||||||
loadedData, err := os.ReadFile(thePath)
|
loadedData, err := os.ReadFile(p.TemplatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -150,13 +164,18 @@ func (p *Page) getPageData() (*DataYaml, error) {
|
|||||||
defer p.StoredDataMutex.Unlock()
|
defer p.StoredDataMutex.Unlock()
|
||||||
}
|
}
|
||||||
if p.StoredData == nil {
|
if p.StoredData == nil {
|
||||||
thePath := yamlName
|
thePath := p.DataPath
|
||||||
if p.DataStore != "" {
|
|
||||||
thePath = path.Join(p.DataStore, thePath)
|
|
||||||
}
|
|
||||||
stat, err := os.Stat(thePath)
|
stat, err := os.Stat(thePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if p.YMLDataFallback && errors.Is(err, os.ErrNotExist) {
|
||||||
|
thePath += ".yml"
|
||||||
|
stat, err = os.Stat(thePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p.LastModifiedData = stat.ModTime()
|
p.LastModifiedData = stat.ModTime()
|
||||||
fileHandle, err := os.Open(thePath)
|
fileHandle, err := os.Open(thePath)
|
||||||
@ -178,15 +197,16 @@ func (p *Page) getPageData() (*DataYaml, error) {
|
|||||||
p.StoredData = dataYaml
|
p.StoredData = dataYaml
|
||||||
}
|
}
|
||||||
return dataYaml, nil
|
return dataYaml, nil
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return p.StoredData, nil
|
return p.StoredData, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSortValue(toCheckIn string) int8 {
|
func getSortValue(toCheckIn string) int8 {
|
||||||
if toCheckIn == "desc" || toCheckIn == "descending" {
|
if toCheckIn == "asc" || toCheckIn == "ascending" {
|
||||||
return -1
|
|
||||||
} else {
|
|
||||||
return 1
|
return 1
|
||||||
|
} else {
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,31 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import "sort"
|
import (
|
||||||
|
"html/template"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
type Marshal struct {
|
type Marshal struct {
|
||||||
Data DataYaml
|
Data DataYaml
|
||||||
|
Parameters template.URL
|
||||||
OrderStartDate int8
|
OrderStartDate int8
|
||||||
OrderEndDate int8
|
OrderEndDate int8
|
||||||
OrderName int8
|
OrderName int8
|
||||||
OrderDuration int8
|
OrderDuration int8
|
||||||
Light bool
|
Light bool
|
||||||
|
Counter int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Marshal) GetEntries() (toReturn []EntryYaml) {
|
func (m *Marshal) GetEntries() (toReturn []EntryYaml) {
|
||||||
toReturn = m.Data.Entries
|
toReturn = m.Data.Entries
|
||||||
if m.OrderStartDate > 0 {
|
if m.OrderStartDate > 0 {
|
||||||
sort.Slice(toReturn, func(i, j int) bool {
|
sort.Slice(toReturn, func(i, j int) bool {
|
||||||
return toReturn[i].StartDate.Before(toReturn[j].StartDate)
|
return toReturn[i].StartDate.Before(toReturn[j].StartDate.Time)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if m.OrderStartDate < 0 {
|
if m.OrderStartDate < 0 {
|
||||||
sort.Slice(toReturn, func(i, j int) bool {
|
sort.Slice(toReturn, func(i, j int) bool {
|
||||||
return toReturn[i].StartDate.After(toReturn[j].StartDate)
|
return toReturn[i].StartDate.After(toReturn[j].StartDate.Time)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if m.OrderEndDate > 0 {
|
if m.OrderEndDate > 0 {
|
||||||
@ -55,3 +60,9 @@ func (m Marshal) GetEntries() (toReturn []EntryYaml) {
|
|||||||
}
|
}
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Marshal) CounterPlusPlus() int {
|
||||||
|
toret := m.Counter
|
||||||
|
m.Counter++
|
||||||
|
return toret
|
||||||
|
}
|
||||||
|
31
utils/yaml/date-type.go
Normal file
31
utils/yaml/date-type.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dateFormat = "02/01/2006"
|
||||||
|
|
||||||
|
type DateType struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *DateType) MarshalYAML() (interface{}, error) {
|
||||||
|
return dt.Time.Format(dateFormat), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *DateType) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
var stringIn string
|
||||||
|
err := value.Decode(&stringIn)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pt, err := time.Parse(dateFormat, strings.TrimSpace(stringIn))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dt.Time = pt
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user