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