diff --git a/README.md b/README.md
index d5b6cdc..edac7ff 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,37 @@ In action on my website: [deadvey.com](https://deadvey.com)
* probably scales like shit
* probably insecure as hell
-# planned features
+# planned features/todo list
* atom
-* federation
+* federation (looks tricky)
* sign up
* All strings (including in edit and post page) customisable
+* split code into files to tidy it up a bit
+* inline comments and docs
+* give each post a hard postID to prevent potential issues
+* clean up code a bit
+
+# format indicators
+* %% - A literal %
+* %A - List of tags
+* %B - List of tags, each one with a hyperlink to that tag page
+* %C - Post content
+* %D - Published date in the format specified by date_format
+* %E - Edited date in the format specified by date_format
+* %F - Pretty name
+* %G - Tag name (used for the tag page only)
+* %H - Frontpage hit count
+* %I - User description
+* %L - URL Permanent link to the post
+* %M - comments
+* %N - the username of the user (poster)
+* %P - URL to create a new post
+* %O - URL to edit this post
+* %R - Site wide RSS feed
+* %S - post seperator as defined by post_seperator
+* %T - Title
+* %U - URL the the user (poster)
+* %W - Site Description as defined by site_description
+* %X - Comment submission box
+* %Y - Site Name as defined by site_name
+* %Z - Attribution (to me) and source code link and license
diff --git a/app.js b/app.js
index 8f799e5..45d22ce 100755
--- a/app.js
+++ b/app.js
@@ -1,46 +1,64 @@
-const fs = require('fs');
-const express = require('express');
-const showdown = require('showdown')
-const crypto = require('crypto'); // For encrypting passwords
+// Get the libraries
+const fs = require('fs'); // For modifying and reading files
+const express = require('express'); // For running a webserver in nodejs
+const showdown = require('showdown') // For converting markdown to html on demand, https://showdownjs.com/
+const crypto = require('crypto'); // For encrypting passwords, I use sha512
+// fromUnixTime creates a date object out of an integer
+// format is used to format a date object into a defined format eg yyyy-MM-dd
+// getUnixTime converts a date object into an integer (unix time)
+// find out more at https://date-fns.org/ \or docs: https://date-fns.org/docs/Getting-Started
const { fromUnixTime, format, getUnixTime } = require("date-fns")
+// There's only one possible argument, so we can just check if the user passed that one
+// TODO I plan on adding more such as --help and --post so I should make this more robust at some point
if (process.argv[2] == "--first-time") {
- initialise()
+ initialise() // Creates any files such users.js, posts.js, comments.js or config.js if they are not present
}
-let users
-let posts
+// Define the modules now so they are global
+let users // contains a list of users, each user is an object containing username,prettyname,hash and description
+let posts // contains a list of posts,
let comments
let config
try {
- users = require('./users.js');
+ // We're going to try and import the modules, users = require('./users.js');
posts = require('./posts.js');
comments = require('./comments.js');
config = require('./config.js');
}
catch (error) {
+ // if they don't all import then
+ // inform the user to pass --first-time and exit with an error code
console.log("A file is missing!")
console.log("Run with --first-time to initialise the program")
console.log(error)
process.exit(1)
}
-let converter = new showdown.Converter({
- simpleLineBreaks: true,
- tables: true,
- strikethrough: true,
- tasklists: true,
- encodeEmails: true
-})
-const app = express();
+// https://showdownjs.com/docs/available-options
+let converter = new showdown.Converter({
+ simpleLineBreaks: true, // Parse line breaks as in paragraphs (GitHub-style behavior).
+ tables: true, // Enable support for tables syntax.
+ strikethrough: true, // Enable support for strikethrough: ~~text~~
+ tasklists: true, // Enable support for GitHub style tasklists. - [x] and - [ ]
+ encodeEmails: true //Enable automatic obfuscation of email addresses. emails are encoded via character entities
+})
+
+// The footer div is globale because it's a site wide, so define it here
let footer_div = config.site_wide_footer
footer_div = replace_format_indicators(footer_div)
+// Define stuff to do with express (nodejs webserver)
+const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static(config.root_path));
+// Initialise the program by creating users.js, comments.js, posts.js and config.js
+// All require default content in them to start off with
+// Then exit successfully
+// returns nothing
function initialise() {
try {
const users = require("./users.js");
@@ -73,18 +91,28 @@ function initialise() {
console.log("Error copying file")
})
}
+ console.log("Successfully initialised")
process.exit(0)
}
+// The users are stored as a list of objects [ user_object, user_object, user_object ]
+// So you cannot easily find the userID (position in list) from the username
+// This function returns the username for a given userID by looping over every user
+// if the user is present, it returns the index of the user (integer)
+// if the user is not present it returns -1
function get_userID(username) {
- for (let i = 0; i < users.users.length; i++) {
+ for (let i = 0; i < users.users.length; i++) { // Loop over every user
if (users.users[i]['username'] == username) {
- return i
+ return i // If the username matches then return the index of that user
}
}
- return -1
+ return -1 // If user is not present, return -1
}
+// The configuration defines a date format using the date-fns syntax
+// this converts unix time (an integer) into a string that is formatted according to config.js
+// uses date-fns's fromUnixTime() and format()
+// returns the formatted date (string)
function unix_time_to_date_format(unix_time) {
date = fromUnixTime(unix_time)
formatted_date = format(date, config.date_format)
@@ -92,26 +120,41 @@ function unix_time_to_date_format(unix_time) {
return formatted_date
}
+// This is similar to the above function, however, instead of formatting to the users
+// configuration, it formats to RFC-822 which is the date format used by RSS feeds
+// eg "Mon, 23 May 2025 18:59:59 +0100"
+// returns the formatted date (string)
function unix_time_to_rss_date(unix_time) {
date = fromUnixTime(unix_time)
formatted_date = format(date, "EEE, dd MMM yyyy HH:mm:ss")
return `${formatted_date} ${config.time_zone}`
}
+// This function accepts a list of strings eg ["string1","string2,"string3"] (any length)
+// then returns a string of them each pointing to a seperate url
+// eg "string1, string2, string3
+// this is so you can have a list of tags that each point to their individual tag page
+// returns: string
function hyperlink_tags(tags) {
- string = ""
- for (let tag_index = 0; tag_index < tags.length; tag_index++) {
- string += `${tags[tag_index]}`
- if (tag_index < tags.length - 1) {
+ string = "" // Initialises the string
+ for (let tag_index = 0; tag_index < tags.length; tag_index++) { // Loop over each tag
+ string += `${tags[tag_index]}` // Adds the tag to the string as a HTML href
+ if (tag_index < tags.length - 1) { // If there are more tags, then insert a comma
string += ", ";
}
}
return string
}
-function replace_format_indicators(input_string, post_index=0, tag_name="tag") {
- post_object = posts.posts[post_index]
- output_string = input_string
+// See the readme format indicators section for a full list of format indicators
+// This function replaces the format indicators in a template to the content they represent
+// accepts the template (string),
+// the post index (int) as an optional paramter to indicate what post is to be used (for replacing things like content and titles)
+// the tag (strig) as an optional parameter to indicate what tag is being used (for /tag/:tag pages)
+// returns the template with it's format indiactors replaced (string)
+function replace_format_indicators(template, post_index=0, tag_name="tag") {
+ post_object = posts.posts[post_index] // Defines the post object for easy reference
+ output_string = template // These should always be replaceable
.replaceAll("%%", "%")
.replaceAll("%P", "/post")
.replaceAll("%O", `/edit/${post_index}`)
@@ -119,7 +162,7 @@ function replace_format_indicators(input_string, post_index=0, tag_name="tag") {
.replaceAll("%Y", config.site_name)
.replaceAll("%W", config.site_description)
.replaceAll("%Z", config.attribution)
- if (posts.posts.length > 0) {
+ if (posts.posts.length > 0) { // These can only be replaced if there are more than 0 posts in the posts list
output_string = output_string
.replaceAll("%A", (post_object["tags"]))
.replaceAll("%B", (hyperlink_tags(post_object["tags"])))
@@ -142,7 +185,7 @@ function replace_format_indicators(input_string, post_index=0, tag_name="tag") {
`)
}
- if (config.enable_hitcount == true) {
+ if (config.enable_hitcount == true) { // Finally, the hitcounter should only be replaced if config.enable_hitcount is true
output_string = output_string
.replaceAll("%H", fs.readFileSync('hitcount.txt'))
}
@@ -150,6 +193,10 @@ function replace_format_indicators(input_string, post_index=0, tag_name="tag") {
return output_string
}
+// This escapes some potentially dangerous HTML characters with their HTML entities
+// https://www.freeformatter.com/html-entities.html
+// accepts a string
+// returns a string with some character replaced by their entities
function escape_input(input) {
let output = input
.replaceAll("<", "<")
@@ -158,6 +205,7 @@ function escape_input(input) {
.replaceAll('"', """)
.replaceAll("'", "'")
.replaceAll("/", "/")
+ .replaceAll("%", "%")
return output
}
diff --git a/hitcount.txt b/hitcount.txt
index 030d25b..99bc3d5 100644
--- a/hitcount.txt
+++ b/hitcount.txt
@@ -1 +1 @@
-248
\ No newline at end of file
+253
\ No newline at end of file