Compare commits
64 Commits
master
...
edge-2022-
Author | SHA1 | Date | |
---|---|---|---|
a58f9fae3a | |||
41c5e37a07 | |||
04f2f1105b | |||
c649064ae3 | |||
493f73c8d2 | |||
9404026610 | |||
ed254b47c6 | |||
76cbb19317 | |||
77e97a68bb | |||
9876cbd64c | |||
b8f3d12399 | |||
03b3c03f4f | |||
7312af98fe | |||
845105d8e6 | |||
049d5e332a | |||
d18064ac9b | |||
63bd03014a | |||
8d37f85476 | |||
798d771a48 | |||
d266582d7b | |||
a9b224026c | |||
560650644b | |||
8b4b3b5ff0 | |||
21aadcad96 | |||
baf769f32d | |||
7d68f0be91 | |||
b4ff56e7ca | |||
5c4b15f1ba | |||
220a2f5645 | |||
ee7e4c6d05 | |||
47866bc7b7 | |||
a20e1ec947 | |||
eecf2e9a70 | |||
9be55ab6dc | |||
1716f3c3af | |||
fd838dda97 | |||
98b1380d2a | |||
1fce0a6cf4 | |||
eee1530ca6 | |||
e8aeb2c1f7 | |||
6edc487643 | |||
24278e6e08 | |||
c148e71e7d | |||
f6711bd7f3 | |||
6bae274759 | |||
2bea4cb4b3 | |||
8f35dc92cf | |||
d8d509c001 | |||
3ffbee52af | |||
a41b07c58c | |||
c45ad78560 | |||
b9fd027a9f | |||
f7e19d597b | |||
42a239ddd4 | |||
1df8a6646e | |||
764b9d3239 | |||
0212c870c4 | |||
cf0e2186e5 | |||
4dc4abf229 | |||
f70b826e51 | |||
fe6340e6df | |||
19fca1a26f | |||
3d4b229d2d | |||
0b2ba868a3 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -7,3 +7,11 @@ dist/
|
||||
# Test data and logs folders
|
||||
.data/
|
||||
.idea/dataSources.xml
|
||||
|
||||
# CDN link
|
||||
cdn/
|
||||
cdn
|
||||
|
||||
# Config Link
|
||||
cnf/
|
||||
cnf
|
||||
|
4
Makefile
4
Makefile
@ -34,4 +34,8 @@ clean:
|
||||
deploy: build
|
||||
sudo systemctl stop wappcityuni
|
||||
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
|
||||
|
241
base.css
Normal file
241
base.css
Normal file
@ -0,0 +1,241 @@
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
main{
|
||||
padding-top: 90px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
.no-dec{
|
||||
text-decoration: none;
|
||||
}
|
||||
.no-lst-style{
|
||||
list-style: none;
|
||||
}
|
||||
.centered{
|
||||
text-align: center;
|
||||
}
|
||||
.content > p{
|
||||
word-break: break-word;
|
||||
-ms-word-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.header{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.home-button, .sort-button{
|
||||
display: inline-block;
|
||||
width: 64px;
|
||||
height: 82px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
.sort-button{
|
||||
cursor: pointer;
|
||||
}
|
||||
.home-button > div, .sort-button > div{
|
||||
display: inline;
|
||||
font-size: 60px;
|
||||
padding: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.nav{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
}
|
||||
.so-pane{
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
max-height: 0;
|
||||
}
|
||||
.so-pane > *{
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
padding: 2px;
|
||||
}
|
||||
.so-pane > label{
|
||||
background-color: transparent;
|
||||
}
|
||||
.nav-menu, .sort-menu, .data-hold{
|
||||
display: none;
|
||||
}
|
||||
.menu a{
|
||||
display: block;
|
||||
padding: 32px 18px;
|
||||
}
|
||||
.hmb{
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
background-color: transparent;
|
||||
padding: 40px 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{
|
||||
max-height: 100%;
|
||||
}
|
||||
.nav-menu:checked ~ .hmb .hmb-line{
|
||||
background: transparent;
|
||||
}
|
||||
.nav-menu:checked ~ .hmb .hmb-line::before{
|
||||
transform: rotate(-45deg);
|
||||
top:0;
|
||||
}
|
||||
.nav-menu:checked ~ .hmb .hmb-line::after{
|
||||
transform: rotate(45deg);
|
||||
top:0;
|
||||
}
|
||||
.sort-menu:checked ~ .so-pane{
|
||||
display: block;
|
||||
box-sizing: content-box;
|
||||
position: fixed;
|
||||
top: 85px;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
}
|
||||
.main-box{
|
||||
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;
|
||||
width: 100%;
|
||||
}
|
||||
.main-box > div, footer > p{
|
||||
margin: 3px 0;
|
||||
padding: 2px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.item-table{
|
||||
display: table;
|
||||
width: 100%;
|
||||
box-sizing: content-box;
|
||||
background-color: transparent;
|
||||
}
|
||||
.item-table > div{
|
||||
display: table-row;
|
||||
background-color: transparent;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
.item-table > div > div > div{
|
||||
padding: 2px;
|
||||
}
|
||||
.item-table-caption{
|
||||
display: table-caption !important;
|
||||
caption-side: bottom;
|
||||
}
|
||||
.item-table-full, .item-table-360, .item-table-caption{
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
background-color: transparent;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
.item-table-full{
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
}
|
||||
.item-table-360{
|
||||
display: none;
|
||||
}
|
||||
.image-box{
|
||||
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;
|
||||
}
|
||||
.image-box > a{
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
margin: 2px;
|
||||
}
|
||||
@media (min-width: 560px){
|
||||
.main-box > div, footer > p{
|
||||
margin: 5px;
|
||||
}
|
||||
.item-table-360{
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 360px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.image-box > a{
|
||||
border-width: 4px;
|
||||
margin: 10px;
|
||||
}
|
||||
.small-only-row{
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 640px){
|
||||
.nav{
|
||||
max-height: none;
|
||||
top: 0;
|
||||
position: relative;
|
||||
float: right;
|
||||
width: auto;
|
||||
}
|
||||
.menu li{
|
||||
float: left;
|
||||
}
|
||||
.menu a:hover{
|
||||
background-color: transparent;
|
||||
}
|
||||
.hmb{
|
||||
display: none;
|
||||
}
|
||||
}
|
38
dark.css
Normal file
38
dark.css
Normal file
@ -0,0 +1,38 @@
|
||||
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, .so-pane > *{
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.home-button:hover, .menu a:hover, .hmb:hover, .sort-button:hover, .sort-menu:checked ~ .sort-button, .so-pane > input, .so-pane > select{
|
||||
background-color: #606061;
|
||||
}
|
||||
.hmb-line, .hmb-line::before, .hmb-line::after{
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.main-box{
|
||||
background-color: #0f0f0f;
|
||||
}
|
||||
.item-table{
|
||||
background-color: #3f3f3f;
|
||||
}
|
||||
.so-pane{
|
||||
border-color: #3f3f3f;
|
||||
}
|
||||
.item-table > div > div, .item-table-caption{
|
||||
border-color: #f5deb3;
|
||||
}
|
||||
.image-box{
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
.image-box > a{
|
||||
border-color: #b0b0f0;
|
||||
}
|
@ -13,28 +13,25 @@
|
||||
text-align: center;
|
||||
background-color: mediumslateblue;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
margin: auto;
|
||||
text-align: left;
|
||||
border: black 1px solid;
|
||||
border-collapse: collapse;
|
||||
word-break: break-word;
|
||||
-ms-word-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
table, td {
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: lightsteelblue;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
td {
|
||||
width: 75%;
|
||||
}
|
||||
|
196
index.go.html
Normal file
196
index.go.html
Normal file
@ -0,0 +1,196 @@
|
||||
<!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"/>
|
||||
<title>City University Portfolio</title>
|
||||
<link rel="stylesheet" href="{{ .Data.CSSBaseURL }}"/>
|
||||
{{ if .Light }}
|
||||
<link rel="stylesheet" href="{{ .Data.CSSLightURL }}"/>
|
||||
{{ else }}
|
||||
<link rel="stylesheet" href="{{ .Data.CSSDarkURL }}"/>
|
||||
{{ end }}
|
||||
<script 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 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 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 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" title="Order and Sort Options"><div><img src="{{ .Data.SortImageLocation }}" width="64px" alt='↓'></div></label>
|
||||
<form class="so-pane" action="?" method="get" id="so-form">
|
||||
{{ if .Light }}
|
||||
<input id="so-theme" type="hidden" name="light" />
|
||||
{{ end }}
|
||||
{{ $sort := 0 }}
|
||||
<label class="no-dec" for="so-order">Order by:</label>
|
||||
<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>
|
||||
<label class="no-dec" for="so-sort">Sort:</label>
|
||||
<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>
|
||||
<input id="so-submit" type="submit" value="Commit">
|
||||
</form>
|
||||
<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="nav" id="nav">
|
||||
<ul class="menu no-lst-style">
|
||||
{{ range .Data.GetHeaderLabels }}
|
||||
<li><b><a href="{{ $.Data.GetHeaderLink . }}" class="no-dec" title="{{ . }}">{{ . }}</a></b></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="main-box">
|
||||
<div>
|
||||
<div class="item-table">
|
||||
<div>
|
||||
<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 class="small-only-row">
|
||||
<div class="item-table-full">
|
||||
<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 class="small-only-row">
|
||||
<div class="item-table-full">
|
||||
<div class="centered"><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>
|
||||
{{ range .GetEntries }}
|
||||
<div>
|
||||
<div class="item-table">
|
||||
<div>
|
||||
<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 class="small-only-row">
|
||||
<div class="item-table-full">
|
||||
{{ 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>
|
||||
{{ if eq .VideoLocation "" }}
|
||||
<img src="{{ $.Data.NoVideoImageLocation }}" alt="No Video" width="360px">
|
||||
{{ else }}
|
||||
<video controls width="360px">
|
||||
<source src="{{ .VideoLocation }}" type="{{ .VideoContentType }}">
|
||||
<a href="{{ .VideoLocation }}">The Video</a>
|
||||
</video>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-only-row">
|
||||
<div class="item-table-full">
|
||||
<div class="centered">
|
||||
{{ if eq .VideoLocation "" }}
|
||||
<img src="{{ $.Data.NoVideoImageLocation }}" alt="No Video" width="360px">
|
||||
{{ else }}
|
||||
<video controls width="360px">
|
||||
<source src="{{ .VideoLocation }}" type="{{ .VideoContentType }}">
|
||||
<a href="{{ .VideoLocation }}">The Video</a>
|
||||
</video>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ if not (eq .GetImageCount 0) }}
|
||||
<div class="item-table-caption">
|
||||
<div class="image-box">
|
||||
{{ range .GetImages }}
|
||||
<a href="{{ .ImageLocation }}"><img src="{{ .ThumbnailLocation }}" alt="{{ .ImageAltText }}" title="{{ .ImageAltText }}" width="240px"></a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="data-hold">{{ .GetInt64Duration }}</div>
|
||||
<div class="data-hold">{{ .GetStartDateHTML }}</div>
|
||||
<div class="data-hold">{{ .GetEndDateHTML }}</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</main>
|
||||
<footer>
|
||||
<p>
|
||||
Looking for the old static HTML page, here's the <a href="index.html">link</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
160
index.go.yml
Normal file
160
index.go.yml
Normal file
@ -0,0 +1,160 @@
|
||||
#This file is (C) Captain ALM
|
||||
#Under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
|
||||
cssBaseURL: "https://cityuni.captainalm.com/resources/assets/base.css"
|
||||
cssDarkURL: "https://cityuni.captainalm.com/resources/assets/dark.css"
|
||||
cssLightURL: "https://cityuni.captainalm.com/resources/assets/light.css"
|
||||
jScriptURL: "https://cityuni.captainalm.com/resources/assets/index.js"
|
||||
noVideoImageLocation: "https://cityuni.captainalm.com/resources/assets/novideo.png"
|
||||
logoImageLocation: "https://cityuni.captainalm.com/resources/assets/logo.png"
|
||||
moonImageLocation: "https://cityuni.captainalm.com/resources/assets/moon.png"
|
||||
sunImageLocation: "https://cityuni.captainalm.com/resources/assets/sun.png"
|
||||
sortImageLocation: "https://cityuni.captainalm.com/resources/assets/sort.png"
|
||||
headerLinks:
|
||||
Main Portfolio: "https://portfolio.captainalm.com/"
|
||||
Root Site Home: "https://www.captainalm.com/"
|
||||
Github: "https://github.com/Captain-ALM/"
|
||||
about:
|
||||
title: "Alfred Manville (Captain ALM)"
|
||||
content: >
|
||||
<p>
|
||||
Hello, I'm Alfred Manville (#age# Years Old).
|
||||
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 (Circa. 2000), Processing and Microsoft Smallbasic
|
||||
(I have also dabbled in C++, Python and Bash/Batch).
|
||||
I am currently in the progress of writing infrastructure software in Go, in the past, I wrote a command console in VB .net for my own
|
||||
pluggable libraries (I created a CMD emulator to get past the school disabling interactive CMD) and some network communication applications
|
||||
(Including a peer-to-peer VOIP client using NAudio as the audio library and my own network wrapper library).
|
||||
</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 accessing the RDP servers when I was in secondary school (Turns out remote apps
|
||||
is just a glorified application auto-launcher with window size detection).
|
||||
</p>
|
||||
<p>
|
||||
I also <a href="https://subsection.captainalm.com/">bake bread</a> although this sub-site is still under construction (Mostly learnt from my grandma).
|
||||
I also play video-games 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.
|
||||
My <a href="https://cdn.captainalm.com/download/keys/alfred@captainalm.com.asc">GPG Key</a> for my email address.
|
||||
</p>
|
||||
thumbnailLocation: "https://cityuni.captainalm.com/resources/assets/imageofyou_t.jpg"
|
||||
imageLocation: "https://cityuni.captainalm.com/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>
|
||||
startDate: "01/10/2021"
|
||||
endDate: "31/10/2021"
|
||||
videoLocation: "https://cityuni.captainalm.com/resources/stream/vid1.mp4"
|
||||
videoContentType: "video/mp4"
|
||||
thumbnailLocations:
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic1_t.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic2_t.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic3_t.png"
|
||||
imageLocations:
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic1.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic2.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic3.png"
|
||||
imageAltTexts:
|
||||
- "Level select screen."
|
||||
- "Empty content interface (Gameplay)."
|
||||
- "Level editor screen."
|
||||
- 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>
|
||||
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>
|
||||
startDate: "25/02/2022"
|
||||
endDate: "08/05/2022"
|
||||
videoLocation: "https://cityuni.captainalm.com/resources/stream/vid2.mp4"
|
||||
videoContentType: "video/mp4"
|
||||
thumbnailLocations:
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic4_t.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic5_t.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic6_t.png"
|
||||
imageLocations:
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic4.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic5.png"
|
||||
- "https://cityuni.captainalm.com/resources/assets/pic6.png"
|
||||
imageAltTexts:
|
||||
- "Cave level."
|
||||
- "Tutorial level."
|
||||
- "Training level."
|
||||
- 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/ShadowWorkExecutable.zip">https://cdn.captainalm.com/download/ShadowWorkExecutable.zip</a>
|
||||
</p>
|
||||
startDate: "20/01/2022"
|
||||
endDate: "30/01/2022"
|
||||
#videoLocation: "https://cityuni.captainalm.com/resources/stream/vid3.mp4"
|
||||
#videoContentType: "video/mp4"
|
||||
- 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 <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">
|
||||
<img src="https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc-nd.png" alt="License" height="16"></a>.
|
||||
</p>
|
||||
<p>
|
||||
Find the source code here: <a href="https://code.mrmelon54.xyz/alfred/cityuni-webserver">https://code.mrmelon54.xyz/alfred/cityuni-webserver</a>
|
||||
</p>
|
||||
startDate: "13/07/2022"
|
38
light.css
Normal file
38
light.css
Normal file
@ -0,0 +1,38 @@
|
||||
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, .so-pane > *{
|
||||
color: #1f1f1f;
|
||||
}
|
||||
.home-button:hover, .menu a:hover, .hmb:hover, .sort-button:hover, .sort-menu:checked ~ .sort-button, .so-pane > input, .so-pane > select{
|
||||
background-color: #9f9f9e;
|
||||
}
|
||||
.hmb-line, .hmb-line::before, .hmb-line::after{
|
||||
background: #1f1f1f;
|
||||
}
|
||||
.main-box{
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.item-table{
|
||||
background-color: #c0c0c0;
|
||||
}
|
||||
.so-pane{
|
||||
border-color: #c0c0c0;
|
||||
}
|
||||
.item-table > div > div, .item-table-caption{
|
||||
border-color: #0a214c;
|
||||
}
|
||||
.image-box{
|
||||
background-color: #b0b0b0;
|
||||
}
|
||||
.image-box > a{
|
||||
border-color: #4f4f0f;
|
||||
}
|
@ -7,13 +7,14 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const indexName = "index.go"
|
||||
|
||||
type PageHandler struct {
|
||||
PageContentsCache map[string]*CachedPage
|
||||
PageProviders map[string]PageProvider
|
||||
@ -50,7 +51,16 @@ func NewPageHandler(config conf.ServeYaml) *PageHandler {
|
||||
}
|
||||
|
||||
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
|
||||
canCache := false
|
||||
@ -64,7 +74,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
||||
actualQueries = currentProvider.GetCacheIDExtension(queryValues)
|
||||
|
||||
if ph.CacheSettings.EnableContentsCaching {
|
||||
cached := ph.getPageFromCache(request.URL, actualQueries)
|
||||
cached := ph.getPageFromCache(request.URL.Path, actualQueries)
|
||||
if cached != nil {
|
||||
pageContent = cached.Content
|
||||
pageContentType = cached.ContentType
|
||||
@ -76,7 +86,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
||||
pageContentType, pageContent, canCache = currentProvider.GetContents(queryValues)
|
||||
lastMod = currentProvider.GetLastModified()
|
||||
if pageContentType != "" && canCache && ph.CacheSettings.EnableContentsCaching {
|
||||
ph.setPageToCache(request.URL, actualQueries, &CachedPage{
|
||||
ph.setPageToCache(request.URL.Path, actualQueries, &CachedPage{
|
||||
Content: pageContent,
|
||||
ContentType: pageContentType,
|
||||
LastMod: lastMod,
|
||||
@ -143,7 +153,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
||||
}
|
||||
}
|
||||
case http.MethodDelete:
|
||||
ph.PurgeTemplateCache(actualPagePath)
|
||||
ph.PurgeTemplateCache(actualPagePath, request.URL.Path == "/")
|
||||
ph.PurgeContentsCache(request.URL.Path, actualQueries)
|
||||
utils.SetNeverCacheHeader(writer.Header())
|
||||
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) {
|
||||
if ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge {
|
||||
if path == "" {
|
||||
if path == "/" {
|
||||
ph.pageContentsCacheRWMutex.Lock()
|
||||
ph.PageContentsCache = make(map[string]*CachedPage)
|
||||
ph.pageContentsCacheRWMutex.Unlock()
|
||||
} else {
|
||||
if strings.HasSuffix(path, "/") {
|
||||
if strings.HasSuffix(path, ".go/") {
|
||||
ph.pageContentsCacheRWMutex.RLock()
|
||||
toDelete := make([]string, len(ph.PageContentsCache))
|
||||
theSize := 0
|
||||
@ -189,22 +199,24 @@ func (ph *PageHandler) PurgeContentsCache(path string, query string) {
|
||||
delete(ph.PageContentsCache, toDelete[i])
|
||||
}
|
||||
ph.pageContentsCacheRWMutex.Unlock()
|
||||
} else {
|
||||
ph.pageContentsCacheRWMutex.Lock()
|
||||
if query == "" {
|
||||
delete(ph.PageContentsCache, path)
|
||||
} else {
|
||||
delete(ph.PageContentsCache, path+"?"+query)
|
||||
}
|
||||
ph.pageContentsCacheRWMutex.Unlock()
|
||||
return
|
||||
} else if strings.HasSuffix(path, "/") {
|
||||
path += indexName
|
||||
}
|
||||
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 path == "" {
|
||||
if all {
|
||||
for _, pageProvider := range ph.PageProviders {
|
||||
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()
|
||||
defer ph.pageContentsCacheRWMutex.RUnlock()
|
||||
if strings.HasSuffix(urlIn.Path, "/") {
|
||||
return ph.PageContentsCache[strings.TrimRight(urlIn.Path, "/")]
|
||||
if strings.HasSuffix(pathIn, ".go/") {
|
||||
return ph.PageContentsCache[strings.TrimRight(pathIn, "/")]
|
||||
} else if strings.HasSuffix(pathIn, "/") {
|
||||
pathIn += indexName
|
||||
}
|
||||
if cleanedQueries == "" {
|
||||
return ph.PageContentsCache[pathIn]
|
||||
} else {
|
||||
if cleanedQueries == "" {
|
||||
return ph.PageContentsCache[urlIn.Path]
|
||||
} else {
|
||||
return ph.PageContentsCache[urlIn.Path+"?"+cleanedQueries]
|
||||
}
|
||||
return ph.PageContentsCache[pathIn+"?"+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()
|
||||
defer ph.pageContentsCacheRWMutex.Unlock()
|
||||
if strings.HasSuffix(urlIn.Path, "/") {
|
||||
ph.PageContentsCache[strings.TrimRight(urlIn.Path, "/")] = newPage
|
||||
if strings.HasSuffix(pathIn, ".go/") {
|
||||
ph.PageContentsCache[strings.TrimRight(pathIn, "/")] = newPage
|
||||
return
|
||||
} else if strings.HasSuffix(pathIn, "/") {
|
||||
pathIn += indexName
|
||||
}
|
||||
if cleanedQueries == "" {
|
||||
ph.PageContentsCache[pathIn] = newPage
|
||||
} else {
|
||||
if cleanedQueries == "" {
|
||||
ph.PageContentsCache[urlIn.Path] = newPage
|
||||
} else {
|
||||
ph.PageContentsCache[urlIn.Path+"?"+cleanedQueries] = newPage
|
||||
}
|
||||
ph.PageContentsCache[pathIn+"?"+cleanedQueries] = newPage
|
||||
}
|
||||
}
|
||||
|
||||
func (ph *PageHandler) getAllowedMethodsForPath(pathIn string) []string {
|
||||
if strings.HasSuffix(pathIn, "/") {
|
||||
if pathIn == "/" || strings.HasSuffix(pathIn, ".go/") {
|
||||
if (ph.CacheSettings.EnableTemplateCaching && ph.CacheSettings.EnableTemplateCachePurge) ||
|
||||
(ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge) {
|
||||
return []string{http.MethodHead, http.MethodGet, http.MethodOptions, http.MethodDelete}
|
||||
|
@ -2,20 +2,23 @@ package index
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AboutYaml struct {
|
||||
Title string `yaml:"title"`
|
||||
Content string `yaml:"content"`
|
||||
ThumbnailLocation string `yaml:"thumbnailLocation"`
|
||||
ImageLocation string `yaml:"imageLocation"`
|
||||
BirthYear int `yaml:"birthYear"`
|
||||
ContactEmail string `yaml:"contactEmail"`
|
||||
Title string `yaml:"title"`
|
||||
Content string `yaml:"content"`
|
||||
ThumbnailLocation template.URL `yaml:"thumbnailLocation"`
|
||||
ImageLocation template.URL `yaml:"imageLocation"`
|
||||
ImageAltText string `yaml:"imageAltText"`
|
||||
BirthYear int `yaml:"birthYear"`
|
||||
ContactEmail string `yaml:"contactEmail"`
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -1,12 +1,38 @@
|
||||
package index
|
||||
|
||||
import "html/template"
|
||||
|
||||
type DataYaml struct {
|
||||
HomeLink string `yaml:"homeLink"`
|
||||
PortfolioLink string `yaml:"portfolioLink"`
|
||||
CSSBaseURL string `yaml:"cssBaseURL"`
|
||||
CSSLightURL string `yaml:"cssLightURL"`
|
||||
CSSDarkURL string `yaml:"cssDarkURL"`
|
||||
JScriptURL string `yaml:"jScriptURL"`
|
||||
About AboutYaml `yaml:"about"`
|
||||
Entries []EntryYaml `yaml:"entries"`
|
||||
HeaderLinks map[string]template.URL `yaml:"headerLinks"`
|
||||
CSSBaseURL template.URL `yaml:"cssBaseURL"`
|
||||
CSSLightURL template.URL `yaml:"cssLightURL"`
|
||||
CSSDarkURL template.URL `yaml:"cssDarkURL"`
|
||||
JScriptURL template.URL `yaml:"jScriptURL"`
|
||||
NoVideoImageLocation template.URL `yaml:"noVideoImageLocation"`
|
||||
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,41 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"golang.captainalm.com/cityuni-webserver/utils/yaml"
|
||||
"html/template"
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const dateFormat = "2006-01-02"
|
||||
const dateFormat = "01/2006"
|
||||
|
||||
type EntryYaml struct {
|
||||
Name string `yaml:"name"`
|
||||
Content string `yaml:"content"`
|
||||
StartDate time.Time `yaml:"startDate"`
|
||||
EndDate time.Time `yaml:"endDate"`
|
||||
VideoLocation string `yaml:"videoLocation"`
|
||||
VideoContentType string `yaml:"videoContentType"`
|
||||
ThumbnailLocations []string `yaml:"thumbnailLocations"`
|
||||
ImageLocations []string `yaml:"imageLocations"`
|
||||
Name string `yaml:"name"`
|
||||
Content string `yaml:"content"`
|
||||
StartDate yaml.DateType `yaml:"startDate"`
|
||||
EndDate yaml.DateType `yaml:"endDate"`
|
||||
VideoLocation template.URL `yaml:"videoLocation"`
|
||||
VideoContentType string `yaml:"videoContentType"`
|
||||
ThumbnailLocations []template.URL `yaml:"thumbnailLocations"`
|
||||
ImageLocations []template.URL `yaml:"imageLocations"`
|
||||
ImageAltTexts []string `yaml:"imageAltTexts"`
|
||||
}
|
||||
|
||||
type ImageReference struct {
|
||||
ThumbnailLocation template.URL
|
||||
ImageLocation template.URL
|
||||
ImageAltText string
|
||||
}
|
||||
|
||||
func (ey EntryYaml) GetStartDate() string {
|
||||
return ey.StartDate.Format(dateFormat)
|
||||
}
|
||||
|
||||
func (ey EntryYaml) GetStartDateHTML() string {
|
||||
return ey.StartDate.Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
func (ey EntryYaml) GetEndDate() string {
|
||||
if ey.EndDate.IsZero() {
|
||||
return ""
|
||||
@ -30,11 +44,15 @@ func (ey EntryYaml) GetEndDate() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (ey EntryYaml) GetEndDateHTML() string {
|
||||
return ey.GetEndTime().Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
func (ey EntryYaml) GetEndTime() time.Time {
|
||||
if ey.EndDate.IsZero() {
|
||||
return time.Now()
|
||||
} else {
|
||||
return ey.EndDate
|
||||
return ey.EndDate.Time
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,5 +61,25 @@ func (ey EntryYaml) GetContent() template.HTML {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -17,12 +17,14 @@ const yamlName = "index.go.yml"
|
||||
|
||||
func NewPage(dataStore string, cacheTemplates bool) *Page {
|
||||
var ptm *sync.Mutex
|
||||
var sdm *sync.Mutex
|
||||
if cacheTemplates {
|
||||
ptm = &sync.Mutex{}
|
||||
sdm = &sync.Mutex{}
|
||||
}
|
||||
pageToReturn := &Page{
|
||||
DataStore: dataStore,
|
||||
StoredDataMutex: &sync.Mutex{},
|
||||
StoredDataMutex: sdm,
|
||||
PageTemplateMutex: ptm,
|
||||
}
|
||||
return pageToReturn
|
||||
@ -51,6 +53,17 @@ func (p *Page) GetLastModified() time.Time {
|
||||
}
|
||||
|
||||
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 := ""
|
||||
if urlParameters.Has("order") {
|
||||
if theParameter := strings.ToLower(urlParameters.Get("order")); theParameter == "start" || theParameter == "end" || theParameter == "name" || theParameter == "duration" {
|
||||
@ -59,12 +72,9 @@ func (p *Page) GetCacheIDExtension(urlParameters url.Values) string {
|
||||
}
|
||||
if urlParameters.Has("sort") {
|
||||
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, "&")
|
||||
}
|
||||
|
||||
@ -78,8 +88,9 @@ func (p *Page) GetContents(urlParameters url.Values) (contentType string, conten
|
||||
return "text/plain", []byte("Cannot Get Data.\r\n" + err.Error()), false
|
||||
}
|
||||
theMarshal := &Marshal{
|
||||
Data: *theData,
|
||||
Light: urlParameters.Has("light"),
|
||||
Data: *theData,
|
||||
Light: urlParameters.Has("light"),
|
||||
Parameters: template.URL(p.getNonThemedCleanQuery(urlParameters)),
|
||||
}
|
||||
switch strings.ToLower(urlParameters.Get("order")) {
|
||||
case "end":
|
||||
|
@ -1,9 +1,13 @@
|
||||
package index
|
||||
|
||||
import "sort"
|
||||
import (
|
||||
"html/template"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Marshal struct {
|
||||
Data DataYaml
|
||||
Parameters template.URL
|
||||
OrderStartDate int8
|
||||
OrderEndDate int8
|
||||
OrderName int8
|
||||
@ -15,12 +19,12 @@ func (m Marshal) GetEntries() (toReturn []EntryYaml) {
|
||||
toReturn = m.Data.Entries
|
||||
if m.OrderStartDate > 0 {
|
||||
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 {
|
||||
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 {
|
||||
|
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