Browse Source

Initial website sources.

looper
Ben Niemann 4 years ago
parent
commit
aea6189fd4
  1. 2
      .gitignore
  2. 26
      website/README.md
  3. 62
      website/blog/2019/01-10-development-update.md
  4. 128
      website/css/main.css
  5. 205
      website/css/pygment.css
  6. 52
      website/css/reset.css
  7. 3
      website/css/typogrify.css
  8. 15
      website/imprint.md
  9. 7
      website/index.md
  10. 326
      website/js/html5shiv.js
  11. 2
      website/robots.txt
  12. 36
      website/siteconf.py
  13. 46
      website/templates/base.html
  14. 11
      website/templates/blog-archive.html
  15. 68
      website/templates/blog-base.html
  16. 18
      website/templates/blog-index.html
  17. 19
      website/templates/blog-post.html
  18. 5
      website/templates/page.html

2
.gitignore vendored

@ -6,3 +6,5 @@ __pycache__/
*~
.nobackup
/.venv
/website/_build/
/website/_serve/

26
website/README.md

@ -0,0 +1,26 @@
This is the source code for the http://noisicaa.odahoda.de/ website.
Note that it's using a static site generator called `odasite`, which is currently not publically
available, so the following instructions only work for myself.
# Install odasite
```bash
pip install -e ~/projects/odasite
```
# Editing
```bash
cd website/
odasite serve
```
View website at http://localhost:8000/
# Upload
```bash
odasite remote-diff # Review changes
odasite upload
```

62
website/blog/2019/01-10-development-update.md

@ -0,0 +1,62 @@
Title: development update
Date: 2019-01-10
Development made had a small hiatus in second half of 2018, but in
December - thanks to a lot of spare time - I picked coding up again.
The biggest and most visible change was that I completely rethought
the general idea behind noisicaä. In the beginning I started out with
something that should resemble a piece of paper with staves on it,
that lets you easily create, edit and play music, and I only
envisioned basic audio processing features. Over time that turned into
something much more like the typical DAW with tracks and plugins and
all that stuff. But I wasn't very happy about the way DAWs typically
manage the audio processing. In my mind manipulating the actual
processing graph feels like the most intuitive way, something like
[Pure Data](https://puredata.info/) or a modular synth. Which also
happens to be the way the engine works internally. I didn't quite know
how to make this properly accessible, so I just had something basic in
place.
At some point I came across a [video
demo](https://www.youtube.com/watch?v=pMXhnBANiMA) for
[BespokeSynth](https://github.com/awwbees/BespokeSynth) and I really
liked the general approach of that UI. Eventually I started thinking
more about what noisicaä should actually be. The primary use case has
always been musical composition, and that doesn't change. But most
apps have some kind of metaphor to the real world that they're built
upon (e.g. sheet music for [MuseScore](https://musescore.org/),
recording studio for [Ardour](https://ardour.org/), etc.). And I
simply decided that noisicaä's foundational idea should be the modular
synth.
So made did a major refactoring, putting the processing graph onto the
center stage, both in terms of internal code structure, as well as on
the UI. There are still tracks, and they are prominently featured on
the UI, because time and temporal editing remains to be a central
ingredient. But e.g. now you add a 'Score Track' node to the
processing graph, which will also add an editable track, instead of
the other way around.
Another major change during the past weeks was that I finally got
around to purge all Python from the critical path in the audio
thread. This is now all C++ and hopefully realtime safe. Now playback
even at smaller block sizes is much more reliable.
Finally on the more organizational side, I adopted a scheme of doing
"sprints". Well... I know that there is some development methodology,
which uses the term "sprint", but I don't even know which one that is,
what it exactly means by that word, nor do I intend to adopt any
methodology or become serious about project management or anything
evil like that. But it seems to work quite well to structure that
gigantic pile of ideas, which I want to implement, into reasonably
sized chunks, and then spend a week or two tick off all the boxes for
one of those chunks. And I just call those chunks "sprints".
And maybe, just maybe... if every other week or so, when I merged
all the changes for a completed sprint into the master branch, and
have that nice feeling of accomplishment... maybe that also makes me
want to write a post with a short update of what I did. Otherwise this
blogging thing simply doesn't seem to work for me, as you can tell
from the frequency of postings.

128
website/css/main.css

@ -0,0 +1,128 @@
@import url("reset.css");
@import url("pygment.css");
@import url("typogrify.css");
html, body, #page {
height: 100%;
margin: 0;
padding: 0;
}
html, body {
background-color: #f4f4f4;
font-family: Georgia, "Times New Roman", Times, serif;
}
#header {
padding: 0.4em 1em;
font-size: 2em;
background-color: #fff0ff;
border-bottom: 1px solid #000;
}
#page {
min-height: 100%
color: #000;
}
#main-menu {
margin: 0.5em 0;
padding: 0;
text-align: center;
}
#main-menu li {
display: inline;
margin: 0 0.2em;
padding: 0;
}
#main-menu a {
margin: 0 1px;
padding: 0 0.4em;
}
#content {
padding: 0 1em 2em;
line-height: 1.6em;
}
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6 {
font-weight: bold;
letter-spacing: 0.05em;
line-height: 100%;
margin: 0.8em 0 0.4em 0;
padding: 0;
}
#content h1 {
font-size: 1.96em;
text-indent: -2em;
padding: 0.1em 0 0 2.2em;
}
#content h2 {
font-size: 1.4em;
text-indent: -2em;
padding: 0.1em 0 0 2.2em;
}
#content h3 {
font-size: 1.3em;
font-weight: bold;
}
#content p {
margin: 0 0 0.4em 0;
padding: 0;
word-spacing: 0.1em;
text-indent: 2em;
}
#content ul p, #content h3 + p {
text-indent: 0;
}
#content ul {
list-style: disc outside none;
margin-left: 2em;
}
.imprint {
margin: 1em 2em;
}
.imprint .name {
font-weight: bold;
}
#content .imprint dt {
font-weight: normal;
}
#footer {
position: fixed;
bottom: 0;
width: 100%;
border-top: 1px solid #606060;
padding: 0.2em 0;
background-color: #f0f0ff;
color: #606060;
font-size: 0.8em;
}
#footer p {
margin: 0.2em 0 0;
text-align: center;
}
#footer a:link,
#footer a:visited,
#footer a:active {
color: #606060;
}
#footer a:hover {
color: #600060;
}

