commit 8f174742f00a61c8d54304cab165e3e7ee8fde68 Author: Dorian Niemiec Date: Tue Apr 16 21:20:08 2024 +0200 Initial commit diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..583b068 --- /dev/null +++ b/README.txt @@ -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. + diff --git a/backend/serverSideScript.js b/backend/serverSideScript.js new file mode 100644 index 0000000..77f96e8 --- /dev/null +++ b/backend/serverSideScript.js @@ -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,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"); +} + +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": "URL must not be empty" + }, 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": "Invalid URL" + }, 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": "

Shortened URL: " + antiXSS(shorturl) + "" + }, 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(); + } + }); + } +} diff --git a/backend/shortener-config.json b/backend/shortener-config.json new file mode 100644 index 0000000..23f9b3c --- /dev/null +++ b/backend/shortener-config.json @@ -0,0 +1,5 @@ +{ + "mongoURL": "mongodb://localhost/shortenerdb", + "database": "shortenerdb", + "defaultURL": "https://svrjs.org" +} diff --git a/backend/templates/index.html.template b/backend/templates/index.html.template new file mode 100644 index 0000000..0e4513f --- /dev/null +++ b/backend/templates/index.html.template @@ -0,0 +1,120 @@ + + + + + Shorten URL - SVR.JS + + + + + + + + + + + + + + + + + + + + + + + +

+
+ + +
+
+ + + + +
+ +
+ +

Shorten URL

+
+ + + +
+ {{shorturl}} + +
+
+ + + + + + + + + diff --git a/frontend/css/main-ie7.css b/frontend/css/main-ie7.css new file mode 100644 index 0000000..e897366 --- /dev/null +++ b/frontend/css/main-ie7.css @@ -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; +} diff --git a/frontend/css/main.css b/frontend/css/main.css new file mode 100644 index 0000000..4995135 --- /dev/null +++ b/frontend/css/main.css @@ -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"); +} + diff --git a/frontend/favicon.ico b/frontend/favicon.ico new file mode 100644 index 0000000..c5d08a7 Binary files /dev/null and b/frontend/favicon.ico differ diff --git a/frontend/fonts/DejaVuSansMono-Bold.ttf b/frontend/fonts/DejaVuSansMono-Bold.ttf new file mode 100644 index 0000000..8184ced Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Bold.ttf differ diff --git a/frontend/fonts/DejaVuSansMono-Bold.woff b/frontend/fonts/DejaVuSansMono-Bold.woff new file mode 100644 index 0000000..abb18d0 Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Bold.woff differ diff --git a/frontend/fonts/DejaVuSansMono-BoldOblique.ttf b/frontend/fonts/DejaVuSansMono-BoldOblique.ttf new file mode 100644 index 0000000..754dca7 Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-BoldOblique.ttf differ diff --git a/frontend/fonts/DejaVuSansMono-BoldOblique.woff b/frontend/fonts/DejaVuSansMono-BoldOblique.woff new file mode 100644 index 0000000..abb1c2b Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-BoldOblique.woff differ diff --git a/frontend/fonts/DejaVuSansMono-Oblique.ttf b/frontend/fonts/DejaVuSansMono-Oblique.ttf new file mode 100644 index 0000000..4c858d4 Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Oblique.ttf differ diff --git a/frontend/fonts/DejaVuSansMono-Oblique.woff b/frontend/fonts/DejaVuSansMono-Oblique.woff new file mode 100644 index 0000000..6493cb6 Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Oblique.woff differ diff --git a/frontend/fonts/DejaVuSansMono.ttf b/frontend/fonts/DejaVuSansMono.ttf new file mode 100644 index 0000000..f578602 Binary files /dev/null and b/frontend/fonts/DejaVuSansMono.ttf differ diff --git a/frontend/fonts/DejaVuSansMono.woff b/frontend/fonts/DejaVuSansMono.woff new file mode 100644 index 0000000..629c352 Binary files /dev/null and b/frontend/fonts/DejaVuSansMono.woff differ diff --git a/frontend/fonts/FreeSans.ttf b/frontend/fonts/FreeSans.ttf new file mode 100644 index 0000000..9db9585 Binary files /dev/null and b/frontend/fonts/FreeSans.ttf differ diff --git a/frontend/fonts/FreeSans.woff b/frontend/fonts/FreeSans.woff new file mode 100644 index 0000000..2b60745 Binary files /dev/null and b/frontend/fonts/FreeSans.woff differ diff --git a/frontend/fonts/FreeSansBold.ttf b/frontend/fonts/FreeSansBold.ttf new file mode 100644 index 0000000..63644e7 Binary files /dev/null and b/frontend/fonts/FreeSansBold.ttf differ diff --git a/frontend/fonts/FreeSansBold.woff b/frontend/fonts/FreeSansBold.woff new file mode 100644 index 0000000..9c698ef Binary files /dev/null and b/frontend/fonts/FreeSansBold.woff differ diff --git a/frontend/fonts/FreeSansBoldOblique.ttf b/frontend/fonts/FreeSansBoldOblique.ttf new file mode 100644 index 0000000..dde7f32 Binary files /dev/null and b/frontend/fonts/FreeSansBoldOblique.ttf differ diff --git a/frontend/fonts/FreeSansBoldOblique.woff b/frontend/fonts/FreeSansBoldOblique.woff new file mode 100644 index 0000000..e374d95 Binary files /dev/null and b/frontend/fonts/FreeSansBoldOblique.woff differ diff --git a/frontend/fonts/FreeSansOblique.ttf b/frontend/fonts/FreeSansOblique.ttf new file mode 100644 index 0000000..7452885 Binary files /dev/null and b/frontend/fonts/FreeSansOblique.ttf differ diff --git a/frontend/fonts/FreeSansOblique.woff b/frontend/fonts/FreeSansOblique.woff new file mode 100644 index 0000000..9953932 Binary files /dev/null and b/frontend/fonts/FreeSansOblique.woff differ diff --git a/frontend/fonts/main.css b/frontend/fonts/main.css new file mode 100644 index 0000000..4995135 --- /dev/null +++ b/frontend/fonts/main.css @@ -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"); +} + diff --git a/frontend/img/navbar-logo.png b/frontend/img/navbar-logo.png new file mode 100644 index 0000000..5df709b Binary files /dev/null and b/frontend/img/navbar-logo.png differ diff --git a/frontend/js/html5shiv.js b/frontend/js/html5shiv.js new file mode 100644 index 0000000..45ea723 --- /dev/null +++ b/frontend/js/html5shiv.js @@ -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 = ''; + //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'; + 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