Initial commit

This commit is contained in:
Dorian Niemiec 2024-04-16 21:20:08 +02:00
commit 8f174742f0
27 changed files with 1774 additions and 0 deletions

5
README.txt Normal file
View file

@ -0,0 +1,5 @@
The "backend" folder contains files, that would be copied to /usr/lib/svrjs directory.
The "frontend" folder contains files, that would be copied to /var/www/svrjs directory.
You may need to set "useWebRootServerSideScript" in SVR.JS config.json to "false" and set up HTTP authentication at "/admin" and "/admin/" URLs.

186
backend/serverSideScript.js Normal file
View file

@ -0,0 +1,186 @@
disableEndElseCallbackExecute = true;
var querystring = require("querystring");
var urlregex = /^(?:https?:\/\/)?(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:])*@)?(?:\[(?:(?:(?:[0-9a-f]{1,4}:){6}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|::(?:[0-9a-f]{1,4}:){5}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|v[0-9a-f]+\.[-a-z0-9\._~!\$&'\(\)\*\+,;=:]+)\]|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}|(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=])*)(?::[0-9]*)?(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))*)*|\/(?:(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))*)*)?|(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))*)*|(?!(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@])))(?:\?(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@])|[\uE000-\uF8FF\/\?])*)?(?:\#(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@])|[\/\?])*)?$/i;
var MongoClient = require('mongodb').MongoClient;
if(!customvar1 && !customvar2) {
try {
customvar1 = JSON.parse(fs.readFileSync(__dirname + "/../shortener-config.json"));
} catch (err) {
customvar2 = err;
}
}
if(customvar2) {
// customvar2 is a instance of Error
callServerError(500, customvar2);
}
// customvar1 is a shortener configuration
var mongoURL = customvar1.mongoURL;
var dbname = customvar1.dbname;
var defaultURL = customvar1.defaultURL;
function antiXSS(string) {
return string.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;");
}
function generateShortenedURLID() {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
function generateUniqueShortenedURLID(db, callback) {
var id = generateShortenedURLID();
db.collection("urls").find({id: id}).toArray(function(err, result) {
if (err) {
db.close();
callServerError(500, err);
return;
}
if(result.length > 0) {
generateUniqueShortenedURLID(db);
} else {
callback(id);
}
});
}
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function connectMongo(callback) {
MongoClient.connect(mongoURL, function(err, db) {
if (err) {
callServerError(500, err);
return;
}
var dbo = db.db(dbname);
dbo.createCollection("urls", function(err, dbres) {
callback(dbo);
});
});
}
function formatTemplate(templateName, templateData, callback) {
fs.readFile(__dirname + "/../templates/" + templateName + ".template", function(err, data) {
if (err) {
callServerError(500, err);
return;
}
var tD = data.toString();
Object.keys(templateData).forEach(function (key) {
tD = tD.replace(new RegExp("\\{\\{" + escapeRegExp(key) + "\\}\\}", "g"), templateData[key]);
});
callback(tD);
});
}
if (href.match(/^\/admin\/?$/)) {
if(req.method != "POST") {
formatTemplate("index.html", {
"url": "",
"shorturl": ""
}, function(data) {
res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
res.end(data);
});
return;
}
var postdata = "";
req.on("data", function(data) {postdata += data.toString();});
req.on("end", function() {
postdata = querystring.parse(postdata);
if(!postdata.url) {
formatTemplate("index.html", {
"url": "",
"shorturl": "<b>URL must not be empty</b>"
}, function(data) {
res.writeHead(400, {"Content-Type": "text/html; charset=utf-8"});
res.end(data);
});
return;
} else if(!postdata.url.match(urlregex)) {
formatTemplate("index.html", {
"url": "",
"shorturl": "<b>Invalid URL</b>"
}, function(data) {
res.writeHead(400, {"Content-Type": "text/html; charset=utf-8"});
res.end(data);
});
return;
} else {
function finalizeResponse(uri, id) {
var shorturl = (req.socket.encrypted ? "https" : "http") + "://" + (req.headers.host ? req.headers.host : req.socket.localAddress) + "/" + id;
formatTemplate("index.html", {
"url": antiXSS(uri),
"shorturl": "<p>Shortened URL: <b><a href=\"" + antiXSS(shorturl) + "\" target=\"_blank\">" + antiXSS(shorturl) + "</a></b>"
}, function(data) {
res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
res.end(data);
});
}
var purl = postdata.url;
if(!purl.match(/^https?:\/\//i)) purl = "http://" + purl;
connectMongo(function(db) {
db.collection("urls").find({url: purl}).toArray(function(err, result) {
if (err) {
db.close();
callServerError(500, err);
return;
}
if(result.length > 0) {
finalizeResponse(purl, result[0].id);
} else {
generateUniqueShortenedURLID(db, function(id) {
db.collection("urls").insertOne({id: id, url: purl}, function(err, dbres) {
if (err) {
db.close();
callServerError(500, err);
return;
}
finalizeResponse(purl, id);
});
});
}
});
});
}
});
} else {
function shortenRedirect() {
var id = href.substr(1);
connectMongo(function(db) {
db.collection("urls").find({id: id}).toArray(function(err, result) {
if (err) {
db.close();
callServerError(500, err);
return;
}
if(result.length > 0) {
redirect(result[0].url);
} else {
redirect(defaultURL, href == "/" ? false : true);
}
});
});
}
if (href == "/") {
shortenRedirect();
} else {
fs.stat("." + decodeURIComponent(href), function(err, stats) {
if (!err && (stats.isDirectory() || stats.isFile())) {
elseCallback();
} else {
shortenRedirect();
}
});
}
}

View file

@ -0,0 +1,5 @@
{
"mongoURL": "mongodb://localhost/shortenerdb",
"database": "shortenerdb",
"defaultURL": "https://svrjs.org"
}

View file

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Shorten URL - SVR.JS</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/favicon.ico">
<!--[if lt IE 9]>
<script src="/js/html5shiv.js"></script>
<![endif]-->
<link rel="stylesheet" href="/css/main.css">
<!--[if lte IE 7]>
<link rel="stylesheet" href="/css/main-ie7.css">
<![endif]-->
</head>
<body>
<header class="header">
<div class="header-contents">
<div class="mainlink">
<a href="https://svrjs.org/" translate="no">
<span class="headlogo-container">
<img src="/img/navbar-logo.png" class="headlogo" alt="SVR.JS logo"><span class="headname">SVR.JS</span>
</span>
</a>
</div>
<nav class="navigation">
<ul>
<li><a href="https://forum.svrjs.org">Forum</a></li>
<li><a href="https://git.svrjs.org">Git</a></li>
<li><a href="https://blog.svrjs.org">Blog</a></li>
<li><a href="https://svrjs.org/docs">Docs</a></li>
</ul>
</nav>
</div>
</header>
<div class="content">
<main class="real-content">
<h1>Shorten URL</h1>
<form method="post">
<label for="url">URL to shorten:</label>
<input type="url" name="url" placeholder="https://example.com" value="{{url}}">
<input type="submit" value="Shorten!">
</form>
{{shorturl}}
</main>
</div>
<footer class="footer">
<div class="footer-contents">
<div class="footer-layout">
<div class="footer-column">
<span class="footer-headline">Resources and development</span>
<ul class="footer-list">
<li><a href="https://blog.svrjs.org">Blog</a> (<a href="https://blog.svrjs.org/atom.xml">Feed</a>)</li>
<li><a href="https://git.svrjs.org">Git server</a></li>
<li><a href="https://svrjs.org/contribute">Contribute</a></li>
<li><a href="https://svrjs.org/changelog">Change log</a></li>
<li><a href="https://downloads.svrjs.org">Downloads</a></li>
<li><a href="https://svrjs.org/mods">Official SVR.JS mods</a></li>
<li><a href="https://svrjs.org/branding">Branding</a></li>
</ul>
</div>
<div class="footer-column">
<span class="footer-headline">Support</span>
<ul class="footer-list">
<li><a href="https://svrjs.org/docs">Documentation</a></li>
<li><a href="https://forum.svrjs.org">Forum</a> (<a href="https://forum.svrjs.org/feed">Feed</a>)</li>
<li><a href="https://svrjs.org/contact">Contact us</a></li>
<li><a href="https://status.svrjs.org">Server uptime</a></li>
</ul>
</div>
<div class="footer-column">
<span class="footer-headline">Social</span>
<ul class="footer-list">
<li><a rel="me" href="https://mastodon.social/@svrjs">Mastodon</a></li>
<li><a rel="me" href="https://twitter.com/SVR_JS">X (Twitter)</a></li>
<li><a rel="me" href="https://bsky.app/profile/svrjs.org">Bluesky</a></li>
</ul>
<span class="footer-headline">Policies and Security</span>
<ul class="footer-list">
<li><a href="https://svrjs.org/vulnerabilities">Security</a></li>
<li><a href="https://svrjs.org/toc">Terms and Conditions</a></li>
<li><a href="https://svrjs.org/privacy">Privacy Policy</a></li>
</ul>
</div>
</div>
<hr/>
<p class="footer-copyright-text">Copyright &copy; 2023-2024 SVR.JS. All rights reserved.</p>
</div>
</footer>
</body>
</html>

18
frontend/css/main-ie7.css Normal file
View file

@ -0,0 +1,18 @@
.mainlink {
float: left;
}
.headname {
line-height: normal;
display: inline;
}
.navigation {
float: right;
padding: 10px 0;
}
.footer-layout {
display: block;
text-align: center;
}
.footer-column {
display: block;
}

556
frontend/css/main.css Normal file
View file

@ -0,0 +1,556 @@
/*
* Prefixed by https://autoprefixer.github.io
* PostCSS: v8.4.14,
* Autoprefixer: v10.4.7
* Browsers: last 400 version
*/
body {
margin: 0;
padding: 0;
font-family: FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;
background-color: #dfffdf;
color: #000000;
}
.translated-rtl {
direction: rtl;
}
.header {
position: relative;
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 10;
padding: 0.65em 0.45em;
background-color: #ffffff;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: block;
}
.header:after {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
-webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
z-index: -1;
}
.header-contents {
display: table;
width: 100%;
table-layout: fixed;
}
.headlogo {
display: inline;
height: 2.2em;
padding-left: 0.2em;
padding-right: 0.35em;
padding-bottom: 0.03em;
vertical-align: middle;
}
.translated-rtl .headlogo {
padding-left: 0.35em;
padding-right: 0.2em;
}
.headname {
font-size: 2.3em;
font-weight: bold;
vertical-align: middle;
line-height: 0.9em;
display: inline-block;
}
.navigation {
display: table-cell;
text-align: right;
position: relative;
vertical-align: middle;
width: 57.5%;
}
.navigation ul {
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
margin: 0;
padding: 0;
list-style-type: none;
display: block;
}
.mainlink {
display: table-cell;
vertical-align: middle;
position: relative;
white-space: nowrap;
width: 42.5%;
}
.mainlink a {
text-decoration: none;
color: #000000;
}
.translated-rtl .navigation {
text-align: left;
}
.headlogo-container {
display: inline-block;
}
.navigation ul li {
display: inline;
}
.navigation ul li a {
text-decoration: none;
font-size: 1.45em;
padding: 0 0.25em;
color: #000000;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@-moz-document url-prefix() {
.navigation ul li a {
display: inline-block;
margin-bottom: 0.05em;
}
}
.navigation ul li a:hover {
color: #007f00;
}
.content {
padding: 0.5em;
}
.real-content {
width: 100%;
max-width: 1280px;
margin: 0 auto;
display: block;
}
.real-content img {
max-width: 100%;
margin: auto;
display: block;
}
.styled-button {
display: inline-block;
text-decoration: none;
color: #ffffff;
background-color: #00b000;
font-size: 1.225em;
padding: 0.725em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
-moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
}
.styled-button:hover {
background-color: #007000;
}
.styled-button.styled-button-disabled, .styled-button.styled-button-disabled:hover {
background-color: #808080;
}
.footer {
background-color: #007000;
color: #ffffff;
-webkit-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
padding: 1.75em 1em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: block;
}
.footer hr {
border-color: white;
}
.footer a {
color: #ffffff;
}
.footer a:hover {
color: #00ff00;
}
.footer-contents {
max-width: 1440px;
width: 100%;
margin: auto;
}
.footer-copyright-text {
font-size: 1.25em;
margin: 0;
margin-top: 0.5em;
padding-bottom: 0.5em;
display: block;
text-align: center;
}
.footer-layout {
display: table;
table-layout: fixed;
width: 100%;
font-size: 1.075em;
}
.footer-column {
display: table-cell;
padding: 0.3em;
}
.footer-headline {
display: block;
font-weight: bold;
font-size: 1.525em;
}
.footer-list {
margin: 0;
padding: 0;
margin-bottom: 1em;
list-style-type: none;
}
img {
border-style: none;
}
table {
border-collapse: collapse;
margin: 0;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
word-break: break-word;
position: relative;
}
table tbody {
background-color: #ffffff;
color: #000000;
}
table tbody:after {
-webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
-moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
content: ' ';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
table img {
margin: 0;
display: inline;
}
th,
tr {
padding: 0.15em;
text-align: center;
}
th {
background-color: #007000;
color: #ffffff;
}
th a {
color: #ffffff;
}
.highlight, .gist {
margin: 0 -0.5em;
}
pre {
margin: 0 -0.588235em;
}
.header-banner {
background-color: #007000;
color: #ffffff;
font-size: 1.1em;
padding: 0.325em;
text-align: center;
-webkit-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
}
.header-banner a {
color: #ffffff;
}
.header-banner a:hover {
color: #00ff00;
}
input {
max-width: 60%;
font-family: FreeSans, Helvetica, Arial, sans-serif;
background-color: #fafafa;
border: 1px solid #bbbbbb;
}
select {
background-color: #fafafa;
border: 1px solid #bbbbbb;
}
input:hover, submit:hover,
input:focus, submit:focus {
border-color: #00b000;
}
input[type=submit] {
font-weight: bold;
color: #ffffff;
border: none;
background-color: #00b000;
padding: 3px 5px;
}
input[type=submit]:hover {
background-color: #007000;
cursor: pointer;
}
@media screen and (-ms-high-contrast:active), (-ms-high-contrast:none) {
.headname {
margin-top: 0.06em;
}
}
@media screen and (prefers-color-scheme: dark) {
.dummy {}
body {
background-color: #002000;
color: #ffffff;
}
a {
color: #ffffff;
}
a:hover {
color: #00ff00;
}
.header {
background-color: #004f00;
}
.header:after {
-webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
-moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
}
.mainlink a {
color: #ffffff;
}
.mainlink:hover {
color: #ffffff;
}
.navigation ul li a {
color: #ffffff;
}
.navigation ul li a:hover {
color: #00ff00;
}
.footer {
-webkit-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
-moz-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
}
.styled-button {
background-color: #00b000;
-webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
-moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
}
.styled-button:hover {
background-color: #00d000;
color: #ffffff;
}
table tbody {
background-color: #000f00;
color: #ffffff;
}
table tbody:after {
-webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
-moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
}
.header-banner {
-webkit-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
-moz-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
}
input, select {
background-color: #000000;
color: #ffffff;
}
input[type=submit] {
color: #ffffff;
background-color: #00b000;
}
input[type=submit]:hover {
background-color: #00d000;
}
}
@media screen and (max-width: 768px) {
.footer-layout {
display: block;
}
.footer-column {
display: block;
text-align: center;
padding: 0;
}
}
@media screen and (max-width: 480px) {
.headlogo {
height: 1.8em;
padding-left: 0;
padding-right: 0.25em;
padding-bottom: 0;
}
.translated-rtl .headlogo {
padding-left: 0.25em;
padding-right: 0;
}
.headname {
font-size: 1.85em;
}
.navigation ul li a {
font-size: 1.15em;
padding: 0 0.225em;
}
}
@media screen and (max-width: 372px) {
.navigation ul li a {
font-size: 1.075em;
padding: 0 0.2em;
}
}
@media screen and (max-width: 350px) {
.navigation {
display: block;
text-align: left;
width: 100%;
}
.translated-rtl .navigation {
text-align: right
}
.mainlink {
display: block;
width: 100%;
}
.header-contents {
display: block;
}
}
@media print {
.dummy {}
.header, .footer, .header-banner {
display: none;
}
.real-content {
max-width: none !important;
}
}
@font-face {
font-family: "FreeSans";
font-display: swap;
src: url("/fonts/FreeSans.woff") format("woff"), url("/fonts/FreeSans.ttf") format("truetype");
}
@font-face {
font-family: "FreeSans";
font-weight: bold;
font-display: swap;
src: url("/fonts/FreeSansBold.woff") format("woff"), url("/fonts/FreeSansBold.ttf") format("truetype");
}
@font-face {
font-family: "FreeSans";
font-style: italic;
font-display: swap;
src: url("/fonts/FreeSansOblique.woff") format("woff"), url("/fonts/FreeSansOblique.ttf") format("truetype");
}
@font-face {
font-family: "FreeSans";
font-style: italic;
font-weight: bold;
font-display: swap;
src: url("/fonts/FreeSansBoldOblique.woff") format("woff"), url("/fonts/FreeSansBoldOblique.ttf") format("truetype");
}

BIN
frontend/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
frontend/fonts/FreeSans.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

556
frontend/fonts/main.css Normal file
View file

@ -0,0 +1,556 @@
/*
* Prefixed by https://autoprefixer.github.io
* PostCSS: v8.4.14,
* Autoprefixer: v10.4.7
* Browsers: last 400 version
*/
body {
margin: 0;
padding: 0;
font-family: FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;
background-color: #dfffdf;
color: #000000;
}
.translated-rtl {
direction: rtl;
}
.header {
position: relative;
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 10;
padding: 0.65em 0.45em;
background-color: #ffffff;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: block;
}
.header:after {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
-webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
z-index: -1;
}
.header-contents {
display: table;
width: 100%;
table-layout: fixed;
}
.headlogo {
display: inline;
height: 2.2em;
padding-left: 0.2em;
padding-right: 0.35em;
padding-bottom: 0.03em;
vertical-align: middle;
}
.translated-rtl .headlogo {
padding-left: 0.35em;
padding-right: 0.2em;
}
.headname {
font-size: 2.3em;
font-weight: bold;
vertical-align: middle;
line-height: 0.9em;
display: inline-block;
}
.navigation {
display: table-cell;
text-align: right;
position: relative;
vertical-align: middle;
width: 57.5%;
}
.navigation ul {
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
margin: 0;
padding: 0;
list-style-type: none;
display: block;
}
.mainlink {
display: table-cell;
vertical-align: middle;
position: relative;
white-space: nowrap;
width: 42.5%;
}
.mainlink a {
text-decoration: none;
color: #000000;
}
.translated-rtl .navigation {
text-align: left;
}
.headlogo-container {
display: inline-block;
}
.navigation ul li {
display: inline;
}
.navigation ul li a {
text-decoration: none;
font-size: 1.45em;
padding: 0 0.25em;
color: #000000;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@-moz-document url-prefix() {
.navigation ul li a {
display: inline-block;
margin-bottom: 0.05em;
}
}
.navigation ul li a:hover {
color: #007f00;
}
.content {
padding: 0.5em;
}
.real-content {
width: 100%;
max-width: 1280px;
margin: 0 auto;
display: block;
}
.real-content img {
max-width: 100%;
margin: auto;
display: block;
}
.styled-button {
display: inline-block;
text-decoration: none;
color: #ffffff;
background-color: #00b000;
font-size: 1.225em;
padding: 0.725em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
-moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
}
.styled-button:hover {
background-color: #007000;
}
.styled-button.styled-button-disabled, .styled-button.styled-button-disabled:hover {
background-color: #808080;
}
.footer {
background-color: #007000;
color: #ffffff;
-webkit-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
padding: 1.75em 1em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: block;
}
.footer hr {
border-color: white;
}
.footer a {
color: #ffffff;
}
.footer a:hover {
color: #00ff00;
}
.footer-contents {
max-width: 1440px;
width: 100%;
margin: auto;
}
.footer-copyright-text {
font-size: 1.25em;
margin: 0;
margin-top: 0.5em;
padding-bottom: 0.5em;
display: block;
text-align: center;
}
.footer-layout {
display: table;
table-layout: fixed;
width: 100%;
font-size: 1.075em;
}
.footer-column {
display: table-cell;
padding: 0.3em;
}
.footer-headline {
display: block;
font-weight: bold;
font-size: 1.525em;
}
.footer-list {
margin: 0;
padding: 0;
margin-bottom: 1em;
list-style-type: none;
}
img {
border-style: none;
}
table {
border-collapse: collapse;
margin: 0;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
word-break: break-word;
position: relative;
}
table tbody {
background-color: #ffffff;
color: #000000;
}
table tbody:after {
-webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
-moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
content: ' ';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
table img {
margin: 0;
display: inline;
}
th,
tr {
padding: 0.15em;
text-align: center;
}
th {
background-color: #007000;
color: #ffffff;
}
th a {
color: #ffffff;
}
.highlight, .gist {
margin: 0 -0.5em;
}
pre {
margin: 0 -0.588235em;
}
.header-banner {
background-color: #007000;
color: #ffffff;
font-size: 1.1em;
padding: 0.325em;
text-align: center;
-webkit-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
}
.header-banner a {
color: #ffffff;
}
.header-banner a:hover {
color: #00ff00;
}
input {
max-width: 60%;
font-family: FreeSans, Helvetica, Arial, sans-serif;
background-color: #fafafa;
border: 1px solid #bbbbbb;
}
select {
background-color: #fafafa;
border: 1px solid #bbbbbb;
}
input:hover, submit:hover,
input:focus, submit:focus {
border-color: #00b000;
}
input[type=submit] {
font-weight: bold;
color: #ffffff;
border: none;
background-color: #00b000;
padding: 3px 5px;
}
input[type=submit]:hover {
background-color: #007000;
cursor: pointer;
}
@media screen and (-ms-high-contrast:active), (-ms-high-contrast:none) {
.headname {
margin-top: 0.06em;
}
}
@media screen and (prefers-color-scheme: dark) {
.dummy {}
body {
background-color: #002000;
color: #ffffff;
}
a {
color: #ffffff;
}
a:hover {
color: #00ff00;
}
.header {
background-color: #004f00;
}
.header:after {
-webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
-moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
}
.mainlink a {
color: #ffffff;
}
.mainlink:hover {
color: #ffffff;
}
.navigation ul li a {
color: #ffffff;
}
.navigation ul li a:hover {
color: #00ff00;
}
.footer {
-webkit-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
-moz-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
}
.styled-button {
background-color: #00b000;
-webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
-moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
}
.styled-button:hover {
background-color: #00d000;
color: #ffffff;
}
table tbody {
background-color: #000f00;
color: #ffffff;
}
table tbody:after {
-webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
-moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
}
.header-banner {
-webkit-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
-moz-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
}
input, select {
background-color: #000000;
color: #ffffff;
}
input[type=submit] {
color: #ffffff;
background-color: #00b000;
}
input[type=submit]:hover {
background-color: #00d000;
}
}
@media screen and (max-width: 768px) {
.footer-layout {
display: block;
}
.footer-column {
display: block;
text-align: center;
padding: 0;
}
}
@media screen and (max-width: 480px) {
.headlogo {
height: 1.8em;
padding-left: 0;
padding-right: 0.25em;
padding-bottom: 0;
}
.translated-rtl .headlogo {
padding-left: 0.25em;
padding-right: 0;
}
.headname {
font-size: 1.85em;
}
.navigation ul li a {
font-size: 1.15em;
padding: 0 0.225em;
}
}
@media screen and (max-width: 372px) {
.navigation ul li a {
font-size: 1.075em;
padding: 0 0.2em;
}
}
@media screen and (max-width: 350px) {
.navigation {
display: block;
text-align: left;
width: 100%;
}
.translated-rtl .navigation {
text-align: right
}
.mainlink {
display: block;
width: 100%;
}
.header-contents {
display: block;
}
}
@media print {
.dummy {}
.header, .footer, .header-banner {
display: none;
}
.real-content {
max-width: none !important;
}
}
@font-face {
font-family: "FreeSans";
font-display: swap;
src: url("/fonts/FreeSans.woff") format("woff"), url("/fonts/FreeSans.ttf") format("truetype");
}
@font-face {
font-family: "FreeSans";
font-weight: bold;
font-display: swap;
src: url("/fonts/FreeSansBold.woff") format("woff"), url("/fonts/FreeSansBold.ttf") format("truetype");
}
@font-face {
font-family: "FreeSans";
font-style: italic;
font-display: swap;
src: url("/fonts/FreeSansOblique.woff") format("woff"), url("/fonts/FreeSansOblique.ttf") format("truetype");
}
@font-face {
font-family: "FreeSans";
font-style: italic;
font-weight: bold;
font-display: swap;
src: url("/fonts/FreeSansBoldOblique.woff") format("woff"), url("/fonts/FreeSansBoldOblique.ttf") format("truetype");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

326
frontend/js/html5shiv.js vendored Normal file
View file

@ -0,0 +1,326 @@
/**
* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
;(function(window, document) {
/*jshint evil:true */
/** version */
var version = '3.7.3';
/** Preset options */
var options = window.html5 || {};
/** Used to skip problem elements */
var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
/** Not all elements can be cloned in IE **/
var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
/** Detect whether the browser supports default html5 styles */
var supportsHtml5Styles;
/** Name of the expando, to work with multiple documents or to re-shiv one document */
var expando = '_html5shiv';
/** The id for the the documents expando */
var expanID = 0;
/** Cached data for each document */
var expandoData = {};
/** Detect whether the browser supports unknown elements */
var supportsUnknownElements;
(function() {
try {
var a = document.createElement('a');
a.innerHTML = '<xyz></xyz>';
//if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
supportsHtml5Styles = ('hidden' in a);
supportsUnknownElements = a.childNodes.length == 1 || (function() {
// assign a false positive if unable to shiv
(document.createElement)('a');
var frag = document.createDocumentFragment();
return (
typeof frag.cloneNode == 'undefined' ||
typeof frag.createDocumentFragment == 'undefined' ||
typeof frag.createElement == 'undefined'
);
}());
} catch(e) {
// assign a false positive if detection fails => unable to shiv
supportsHtml5Styles = true;
supportsUnknownElements = true;
}
}());
/*--------------------------------------------------------------------------*/
/**
* Creates a style sheet with the given CSS text and adds it to the document.
* @private
* @param {Document} ownerDocument The document.
* @param {String} cssText The CSS text.
* @returns {StyleSheet} The style element.
*/
function addStyleSheet(ownerDocument, cssText) {
var p = ownerDocument.createElement('p'),
parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
p.innerHTML = 'x<style>' + cssText + '</style>';
return parent.insertBefore(p.lastChild, parent.firstChild);
}
/**
* Returns the value of `html5.elements` as an array.
* @private
* @returns {Array} An array of shived element node names.
*/
function getElements() {
var elements = html5.elements;
return typeof elements == 'string' ? elements.split(' ') : elements;
}
/**
* Extends the built-in list of html5 elements
* @memberOf html5
* @param {String|Array} newElements whitespace separated list or array of new element names to shiv
* @param {Document} ownerDocument The context document.
*/
function addElements(newElements, ownerDocument) {
var elements = html5.elements;
if(typeof elements != 'string'){
elements = elements.join(' ');
}
if(typeof newElements != 'string'){
newElements = newElements.join(' ');
}
html5.elements = elements +' '+ newElements;
shivDocument(ownerDocument);
}
/**
* Returns the data associated to the given document
* @private
* @param {Document} ownerDocument The document.
* @returns {Object} An object of data.
*/
function getExpandoData(ownerDocument) {
var data = expandoData[ownerDocument[expando]];
if (!data) {
data = {};
expanID++;
ownerDocument[expando] = expanID;
expandoData[expanID] = data;
}
return data;
}
/**
* returns a shived element for the given nodeName and document
* @memberOf html5
* @param {String} nodeName name of the element
* @param {Document|DocumentFragment} ownerDocument The context document.
* @returns {Object} The shived element.
*/
function createElement(nodeName, ownerDocument, data){
if (!ownerDocument) {
ownerDocument = document;
}
if(supportsUnknownElements){
return ownerDocument.createElement(nodeName);
}
if (!data) {
data = getExpandoData(ownerDocument);
}
var node;
if (data.cache[nodeName]) {
node = data.cache[nodeName].cloneNode();
} else if (saveClones.test(nodeName)) {
node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
} else {
node = data.createElem(nodeName);
}
// Avoid adding some elements to fragments in IE < 9 because
// * Attributes like `name` or `type` cannot be set/changed once an element
// is inserted into a document/fragment
// * Link elements with `src` attributes that are inaccessible, as with
// a 403 response, will cause the tab/window to crash
// * Script elements appended to fragments will execute when their `src`
// or `text` property is set
return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
}
/**
* returns a shived DocumentFragment for the given document
* @memberOf html5
* @param {Document} ownerDocument The context document.
* @returns {Object} The shived DocumentFragment.
*/
function createDocumentFragment(ownerDocument, data){
if (!ownerDocument) {
ownerDocument = document;
}
if(supportsUnknownElements){
return ownerDocument.createDocumentFragment();
}
data = data || getExpandoData(ownerDocument);
var clone = data.frag.cloneNode(),
i = 0,
elems = getElements(),
l = elems.length;
for(;i<l;i++){
clone.createElement(elems[i]);
}
return clone;
}
/**
* Shivs the `createElement` and `createDocumentFragment` methods of the document.
* @private
* @param {Document|DocumentFragment} ownerDocument The document.
* @param {Object} data of the document.
*/
function shivMethods(ownerDocument, data) {
if (!data.cache) {
data.cache = {};
data.createElem = ownerDocument.createElement;
data.createFrag = ownerDocument.createDocumentFragment;
data.frag = data.createFrag();
}
ownerDocument.createElement = function(nodeName) {
//abort shiv
if (!html5.shivMethods) {
return data.createElem(nodeName);
}
return createElement(nodeName, ownerDocument, data);
};
ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
'var n=f.cloneNode(),c=n.createElement;' +
'h.shivMethods&&(' +
// unroll the `createElement` calls
getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
data.createElem(nodeName);
data.frag.createElement(nodeName);
return 'c("' + nodeName + '")';
}) +
');return n}'
)(html5, data.frag);
}
/*--------------------------------------------------------------------------*/
/**
* Shivs the given document.
* @memberOf html5
* @param {Document} ownerDocument The document to shiv.
* @returns {Document} The shived document.
*/
function shivDocument(ownerDocument) {
if (!ownerDocument) {
ownerDocument = document;
}
var data = getExpandoData(ownerDocument);
if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
data.hasCSS = !!addStyleSheet(ownerDocument,
// corrects block display not defined in IE6/7/8/9
'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
// adds styling not present in IE6/7/8/9
'mark{background:#FF0;color:#000}' +
// hides non-rendered elements
'template{display:none}'
);
}
if (!supportsUnknownElements) {
shivMethods(ownerDocument, data);
}
return ownerDocument;
}
/*--------------------------------------------------------------------------*/
/**
* The `html5` object is exposed so that more elements can be shived and
* existing shiving can be detected on iframes.
* @type Object
* @example
*
* // options can be changed before the script is included
* html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
*/
var html5 = {
/**
* An array or space separated string of node names of the elements to shiv.
* @memberOf html5
* @type Array|String
*/
'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
/**
* current version of html5shiv
*/
'version': version,
/**
* A flag to indicate that the HTML5 style sheet should be inserted.
* @memberOf html5
* @type Boolean
*/
'shivCSS': (options.shivCSS !== false),
/**
* Is equal to true if a browser supports creating unknown/HTML5 elements
* @memberOf html5
* @type boolean
*/
'supportsUnknownElements': supportsUnknownElements,
/**
* A flag to indicate that the document's `createElement` and `createDocumentFragment`
* methods should be overwritten.
* @memberOf html5
* @type Boolean
*/
'shivMethods': (options.shivMethods !== false),
/**
* A string to describe the type of `html5` object ("default" or "default print").
* @memberOf html5
* @type String
*/
'type': 'default',
// shivs the document according to the specified `html5` object options
'shivDocument': shivDocument,
//creates a shived element
createElement: createElement,
//creates a shived documentFragment
createDocumentFragment: createDocumentFragment,
//extends list of elements
addElements: addElements
};
/*--------------------------------------------------------------------------*/
// expose html5
window.html5 = html5;
// shiv the document
shivDocument(document);
if(typeof module == 'object' && module.exports){
module.exports = html5;
}
}(typeof window !== "undefined" ? window : this, document));

2
frontend/robots.txt Normal file
View file

@ -0,0 +1,2 @@
User-agent: *
Disallow: /