Merge branch 'master' into server-side-markdown
This commit is contained in:
commit
1411762ca1
|
@ -2,6 +2,10 @@
|
|||
/.idea
|
||||
/db.sqlite3
|
||||
/static
|
||||
/media
|
||||
/log
|
||||
stale_outputs_checked
|
||||
__pycache__
|
||||
Thoughts.iml
|
||||
.env
|
||||
*.pages
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import os
|
||||
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "whispermaphone.settings"
|
||||
|
||||
import django
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
|
||||
django.setup()
|
||||
from main.models import Thought
|
||||
|
||||
from jetforce import GeminiServer, JetforceApplication, Response, Status
|
||||
|
||||
from decouple import config
|
||||
|
||||
app = JetforceApplication()
|
||||
|
||||
|
||||
@app.route("", strict_trailing_slash=False)
|
||||
def index(request):
|
||||
thoughts = Thought.objects.order_by("-posted")
|
||||
|
||||
rendered_text = render_to_string("whispermaphone/index.gmi", {
|
||||
"thoughts": thoughts,
|
||||
})
|
||||
|
||||
return Response(Status.SUCCESS, "text/gemini", rendered_text)
|
||||
|
||||
|
||||
@app.route("/about", strict_trailing_slash=False)
|
||||
def about(request):
|
||||
return Response(Status.SUCCESS, "text/gemini", render_to_string("whispermaphone/about.gmi"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = GeminiServer(
|
||||
app=app,
|
||||
host="0.0.0.0",
|
||||
hostname="localhost",
|
||||
certfile=config("CERTFILE", default=None),
|
||||
keyfile=config("KEYFILE", default=None),
|
||||
port=1973
|
||||
)
|
||||
server.run()
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.7 on 2021-04-20 16:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0003_thought_uuid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='thought',
|
||||
name='media',
|
||||
field=models.FileField(blank=True, upload_to=''),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2 on 2021-05-01 20:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0004_thought_media'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='thought',
|
||||
name='media_alt',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,13 @@
|
|||
import os
|
||||
import uuid
|
||||
|
||||
import magic
|
||||
|
||||
from django.db import models
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.utils.text import normalize_newlines
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class Thought(models.Model):
|
||||
|
@ -9,3 +16,86 @@ class Thought(models.Model):
|
|||
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||
posted = models.DateTimeField(auto_now_add=True)
|
||||
timezone_offset = models.IntegerField() # The number of minutes behind UTC we were when this was posted
|
||||
media = models.FileField(upload_to="", blank=True) # A single image, video, or sound clip per post
|
||||
media_alt = models.TextField(blank=True) # An optional transcription of the Thought's media
|
||||
|
||||
def get_media_type(self):
|
||||
if not self.media:
|
||||
return ""
|
||||
else:
|
||||
return os.path.splitext(self.media.path)[1][1:]
|
||||
|
||||
def get_timezone(self):
|
||||
return timezone.get_fixed_timezone(-self.timezone_offset)
|
||||
|
||||
def get_offset_hours(self):
|
||||
offset_hours = -self.timezone_offset / 60
|
||||
# Convert 4.0 to 4
|
||||
if offset_hours == int(offset_hours):
|
||||
offset_hours = int(offset_hours)
|
||||
if offset_hours > 0:
|
||||
offset_hours = "+" + str(offset_hours)
|
||||
return str(offset_hours)
|
||||
|
||||
|
||||
# Honestly I'm so sick of this problem that writing out a comment here to explain why it is necessary is beyond me.
|
||||
# I'm calling this CharField and not MySpecialLineNormalizingCharField
|
||||
# because I think this should be the default behavior and I don't understand why it's not.
|
||||
class CharField(forms.CharField):
|
||||
def to_python(self, value):
|
||||
# I am assuming that value is always a string and normalize_newlines isn't going to error on any string input
|
||||
return super().to_python(normalize_newlines(value))
|
||||
|
||||
|
||||
# A dict mapping allowed mime types to file extensions
|
||||
ALLOWED_MEDIA_TYPES = {
|
||||
"image/png": "png",
|
||||
"image/jpeg": "jpeg",
|
||||
"audio/x-m4a": "m4a",
|
||||
"audio/mp3": "mp3",
|
||||
"video/mp4": "mp4",
|
||||
"video/quicktime": "mov",
|
||||
}
|
||||
|
||||
|
||||
class ThoughtForm(forms.ModelForm):
|
||||
def clean_media(self):
|
||||
f = self.cleaned_data["media"]
|
||||
if not f:
|
||||
return f
|
||||
|
||||
# 16 MB file size limit.
|
||||
if f.size > 2**24:
|
||||
raise ValidationError("Content too long", code="max_size")
|
||||
|
||||
chunk = next(f.chunks(chunk_size=2048))
|
||||
media_type = magic.from_buffer(chunk, mime="True")
|
||||
|
||||
if media_type not in ALLOWED_MEDIA_TYPES:
|
||||
raise ValidationError("Invalid media type", code="invalid")
|
||||
|
||||
return self.cleaned_data["media"]
|
||||
|
||||
class Meta:
|
||||
model = Thought
|
||||
exclude = ["posted"]
|
||||
widgets = {
|
||||
"text": forms.Textarea(attrs={"class": "thought", "rows": 1, "placeholder": "What are you thinking?"}),
|
||||
"extended_text": forms.Textarea(attrs={"class": "thought", "rows": 1, "placeholder": "Anything else?"}),
|
||||
"timezone_offset": forms.HiddenInput,
|
||||
"media_alt": forms.Textarea(attrs={"placeholder": "Media alternate text, please", "rows": 1})
|
||||
}
|
||||
field_classes = {
|
||||
"text": CharField,
|
||||
"extended_text": CharField,
|
||||
"media_alt": CharField,
|
||||
}
|
||||
|
||||
error_messages = {
|
||||
"text": {
|
||||
# It's debatable whether this is actually a "nice" error message
|
||||
# but I've grown fond of it
|
||||
"required": "Need some text.",
|
||||
"max_length": "Text must be at most 140 characters."
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import datetime
|
||||
|
||||
from django.utils.text import slugify
|
||||
|
||||
from .models import Thought
|
||||
|
||||
class Page:
|
||||
def __init__(self, formatted_name):
|
||||
self.formatted_name = formatted_name
|
||||
self.slug = slugify(formatted_name)
|
||||
|
||||
def get_all_entries(self):
|
||||
pass
|
||||
|
||||
|
||||
# Okay we're going to use some wack seasons here hold on
|
||||
# Let's go meteorological seasons
|
||||
# 1 - jan -> 1, 0
|
||||
# 2 - feb -> 2, 0
|
||||
# 3 - mar -> 3, 1, spring
|
||||
# 4 - apl -> 4, 1
|
||||
# 5 - may -> 5, 1
|
||||
# 6 - jun -> 6, 2, summer
|
||||
# 7 - jul -> 7, 2
|
||||
# 8 - aug -> 8, 2
|
||||
# 9 - sep -> 9, 3, fall
|
||||
# 10 - oct -> 10, 3
|
||||
# 11 - nov -> 11, 3
|
||||
# 12 - dec -> 0, 0, winter
|
||||
def season_for_date(date):
|
||||
return (date.month % 12) // 3
|
||||
|
||||
|
||||
def season_year_for_date(date):
|
||||
return season_for_date(date), date.year - (1 if date.month in [1, 2] else 0)
|
||||
|
||||
|
||||
def formatted_name_for_season_year(current_season, current_year):
|
||||
return ["Winter", "Spring", "Summer", "Fall"][current_season] + " " + str(current_year)
|
||||
|
||||
|
||||
def get_page_slug(thought):
|
||||
return slugify(formatted_name_for_season_year(*season_year_for_date(thought.posted)))
|
||||
|
||||
|
||||
class SeasonPage(Page):
|
||||
def __init__(self, current_season, current_year):
|
||||
super().__init__(formatted_name_for_season_year(current_season, current_year))
|
||||
|
||||
self.first_day_of_season = datetime.date(
|
||||
current_year,
|
||||
12 if current_season == 0 else current_season * 3,
|
||||
1
|
||||
)
|
||||
# If the current season is winter, then the next season starts on the next year
|
||||
# This is actually the first day of the next season but that's hard to type out and it's 2am
|
||||
self.last_day_of_season = datetime.date(
|
||||
current_year + (1 if current_season == 0 else 0),
|
||||
12 if current_season == 3 else (current_season + 1) * 3,
|
||||
1
|
||||
)
|
||||
|
||||
def get_all_entries(self):
|
||||
return Thought.objects.order_by("-posted").filter(
|
||||
posted__gte=self.first_day_of_season, # First month of this season
|
||||
posted__lt=self.last_day_of_season # First month of next season
|
||||
)
|
||||
|
||||
|
||||
# Need to loop over all thoughts? and yield a new Page for each season
|
||||
# Assume that if you're using this generator, you have at least 1 thought each
|
||||
# season between the first and last
|
||||
# We don't take into account timezone here. Because local time isn't monotonic increasing,
|
||||
# it's ill-defined how to split up a list of items ordered by server time. This is annoying
|
||||
# But the alternative is to call get_season for every thought and sort them into buckets
|
||||
def season_pages():
|
||||
ordered_thoughts = Thought.objects.order_by("posted")
|
||||
first_thought = ordered_thoughts.first()
|
||||
last_thought = ordered_thoughts.last()
|
||||
|
||||
current_year = first_thought.posted.year
|
||||
current_season = season_for_date(first_thought.posted)
|
||||
while current_year < last_thought.posted.year or current_season != season_for_date(last_thought.posted):
|
||||
yield SeasonPage(current_season, current_year)
|
||||
|
||||
if current_season == 0:
|
||||
current_year += 1
|
||||
current_season += 1
|
||||
current_season = current_season % 4
|
||||
|
||||
yield SeasonPage(current_season, current_year)
|
||||
|
||||
def get_all_pages():
|
||||
pages = list(season_pages())
|
||||
pages.reverse()
|
||||
return pages
|
||||
# Where a Page has:
|
||||
# .slug
|
||||
# .formatted_name
|
||||
# .get_all_entries
|
||||
# return [Pages]
|
|
@ -1,8 +1,3 @@
|
|||
.main-content {
|
||||
padding: 20px 30px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#password {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
:root {
|
||||
/*Background and text colors from Atom One Dark*/
|
||||
--background-color: #23252E;
|
||||
/*C9CED5*/
|
||||
--text-color: #C5CBD3;
|
||||
/*A lot of accent colors work with this one*/
|
||||
--accent-color: #78916e;
|
||||
/*Old, og: #78916e; AA compliant: #7F9875; AAA compliant: #96BD86 */
|
||||
--accent-color: #7F9875;
|
||||
/* Pink Official Green Forest Lime Dark theme-green Yellow Muted yellow */
|
||||
/* #D787FF #5FAF87, #286546 #75a98a #78916e, #968540 #9a9168 */
|
||||
}
|
||||
|
@ -44,12 +46,12 @@ body {
|
|||
}
|
||||
|
||||
.main-wrap {
|
||||
max-width: 1000px;
|
||||
max-width: 900px;
|
||||
margin: 20px 30px;
|
||||
}
|
||||
|
||||
.thought {
|
||||
margin: 20px 30px;
|
||||
line-height: 1.5em;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.thought.highlighted .main, .thought.highlighted .extended-text {
|
||||
|
@ -78,7 +80,7 @@ input[type="submit"]:hover, input[type="submit"]:focus {
|
|||
|
||||
/* Textbox/textarea styles */
|
||||
textarea, input[type="text"], input[type="password"] {
|
||||
padding: 1px 5px;
|
||||
padding: 0 5px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
background: none;
|
||||
|
@ -89,20 +91,23 @@ textarea, input[type="text"], input[type="password"] {
|
|||
border-bottom: 1px solid var(--accent-color);
|
||||
border-radius: 0;
|
||||
overflow-x: hidden;
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
}
|
||||
textarea::placeholder, input[type="text"]::placeholder {
|
||||
color: var(--text-color);
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.text {
|
||||
.text, .thought-end {
|
||||
max-width: 100%;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.main-text, .main-text p:last-child {
|
||||
display: inline; /*inline so that the Show More is on the same line*/
|
||||
}
|
||||
|
||||
.text pre {
|
||||
display: inline-block; /*Code blocks need to be inline-block*/
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -110,7 +115,7 @@ a {
|
|||
.text a {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
.text p, .text pre {
|
||||
.text p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
@ -118,24 +123,43 @@ a {
|
|||
.text > :not(:last-child) {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.text pre {
|
||||
background: black;
|
||||
/*Inline code*/
|
||||
.text p > code {
|
||||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
padding: 1px 5px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
/*Code blocks*/
|
||||
.text pre {
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
padding: 3px 7px;
|
||||
}
|
||||
.text pre > code {
|
||||
/* block forces the text to the whole width, but since inline-block is buggy in Safari, this is our best option */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.text img {
|
||||
max-width: 100%;
|
||||
.extended-media {
|
||||
max-width: calc(100% - 20px);
|
||||
max-height: 500px;
|
||||
margin: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.extended-text {
|
||||
margin-top: 1em;
|
||||
display: inline-block; /*Needs to be inline-block, so that margin on it works*/
|
||||
display: block; /*Needs to be inline-block or block, so that margin on it works*/
|
||||
}
|
||||
|
||||
.thought-end {
|
||||
overflow: auto;
|
||||
font-size: 14px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.thought hr {
|
||||
|
@ -144,33 +168,87 @@ a {
|
|||
border-bottom: 1px var(--accent-color) solid;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
.timestamp, .permalink {
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
.permalink {
|
||||
margin-right: 6px;
|
||||
color: var(--text-color)
|
||||
}
|
||||
.permalink .button, .permalink .button:visited {
|
||||
color: var(--accent-color);
|
||||
margin-right: 4px;
|
||||
}
|
||||
.permalink .button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.permalink::after {
|
||||
content: "•"
|
||||
}
|
||||
|
||||
/* Navbar styles */
|
||||
#main-nav {
|
||||
padding: 4% 30px 15px;
|
||||
font-size: 3em;
|
||||
font-size: 3rem !important;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
header .text {
|
||||
|
||||
#main-nav .text {
|
||||
color: var(--text-color);
|
||||
|
||||
font-family: Georgia, Libre Baskerville, serif;
|
||||
}
|
||||
|
||||
#main-nav h1.text {
|
||||
border-bottom: 4px solid var(--accent-color);
|
||||
}
|
||||
|
||||
/*footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background: var(--accent-color);
|
||||
color: var(--background-color);
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
height: 20px;
|
||||
}*/
|
||||
#main-nav h1 {
|
||||
/* Disable browser styles for h1 */
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.history-nav.top {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 40px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.history-nav.bottom {
|
||||
margin-top: 50px;
|
||||
margin-bottom: 30px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
.history-nav ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.history-nav li {
|
||||
display: inline;
|
||||
}
|
||||
.history-nav li:not(:first-child)::before {
|
||||
content: "•";
|
||||
}
|
||||
.history-nav li:not(:first-child) > * {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.history-nav li:not(:last-child) > * {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.history-nav .current-page {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.history-nav a, .history-nav a:visited {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
.history-nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -1,22 +1,35 @@
|
|||
textarea.thought {
|
||||
display: block;
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
|
||||
textarea.thought::placeholder {
|
||||
color: var(--text-color);
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
#post-button {
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
margin: 30px;
|
||||
padding: 0;
|
||||
font-size: 32px;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
font-family: Georgia, Libre Baskerville, serif;
|
||||
}
|
||||
|
||||
.media-wrapper {
|
||||
padding: 8px;
|
||||
margin: 20px 0;
|
||||
border-left: 2px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.media-wrapper [name="media"] {
|
||||
/* Tries to make the media upload button have a cursor. Doesn't work but I blame WebKit */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.media-wrapper textarea {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-style: italic;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
=> / Thoughts
|
||||
# About
|
||||
|
||||
## Welcome!
|
||||
|
||||
This is a website that I created to document my thoughts. It addresses many of my gripes with similar platforms, like Twitter, Tumblr, Micro.blog, and Blogger, although it takes inspiration from all of these.
|
||||
|
||||
I’m Matthias. These are mostly my thoughts. Raw, un-distilled, thoughts. I do filter any thoughts that would be offensive or NSFW. Generally, with the exception of censored profanity, complex ideas, and links to other sites that are less principled, this site should be safe for your child to read.
|
||||
|
||||
This website is my own creation. It follows my own rules. It is dedicated to 3 things.
|
||||
|
||||
First, of course, is thoughts. The mere act of creating ideas is good, and those ideas should be shared.
|
||||
Second, is words. Words still have power.
|
||||
Third, is design. Design is simplicity and functionality and form and appearance.
|
||||
|
||||
Welcome to a website, welcome to my website. Or someone’s website, at least. Websites are strange like that, you can never really tell who they belong to. Maybe this website belongs to you, for the amount of time that you’re here and you have it. Maybe it belongs to to no one, or all of us.
|
||||
|
||||
I am a very small amount of everyone. Infinitesimally small pieces of me belong to everyone, and I suppose that includes you, so I owe you at least something.
|
||||
|
||||
This is a website. It is not an academic paper, thesis, or dissertation. I do guarantee that all of the thoughts here are genuine—that is, I actually had them. However, I make no guarantees regarding their quality. Many of the thoughts here were had, posted, and then immediately discarded as clearly worthless. This is not disclaimed, I trust that you too can discern which of these thoughts are core values of my life and which are the merest passing fancy.
|
||||
|
||||
This is a website.
|
||||
|
||||
This is a breeding ground for ideas. The only criteria for something to be posted here is that I want it to be.
|
||||
|
||||
Here I work on answering the question. Everything posted here is an attempt to capture the liquid of life. Some moments are rocks, forcing you to hold on to them, weighing you down. Others are liquid, slipping out of your hands despite your best attempts. I suspect we are all frantically searching for a way to hold on to the things that are rapidly slipping away from our grasp.
|
||||
|
||||
This is a blog. In much the sense that Tumblr is a blogging platform, so too is this website.
|
||||
|
||||
If you realize that you’re dying, they only thing to do is turn back toward your childhood. Oh so many people don’t realize, or realize too late. I saw a man getting a transfusion of blood. Though it was necessary to keep him alive, the pain of having it injected into him incapacitated him.
|
||||
|
||||
This is WhisperMaPhone. It’s a tool I created to share my thoughts. It is currently closed source, as it is still in active development. However, if you’re interested in setting up a page similar to this, let me know.
|
||||
|
||||
“Our brains are dulled by the incurable mania of wanting to make the unknown known, classifiable. The desire for analysis wins out over the sentiments.”
|
||||
|
||||
This is the void. There’s no way for you to contact me on here. There are no guarantees when you’re shouting into the void. But the void doesn’t normally shout back. No one ever said the void wasn’t listening. You’re listening. This is my void.
|
||||
|
||||
I use single quotes to indicate that I’m paraphrasing, and double-quotes to indicate a direct quotation from someone else. Sometimes I will not cite my source, because the origin is less important that the words. Sometimes I don’t remember the source.
|
||||
|
||||
The portion of this website which is hidden from prying eyes is written in Python, with Django. Although it is visible to a couple of eyes on account of their not-prying. Everyone looks but only few find. Still, you cannot find if you do not look, although whether you are more or less prepared to understand what you find is a different point entirely.
|
||||
|
||||
“In this day and age logical methods are applicable only to solving problems of secondary interest”
|
||||
|
||||
It has recently come to my attention that there is some confusion regarding the purpose of this website, or even, what it is. This page should answer both of those questions.
|
||||
|
||||
I can only pray you understand what I give you, but likely you will not. They never did understand the gifts they were given.
|
||||
|
||||
This is a surrender and a revel. This is an experiment. This is a rejection of social media and an embrace of it.
|
||||
|
||||
This is a circus inside a clown factory. This is a bomb.
|
||||
|
||||
```
|
||||
All thoughts are provided with a timestamp in the timezone of the poster, to ensure proper contextualization.
|
||||
Contextualization is of utmost importance.
|
||||
```
|
||||
|
||||
```
|
||||
This is different.
|
||||
This is the same.
|
||||
```
|
||||
|
||||
I’m Matthias. (most of the time)
|
||||
|
||||
This is a Russian bot attempting to manipulate the election process. Posts here represent a subset of the result of training a GPT-3 text generation algorithm on my brain.
|
||||
|
||||
This is a result of not even knowing what alcohol tastes like. This is fire from the past. This is my first most prized possession. This is Researcher ████████.
|
||||
|
||||
```
|
||||
All thoughts are provided completely free of context.
|
||||
```
|
||||
|
||||
Gödel has disproved logic. The imperfections of the human mind are inescapable. There are perhaps corners of that 2D plane of logic that remain unexplored, but by logic alone that plane will never be fully explored, so those corners do not interest us. No, let us think in 3D.
|
||||
|
||||
This is an ebenezer to the Lord and the things He has done for me.
|
||||
|
||||
I’m hungry.
|
||||
|
||||
Hey there, it’s Nate.
|
||||
|
||||
This website is an outlet for the many intrusive thoughts that I have, that I cannot speak out loud. These thoughts don’t have meaning, they aren’t important. Even if it were socially acceptable for me to share them at random times, even if people were interested, they’re still not of any particular value. On the other hand, here they can be themselves. They can exist free from judgement. You, and me, come here to examine the curious thoughts of a curious boy. Here my thoughts can thrive.
|
||||
|
||||
This is the real track four.
|
||||
|
||||
This is a story about a boy.
|
||||
People don’t like things that are different.
|
||||
The world is confusing.
|
||||
This is a cult.
|
||||
|
||||
This is an explanation. This page answers any questions you may have about this website.
|
||||
|
||||
Welcome to my website! This is a micro-blogging platform for me to share my thoughts. This website breaks normal blogging and micro-blogging paradigms. I have 140 character limit on the first line, for real quick thoughts, and then a Show More button, and then unlimited characters in a second field. I’ll sometimes use hundreds of characters, writing mini-essays on topics that catch my interest. This would be very impractical to do on Twitter. Although this websites solves another gripe with Twitter, the concept of “liking” Tweets. I don’t ask for your feedback here. This frees me to post what I’m inspired to, and allows you to experience what I post without worrying about if you should “like” it.
|
||||
|
||||
This is how I learned to stop worrying. This is a flea in which our two bloods mingled be.
|
||||
|
||||
All thoughts posted here are those of my employer.
|
||||
|
||||
This is the end and a new beginning. I’ve stopped using Discord, Twitter, Instagram, and other social media since creating this website.
|
||||
|
||||
I open vim to write. I don’t have a spell checker, so I struggle, catching my own errors by sight, recognizeing that the word looks wrong, but not knowing how to fix it. I shrug. I’ll go over it with a spell-check at some point. But I might not. I might impuse-publish this page, one night, when I’m feeling particularly lonely. Then the message that I dread (“you spelled ‘recognizeing’ wrong”) might come as a welcome social interaction, and an opprotunity to improve my website. Rather than an annoyance.
|
||||
|
||||
This is not a test. This is considered harmful. This is mostly harmless. Fear the daemons you have created at JOKEEFUNNY.COM.
|
||||
|
||||
This is art. This is poetry.
|
||||
|
||||
This is a revolution of the proletariat. We will not sit quietly.
|
||||
This is sitting loudly. This website is inaction. It exists only to let me complain.
|
||||
|
||||
I find it helps to imagine all of my posts here as the murmurings of someone talking in their sleep.
|
||||
|
||||
I bought a book earlier today. I am writing instead of reading it. The irony. I created this site to give me a space to write, and yet, often I post here merely things that I have read. The irony.
|
||||
|
||||
Thank you for coming to my website, and reading some of my thoughts. It really does mean a lot to me that you’re interested enough to read them. I pride myself on my thoughts, and if people weren’t interested in them
|
||||
|
||||
I’m tired.
|
||||
I’m tired of this. I want to do something interesting, something different. I want to do something different every day. I want to feel something.
|
||||
|
||||
This is an attempt to feel something.
|
||||
This is a safe space for me to vent.
|
||||
|
||||
This is the end of my sanity, of normalcy, of grammaer and ru;es and smaness.
|
||||
|
||||
This is poetry, this is all poetry, and if you haven’t figured that out by now, then I’m probably not that interested in listening to you, so you shouldn’t feel any obligation to listen to me. This is the poetry of a broken generation. Of a generation that is as broken as any that came before it, and yet feels the pain of brokenness just as strongly as any others.
|
||||
|
||||
Pray with me. “The Lord is close to the brokenhearted and saves those who are crushed in spirit.” The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me.
|
||||
|
||||
This website is a prayer.
|
||||
|
||||
This website is.
|
||||
|
||||
This website is an exercise in mental stimulation. This website stores Thoughts that I have, but it also encourage me to have thoughts in the first place.
|
||||
|
||||
This website is a part of who I am
|
||||
This is a representation of my mind.
|
||||
This is an influence on me
|
||||
|
||||
Soli Deo gloria
|
||||
|
||||
Thoughts posted here seem to fall into one of a few discrete categories. Firstly, analysises of topics, things that would be too long for Twitter, or else invite debate. Second, updates on my life and what I’m doing and feeling. Third, expressions, of feelings. All try to be informative, humours, and rehtorically enjoyable.
|
||||
|
||||
Poetry is text written to convey emotion. Text is written to convey emotion. This is text.
|
||||
|
||||
This is not text.
|
||||
|
||||
This is a fan page for the Breckenridge Jazz Hands.
|
||||
|
||||
I’m always hungry.
|
||||
|
||||
Please, don’t try to share anything here in other places. I just don’t imagine it going well. Although sharing this website as a whole I imagine would be beneficial.
|
||||
|
||||
I fear this is a creative writing exercise.
|
||||
|
||||
This is the end.
|
||||
|
||||
This is my last ditch effort. My guilty pleasure. My bad habit.
|
||||
|
||||
Good Night.
|
||||
|
||||
This is my late-night indulgence. Be sure to like and subscribe for more.
|
||||
|
||||
This is the result of not getting enough sleep.
|
||||
|
||||
This what I’m thinking.
|
||||
|
||||
These are my Thoughts.
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
{% extends "whispermaphone/page.html" %}
|
||||
|
||||
{% block title %}About{% endblock %}
|
||||
|
||||
{% block navigation %}
|
||||
<a href="/" class="text" style="border: none">Thoughts</a>
|
||||
<h1 class="text">About</h1>{% if authenticated %}
|
||||
<a href="/post" class="text" style="border: none">Post</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h1>Welcome!</h1>
|
||||
|
||||
<p class="text">
|
||||
This is a website that I created to document my thoughts. It addresses many of my gripes with similar platforms, like Twitter, Tumblr, Micro.blog, and Blogger, although it takes inspiration from all of these.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I’m Matthias. These are mostly my thoughts. Raw, un-distilled, thoughts. I do filter any thoughts that would be offensive or NSFW. Generally, with the exception of censored profanity, complex ideas, and links to other sites that are less principled, this site should be safe for your child to read.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This website is my own creation. It follows my own rules. It is dedicated to 3 things.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
First, of course, is thoughts. The mere act of creating ideas is good, and those ideas should be shared.<br>
|
||||
Second, is words. Words still have power.<br>
|
||||
Third, is design. Design is simplicity and functionality and form and appearance.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Welcome to a website, welcome to my website. Or someone’s website, at least.
|
||||
Websites are strange like that, you can never really tell who they belong to. Maybe this website belongs to you,
|
||||
for the amount of time that you’re here and you have it. Maybe it belongs to to no one, or all of us.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I am a very small amount of everyone. Infinitesimally small pieces of me belong to everyone,
|
||||
and I suppose that includes you, so I owe you at least something.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a website. It is not an academic paper, thesis, or dissertation.
|
||||
I do guarantee that all of the thoughts here are genuine—that is,
|
||||
I actually had them.
|
||||
However, I make no guarantees regarding their quality.
|
||||
Many of the thoughts here were had, posted, and then immediately discarded as clearly worthless.
|
||||
This is not disclaimed, I trust that you too can discern which of these thoughts are core values of my life
|
||||
and which are the merest passing fancy.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a website.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a breeding ground for ideas. The only criteria for something to be posted here is that I want it to be.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Here I work on answering the question. Everything posted here is an attempt to capture the liquid of life.
|
||||
Some moments are rocks, forcing you to hold on to them, weighing you down. Others are liquid, slipping out of your
|
||||
hands despite your best attempts. I suspect we are all frantically searching for a way to hold on
|
||||
to the things that are rapidly slipping away from our grasp.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a blog. In much the sense that Tumblr is a blogging platform,
|
||||
so too is this website.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
If you realize that you’re dying, they only thing to do is turn back toward your childhood.
|
||||
Oh so many people don’t realize, or realize too late. I saw a man getting a transfusion of blood.
|
||||
Though it was necessary to keep him alive, the pain of having it injected into him incapacitated him.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is WhisperMaPhone. It’s a tool I created to share my thoughts. It is currently closed source,
|
||||
as it is still in active development. However, if you’re interested in setting up a page similar to this,
|
||||
let me know.
|
||||
</p>
|
||||
|
||||
<p class="text quote">
|
||||
“Our brains are dulled by the incurable mania of wanting to make the unknown known, classifiable.
|
||||
The desire for analysis wins out over the sentiments.”
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is the void. There’s no way for you to contact me on here.
|
||||
There are no guarantees when you’re shouting into the void. But the void doesn’t normally shout back.
|
||||
No one ever said the void wasn’t listening. You’re listening. This is my void.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I use single quotes to indicate that I’m paraphrasing, and double-quotes to indicate a direct quotation from someone else.
|
||||
Sometimes I will not cite my source, because the origin is less important that the words.
|
||||
Sometimes I don’t remember the source.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
The portion of this website which is hidden from prying eyes is written in Python, with Django.
|
||||
Although it is visible to a couple of eyes on account of their not-prying. Everyone looks but only
|
||||
few find. Still, you cannot find if you do not look, although whether you are more or less prepared
|
||||
to understand what you find is a different point entirely.
|
||||
</p>
|
||||
|
||||
<p class="text quote">
|
||||
“In this day and age logical
|
||||
methods are applicable only to solving problems of secondary
|
||||
interest”
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
It has recently come to my attention that there is some confusion regarding the purpose of this website,
|
||||
or even, what it is. This page should answer both of those questions.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I can only pray you understand what I give you, but likely you will not. They never did understand the gifts they
|
||||
were given.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a surrender and a revel. This is an experiment.
|
||||
This is a rejection of social media and an embrace of it.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a circus inside a clown factory. This is a bomb.
|
||||
</p>
|
||||
|
||||
<em class="text">
|
||||
All thoughts are provided with a timestamp in the timezone of the poster, to ensure proper contextualization.
|
||||
Contextualization is of utmost importance.
|
||||
</em>
|
||||
|
||||
<p class="text" style="padding-left: calc(100% - 200px)">
|
||||
This is different. This is the same.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I’m Matthias. (most of the time)
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a Russian bot attempting to manipulate the election process.
|
||||
Posts here represent a subset of the result of training a GPT-3 text generation algorithm on my brain.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a result of not even knowing what alcohol tastes like.
|
||||
This is fire from the past. This is my first most prized possession.
|
||||
This is <span style="text-decoration: line-through">Researcher Talloran</span>.
|
||||
</p>
|
||||
|
||||
<em class="text">
|
||||
All thoughts are provided completely free of context.
|
||||
</em>
|
||||
|
||||
<p class="text">
|
||||
Gödel has disproved logic. The imperfections of the human mind are inescapable. There are perhaps
|
||||
corners of that 2D plane of logic that remain unexplored, but by logic alone that plane will never be fully
|
||||
explored, so those corners do not interest us. No, let us think in 3D.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is an ebenezer to the Lord and the things He has done for me.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I’m hungry.
|
||||
</p>
|
||||
|
||||
<p class="text">Hey there, it’s Nate.</p>
|
||||
|
||||
<p class="text">
|
||||
This website is an outlet for the many intrusive thoughts that I have, that I cannot speak out loud. These thoughts don’t have meaning, they aren’t important. Even if it were socially acceptable for me to share them at random times, even if people were interested, they’re still not of any particular value. On the other hand, here they can be themselves. They can exist free from judgement. You, and me, come here to examine the curious thoughts of a curious boy. Here my thoughts can thrive.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is the real track four.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a story about a boy.<br>
|
||||
People don’t like things that are different.<br>
|
||||
The world is confusing.<br>
|
||||
This is a cult.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is an explanation. This page answers any questions you may have about this website.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Welcome to my website! This is a micro-blogging platform for me to share my thoughts.
|
||||
This website breaks normal blogging and micro-blogging paradigms. I have 140 character limit on the first line,
|
||||
for real quick thoughts, and then a Show More button, and then unlimited characters in a second field.
|
||||
I’ll sometimes use hundreds of characters, writing mini-essays on topics that catch my interest.
|
||||
This would be very impractical to do on Twitter. Although this websites solves another gripe with Twitter,
|
||||
the concept of “liking” Tweets. I don’t ask for your feedback here. This frees me to post what I’m inspired to,
|
||||
and allows you to experience what I post without worrying about if you should “like” it.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is how I learned to stop worrying. This is a flea in which our two bloods mingled be.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
All thoughts posted here are those of my employer.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is the end and a new beginning. I’ve stopped using Discord, Twitter, Instagram, and other social media
|
||||
since creating this website.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I open vim to write. I don’t have a spell checker, so I struggle, catching my own errors by sight, recognizeing that the word looks wrong, but not knowing how to fix it. I shrug. I’ll go over it with a spell-check at some point. But I might not. I might impuse-publish this page, one night, when I’m feeling particularly lonely. Then the message that I dread (“you spelled ‘recognizeing’ wrong”) might come as a welcome social interaction, and an opprotunity to improve my website. Rather than an annoyance.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is not a test. This is considered harmful. This is mostly harmless.
|
||||
Fear the daemons you have created at JOKEEFUNNY.COM.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is art. This is poetry.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a revolution of the proletariat. We will not sit quietly.<br>
|
||||
This is sitting loudly. This website is inaction. It exists only to let me complain.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I find it helps to imagine all of my posts here as the murmurings of someone talking in their sleep.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I bought a book earlier today. I am writing instead of reading it. The irony. I created this site to give me a space to write, and yet, often I post here merely things that I have read. The irony.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Thank you for coming to my website, and reading some of my thoughts.
|
||||
It really does mean a lot to me that you’re interested enough to read them.
|
||||
I pride myself on my thoughts, and if people weren’t interested in them
|
||||
|
||||
<p class="text">
|
||||
I’m tired.<br>
|
||||
I’m tired of this. I want to do something interesting, something different. I want to do something different every day.
|
||||
I want to feel something.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is an attempt to feel something.<br>
|
||||
This is a safe space for me to vent.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is the end of my sanity, of normalcy, of grammaer and ru;es and smaness.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is poetry, this is all poetry, and if you haven’t figured that out by now, then I’m probably not that interested in listening to you, so you shouldn’t feel any obligation to listen to me. This is the poetry of a broken generation. Of a generation that is as broken as any that came before it, and yet feels the pain of brokenness just as strongly as any others.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Pray with me. <em>The Lord is close to the brokenhearted and saves those who are crushed in spirit.</em> The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me. The LORD is close to me.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This website is a prayer.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This website is.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This website is an exercise in mental stimulation.
|
||||
This website stores Thoughts that I have, but it also encourage me to have thoughts in the first place.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This website is a part of who I am<br>
|
||||
This is a representation of my mind.<br>
|
||||
This is an influence on me<br>
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Soli Deo gloria
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Thoughts posted here seem to fall into one of a few discrete categories. Firstly, analysises of topics, things that would be too long for Twitter, or else invite debate. Second, updates on my life and what I’m doing and feeling. Third, expressions, of feelings. All try to be informative, humours, and rehtorically enjoyable.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Poetry is text written to convey emotion. Text is written to convey emotion. This is text.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is not text.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is a fan page for the Breckenridge Jazz Hands.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I’m always hungry.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Please, don’t try to share anything here in other places. I just don’t imagine it going well. Although sharing this website as a whole I imagine would be beneficial.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
I fear this is a creative writing exercise.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is the end.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is my last ditch effort. My guilty pleasure. My bad habit.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
Good Night.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is my late-night indulgence. Be sure to like and subscribe for more.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This is the result of not getting enough sleep.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
This what I’m thinking.
|
||||
</p>
|
||||
|
||||
<p class="text">
|
||||
These are my Thoughts.
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
|||
=> /about About
|
||||
# Thoughts
|
||||
{% for thought in thoughts %}{{ thought.text|safe }}
|
||||
{% if thought.extended_text %}
|
||||
{{ thought.extended_text|safe }}
|
||||
{% endif %}{% if thought.media %}
|
||||
=> gemini://thoughts.learnerpages.com{{ thought.media.url }}{% endif %}{% load tz %}
|
||||
```
|
||||
{% timezone thought.get_timezone %}{{ thought.posted|time:"g:i a" }} {{ thought.posted|date:"M d, Y" }}, UTC{{ thought.get_offset_hours }}{% endtimezone %}
|
||||
```
|
||||
▔▔▔
|
||||
{% endfor %}
|
|
@ -1,85 +1,118 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Thoughts</title>
|
||||
{% extends "whispermaphone/page.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load static %}
|
||||
<link href="{% static 'main/main.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'main/codehighlight.css' %}" rel="stylesheet">
|
||||
{% block title %}Thoughts{% endblock %}
|
||||
|
||||
<link rel="icon" sizes="192x192" href="{% static 'images/favicon-192x192.png'%}">
|
||||
<link rel="apple-touch-icon" href="{% static 'images/apple-touch-icon.png'%}"/>
|
||||
{% block navigation %}
|
||||
<h1 class="text" aria-current="page">Thoughts</h1>
|
||||
<a href="/about" class="text">About</a>{% if authenticated %}
|
||||
<a href="/post" class="text" style="border: none">Post</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<link rel="alternate" href="/feed" type="application/rss+xml" title="RSS">
|
||||
{% block head %}
|
||||
<link rel="alternate" href="/feed" type="application/rss+xml" title="RSS">
|
||||
|
||||
<style>
|
||||
/*Semantic styles load as part of the page*/
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<span class="text">Thoughts</span>{% if authenticated %}
|
||||
<a href="/post" class="text" style="border: none">Post</a>
|
||||
{% endif %}</h1>
|
||||
</header>
|
||||
<link href="{% static 'main/codehighlight.css' %}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if not first_page %}
|
||||
<nav class="history-nav top" aria-label="History Navigation">
|
||||
<ul>
|
||||
{% for page in pages %}
|
||||
{% if page.slug == current_page %}
|
||||
<li><span class="current-page">{{ page.formatted_name }}</span></li>
|
||||
{% else %}
|
||||
<li><a href="?page={{ page.slug }}">{{ page.formatted_name }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<section class="main-wrap">
|
||||
{% load tz %}
|
||||
{% load markdown %}
|
||||
|
||||
{% for thought in thoughts %}
|
||||
<div class="thought{% if thought.uuid == highlighted %} highlighted{% endif %}" id="{{thought.uuid}}">
|
||||
<div class="thought{% if thought.uuid == highlighted %} highlighted{% endif %}" id="{{ thought.uuid }}">
|
||||
<div class="main">
|
||||
<span class="main-text text">{{ thought.text|markdown }}</span>
|
||||
</div>
|
||||
<div class="extended-text text">{{ thought.extended_text|markdown }}</div>
|
||||
|
||||
{% with file_type=thought.get_media_type %}
|
||||
{% if file_type or thought.extended_text.strip %}
|
||||
<div class="extended">
|
||||
{% if thought.extended_text.strip %}
|
||||
<span class="extended-text text">{{ thought.extended_text|markdown }}</span>
|
||||
{% endif %}
|
||||
{% if file_type == "png" or file_type == "jpeg" %}
|
||||
<img src="{{ thought.media.url }}" class="extended-media" alt="{{ thought.media_alt }}">
|
||||
{% elif file_type == "m4a" or file_type == "mp3" or file_type == "aac" %}
|
||||
<audio controls src="{{ thought.media.url }}" class="extended-media"></audio>
|
||||
{% elif file_type == "mov" or file_type == "mp4" %}
|
||||
<video src="{{ thought.media.url }}" class="extended-media"></video>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="thought-end">
|
||||
<span class="timestamp">
|
||||
{% timezone thought.timezone %}
|
||||
{% timezone thought.get_timezone %}
|
||||
{{ thought.posted|time:"g:i a" }}
|
||||
{{ thought.posted|date:"M d, Y" }},
|
||||
|
||||
UTC{{ thought.offset_hours }}
|
||||
UTC{{ thought.get_offset_hours }}
|
||||
{{ thought.get_season }}
|
||||
{% endtimezone %}
|
||||
</span>
|
||||
<span class="permalink">
|
||||
<a class="button" href="/?show={{thought.uuid}}">Permalink</a>
|
||||
</span>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
</section>
|
||||
|
||||
<!-- <footer>Copyright Matthias @2020{% if authenticated %}-->
|
||||
<!-- <a href="/post" style="color: var(--text-color); margin-left: 100px; text-decoration: none">POST</a>-->
|
||||
<!-- {% endif %}</footer>-->
|
||||
{% block footer %}
|
||||
<nav class="history-nav bottom" aria-label="History Navigation">
|
||||
<ul>
|
||||
{% for page in pages %}
|
||||
{% if page.slug == current_page %}
|
||||
<li><span class="current-page">{{ page.formatted_name }}</span></li>
|
||||
{% else %}
|
||||
<li><a href="?page={{ page.slug }}">{{ page.formatted_name }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const els = document.querySelectorAll(".thought");
|
||||
|
||||
for (let el of els) {
|
||||
const extended = el.querySelector(".extended-text");
|
||||
const extended = el.querySelector(".extended");
|
||||
|
||||
//Hide extended text
|
||||
extended.classList.add("hidden");
|
||||
//Add button to show extended text
|
||||
if (extended.textContent.length) {
|
||||
const main = el.querySelector(".main");
|
||||
const showMoreButton = document.createElement("button");
|
||||
showMoreButton.appendChild(document.createTextNode("Show More"));
|
||||
showMoreButton.classList.add("show-more");
|
||||
showMoreButton.addEventListener("click", evt => {
|
||||
// Remove ourself
|
||||
showMoreButton.parentNode.removeChild(showMoreButton);
|
||||
// Show the extended text
|
||||
extended.classList.remove("hidden");
|
||||
})
|
||||
main.appendChild(showMoreButton);
|
||||
if (extended) {
|
||||
//Hide extended text
|
||||
extended.classList.add("hidden");
|
||||
//Add button to show extended text
|
||||
if (extended.childNodes.length) {
|
||||
const main = el.querySelector(".main");
|
||||
const showMoreButton = document.createElement("button");
|
||||
showMoreButton.appendChild(document.createTextNode("Show More"));
|
||||
showMoreButton.classList.add("show-more");
|
||||
showMoreButton.addEventListener("click", evt => {
|
||||
// Remove ourself
|
||||
showMoreButton.parentNode.removeChild(showMoreButton);
|
||||
// Show the extended text
|
||||
extended.classList.remove("hidden");
|
||||
})
|
||||
main.appendChild(showMoreButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,20 +150,27 @@
|
|||
}
|
||||
}).use(remarkable.linkify);
|
||||
|
||||
// Allow data: URIs for images, from https://github.com/jonschlinkert/remarkable/issues/329
|
||||
const originalLinkValidator = md.inline.validateLink;
|
||||
const dataLinkRegex = /^\s*data:([a-z]+\/[a-z]+(;[a-z-]+=[a-z-]+)?)?(;base64)?,[a-z0-9!$&'',()*+,;=\-._~:@/?%\s]*\s*$/i;
|
||||
|
||||
md.inline.validateLink = (url) => originalLinkValidator(url) || url.match(dataLinkRegex);
|
||||
// I'm leaving in "references", which handles the convention of putting your links at the bottom of your post
|
||||
md.core.ruler.disable(["abbr", "abbr2", "footnote_tail", "replacements"]);
|
||||
md.block.ruler.disable(["hr", "footnote", "heading", "lheading", "table", "htmlblock"]);
|
||||
md.inline.ruler.disable(["del", "ins", "mark", "sub", "sup", "footnote_inline", "footnote_ref", "htmltag", "entity", "autolink"]);
|
||||
|
||||
//Images are parsed as inline links, so we can't turn off parsing,
|
||||
//but we can overwrite the renderer so they never get displayed. Not ideal
|
||||
/*md.renderer.rules.image = function () {
|
||||
return "";
|
||||
};*/
|
||||
/*
|
||||
for (let el of els) {
|
||||
const extended = el.querySelector(".extended-text");
|
||||
const extendedText = el.querySelector(".extended-text");
|
||||
const mainText = el.querySelector(".main-text");
|
||||
|
||||
//Markdown + highlight
|
||||
extended.innerHTML = md.render(extended.textContent);
|
||||
mainText.innerHTML = md.render(mainText.textContent);
|
||||
}*/
|
||||
if (extendedText) {
|
||||
extendedText.innerHTML = md.render(extendedText.textContent);
|
||||
}
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Login</title>
|
||||
{% extends "whispermaphone/page.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load static %}
|
||||
<link href="{% static 'main/login.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'main/main.css' %}" rel="stylesheet">
|
||||
{% block title %}Post{% endblock %}
|
||||
|
||||
<link rel="icon" sizes="192x192" href="{% static 'images/favicon-192x192.png'%}">
|
||||
<link rel="apple-touch-icon" href="{% static 'images/apple-touch-icon.png'%}"/>
|
||||
</head>
|
||||
<body>
|
||||
<section class="main-content">
|
||||
{% block head %}
|
||||
<link href="{% static 'main/login.css' %}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block navigation %}
|
||||
<a href="/" class="text" style="border: none">Thoughts</a>
|
||||
<h1 class="text">Login</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<span class="text">
|
||||
Please enter the password to access this page.
|
||||
</span>
|
||||
|
@ -22,14 +21,13 @@
|
|||
<input type="password" id="password">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.getElementById("password-form").addEventListener("submit", evt => {
|
||||
document.cookie = `password=${document.getElementById("password").value}; max-age=15768000; samesite=strict`;
|
||||
window.location.reload();
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
|
||||
{% load static %}
|
||||
<link href="{% static 'main/main.css' %}" rel="stylesheet">
|
||||
{% block head %}{% endblock %}
|
||||
|
||||
<link rel="icon" sizes="192x192" href="{% static 'images/favicon-192x192.png'%}">
|
||||
<link rel="apple-touch-icon" href="{% static 'images/apple-touch-icon.png'%}"/>
|
||||
|
||||
<style>
|
||||
/*Semantic styles load as part of the page*/
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav id="main-nav">
|
||||
{% block navigation %}{% endblock %}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="main-wrap" id="main-content">
|
||||
{% block main %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
{% block footer %}
|
||||
{% endblock %}
|
||||
</footer>
|
||||
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,41 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Post</title>
|
||||
{% extends "whispermaphone/page.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load static %}
|
||||
<link href="{% static 'main/post.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'main/main.css' %}" rel="stylesheet">
|
||||
{% block title %}Post{% endblock %}
|
||||
|
||||
<link rel="icon" sizes="192x192" href="{% static 'images/favicon-192x192.png'%}">
|
||||
<link rel="apple-touch-icon" href="{% static 'images/apple-touch-icon.png'%}"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/" class="text" style="border: none">Thoughts</a>
|
||||
<span class="text">Post</span>
|
||||
</h1>
|
||||
</header>
|
||||
{% block head %}
|
||||
<link href="{% static 'main/post.css' %}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
<section class="main-wrap">
|
||||
<form action="{% url 'post'%}" method="post">
|
||||
{% csrf_token %}
|
||||
<textarea name="text" id="text" class="thought" rows="1" placeholder="What are you thinking?"></textarea>
|
||||
<textarea name="extended_text" id="extended_text" class="thought" rows="2" placeholder="Anything else?"></textarea>
|
||||
<input type="hidden" name="timezone_offset" id="timezone_offset">
|
||||
{% block navigation %}
|
||||
<a href="/" class="text" style="border: none">Thoughts</a>
|
||||
<a href="/about" class="text" style="border: none">About</a>
|
||||
<h1 class="text">Post</h1>
|
||||
{% endblock %}
|
||||
|
||||
<input type="submit" id="post-button" value="Submit">
|
||||
</form>
|
||||
</section>
|
||||
{% block main %}
|
||||
<form action="{% url 'post'%}" method="post" enctype="multipart/form-data" id="post-form">
|
||||
<div class="error">{{ form_error }}</div>
|
||||
|
||||
{% csrf_token %}
|
||||
{{ form.text }}
|
||||
{{ form.extended_text }}
|
||||
|
||||
{{ form.timezone_offset }}
|
||||
|
||||
<div class="media-wrapper">
|
||||
{{ form.media }}
|
||||
{{ form.media_alt }}
|
||||
</div>
|
||||
|
||||
<input type="submit" id="post-button" value="Submit">
|
||||
{{ form.submit }}
|
||||
</form>
|
||||
|
||||
<!-- {{ form }} -->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const textEl = document.getElementById("text");
|
||||
const textExtEl = document.getElementById("extended_text");
|
||||
const textEl = document.getElementById("id_text");
|
||||
const textExtEl = document.getElementById("id_extended_text");
|
||||
|
||||
function updateBoxHeight(box) {
|
||||
//style.height doesn't include padding, .scrollHeight does
|
||||
box.style.height = "auto";
|
||||
box.style.height = box.scrollHeight + "px";
|
||||
}
|
||||
|
||||
// If we're handling maxlength in JS, then having the maxlength attribute is annoying
|
||||
// We could (should?) also leave the property on there and then add a keydown
|
||||
// listener when you're at 140 characters, but this is easier
|
||||
textEl.removeAttribute("maxlength");
|
||||
textEl.addEventListener("input", evt => {
|
||||
// If the length is more than 120
|
||||
// If the length is more than 140
|
||||
const value = textEl.value;
|
||||
if (value.length > 140) {
|
||||
const splitAt = value.lastIndexOf(" ", 140) + 1; // Plus 1 keeps the space in the original text instead of moving it
|
||||
|
@ -61,46 +78,50 @@
|
|||
textEl.selectionStart = startPos;
|
||||
textEl.selectionEnd = endPos;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Auto-set width of short text box
|
||||
//textEl.style.width = "auto";
|
||||
//textEl.style.width = textEl.scrollWidth + "px";
|
||||
});
|
||||
|
||||
//Allow pasting images into the extended text text-box
|
||||
textExtEl.addEventListener("paste", (evt) => {
|
||||
const items = evt.clipboardData.items;
|
||||
const el = evt.target;
|
||||
for (const item of items) {
|
||||
//Mac clipboard at least only handles PNGs
|
||||
if ("image/png" === item.type) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("load", evt => {
|
||||
const newText = `![](${evt.target.result})`;
|
||||
el.value = el.value.slice(0, el.selectionStart) + newText + el.value.slice(el.selectionEnd);
|
||||
});
|
||||
reader.readAsDataURL(item.getAsFile());
|
||||
}
|
||||
//Resize both text boxes
|
||||
updateBoxHeight(textExtEl);
|
||||
updateBoxHeight(textEl);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const mediaInput = document.getElementById("id_media");
|
||||
const mediaAlt = document.getElementById("id_media_alt");
|
||||
|
||||
function updateMediaAlt() {
|
||||
if (mediaInput.value) {
|
||||
mediaAlt.classList.remove("hidden");
|
||||
}else {
|
||||
mediaAlt.classList.add("hidden");
|
||||
}
|
||||
updateBoxHeight(mediaAlt);
|
||||
}
|
||||
mediaAlt.addEventListener("input", () => {
|
||||
updateBoxHeight(mediaAlt);
|
||||
});
|
||||
mediaInput.addEventListener("input", updateMediaAlt);
|
||||
updateMediaAlt();
|
||||
|
||||
const textboxes = document.getElementsByClassName("thought");
|
||||
for (const box of textboxes) {
|
||||
//style.height doesn't include padding, .scrollHeight does
|
||||
//This is fine as long as we reset scrollHeight before changing it
|
||||
//And we have to set it this first time
|
||||
//This also effectively doubles the padding value we set in CSS
|
||||
box.style.height = box.scrollHeight + "px";
|
||||
box.addEventListener("input", e => {
|
||||
box.style.height = "auto";
|
||||
box.style.height = box.scrollHeight + "px";
|
||||
// Set the initial heights of the boxes
|
||||
updateBoxHeight(box);
|
||||
box.addEventListener("input", evt => updateBoxHeight(box));
|
||||
|
||||
// Allow pasting images into either text box
|
||||
box.addEventListener("paste", evt => {
|
||||
const files = evt.clipboardData.files;
|
||||
// We can only use the first image from the pasteboard
|
||||
if (files && files.length > 0) {
|
||||
// This seems to only allow entry of a single file
|
||||
mediaInput.files = files;
|
||||
updateMediaAlt();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const timezoneOffsetEl = document.getElementById("timezone_offset");
|
||||
const timezoneOffsetEl = document.getElementById("id_timezone_offset");
|
||||
timezoneOffsetEl.value = (new Date()).getTimezoneOffset();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
|
118
main/views.py
118
main/views.py
|
@ -1,61 +1,121 @@
|
|||
import os.path
|
||||
import uuid
|
||||
import subprocess
|
||||
|
||||
import magic
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import constant_time_compare
|
||||
|
||||
from .models import Thought
|
||||
from whispermaphone import settings
|
||||
from .models import Thought, ThoughtForm, ALLOWED_MEDIA_TYPES
|
||||
|
||||
from .pagination import get_all_pages, get_page_slug
|
||||
|
||||
def index(request):
|
||||
def check_authenticated(request):
|
||||
authenticated = False
|
||||
try:
|
||||
if request.COOKIES["password"] == "ChromaticWave":
|
||||
if constant_time_compare(request.COOKIES["password"], settings.PASSWORD):
|
||||
authenticated = True
|
||||
except KeyError:
|
||||
pass
|
||||
return authenticated
|
||||
|
||||
|
||||
def index(request):
|
||||
authenticated = check_authenticated(request)
|
||||
|
||||
try:
|
||||
highlighted_uuid = uuid.UUID(request.GET.get("show", ""))
|
||||
except ValueError:
|
||||
highlighted_uuid = ""
|
||||
|
||||
thoughts = Thought.objects.order_by("-posted")
|
||||
# Figure out what page we're viewing
|
||||
pages = get_all_pages()
|
||||
|
||||
for thought in thoughts:
|
||||
thought.timezone = timezone.get_fixed_timezone(-thought.timezone_offset)
|
||||
offset_hours = -thought.timezone_offset / 60
|
||||
if offset_hours == int(offset_hours):
|
||||
offset_hours = int(offset_hours)
|
||||
if offset_hours > 0:
|
||||
offset_hours = "+" + str(offset_hours)
|
||||
thought.offset_hours = offset_hours
|
||||
# First item in pages should be listed first
|
||||
requested_page = pages[0]
|
||||
requested_slug = request.GET.get("page", default=requested_page.slug)
|
||||
|
||||
# show=uuid takes priority over page
|
||||
if highlighted_uuid:
|
||||
try:
|
||||
highlighted_thought = Thought.objects.get(uuid=highlighted_uuid)
|
||||
requested_slug = get_page_slug(highlighted_thought)
|
||||
except Thought.DoesNotExist:
|
||||
pass
|
||||
|
||||
if requested_page.slug != requested_slug:
|
||||
for p in pages:
|
||||
if p.slug == requested_slug:
|
||||
requested_page = p
|
||||
|
||||
thoughts = requested_page.get_all_entries()
|
||||
|
||||
return render(request, "whispermaphone/index.html", {
|
||||
"thoughts": thoughts,
|
||||
"highlighted": highlighted_uuid,
|
||||
"authenticated": authenticated
|
||||
"authenticated": authenticated,
|
||||
"pages": pages,
|
||||
"current_page": requested_slug,
|
||||
"first_page": requested_page == pages[0] # if you're viewing the first page
|
||||
})
|
||||
|
||||
|
||||
def post(request):
|
||||
try:
|
||||
if not request.COOKIES["password"] == "ChromaticWave":
|
||||
return render(request, "whispermaphone/login.html", status=401)
|
||||
except KeyError:
|
||||
if not check_authenticated(request):
|
||||
return render(request, "whispermaphone/login.html", status=401)
|
||||
|
||||
if request.method == "POST":
|
||||
if len(request.POST["text"].strip()) == 0:
|
||||
return HttpResponse("Need some text.", status=400)
|
||||
thought_form = ThoughtForm(request.POST, request.FILES, instance=Thought())
|
||||
if not thought_form.is_valid():
|
||||
errors = thought_form.errors.as_data()
|
||||
# Media formatting errors
|
||||
try:
|
||||
problem_field = list(errors.keys())[0]
|
||||
message = list(errors.values())[0][0].messages[0]
|
||||
# error_line = f"{problem_field[0].upper()}{problem_field[1:]}: {message}"
|
||||
error_line = f"{message}"
|
||||
except:
|
||||
error_line = f"An unknown error occurred processing your request: {errors}"
|
||||
|
||||
if len(request.POST["text"]) > 140:
|
||||
return HttpResponse("Content too long", status=400)
|
||||
return render(request, "whispermaphone/post.html", {
|
||||
"form": thought_form,
|
||||
"form_error": error_line
|
||||
}, status=400)
|
||||
|
||||
Thought(
|
||||
text=request.POST["text"],
|
||||
extended_text=request.POST["extended_text"],
|
||||
timezone_offset=request.POST["timezone_offset"]
|
||||
).save()
|
||||
# Create a thought object we can work with
|
||||
# But don't save it the DB yet
|
||||
thought = thought_form.save(commit=False)
|
||||
|
||||
return render(request, "whispermaphone/post.html", {})
|
||||
# Do media processing (already validated)
|
||||
if "media" in request.FILES:
|
||||
chunk = next(thought.media.chunks(chunk_size=2048))
|
||||
media_type = magic.from_buffer(chunk, mime="True")
|
||||
|
||||
thought.media.name = f"{thought.uuid}.{ALLOWED_MEDIA_TYPES[media_type]}"
|
||||
|
||||
if media_type == "audio/x-m4a":
|
||||
# This is a hack-fix because I want to be able to upload audio
|
||||
# In the future, this should be refactored to convert file types
|
||||
# using ffmpeg.js on the client side (so there are 0 security concerns)
|
||||
# and then the backend just has to check a 3 item whitelist
|
||||
thought.save() # Save so that we have a file to work with
|
||||
subprocess.run(["ffmpeg",
|
||||
"-i", thought.media.path,
|
||||
"-codec:a", "aac", "-vn",
|
||||
os.path.join(settings.MEDIA_ROOT, f"{thought.uuid}.aac")
|
||||
], check=True)
|
||||
os.remove(os.path.join(settings.MEDIA_ROOT, f"{thought.uuid}.m4a")) # Remove the original file
|
||||
thought.media.name = f"{thought.uuid}.aac" # Update the file in the DB
|
||||
|
||||
# Save for real
|
||||
thought.save()
|
||||
|
||||
return render(request, "whispermaphone/post.html", {"form": ThoughtForm()})
|
||||
|
||||
|
||||
def about(request):
|
||||
authenticated = check_authenticated(request)
|
||||
|
||||
return render(request, "whispermaphone/about.html", {"authenticated": authenticated})
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
bleach==3.3.0
|
||||
Django==3.1.7
|
||||
Markdown==3.3.4
|
||||
Django~=3.2
|
||||
python-magic~=0.4.22
|
||||
bleach~=3.3.0
|
||||
Pygments~=2.8.1
|
||||
Markdown~=3.3.4
|
||||
jetforce
|
||||
python-decouple
|
||||
|
|
|
@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
|
|||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from decouple import config
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
@ -20,14 +21,16 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'qdm4_0b)3^)k$6r($!o^a7&0l#^6)@g2wr!x0r40ii@9otfnwo'
|
||||
SECRET_KEY = config("SECRET_KEY", default="qdm4_0b)3^)k$6r($!o^a7&0l#^6)@g2wr!x0r40ii@9otfnwo")
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
DEBUG = config("DEBUG", default=True, cast=bool)
|
||||
|
||||
PASSWORD = config("PASSWORD", default="password")
|
||||
|
||||
ALLOWED_HOSTS = ["thoughts.learnerpages.com"]
|
||||
if DEBUG:
|
||||
ALLOWED_HOSTS += ["*"]
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
@ -45,6 +48,8 @@ MIDDLEWARE = [
|
|||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
CSRF_COOKIE_SECURE = True
|
||||
|
||||
ROOT_URLCONF = 'whispermaphone.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
|
@ -74,6 +79,7 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||
|
@ -95,3 +101,6 @@ USE_TZ = True
|
|||
STATIC_URL = '/static/'
|
||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
STATIC_ROOT = BASE_DIR / "static/"
|
||||
|
||||
MEDIA_ROOT = BASE_DIR / "media/"
|
||||
MEDIA_URL = "/media/"
|
||||
|
|
|
@ -1,25 +1,13 @@
|
|||
"""whispermaphone URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/3.1/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import path
|
||||
|
||||
from main import views
|
||||
from main.feed import MainFeed
|
||||
from whispermaphone import settings
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.index, name="index"),
|
||||
path("about", views.about, name="about"),
|
||||
path("post", views.post, name="post"),
|
||||
path("feed", MainFeed())
|
||||
]
|
||||
path("feed", MainFeed()),
|
||||
] + (static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG else [])
|
||||
|
|
Loading…
Reference in New Issue