205
website/css/pygment.css

@ -0,0 +1,205 @@
.hll {
background-color:#eee;
}
.c {
color:#408090;
font-style:italic;
}
.err {
border:1px solid #FF0000;
}
.k {
color:#007020;
font-weight:bold;
}
.o {
color:#666666;
}
.cm {
color:#408090;
font-style:italic;
}
.cp {
color:#007020;
}
.c1 {
color:#408090;
font-style:italic;
}
.cs {
background-color:#FFF0F0;
color:#408090;
}
.gd {
color:#A00000;
}
.ge {
font-style:italic;
}
.gr {
color:#FF0000;
}
.gh {
color:#000080;
font-weight:bold;
}
.gi {
color:#00A000;
}
.go {
color:#303030;
}
.gp {
color:#C65D09;
font-weight:bold;
}
.gs {
font-weight:bold;
}
.gu {
color:#800080;
font-weight:bold;
}
.gt {
color:#0040D0;
}
.kc {
color:#007020;
font-weight:bold;
}
.kd {
color:#007020;
font-weight:bold;
}
.kn {
color:#007020;
font-weight:bold;
}
.kp {
color:#007020;
}
.kr {
color:#007020;
font-weight:bold;
}
.kt {
color:#902000;
}
.m {
color:#208050;
}
.s {
color:#4070A0;
}
.na {
color:#4070A0;
}
.nb {
color:#007020;
}
.nc {
color:#0E84B5;
font-weight:bold;
}
.no {
color:#60ADD5;
}
.nd {
color:#555555;
font-weight:bold;
}
.ni {
color:#D55537;
font-weight:bold;
}
.ne {
color:#007020;
}
.nf {
color:#06287E;
}
.nl {
color:#002070;
font-weight:bold;
}
.nn {
color:#0E84B5;
font-weight:bold;
}
.nt {
color:#062873;
font-weight:bold;
}
.nv {
color:#BB60D5;
}
.ow {
color:#007020;
font-weight:bold;
}
.w {
color:#BBBBBB;
}
.mf {
color:#208050;
}
.mh {
color:#208050;
}
.mi {
color:#208050;
}
.mo {
color:#208050;
}
.sb {
color:#4070A0;
}
.sc {
color:#4070A0;
}
.sd {
color:#4070A0;
font-style:italic;
}
.s2 {
color:#4070A0;
}
.se {
color:#4070A0;
font-weight:bold;
}
.sh {
color:#4070A0;
}
.si {
color:#70A0D0;
font-style:italic;
}
.sx {
color:#C65D09;
}
.sr {
color:#235388;
}
.s1 {
color:#4070A0;
}
.ss {
color:#517918;
}
.bp {
color:#007020;
}
.vc {
color:#BB60D5;
}
.vg {
color:#BB60D5;
}
.vi {
color:#BB60D5;
}
.il {
color:#208050;
}

