Added Frontend

This commit is contained in:
2022-02-06 22:25:00 +01:00
parent daab052928
commit 303d58c87b
19 changed files with 1680 additions and 0 deletions

245
frontend/css/index.css Normal file
View File

@@ -0,0 +1,245 @@
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
background-color: #03071E;
color: #EAE2B7;
}
a {
color: inherit;
font-size: 125%;
text-decoration: none;
letter-spacing: 2px;
}
p {
font-size: 150%;
margin: 0;
}
h3 {
font-size: 200%;
margin: 0;
}
ul {
display: flex;
flex-direction: row;
font-size: 20px;
list-style: none;
}
ul li {
padding-right: 30px;
}
ol {
display: flex;
flex-direction: column;
list-style: georgian;
}
.header {
background-color: #03071E;
width: 100%;
height: 100px;
display: flex;
align-items: center;
justify-content: flex-end;
font-family: "Roboto", sans-serif;
}
.header-title {
margin: 100px 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: "Staatliches", sans-serif;
}
.header-title h1 {
font-size: 90px;
letter-spacing: 30px;
margin: 0;
}
.header-title h2 {
font-size: 30px;
letter-spacing: 10px;
margin: 0;
}
.divider {
height: 3px;
background-color: #ad080f;
}
.navigation {
background-color: black;
color: inherit;
font-family: "Roboto", sans-serif;
width: 100%;
padding-top: 1%;
padding-bottom: 1%;
height: 5%;
min-height: 50px;
display: flex;
align-items: center;
}
.navigation img {
margin-right: auto;
width: 3%;
min-width: 30px;
padding-left: 2.5%;
}
.navigation a {
padding: 0 2%;
font-size: 200%;
font-weight: bolder;
}
.article {
padding: 2%;
}
.article:hover {
color: white;
}
.article-body {
width: 60;
font-size: 80%;
font-weight: normal;
margin-top: 1%;
font-family: "Roboto Mono", monospace;
}
.article-body, .article-body-fulltext {
width: 100%;
}
.article-description {
font-weight: 500;
font-family: "Roboto Mono", monospace;
color: #D00000;
font-size: 75%;
margin-top: 0.5%;
}
.article-description:p {
white-space: pre;
}
.articles {
margin: auto;
width: 80%;
font-family: "Roboto", sans-serif;
}
.foot {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin: 2.5% 0;
}
.foot a {
font-family: "Roboto Mono", monospace;
font-weight: normal;
padding: 0 10%;
}
i {
margin-right: 20px;
}
.about-section {
font-family: "Roboto", sans-serif;
width: 60%;
text-align: center;
}
.legal-section {
font-family: "Roboto", sans-serif;
width: 60%;
}
.legal-section p {
margin-bottom: 5%;
}
.contact-section {
font-family: "Roboto", sans-serif;
width: 60%;
}
.contact-section p {
margin-bottom: 5%;
}
input {
border: 2px solid #D62828;
padding: 5px 3px;
background: #03071E;
color: #EAE2B7;
margin: 0 10px;
font-size: 1rem;
border-radius: 3px;
max-width: 200px;
width: 10%;
min-width: 100px;
}
@media screen and (max-width: 1000px) {
.navigation {
flex-direction: column;
}
.navigation img {
margin: 10px 0;
padding: 0;
}
.navigation * {
margin: 5px 0;
}
.header-title {
text-align: center;
}
.header-title h1 {
font-size: 50px;
letter-spacing: 20px;
}
.header-title h2 {
font-size: 20px;
letter-spacing: 10px;
}
.articles {
width: 95%;
}
.articles div {
margin: 10px 0;
}
h3 {
font-size: 130%;
}
p {
font-size: 100%;
}
.foot {
flex-direction: column;
}
}
@media screen and (max-width: 500px) {
.header-title h1 {
font-size: 40px;
letter-spacing: 5px;
}
.header-title h2 {
letter-spacing: 3px;
}
.article-description p {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.sass","index.css"],"names":[],"mappings":"AAMA;EACE,SAAA;EACA,UAAA;EACA,aAAA;EACA,sBAAA;EACA,yBAXiB;EAYjB,cAXW;ACMb;;ADOA;EACE,cAAA;EACA,eAAA;EACA,qBAAA;EACA,mBAAA;ACJF;;ADMA;EACE,eAAA;EACA,SAAA;ACHF;;ADKA;EACE,eAAA;EACA,SAAA;ACFF;;ADIA;EACE,aAAA;EACA,mBAAA;EACA,eAAA;EACA,gBAAA;ACDF;ADGE;EACE,mBAAA;ACDJ;;ADGA;EACE,aAAA;EACA,sBAAA;EACA,oBAAA;ACAF;;ADEA;EACE,yBA3CiB;EA4CjB,WAAA;EACA,aAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;EACA,iCAAA;ACCF;;ADCA;EACE,eAAA;EACA,aAAA;EACA,sBAAA;EACA,mBAAA;EACA,uBAAA;EACA,sCAAA;ACEF;ADAE;EACE,eAAA;EACA,oBAAA;EACA,SAAA;ACEJ;ADAE;EACE,eAAA;EACA,oBAAA;EACA,SAAA;ACEJ;;ADAA;EACE,WAAA;EACA,yBAAA;ACGF;;ADDA;EACE,uBAAA;EACA,cAAA;EACA,iCAAA;EACA,WAAA;EACA,eAAA;EACA,kBAAA;EACA,UAAA;EACA,gBAAA;EACA,aAAA;EACA,mBAAA;ACIF;ADFE;EACE,kBAAA;EACA,SAAA;EACA,eAAA;EACA,kBAAA;ACIJ;ADFE;EACE,aAAA;EACA,eAAA;EACA,mBAAA;ACIJ;;ADFA;EACE,WAAA;ACKF;ADHE;EACE,YAAA;ACKJ;;ADHA;EACE,SAAA;EACA,cAAA;EACA,mBAAA;EACA,cAAA;EACA,qCAAA;ACMF;;ADJA;EACE,WAAA;ACOF;;ADLA;EACE,gBAAA;EACA,qCAAA;EACA,cAAA;EACA,cAAA;EACA,gBAAA;ACQF;ADNE;EACE,gBAAA;ACQJ;;ADNA;EACE,YAAA;EACA,UAAA;EACA,iCAAA;ACSF;;ADPA;EACE,aAAA;EACA,mBAAA;EACA,8BAAA;EACA,WAAA;EACA,cAAA;ACUF;ADRE;EACE,qCAAA;EACA,mBAAA;EACA,cAAA;ACUJ;;ADRA;EACE,kBAAA;ACWF;;ADTA;EACE,iCAAA;EACA,UAAA;EACA,kBAAA;ACYF;ADPA;EACE,iCAAA;EACA,UAAA;ACSF;ADLA;EACE,iBAAA;ACOF;;ADJA;EACE,iCAAA;EACA,UAAA;ACOF;ADHA;EACE,iBAAA;ACKF;;ADFA;EACI,yBAAA;EACA,gBAAA;EACA,mBA7Ke;EA8Kf,cA7KS;EA8KT,cAAA;EACA,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,UAAA;EACA,gBAAA;ACKJ;;ADFA;EACE;IACE,sBAAA;ECKF;EDHE;IACE,cAAA;IACA,UAAA;ECKJ;EDHE;IACE,aAAA;ECKJ;;EDHA;IACE,kBAAA;ECMF;EDJE;IACE,eAAA;IACA,oBAAA;ECMJ;EDJE;IACE,eAAA;IACA,oBAAA;ECMJ;;EDJA;IACE,UAAA;ECOF;EDLE;IACE,cAAA;ECOJ;;EDLA;IACE,eAAA;ECQF;;EDPA;IACE,eAAA;ECUF;;EDRA;IACE,sBAAA;ECWF;AACF;ADRA;EAEI;IACE,eAAA;IACA,mBAAA;ECSJ;EDPE;IACE,mBAAA;ECSJ;;EDNE;IACE,aAAA;IACA,mBAAA;IACA,8BAAA;ECSJ;AACF","file":"index.css"}

235
frontend/css/index.sass Normal file
View File

@@ -0,0 +1,235 @@
$background-color: #03071E
$text-color: #EAE2B7
$secondary-text-color: #F77F00
$primary-color: #D62828
$secondary-color: #F77F00
body
margin: 0
padding: 0
display: flex
flex-direction: column
background-color: $background-color
color: $text-color
a
color: inherit
font-size: 125%
text-decoration: none
letter-spacing: 2px
p
font-size: 150%
margin: 0
h3
font-size: 200%
margin: 0
ul
display: flex
flex-direction: row
font-size: 20px
list-style: none
li
padding-right: 30px
ol
display: flex
flex-direction: column
list-style: georgian
.header
background-color: $background-color
width: 100%
height: 100px
display: flex
align-items: center
justify-content: flex-end
font-family: 'Roboto', sans-serif
.header-title
margin: 100px 0
display: flex
flex-direction: column
align-items: center
justify-content: center
font-family: 'Staatliches', sans-serif
h1
font-size: 90px
letter-spacing: 30px
margin: 0
h2
font-size: 30px
letter-spacing: 10px
margin: 0
.divider
height: 3px
background-color: #ad080f
.navigation
background-color: black
color: inherit
font-family: 'Roboto', sans-serif
width: 100%
padding-top: 1%
padding-bottom: 1%
height: 5%
min-height: 50px
display: flex
align-items: center
img
margin-right: auto
width: 3%
min-width: 30px
padding-left: 2.5%
a
padding: 0 2%
font-size: 200%
font-weight: bolder
.article
padding: 2%
&:hover
color: white
.article-body
width: 60
font-size: 80%
font-weight: normal
margin-top: 1%
font-family: 'Roboto Mono', monospace
.article-body, .article-body-fulltext
width: 100%
.article-description
font-weight: 500
font-family: 'Roboto Mono', monospace
color: #D00000
font-size: 75%
margin-top: 0.5%
&:p
white-space: pre
.articles
margin: auto
width: 80%
font-family: 'Roboto', sans-serif
.foot
display: flex
align-items: center
justify-content: space-between
width: 100%
margin: 2.5% 0
a
font-family: 'Roboto Mono', monospace
font-weight: normal
padding: 0 10%
i
margin-right: 20px
.about-section
font-family: "Roboto", sans-serif
width: 60%
text-align: center
margin:auto
.legal-section
font-family: "Roboto", sans-serif
width: 60%
margin:auto
.legal-section p
margin-bottom: 5%
.contact-section
font-family: "Roboto", sans-serif
width: 60%
margin:auto
.contact-section p
margin-bottom: 5%
input
border: 2px solid $primary-color
padding: 5px 3px
background: $background-color
color: $text-color
margin: 0 10px
font-size: 1rem
border-radius: 3px
max-width: 200px
width: 10%
min-width: 100px
@media screen and (max-width: 1000px)
.navigation
flex-direction: column
img
margin: 10px 0
padding: 0
*
margin: 5px 0
.header-title
text-align: center
h1
font-size: 50px
letter-spacing: 20px
h2
font-size: 20px
letter-spacing: 10px
.articles
width: 95%
div
margin: 10px 0
h3
font-size: 130%
p
font-size: 100%
.foot
flex-direction: column
@media screen and (max-width: 500px)
.header-title
h1
font-size: 40px
letter-spacing: 5px
h2
letter-spacing: 3px
.article-description
p
display: flex
flex-direction: row
justify-content: space-between

46
frontend/error/404.html Normal file
View File

@@ -0,0 +1,46 @@
<html>
<meta charset="utf-8">
<title>Error 404</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Mono&display=swap" rel="stylesheet">
<body>
<div class="ErrorDiv">
<h1>Error 404</h1>
<h2>Page Not Found</h2>
</div>
</body>
<style>
.ErrorDiv {
position: absolute;
margin: auto;
width: 50%;
text-align: center;
top: 50%;
left: 25%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
body {
background-color: #03071e;
}
h1 {
margin: 0;
font-size: 800%;
font-family: 'Roboto Mono', monospace;
color: #ffffff;
}
h2 {
margin: 0;
font-size: 400%;
font-family: 'Roboto Mono', monospace;
color: #ffffff;
}
</style>
</html>

46
frontend/error/500.html Normal file
View File

@@ -0,0 +1,46 @@
<html>
<meta charset="utf-8">
<title>Error 500</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Mono&display=swap" rel="stylesheet">
<body>
<div class="ErrorDiv">
<h1>Error 500</h1>
<h2>Internal Server Error</h2>
</div>
</body>
<style>
.ErrorDiv {
position: absolute;
margin: auto;
width: 50%;
text-align: center;
top: 50%;
left: 25%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
body {
background-color: #03071e;
}
h1 {
margin: 0;
font-size: 800%;
font-family: 'Roboto Mono', monospace;
color: #ffffff;
}
h2 {
margin: 0;
font-size: 400%;
font-family: 'Roboto Mono', monospace;
color: #ffffff;
}
</style>
</html>

32
frontend/html/about.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<link rel="stylesheet" href="../css/index.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Roboto:wght@400;700&family=Staatliches:wght@400;500&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="navigation">
<img id="logo" src="../img/logodark.svg">
<a href="../">Home</a>
<a href="about.html">About</a>
</div>
<div class="about-section">
<h1>About</h1>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
</div>
</body>
<footer>
<div class="foot">
<a href="about.html">ABOUT</a>
<a href="contact.html">CONTACT</a>
<a href="legal-notice.html">LEGAL NOTICE</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<meta charset="utf-8">
<title>{{.Title}}</title>
<link rel="icon" type="image/x-icon" href="img/logodark.svg">
<base href="{{.BasePath}}">
<link rel="stylesheet" href="css/index.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="{{.Title}}">
<meta name="og:title" content="{{.Title}}">
<meta name="description" content="{{.Summary}}">
<meta name="og:description" content="{{.Summary}}">
<link rel="img" src="{{.Image}}">
<meta name="og:image" content="{{.Image}}">
{{if .Authors}}
<meta name="author" content="{{index .Authors 0}}">
{{end}}
<meta name="keywords" content="{{.Tags}}">
</head>
<body>
<div class="navigation">
<img id="logo" src="img/logodark.svg">
<a href="#">Home</a>
<a href="html/about.html">About</a>
</div>
<div class="content">
<div class="articles">
<div class="article">
<div class="article-header">
<h3>{{.Title}}</h3>
</div>
{{if .Image}}
<div class="article-image">
<img src="{{.Image}}" alt="">
</div>
{{end}}
<div class="article-date">
<p>{{.Date}}</p>
{{if .Modified}}
<i>(modified)</i>
{{end}}
</div>
<div class="article-authors">
{{range .Authors}}
<p>{{.}}</p>
{{end}}
</div>
<div class="article-tags">
{{range .Tags}}
<p>{{.}}</p>
{{end}}
</div>
{{if .Summary}}
<div class="article-summary">
<p>{{.Summary}}</p>
</div>
{{end}}
<div class="article-body fulltext">
{{.Content}}
</div>
</div>
</div>
</div>
</body>
<footer>
<div class="foot">
<a href="html/about.html">ABOUT</a>
<a href="html/contact.html">CONTACT</a>
<a href="html/legal-notice.html">LEGAL NOTICE</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<title>Contact</title>
<link rel="stylesheet" href="../css/index.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Roboto:wght@400;700&family=Staatliches:wght@400;500&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="navigation">
<img id="logo" src="../img/logodark.svg" alt="Logo">
<a href="../">Home</a>
<a href="about.html">About</a>
</div>
<div class="contact-section">
<h1>Contact</h1>
<p>Twitter: theadversary</p>
<p>Instagram: theadversary</p>
<p>E-Mail: contact@theadversary.org</p>
<p>Post: Jahnstraße 5, Hühnfeld, Hessen, Deutschland</p>
</div>
</body>
<footer>
<div class="foot">
<a href="about.html">ABOUT</a>
<a href="contact.html">CONTACT</a>
<a href="legal-notice.html">LEGAL NOTICE</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<meta charset="utf-8">
<title>THE ADVERSARY</title>
<link rel="icon" type="image/x-icon" href="img/logodark.svg">
<base href="{{.BasePath}}">
<link rel="stylesheet" href="css/index.css">
<script src="js/api.js" type="text/javascript"></script>
<script src="js/main.js" type="text/javascript" defer></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Roboto:wght@400;700&family=Staatliches:wght@400;500&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="navigation">
<img id="logo" src="img/logodark.svg">
<a href="#">Home</a>
<a href="html/about.html">About</a>
<input oninput="updateSeach(this.value)" type="text" placeholder="Search" name="" value="">
</div>
<div class="header-title">
<h1>The Adversary</h1>
<h2>Assault penguins to the Power</h2>
</div>
<div class="content">
<div id="articles" class="articles">
<!--
<a href="#">
<div class="article">
<div class="article-header">
<h3>Java is trash, and here is the proof.</h3>
</div>
<div class="article-description">
<p><i>OPINION</i><i>DAVID</i><i>15.12.2021</i></p>
</div>
<div class="article-body">
<p>After a recent vulnerability voices get louder that demand to retire the language.</p>
</div>
</div>
</a>
<div class="divider"></div>
-->
</div>
</div>
</body>
<footer>
<div class="foot">
<a href="html/about.html">ABOUT</a>
<a href="html/contact.html">CONTACT</a>
<a href="html/legal-notice.html">LEGAL NOTICE</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<link rel="stylesheet" href="../css/index.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Roboto:wght@400;700&family=Staatliches:wght@400;500&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="navigation">
<img id="logo" src="../img/logodark.svg">
<a href="../">Home</a>
<a href="about.html">About</a>
</div>
<div class="legal-section">
<h1>Legal Notice</h1>
<p>The information contained in this website is for general information purposes only. The information is provided by The Adversary and while we endeavour to keep the information up to date and correct, we make no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the website or the information, products, services, or related graphics contained on the website for any purpose. Any reliance you place on such information is therefore strictly at your own risk.</p>
<p>Through this website you are able to link to other websites which are not under the control of The Adversary. We have no control over the nature, content and availability of those sites. The inclusion of any links does not necessarily imply a recommendation or endorse the views expressed within them.</p>
<p>Every effort is made to keep the website up and running smoothly. However, The Adversary takes no responsibility for, and will not be liable for, the website being temporarily unavailable due to technical issues beyond our control.</p>
</div>
</body>
<footer>
<div class="foot">
<a href="about.html">ABOUT</a>
<a href="contact.html">CONTACT</a>
<a href="legal-notice.html">LEGAL NOTICE</a>
</div>
</footer>
</html>

98
frontend/img/logodark.svg Normal file
View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="LOGO-FINAL-DARK.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 17.197914 15.875005"
height="15.875005mm"
width="17.197914mm">
<defs
id="defs2">
<inkscape:perspective
id="perspective1549"
inkscape:persp3d-origin="105 : -182.125 : 1"
inkscape:vp_z="210 : -132.625 : 1"
inkscape:vp_y="0 : 999.99997 : 0"
inkscape:vp_x="0 : -132.625 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<sodipodi:namedview
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
showguides="true"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="-8"
inkscape:window-height="1017"
inkscape:window-width="1920"
showgrid="true"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="36.490448"
inkscape:cx="22.175528"
inkscape:zoom="22.627417"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base">
<inkscape:grid
originy="-120.38541"
originx="-88.635412"
id="grid833"
type="xygrid" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-88.635416,-120.38542)"
inkscape:label="Background"
id="layer2"
inkscape:groupmode="layer">
<path
id="path1541"
d="m 91.28125,136.26042 h 1.322917 l 5.291666,-10.58333 h -1.322915 z"
style="fill:#003049;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path1682"
d="m 96.572915,128.32292 h 0.529167 l 1.852083,1.05833 h -0.529166 z"
style="fill:#003049;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
transform="translate(-88.635416,-120.38542)"
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Ebene 1">
<path
transform="scale(0.26458333)"
d="m 365,455 -30,60 h 10 l 10,-20 10,-5 10,5 10,20 h 10 z m 0,20 7,14 -7,-4 -7,4 z"
style="fill:#d62828;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.944882;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1468" />
<path
id="path1678"
d="m 96.572915,120.38542 h 1.322917 l 7.937498,15.87499 h -1.32291 z"
style="fill:#003049;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="LOGO-FINAL-LIGHT.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 17.197914 15.875005"
height="15.875005mm"
width="17.197914mm">
<defs
id="defs2">
<inkscape:perspective
id="perspective1549"
inkscape:persp3d-origin="105 : -182.125 : 1"
inkscape:vp_z="210 : -132.625 : 1"
inkscape:vp_y="0 : 999.99997 : 0"
inkscape:vp_x="0 : -132.625 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<sodipodi:namedview
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
showguides="true"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="-8"
inkscape:window-height="1017"
inkscape:window-width="1920"
showgrid="true"
inkscape:document-rotation="0"
inkscape:current-layer="layer2"
inkscape:document-units="mm"
inkscape:cy="35.054888"
inkscape:cx="18.899507"
inkscape:zoom="8"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base">
<inkscape:grid
originy="-120.38541"
originx="-88.635412"
id="grid833"
type="xygrid" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-88.635416,-120.38542)"
inkscape:label="Background"
id="layer2"
inkscape:groupmode="layer">
<path
id="path1541"
d="m 91.28125,136.26042 h 1.322917 l 5.291666,-10.58333 h -1.322915 z"
style="fill:#d62828;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path1682"
d="m 96.572915,128.32292 h 0.529167 l 1.852083,1.05833 h -0.529166 z"
style="fill:#d62828;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
transform="translate(-88.635416,-120.38542)"
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Ebene 1">
<path
transform="scale(0.26458333)"
d="m 365,455 -30,60 h 10 l 10,-20 10,-5 10,5 10,20 h 10 z m 0,20 7,14 -7,-4 -7,4 z"
style="fill:#003049;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.944882;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1468" />
<path
id="path1678"
d="m 96.572915,120.38542 h 1.322917 l 7.937498,15.87499 h -1.32291 z"
style="fill:#d62828;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

198
frontend/js/api.js Normal file
View File

@@ -0,0 +1,198 @@
const prefix = document.getElementsByTagName("base")[0].href.replace(/(?=.*)\/$/gm, "");
async function unknownResponse(resp) {
let text = await resp.text();
try {
let json = JSON.parse(text);
return new Error(`${json["message"]} (${resp.status})`);
}
catch (error) {
return new Error(`Server sent unknown error: ${text} (${resp.status})`);
}
}
function buildQuery(options) {
let query = [];
options.forEach(element => {
if (element[1] !== undefined) {
if (element[1] instanceof Date) {
element[1] = element[1].getSeconds();
}
else if (element[1] instanceof Array) {
element[1] = JSON.stringify(element[1]);
}
query.push(`${element[0]}=${element[1]}`);
}
});
return query.join("&");
}
async function login(username, password) {
let result = await fetch(`${prefix}/api/login`, {
method: "POST",
body: JSON.stringify({ "username": username, "password": password }),
credentials: "same-origin"
});
switch (result.status) {
case 200:
return;
case 401:
throw new Error("Wrong username and/or password");
default:
throw await unknownResponse(result);
}
}
async function authors(name, limit) {
let query = [["name", name], ["limit", limit]];
let result = await fetch(`${prefix}/api/authors?${buildQuery(query)}`);
if (result.status == 200) {
return await result.json();
}
else {
throw await unknownResponse(result);
}
}
async function tags(name, limit) {
let query = [["name", name], ["limit", limit]];
let result = await fetch(`${prefix}/api/tags?${buildQuery(query)}`);
if (result.status == 200) {
return await result.json();
}
else {
throw await unknownResponse(result);
}
}
async function recent(limit = 20) {
let query = [["limit", limit]];
let result = await fetch(`${prefix}/api/recent?${buildQuery(query)}`);
if (result.status == 200) {
return await result.json();
}
else {
throw await unknownResponse(result);
}
}
async function search(q) {
let query = [["q", q.query], ["from", q.from], ["to", q.to], ["authors", q.authors], ["tags", q.tags], ["limit", q.limit]];
let result = await fetch(`${prefix}/api/search?${buildQuery(query)}`);
if (result.status == 200) {
return await result.json();
}
else {
throw unknownResponse(result);
}
}
async function getArticle(id) {
let query = [["id", id]];
let result = await fetch(`${prefix}/api/article?${buildQuery(query)}`, {
method: "GET"
});
switch (result.status) {
case 200:
return await result.json();
case 401:
throw new Error("Not authorized");
case 404:
throw new Error("Article not found");
default:
throw await unknownResponse(result);
}
}
async function uploadArticle(payload) {
let result = await fetch(`${prefix}/api/article`, {
method: "POST",
body: JSON.stringify(payload)
});
switch (result.status) {
case 201:
return await result.json();
case 401:
throw new Error("Not authorized");
case 409:
throw new Error("An article with the same title already exists");
default:
throw await unknownResponse(result);
}
}
async function editArticle(payload) {
let result = await fetch(`${prefix}/api/article`, {
method: "PATCH",
body: JSON.stringify(payload)
});
let json = await result.json();
switch (result.status) {
case 201:
return json;
case 401:
throw new Error("Not authorized");
case 404:
throw new Error("Could not find article");
case 409:
throw new Error("An article with the same title already exists");
default:
throw await unknownResponse(result);
}
}
async function deleteArticle(id) {
let result = await fetch(`${prefix}/api/article`, {
method: "DELETE",
body: JSON.stringify({ "id": id })
});
switch (result.status) {
case 200:
return;
case 401:
throw new Error("Not authorized");
case 404:
throw new Error("Could not find article");
default:
throw await unknownResponse(result);
}
}
async function getAssets(name, limit = 20) {
let query = [["q", name], ["limit", limit]];
let result = await fetch(`${prefix}/api/assets?${buildQuery(query)}`, {
method: "GET",
});
switch (result.status) {
case 200:
return await result.json();
case 401:
throw new Error("Not authorized");
default:
throw await unknownResponse(result);
}
}
async function addAsset(name, content) {
let result = await fetch(`${prefix}/api/assets`, {
method: "POST",
body: JSON.stringify({
"name": name,
"content": content,
})
});
switch (result.status) {
case 201:
return await result.json();
case 401:
throw new Error("Not authorized");
case 409:
throw new Error("An asset with the same name already exists");
default:
throw await unknownResponse(result);
}
}
async function deleteAsset(id) {
let result = await fetch(`${prefix}/api/assets`, {
method: "DELETE",
body: JSON.stringify({ "id": id })
});
switch (result.status) {
case 200:
return;
case 401:
throw new Error("Not authorized");
case 404:
throw new Error("An asset with this id does not exist");
default:
throw await unknownResponse(result);
}
}
//# sourceMappingURL=api.js.map

1
frontend/js/api.js.map Normal file

File diff suppressed because one or more lines are too long

277
frontend/js/api.ts Normal file
View File

@@ -0,0 +1,277 @@
const prefix = document.getElementsByTagName("base")[0].href.replace(/(?=.*)\/$/gm, "");
async function unknownResponse(resp: Response): Promise<Error> {
let text = await resp.text()
try {
let json = JSON.parse(text)
return new Error(`${json["message"]} (${resp.status})`)
} catch (error) {
return new Error(`Server sent unknown error: ${text} (${resp.status})`)
}
}
function buildQuery(options: any[][]): string {
let query: string[] = []
options.forEach(element => {
if (element[1] !== undefined) {
if (element[1] instanceof Date) {
element[1] = element[1].getSeconds()
} else if (element[1] instanceof Array) {
element[1] = JSON.stringify(element[1])
}
query.push(`${element[0]}=${element[1]}`)
}
});
return query.join("&")
}
interface Author {
id: number,
name: string,
information: string
}
interface ArticleSummary {
id: number,
title: string,
summary: string
authors: Author[],
image?: string,
tags: string[],
created: Date,
modified?: Date,
link: string
}
interface Asset {
id: number,
name: string,
link: string
}
async function login(username: string, password: string): Promise<void> {
let result = await fetch(`${prefix}/api/login`, {
method: "POST",
body: JSON.stringify({"username": username, "password": password}),
credentials: "same-origin"
})
switch (result.status) {
case 200:
return
case 401:
throw new Error("Wrong username and/or password")
default:
throw await unknownResponse(result)
}
}
async function authors(name?: string, limit?: number): Promise<Author[]> {
let query = [["name", name], ["limit", limit]]
let result = await fetch(`${prefix}/api/authors?${buildQuery(query)}`)
if (result.status == 200) {
return await result.json()
} else {
throw await unknownResponse(result)
}
}
async function tags(name?: string, limit?: number): Promise<string[]> {
let query = [["name", name], ["limit", limit]]
let result = await fetch(`${prefix}/api/tags?${buildQuery(query)}`)
if (result.status == 200) {
return await result.json()
} else {
throw await unknownResponse(result)
}
}
async function recent(limit: number = 20): Promise<ArticleSummary[]> {
let query = [["limit", limit]]
let result = await fetch(`${prefix}/api/recent?${buildQuery(query)}`)
if (result.status == 200) {
return await result.json()
} else {
throw await unknownResponse(result)
}
}
interface SearchQuery {
query?: string,
from?: Date,
to?: Date,
authors?: number[],
tags?: string[],
limit?: number
}
async function search(q: SearchQuery): Promise<ArticleSummary[]> {
let query = [["q", q.query], ["from", q.from], ["to", q.to], ["authors", q.authors], ["tags", q.tags], ["limit", q.limit]]
let result = await fetch(`${prefix}/api/search?${buildQuery(query)}`)
if (result.status == 200) {
return await result.json()
} else {
throw unknownResponse(result)
}
}
interface ArticleGetPayload {
title: string,
summary: string,
authors: number[],
image: string,
tags: string[],
link: string
content: string
}
async function getArticle(id: number): Promise<ArticleGetPayload> {
let query = [["id", id]]
let result = await fetch(`${prefix}/api/article?${buildQuery(query)}`, {
method: "GET"
})
switch (result.status) {
case 200:
return await result.json()
case 401:
throw new Error("Not authorized")
case 404:
throw new Error("Article not found")
default:
throw await unknownResponse(result)
}
}
interface ArticleUploadPayload {
title: string,
summary: string,
authors: number[],
image?: string,
tags: string[],
link?: string
content: string
}
async function uploadArticle(payload: ArticleUploadPayload): Promise<ArticleSummary> {
let result = await fetch(`${prefix}/api/article`, {
method: "POST",
body: JSON.stringify(payload)
})
switch (result.status) {
case 201:
return await result.json()
case 401:
throw new Error("Not authorized")
case 409:
throw new Error("An article with the same title already exists")
default:
throw await unknownResponse(result)
}
}
interface ArticleEditPayload {
id: number,
title?: string,
summary?: string,
authors?: number[],
image?: string,
tags?: string[],
link?: string
content?: string
}
async function editArticle(payload: ArticleEditPayload): Promise<ArticleSummary> {
let result = await fetch(`${prefix}/api/article`, {
method: "PATCH",
body: JSON.stringify(payload)
})
let json = await result.json()
switch (result.status) {
case 201:
return json
case 401:
throw new Error("Not authorized")
case 404:
throw new Error("Could not find article")
case 409:
throw new Error("An article with the same title already exists")
default:
throw await unknownResponse(result)
}
}
async function deleteArticle(id: number): Promise<void> {
let result = await fetch(`${prefix}/api/article`, {
method: "DELETE",
body: JSON.stringify({"id": id})
})
switch (result.status) {
case 200:
return
case 401:
throw new Error("Not authorized")
case 404:
throw new Error("Could not find article")
default:
throw await unknownResponse(result)
}
}
async function getAssets(name?: string, limit: number = 20): Promise<Asset[]> {
let query = [["q", name], ["limit", limit]]
let result = await fetch(`${prefix}/api/assets?${buildQuery(query)}`, {
method: "GET",
})
switch (result.status) {
case 200:
return await result.json()
case 401:
throw new Error("Not authorized")
default:
throw await unknownResponse(result)
}
}
async function addAsset(name: string, content: string): Promise<Asset> {
let result = await fetch(`${prefix}/api/assets`, {
method: "POST",
body: JSON.stringify({
"name": name,
"content": content,
})
})
switch (result.status) {
case 201:
return await result.json()
case 401:
throw new Error("Not authorized")
case 409:
throw new Error("An asset with the same name already exists")
default:
throw await unknownResponse(result)
}
}
async function deleteAsset(id: number): Promise<void> {
let result = await fetch(`${prefix}/api/assets`, {
method: "DELETE",
body: JSON.stringify({"id": id})
})
switch (result.status) {
case 200:
return
case 401:
throw new Error("Not authorized")
case 404:
throw new Error("An asset with this id does not exist")
default:
throw await unknownResponse(result)
}
}

69
frontend/js/main.js Normal file
View File

@@ -0,0 +1,69 @@
let articleParent = document.getElementById("articles");
function updateSeach(value) {
if (value == "") {
addRecent();
return;
}
let query = {
query: value,
limit: 5
};
clearArticles();
search(query).then(function (data) {
data.forEach(function (article) {
addArticle(article);
});
});
}
function clearArticles() {
articleParent.innerHTML = "";
}
window.onload = function () {
addRecent();
};
function addRecent() {
clearArticles();
recent(5).then(function (data) {
data.forEach(function (article) {
addArticle(article);
});
});
}
function addArticle(article) {
let articleA = document.createElement("a");
articleA.setAttribute("href", article.link);
let articleDiv = document.createElement("div");
articleDiv.setAttribute("class", "article");
let articleHeader = document.createElement("div");
articleHeader.setAttribute("class", "article-header");
let articleHeaderTitle = document.createElement("h3");
articleHeaderTitle.innerHTML = article.title;
articleHeader.appendChild(articleHeaderTitle);
articleDiv.appendChild(articleHeader);
let articleDescription = document.createElement("div");
articleDescription.setAttribute("class", "article-description");
let articleDescriptionP = document.createElement("p");
let articleDescriptionTopics = document.createElement("i");
articleDescriptionTopics.innerHTML = article.tags.join(", ");
let articleDescriptionAuthors = document.createElement("i");
articleDescriptionAuthors.innerHTML = article.authors[0].name;
let articleDescriptionDate = document.createElement("i");
articleDescriptionDate.innerHTML = article.modified.toString();
articleDescriptionP.appendChild(articleDescriptionTopics);
articleDescriptionP.appendChild(articleDescriptionAuthors);
articleDescriptionP.appendChild(articleDescriptionDate);
articleDescription.appendChild(articleDescriptionP);
articleDiv.appendChild(articleDescription);
let articleBody = document.createElement("div");
articleBody.setAttribute("class", "article-body");
let articleBodyP = document.createElement("p");
articleBodyP.innerHTML = article.summary;
articleBody.appendChild(articleBodyP);
articleDiv.appendChild(articleBody);
articleA.appendChild(articleDiv);
articleParent.appendChild(articleA);
let divider = document.createElement("div");
divider.setAttribute("class", "divider");
articleParent.appendChild(divider);
}
//# sourceMappingURL=main.js.map

1
frontend/js/main.js.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"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"}

93
frontend/js/main.ts Normal file
View File

@@ -0,0 +1,93 @@
let articleParent = document.getElementById("articles");
function updateSeach(value: string) {
if(value == "") {
addRecent()
return
}
let query: SearchQuery = {
query: value,
limit: 5
}
clearArticles()
search(query).then(function(data) {
data.forEach(function(article) {
addArticle(article)
})
})
}
function clearArticles() {
articleParent.innerHTML = ""
}
window.onload = function() {
addRecent()
}
function addRecent () {
clearArticles()
recent(5).then(function(data) {
data.forEach(function(article) {
addArticle(article)
})
})
}
function addArticle(article: ArticleSummary) {
let articleA = document.createElement("a")
articleA.setAttribute("href", article.link)
let articleDiv = document.createElement("div")
articleDiv.setAttribute("class", "article")
let articleHeader = document.createElement("div")
articleHeader.setAttribute("class", "article-header")
let articleHeaderTitle = document.createElement("h3")
articleHeaderTitle.innerHTML = article.title
articleHeader.appendChild(articleHeaderTitle)
articleDiv.appendChild(articleHeader)
let articleDescription = document.createElement("div")
articleDescription.setAttribute("class", "article-description")
let articleDescriptionP = document.createElement("p")
let articleDescriptionTopics = document.createElement("i")
articleDescriptionTopics.innerHTML = article.tags.join(", ")
let articleDescriptionAuthors = document.createElement("i")
articleDescriptionAuthors.innerHTML = article.authors[0].name //TODO join ALL Auhtors
let articleDescriptionDate = document.createElement("i")
articleDescriptionDate.innerHTML = article.modified.toString()
articleDescriptionP.appendChild(articleDescriptionTopics)
articleDescriptionP.appendChild(articleDescriptionAuthors)
articleDescriptionP.appendChild(articleDescriptionDate)
articleDescription.appendChild(articleDescriptionP)
articleDiv.appendChild(articleDescription)
let articleBody = document.createElement("div")
articleBody.setAttribute("class", "article-body")
let articleBodyP = document.createElement("p")
articleBodyP.innerHTML = article.summary
articleBody.appendChild(articleBodyP)
articleDiv.appendChild(articleBody)
articleA.appendChild(articleDiv)
articleParent.appendChild(articleA)
let divider = document.createElement("div")
divider.setAttribute("class", "divider")
articleParent.appendChild(divider)
}

10
frontend/js/tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"compileOnSave": true,
"compilerOptions": {
"module": "commonjs",
"target": "es2018",
"removeComments": true,
"sourceMap": true,
"lib": ["es2018", "dom"]
}
}