Add RSS feed, contact page, Kirsle.net stuff
This commit is contained in:
parent
4bb9b5d687
commit
9a4f74844d
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
_basedir = os.path.abspath(os.path.dirname(__file__))
|
_basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
import datetime
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
|
@ -63,6 +64,19 @@ BLOG_DEFAULT_PRIVACY = "public"
|
||||||
BLOG_TIME_FORMAT = "%A, %B %d %Y @ %I:%M:%S %p" # "Weekday, Month dd yyyy @ hh:mm:ss AM"
|
BLOG_TIME_FORMAT = "%A, %B %d %Y @ %I:%M:%S %p" # "Weekday, Month dd yyyy @ hh:mm:ss AM"
|
||||||
BLOG_ALLOW_COMMENTS = True
|
BLOG_ALLOW_COMMENTS = True
|
||||||
|
|
||||||
|
# RSS feed settings.
|
||||||
|
RSS_TITLE = "Kirsle.net"
|
||||||
|
RSS_LINK = "http://www.kirsle.net/"
|
||||||
|
RSS_LANGUAGE = "en"
|
||||||
|
RSS_DESCRIPTION = "The web blog of Kirsle"
|
||||||
|
RSS_COPYRIGHT = "Copyright {}, Kirsle.net".format(str(datetime.datetime.now().strftime("%Y")))
|
||||||
|
RSS_WEBMASTER = NOTIFY_ADDRESS[0]
|
||||||
|
RSS_IMAGE_TITLE = RSS_TITLE
|
||||||
|
RSS_IMAGE_URL = "http://www.kirsle.net/static/avatars/casey.png"
|
||||||
|
RSS_IMAGE_WIDTH = 96
|
||||||
|
RSS_IMAGE_HEIGHT = 96
|
||||||
|
RSS_IMAGE_DESCRIPTION = "Kirsle's Avatar"
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
## Photo Settings ##
|
## Photo Settings ##
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
|
@ -21,12 +21,14 @@ from rophako.modules.blog import mod as BlogModule
|
||||||
from rophako.modules.photo import mod as PhotoModule
|
from rophako.modules.photo import mod as PhotoModule
|
||||||
from rophako.modules.comment import mod as CommentModule
|
from rophako.modules.comment import mod as CommentModule
|
||||||
from rophako.modules.emoticons import mod as EmoticonsModule
|
from rophako.modules.emoticons import mod as EmoticonsModule
|
||||||
|
from rophako.modules.contact import mod as ContactModule
|
||||||
app.register_blueprint(AdminModule)
|
app.register_blueprint(AdminModule)
|
||||||
app.register_blueprint(AccountModule)
|
app.register_blueprint(AccountModule)
|
||||||
app.register_blueprint(BlogModule)
|
app.register_blueprint(BlogModule)
|
||||||
app.register_blueprint(PhotoModule)
|
app.register_blueprint(PhotoModule)
|
||||||
app.register_blueprint(CommentModule)
|
app.register_blueprint(CommentModule)
|
||||||
app.register_blueprint(EmoticonsModule)
|
app.register_blueprint(EmoticonsModule)
|
||||||
|
app.register_blueprint(ContactModule)
|
||||||
|
|
||||||
# Custom Jinja handler to support custom- and default-template folders for
|
# Custom Jinja handler to support custom- and default-template folders for
|
||||||
# rendering templates.
|
# rendering templates.
|
||||||
|
@ -114,6 +116,8 @@ def catchall(path):
|
||||||
return send_file(abspath)
|
return send_file(abspath)
|
||||||
elif not "." in path and os.path.isfile(abspath + ".html"):
|
elif not "." in path and os.path.isfile(abspath + ".html"):
|
||||||
return rophako.utils.template(path + ".html")
|
return rophako.utils.template(path + ".html")
|
||||||
|
elif not "." in path and os.path.isfile(abspath + "/index.html"):
|
||||||
|
return rophako.utils.template(path + "/index.html")
|
||||||
|
|
||||||
return not_found("404")
|
return not_found("404")
|
||||||
|
|
||||||
|
@ -125,7 +129,6 @@ def index():
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found(error):
|
def not_found(error):
|
||||||
print "NOT FOUND"
|
|
||||||
return render_template('errors/404.html', **g.info), 404
|
return render_template('errors/404.html', **g.info), 404
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,26 +46,19 @@ def get_index():
|
||||||
return db
|
return db
|
||||||
|
|
||||||
|
|
||||||
def __get_categories():
|
def get_categories():
|
||||||
"""Get the blog categories cache.
|
"""Get the blog categories and their popularity."""
|
||||||
|
index = get_index()
|
||||||
|
|
||||||
The category cache is in the following format:
|
# Group by tags.
|
||||||
|
tags = {}
|
||||||
|
for post, data in index.iteritems():
|
||||||
|
for tag in data["categories"]:
|
||||||
|
if not tag in tags:
|
||||||
|
tags[tag] = 0
|
||||||
|
tags[tag] += 1
|
||||||
|
|
||||||
```
|
return tags
|
||||||
{
|
|
||||||
'category_name': {
|
|
||||||
'post_id': 'friendly_id',
|
|
||||||
...
|
|
||||||
},
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Index doesn't exist?
|
|
||||||
if not JsonDB.exists("blog/tags"):
|
|
||||||
return {}
|
|
||||||
return JsonDB.get("blog/tags")
|
|
||||||
|
|
||||||
|
|
||||||
def get_entry(post_id):
|
def get_entry(post_id):
|
||||||
|
|
|
@ -6,6 +6,8 @@ from flask import Blueprint, g, request, redirect, url_for, session, flash
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import calendar
|
import calendar
|
||||||
|
import time
|
||||||
|
from xml.dom.minidom import Document
|
||||||
|
|
||||||
import rophako.model.user as User
|
import rophako.model.user as User
|
||||||
import rophako.model.blog as Blog
|
import rophako.model.blog as Blog
|
||||||
|
@ -225,6 +227,85 @@ def delete():
|
||||||
|
|
||||||
return template("blog/delete.html")
|
return template("blog/delete.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/rss")
|
||||||
|
def rss():
|
||||||
|
"""RSS feed for the blog."""
|
||||||
|
doc = Document()
|
||||||
|
|
||||||
|
rss = doc.createElement("rss")
|
||||||
|
rss.setAttribute("version", "2.0")
|
||||||
|
rss.setAttribute("xmlns:blogChannel", "http://backend.userland.com/blogChannelModule")
|
||||||
|
doc.appendChild(rss)
|
||||||
|
|
||||||
|
channel = doc.createElement("channel")
|
||||||
|
rss.appendChild(channel)
|
||||||
|
|
||||||
|
rss_time = "%a, %d %b %Y %H:%M:%S GMT"
|
||||||
|
|
||||||
|
######
|
||||||
|
## Channel Information
|
||||||
|
######
|
||||||
|
|
||||||
|
today = time.strftime(rss_time, time.gmtime())
|
||||||
|
|
||||||
|
xml_add_text_tags(doc, channel, [
|
||||||
|
["title", RSS_TITLE],
|
||||||
|
["link", RSS_LINK],
|
||||||
|
["description", RSS_DESCRIPTION],
|
||||||
|
["language", RSS_LANGUAGE],
|
||||||
|
["copyright", RSS_COPYRIGHT],
|
||||||
|
["pubDate", today],
|
||||||
|
["lastBuildDate", today],
|
||||||
|
["webmaster", RSS_WEBMASTER],
|
||||||
|
])
|
||||||
|
|
||||||
|
######
|
||||||
|
## Image Information
|
||||||
|
######
|
||||||
|
|
||||||
|
image = doc.createElement("image")
|
||||||
|
channel.appendChild(image)
|
||||||
|
xml_add_text_tags(doc, image, [
|
||||||
|
["title", RSS_IMAGE_TITLE],
|
||||||
|
["url", RSS_IMAGE_URL],
|
||||||
|
["link", RSS_LINK],
|
||||||
|
["width", RSS_IMAGE_WIDTH],
|
||||||
|
["height", RSS_IMAGE_HEIGHT],
|
||||||
|
["description", RSS_IMAGE_DESCRIPTION],
|
||||||
|
])
|
||||||
|
|
||||||
|
######
|
||||||
|
## Add the blog posts
|
||||||
|
######
|
||||||
|
|
||||||
|
index = Blog.get_index()
|
||||||
|
posts = get_index_posts(index)
|
||||||
|
for post_id in posts[:BLOG_ENTRIES_PER_RSS]:
|
||||||
|
post = Blog.get_entry(post_id)
|
||||||
|
item = doc.createElement("item")
|
||||||
|
channel.appendChild(item)
|
||||||
|
xml_add_text_tags(doc, item, [
|
||||||
|
["title", post["subject"]],
|
||||||
|
["link", url_for("blog.entry", fid=post["fid"])],
|
||||||
|
["description", post["body"]],
|
||||||
|
["pubDate", time.strftime(rss_time, time.gmtime(post["time"]))],
|
||||||
|
])
|
||||||
|
|
||||||
|
return doc.toprettyxml(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def xml_add_text_tags(doc, root_node, tags):
|
||||||
|
"""RSS feed helper function.
|
||||||
|
|
||||||
|
Add a collection of simple tag/text pairs to a root XML element."""
|
||||||
|
for pair in tags:
|
||||||
|
name, value = pair
|
||||||
|
channelTag = doc.createElement(name)
|
||||||
|
channelTag.appendChild(doc.createTextNode(str(value)))
|
||||||
|
root_node.appendChild(channelTag)
|
||||||
|
|
||||||
|
|
||||||
def partial_index():
|
def partial_index():
|
||||||
"""Partial template for including the index view of the blog."""
|
"""Partial template for including the index view of the blog."""
|
||||||
|
|
||||||
|
@ -249,18 +330,8 @@ def partial_index():
|
||||||
else:
|
else:
|
||||||
pool = index
|
pool = index
|
||||||
|
|
||||||
# Separate the sticky posts from the normal ones.
|
# Get the posts we want.
|
||||||
sticky, normal = set(), set()
|
posts = get_index_posts(pool)
|
||||||
for post_id, data in pool.iteritems():
|
|
||||||
if data["sticky"]:
|
|
||||||
sticky.add(post_id)
|
|
||||||
else:
|
|
||||||
normal.add(post_id)
|
|
||||||
|
|
||||||
# Sort the blog IDs by published time.
|
|
||||||
posts = []
|
|
||||||
posts.extend(sorted(sticky, key=lambda x: pool[x]["time"], reverse=True))
|
|
||||||
posts.extend(sorted(normal, key=lambda x: pool[x]["time"], reverse=True))
|
|
||||||
|
|
||||||
# Handle pagination.
|
# Handle pagination.
|
||||||
offset = request.args.get("skip", 0)
|
offset = request.args.get("skip", 0)
|
||||||
|
@ -313,3 +384,41 @@ def partial_index():
|
||||||
g.info["posts"] = selected
|
g.info["posts"] = selected
|
||||||
|
|
||||||
return template("blog/index.inc.html")
|
return template("blog/index.inc.html")
|
||||||
|
|
||||||
|
|
||||||
|
def get_index_posts(index):
|
||||||
|
"""Helper function to get data for the blog index page."""
|
||||||
|
# Separate the sticky posts from the normal ones.
|
||||||
|
sticky, normal = set(), set()
|
||||||
|
for post_id, data in index.iteritems():
|
||||||
|
if data["sticky"]:
|
||||||
|
sticky.add(post_id)
|
||||||
|
else:
|
||||||
|
normal.add(post_id)
|
||||||
|
|
||||||
|
# Sort the blog IDs by published time.
|
||||||
|
posts = []
|
||||||
|
posts.extend(sorted(sticky, key=lambda x: index[x]["time"], reverse=True))
|
||||||
|
posts.extend(sorted(normal, key=lambda x: index[x]["time"], reverse=True))
|
||||||
|
return posts
|
||||||
|
|
||||||
|
def partial_tags():
|
||||||
|
"""Get a listing of tags and their quantities for the nav bar."""
|
||||||
|
tags = Blog.get_categories()
|
||||||
|
|
||||||
|
# Sort the tags by popularity.
|
||||||
|
sort_tags = [ tag for tag in sorted(tags.keys(), key=lambda y: tags[y], reverse=True) ]
|
||||||
|
result = []
|
||||||
|
has_small = False
|
||||||
|
for tag in sort_tags:
|
||||||
|
result.append(dict(
|
||||||
|
category=tag,
|
||||||
|
count=tags[tag],
|
||||||
|
small=tags[tag] < 3, # TODO: make this configurable
|
||||||
|
))
|
||||||
|
if tags[tag] < 3:
|
||||||
|
has_small = True
|
||||||
|
|
||||||
|
g.info["tags"] = result
|
||||||
|
g.info["has_small"] = has_small
|
||||||
|
return template("blog/categories.inc.html")
|
|
@ -14,7 +14,6 @@ from config import *
|
||||||
|
|
||||||
mod = Blueprint("comment", __name__, url_prefix="/comments")
|
mod = Blueprint("comment", __name__, url_prefix="/comments")
|
||||||
|
|
||||||
## TODO: emoticon support
|
|
||||||
|
|
||||||
@mod.route("/")
|
@mod.route("/")
|
||||||
def index():
|
def index():
|
||||||
|
|
67
rophako/modules/contact.py
Normal file
67
rophako/modules/contact.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Endpoints for contacting the site owner."""
|
||||||
|
|
||||||
|
from flask import Blueprint, g, request, redirect, url_for, session, flash
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rophako.utils import template, send_email
|
||||||
|
from rophako.log import logger
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
mod = Blueprint("contact", __name__, url_prefix="/contact")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/")
|
||||||
|
def index():
|
||||||
|
return template("contact/index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/send", methods=["POST"])
|
||||||
|
def send():
|
||||||
|
"""Submitting the contact form."""
|
||||||
|
name = request.form.get("name", "") or "Anonymous"
|
||||||
|
email = request.form.get("email", "")
|
||||||
|
subject = request.form.get("subject", "") or "[No Subject]"
|
||||||
|
message = request.form.get("message", "")
|
||||||
|
|
||||||
|
# Spam traps.
|
||||||
|
trap1 = request.form.get("contact", "x") != ""
|
||||||
|
trap2 = request.form.get("website", "x") != "http://"
|
||||||
|
if trap1 or trap2:
|
||||||
|
flash("Wanna try that again?")
|
||||||
|
return redirect(url_for(".index"))
|
||||||
|
|
||||||
|
# Message is required.
|
||||||
|
if len(message) == 0:
|
||||||
|
flash("The message is required.")
|
||||||
|
return redirect(url_for(".index"))
|
||||||
|
|
||||||
|
# Send the e-mail.
|
||||||
|
send_email(
|
||||||
|
to=NOTIFY_ADDRESS,
|
||||||
|
subject="Contact Form on {}: {}".format(SITE_NAME, subject),
|
||||||
|
message="""A visitor to {site_name} has sent you a message!
|
||||||
|
|
||||||
|
IP Address: {ip}
|
||||||
|
User Agent: {ua}
|
||||||
|
Referrer: {referer}
|
||||||
|
Name: {name}
|
||||||
|
E-mail: {email}
|
||||||
|
Subject: {subject}
|
||||||
|
|
||||||
|
{message}""".format(
|
||||||
|
site_name=SITE_NAME,
|
||||||
|
ip=request.remote_addr,
|
||||||
|
ua=request.user_agent.string,
|
||||||
|
referer=request.headers.get("Referer", ""),
|
||||||
|
name=name,
|
||||||
|
email=email,
|
||||||
|
subject=subject,
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
flash("Your message has been delivered.")
|
||||||
|
return redirect(url_for("index"))
|
|
@ -2,14 +2,21 @@
|
||||||
|
|
||||||
# Legacy endpoint compatibility from kirsle.net.
|
# Legacy endpoint compatibility from kirsle.net.
|
||||||
|
|
||||||
from flask import request, redirect, url_for
|
from flask import g, request, redirect, url_for, flash
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
from rophako import app
|
from rophako import app
|
||||||
|
from rophako.utils import template
|
||||||
import rophako.model.blog as Blog
|
import rophako.model.blog as Blog
|
||||||
|
import rophako.jsondb as JsonDB
|
||||||
|
|
||||||
|
|
||||||
@app.route("/+")
|
@app.route("/+")
|
||||||
def google_plus():
|
def google_plus():
|
||||||
return redirect("https://plus.google.com/+NoahPetherbridge/posts")
|
return redirect("https://plus.google.com/+NoahPetherbridge/posts")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/blog.html")
|
@app.route("/blog.html")
|
||||||
def ancient_legacy_blog():
|
def ancient_legacy_blog():
|
||||||
post_id = request.args.get("id", None)
|
post_id = request.args.get("id", None)
|
||||||
|
@ -24,10 +31,67 @@ def ancient_legacy_blog():
|
||||||
|
|
||||||
return redirect(url_for("blog.entry", fid=post["fid"]))
|
return redirect(url_for("blog.entry", fid=post["fid"]))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/blog/kirsle/<fid>")
|
@app.route("/blog/kirsle/<fid>")
|
||||||
def legacy_blog(fid):
|
def legacy_blog(fid):
|
||||||
return redirect(url_for("blog.entry", fid=fid))
|
return redirect(url_for("blog.entry", fid=fid))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/rss.cgi")
|
||||||
|
def legacy_rss():
|
||||||
|
return redirect(url_for("blog.rss"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/firered/<page>")
|
||||||
|
@app.route("/firered")
|
||||||
|
def legacy_firered(page=""):
|
||||||
|
g.info["page"] = str(page) or "1"
|
||||||
|
return template("firered.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/download", methods=["GET", "POST"])
|
||||||
|
def legacy_download():
|
||||||
|
form = None
|
||||||
|
if request.method == "POST":
|
||||||
|
form = request.form
|
||||||
|
else:
|
||||||
|
form = request.args
|
||||||
|
|
||||||
|
method = form.get("method", "index")
|
||||||
|
project = form.get("project", "")
|
||||||
|
filename = form.get("file", "")
|
||||||
|
|
||||||
|
root = "/home/kirsle/www/projects"
|
||||||
|
|
||||||
|
if project and filename:
|
||||||
|
# Filter the sections.
|
||||||
|
project = re.sub(r'[^A-Za-z0-9]', '', project) # Project name is alphanumeric only.
|
||||||
|
filename = re.sub(r'[^A-Za-z0-9\-_\.]', '', filename)
|
||||||
|
|
||||||
|
# Check that all the files exist.
|
||||||
|
if os.path.isdir(os.path.join(root, project)) and os.path.isfile(os.path.join(root, project, filename)):
|
||||||
|
# Hit counters.
|
||||||
|
hits = { "hits": 0 }
|
||||||
|
db = "data/downloads/{}-{}".format(project, filename)
|
||||||
|
if JsonDB.exists(db.format(project, filename)):
|
||||||
|
hits = JsonDB.get(db)
|
||||||
|
|
||||||
|
# Actually getting the file?
|
||||||
|
if method == "get":
|
||||||
|
# Up the hit counter.
|
||||||
|
hits["hits"] += 1
|
||||||
|
JsonDB.commit(db, hits)
|
||||||
|
|
||||||
|
g.info["method"] = method
|
||||||
|
g.info["project"] = project
|
||||||
|
g.info["file"] = filename
|
||||||
|
g.info["hits"] = hits["hits"]
|
||||||
|
return template("download.html")
|
||||||
|
|
||||||
|
flash("The file or project wasn't found.")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<page>.html")
|
@app.route("/<page>.html")
|
||||||
def legacy_url(page):
|
def legacy_url(page):
|
||||||
return "/{}".format(page)
|
return "/{}".format(page)
|
19
rophako/www/blog/categories.inc.html
Normal file
19
rophako/www/blog/categories.inc.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% for tag in tags %}
|
||||||
|
{% if not tag["small"] %}
|
||||||
|
» <a href="{{ url_for('blog.category', category=tag['category']) }}">{{ tag['category'] }}</a>
|
||||||
|
<small>({{ tag['count'] }})</small><br>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if has_small %}
|
||||||
|
<div id="blog_show_more" style="display: none">
|
||||||
|
{% for tag in tags %}
|
||||||
|
{% if tag["small"] %}
|
||||||
|
» <a href="{{ url_for('blog.category', category=tag['category']) }}">{{ tag['category'] }}</a>
|
||||||
|
<small>({{ tag['count'] }})</small><br>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div id="blog_show_less" style="display: block">
|
||||||
|
¤ <a href="#" onClick="$('#blog_show_less').hide(); $('#blog_show_more').show(1000); return false">Show more...</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
|
@ -3,7 +3,7 @@
|
||||||
{% if can_older or can_newer %}
|
{% if can_older or can_newer %}
|
||||||
<div class="right">
|
<div class="right">
|
||||||
[
|
[
|
||||||
<a href="/rss">RSS Feed</a> | {# TODO! #}
|
<a href="{{ url_for('blog.rss') }}">RSS Feed</a> |
|
||||||
{% if can_earlier %}
|
{% if can_earlier %}
|
||||||
{% if category %}
|
{% if category %}
|
||||||
<a href="{{ url_for('blog.category', category=category) }}?skip={{ earlier }}">< Newer</a>
|
<a href="{{ url_for('blog.category', category=category) }}?skip={{ earlier }}">< Newer</a>
|
||||||
|
|
48
rophako/www/contact/index.html
Normal file
48
rophako/www/contact/index.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Contact Me{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Contact Me</h1>
|
||||||
|
|
||||||
|
You can use the form below to send me an e-mail.<p>
|
||||||
|
|
||||||
|
<form name="contact" action="{{ url_for('contact.send') }}" method="POST">
|
||||||
|
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||||
|
<table border="0" cellspacing="0" cellpadding="2">
|
||||||
|
<tr>
|
||||||
|
<td width="50%" align="left" valign="middle">
|
||||||
|
<strong>Your name:</strong><br>
|
||||||
|
<small>(so I know who you are)</small><br>
|
||||||
|
<input type="text" size="40" name="name">
|
||||||
|
</td>
|
||||||
|
<td width="50%" align="left" valign="middle">
|
||||||
|
<strong>Your email:</strong><br>
|
||||||
|
<small>(if you want a response)</small><br>
|
||||||
|
<input type="email" size="40" name="email">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" align="left" valign="middle">
|
||||||
|
<strong>Message subject:</strong><br>
|
||||||
|
<small>(optional)</small><br>
|
||||||
|
<input type="text" size="40" name="subject" style="width: 100%"><p>
|
||||||
|
|
||||||
|
<strong>Message:</strong><br>
|
||||||
|
<small>(required)</small><br>
|
||||||
|
<textarea cols="40" rows="12" name="message" style="width: 100%"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" align="right" valign="middle">
|
||||||
|
<button type="submit">Send Message</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div style="display: none">
|
||||||
|
If you can see these boxes, don't touch them.<br>
|
||||||
|
<input type="text" size="40" name="contact" value=""><br>
|
||||||
|
<input type="text" size="40" name="website" value="http://">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user