initial commit

This commit is contained in:
Rupus Reinefjord 2018-01-11 23:40:31 +01:00
commit a528892ac8
46 changed files with 2152 additions and 0 deletions

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
__pycache__/
venv/
*.db
*.sqlite
instance/*
static/uploads/*
static/gen/*
static/.webassets-cache*

373
LICENSE Normal file
View file

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

21
Pipfile Normal file
View file

@ -0,0 +1,21 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask = "*"
peewee = "*"
flask-uploads = "*"
flask-login = "*"
flask-wtf = "*"
markdown = "*"
nodeenv = "*"
webassets = {git = "https://github.com/miracle2k/webassets.git", ref = "master"}
flask-assets = "*"
[dev-packages]

128
Pipfile.lock generated Normal file
View file

@ -0,0 +1,128 @@
{
"_meta": {
"hash": {
"sha256": "2de5ec90d8ee6f01b6383c466d82f6361f2326579862ae1b3c4014efd9e52fc8"
},
"host-environment-markers": {
"implementation_name": "cpython",
"implementation_version": "3.6.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "4.14.12-1-ARCH",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT Fri Jan 5 18:19:34 UTC 2018",
"python_full_version": "3.6.4",
"python_version": "3.6",
"sys_platform": "linux"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
],
"version": "==6.7"
},
"flask": {
"hashes": [
"sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856",
"sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1"
],
"version": "==0.12.2"
},
"flask-assets": {
"hashes": [
"sha256:6031527b89fb3509d1581d932affa5a79dd348cfffb58d0aef99a43461d47847"
],
"version": "==0.12"
},
"flask-login": {
"hashes": [
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
],
"version": "==0.4.1"
},
"flask-uploads": {
"hashes": [
"sha256:53ecbd6033667d50ae02b63adebbaa33c7fc56c09e5293025810cf9d841ecb02"
],
"version": "==0.2.1"
},
"flask-wtf": {
"hashes": [
"sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac",
"sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36"
],
"version": "==0.14.2"
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
],
"version": "==0.24"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markdown": {
"hashes": [
"sha256:9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f",
"sha256:a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81"
],
"version": "==2.6.11"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"nodeenv": {
"hashes": [
"sha256:98835dab727f94a713eacc7234e3db6777a55cafb60f391485011899e5c818df"
],
"version": "==1.2.0"
},
"peewee": {
"hashes": [
"sha256:2342067f48a779e35956a44cd547df883dda35153daa9fe994d970585aaec281"
],
"version": "==2.10.2"
},
"webassets": {
"hashes": [
"sha256:e7d9c8887343123fd5b32309b33167428cb1318cdda97ece12d0907fd69d38db"
],
"version": "==0.12.1"
},
"werkzeug": {
"hashes": [
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b",
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c"
],
"version": "==0.14.1"
},
"wtforms": {
"hashes": [
"sha256:ffdf10bd1fa565b8233380cb77a304cd36fd55c73023e91d4b803c96bc11d46f"
],
"version": "==2.1"
}
},
"develop": {}
}

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# Website
This is my personal website. Made with Flask and other stuff I like (and some
I don't like, unfortunatly).

295
app.py Normal file
View file

@ -0,0 +1,295 @@
import datetime
from urllib.parse import urlparse, urljoin
import flask
import flask_assets
import flask_login
import flask_uploads
import flask_wtf
import flask_wtf.file
import markdown
import peewee as pw
import werkzeug
import wtforms
from playhouse import flask_utils as pw_util
app = flask.Flask(__name__, instance_relative_config=True)
app.config.from_object('config')
app.config.from_pyfile('config.py')
db_wrapper = pw_util.FlaskDB(app)
bundles = {
'common_css': flask_assets.Bundle(
'css/lib/normalize.css',
'css/fonts.css',
'css/style.css',
output='gen/common.css',
filters=['autoprefixer6', 'cleancss'],
),
}
assets = flask_assets.Environment(app)
assets.register(bundles)
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
login_manager.login_view = '.login'
login_manager.login_message_category = 'info'
photo_set = flask_uploads.UploadSet('photos', flask_uploads.IMAGES)
flask_uploads.configure_uploads(app, photo_set)
def photo_resize_url(photo, size):
if app.debug:
return photo_set.url(photo.filename)
return f"{photo_set.config.base_url}img{size}/{photo.filename}"
def image_resize_url(image, size):
if app.debug:
return f'static/images/{image}'
return f"/static/images/img{size}/{image}"
app.jinja_env.globals['photo_resize_url'] = photo_resize_url
app.jinja_env.globals['image_resize_url'] = image_resize_url
class Photo(db_wrapper.Model):
filename = pw.CharField()
title = pw.CharField(null=True)
description = pw.TextField(null=True)
timestamp = pw.DateTimeField(default=datetime.datetime.now)
@property
def html_description(self):
return markdown.markdown(self.description, output_format='html5')
class User(flask_login.UserMixin):
pass
class LoginForm(flask_wtf.FlaskForm):
password = wtforms.fields.PasswordField('Password')
class UploadPhotoForm(flask_wtf.FlaskForm):
photo = wtforms.fields.FileField('Photo', validators=[
flask_wtf.file.FileRequired(),
flask_wtf.file.FileAllowed(photo_set, 'Images only!')
])
title = wtforms.fields.StringField('Title')
description = wtforms.fields.TextAreaField('Description')
class EditPhotoForm(flask_wtf.FlaskForm):
photo = wtforms.fields.FileField('Photo', validators=[
flask_wtf.file.FileAllowed(photo_set, 'Images only!')
])
title = wtforms.fields.StringField('Title')
description = wtforms.fields.TextAreaField('Description')
class ConfirmForm(flask_wtf.FlaskForm):
pass
def flash_errors(form):
"""Flash all errors in a form."""
for field in form:
for error in field.errors:
flask.flash(("Error in {} field: {}"
.format(field.label.text, error)),
'error'
)
@login_manager.user_loader
def load_user(user_id):
if user_id != app.config['LOGIN']:
return
user = User()
user.id = user_id
return user
def is_safe_url(target):
ref_url = urlparse(flask.request.host_url)
test_url = urlparse(urljoin(flask.request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
@app.errorhandler(404)
@app.errorhandler(500)
def not_found(e):
return flask.render_template('error/{}.html'.format(e.code)), e.code
@app.route('/')
def index():
return flask.render_template('index.html')
@app.route('/photo/', defaults={'page': 1})
@app.route('/photo/page/<int:page>')
def photography(page):
photos = Photo.select().order_by(Photo.timestamp.desc()).paginate(page, 9)
return flask.render_template('photography.html', photos=photos)
@app.route('/photo/<int:photo_id>')
def view_photo(photo_id):
query = Photo.select().order_by(Photo.timestamp.desc())
photo = pw_util.get_object_or_404(query, Photo.id == photo_id)
try:
prev = query.where(Photo.timestamp < photo.timestamp).get()
except Photo.DoesNotExist:
prev = None
try:
next = (query.order_by(Photo.timestamp.asc())
.where(Photo.timestamp > photo.timestamp).get())
except Photo.DoesNotExist:
next = None
return flask.render_template('view_photo.html',
photo=photo,
prev=prev,
next=next)
@app.route('/me')
def me():
return flask.render_template('me.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if flask_login.current_user.is_authenticated:
return flask.redirect(flask.url_for('.admin'))
if form.validate_on_submit():
if form.password.data == app.config['LOGIN']:
user = User()
user.id = app.config['LOGIN']
flask_login.login_user(user)
next = flask.request.args.get('next')
if not is_safe_url(next):
return flask.abort(400)
flask.flash('Login successful!', 'success')
return flask.redirect(next or flask.url_for('.admin'))
else:
flask.flash('Wrong password.', 'error')
return flask.render_template('login.html', form=form)
@app.route('/logout')
def logout():
if flask_login.current_user.is_authenticated:
flask_login.logout_user()
else:
flask.flash('You have to be logged in to log out! ಠ_ಠ', 'error')
return flask.redirect(flask.url_for('.index'))
@app.route('/admin/')
@flask_login.login_required
def admin():
return flask.render_template('admin/admin.html')
@app.route('/admin/photos/')
@flask_login.login_required
def admin_photos():
photos = Photo.select().order_by(Photo.timestamp.desc())
return flask.render_template('admin/photos.html', photos=photos)
@app.route('/admin/photos/<int:photo_id>', methods=['GET', 'POST'])
@flask_login.login_required
def admin_edit_photo(photo_id):
photo = pw_util.get_object_or_404(Photo, Photo.id == photo_id)
form = EditPhotoForm(
werkzeug.datastructures.CombinedMultiDict((flask.request.files,
flask.request.form)),
obj=photo
)
if form.validate_on_submit():
if form.photo.data:
photo.filename = photo_set.save(form.photo.data)
photo.title = form.title.data
photo.description = form.description.data
photo.save()
flask.flash('Photo updated!', 'success')
else:
flash_errors(form)
return flask.render_template('admin/photo.html', form=form, photo=photo)
@app.route('/admin/photos/new', methods=['GET', 'POST'])
@flask_login.login_required
def admin_new_photo():
form = UploadPhotoForm()
if form.validate_on_submit():
filename = photo_set.save(form.photo.data)
photo = Photo.create(filename=filename,
title=form.title.data or None,
description=form.description.data or None
)
flask.flash('Photo uploaded successfully!', 'success')
return flask.redirect(flask.url_for('admin_edit_photo',
photo_id=photo.id))
else:
flash_errors(form)
return flask.render_template('admin/new_photo.html', form=form)
@app.route('/admin/photos/remove/<int:photo_id>', methods=['GET', 'POST'])
@flask_login.login_required
def admin_remove_photo(photo_id):
photo = pw_util.get_object_or_404(Photo, Photo.id == photo_id)
form = ConfirmForm()
if form.validate_on_submit():
photo.delete_instance()
flask.flash('Photo removed.', 'success')
return flask.redirect(flask.url_for('admin_photos'))
else:
flash_errors(form)
return flask.render_template('admin/remove_photo.html',
form=form,
photo=photo)

11
config.py Normal file
View file

@ -0,0 +1,11 @@
SECRET_KEY = 'secret!'
TEMPLATES_AUTO_RELOAD = True
LOGIN = 'supersecretpassword'
DATABASE = 'sqlite:///instance/db.sqlite'
UPLOADS_DEFAULT_DEST = 'static/uploads'
UPLOADS_DEFAULT_URL = 'http://localhost:5000/static/uploads/'
AUTOPREFIXER_BIN = 'postcss'

3
npm-requirements.txt Normal file
View file

@ -0,0 +1,3 @@
postcss-cli@4.1.1
autoprefixer@7.2.4
clean-css-cli@4.1.10

66
static/css/fonts.css Normal file
View file

@ -0,0 +1,66 @@
@font-face {
font-family: "Crimson Text";
src: url('/static/fonts/crimsontext/Crimson-Roman.woff2') format('woff2'),
url('/static/fonts/crimsontext/Crimson-Roman.woff') format('woff');
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: "Crimson Text";
src: url('/static/fonts/crimsontext/Crimson-Italic.woff2') format('woff2'),
url('/static/fonts/crimsontext/Crimson-Italic.woff') format('woff');
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Crimson Text";
src: url('/static/fonts/crimsontext/Crimson-SemiBold.woff2') format('woff2'),
url('/static/fonts/crimsontext/Crimson-SemiBold.woff') format('woff');
font-weight: 600;
font-display: swap;
}
@font-face {
font-family: "Crimson Text";
src: url('/static/fonts/crimsontext/Crimson-SemiBoldItalic.woff2') format('woff2'),
url('/static/fonts/crimsontext/Crimson-SemiBoldItalic.woff') format('woff');
font-weight: 600;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Crimson Text";
src: url('/static/fonts/crimsontext/Crimson-Bold.woff2') format('woff2'),
url('/static/fonts/crimsontext/Crimson-Bold.woff') format('woff');
font-weight: 700;
font-display: swap;
}
@font-face {
font-family: "Crimson Text";
src: url('/static/fonts/crimsontext/Crimson-BoldItalic.woff2') format('woff2'),
url('/static/fonts/crimsontext/Crimson-BoldItalic.woff') format('woff');
font-weight: 700;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Fira Mono";
src: url('/static/fonts/firamono/FiraMono-Regular.woff2') format('woff2'),
url('/static/fonts/firamono/FiraMono-Regular.woff') format('woff');
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: "Fira Mono";
src: url('/static/fonts/firamono/FiraMono-Bold.woff2') format('woff2'),
url('/static/fonts/firamono/FiraMono-Bold.woff') format('woff');
font-weight: 600;
font-display: swap;
}

447
static/css/lib/normalize.css vendored Normal file
View file

@ -0,0 +1,447 @@
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in
* IE on Windows Phone and in iOS.
*/
html {
line-height: 1.15; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/**
* Add the correct display in IE 9-.
*/
article,
aside,
footer,
header,
nav,
section {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in IE.
*/
figcaption,
figure,
main { /* 1 */
display: block;
}
/**
* Add the correct margin in IE 8.
*/
figure {
margin: 1em 40px;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/
a {
background-color: transparent; /* 1 */
-webkit-text-decoration-skip: objects; /* 2 */
}
/**
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Add the correct background and color in IE 9-.
*/
mark {
background-color: #ff0;
color: #000;
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
audio,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers (opinionated).
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: sans-serif; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type="button"], /* 1 */
[type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* 1. Add the correct display in IE 9-.
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Remove the default vertical scrollbar in IE.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
*/
details, /* 1 */
menu {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Scripting
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
canvas {
display: inline-block;
}
/**
* Add the correct display in IE.
*/
template {
display: none;
}
/* Hidden
========================================================================== */
/**
* Add the correct display in IE 10-.
*/
[hidden] {
display: none;
}

178
static/css/style.css Normal file
View file

@ -0,0 +1,178 @@
* {
box-sizing: border-box;
overflow-wrap: break-word;
word-wrap: break-word; /* legacy */
word-break: break-word; /* for WebKit/Chrome */
}
.wrapper > * {
/* Fixes overflowing content (anything other than "overflow: visible")
* in grid. Won't overflow without this. */
min-width: 0;
max-width: 100%;
}
img {
max-width: 100%;
vertical-align: middle;
}
body {
font-size: 18px;
line-height: 1.6;
font-family: "Crimson Text", serif;
text-decoration-skip: ink;
display: flex;
flex-direction: column;
align-items: center;
}
.wrapper {
display: grid;
grid-gap: 1rem;
grid-template-columns: minmax(0, 40rem);
padding: 1rem 1rem 5rem 1rem;
}
code, .email {
font-family: "Fira Mono", monospace;
font-size: 15px
}
header h1 {
margin: 0;
}
header a {
text-decoration: none;
}
nav ul {
list-style: none;
padding: 0;
margin: 0;
}
.sitenav {
padding-left: 1rem;
}
.nav-item {
color: #000;
}
.nav-item:hover {
color: #666;
}
.active {
color: #666;
text-decoration: none;
}
.photo-grid {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, 200px);
justify-content: center;
align-items: center;
justify-items: center;
}
.photo-grid img {
max-height: 220px;
}
.view-image {
display: flex;
flex-direction: column;
}
.view-image img {
max-height: 700px;
align-self: center;
}
.view-image nav {
align-self: center;
margin-top: 2rem;
max-width: 24rem;
width: 100%;
display: flex;
}
.view-image form {
margin-top: 2rem;
border-top: 1px solid #ddd;
padding-top: 1rem;
}
.prev {
margin-left: auto;
}
.field {
margin: 1rem 0;
display: block;
}
.field > label:first-child {
display: block;
}
textarea {
width: 30rem;
height: 15rem;
max-width: 100%;
}
.flashes {
justify-self: center;
text-align: center;
list-style: none;
margin: .5rem 1rem;
padding: 0;
}
.flash {
min-width: 15em;
padding: .5rem 1rem;
margin: .5rem auto;
border-radius: 7px;
}
.success {
background-color: #c9f7dd;
border: 1px solid #62d895;
color: #004d21;
}
.info {
background-color: #cbdef5;
border: 1px solid #6797d0;
color: #042246;
}
.error {
background-color: #ffd9d0;
border: 1px solid #ff9073;
color: #6c1600;
}
@media screen and (min-width: 60rem) {
.wrapper {
grid-template-columns: 14rem 40rem;
}
header {
grid-column: 1 / -1;
}
nav {
grid-column: 1;
}
main {
grid-column: 2;
}
.photo-grid {
justify-content: left;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,93 @@
Copyright (c) 2012-2014, The Crimson Project Developers.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,94 @@
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
with Reserved Font Name < Fira >,
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

8
static/js/emaildecode.js Normal file
View file

@ -0,0 +1,8 @@
var emails = document.getElementsByClassName("email");
for (var i = 0; i < emails.length; i++) {
var b64 = emails[i].innerHTML;
var decoded = window.atob(b64);
emails[i].innerHTML = decoded;
emails[i].href = "mailto:" + decoded;
}

View file

@ -0,0 +1,112 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFfJf7MBEACprYkxM1P4iYpWWDRkLsfS5QJCzh7pBGzfc82izOdWBmWwmKYI
o0MPOEtWaRbS6TNd7ezJcM3AxrX+cYErRE9S9C4Vk9vOUCflkDv2u/0vAEdRh9It
zVlzio/1fOaW4POrN/gp2JJl9YSzh3XvDiftmd+K2JzqVQTj2MXedLjeFtEccXNQ
q0rYsAfKE0nU8/B3JLTp2LmB2VvDne50IznrtnEdC75mD3Thlc1YZQPF+G1bDU7h
679khPeKDEE1vK+GYBSA67ADkniJIyOEzYcJfobjpf0YFIKzaNwAmez3RCntY2hM
XBQtkxjRPFS51322T/eAY/+MBxfHATq/iqTETWIOvT5M7WMQwIahI+YauLa5i3++
1dI7OuJ6wwmBzBtRt5XFeHRXo1FkylWB5mXh76zPGjqiI/soMcnWubORhHBXXXb5
XMPjiAUBfU5iG2m/Qn99//zmHJqXflVzobH15TPyLHWWhm3K6s9fQTFPTmrI9Y3f
HF792ingAAxevmBpQDwZLEST2YGSXsD8uOZyUlTDMikHyoUTQE4xLpyMvGsrGuvx
owGL2RuGxzUjDGb5Grp6PvApVKa9NqSga4PHVxINZ0+wff5PbVb70/qCq4fACThk
Sz7n6GcGwR7OaT31VdwHZ/qbUzG3+DUMVWdgEBwmrnygTAbOtN+rFzaq5wARAQAB
tCJSdXB1cyBSZWluZWZqb3JkIDxydXB1c0BrbHRyc3Quc2U+iQI3BBMBCAAhBQJX
yX+zAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEOkYmZvmaIvS5n4P/3YM
1NTfKucaY8vozXMPB1GWIWAhfWpWqjFR6W5+fWPRxj26kfbJ201l0l+R9xQWwfgm
d9+1J7K0ok/sK2vXkB/xyBWCLYDlDa2bOQc1XEBdk4C5gVawJkVh98J/k/LYgMZr
ahqS7A4ApopFRS12YD61Rm0ls+vJDPVDMOvhiGAexYsJ18aqpobGekL92m1AlRFS
6gGp79KKLTdf1qArzuU1m7w8ukv+j7DrZ5L56WaGXGU5X+pyKXLqT+oe/7IpBCsv
tRhwxTtabNTr4Unxtn619Zq7CzUMkGVhE65NEJWQSKb8F80CDniXYg77StzIcHnZ
g+cZ0KB+0TfyZcC7vIGJJiIC/ld46+8K7DT/Cz0A3TXOZNObNW98Drud8yC1TjYG
jHLY83MkM8No11aczfJ9ZPZGWDO9BOY24mr8zidZWeS/lqM9FNIJCah+VUlSsKQn
mOoau4T+OVhGnAlzyB7MyYK54VpO6Y07OhLujD39JKcfyRFMy5cpSlF0SQjy8xyD
R1rjf9jgzvnR605K/NM3J/HV/2H25ZXob2h2+/iiUCthOuHbYnF0j3PQN8BKRFlr
dHAzV5oMKCLz666j6Cyp1Fy7Dn/VLxGxM91v9JtAid6SWfkS/N3mkHl8ooVJVg1U
995EqywlK61zsYpEbJfQl6DSkaoglUxv7CVTA15liQJRBBMBCAA7AhsDBQsJCAcC
BhUICQoLAgQWAgMBAh4BAheAFiEE4nXD8Idl68PJBmb06RiZm+Zoi9IFAlnE5xcC
GQEACgkQ6RiZm+Zoi9I1jA/+IvZRLjk/4/c8W2MZnwhljE3acHWI9hHWpf7lX+9D
logKOTnPZ+NhjNF7aHd+JkwUFLKB+OorIyoSZQIdx11VnidlQjp6niCbeUr/T5eu
s99WdDxRNnSkNvTtnzc33JvFafN4D0q44RvyAk3UUWtWnDB9HwL58LQKr6NuIZxy
MXHHZJ3ugh5i6j6KPXFGKK57V8kX0rN3GZ522bDE7o23ziB9GB5dqeAOYW1d0xed
yIMi8P/cqVphIzXM14zCv1dJd5NjDZpTnJ06+E6oQT3HWmkUF3O+6aboW0XPlpRW
aK4P3li1Y1E00M2m7JAAbNC4sh7XZ0VzNll64siJeOSw1NoKvLYEoHL5Oeq6WBP7
ZYViWTkkL3JYiWZGFGQHdASG6i5X0QKPCNo51beEQOXC71iK6FQKXUgqtcMKRW4T
ogptY4//3PAq4WjXnEhUdkK40wLnrC/hEMQvNOzbZzQAgQV2hRmRap639WMdjzYE
1KuctBEX9doBThxgN/A3bQO0Uzcs0Bv493yubDBqA1lNHGnJppDRlV2Dj2yc+x7w
YMsLIQOZJpdT8FquY+vlSwxZpbHWOGN6t/e7vzGqQ5n4lfViOonvZdRShVHInjdA
6ScugVq0ftogTxzg50CHNfA9WR7j6qEqA0IPla0ylpQDfWCI2QuY/N6W2NLdH9u9
eZi0LVJ1cHVzIFJlaW5lZmpvcmQgPHdlYm1hc3RlckB0ZWtub2xvZ2tvcmVuLnNl
PokCTQQTAQgAOBYhBOJ1w/CHZevDyQZm9OkYmZvmaIvSBQJZxOMoAhsDBQsJCAcC
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
=g3+W
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,12 @@
{% extends 'base.html' %}
{% set navbar = [
(url_for('admin_new_photo'), 'new_photo', 'Upload new photo'),
(url_for('admin_photos'), 'photos', 'View photos'),
(url_for('logout'), 'logout', 'Logout')
] %}
{% block header %}
<h1><a href="{{ url_for('admin') }}" class="nav-item">administration</a></h1>
{% endblock %}

View file

@ -0,0 +1,26 @@
{% extends 'admin/admin.html' %}
{% set title = 'New photo' %}
{% set active_page = 'new_photo' %}
{% block body %}
<main>
<form method="POST" enctype="multipart/form-data">
{{ form.csrf_token }}
<div class="field">
{{ form.photo.label }}
{{ form.photo }}
</div>
<div class="field">
{{ form.title.label }}
{{ form.title }}
</div>
<div class="field">
{{ form.description.label }}
{{ form.description }}
</div>
<button type="submit">Submit</button>
</form>
</main>
{% endblock %}

View file

@ -0,0 +1,34 @@
{% extends 'admin/admin.html' %}
{% set title = photo.title or 'Unnamed photo' %}
{% set active_page = 'photos' %}
{% block body %}
<main class="view-image">
<img src="{{ photo_resize_url(photo, 800) }}">
<h2>{{ photo.title or 'Unnamed photo' }}</h2>
{{ photo.description or 'No description'}}
<form method="POST" enctype="multipart/form-data">
{{ form.csrf_token }}
<div class="field">
{{ form.photo.label }}
{{ form.photo }}
</div>
<div class="field">
{{ form.title.label }}
{{ form.title }}
</div>
<div class="field">
{{ form.description.label }}
{{ form.description }}
</div>
<button type="submit">Submit</button>
</form>
<div class="field">
<a href="{{ url_for('admin_remove_photo', photo_id=photo.id) }}">Remove</a>
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends 'admin/admin.html' %}
{% set title = 'Photos' %}
{% set active_page = 'photos' %}
{% block body %}
<main>
<h2>Photos</h2>
{% if photos %}
<ul>
{% for photo in photos %}
<li><a href="{{ url_for('admin_edit_photo', photo_id=photo.id) }}">{{ photo.timestamp.strftime("%Y-%m-%d %H:%M:%S") }} - {{ photo.title or photo.filename }}</a></li>
{% endfor %}
</ul>
{% endif %}
</main>
{% endblock %}

View file

@ -0,0 +1,26 @@
{% extends 'admin/admin.html' %}
{% set title = 'Remove ' + (photo.title or 'unnamed photo') %}
{% set active_page = 'photos' %}
{% block body %}
<main class="view-image">
<img src="{{ photo_resize_url(photo, 800) }}">
<h2>{{ photo.title or 'Unnamed photo' }}</h2>
{{ photo.description or 'No description'}}
<form method="POST" enctype="multipart/form-data">
{{ form.csrf_token }}
<h2>Remove photo?</h2>
<p>Are you sure?</p>
<div class="field">
<button type="submit">Remove</button>
</div>
<div class="field">
<a href="{{ url_for('admin_edit_photo', photo_id=photo.id) }}">No, take me away from here</a>
</div>
</form>
</main>
{% endblock %}

44
templates/base.html Normal file
View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
{% block head %}
<title>{{ title }} | reinefjord</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--
<link rel="icon" href="{{ url_for('static', filename='images/favicon.ico') }}">
-->
{% assets 'common_css' %}
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
{% endblock %}
</head>
<body>
<div class="wrapper">
<header>
{% block header %}{% endblock %}
</header>
<nav>
<ul>
{% for href, id, caption in navbar %}
<li><a href="{{ href|e }}" class="nav-item {% if id == active_page %}active{% endif %}">{{ caption|e }}</a></li>
{% endfor %}
</ul>
</nav>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<li class="flash {{ category }}">{{ message|safe }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
</div>
</body>
</html>

10
templates/error/404.html Normal file
View file

@ -0,0 +1,10 @@
{% extends "layout.html" %}
{% set title = "404 - Not found" %}
{% block body %}
<main>
<h2>404 &mdash; Not found</h2>
<p>The page you were looking for was not found :(</p>
</main>
{% endblock %}

11
templates/error/500.html Normal file
View file

@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% set title = "500 - Internal Server Error" %}
{% block body %}
<main>
<h2>500 &mdash; Internal Server Error</h2>
<p>It seems like something broke on the server end. Sorry! :S</p>
<p>Try again or try something else.</p>
</main>
{% endblock %}

13
templates/index.html Normal file
View file

@ -0,0 +1,13 @@
{% extends 'layout.html' %}
{% set active_page = 'index' %}
{% set title = '^_^' %}
{% block body %}
<main>
<h2>Welcome</h2>
<p>... to my fancy webpage!</p>
</main>
{% endblock %}

11
templates/layout.html Normal file
View file

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% set navbar = [
(url_for('index'), 'index', 'Home'),
(url_for('photography'), 'photo', 'Photography'),
(url_for('me'), 'me', 'Find me')
] %}
{% block header %}
<h1><a href="{{ url_for('index') }}" class="nav-item">rupus reinefjord</a></h1>
{% endblock %}

20
templates/login.html Normal file
View file

@ -0,0 +1,20 @@
{% extends 'layout.html' %}
{% set active_page = None %}
{% set title = 'Login' %}
{% block body %}
<main>
<h2>Login</h2>
<form method="POST">
{{ form.csrf_token }}
<div class="field">
{{ form.password.label }}
{{ form.password }}
</div>
<button type="submit">Login</button>
</form>
</main>
{% endblock %}

31
templates/me.html Normal file
View file

@ -0,0 +1,31 @@
{% extends 'layout.html' %}
{% set active_page = 'me' %}
{% set title = 'Find me' %}
{% block body %}
<main>
<h2>Find me</h2>
<dl>
<dt>GitHub:</dt>
<dd><a href="https://github.com/reinefjord">reinefjord</a></dd>
<dt>Instagram:</dt>
<dd><a href="https://www.instagram.com/rupusreinefjord/">@rupusreinefjord</a></dd>
<dt>Flickr:</dt>
<dd><a href="https://www.flickr.com/photos/koltrast_">koltrast_</a></dd>
<dt>Keybase:</dt>
<dd><a href="https://keybase.io/rupus">rupus</a></dd>
</dl>
<h2>Contact me</h2>
<dl>
<dt>Email:</dt>
<dd>
<a href="mailto:firstname(at)lastname(dot)net" class="email">cnVwdXNAa2x0cnN0LnNl</a>
<noscript><p>Please activate javascript to see the address. Hint: it is <span class="email">firstname(at)lastname(dot)net</span></p></noscript>
</dd>
<dt>PGP:</dt>
<dd><a href="{{ url_for('static', filename='rupusreinefjord_pubkey.asc') }}"><code>E275 C3F0 8765 EBC3 C906 66F4 E918 999B E668 8BD2</code></a></dd>
</dl>
<script src="{{ url_for('static', filename='js/emaildecode.js') }}"></script>
</main>
{% endblock %}

View file

@ -0,0 +1,20 @@
{% extends 'layout.html' %}
{% set active_page = 'photo' %}
{% set title = 'Photography' %}
{% block body %}
<main>
<h2>Photography</h2>
<div class="photo-grid">
{% for photo in photos %}
<a href="{{ url_for('view_photo', photo_id=photo.id) }}">
<img srcset="{{ photo_resize_url(photo, 200) }},
{{ photo_resize_url(photo, 300) }} 1.5x,
{{ photo_resize_url(photo, 400) }} 2x"
src="{{ photo_resize_url(photo, 200) }}">
</a>
{% endfor %}
</div>
</main>
{% endblock %}

32
templates/view_photo.html Normal file
View file

@ -0,0 +1,32 @@
{% extends 'layout.html' %}
{% set active_page = 'photo' %}
{% set title = 'Photography' %}
{% block body %}
<main class="view-image">
<img srcset="{{ photo_resize_url(photo, 800) }},
{{ photo_resize_url(photo, 1200) }} 1.5x,
{{ photo_resize_url(photo, 1600) }} 2x"
src="{{ photo_resize_url(photo, 800) }}">
<article>
{% if photo.title %}
<h2>{{ photo.title }}</h2>
{% endif %}
{% if photo.description %}
{{ photo.html_description|safe }}
{% endif %}
{% if next or prev %}
</article>
<nav>
{% if next %}
<a href="{{ url_for('view_photo', photo_id=next.id) }}" class="nav-item next">&larr; Next</a>
{% endif %}
{% if prev %}
<a href="{{ url_for('view_photo', photo_id=prev.id) }}" class="nav-item prev">Previous &rarr;</a>
{% endif %}
{% endif %}
</main>
{% endblock %}