35 changed files with 556 additions and 150 deletions
@ -0,0 +1,4 @@ |
|||
:set -isrc |
|||
:set -hide-package MonadCatchIO-mtl |
|||
:set -hide-package monads-fd |
|||
:set -XOverloadedStrings |
@ -0,0 +1,59 @@ |
|||
from __future__ import with_statement |
|||
from fabric.api import * |
|||
from fabric.contrib.console import confirm |
|||
from fabric.contrib.project import rsync_project |
|||
import fabric.operations as op |
|||
|
|||
env.hosts = ["wes@mgoal.ca:444"] |
|||
|
|||
@task |
|||
def buildTags(): |
|||
with lcd("./build"): |
|||
local("riot ../src/tags scripts/tags.min.js") |
|||
|
|||
@task |
|||
def buildScss(): |
|||
with lcd("./build"): |
|||
local("sassc ../src/styles/riotblog.scss > styles/riotblog.min.css") |
|||
|
|||
@task |
|||
def minifyJS(): |
|||
with lcd("./build"): |
|||
local("uglifyjs ../src/scripts/riotblog.js > scripts/riotblog.min.js") |
|||
|
|||
@task |
|||
def buildVenv(): |
|||
local("virtualenv -p $(which python3) ./venv") |
|||
with prefix("source ./venv/bin/activate"): |
|||
local("pip3 install -r requirements.txt") |
|||
local("mv venv ./build/") |
|||
|
|||
@task |
|||
def copyFiles(): |
|||
local("cp ./{blog.ini,blog.service} ./build/") |
|||
local("cp ./src/*py ./build/") |
|||
local("cp ./src/styles/*.css ./build/styles/") |
|||
local("cp -r ./src/templates ./build/templates") |
|||
|
|||
@task |
|||
def upload(): |
|||
run("mkdir -p ~/build") |
|||
rsync_project(local_dir="./build/", remote_dir="~/build/", delete=True, exclude=[".git"]) |
|||
|
|||
@task |
|||
def serveUp(): |
|||
sudo("cp -r /home/wes/build /srv/riotblog") |
|||
sudo("cp /home/wes/build/blog.service /etc/systemd/system/blog.service") |
|||
sudo("systemctl enable blog.service") |
|||
|
|||
@task(default=True) |
|||
def build(): |
|||
local("rm -r ./build") |
|||
local("mkdir -p build/{scripts,styles}") |
|||
buildTags() |
|||
buildScss() |
|||
minifyJS() |
|||
buildVenv() |
|||
copyFiles() |
|||
upload() |
|||
serveUp() |
@ -1,6 +0,0 @@ |
|||
default: |
|||
sass blog.scss > styles/blog.min.css |
|||
riot tags/ scripts/tags.js |
|||
|
|||
watch: |
|||
while true; do make ; inotifywait -qre close_write .; done |
@ -0,0 +1,17 @@ |
|||
[uwsgi] |
|||
wsgi_file = /srv/http/riotblog/website.py |
|||
chdir = /srv/http/riotblog/ |
|||
module = website |
|||
callable = app |
|||
virtualenv = /srv/http/riotblog/venv |
|||
uid = http |
|||
gid = http |
|||
|
|||
master = true |
|||
processes = 5 |
|||
|
|||
socket = /tmp/blog.sock |
|||
chown-socket = http:http |
|||
chmod-socket = 660 |
|||
vacuum = true |
|||
die-on-term = true |
@ -1,69 +0,0 @@ |
|||
@mixin responsive-block($bw) { |
|||
@media (max-width: 700px) { |
|||
max-width: $bw+20%; |
|||
} |
|||
@media (max-width: 500px) { |
|||
max-width: $bw+40%; |
|||
} |
|||
} |
|||
|
|||
.post-text { |
|||
text-align: center; |
|||
} |
|||
|
|||
.blog-title { |
|||
@extend .post-text; |
|||
} |
|||
|
|||
.post { |
|||
@extend .post-text; |
|||
} |
|||
|
|||
.post-content { |
|||
max-width: 50%; |
|||
text-align: justify; |
|||
font-size: 1.5em; |
|||
@include responsive-block(50); |
|||
} |
|||
|
|||
.comment-block { |
|||
max-width: 30%; |
|||
@include responsive-block(30); |
|||
} |
|||
|
|||
.comments { |
|||
margin-top: 5%; |
|||
@extend .comment-block; |
|||
} |
|||
|
|||
.comment { |
|||
margin-top: 2%; |
|||
max-width: 40%; |
|||
@include responsive-block(45); |
|||
} |
|||
|
|||
.comments-loader { |
|||
margin-top: 2%; |
|||
} |
|||
|
|||
.comment-body { |
|||
margin-left: 10px; |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.postnav { |
|||
@extend .post-text; |
|||
} |
|||
|
|||
.rounded-button { |
|||
border-radius: 13px; |
|||
} |
|||
|
|||
.maxinput { |
|||
background-color: grey; |
|||
} |
|||
|
|||
.maxwarn { |
|||
margin-top: 15px; |
|||
@extend .comment-block; |
|||
} |
@ -0,0 +1,12 @@ |
|||
[Unit] |
|||
Description=My Blargh |
|||
After=network.target |
|||
|
|||
[Service] |
|||
User=http |
|||
Group=http |
|||
WorkingDirectory=/srv/http/riotblog |
|||
ExecStart=/usr/bin/uwsgi --ini /srv/http/riotblog/blog.ini |
|||
|
|||
[Install] |
|||
WantedBy=multi.user.target |
@ -0,0 +1,17 @@ |
|||
[uwsgi] |
|||
wsgi_file = /srv/http/riotblog/website.py |
|||
chdir = /srv/http/riotblog/ |
|||
module = website |
|||
callable = app |
|||
virtualenv = /srv/http/riotblog/venv |
|||
uid = http |
|||
gid = http |
|||
|
|||
master = true |
|||
processes = 5 |
|||
|
|||
socket = /tmp/blog.sock |
|||
chown-socket = http:http |
|||
chmod-socket = 660 |
|||
vacuum = true |
|||
die-on-term = true |
@ -0,0 +1,12 @@ |
|||
[Unit] |
|||
Description=My Blargh |
|||
After=network.target |
|||
|
|||
[Service] |
|||
User=http |
|||
Group=http |
|||
WorkingDirectory=/srv/http/riotblog |
|||
ExecStart=/usr/bin/uwsgi --ini /srv/http/riotblog/blog.ini |
|||
|
|||
[Install] |
|||
WantedBy=multi.user.target |
@ -0,0 +1 @@ |
|||
riot.mount("post",{creator:"wes",title:"A cool post here"});riot.mount("bbutton"); |
@ -0,0 +1,57 @@ |
|||
.post-text, .blog-title, .post, .postnav { |
|||
text-align: center; } |
|||
|
|||
.post-content { |
|||
max-width: 50%; |
|||
text-align: justify; |
|||
font-size: 1.5em; } |
|||
|
|||
@media (max-width: 700px) { |
|||
.post-content { |
|||
max-width: 70%; } } |
|||
|
|||
@media (max-width: 500px) { |
|||
.post-content { |
|||
max-width: 90%; } } |
|||
|
|||
.comment-block, .comments, .maxwarn { |
|||
max-width: 30%; } |
|||
|
|||
@media (max-width: 700px) { |
|||
.comment-block, .comments, .maxwarn { |
|||
max-width: 50%; } } |
|||
|
|||
@media (max-width: 500px) { |
|||
.comment-block, .comments, .maxwarn { |
|||
max-width: 70%; } } |
|||
|
|||
.comments { |
|||
margin-top: 5%; } |
|||
|
|||
.comment { |
|||
margin-top: 2%; |
|||
max-width: 40%; } |
|||
|
|||
@media (max-width: 700px) { |
|||
.comment { |
|||
max-width: 65%; } } |
|||
|
|||
@media (max-width: 500px) { |
|||
.comment { |
|||
max-width: 85%; } } |
|||
|
|||
.comments-loader { |
|||
margin-top: 2%; } |
|||
|
|||
.comment-body { |
|||
margin-left: 10px; |
|||
margin-right: 10px; } |
|||
|
|||
.rounded-button { |
|||
border-radius: 13px; } |
|||
|
|||
.maxinput { |
|||
background-color: grey; } |
|||
|
|||
.maxwarn { |
|||
margin-top: 15px; } |
@ -0,0 +1,57 @@ |
|||
from __future__ import with_statement |
|||
from fabric.api import * |
|||
from fabric.contrib.console import confirm |
|||
from fabric.contrib.project import rsync_project |
|||
import fabric.operations as op |
|||
|
|||
env.hosts = ["wes@mgoal.ca:444"] |
|||
|
|||
@task |
|||
def buildTags(): |
|||
with lcd("./build"): |
|||
local("riot ../src/tags scripts/tags.min.js") |
|||
|
|||
@task |
|||
def buildScss(): |
|||
with lcd("./build"): |
|||
local("sassc ../src/styles/riotblog.scss > styles/riotblog.min.css") |
|||
|
|||
@task |
|||
def minifyJS(): |
|||
with lcd("./build"): |
|||
local("uglifyjs ../src/scripts/riotblog.js > scripts/riotblog.min.js") |
|||
|
|||
@task |
|||
def buildVenv(): |
|||
local("virtualenv -p $(which python3) ./venv") |
|||
with prefix("source ./venv/bin/activate"): |
|||
local("pip3 install -r requirements.txt") |
|||
local("mv venv ./build/") |
|||
|
|||
@task |
|||
def copyFiles(): |
|||
local("cp ./{blog.ini,blog.service} ./build/") |
|||
local("cp ./src/*py ./build/") |
|||
local("cp ./src/styles/*.css ./build/styles/") |
|||
local("cp -r ./src/templates ./build/templates") |
|||
|
|||
@task |
|||
def upload(): |
|||
run("mkdir -p ~/build") |
|||
rsync_project(local_dir="./build/", remote_dir="~/build/", delete=True, exclude=[".git"]) |
|||
|
|||
@task |
|||
def serveUp(): |
|||
sudo("cp -r /home/wes/build /srv/riotblog") |
|||
|
|||
@task(default=True) |
|||
def build(): |
|||
local("rm -r ./build") |
|||
local("mkdir -p build/{scripts,styles}") |
|||
buildTags() |
|||
buildScss() |
|||
minifyJS() |
|||
buildVenv() |
|||
copyFiles() |
|||
upload() |
|||
serveUp() |
@ -0,0 +1,10 @@ |
|||
appdirs==1.4.0 |
|||
click==6.7 |
|||
Flask==0.12 |
|||
itsdangerous==0.24 |
|||
Jinja2==2.9.5 |
|||
MarkupSafe==0.23 |
|||
packaging==16.8 |
|||
pyparsing==2.1.10 |
|||
six==1.10.0 |
|||
Werkzeug==0.11.15 |
@ -1,33 +0,0 @@ |
|||
Name: riotblog |
|||
Version: 0.1 |
|||
Synopsis: Project Synopsis Here |
|||
Description: Project Description Here |
|||
License: AllRightsReserved |
|||
Author: Author |
|||
Maintainer: maintainer@example.com |
|||
Stability: Experimental |
|||
Category: Web |
|||
Build-type: Simple |
|||
Cabal-version: >=1.2 |
|||
|
|||
Executable riotblog |
|||
hs-source-dirs: src |
|||
main-is: Main.hs |
|||
|
|||
Build-depends: |
|||
base >= 4 && < 5, |
|||
bytestring >= 0.9.1 && < 0.11, |
|||
monad-control >= 1.0 && < 1.1, |
|||
mtl >= 2 && < 3, |
|||
snap-core >= 1.0 && < 1.1, |
|||
snap-server >= 1.0 && < 1.1, |
|||
text -any, |
|||
unordered-containers -any, |
|||
data-default -any, |
|||
shakespeare >=2.0.11.2 |
|||
|
|||
if impl(ghc >= 6.12.0) |
|||
ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2 |
|||
-fno-warn-unused-do-bind |
|||
else |
|||
ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2 |
@ -1,28 +0,0 @@ |
|||
{-# LANGUAGE OverloadedStrings #-} |
|||
module Main where |
|||
|
|||
import Control.Applicative |
|||
import Snap.Core |
|||
import Snap.Util.FileServe |
|||
import Snap.Http.Server |
|||
import qualified Data.Text.IO as TIO (readFile) |
|||
import Data.Text |
|||
import qualified Data.HashMap.Strict as HM |
|||
import Text.Hamlet |
|||
|
|||
main :: IO () |
|||
main = quickHttpServe site |
|||
|
|||
site :: Snap () |
|||
site = |
|||
ifTop (writeBS "Hello, world! Hey Hey Hey") <|> |
|||
route [ ("foo", writeBS "bar") |
|||
, ("echo/:echoparam", echoHandler) |
|||
] <|> |
|||
dir "static" (serveDirectory ".") |
|||
|
|||
echoHandler :: Snap () |
|||
echoHandler = do |
|||
param <- getParam "echoparam" |
|||
maybe (writeBS "must specify echo/param in URL") |
|||
writeBS param |
@ -0,0 +1,18 @@ |
|||
from json import dumps |
|||
|
|||
def comment(author, title, text): |
|||
return { |
|||
"title" : title, |
|||
"author" : author, |
|||
"text" : text |
|||
} |
|||
|
|||
|
|||
testcomments = { |
|||
1 : dumps( |
|||
[ |
|||
comment("Anonymous Coward", "Some comment?", "super duper awesome comment here"), |
|||
comment("Anonymous Coward 3", "Something? IDEK", "super duper worse comment here"), |
|||
comment("Anonymous Coward 2", "Some other comment?", "super duper dang terrible comment here") |
|||
]) |
|||
} |
@ -0,0 +1 @@ |
|||
from functools import total_ordering |
@ -0,0 +1,154 @@ |
|||
riot.tag2('bbutton', '<button class="btn rounded-button"> </button>', '', '', function(opts) { |
|||
}); |
|||
|
|||
riot.tag2('comment', '<div class="comment centered"> <div class="card"> <div class="card-header"> <h4 class="card-title">{title} by {author}</h4> </div> <div class="card-body comment-body"> {R.join(⁗ ⁗)(R.repeat(text, 20))} </div> </div> </div>', '', '', function(opts) { |
|||
}); |
|||
|
|||
riot.tag2('comments', '<div if="{loading}" class="loading comments-loader"> </div> <comment if="{!loading}" each="{comments}" data="{this}"></comment> <textarea onfocus="{clearplaceholder}" onblur="{checkplaceholder}" oninput="{echo}" __disabled="{disabled}" class="{⁗form-input comments centered ⁗ + maxed}" name="textarea" rows="10" cols="50" maxlength="{maxlength}"> {placeholder} </textarea> <div if="{warn}" class="toast toast-danger maxwarn centered"> <button onclick="{closewarning}" class="btn btn-clear float-right"> </button> You\'ve reached the max comment size </div>', '', '', function(opts) { |
|||
|
|||
comments = []; |
|||
maxlength = 700; |
|||
|
|||
placeholder = "Comment here!"; |
|||
focused = false; |
|||
maxed = false; |
|||
warn = false; |
|||
disabled = ""; |
|||
loading = true; |
|||
|
|||
this.clearplaceholder = function() { |
|||
if (!this.focused) { |
|||
this.update({ |
|||
"placeholder" : "", |
|||
"focused" : true |
|||
}) |
|||
} |
|||
}.bind(this) |
|||
|
|||
this.checkplaceholder = function() { |
|||
if (this.textarea.value.trim().length == 0) { |
|||
this.update({ |
|||
"placeholder" : "Comment here!", |
|||
"focused" : false |
|||
}); |
|||
} |
|||
}.bind(this) |
|||
|
|||
this.closewarning = function() { |
|||
this.update({"warn" : false}); |
|||
}.bind(this) |
|||
|
|||
this.echo = function(ev) { |
|||
if (this.textarea.value.length >= maxlength) { |
|||
this.update({ |
|||
"maxed" : "maxinput", |
|||
"warn" : true |
|||
}); |
|||
} |
|||
else { |
|||
this.update({ |
|||
"maxed" : false, |
|||
"warn" : false |
|||
}); |
|||
window.setTimeout(this.closewarning, 5000); |
|||
} |
|||
}.bind(this) |
|||
|
|||
var self = this; |
|||
|
|||
this.getComments = function(pid) { |
|||
fetch("/comments/"+pid) |
|||
.then( |
|||
function(resp) { |
|||
return resp.text(); |
|||
}) |
|||
.then( |
|||
function(body) { |
|||
self.update( |
|||
{ |
|||
"comments" : JSON.parse(body), |
|||
"loading" : false |
|||
}); |
|||
}); |
|||
}.bind(this) |
|||
|
|||
this.on("mount", |
|||
function() { |
|||
this.getComments(self.opts.pid); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
riot.tag2('post', '<div class="postnav centered"> <button class="{⁗btn btn-primary ⁗ + (this.pid <= 1 ? ⁗disabled⁗ : ⁗ ⁗) + this.prevloading}" onclick="{prev}">Previous Post</button> <button class="{⁗btn btn-primary ⁗ + (this.nomore ? ⁗disabled⁗ : ⁗ ⁗) + this.nextloading}" onclick="{next}">Next Post</button> </div> <h4 class="post centered" if="{nomore}"> No More Posts! </h4> <div if="{!(loading || nomore)}" class="post centered"> <h4>{opts.title}</h4> <h5>By {opts.creator}</h5> <p class="post-content centered text-break">{content}</p> <div class="divider"></div> <comments pid="{pid}"> </comments> </div>', '', '', function(opts) { |
|||
var self = this; |
|||
|
|||
this.loading = false; |
|||
this.prevloading = ""; |
|||
this.nextloading = ""; |
|||
|
|||
this.nomore = false |
|||
this.pid = 1; |
|||
content = ""; |
|||
|
|||
this.prev = function() { |
|||
if (self.prevloading || self.nextloading) { |
|||
return; |
|||
} |
|||
self.prevloading = " loading"; |
|||
if (self.nomore) { |
|||
self.nomore = false; |
|||
} |
|||
if (self.pid > 1) { |
|||
self.pid--; |
|||
self.setPost(self.pid); |
|||
self.update(); |
|||
} |
|||
}.bind(this) |
|||
|
|||
this.next = function() { |
|||
if (self.nextloading || self.prevloading) { |
|||
return; |
|||
} |
|||
self.nextloading = " loading"; |
|||
console.log(self.pid); |
|||
console.log(self.nomore); |
|||
if (!self.nomore) { |
|||
self.pid++; |
|||
self.setPost(self.pid); |
|||
self.update(); |
|||
} |
|||
}.bind(this) |
|||
|
|||
this.setPost = function(pid) { |
|||
self.update(); |
|||
self.loading = true; |
|||
fetch("/switchpost/"+pid) |
|||
.then( |
|||
function(resp) { |
|||
return resp.text(); |
|||
}) |
|||
.then( |
|||
function(body) { |
|||
if (body === "false") { |
|||
self.nomore = true; |
|||
route("/"); |
|||
self.update() |
|||
} |
|||
else { |
|||
self.content = R.join(" ")(R.repeat(body, 20)); |
|||
route("/"+pid); |
|||
} |
|||
|
|||
self.loading = false; |
|||
self.prevloading = ""; |
|||
self.nextloading = ""; |
|||
self.update(); |
|||
}); |
|||
} |
|||
|
|||
this.on("mount", function() { this.setPost(self.pid) }); |
|||
|
|||
}); |
|||
|
|||
riot.tag2('posts', '<yield></yield>', '', '', function(opts) { |
|||
}); |
File diff suppressed because one or more lines are too long
@ -0,0 +1,42 @@ |
|||
{% block head %} |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
<header class="text-center nav navbar"> |
|||
<section class="centered page-top navbar-section"> |
|||
<h1 class="blog-title">blog</h1> |
|||
</section> |
|||
</header> |
|||
{% endblock %} |
|||
|
|||
<html> |
|||
<body> |
|||
{% block content %} |
|||
|
|||
<posts> |
|||
<post></post> |
|||
</posts> |
|||
<editor> |
|||
</editor> |
|||
|
|||
{% endblock %} |
|||
|
|||
<footer class="footer"> |
|||
</footer> |
|||
|
|||
{% block styles %} |
|||
|
|||
<link rel="stylesheet" href="/styles/spectre.min.css"> |
|||
<link rel="stylesheet" href="/styles/riotblog.min.css"> |
|||
|
|||
{% endblock %} |
|||
|
|||
{% block scripts %} |
|||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script> |
|||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script> |
|||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/riot/2.6.7/riot+compiler.min.js"></script> |
|||
<script type="text/javascript" src="//cdn.jsdelivr.net/riot-route/3.0.2/route.min.js"></script> |
|||
<script type="text/javascript" src="/scripts/tags.min.js"></script> |
|||
<script type="text/javascript" src="/scripts/riotblog.min.js"></script> |
|||
{% endblock %} |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,88 @@ |
|||
#! /usr/bin/python3 |
|||
from functools import partial |
|||
|
|||
from flask import abort, Flask, render_template, flash, request, send_from_directory |
|||
from flask_appconfig import AppConfig |
|||
|
|||
from time import sleep |
|||
|
|||
from urllib.parse import unquote |
|||
from urllib.parse import quote, unquote |
|||
from json import dumps, loads |
|||
|
|||
from comment import testcomments |
|||
|
|||
from werkzeug.contrib.cache import MemcachedCache |
|||
cache = MemcachedCache(['127.0.0.1:11211']) |
|||
|
|||
import os |
|||
|
|||
def cacheit(key, thunk): |
|||
""" |
|||
Tries to find a cached version of ``key'' |
|||
If there is no cached version then it will |
|||
evaluate thunk (which must be a generator) |
|||
and cache that, then return the result |
|||
""" |
|||
cached = cache.get(quote(key)) |
|||
if cached is None: |
|||
result = list(thunk()) |
|||
cache.set(quote(key), result) |
|||
return result |
|||
return cached |
|||
|
|||
def NeverWhere(configfile=None): |
|||
|
|||
app = Flask(__name__) |
|||
app.config["TEMPLATES_AUTO_RELOAD"] = True |
|||
#def favicon(): |
|||
#return send_from_directory("/srv/http/goal/favicon.ico", |
|||
#'favicon.ico', mimetype='image/vnd.microsoft.icon') |
|||
|
|||
@app.route("/", methods=("GET", "POST")) |
|||
def index(): |
|||
print("matched index") |
|||
return render_template("index.html") |
|||
|
|||
@app.route("/scripts/<filename>", methods=("GET", "POST")) |
|||
def send_script(filename): |
|||
print("matched scripts route") |
|||
return send_from_directory("./scripts", filename) |
|||
|
|||
@app.route("/styles/<filename>", methods=("GET", "POST")) |
|||
def send_style(filename): |
|||
return send_from_directory("./styles", filename) |
|||
|
|||
@app.route("/switchpost/<pid>") |
|||
def switchPost(pid): |
|||
posts = { |
|||
"1" : "Post one is changed! ", |
|||
"2" : "Post two here and it's changed! " |
|||
} |
|||
return posts.get(pid, "false") |
|||
|
|||
|
|||
@app.route("/comments/<pid>") |
|||
def comments(pid): |
|||
sleep(5); |
|||
try: |
|||
return testcomments.get(int(pid), dumps([])) |
|||
except ValueError as e: |
|||
print(e) |
|||
return dumps([]) |
|||
|
|||
@app.route("/insert/<pid>") |
|||
def insert(pid): |
|||
print("inserting new post") |
|||
|
|||
@app.route("/<path:path>") |
|||
def page_not_found(path): |
|||
return "Custom failure message" |
|||
|
|||
app.run(debug=True) |
|||
return app |
|||
|
|||
app = NeverWhere() |
|||
|
|||
if __name__ == "__main__": |
|||
NeverWhere("./appconfig").run(host="localhost", port=8001, debug=True) |
@ -1,7 +0,0 @@ |
|||
flags: {} |
|||
extra-package-dbs: [] |
|||
packages: |
|||
- '.' |
|||
extra-deps: |
|||
- ginger-0.3.7.2 |
|||
resolver: lts-7.12 |
Loading…
Reference in new issue