52
website/css/reset.css

@ -0,0 +1,52 @@
/*
Name: Reset Stylesheet
Description: Resets browser's default CSS
Author: Eric Meyer
Author URI: http://meyerweb.com/eric/tools/css/reset/
*/
/* v1.0 | 20080212 */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
background: transparent;
border: 0;
font-size: 100%;
margin: 0;
outline: 0;
padding: 0;
vertical-align: baseline;
}
body {line-height: 1;}
ol, ul {list-style: none;}
blockquote, q {quotes: none;}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
/* remember to define focus styles! */
:focus {
outline: 0;
}
/* remember to highlight inserts somehow! */
ins {text-decoration: none;}
del {text-decoration: line-through;}
/* tables still need 'cellspacing="0"' in the markup */
table {
border-collapse: collapse;
border-spacing: 0;
}

3
website/css/typogrify.css

@ -0,0 +1,3 @@
.caps {font-size:.92em;}
.amp {color:#666; font-size:1.05em;font-family:"Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua",serif; font-style:italic;}
.dquo {margin-left:-.38em;}

15
website/imprint.md

@ -0,0 +1,15 @@
Title: Imprint
Date: 2019-01-10
<h2>Imprint</h2>
<div class="imprint">
<div class="name">Benjamin Niemann</div>
<div class="address">Holderbachweg 6<br>8046 Zürich<br>Switzerland</div>
<div class="contact">
<strong>Tel:</strong> +47 43 5383633<br>
<strong>eMail:</strong> <a href="mailto:pink@odahoda.de">pink@odahoda.de</a><br>
<strong>WWW:</strong> <a href="http://pink.odahoda.de/">pink.odahoda.de</a>
</div>
</div>

7
website/index.md

@ -0,0 +1,7 @@
Title: home
# Welcome to noisicaä
noisicaä is an open source DAW for GNU/Linux built around the idea of a modular synthesizer.
The project is currently in pre-alpha state. You can try it out, but it is not recommended to use it for anything serious.

326
website/js/html5shiv.js vendored

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

2
website/robots.txt

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

36
website/siteconf.py

@ -0,0 +1,36 @@
import odasite
import odasite.blog
class Site(odasite.Site):
def configure(self):
ignore = ['*~', '.#*', '*.pyc', 'README.md']
self.add_collector(odasite.FileCollector('.', ignore=ignore))
default_context = {}
jinja2_engine = odasite.Jinja2Engine(
site=self,
template_dir='templates',
default_context=default_context)
blog = odasite.blog.Blog(
source_root='blog',
post_renderer=jinja2_engine.get_renderer('blog-post.html'),
index_renderer=jinja2_engine.get_renderer('blog-index.html'),
archive_renderer=jinja2_engine.get_renderer('blog-archive.html'),
)
blog.setup(self)
self.add_classifier(odasite.Classifier(
odasite.StaticFile,
'css/*', 'js/*',
'robots.txt'))
self.add_classifier(odasite.Classifier(
odasite.MarkdownFile, '*.md',
renderer=jinja2_engine.get_renderer('page.html'),
typogrify=True))
self.add_remote(odasite.RSyncRemote(
host='odahoda.de',
user='pink',
directory='/srv/clients/odahoda/de.odahoda.noisicaa/htdocs/'))

46
website/templates/base.html

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>noisicaä - {% block title %}{%endblock%}</title>
{% block css %}
<link rel="stylesheet" href="/css/main.css" />
{% endblock %}
<!--[if lt IE 9]>
<script src="/js/html5shiv.js"></script>
<![endif]-->
{% if __INTERNAL_BUILD_VERSION %}
<script src="/__internal__/autoreload.js" type="application/javascript"></script>
<script type="application/javascript">
init_autoreload({{__INTERNAL_BUILD_VERSION}});
</script>
{% endif %}
</head>
<body>
<div id="page">
<header id="header">
noisicaä
</header>
<nav id="main-menu">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="https://github.com/odahoda/noisicaa">GitHub</a></li>
</ul>
</nav>
<section id="content">
{% block content %}
{% endblock %}
</section>
<footer id="footer">
<p>(c) 2019 by Ben Niemann - <a href="/imprint">Imprint</a></p>
</footer>
</div>
</body>
</html>

11
website/templates/blog-archive.html

@ -0,0 +1,11 @@
{% extends "blog-base.html" %}
{% block title %}Blog Archive{% endblock %}
{% block blog_content %}
<h2>Archive</h2>
<ul>
{% for post in posts %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}

68
website/templates/blog-base.html

@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block css %}
{{ super() }}
<style type="text/css">
#blog-nav {
border: 1px solid #000;
background-color: #fff;
display: block;
float: right;
padding: 0.2em 1em 0.2em 0.4em;
margin: 0 0em 1em 1em;
min-width: 10em;
line-height: 1.3em;
font-size: 0.9em;
}
#blog-nav ul {
list-style: none;
margin: 0;
padding: 0;
}
#blog-nav li {
list-style: none;
margin: 0;
padding: 0;
}
.blog-post-footer {
font-size: 0.9em;
font-style: italic;
}
.blog-prev-post {
display: inline-block;
float: left;
margin-left: 2em;
}
.blog-next-post {
display: inline-block;
float: right;
margin-right: 2em;
}
</style>
{% endblock %}
{% block content %}
<nav id="blog-nav">
<ul>
<li><a href="/blog/archive">Archive</a></li>
</ul>
<br/>
<strong>Recent posts</strong>
<ul>
{% for post in posts[-10:] | reverse %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
</nav>
{% block blog_content %}{% endblock %}
{% endblock %}

18
website/templates/blog-index.html

@ -0,0 +1,18 @@
{% extends "blog-base.html" %}
{% block title %}blog{% endblock %}
{% block blog_content %}
{% set post = posts[-1] %}
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
{{ post.content }}
<div class="blog-post-footer">
<p>Posted: {{ post.date.strftime('%Y-%m-%d') }}</p>
</div>
{% if post.prev_post %}
<a class="blog-prev-post" href="{{ post.prev_post.url }}">&lt;&lt;&lt; {{ post.prev_post.title }}</a>
{% endif %}
{% endblock %}

19
website/templates/blog-post.html

@ -0,0 +1,19 @@
{% extends "blog-base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block blog_content %}
<h2>{{ title }}</h2>
{{ content }}
<div class="blog-post-footer">
<p>Posted: {{ post.date.strftime('%Y-%m-%d') }}</p>
</div>
{% if post.prev_post %}
<a class="blog-prev-post" href="{{ post.prev_post.url }}">&lt;&lt;&lt; {{ post.prev_post.title }}</a>
{% endif %}
{% if post.next_post %}
<a class="blog-next-post" href="{{ post.next_post.url }}">{{ post.next_post.title }} &gt;&gt;&gt;</a>
{% endif %}
{% endblock %}

5
website/templates/page.html

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
{{ content }}
{% endblock %}
Loading…
Cancel
Save