{{ metadata.title}}
+{{ metadata.date | human_date }}
++ {{post|safe}} +
')
+def error(code):
+ error_definitions = {
+ 400: 'Bad Request',
+ 403: 'Forbidden',
+ 404: 'Page Not Found',
+ 418: 'I\'m a Teapot',
+ 500: 'Internal Server Error',
+ 503: 'Service Temporarily Unavailable',
+ 505: 'HTTP Version Not Supported'
+ }
+ error_desc = {
+ 400: 'Sorry, I didn\'t understand your request.',
+ 403: 'Sorry, you aren\'t allowed to view this page.',
+ 404: 'Sorry, that page doesn\'t exist.',
+ 418: 'I can\'t brew coffee as I am, in fact, a teapot.',
+ 500: 'Something went wrong on my end.',
+ 503: 'My website is experiencing some issues and will be back shortly.',
+ 505: 'Your browser tried to use a HTTP version I don\'t support. Check it is up to date.'
+ }
+
+ return render_template('error.html',
+ error=f'{code}: {error_definitions.get(int(code))}',
+ description=error_desc.get(int(code)))
diff --git a/src/projects.py b/src/projects.py
new file mode 100644
index 0000000..81b2ec8
--- /dev/null
+++ b/src/projects.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python3
+
+from os import path
+import json
+from flask import Flask, render_template, Response, send_from_directory
+from markdown import markdown
+import frontmatter
+from glob import glob
+from datetime import datetime
+from index import app
+from bs4 import BeautifulSoup
+
+md_directory = path.join(path.realpath(path.dirname(__file__)), path.normpath('projects/'))
+
+@app.context_processor
+def processor():
+ def get_excerpt(post):
+ html = markdown(post.content)
+ post_soup = BeautifulSoup(html, 'html.parser')
+ all_text = ' '.join([x.get_text() for x in post_soup.findAll('p')])
+ return ' '.join(all_text.split()[:200])
+ return dict(get_excerpt=get_excerpt)
+
+@app.template_filter('category_title')
+def category_title(category_id: str) -> str:
+ with open(path.join(md_directory, 'categories.json')) as categories_file:
+ categories = json.load(categories_file)
+
+ return categories.get(category_id).get('title', '')
+
+@app.template_filter('human_date')
+def human_date(iso_date: str) -> str:
+ try:
+ return datetime.fromisoformat(str(iso_date)).strftime('%A %d %B %Y')
+ except ValueError:
+ return iso_date
+
+@app.template_filter('to_html')
+def to_html(content):
+ return markdown(content)
+
+def get_all_posts(directory: str) -> list:
+ abs_paths = [path.join(directory, x) for x in glob(f'{directory}/*.md')]
+ return [frontmatter.load(x) for x in abs_paths]
+
+def get_by_meta_key(directory: str, key: str, value: str):
+ return [x for x in get_all_posts(directory) if x.get(key) == value or type(x.get(key, [])) is list and value in x.get(key, [])]
+
+@app.route('/projects/')
+def projects():
+ articles_to_return = sorted(
+ get_all_posts(
+ md_directory),
+ key=lambda d: d.metadata.get('date'),
+ reverse=True
+ )
+
+ if len(articles_to_return) < 1:
+ return render_template('error.html',
+ error='There\'s nothing here... yet.',
+ description='I\'m still working on this page. Check back soon for some content.')
+ try:
+ with open(path.join(md_directory, 'categories.json')) as categories_file:
+ categories = json.load(categories_file)
+ except FileNotFoundError:
+ return render_template('error.html',
+ error='There\'s nothing here... yet.',
+ description='I\'m still working on this page. Check back soon for some content.')
+
+ return render_template('projects.html',
+ articles=articles_to_return,
+ all_categories=categories,
+ title='Projects',
+ description='A selection of projects I\'ve been involved in')
+
+@app.route('/projects/category//')
+def category(category):
+ try:
+ with open(path.join(md_directory, 'categories.json')) as categories_file:
+ categories = json.load(categories_file)
+ the_category = categories.get(category)
+ except FileNotFoundError:
+ return render_template('error.html',
+ error='There\'s nothing here... yet.',
+ description='I\'m still working on this page. Check back soon for some content.')
+
+ if the_category is None:
+ return Response(status=404)
+
+ articles_to_return = sorted(
+ get_by_meta_key(
+ md_directory, 'categories', category),
+ key=lambda d: d.metadata.get('date'),
+ reverse=True
+ )
+
+ if len(articles_to_return) < 1:
+ return render_template('error.html',
+ error='There\'s nothing here... yet.',
+ description='I\'m still working on this page. Check back soon for some content.')
+
+ return render_template('projects.html', articles=articles_to_return,
+ title=the_category['title'],
+ description=the_category['long_description'],
+ page_title=f'{the_category["title"]} - ',
+ all_categories=categories,
+ current_category=category)
+
+@app.route('/projects/')
+def article(article):
+ articles = get_by_meta_key(md_directory, 'id', article)
+
+ if len(articles) == 0:
+ return Response(status=404)
+ if len(articles) > 1:
+ return Response(status=500)
+
+ the_article = articles[0]
+ return render_template('article.html', post=markdown(the_article.content),
+ metadata=the_article.metadata,
+ page_title=f'{the_article.metadata["title"]} - ')
+
+@app.route('/projects/image/')
+def image(image):
+ return send_from_directory(path.join(md_directory, 'images'), image)
diff --git a/src/projects.wsgi b/src/projects.wsgi
new file mode 100644
index 0000000..02fed47
--- /dev/null
+++ b/src/projects.wsgi
@@ -0,0 +1,6 @@
+#!/usr/bin/python3
+
+import sys
+sys.path.append('/var/www/jc')
+
+from index import app as application
diff --git a/src/requirements.txt b/src/requirements.txt
new file mode 100644
index 0000000..e2d8dd1
--- /dev/null
+++ b/src/requirements.txt
@@ -0,0 +1,5 @@
+flask>=2.2.3
+flask-markdown>=0.3
+markdown>=3.4.1
+beautifulsoup4>=4.11.1
+python-frontmatter>=1.1.0
diff --git a/src/fonts/fontawesome/css/all.min.css b/src/static/fonts/fontawesome/css/all.min.css
similarity index 100%
rename from src/fonts/fontawesome/css/all.min.css
rename to src/static/fonts/fontawesome/css/all.min.css
diff --git a/src/fonts/fontawesome/webfonts/fa-brands-400.ttf b/src/static/fonts/fontawesome/webfonts/fa-brands-400.ttf
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-brands-400.ttf
rename to src/static/fonts/fontawesome/webfonts/fa-brands-400.ttf
diff --git a/src/fonts/fontawesome/webfonts/fa-brands-400.woff2 b/src/static/fonts/fontawesome/webfonts/fa-brands-400.woff2
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-brands-400.woff2
rename to src/static/fonts/fontawesome/webfonts/fa-brands-400.woff2
diff --git a/src/fonts/fontawesome/webfonts/fa-regular-400.ttf b/src/static/fonts/fontawesome/webfonts/fa-regular-400.ttf
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-regular-400.ttf
rename to src/static/fonts/fontawesome/webfonts/fa-regular-400.ttf
diff --git a/src/fonts/fontawesome/webfonts/fa-regular-400.woff2 b/src/static/fonts/fontawesome/webfonts/fa-regular-400.woff2
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-regular-400.woff2
rename to src/static/fonts/fontawesome/webfonts/fa-regular-400.woff2
diff --git a/src/fonts/fontawesome/webfonts/fa-solid-900.ttf b/src/static/fonts/fontawesome/webfonts/fa-solid-900.ttf
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-solid-900.ttf
rename to src/static/fonts/fontawesome/webfonts/fa-solid-900.ttf
diff --git a/src/fonts/fontawesome/webfonts/fa-solid-900.woff2 b/src/static/fonts/fontawesome/webfonts/fa-solid-900.woff2
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-solid-900.woff2
rename to src/static/fonts/fontawesome/webfonts/fa-solid-900.woff2
diff --git a/src/fonts/fontawesome/webfonts/fa-v4compatibility.ttf b/src/static/fonts/fontawesome/webfonts/fa-v4compatibility.ttf
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-v4compatibility.ttf
rename to src/static/fonts/fontawesome/webfonts/fa-v4compatibility.ttf
diff --git a/src/fonts/fontawesome/webfonts/fa-v4compatibility.woff2 b/src/static/fonts/fontawesome/webfonts/fa-v4compatibility.woff2
similarity index 100%
rename from src/fonts/fontawesome/webfonts/fa-v4compatibility.woff2
rename to src/static/fonts/fontawesome/webfonts/fa-v4compatibility.woff2
diff --git a/src/images/njr-code.png b/src/static/images/njr-code.png
similarity index 100%
rename from src/images/njr-code.png
rename to src/static/images/njr-code.png
diff --git a/src/images/topfuel_startline.jpg.jpeg b/src/static/images/topfuel_startline.jpg.jpeg
similarity index 100%
rename from src/images/topfuel_startline.jpg.jpeg
rename to src/static/images/topfuel_startline.jpg.jpeg
diff --git a/src/static/js/filter_projects.js b/src/static/js/filter_projects.js
new file mode 100644
index 0000000..6d772e0
--- /dev/null
+++ b/src/static/js/filter_projects.js
@@ -0,0 +1,10 @@
+function update_filter() {
+ var project_filter = document.getElementById("filter_category");
+ console.log(project_filter.value)
+ if (project_filter.value == 'all') {
+ window.location.href = '/projects';
+ }
+ else {
+ window.location.href = '/projects/category/' + project_filter.value;
+ }
+}
diff --git a/src/static/js/update_copyright.js b/src/static/js/update_copyright.js
new file mode 100644
index 0000000..1ee3772
--- /dev/null
+++ b/src/static/js/update_copyright.js
@@ -0,0 +1,5 @@
+window.onload = function () {
+ var current_year = new Date().getFullYear();
+ var copyright = document.getElementById('cr-year');
+ copyright.textContent = current_year;
+}
\ No newline at end of file
diff --git a/src/style/desktop.css b/src/static/style/desktop.css
similarity index 73%
rename from src/style/desktop.css
rename to src/static/style/desktop.css
index 17dd22f..e77c0f5 100644
--- a/src/style/desktop.css
+++ b/src/static/style/desktop.css
@@ -36,4 +36,22 @@
.gradient-right{
background-image: linear-gradient(to left, rgba(23, 22, 20, 1), rgba(23, 22, 20, 1), rgba(23, 22, 20, 0));
}
+
+ #projects{
+ align-items: center;
+ justify-content: center;
+ flex-direction: row;
+ flex-wrap: wrap;
+ }
+
+ .project{
+ width: 20vw;
+ height: 25vw;
+ }
+
+ #top-nav{
+ float: right;
+ width: fit-content;
+ padding-right: 20px;
+ }
}
\ No newline at end of file
diff --git a/src/style/mobile.css b/src/static/style/mobile.css
similarity index 58%
rename from src/style/mobile.css
rename to src/static/style/mobile.css
index 175d6e5..8606853 100644
--- a/src/style/mobile.css
+++ b/src/static/style/mobile.css
@@ -2,9 +2,10 @@ body{
margin: 0;
color: #e5e5e5;
font-family: "Noto Sans", sans-serif;
+ background-color: rgba(23, 22, 20, 1);
}
-h1, h2{
+h1, h2, h3{
font-family: "Orbitron", sans-serif;
font-optical-sizing: auto;
font-weight: 500;
@@ -12,17 +13,23 @@ h1, h2{
}
header{
- width: 100%;
- height: 25vh;
- display: flex;
- align-items: center;
background-color: 4c4c4c;
+ height: 25vh;
+ width: 100%;
}
footer{
background-color: 4c4c4c;
- padding: 30px;
- text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 10vh;
+}
+
+#logo-container{
+ width: 100%;
+ justify-content: center;
+ display: flex;
}
#logo{
@@ -30,6 +37,10 @@ footer{
margin: 0 auto 0 auto;
}
+#logo>a{
+ text-decoration: none;
+}
+
footer h2, section h2{
margin: 0;
}
@@ -90,4 +101,70 @@ footer h2, section h2{
a{
color: #e5e5e5;
+}
+
+#projects{
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+}
+
+.project{
+ width: 80vw;
+ border: 2px solid #e5e5e5;
+ margin: 10px;
+}
+
+.project-thumb{
+ max-width: 100%;
+ max-height: 100%;
+}
+
+#filter {
+ float: right;
+}
+
+#top-nav{
+ width: 100%;
+ text-align: center;
+ padding-bottom: 30px;
+ padding-top: 20px
+}
+
+#top-nav>a{
+ text-decoration: none;
+ padding: 0 10px 0 10px;
+ font-weight: bold;
+}
+
+#top-nav>a:hover{
+ text-decoration: underline;
+}
+
+#error-container{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 65vh;
+}
+
+#error{
+ text-align: center;
+}
+
+#project-main{
+ padding: 0 10px 0 10px;
+ min-height: 65vh;
+}
+
+.project-text{
+ padding: 10px;
+}
+
+.article-category{
+ text-decoration: none;
+}
+
+.article-category:hover{
+ text-decoration: underline;
}
\ No newline at end of file
diff --git a/src/templates/article.html b/src/templates/article.html
new file mode 100644
index 0000000..6e1524a
--- /dev/null
+++ b/src/templates/article.html
@@ -0,0 +1,10 @@
+{% include 'header.html' %}
+
+
+ {{ metadata.title}}
+ {{ metadata.date | human_date }}
+
+ {{post|safe}}
+
+
+{% include 'footer.html' %}
\ No newline at end of file
diff --git a/src/templates/error.html b/src/templates/error.html
new file mode 100644
index 0000000..0003ba3
--- /dev/null
+++ b/src/templates/error.html
@@ -0,0 +1,11 @@
+{% include 'header.html' %}
+
+
+
+
+
+{% include 'footer.html' %}
\ No newline at end of file
diff --git a/src/templates/footer.html b/src/templates/footer.html
new file mode 100644
index 0000000..4fb12d8
--- /dev/null
+++ b/src/templates/footer.html
@@ -0,0 +1,5 @@
+
+