shii.moe

Source for `shii.moe' website.
git clone git://git.knutsen.co/shii.moe
Log | Files | Refs | README | LICENSE

commit fa4a173e8e339356f61a25c38ee563b0348e62cf
parent 9844d063587165f8687a25f4369fc55c20de0808
Author: knutsen <samuel@knutsen.co>
Date:   Wed, 29 Sep 2021 16:18:51 +0100

Cache/pre-render certain templates that do not change.

Diffstat:
Mpublic/style.css | 14++++++++++++--
Mshiimoe.py | 205++-----------------------------------------------------------------------------
Ashiimoe/__init__.py | 264+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ashiimoe/templates/blogindex.html | 46++++++++++++++++++++++++++++++++++++++++++++++
Ashiimoe/templates/blogpost.html | 41+++++++++++++++++++++++++++++++++++++++++
Ashiimoe/templates/books.html | 9+++++++++
Ashiimoe/templates/guestbook.html | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ashiimoe/templates/index.html | 34++++++++++++++++++++++++++++++++++
Ashiimoe/templates/layout.html | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Ashiimoe/templates/music.html | 32++++++++++++++++++++++++++++++++
Ashiimoe/templates/norsk.html | 10++++++++++
Ashiimoe/templates/school.html | 9+++++++++
Dstatic/books.html | 37-------------------------------------
Dstatic/index.html | 56--------------------------------------------------------
Dstatic/music.html | 62--------------------------------------------------------------
Dstatic/norsk.html | 38--------------------------------------
Dstatic/school.html | 37-------------------------------------
Dtemplates/blogindex.html | 72------------------------------------------------------------------------
Dtemplates/blogpost.html | 68--------------------------------------------------------------------
Dtemplates/guestbook.html | 194-------------------------------------------------------------------------------
20 files changed, 680 insertions(+), 767 deletions(-)

diff --git a/public/style.css b/public/style.css @@ -53,6 +53,15 @@ body { margin-right: auto; } +a.link { + display: inline-block; + transition: all .1s ease; +} + +a.link:hover { + transform: skewX(-7deg); +} + hr { background: none; border: none; @@ -125,11 +134,12 @@ blockquote > *:last-child { background-color: #acb9f8; display: inline-block; margin: 0.3em 0.3em; - padding: 0.12em 0.3em; + padding: 0.12em 0; box-shadow: 0.3em 0.3em 0 #00000030; transition: 0.3s; font-size: 20pt; - width: 12.5%; + text-align: center; + width: 15%; font-weight: bold; font-variant: small-caps; } diff --git a/shiimoe.py b/shiimoe.py @@ -1,202 +1,5 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +import shiimoe -from flask import Flask, render_template, send_from_directory, request, redirect - -import markdown -import markdown.extensions.codehilite -import markdown.extensions.fenced_code -import pymdownx, pymdownx.emoji -import bleach - -from datetime import datetime -import os -import json -import re - -BLOG_PATH = os.path.realpath('./blog') -STATIC_PATH = os.path.realpath('./static') -PUBLIC_PATH = os.path.realpath('./public') -COMMENT_FILE = os.path.realpath('./comments.json') -DATE_FORMAT = "%Y-%m-%d %H:%M" -app = Flask(__name__, static_folder=PUBLIC_PATH) -md = markdown.Markdown( - extensions=[ - 'fenced_code', 'codehilite', - 'pymdownx.emoji', 'smarty', 'attr_list', - 'full_yaml_metadata', 'markdown_captions'], - extension_configs={ - 'pymdownx.emoji': { - 'emoji_index': pymdownx.emoji.gemoji, - 'emoji_generator': pymdownx.emoji.to_png, - } - }) - - -ALLOWED_TAGS = list(set(bleach.sanitizer.ALLOWED_TAGS + [ - 'ul', 'ol', 'li', 'p', 'pre', 'code', 'blockquote', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'br', - 'strong', 'em', 'a', 'img' -])) -ALLOWED_ATTRIBUTES = { - **bleach.sanitizer.ALLOWED_ATTRIBUTES, - 'a': ['href', 'title'], - 'img': ['src', 'title', 'alt'] -} -ALLOWED_PROTOCOLS = list(set(bleach.sanitizer.ALLOWED_PROTOCOLS - + ['http', 'https', 'mailto'])) - -def read_comments(): - comments = None - try: - with open(COMMENT_FILE, 'r') as f: - comments = json.load(f) - except FileNotFoundError: - comments = [] - - return comments - -def is_subpath(path, dir): - return path.startswith(dir) - -def send_static_page(path, filename): - path = os.path.realpath(os.path.join(STATIC_PATH, path)) - if not is_subpath(path, STATIC_PATH): - return 403 - - if not filename.endswith('.html'): - filename += '.html' - return send_from_directory(path, filename) - -def send_public_asset(path, filename): - path = os.path.realpath(os.path.join(PUBLIC_PATH, path)) - if not is_subpath(path, PUBLIC_PATH): - return 403 - - return send_from_directory(path, filename) - -@app.route('/') -def home(): - return send_static_page('./', 'index.html') - -def render_guestbook(**kw): - comments = read_comments() - comments.reverse() - - for comment in comments: - comment['comment'] = bleach.clean(comment['comment'], - tags=ALLOWED_TAGS, - attributes=ALLOWED_ATTRIBUTES, - protocols=ALLOWED_PROTOCOLS) - comment['comment'] = md.convert(comment['comment']) - - kw['comments'] = comments - return render_template('guestbook.html', **kw) - -@app.route('/guestbook') -@app.route('/guestbook.html') -def guestbook(): - return render_guestbook() - -@app.route('/blog') -@app.route('/blog/index.html') -def blogindex(): - posts = [] - for file in os.listdir(BLOG_PATH): - slug = file[:-3] - meta = None - with open(f"{BLOG_PATH}/{file}", 'r') as f: - md.convert(f.read()) - meta = md.Meta - posts.append({ - 'slug': slug, - 'title': meta['title'], - 'published': meta['published'], - 'date': datetime.strftime(meta['published'], '%Y-%m-%d'), - 'datetime': meta['published'].isoformat() - }) - print(posts) - posts.sort(key = lambda post: post['published'], reverse=True) - return render_template("blogindex.html", posts = posts) - -@app.route('/blog/<slug>') -@app.route('/blog/<slug>.html') -def blogpost(slug): - keywords = { - 'slug': slug, - 'title': None - } - for post in os.listdir(BLOG_PATH): - if post == slug + ".md": - with open(f"{BLOG_PATH}/{post}", 'r') as file: - content = file.read() - keywords["content"] = md.convert(content) - keywords["title"] = md.Meta['title'] - break - - if 'content' not in keywords: - return send_static_page('./', '404.html'), 404 - - - return render_template("blogpost.html", **keywords) - - -LINK_MATCH = re.compile("https?://") - -@app.route('/postcomment', methods=['POST']) -def postcomment(): - now = datetime.now() - - name = request.form.get('name') - comment = request.form.get('comment') - date = request.form.get('date') or datetime.strftime(now, DATE_FORMAT) - ip = (request.environ.get('HTTP_X_REAL_IP') - or request.environ.get('REMOTE_ADDR') - or request.remote_addr) - - err = lambda msg: render_guestbook(error_msg=msg, dcomment=comment, dname=name) - - # Comment or name left empty, send error message. - if not (name or "").strip() or not (comment or "").strip(): - return err("Please provide a name and comment.") - - # Spam filtering!! - if len(name) > 110: - return err("You're taking the piss with a name that long mate.") - if len(comment) > 850: - return err("No more than 850 characters!") - if name.lower() == "annasysdek": - return err("Sorry, that name is toxic. Pick another.") - if len(LINK_MATCH.findall(comment)) != 0: - return err("No links!") - - comments = read_comments() - if any(c['comment'] == comment for c in comments): - return err("Duplicate comment. Please write unique comments.") - - comments.append({ - 'name': name, - 'comment': comment, - 'date': date, - 'ip': ip - }) - - with open(COMMENT_FILE, 'w') as f: - json.dump(comments, f, indent=4, separators=(",", ": ")) - - return redirect('/guestbook', code=302) - - -@app.route('/<path:filepath>') -def serve_static(filepath): - path, filename = os.path.split(filepath) - if os.path.exists(os.path.join(PUBLIC_PATH, path, filename)): - return send_public_asset(path, filename) - if not filename.endswith('.html'): - filename += '.html' - if os.path.exists(os.path.join(STATIC_PATH, path, filename)): - return send_static_page(path, filename) - # Otherwise, 404. - return send_static_page('./', '404.html'), 404 - -if __name__ == "__main__": - app.run(debug=True) +if __name__ == '__main__': + shiimoe.app.run(debug=True) diff --git a/shiimoe/__init__.py b/shiimoe/__init__.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 + +from flask import Flask, render_template, send_from_directory, request, redirect +import jinja2 + +import markdown +import markdown.extensions.codehilite +import markdown.extensions.fenced_code +import pymdownx, pymdownx.emoji +import bleach + +from datetime import datetime +import os +import random +import json +import re + +BLOG_PATH = os.path.realpath('./blog') +STATIC_PATH = os.path.realpath('./static') +PUBLIC_PATH = os.path.realpath('./public') +TEMPLATE_PATH = os.path.realpath('./shiimoe/templates') +COMMENT_FILE = os.path.realpath('./comments.json') +DATE_FORMAT = "%Y-%m-%d %H:%M" + +app = Flask(__name__, static_folder=PUBLIC_PATH) + +md = markdown.Markdown( + extensions=[ + 'fenced_code', 'codehilite', + 'pymdownx.emoji', 'smarty', 'attr_list', + 'full_yaml_metadata', 'markdown_captions'], + extension_configs={ + 'pymdownx.emoji': { + 'emoji_index': pymdownx.emoji.gemoji, + 'emoji_generator': pymdownx.emoji.to_png, + } + }) + +ALLOWED_TAGS = list(set(bleach.sanitizer.ALLOWED_TAGS + [ + 'ul', 'ol', 'li', 'p', 'pre', 'code', 'blockquote', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'br', + 'strong', 'em', 'a', 'img' +])) +ALLOWED_ATTRIBUTES = { + **bleach.sanitizer.ALLOWED_ATTRIBUTES, + 'a': ['href', 'title'], + 'img': ['src', 'title', 'alt'] +} +ALLOWED_PROTOCOLS = list(set(bleach.sanitizer.ALLOWED_PROTOCOLS + + ['http', 'https', 'mailto'])) + +MASCOT_COUNT = int(len(os.listdir(f"{PUBLIC_PATH}/mascots")) / 2) + +def get_blog_posts(): + posts = [] + for file in os.listdir(BLOG_PATH): + slug = file[:-3] + meta = None + with open(f"{BLOG_PATH}/{file}", 'r') as f: + md.convert(f.read()) + meta = md.Meta + posts.append({ + 'slug': slug, + 'title': meta['title'], + 'published': meta['published'], + 'date': datetime.strftime(meta['published'], '%Y-%m-%d'), + 'datetime': meta['published'].isoformat() + }) + posts.sort(key = lambda post: post['published'], reverse=True) + return tuple(posts) + +def get_blog_post(slug): + keywords = { + 'slug': slug, + 'title': None + } + try: + with open(f"{BLOG_PATH}/{slug}.md", 'r') as file: + content = file.read() + keywords["content"] = md.convert(content) + keywords["title"] = md.Meta['title'] + except FileNotFoundError: + return None + + return keywords + +STATIC_BLOG_POSTS = get_blog_posts() + +def _template(tmplt, **kw): + kw['mascot'] = random.randint(1, MASCOT_COUNT) + return render_template(tmplt, **kw) + +def static_template(tmplt, **kw): + kw['mascot'] = random.randint(1, MASCOT_COUNT) + kw['request'] = { 'path': f"/{tmplt[:-5]}" } + if tmplt == 'blogindex.html': + kw['request']['path'] = '/blog' + env = jinja2.Environment( + loader=jinja2.PackageLoader('shiimoe','templates')) + template = env.get_template(tmplt) + return template.render(**kw) + +def frozen(d): + if isinstance(d, dict): + return frozenset(frozen(item) for item in d.items()) + if isinstance(d, list) or isinstance(d, tuple): + return tuple(frozen(item) for item in d) + return d + +STATIC_TEMPLATES = { + ('index.html', frozen({})): static_template('index.html'), + ('music.html', frozen({})): static_template('music.html'), + ('school.html', frozen({})): static_template('school.html'), + ('norsk.html', frozen({})): static_template('music.html'), + ('books.html', frozen({})): static_template('books.html'), + ('blogindex.html', frozen({ + 'posts': STATIC_BLOG_POSTS + })): static_template('blogindex.html', posts=STATIC_BLOG_POSTS), +} + +for blogpost in os.listdir(BLOG_PATH): + slug = blogpost[:-3] + kw = get_blog_post(slug) + STATIC_TEMPLATES[('blogpost.html', frozen(kw))] = ( + static_template('blogpost.html', **kw) + ) + +def template(tmplt, **kw): + rendered = STATIC_TEMPLATES.get((tmplt, frozen(kw))) + if rendered: + print("Serving cached static template:", tmplt) + return rendered + print("Server rendering template:", tmplt) + return _template(tmplt, **kw) + +def read_comments(): + comments = None + try: + with open(COMMENT_FILE, 'r') as f: + comments = json.load(f) + except FileNotFoundError: + comments = [] + + return comments + +def is_subpath(path, dir): + return path.startswith(dir) + +def send_static_page(path, filename): + path = os.path.realpath(os.path.join(STATIC_PATH, path)) + if not is_subpath(path, STATIC_PATH): + return 403 + + if not filename.endswith('.html'): + filename += '.html' + return send_from_directory(path, filename) + +def send_public_asset(path, filename): + path = os.path.realpath(os.path.join(PUBLIC_PATH, path)) + if not is_subpath(path, PUBLIC_PATH): + return 403 + + return send_from_directory(path, filename) + +@app.route('/') +def home(): + return template('index.html') + +def render_guestbook(**kw): + comments = read_comments() + comments.reverse() + + for comment in comments: + comment['comment'] = bleach.clean(comment['comment'], + tags=ALLOWED_TAGS, + attributes=ALLOWED_ATTRIBUTES, + protocols=ALLOWED_PROTOCOLS) + comment['comment'] = md.convert(comment['comment']) + + kw['comments'] = comments + return template('guestbook.html', **kw) + +@app.route('/guestbook') +@app.route('/guestbook.html') +def guestbook(): + return render_guestbook() + +@app.route('/blog') +@app.route('/blog/index.html') +def blogindex(): + posts = get_blog_posts() + return template('blogindex.html', posts=posts) + +@app.route('/blog/<slug>') +@app.route('/blog/<slug>.html') +def blogpost(slug): + keywords = get_blog_post(slug) + if keywords is None: + return send_static_page('./', '404.html'), 404 + + return template('blogpost.html', **keywords) + +LINK_MATCH = re.compile("https?://") + +@app.route('/postcomment', methods=['POST']) +def postcomment(): + now = datetime.now() + + name = request.form.get('name') + comment = request.form.get('comment') + date = request.form.get('date') or datetime.strftime(now, DATE_FORMAT) + ip = (request.environ.get('HTTP_X_REAL_IP') + or request.environ.get('REMOTE_ADDR') + or request.remote_addr) + + err = lambda msg: render_guestbook(error_msg=msg, dcomment=comment, dname=name) + + # Comment or name left empty, send error message. + if not (name or "").strip() or not (comment or "").strip(): + return err("Please provide a name and comment.") + + # Spam filtering!! + if len(name) > 110: + return err("You're taking the piss with a name that long mate.") + if len(comment) > 850: + return err("No more than 850 characters!") + if name.lower() == "annasysdek": + return err("Sorry, that name is toxic. Pick another.") + if len(LINK_MATCH.findall(comment)) != 0: + return err("No links!") + + comments = read_comments() + if any(c['comment'] == comment for c in comments): + return err("Duplicate comment. Please write unique comments.") + + comments.append({ + 'name': name, + 'comment': comment, + 'date': date, + 'ip': ip + }) + + with open(COMMENT_FILE, 'w') as f: + json.dump(comments, f, indent=4, separators=(",", ": ")) + + return redirect('/guestbook', code=302) + + +@app.route('/<path:filepath>') +def serve_static(filepath): + path, filename = os.path.split(filepath) + if os.path.exists(os.path.join(PUBLIC_PATH, path, filename)): + return send_public_asset(path, filename) + if not filename.endswith('.html'): + filename += '.html' + if os.path.exists(os.path.join(TEMPLATE_PATH, path, filename)): + return template(os.path.join(path, filename)) + if os.path.exists(os.path.join(STATIC_PATH, path, filename)): + return send_static_page(path, filename) + # Otherwise, 404. + return send_static_page('./', '404.html'), 404 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/shiimoe/templates/blogindex.html b/shiimoe/templates/blogindex.html @@ -0,0 +1,46 @@ +{% extends 'layout.html' %} +{% block head %} + <style> + .blog-posts { + margin: 0; + padding: 0; + list-style: none; + } + .blog-listing a { + display: flex; + justify-content: space-between; + padding: 0.7em 1em; + margin: 0.3em 0; + border-radius: 5pt; + transition: all .05s ease; + text-decoration: none; + } + .blog-listing a:hover { + background-color: #679dd727; + transform: scale(1.01); + } + .blog-listing .title { + text-decoration: underline; + } + .blog-listing .published { + opacity: 0.4; + } + </style> +{% endblock %} +{% block header %} + <a href="/"><h1>{% block title %}Blog List{% endblock %}!!</h1></a> +{% endblock %} +{% block content %} + <ul class="blog-posts"> + {% for post in posts %} + <li class="blog-listing"> + <a href="/blog/{{ post['slug'] }}"> + <span class="title">{{ post['title'] }}</span> + <span class="published"> + (<time datetime="{{ post['datetime'] }}">{{ post['date'] }}</time>) + </span> + </a> + </li> + {% endfor %} + </ul> +{% endblock %} diff --git a/shiimoe/templates/blogpost.html b/shiimoe/templates/blogpost.html @@ -0,0 +1,41 @@ +{% extends 'layout.html' %} +{% block title %}{{ title }}{% endblock %} +{% block head %} + <style> + .blog-title { + text-align: left; + font-size: 3em; + text-shadow: none; + font-weight: 300; + } + article figure { + display: flex; + margin-left: 0; + margin-right: 0; + padding: 0; + justify-content: center; + align-content: center; + align-items: center; + flex-direction: column; + } + article figure figcaption { + font-style: italic; + font-size: small; + margin-top: 1em; + } + article img { + max-width: 100%; + max-height: 30em; + border-radius: 7pt; + border: 1pt solid #fff8; + box-shadow: 0 4px 20px -7px #0006; + } + </style> +{% endblock %} +{% block header %} + <a href="/"><h1>Shimo!!</h1></a> +{% endblock %} +{% block content %} + <h1 class="blog-title">{{ title }}</h1> + <article>{{ content | safe }}</article> +{% endblock %} diff --git a/shiimoe/templates/books.html b/shiimoe/templates/books.html @@ -0,0 +1,9 @@ +{% extends 'layout.html' %} +{% block header %} + <a href="/"><h1>{% block title %}Blog List{% endblock %}!!</h1></a> +{% endblock %} +{% block content %} + <p><em>The Trial?</em> more like <em>The Smile</em>, because it makes me happy + when I read it (✿◡‿◡)</p> + <p>Har du lest <em>Stoner</em>? Du har ikke? Slutt å les dette og begynn å lese det i stedet for!</p> +{% endblock %} diff --git a/shiimoe/templates/guestbook.html b/shiimoe/templates/guestbook.html @@ -0,0 +1,168 @@ +{% extends 'layout.html' %} +{% block head %} + <style> + .name { + float: left; + } + .date { + float: right; + font-family: var(--monospace); + } + .date, .name { + color: #0007; + } + .comment-text { + margin-top: 0.5rem; + } + .comments { + margin-top: 3rem; + } + .comments a { + color: #4e87bd; + text-decoration: underline; + } + .comments a:hover { + color: #3d6f9f; + } + .comments h1, .comments h2, .comments h3, + .comments h4, .comments h5, .comments h6 { + text-align: left; + margin: 0.4em 0 0 0; + border-bottom: 1pt solid #0000000f; + } + .comments img { + max-width: 100%; + max-height: 20em; + display: block; + border: 2pt solid #0000001a; + border-radius: 5pt; + margin: 0.5em 0; + filter: drop-shadow(0 4pt 8pt #0004); + } + .input { + font-family: var(--serif); + padding: 0.4rem 0.6rem; + font-size: 0.9em; + width: 30%; + border: 1px solid #0004; + border-radius: 3pt; + } + .textbox { + margin: 0.5em 0; + padding: 0; + position: relative; + } + .textbox > i { + position: absolute; + left: 0.6em; + bottom: 0.7em; + opacity: 0.3; + font-size: 1rem; + } + .comment-input { + font-family: var(--monospace); + height: 8em; + margin: 0em; + } + .comment { + margin: 2rem 0 1.5rem 0; + background: #00000008; + padding: 0.7rem 1.2rem 0 1.2rem; + border-bottom: 3pt solid #0000001a; + } + .comment-text p:nth-child(1) { + margin-top: 0; + } + input[type="submit"] { + margin-left: 30%; + font-family: inherit; + transform-origin: left; + transform: translateX(-50%); + } + .error { + margin: 1em 0 2em 0; + padding: 1em 1.5em; + border: 2pt solid #ff6b0091; + border-radius: 10pt; + border-top-left-radius: 0; + background: #ce285bad; + color: white; + } + </style> +{% endblock %} +{% block header %} + <a href="/"><h1>{% block title %}Guestbook{% endblock %}!!</h1></a> +{% endblock %} +{% block content %} + {% if error_msg is defined %} + <p class="error">{{ error_msg }}</p> + {% endif %} + + <form action="/postcomment" method="POST" name="commentBox"> + <input class="name-input input" + name="name" placeholder="Your name" + {% if dname is defined %} value="{{ dname }}" {% endif %} + required> + <div class="textbox"> + <textarea class="comment-input input" name="comment" + placeholder="# Your comment&#x0a;&#x0a;(no links, ≤850 characters)" + required>{{ dcomment }}</textarea> + <i class="fab fa-markdown"></i> + </div> + <input type="submit" value="Post"> + </form> + <!-- add gender radio boxes (femboy) --> + + <script> + // Script to get local time, will just fall back on server time + // if the script does not run. + const form = document.forms.namedItem("commentBox"); + form.addEventListener('submit', ev => { + ev.preventDefault(); + + const formData = new FormData(form); + + const now = new Date(); + const { year, month, day } = { + year: String(now.getFullYear()), + month: String(now.getMonth() + 1).padStart(2, '0'), + day: String(now.getDate()).padStart(2, '0') + }; + const { hour, minute } = { + hour: String(now.getHours()).padStart(2, '0'), + minute: String(now.getMinutes()).padStart(2, '0') + }; + const dateString = `${year}-${month}-${day} ${hour}:${minute}`; + + // Append to form: + formData.append('date', dateString); + + // Send form: + const req = new XMLHttpRequest(); + req.open(form.method, form.action, true); + req.onload = event => { + document.write(req.response); + }; + + req.send(formData); + }, false); + </script> + + {% if comments is defined %} + <div class="comments"> + {% for comment in comments %} + <div class="comment"> + <span class="name">~ {{ comment['name'] }}</span><span class="date">~{{ comment['date'] }}</span> + <div style="clear: left;"></div> + <div class="comment-text" style="font-size: 120%;"> + {{ comment['comment'] | safe }} + </div> + </div> + {% else %} + <p align="center">No comment posted yet... :(</p> + {% endfor %} + </div> + {% else %} + <center><p>Comments weren't given by renderer!!</p></center> + {% endif %} +{% endblock %} diff --git a/shiimoe/templates/index.html b/shiimoe/templates/index.html @@ -0,0 +1,34 @@ +{% extends 'layout.html' %} +{% block title %}Hjem{% endblock %} +{% block header %} + <a href="/"><h1>Shimo!!</h1></a> +{% endblock %} +{% block content %} + <noscript><p>Welcome, Based JavaScript Hater.</p></noscript> + + <p>Hei, jeg heter Shimo, jeg studerer fysikk og kjemi på universitet. + Jeg liker å lese (favoritten min er Kafka) og spile gitar. + Waifuen min er Yui, selvfølgelig. + </p> + + <p> + Kontakt: <a class="link" href="mailto:sam@shii.moe"><code>sam@shii.moe</code></a>, + <a class="link" href="https://twitter.com/shimofruit"><code>@shimofruit</code></a>. + </p> + + <h2>Sign the + <a class="titleButton" + style="width:auto; padding:0 0.25em; margin: 0;" + href="/guestbook">Guestbook!!</a> + </h2> + + <h3>Reasons Why The Ladies Love Me:</h3> + <ol> + <li>I'm 6'</li> + <li>I have a blåhaj</li> + <li>My Mum says I'm cool</li> + <li>I'll give you kisses</li> + <li>I have a PGP key: <a class="link" href="shiimoe.pgp"><code>A5E16A6FFC9DD158</code></a></li> + <li>My website is <a class="link" href="https://github.com/Shiimoe/shii.moe">open source!</a></li> + </ol> +{% endblock %} diff --git a/shiimoe/templates/layout.html b/shiimoe/templates/layout.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> + +{% macro nav_link(link, title) -%} + {% if request.path == link %} + <div class="titleButton"><a href="/">Hjem</a></div> + {% else %} + <div class="titleButton"><a href="{{ link }}">{{ title }}</a></div> + {% endif %} +{%- endmacro %} + +<html lang="en-GB"> +<head> + {% block meta %} + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + {% endblock %} + {% block head %} + {% endblock %} + {% block default_head %} + <link rel="stylesheet" type="text/css" href="/style.css"/> + <link rel="icon" type="image/png" href="/mascots/{{ mascot }}-square.png"> + <link rel="preload" as="image" href="/background-300x300.png"> + <title>{% block title %}{% endblock %} – Shiimoe!!</title> + {% endblock %} +</head> +<body> + <div id="content"> + {% block header %} + {% endblock %} + {% block nav %} + <div class="titleBar"> + {{ nav_link('/music', 'Musikk') }} + {{ nav_link('/school', 'Skole') }} + {{ nav_link('/norsk', 'Norsk') }} + {{ nav_link('/books', 'Bøker') }} + {{ nav_link('/blog', 'Blog') }} + </div> + {% endblock %} + {% block content %} + {% endblock %} + </div> + {% block scripts %} + <noscript><style>#mascot { opacity: 0.85; }</style></noscript> + <img src="/mascots/{{ mascot }}.png" id="mascot"></img> + <script src="/colours.js"></script> + <script src="/image.js"></script> + <script>randomMascot(NOVELTY);</script> + {% endblock %} +</body> +</html> diff --git a/shiimoe/templates/music.html b/shiimoe/templates/music.html @@ -0,0 +1,32 @@ +{% extends 'layout.html' %} +{% block header %} + <a href="/"><h1>{% block title %}Musikk{% endblock %}!!</h1></a> +{% endblock %} +{% block content %} + <div class="guitarAudio"> + <audio controls s> + <source src="lemon.mp3" type="audio/mpeg"> + </audio> + </div> + + <div class = "guitarCentre"> + <img src="gita.jpg" class="guitarPicture"> + <p class="guitarText">A recording of <em>Lemon Meringue Tie</em> by <em>Dance Gavin Dance</em> played on my <em>Epiphone Les Paul</em>, coming + through an <em>Orange Crush 20</em> with a <em>Boss DS-1</em> for distortion. + </p> + <p class="guitarText">Et opptak av <em>Lemon Merginue Tie</em> av <em>Dance Gavin Dance</em> spillet + på <em>Epiphone Les Paul</em>en min, gjennom en <em>Orange Crush 20</em> med en <em>Boss DS-1</em> + for forvrengning.</p> + </div> + + <div class ="guitarAudio"> + <audio controls s class="audio"> + <source src="rio.mp3" type="audio/mpeg"> + </audio> + </div> + + <div class = "guitarCentre"> + <img src="classical.jpg" class="guitarPicture"> + <p class="guitarText">Dette musikkstykket heter <em>Rio by Night</em> av <em>Vincent Lindsey-Clark</em>, det er veldig enkel, men det er gøy å spille.</p> + </div> +{% endblock %} diff --git a/shiimoe/templates/norsk.html b/shiimoe/templates/norsk.html @@ -0,0 +1,10 @@ +{% extends 'layout.html' %} +{% block header %} + <a href="/"><h1>{% block title %}Norsk{% endblock %}!!</h1></a> +{% endblock %} +{% block content %} + <p>Hei, kjæresten min er Norsk, han prøver å lære meg Norsk, men det går ikke veldig bra.</p> + <p>Jeg laget dette i GIMP mange år siden.</p> + + <img src="norsk.png" class="image-card"> +{% endblock %} diff --git a/shiimoe/templates/school.html b/shiimoe/templates/school.html @@ -0,0 +1,9 @@ +{% extends 'layout.html' %} +{% block header %} + <a href="/"><h1>{% block title %}Skole{% endblock %}!!</h1></a> +{% endblock %} +{% block content %} + <h3 style="text-align: center;">Maths</h3> + + <iframe class="pdf" src="maths.pdf" width="100%" height="900pt"></iframe> +{% endblock %} diff --git a/static/books.html b/static/books.html @@ -1,37 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Shimo!!</title> - <link rel='stylesheet' type='text/css' href="style.css"/> - <link rel='icon' type='image/png' href="/mascots/2-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> -</head> -<body> - <div id="content"> - - <a href="/"><h1>Bøker!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/music">Musik</a></div> - <div class="titleButton"><a href="/school">Skole</a></div> - <div class="titleButton"><a href="/norsk">Norsk</a></div> - <div class="titleButton"><a href="/">Hjem</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - <p><em>The Trial?</em> more like <em>The Smile</em>, because it makes me happy - when I read it (✿◡‿◡)</p> - <p>Har du lest <em>Stoner</em>? Du har ikke? Slutt å les dette og begynn å lese det i stedet for!</p> - - </div> - - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/2.png" id="mascot"></img> - <script src="colours.js"></script> - <script src="image.js"></script> - <script>randomMascot(NOVELTY);</script> -</body> -</html> diff --git a/static/index.html b/static/index.html @@ -1,56 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Shimo!!</title> - <link rel='stylesheet' type='text/css' href="style.css"> - <link rel='icon' type='image/png' href="/mascots/1-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> -</head> -<body> - <div id="content"> - - <a href="/"><h1>Shimo!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/music">Musik</a></div> - <div class="titleButton"><a href="/school">Skole</a></div> - <div class="titleButton"><a href="/norsk">Norsk</a></div> - <div class="titleButton"><a href="/books">Bøker</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - <noscript><p>Welcome, Based JavaScript Hater.</p></noscript> - - <p>Hei, jeg heter Shimo, jeg studerer fysikk og kjemi på universitet. - Jeg liker å lese (favoritten min er Kafka) og spile gitar. - Waifuen min er Yui, selvfølgelig. - </p> - - <p> - Kontakt: <a href="mailto:sam@shii.moe"><code>sam@shii.moe</code></a>, - <a href="https://twitter.com/shimofruit"><code>@shimofruit</code></a>. - </p> - - <h2>Sign the <a class="titleButton" style="width:auto; margin: 0;" href="/guestbook">Guestbook!!</a></h2> - - <h3>Reasons Why The Ladies Love Me:</h3> - <ol> - <li>I'm 6'</li> - <li>I have a blåhaj</li> - <li>My Mum says I'm cool</li> - <li>I'll give you kisses</li> - <li>I have a PGP key: <a href="shiimoe.pgp"><code>A5E16A6FFC9DD158</code></a></li> - <li>My website is <a href="https://github.com/Shiimoe/shii.moe">open source!</a></li> - </ol> - </div> - - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/1.png" id="mascot"></img> - <script src="colours.js"></script> - <script src="image.js"></script> - <script>randomMascot(NOVELTY);</script> -</body> -</html> diff --git a/static/music.html b/static/music.html @@ -1,62 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Musik!!</title> - <link rel='stylesheet' type='text/css' href="style.css"/> - <link rel='icon' type='image/png' href="/mascots/3-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> -</head> -<body> - <div id="content"> - <a href="/"><h1>Musik!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/">Hjem</a></div> - <div class="titleButton"><a href="/school">Skole</a></div> - <div class="titleButton"><a href="/norsk">Norsk</a></div> - <div class="titleButton"><a href="/books">Bøker</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - <div class="guitarAudio"> - <audio controls s> - <source src="lemon.mp3" type="audio/mpeg"> - </audio> - </div> - - - <div class = "guitarCentre"> - <img src="gita.jpg" class="guitarPicture"> - <p class="guitarText">A recording of <em>Lemon Meringue Tie</em> by <em>Dance Gavin Dance</em> played on my <em>Epiphone Les Paul</em>, coming - through an <em>Orange Crush 20</em> with a <em>Boss DS-1</em> for distortion. - </p> - <p class="guitarText">Et opptak av <em>Lemon Merginue Tie</em> av <em>Dance Gavin Dance</em> spillet - på <em>Epiphone Les Paul</em>en min, gjennom en <em>Orange Crush 20</em> med en <em>Boss DS-1</em> - for forvrengning.</p> - </div> - - <div class ="guitarAudio"> - <audio controls s class="audio"> - <source src="rio.mp3" type="audio/mpeg"> - </audio> - </div> - - <div class = "guitarCentre"> - <img src="classical.jpg" class="guitarPicture"> - <p class="guitarText">Dette musikkstykket heter <em>Rio by Night</em> av <em>Vincent Lindsey-Clark</em>, det er veldig - enkel, men det er gøy å spille.</p> - </div> - - - </div> - - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/3.png" id="mascot"></img> - <script src="colours.js"></script> - <script src="image.js"></script> - <script>randomMascot(NOVELTY);</script> -</body> -</html> diff --git a/static/norsk.html b/static/norsk.html @@ -1,38 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Shimo!!</title> - <link rel='stylesheet' type='text/css' href="style.css"/> - <link rel='icon' type='image/png' href="/mascots/4-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> -</head> -<body> - <div id="content"> - - <a href="/"><h1>Norsk!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/music">Musik</a></div> - <div class="titleButton"><a href="/school">Skole</a></div> - <div class="titleButton"><a href="/">Hjem</a></div> - <div class="titleButton"><a href="/books">Bøker</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - <p>Hei, kjæresten min er Norsk, han prøver å lære meg Norsk, men det går ikke veldig bra.</p> - <p>Jeg laget dette i GIMP mange år siden.</p> - - <img src="norsk.png" class="image-card"> - - </div> - - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/4.png" id="mascot"></img> - <script src="colours.js"></script> - <script src="image.js"></script> - <script>randomMascot(NOVELTY);</script> -</body> -</html> diff --git a/static/school.html b/static/school.html @@ -1,37 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Shimo!!</title> - <link rel='stylesheet' type='text/css' href="style.css"/> - <link rel='icon' type='image/png' href="/mascots/5-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> -</head> -<body> - <div id="content"> - - <a href="/"><h1>Skole!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/music">Musik</a></div> - <div class="titleButton"><a href="/">Hjem</a></div> - <div class="titleButton"><a href="/norsk">Norsk</a></div> - <div class="titleButton"><a href="/books">Bøker</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - <h3 style="text-align: center;">Maths</h3> - - <iframe class="pdf" src="maths.pdf" width="100%" height="900pt"></iframe> - - </div> - - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/5.png" id="mascot"></img> - <script src="colours.js"></script> - <script src="image.js"></script> - <script>randomMascot(NOVELTY);</script> -</body> -</html> diff --git a/templates/blogindex.html b/templates/blogindex.html @@ -1,71 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link rel='stylesheet' type='text/css' href="style.css"/> - <link rel='icon' type='image/png' href="/mascots/8-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <title>Blog Index</title> - <style> - .blog-posts { - margin: 0; - padding: 0; - list-style: none; - } - .blog-listing a { - display: flex; - justify-content: space-between; - padding: 0.7em 1em; - margin: 0.3em 0; - border-radius: 5pt; - transition: background-color .05s ease; - text-decoration: none; - } - .blog-listing a:hover { - background-color: #679dd727; - } - .blog-listing .title { - text-decoration: underline; - } - .blog-listing .published { - opacity: 0.4; - } - </style> -</head> -<body> - <div id="content"> - - <a href="/"><h1>Blog Posts!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/music">Musik</a></div> - <div class="titleButton"><a href="/school">Skole</a></div> - <div class="titleButton"><a href="/norsk">Norsk</a></div> - <div class="titleButton"><a href="/books">Bøker</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - <ul class="blog-posts"> - {% for post in posts %} - <li class="blog-listing"> - <a href="/blog/{{ post['slug'] }}"> - <span class="title">{{ post['title'] }}</span> - <span class="published"> - (<time datetime="{{ post['datetime'] }}">{{ post['date'] }}</time>) - </span> - </a> - </li> - {% endfor %} - </ul> - - </div> - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/8.png" id="mascot"></img> - <script src="/colours.js"></script> - <script src="/image.js"></script> - <script>randomMascot(NOVELTY);</script> - -</body> -</html>- \ No newline at end of file diff --git a/templates/blogpost.html b/templates/blogpost.html @@ -1,67 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link rel='stylesheet' type='text/css' href="/style.css"/> - <link rel='icon' type='image/png' href="/mascots/8-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <title>{{ title }}</title> - <style> - .blog-title { - text-align: left; - font-size: 3em; - text-shadow: none; - font-weight: 300; - } - article figure { - display: flex; - margin-left: 0; - margin-right: 0; - padding: 0; - justify-content: center; - align-content: center; - align-items: center; - flex-direction: column; - } - article figure figcaption { - font-style: italic; - font-size: small; - margin-top: 1em; - } - article img { - max-width: 100%; - max-height: 30em; - border-radius: 7pt; - border: 1pt solid #fff8; - box-shadow: 0 4px 20px -7px #0006; - } - </style> -</head> -<body> - <div id="content"> - - <a href="/"><h1>Shimo!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/music">Musik</a></div> - <div class="titleButton"><a href="/school">Skole</a></div> - <div class="titleButton"><a href="/norsk">Norsk</a></div> - <div class="titleButton"><a href="/books">Bøker</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - <h1 class="blog-title">{{ title }}</h1> - <article>{{ content | safe }}</article> - - </div> - - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/8.png" id="mascot"></img> - <script src="/colours.js"></script> - <script src="/image.js"></script> - <script>randomMascot(NOVELTY);</script> - -</body> -</html>- \ No newline at end of file diff --git a/templates/guestbook.html b/templates/guestbook.html @@ -1,194 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Shimo!!</title> - <style> - .name { - float: left; - } - .date { - float: right; - font-family: var(--monospace); - } - .date, .name { - color: #0007; - } - .comment-text { - margin-top: 0.5rem; - } - .comments { - margin-top: 3rem; - } - .comments a { - color: #4e87bd; - text-decoration: underline; - } - .comments a:hover { - color: #3d6f9f; - } - .comments h1, .comments h2, .comments h3, - .comments h4, .comments h5, .comments h6 { - text-align: left; - margin: 0.4em 0 0 0; - border-bottom: 1pt solid #0000000f; - } - .comments img { - max-width: 100%; - max-height: 20em; - display: block; - border: 2pt solid #0000001a; - border-radius: 5pt; - margin: 0.5em 0; - filter: drop-shadow(0 4pt 8pt #0004); - } - .input { - font-family: var(--serif); - padding: 0.4rem 0.6rem; - font-size: 0.9em; - width: 30%; - border: 1px solid #0004; - border-radius: 3pt; - } - .textbox { - margin: 0.5em 0; - padding: 0; - position: relative; - } - .textbox > i { - position: absolute; - left: 0.6em; - bottom: 0.7em; - opacity: 0.3; - font-size: 1rem; - } - .comment-input { - font-family: var(--monospace); - height: 8em; - margin: 0em; - } - .comment { - margin: 2rem 0 1.5rem 0; - background: #00000008; - padding: 0.7rem 1.2rem 0 1.2rem; - border-bottom: 3pt solid #0000001a; - } - .comment-text p:nth-child(1) { - margin-top: 0; - } - input[type="submit"] { - margin-left: 30%; - font-family: inherit; - transform-origin: left; - transform: translateX(-50%); - } - .error { - margin: 1em 0 2em 0; - padding: 1em 1.5em; - border: 2pt solid #ff6b0091; - border-radius: 10pt; - border-top-left-radius: 0; - background: #ce285bad; - color: white; - } - </style> - - <link rel='stylesheet' type='text/css' href="style.css"/> - <link rel='icon' type='image/png' href="/mascots/8-square.png"> - <link rel="preload" as="image" href="/background-300x300.png"> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> -</head> -<body> - <div id="content"> - - <a href="/"><h1>Shimo!!</h1></a> - - <div class="titleBar"> - <div class="titleButton"><a href="/music">Musik</a></div> - <div class="titleButton"><a href="/school">Skole</a></div> - <div class="titleButton"><a href="/norsk">Norsk</a></div> - <div class="titleButton"><a href="/books">Bøker</a></div> - <div class="titleButton"><a href="/blog">Blog</a></div> - </div> - - {% if error_msg is defined %} - <p class="error">{{ error_msg }}</p> - {% endif %} - - <form action="/postcomment" method="POST" name="commentBox"> - <input class="name-input input" - name="name" placeholder="Your name" - {% if dname is defined %} value="{{ dname }}" {% endif %} - required> - <div class="textbox"> - <textarea class="comment-input input" name="comment" - placeholder="# Your comment&#x0a;&#x0a;(no links, ≤850 characters)" - required>{{ dcomment }}</textarea> - <i class="fab fa-markdown"></i> - </div> - <input type="submit" value="Post"> - </form> - <!-- add gender radio boxes (femboy) --> - - <script> - // Script to get local time, will just fall back on server time - // if the script does not run. - const form = document.forms.namedItem("commentBox"); - form.addEventListener('submit', ev => { - ev.preventDefault(); - - const formData = new FormData(form); - - const now = new Date(); - const { year, month, day } = { - year: String(now.getFullYear()), - month: String(now.getMonth() + 1).padStart(2, '0'), - day: String(now.getDate()).padStart(2, '0') - }; - const { hour, minute } = { - hour: String(now.getHours()).padStart(2, '0'), - minute: String(now.getMinutes()).padStart(2, '0') - }; - const dateString = `${year}-${month}-${day} ${hour}:${minute}`; - - // Append to form: - formData.append('date', dateString); - - // Send form: - const req = new XMLHttpRequest(); - req.open(form.method, form.action, true); - req.onload = event => { - document.write(req.response); - }; - - req.send(formData); - }, false); - </script> - - {% if comments is defined %} - <div class="comments"> - {% for comment in comments %} - <div class="comment"> - <span class="name">~ {{ comment['name'] }}</span><span class="date">~{{ comment['date'] }}</span> - <div style="clear: left;"></div> - <div class="comment-text" style="font-size: 120%;"> - {{ comment['comment'] | safe }} - </div> - </div> - {% else %} - <p align="center">No comment posted yet... :(</p> - {% endfor %} - </div> - {% else %} - <center><p>Comments weren't given by renderer!!</p></center> - {% endif %} - </div> - - <noscript><style>#mascot { opacity: 0.85; }</style></noscript> - <img src="/mascots/8.png" id="mascot"></img> - <script src="/colours.js"></script> - <script src="/image.js"></script> - <script>randomMascot(NOVELTY);</script> -</body> -</html>