Compare commits
93 Commits
72316094e4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9be261d415 | ||
|
|
9398919711 | ||
| 545a848479 | |||
| 54ffac931d | |||
| 18b842e48c | |||
|
|
c7bc64e59e | ||
| 9383bd8058 | |||
| d554fce402 | |||
| d17fcf2dd2 | |||
| a1fa0e3dbc | |||
|
f44a15ed41
|
|||
|
ab1fa5e69a
|
|||
| be75382ead | |||
| 3d58c5b244 | |||
| 54b6f018cf | |||
| 7d38752f34 | |||
| ef8711b0e1 | |||
| c3de930b50 | |||
| 1d1e4d863e | |||
| 3b47701c18 | |||
|
|
ddf9fcae13 | ||
|
|
9f0eb13bb4 | ||
|
|
2d33ce79a8 | ||
|
|
4ad7352fcc | ||
|
|
f8f05221b2 | ||
|
|
35163b5584 | ||
| 66423cb3c0 | |||
| cb7dcde7c5 | |||
| 553f126f2a | |||
| 6f9e7aee13 | |||
| 23add8897b | |||
| 84a34d94f3 | |||
| 783f386dfe | |||
| e63fb4a27a | |||
| 2ada1d970f | |||
| 17919e3078 | |||
| 179a4f83bc | |||
| d9d45ff6ea | |||
| 87fd97730e | |||
| acca51da20 | |||
| 788672cbea | |||
| 521dbccc7e | |||
| 8ad8f01043 | |||
| d27330a3db | |||
| 996bf0018b | |||
| 45af80f747 | |||
| 23744f4000 | |||
| 9305559660 | |||
| 35e6b94ba1 | |||
| 3f173fc2e3 | |||
| b44762ba7c | |||
| 1ecc223433 | |||
| 22a7983737 | |||
| e597fd78f7 | |||
| ef7178cc3f | |||
|
|
bfaf957ae2 | ||
|
|
e6476dcd4e | ||
|
|
93c5f13750 | ||
|
|
0541b704db | ||
|
|
5e4eb38763 | ||
| 09967a0be9 | |||
| e3e5469e1a | |||
| c8af978259 | |||
| 99e07389d0 | |||
| 532010f873 | |||
| 5917789c5b | |||
| 4f0941262e | |||
| 27b9ee6437 | |||
| c73ce69f93 | |||
| 5bd0429ae2 | |||
| 6e583d1410 | |||
| cf784a1a99 | |||
| b3ea048244 | |||
| 9b5d3f3f73 | |||
| 5f07db1e15 | |||
| d298717519 | |||
| 0b25fb221b | |||
| f85c4aa893 | |||
| 6fc1f85e18 | |||
| 8418318d80 | |||
| 44a060508b | |||
| 49c7fc7cdf | |||
| 144c276bc9 | |||
| df4bc99d9a | |||
| 3e2a63bfd7 | |||
| bced9c7c0e | |||
| f723e37732 | |||
| 8b9ddcf048 | |||
| 5f2aba0c2b | |||
| cdfc5f2c30 | |||
| 88b198365d | |||
| b683b658f7 | |||
| 0cc319a702 |
5
.gitignore
vendored
Executable file → Normal file
5
.gitignore
vendored
Executable file → Normal file
@@ -4,5 +4,10 @@ posts.json
|
|||||||
comments.json
|
comments.json
|
||||||
users.json
|
users.json
|
||||||
config.json
|
config.json
|
||||||
|
data.json
|
||||||
hitcount.txt
|
hitcount.txt
|
||||||
*.swp
|
*.swp
|
||||||
|
data
|
||||||
|
images/*
|
||||||
|
webroot/custom.css
|
||||||
|
webroot/robots.txt
|
||||||
|
|||||||
41
Makefile
Normal file
41
Makefile
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
DATA_DIR=data
|
||||||
|
WEBROOT_DIR=webroot
|
||||||
|
|
||||||
|
all: config css users posts comments data
|
||||||
|
clean:
|
||||||
|
rm -rf data
|
||||||
|
rm -f webroot/custom.css
|
||||||
|
rm -f config.json
|
||||||
|
|
||||||
|
# config file
|
||||||
|
config: config.json
|
||||||
|
config.json:
|
||||||
|
cp example-config.json config.json
|
||||||
|
echo '!!!PLEASE MODIFY config.json ACCORDING TO YOUR NEEDS!!!'
|
||||||
|
|
||||||
|
# custom.css
|
||||||
|
css: $(WEBROOT_DIR)/custom.css
|
||||||
|
$(WEBROOT_DIR)/custom.css:
|
||||||
|
mkdir -p webroot
|
||||||
|
echo '* { font-family: sans-serif; }' > $(WEBROOT_DIR)/custom.css
|
||||||
|
|
||||||
|
# users.json
|
||||||
|
users: $(DATA_DIR)/users.json
|
||||||
|
$(DATA_DIR)/users.json:
|
||||||
|
mkdir -p data
|
||||||
|
echo '[]' > $(DATA_DIR)/users.json
|
||||||
|
# posts.json
|
||||||
|
posts: $(DATA_DIR)/posts.json
|
||||||
|
$(DATA_DIR)/posts.json:
|
||||||
|
mkdir -p data
|
||||||
|
echo '[]' > $(DATA_DIR)/posts.json
|
||||||
|
# comments.json
|
||||||
|
comments: $(DATA_DIR)/comments.json
|
||||||
|
$(DATA_DIR)/comments.json:
|
||||||
|
mkdir -p data
|
||||||
|
echo '[]' > $(DATA_DIR)/comments.json
|
||||||
|
# data.json
|
||||||
|
data: $(DATA_DIR)/data.json
|
||||||
|
$(DATA_DIR)/data.json:
|
||||||
|
mkdir -p data
|
||||||
|
echo '{"hitcount": 0}' > $(DATA_DIR)/data.json
|
||||||
50
README.md
50
README.md
@@ -2,6 +2,7 @@ This software aims to provide a lot of power to the web admin who is running the
|
|||||||
Customisation is unlimited with a bit of knowledge of EJS and CSS, you can edit the entire formatting of the pages, making the site truly yours!<br/>
|
Customisation is unlimited with a bit of knowledge of EJS and CSS, you can edit the entire formatting of the pages, making the site truly yours!<br/>
|
||||||
This software also aims to be compatible with text based browsers and as a result contains no client side Javascript, if you're looking for a more<br/>
|
This software also aims to be compatible with text based browsers and as a result contains no client side Javascript, if you're looking for a more<br/>
|
||||||
beautiful and featureful blogging frontend, this isn't for you.<br/>
|
beautiful and featureful blogging frontend, this isn't for you.<br/>
|
||||||
|
The AI robots.txt file is from [ai.robots.txt (MIT)](https://github.com/ai-robots-txt/ai.robots.txt)<br/>
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> This software is not finished yet, so it's very buggy and probably really insecure<br/>
|
> This software is not finished yet, so it's very buggy and probably really insecure<br/>
|
||||||
@@ -9,44 +10,45 @@ beautiful and featureful blogging frontend, this isn't for you.<br/>
|
|||||||
|
|
||||||
See the software in action: [deadvey.com](https://deadvey.com)<br/>
|
See the software in action: [deadvey.com](https://deadvey.com)<br/>
|
||||||
|
|
||||||
|
# Installation and Running:
|
||||||
|
Read the [installation guide](/docs/INSTALLATION.md)
|
||||||
|
|
||||||
# Confiuration
|
# Confiuration:
|
||||||
Read the [configuation guide](docs/CONFIG.md) for configuration help (in config.json)
|
Read the [configuation guide](docs/CONFIG.md) for configuration help (in config.json)
|
||||||
|
|
||||||
# Features
|
# Features:
|
||||||
* post creation, modification and deletion via frontend
|
* post creation, modification and deletion via frontend
|
||||||
* user creation, modification and deletion via frontend
|
* user creation, modification and deletion via frontend
|
||||||
* multi user
|
* multi user
|
||||||
* powerful customisation via EJS
|
* powerful customisation via EJS
|
||||||
* site wide rss, atom
|
* Configuration via config.json
|
||||||
* hitcount
|
* site wide and user specific rss, atom
|
||||||
* Markdown syntax in posts
|
* Markdown syntax in posts
|
||||||
* Commenting on posts
|
* Commenting on posts and replying to other comments
|
||||||
* site wide custom CSS
|
* site wide custom CSS and strings
|
||||||
|
* Search functionality
|
||||||
|
* Page indexes
|
||||||
|
|
||||||
# Bugs
|
# Bugs:
|
||||||
* probably scales like shit
|
* probably scales like shit
|
||||||
* probably insecure as hell
|
* probably insecure as hell
|
||||||
|
|
||||||
# Planned features/todo list
|
# Planned features/todo list:
|
||||||
* user specific RSS/atom feeds
|
|
||||||
* federation (looks tricky)
|
* federation (looks tricky)
|
||||||
* All strings (including in edit and post page) customisable
|
|
||||||
* formatable custom strings
|
|
||||||
* inline comments and docs
|
* inline comments and docs
|
||||||
* clean up code a bit
|
* clean up code a bit
|
||||||
* comment pages?
|
|
||||||
* /postID and /userID pages
|
* /postID and /userID pages
|
||||||
* site index
|
* Make EJS modification more user friendly (half done)
|
||||||
* Make EJS modification more user friendly
|
* API for returning posts, users, comments, tags other?...
|
||||||
|
* Moderation tools including a keyword blacklist
|
||||||
|
* Request account function? Not sure how this should be implemented.
|
||||||
|
* optional SQL
|
||||||
|
* initialisation has prompts for setup process.
|
||||||
|
|
||||||
TODO (not finished)
|
# Docs:
|
||||||
# EJS variable names
|
See [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md)
|
||||||
* config.variable_name - pass any variable in config.json
|
|
||||||
* hitcount - value in hitcount.txt (a single number)
|
# Customisation:
|
||||||
## Posts (/views/posts/)
|
Customisation of settings can be done via the config.json file (use example-config.json as an example) and see [the configuration guide](docs/CONFIG.md)<br/>
|
||||||
* post - an object that includes the data for that post, eg post.title, post.content etc
|
Additionaly, more complex configuration of the precise template of the whole site, can be done via [EJS](https://ejs.co/) (in /views) (see [the list of things variables and functions available in EJS](docs/EJS.md) (you will need to understand EJS syntax and JavaScript, to customise this (why did I use EJS? well I originally had this weird system of format indicators with percent (%) signs and stuff (like in unix's date (`date`)) but then I was told EJS is better and it sure is, though it is a bit harder to understand but MUCH more powerful!))
|
||||||
* user - the object of the user who posted this
|
Also, if you want to change any of the strings on the website, please modify or create a new, customised locale in /locales
|
||||||
* index - an int that refers to the index of the current post
|
|
||||||
## Comments (/views/partials/comment.ejs)
|
|
||||||
* comment - an object storing the comment, eg comment.name, comment.content
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"seperator": "<hr/>",
|
|
||||||
"site_name": "My Blog",
|
|
||||||
"site_url": "https://example.com",
|
|
||||||
"language": "en",
|
|
||||||
"port": 8080,
|
|
||||||
"allow_signup": true,
|
|
||||||
"site_description": "Read my blogs!",
|
|
||||||
"timeline_length": 20,
|
|
||||||
"enable_hitcount": true,
|
|
||||||
"charset": "UTF-8",
|
|
||||||
"root_path": "/home/deadvey/code/web/blogger-webroot/",
|
|
||||||
"edit_account_base_url": "/edit_account",
|
|
||||||
"new_post_url": "/post",
|
|
||||||
"signup_url": "/signup",
|
|
||||||
"edit_post_base_url": "/edit",
|
|
||||||
"default_comenter_username": "Anon",
|
|
||||||
"rss": true,
|
|
||||||
"atom": true,
|
|
||||||
"date_format": "yyyy-MM-dd",
|
|
||||||
"time_zone": "+0000",
|
|
||||||
"string": {
|
|
||||||
"signup_agreement": "I agree to not post illegal or hateful content",
|
|
||||||
"signups_unavailable": "Sorry, this server does not allow signups",
|
|
||||||
"user_exists": "Sorry, this user already exists, try a different username",
|
|
||||||
"user_doesnt_exist": "Sorry, this user does not exist",
|
|
||||||
"delete_account_confirmation": "Delete my account - (I agree that my account and all of my posts will be permanently deleted instantly)",
|
|
||||||
"incorrect_password": "Incorrect Password",
|
|
||||||
"rss_disabled": "Sorry, RSS is disabled",
|
|
||||||
"attribution": "Powered by blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Source Code</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>license (WTFPL)</a>"
|
|
||||||
},
|
|
||||||
"css": ""
|
|
||||||
}
|
|
||||||
4
docs/CLASSES_AND_IDS.md
Normal file
4
docs/CLASSES_AND_IDS.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Post:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
79
docs/CONFIG.md
Executable file → Normal file
79
docs/CONFIG.md
Executable file → Normal file
@@ -5,65 +5,48 @@ Currently all values in example-config.json are required, however I plan to add
|
|||||||
All options show an example configuartion value and the variable type + an explaination below it.<br/>
|
All options show an example configuartion value and the variable type + an explaination below it.<br/>
|
||||||
|
|
||||||
## Technical configuration
|
## Technical configuration
|
||||||
* "site_url": "https://example.com"<br/>
|
| name | example value | variable type | explanation |
|
||||||
This value defines the url of your site, this used for the RSS feed to link back to post.
|
|-----------------|----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
* "port": 8080<br/>
|
| site_url | "https://example.com" | String | This value defines the url of your site, this used for the RSS feed to link back to post. |
|
||||||
Integer. This value defines the port that you run the blog on. Don't change this value if you don't know what that means.
|
| port | 8080 | Integer | This value defines the port that you run the blog on. Don't change this value if you don't know what that means. |
|
||||||
* "allow_signup": true<br/>
|
| allow_signup | true | Boolean | Defines weather new people should be allowed to signup. |
|
||||||
Boolean. Defines weather new people should be allowed to signup.
|
| timeline_length | 20 | Integer | How many posts will be shown on the timeline (home page). |
|
||||||
* "timeline_length": 20<br/>
|
| enable_hitcount | true | Boolean | Enabling the hitcount (a number that represents the amount of front page loads (stored in hitcount.txt)) can slightly slow down loading of the front page. |
|
||||||
Integer. How many posts will be shown on the timeline (home page).
|
| charset | "UTF-8" | String | This is the value in the <meta charset=""> tag in the html of all pages, you should not change this unless you know why. |
|
||||||
* "enable_hitcount": true<br/>
|
| root_path | "/path/to/root/of/website" | String | Anything in this directory will be in the webroot, so put favicon.ico and anything else here. |
|
||||||
Boolean. Enabling the hitcount (a number that represents the amount of front page loads (stored in hitcount.txt)) can slightly slow down loading of the front page.
|
| data_storage | "json" | String | JSON is currently the only supported format, but SQL is going to be added/is a work in progress |
|
||||||
* "charset": "UTF-8"<br/>
|
| cache_data | true | Boolean | Not caching data means you can edit the posts, users, comments etc, maunally and not have to restart the server, however, for large instances this is not reccomended as it takes longer to load the required data. Note: config.json always needs a restart |
|
||||||
String. This is the value in the <meta charset=""> tag in the html of all pages, you should not change this unless you know why.
|
| request_data_limit | 20 | Integer | The maximum number of objects to return (latest), so if set to 20, then only the 20 most recent posts will ever show |
|
||||||
* "root_path": "/path/to/root/of/website"
|
| root_path | '/var/www/blog_root' | String | Relative or Absolute path to the root directory of your static content, holds files such as favicon.ico, custom.css and robots.txt |
|
||||||
String. Anything in this directory will be in the webroot, so put favicon.ico and anything else here.
|
| locale | 'en_GB' | String | The locale to use which determines the language used and minor cultural differences |
|
||||||
|
|
||||||
## Basic Customisation
|
## Basic Customisation
|
||||||
* "seperator": "\<hr/\>"<br/>
|
| name | example value | variable type | explanation |
|
||||||
String. This is what %S represents in the formatting, this will go inbetween posts and generally to seperate out content on pages.
|
|-----------------|----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
* "site_name": "My Blog"<br/>
|
|locale|"en-US"|String|Your locale, see [/locales](/locales) for a list of all locales (you can open a PR for a new translation too)|
|
||||||
String. This is what %Y represents; it's the name of your instance, a human readable string.
|
|seperator|"\<hr/\>"|String|By default, this will go inbetween posts and generally to seperate out content on pages.|
|
||||||
* "site_description": "Read my blogs!"
|
|site_name|"Pete's Blogging Site!"|String|It's the name of your blog site, a human readable string.|
|
||||||
String. This is what %W represents; it's the description of your instance, a human readable string.
|
|site_description|"Read my blogs!"|String|This is what %W represents; it's the description of your instance, a human readable string.|
|
||||||
* "default_commenter_username": "Anon"
|
|default_commenter_username|"Anon"|String|Default commenter username if no username is inputted in comment submission.|
|
||||||
String. Default commenter username if no username is inputted in comment submission.
|
|
||||||
|
|
||||||
## Syndication
|
## Syndication
|
||||||
* "rss": true<br/>
|
| name | example value | variable type | explanation |
|
||||||
Boolean. Enable or Disable RSS feeds.
|
|------|---------------|---------------|-------------------------------|
|
||||||
* "rss_path": "/rss"<br/>
|
| rss | true | Boolean | Enable or Disable RSS feeds. |
|
||||||
String. The path of the global rss feed file.
|
| atom | true | Boolean | Enable or Disable ATOM feeds. |
|
||||||
|
|
||||||
## Dates
|
## Dates
|
||||||
Read more at [date-fns](https://date-fns.org/v4.1.0/docs/format)<br/>
|
Read more at [date-fns](https://date-fns.org/v4.1.0/docs/format)<br/>
|
||||||
* "date_format": "yyyy-MM-dd"<br/>
|
| name | example value | variable type | explanation |
|
||||||
String. The format of date's on the website.
|
|------|---------------|---------------|-------------------------------|
|
||||||
* "time_zone": "+0000"<br/>
|
|date_format|"yyyy-MM-dd"|String|The format of date's on the website.|
|
||||||
String. Your offset from UTC
|
|time_zone|"+0000"|String|Your offset from UTC|
|
||||||
|
|
||||||
## Advanced Customisation
|
## Advanced Customisation
|
||||||
* /views/* files are EJS files (used for formatting HTML) and can be editted to your liking, you might want to read [the EJS docs](https://ejs.co/#docs) for help.
|
* /views/* files are EJS files (used for formatting HTML) and can be editted to your liking, you might want to read [the EJS docs](https://ejs.co/#docs) for help.
|
||||||
* "css": "body { background: red; }"<br/>
|
* "css": "body { background: red; }"<br/>
|
||||||
String. Custom CSS to be applied to all pages, if you want more complex css, you can edit custom.css.<br/>
|
String. Custom CSS to be applied to all pages, if you want more complex css, you can edit custom.css.<br/>
|
||||||
You can also edit the custom.css file in the webroot, as by default this is linked in the global header.
|
You can also edit the custom.css file in the webroot, as by default this is linked in the global header.
|
||||||
|
* You can create a file called custom.css in the webroot and that will be loaded as a style onto every page.
|
||||||
|
|
||||||
## Custom Strings
|
## Custom Strings
|
||||||
All of these values are of type String
|
* You can edit all the strings on the site in /locales/\<your-locale>.json
|
||||||
* "signup_agreement": "I agree to not post illegal or hateful content"<br/>
|
|
||||||
The agreement people must check to signup for the server.
|
|
||||||
* "signups_unavailable": "Sorry, this server does not allow signups"<br/>
|
|
||||||
The string to be displayed if the user tries to signup when signup's are disabled.
|
|
||||||
* "user_exists": "Sorry, this user already exists, try a different username"<br/>
|
|
||||||
The string to be shown when someone is trying to signup with a name exists.
|
|
||||||
* "user_doesnt_exist": "Sorry, this user does not exist"<br/>
|
|
||||||
The string to be shown when someone tries to edit their account or make a post but the username doesn't exist.
|
|
||||||
* "delete_account_confirmation": "I agree that my account and all of my posts will be permanently deleted instantly"<br/>
|
|
||||||
The string to be shown as a confirmation when a user tries to delete their account.
|
|
||||||
* "incorrect_password": "Incorrect Password"<br/>
|
|
||||||
The string to be shown if the password is incorrect.
|
|
||||||
|
|
||||||
## Other
|
|
||||||
* "attribution" = "Powered by blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Source Code</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>license (WTFPL)</a>"
|
|
||||||
String. Represented by format indicator %Z. Only change this value if you modify the source code or just want to change some of the formatting.
|
|
||||||
|
|||||||
1
docs/CONTRIBUTING.md
Normal file
1
docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Just open a PR or something, if it's good I'll pull
|
||||||
4
docs/DOCUMENTATION.md
Normal file
4
docs/DOCUMENTATION.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
All documentation is under construction as the program is also under construction and so is constantly changing and is also a mess and I'm shit at documentation
|
||||||
|
- [EJS Variables and functions](EJS.md)
|
||||||
|
- [configuring config.json](CONFIG.md)
|
||||||
|
- [Contributing](CONTRIBUTING.md)
|
||||||
27
docs/EJS.md
Normal file
27
docs/EJS.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
This is not a guide on how EJS works, look at [EJS's website](https://ejs.co/) for that!<br/>
|
||||||
|
This is just a list of functions and pieces of information provided to each EJS file.<br/>
|
||||||
|
<br/>
|
||||||
|
# syndication/global_rss:
|
||||||
|
- All config.json data
|
||||||
|
- All posts from posts.json
|
||||||
|
- showdown.JS's converter functions
|
||||||
|
- All functions in functions.js
|
||||||
|
# syndication/user_rss:
|
||||||
|
- All config.json data
|
||||||
|
- All posts from posts.json
|
||||||
|
- showdown.JS's converter functions
|
||||||
|
- All functions in functions.js
|
||||||
|
- the userID of the user in question (integer)
|
||||||
|
# syndication/global_atom:
|
||||||
|
- All config.json data
|
||||||
|
- All posts from posts.json
|
||||||
|
- showdown.JS's converter functions
|
||||||
|
- All functions in functions.js
|
||||||
|
- getUnixTime function from date-fns
|
||||||
|
# syndication/user_atom:
|
||||||
|
- All config.json data
|
||||||
|
- All posts from posts.json
|
||||||
|
- showdown.JS's converter functions
|
||||||
|
- All functions in functions.js
|
||||||
|
- the userID of the user in question (integer)
|
||||||
|
- getUnixTime function from date-fns
|
||||||
10
docs/INSTALLATION.md
Normal file
10
docs/INSTALLATION.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Installation
|
||||||
|
This program is currently just ran manually.<br/>
|
||||||
|
All you need to do is clone the git repository:<br/>
|
||||||
|
```git clone https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs.git```<br/>
|
||||||
|
Then run the Makefile:<br/>
|
||||||
|
```make```
|
||||||
|
Then you should modify config.json in / to suit your needs.<br/>
|
||||||
|
# Running
|
||||||
|
I would reccomend running the program in tmux so it does not stop running when you close the terminal window.<br/>
|
||||||
|
• There is currently no init system support. I might add this later (or you could open a PR).
|
||||||
BIN
docs/images/post-css.png
Normal file
BIN
docs/images/post-css.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 239 KiB |
21
example-config.json
Executable file → Normal file
21
example-config.json
Executable file → Normal file
@@ -1,33 +1,26 @@
|
|||||||
{
|
{
|
||||||
|
"site_admin": "your name",
|
||||||
"seperator": "<hr/>",
|
"seperator": "<hr/>",
|
||||||
"site_name": "My Blog",
|
"site_name": "My Blog",
|
||||||
"site_url": "https://example.com",
|
"site_url": "https://example.com",
|
||||||
"language": "en",
|
"locale": "en-US",
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
|
"data_storage": "json",
|
||||||
|
"cache_data": false,
|
||||||
"allow_signup": true,
|
"allow_signup": true,
|
||||||
"site_description": "Read my blogs!",
|
"site_description": "Read my blogs!",
|
||||||
"timeline_length": 20,
|
"request_data_limit": 20,
|
||||||
"enable_hitcount": true,
|
"enable_hitcount": true,
|
||||||
"charset": "UTF-8",
|
"charset": "UTF-8",
|
||||||
"root_path": "/path/to/blogger-webroot",
|
"root_path": "../webroot/",
|
||||||
"edit_account_base_url": "/edit_account",
|
"edit_account_base_url": "/edit_account",
|
||||||
"new_post_url": "/post",
|
"new_post_url": "/post",
|
||||||
"signup_url": "/signup",
|
"signup_url": "/signup",
|
||||||
"edit_post_base_url": "/edit",
|
"edit_post_base_url": "/edit",
|
||||||
"default_comenter_username": "Anon",
|
"default_commenter_username": "Anon",
|
||||||
"rss": true,
|
"rss": true,
|
||||||
"atom": true,
|
"atom": true,
|
||||||
"date_format": "yyyy-MM-dd",
|
"date_format": "yyyy-MM-dd",
|
||||||
"time_zone": "+0000",
|
"time_zone": "+0000",
|
||||||
"string": {
|
|
||||||
"signup_agreement": "I agree to not post illegal or hateful content",
|
|
||||||
"signups_unavailable": "Sorry, this server does not allow signups",
|
|
||||||
"user_exists": "Sorry, this user already exists, try a different username",
|
|
||||||
"user_doesnt_exist": "Sorry, this user does not exist",
|
|
||||||
"delete_account_confirmation": "I agree that my account and all of my posts will be permanently deleted instantly",
|
|
||||||
"incorrect_password": "Incorrect Password",
|
|
||||||
"rss_disabled": "Sorry, RSS is disabled",
|
|
||||||
"attribution": "Powered by blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Source Code</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>license (WTFPL)</a>"
|
|
||||||
},
|
|
||||||
"css": ""
|
"css": ""
|
||||||
}
|
}
|
||||||
|
|||||||
44
locales/en-AU.json
Normal file
44
locales/en-AU.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"quotes": "“”‘’",
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username",
|
||||||
|
"prettyname": "Prettyname",
|
||||||
|
"description": "Description (social links, what you write about etc), supports markdown",
|
||||||
|
"title": "Title",
|
||||||
|
"post_content": "Post Content, supports markdown",
|
||||||
|
"tags": "Tags (comma seperated)",
|
||||||
|
"delete_account_confirmation": "Delete my account - (I agree that my account and all of my posts will be permanently deleted instantly)",
|
||||||
|
"signup_agreement": "I agree to not post illegal or hateful content",
|
||||||
|
"comment": "Comment",
|
||||||
|
"submit": "Submit",
|
||||||
|
|
||||||
|
"site_ran_by": "Site is ran by",
|
||||||
|
"signups_unavailable": "Sorry, this server does not allow signups",
|
||||||
|
"user_exists": "Sorry, this user already exists, try a different username",
|
||||||
|
"user_doesnt_exist": "Sorry, this user does not exist",
|
||||||
|
"comment_doesnt_exist": "This comment doesn't exist, this could be because the post it was attached to was deleted",
|
||||||
|
"post_doesnt_exist": "This post doesn't exist or was deleted",
|
||||||
|
"incorrect_password": "Incorrect Password",
|
||||||
|
"rss_disabled": "Sorry, RSS is disabled",
|
||||||
|
"atom_disabled": "Sorry, ATOM is disabled",
|
||||||
|
"AI_consent": "The content on this website may not be copied, scraped, or used to train AI models or large language models (LLMs) without prior written consent.",
|
||||||
|
|
||||||
|
"rss_feed": "RSS Feed",
|
||||||
|
"atom_feed": "ATOM Feed",
|
||||||
|
"no_tags": "No Tags",
|
||||||
|
"new_post": "New Post",
|
||||||
|
"edit_post": "Edit Post",
|
||||||
|
"sign_up": "Sign Up",
|
||||||
|
"edit_account": "Edit Account",
|
||||||
|
"permalink": "Permalink",
|
||||||
|
"written_by": "Written by",
|
||||||
|
"published": "Published",
|
||||||
|
"last_modified": "Last Modified",
|
||||||
|
"hitcount": "Hitcount",
|
||||||
|
"posts_tagged": "Posts Tagged",
|
||||||
|
"home_page": "Home Page",
|
||||||
|
"site_index": "Site Index",
|
||||||
|
"reply": "reply",
|
||||||
|
"attribution": "Powered by blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Source Code</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>license (WTFPL)</a>",
|
||||||
|
"translated_by": "DeaDvey"
|
||||||
|
}
|
||||||
44
locales/en-GB.json
Normal file
44
locales/en-GB.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"quotes": "“”‘’",
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username",
|
||||||
|
"prettyname": "Prettyname",
|
||||||
|
"description": "Description (social links, what you write about etc), supports markdown",
|
||||||
|
"title": "Title",
|
||||||
|
"post_content": "Post Content, supports markdown",
|
||||||
|
"tags": "Tags (comma seperated)",
|
||||||
|
"delete_account_confirmation": "Delete my account - (I agree that my account and all of my posts will be permanently deleted instantly)",
|
||||||
|
"signup_agreement": "I agree to not post illegal or hateful content",
|
||||||
|
"comment": "Comment",
|
||||||
|
"submit": "Submit",
|
||||||
|
|
||||||
|
"site_ran_by": "Site is ran by",
|
||||||
|
"signups_unavailable": "Sorry, this server does not allow signups",
|
||||||
|
"user_exists": "Sorry, this user already exists, try a different username",
|
||||||
|
"user_doesnt_exist": "Sorry, this user does not exist",
|
||||||
|
"comment_doesnt_exist": "This comment doesn't exist, this could be because the post it was attached to was deleted",
|
||||||
|
"post_doesnt_exist": "This post doesn't exist or was deleted",
|
||||||
|
"incorrect_password": "Incorrect Password",
|
||||||
|
"rss_disabled": "Sorry, RSS is disabled",
|
||||||
|
"atom_disabled": "Sorry, ATOM is disabled",
|
||||||
|
"AI_consent": "The content on this website may not be copied, scraped, or used to train AI models or large language models (LLMs) without prior written consent.",
|
||||||
|
|
||||||
|
"rss_feed": "RSS Feed",
|
||||||
|
"atom_feed": "ATOM Feed",
|
||||||
|
"no_tags": "No Tags",
|
||||||
|
"new_post": "New Post",
|
||||||
|
"edit_post": "Edit Post",
|
||||||
|
"sign_up": "Sign Up",
|
||||||
|
"edit_account": "Edit Account",
|
||||||
|
"permalink": "Permalink",
|
||||||
|
"written_by": "Written by",
|
||||||
|
"published": "Published",
|
||||||
|
"last_modified": "Last Modified",
|
||||||
|
"hitcount": "Hitcount",
|
||||||
|
"posts_tagged": "Posts Tagged",
|
||||||
|
"home_page": "Home Page",
|
||||||
|
"site_index": "Site Index",
|
||||||
|
"reply": "reply",
|
||||||
|
"attribution": "Powered by blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Source Code</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>license (WTFPL)</a>",
|
||||||
|
"translated_by": "DeaDvey"
|
||||||
|
}
|
||||||
44
locales/en-US.json
Normal file
44
locales/en-US.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"quotes": "“”‘’",
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username",
|
||||||
|
"prettyname": "Prettyname",
|
||||||
|
"description": "Description (social links, what you write about etc), supports markdown",
|
||||||
|
"title": "Title",
|
||||||
|
"post_content": "Post Content, supports markdown",
|
||||||
|
"tags": "Tags (comma seperated)",
|
||||||
|
"delete_account_confirmation": "Delete my account - (I agree that my account and all of my posts will be permanently deleted instantly)",
|
||||||
|
"signup_agreement": "I agree to not post illegal or hateful content",
|
||||||
|
"comment": "Comment",
|
||||||
|
"submit": "Submit",
|
||||||
|
|
||||||
|
"site_ran_by": "Site is ran by",
|
||||||
|
"signups_unavailable": "Sorry, this server does not allow signups",
|
||||||
|
"user_exists": "Sorry, this user already exists, try a different username",
|
||||||
|
"user_doesnt_exist": "Sorry, this user does not exist",
|
||||||
|
"comment_doesnt_exist": "This comment doesn't exist, this could be because the post it was attached to was deleted",
|
||||||
|
"post_doesnt_exist": "This post doesn't exist or was deleted",
|
||||||
|
"incorrect_password": "Incorrect Password",
|
||||||
|
"rss_disabled": "Sorry, RSS is disabled",
|
||||||
|
"atom_disabled": "Sorry, ATOM is disabled",
|
||||||
|
"AI_consent": "The content on this website may not be copied, scraped, or used to train AI models or large language models (LLMs) without prior written consent.",
|
||||||
|
|
||||||
|
"rss_feed": "RSS Feed",
|
||||||
|
"atom_feed": "ATOM Feed",
|
||||||
|
"no_tags": "No Tags",
|
||||||
|
"new_post": "New Post",
|
||||||
|
"edit_post": "Edit Post",
|
||||||
|
"sign_up": "Sign Up",
|
||||||
|
"edit_account": "Edit Account",
|
||||||
|
"permalink": "Permalink",
|
||||||
|
"written_by": "Written by",
|
||||||
|
"published": "Published",
|
||||||
|
"last_modified": "Last Modified",
|
||||||
|
"hitcount": "Hitcount",
|
||||||
|
"posts_tagged": "Posts Tagged",
|
||||||
|
"home_page": "Home Page",
|
||||||
|
"site_index": "Site Index",
|
||||||
|
"reply": "reply",
|
||||||
|
"attribution": "Powered by blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Source Code</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>license (WTFPL)</a>",
|
||||||
|
"translated_by": "DeaDvey"
|
||||||
|
}
|
||||||
43
locales/es-ES.json
Normal file
43
locales/es-ES.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"quotes": "«»‹›",
|
||||||
|
"password": "Contraseña",
|
||||||
|
"username": "Nombre de Usuario",
|
||||||
|
"prettyname": "Nombre Bonito",
|
||||||
|
"description": "Descripción (enlaces de redes, sobre que escribes, etc), soporta markdown",
|
||||||
|
"title": "Título",
|
||||||
|
"post_content": "Contenido de la Publicación, soporta markdown",
|
||||||
|
"tags": "Etiquetas (separados por coma)",
|
||||||
|
"delete_account_confirmation": "Eliminar mi cuenta - (Estoy de acuerdo con que todas mis publicaciones serán permanentemente eliminadas al instante)",
|
||||||
|
"signup_agreement": "Acepto no publicar contenido ilegal o de odio",
|
||||||
|
"comment": "Comentar",
|
||||||
|
"submit": "Enviar",
|
||||||
|
|
||||||
|
"site_ran_by": "El sitio es llevado por",
|
||||||
|
"signups_unavailable": "Lo siento, este servidor no permite registrarse",
|
||||||
|
"user_exists": "Lo siento, este usuario ya existe, prueba otro diferente",
|
||||||
|
"user_doesnt_exist": "Lo siento, este usuario no existe",
|
||||||
|
"comment_doesnt_exist": "Este comentario no existe, esto puede ser porque la publicación en la que estaba adjunto se ha eliminado",
|
||||||
|
"post_doesnt_exist": "Esta publicación no existe o se ha aliminado",
|
||||||
|
"incorrect_password": "Contraseña Incorrecta",
|
||||||
|
"rss_disabled": "Lo siento, RSS está desactivado",
|
||||||
|
"atom_disabled": "Lo siento, ATOM está desactivado",
|
||||||
|
"AI_consent": "El contenido de este sitio no debe ser copiado, raspado, o usado para entrenar modelos de IA o de lenguaje (LLMs) sin consentimiento previo.",
|
||||||
|
"rss_feed": "Feed RSS",
|
||||||
|
"atom_feed": "Feed ATOM",
|
||||||
|
"no_tags": "Sin Etiquetas",
|
||||||
|
"new_post": "Nueva Publicación",
|
||||||
|
"edit_post": "Editar Publicación",
|
||||||
|
"sign_up": "Registrarse",
|
||||||
|
"edit_account": "Editar Cuenta",
|
||||||
|
"permalink": "Enlace Permanente",
|
||||||
|
"written_by": "Escrito por",
|
||||||
|
"published": "Publicado",
|
||||||
|
"last_modified": "Última Modificación",
|
||||||
|
"hitcount": "Visitas",
|
||||||
|
"post_tagged": "Publicaciones Etiquetadas",
|
||||||
|
"home_page": "Página Principal",
|
||||||
|
"site_index": "Índice del Sitio",
|
||||||
|
"reply": "Responder",
|
||||||
|
"attribution": "Empujado por blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Código Fuente</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>licencia (WTFPL)</a>",
|
||||||
|
"translated_by": "Javalsai"
|
||||||
|
}
|
||||||
44
locales/ja_JP.json
Normal file
44
locales/ja_JP.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"quotes": "“”‘’",
|
||||||
|
"password": "パスワード",
|
||||||
|
"username": "ユーザー名",
|
||||||
|
"prettyname": "きれいな名前",
|
||||||
|
"description": "説明 (例えばSNSのリンクや何を書くなど)、 マークダウンをスポートする",
|
||||||
|
"title": "題名",
|
||||||
|
"post_content": "投稿の内容、マークダウンをスポートする",
|
||||||
|
"tags": "タグ (カンマで区切られています)",
|
||||||
|
"delete_account_confirmation": "アカウントを削除する - (アカウントと投稿の全部をいつまでも削除するに賛成します。)",
|
||||||
|
"signup_agreement": "違法なコンテントと憎らしいコンテントをポストしないに賛成します。",
|
||||||
|
"comment": "コメント",
|
||||||
|
"submit": "提出する",
|
||||||
|
|
||||||
|
"site_ran_by": "アドミン:",
|
||||||
|
"signups_unavailable": "申し訳ございませんでもこのサーバーはサインアップ",
|
||||||
|
"user_exists": "申し訳ございませんでもこのユーザー名をつかえります。別のユーザー名を入ります。",
|
||||||
|
"user_doesnt_exist": "申し訳ございませんでもこのアカウントがいません。",
|
||||||
|
"comment_doesnt_exist": "このコメントがない、これから投稿を削除したかもしれない",
|
||||||
|
"post_doesnt_exist": "この投稿がないか又は削除しました。",
|
||||||
|
"incorrect_password": "パスワードが違う",
|
||||||
|
"rss_disabled": "申し訳ございませんでもRSSが使用不可能なります。",
|
||||||
|
"atom_disabled": "申し訳ございませんでもATOMが使用不可能なります。",
|
||||||
|
"AI_consent": "書面による同意がないとこのホームページの内容はコピーするか又はスクレイピングするか又はAIモデルか大規模言語モデル(LLM)を仕込むことが禁断します。",
|
||||||
|
|
||||||
|
"rss_feed": "RSSのフィード",
|
||||||
|
"atom_feed": "ATOMのフィード",
|
||||||
|
"no_tags": "タグがない",
|
||||||
|
"new_post": "新しい投稿",
|
||||||
|
"edit_post": "投稿をエディットする",
|
||||||
|
"sign_up": "サインアップ",
|
||||||
|
"edit_account": "アカウントをエディットする",
|
||||||
|
"permalink": "恒久リンク",
|
||||||
|
"written_by": "作家は",
|
||||||
|
"published": "発行の日付",
|
||||||
|
"last_modified": "全変更",
|
||||||
|
"hitcount": "ヒット数",
|
||||||
|
"posts_tagged": "投稿をタグするの数",
|
||||||
|
"home_page": "ホーム",
|
||||||
|
"site_index": "ホームページの索引",
|
||||||
|
"reply": "返事",
|
||||||
|
"attribution": "blogger-nodejsで作成されています: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>ソースコード</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>ライセンス (WTFPL)</a>",
|
||||||
|
"translated_by": "Nullifier"
|
||||||
|
}
|
||||||
44
locales/sv-SE.json
Normal file
44
locales/sv-SE.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"quotes": "“”‘’",
|
||||||
|
"password": "Lösenord",
|
||||||
|
"username": "Användarnamn",
|
||||||
|
"prettyname": "Vackert namn",
|
||||||
|
"description": "Beskrivning (sociala länkar, vad du skriver om m.m.), stöder markdown",
|
||||||
|
"title": "Titel",
|
||||||
|
"post_content": "Inläggsinnehåll, stöder markdown",
|
||||||
|
"tags": "Taggar (komma separerade)",
|
||||||
|
"delete_account_confirmation": "Radera mitt konto - (Jag förstår och accepterar att mitt konto och alla mina inlägg kommer att raderas permanent omedelbart)",
|
||||||
|
"signup_agreement": "Jag samtycker till att inte publicera olagligt eller hatiskt innehåll",
|
||||||
|
"comment": "Kommentera",
|
||||||
|
"submit": "Skicka",
|
||||||
|
|
||||||
|
"site_ran_by": "Webbplatsen drivs av",
|
||||||
|
"signups_unavailable": "Tyvärr, denna server tillåter inte registreringar",
|
||||||
|
"user_exists": "Tyvärr, den här användaren finns redan. Försök med ett annat användarnamn",
|
||||||
|
"user_doesnt_exist": "Tyvärr, den här användaren finns inte",
|
||||||
|
"comment_doesnt_exist": "Denna kommentar finns inte, vilket kan bero på att inlägget den var kopplad till har raderats",
|
||||||
|
"post_doesnt_exist": "Det här inlägget finns inte eller har raderats",
|
||||||
|
"incorrect_password": "Felaktigt Lösenord",
|
||||||
|
"rss_disabled": "Tyvärr, RSS är inaktiverat",
|
||||||
|
"atom_disabled": "Tyvärr, ATOM är inaktiverat",
|
||||||
|
"AI_consent": "Innehållet på denna webbplats får inte kopieras, skrapas eller användas för att träna AI-modeller eller stora språkmodeller (LLM) utan skriftligt samtycke.",
|
||||||
|
|
||||||
|
"rss_feed": "RSS-flöde",
|
||||||
|
"atom_feed": "ATOM-flöde",
|
||||||
|
"no_tags": "Inga taggar",
|
||||||
|
"new_post": "Nytt inlägg",
|
||||||
|
"edit_post": "Redigera inlägg",
|
||||||
|
"sign_up": "Registrera",
|
||||||
|
"edit_account": "Redigera konto",
|
||||||
|
"permalink": "Permalänk",
|
||||||
|
"written_by": "Skriven av",
|
||||||
|
"published": "Publicerad",
|
||||||
|
"last_modified": "Senast ändrad",
|
||||||
|
"hitcount": "Besökare",
|
||||||
|
"posts_tagged": "Taggade inlägg",
|
||||||
|
"home_page": "Startsida",
|
||||||
|
"site_index": "Webbplatsindex",
|
||||||
|
"reply": "Svara",
|
||||||
|
"attribution": "Drivs av blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Källkod</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>licens (WTFPL)</a>",
|
||||||
|
"translated_by": "pickzelle"
|
||||||
|
}
|
||||||
61
locales/template.jsonc
Normal file
61
locales/template.jsonc
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"quotes": "“”‘’", // Single and Double quotes, according to https://github.com/markdown-it/markdown-it format
|
||||||
|
|
||||||
|
// Placeholders in form inputs!
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username",
|
||||||
|
"prettyname": "Prettyname",
|
||||||
|
"description": "Description (social links, what you write about etc), supports markdown", // Should explain what can be entered into the user description/bio
|
||||||
|
"title": "Title", // Post title
|
||||||
|
"post_content": "Post Content, supports markdown",
|
||||||
|
"tags": "Tags (comma seperated)", // An input field that allows you to enter a comma seperated list of tags like: 'sus,test,haha'
|
||||||
|
"delete_account_confirmation": "Delete my account - (I agree that my account and all of my posts will be permanently deleted instantly)", // Should make it clear that all user data and posts will be deleted
|
||||||
|
"signup_agreement": "I agree to not post illegal or hateful content", // Should make it clear that you cannot post illegal or hateful content
|
||||||
|
"comment": "Comment",
|
||||||
|
"submit": "Sumbit",
|
||||||
|
|
||||||
|
"site_ran_by": "Site is ran by", // eg 'Site is ran by Bob', it shows up in the footer of each page
|
||||||
|
|
||||||
|
// Error messages, should just apologise and make it clear the error
|
||||||
|
"signups_unavailable": "Sorry, this server does not allow signups",
|
||||||
|
"user_exists": "Sorry, this user already exists, try a different username",
|
||||||
|
"user_doesnt_exist": "Sorry, this user does not exist",
|
||||||
|
"comment_doesnt_exist": "This comment doesn't exist, this could be because the post it was attached to was deleted",
|
||||||
|
"post_doesnt_exist": "This post doesn't exist or was deleted",
|
||||||
|
"incorrect_password": "Incorrect Password",
|
||||||
|
"rss_disabled": "Sorry, RSS is disabled",
|
||||||
|
"atom_disabled": "Sorry, ATOM is disabled",
|
||||||
|
|
||||||
|
// Disclaimer, not legally binding
|
||||||
|
"AI_consent": "The content on this website may not be copied, scraped, or used to train AI models or large language models (LLMs) without prior written consent.",
|
||||||
|
|
||||||
|
// Hyperlinks to pages and plain text that shows up on the website
|
||||||
|
"rss_feed": "RSS Feed",
|
||||||
|
"atom_feed": "ATOM Feed",
|
||||||
|
"no_tags": "No Tags",
|
||||||
|
"new_post": "New Post",
|
||||||
|
"edit_post": "Edit Post",
|
||||||
|
"sign_up": "Sign Up",
|
||||||
|
"edit_account": "Edit Account",
|
||||||
|
"permalink": "Permalink",
|
||||||
|
"written_by": "Written by", // A post is written/authored by x person
|
||||||
|
"published": "Published", // Published on this date
|
||||||
|
"last_modified": "Last Modified", // Last modified on this date
|
||||||
|
"hitcount": "Hitcount", // The number of views/hits/visits to a page, eg: 'hitcount: 53'
|
||||||
|
"posts_tagged": "Posts Tagged",
|
||||||
|
"home_page": "Home Page", // The main or default page, ie index.html
|
||||||
|
"site_index": "Site Index", // Or 'site map'
|
||||||
|
"reply": "reply", // Reply to a comment
|
||||||
|
|
||||||
|
// Attribution for the source code, don't change the URLs obviously, just the text within them.
|
||||||
|
"attribution": "Powered by blogger-nodejs: <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs'>Source Code</a>, <a href='https://git.javalsai.tuxcord.net/deadvey/blogger-nodejs/raw/branch/master/LICENSE'>license (WTFPL)</a>",
|
||||||
|
|
||||||
|
// Comma seperated list of people who contributed to this translation
|
||||||
|
"translated_by": "DeaDvey"
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// indexes locales
|
||||||
|
// Password again
|
||||||
|
// site_admin?
|
||||||
|
// Should colons be part of the translations?
|
||||||
|
}
|
||||||
12
package.json
Executable file → Normal file
12
package.json
Executable file → Normal file
@@ -1,8 +1,16 @@
|
|||||||
{
|
{
|
||||||
|
"name": "blogger-nodejs",
|
||||||
|
"version": "0.0.5",
|
||||||
|
"description": "Simple web logging backend",
|
||||||
|
"author": "DeaDvey",
|
||||||
|
"license": "WTFPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^5.1.0",
|
"express": "^5.2.1",
|
||||||
"showdown": "^2.1.0"
|
"express-router": "^0.0.1",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
|
"mysql": "^2.18.1",
|
||||||
|
"package.json": "^2.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
171
src/data.js
Normal file
171
src/data.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import { createRequire } from "module";
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
const func = require('./functions.js')
|
||||||
|
const config = require("../config.json")
|
||||||
|
const fs = require("fs")
|
||||||
|
|
||||||
|
// Literally just +1 to the hitcount
|
||||||
|
export function increment_hitcount(postID = -1) { // -1 Means it will increment the timeline hitcount
|
||||||
|
if (config.data_storage == 'json') {
|
||||||
|
if (postID == -1) {
|
||||||
|
let hitcount = getdata('hitcount');
|
||||||
|
hitcount += 1
|
||||||
|
writedata('hitcount', hitcount);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let post = getdata('posts','id', postID);
|
||||||
|
if (post == 1) // Does not exist
|
||||||
|
{
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
else if (typeof post.hitcount != 'undefined') {
|
||||||
|
post.hitcount += 1;
|
||||||
|
writedata('posts', post, postID)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
post.hitcount = 1;
|
||||||
|
writedata('posts', post, postID)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function searchdata(term, type) { // Searches users and posts for any matches
|
||||||
|
let search_results = {"posts": [], "users": []};
|
||||||
|
// Search users
|
||||||
|
if (type.includes('post')) {
|
||||||
|
let list = getdata('posts');
|
||||||
|
list.forEach((element,index) => {
|
||||||
|
if (typeof element.deleted == 'undefined' || element.deleted == false) {
|
||||||
|
if (element.content.includes(term)) {
|
||||||
|
search_results.posts.push(element)
|
||||||
|
}
|
||||||
|
else if (element.title.includes(term)) {
|
||||||
|
search_results.posts.push(element)
|
||||||
|
}
|
||||||
|
else if (element.tags.toString().includes(term)) {
|
||||||
|
search_results.posts.push(element)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (type.includes('user')) {
|
||||||
|
let list = getdata('users');
|
||||||
|
list.forEach((element,index) => {
|
||||||
|
if (typeof element.deleted == 'undefined' || element.deleted == false) {
|
||||||
|
if (element.username.includes(term)) {
|
||||||
|
search_results.users.push(element)
|
||||||
|
}
|
||||||
|
else if (element.prettyname.includes(term)) {
|
||||||
|
search_results.users.push(element)
|
||||||
|
}
|
||||||
|
else if (element.description.includes(term)) {
|
||||||
|
search_results.users.push(element)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return search_results;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getdata(table_name, key=-1, value=-1) {
|
||||||
|
let result = undefined
|
||||||
|
switch (config["data_storage"]) {
|
||||||
|
case 'json':
|
||||||
|
switch (table_name) {
|
||||||
|
case 'users':
|
||||||
|
case 'posts':
|
||||||
|
case 'comments':
|
||||||
|
result = func.require_module(`../data/${table_name}.json`)
|
||||||
|
if (key != -1) {
|
||||||
|
if (key == 'id')
|
||||||
|
{ // id is the index
|
||||||
|
if (value < result.length && value >= 0)
|
||||||
|
{
|
||||||
|
return result[value]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log("No object of this ID exists for the selected table")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result[func.find_key_value_pair(result, key, value)]
|
||||||
|
return -1 // This index doesn't exist
|
||||||
|
}
|
||||||
|
return result.slice(- config['data_request_limit'])
|
||||||
|
break;
|
||||||
|
case 'hitcount':
|
||||||
|
result = func.require_module('../data/data.json') // This file is actually called data.json
|
||||||
|
return result["hitcount"]
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Error, invalid requested")
|
||||||
|
return -1
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// NOT YET WORKING!
|
||||||
|
case 'mysql':
|
||||||
|
const mysql = require('mysql');
|
||||||
|
let con = mysql.createConnection({
|
||||||
|
host: config.database.host,
|
||||||
|
user: config.database.user,
|
||||||
|
password: config.database.password,
|
||||||
|
database: config.database.database,
|
||||||
|
});
|
||||||
|
|
||||||
|
con.connect(function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
if (data == "posts" || data == 'users' || data == 'comments') {
|
||||||
|
con.query(`SELECT * FROM ${data}`, function (err, result, fields) {
|
||||||
|
if (err) throw err;
|
||||||
|
result = Object.values(JSON.parse(JSON.stringify(result)))
|
||||||
|
console.log(result)
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (data == 'hitcount') {
|
||||||
|
con.query(`SELECT paramValue FROM params WHERE paramName = '${data}'`, function (err, result, fields) {
|
||||||
|
if (err) throw err;
|
||||||
|
result = Object.values(JSON.parse(JSON.stringify(result)))
|
||||||
|
console.log(result)
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writedata(data, data_to_write, index=-1) {
|
||||||
|
if (config["data_storage"] == "json") {
|
||||||
|
if (data == "posts" || data == 'users' || data == 'comments') {
|
||||||
|
if (index == -1) {
|
||||||
|
fs.writeFileSync(`../data/${data}.json`, JSON.stringify(data_to_write), 'utf-8')
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
else if (index >= 0) {
|
||||||
|
let result = getdata(data);
|
||||||
|
result[index] = data_to_write;
|
||||||
|
fs.writeFileSync(`../data/${data}.json`, JSON.stringify(result), 'utf-8')
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
else if (data == "hitcount") {
|
||||||
|
let other_data = func.require_module('../data/data.json') // This file is actually called data.json
|
||||||
|
other_data.hitcount = data_to_write
|
||||||
|
fs.writeFileSync('../data/data.json', JSON.stringify(other_data), 'utf-8')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Error, invalid requested")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/functions.js
102
src/functions.js
@@ -1,14 +1,33 @@
|
|||||||
import { createRequire } from 'module';
|
import { createRequire } from 'module';
|
||||||
const require = createRequire(import.meta.url)
|
const require = createRequire(import.meta.url)
|
||||||
|
const config = require("../config.json")
|
||||||
|
const fs = require('fs')
|
||||||
|
const locale = require(`../locales/${config.locale}.json`)
|
||||||
|
|
||||||
|
// This function requires a module without caching it
|
||||||
|
// So the server doesn't need to be restarted, though this can slow it down a bit.
|
||||||
|
// https://stackoverflow.com/a/16060619
|
||||||
|
export function require_module(module)
|
||||||
|
{
|
||||||
|
if (config.cache_data == false)
|
||||||
|
{
|
||||||
|
delete require.cache[require.resolve(module)];
|
||||||
|
return require(module);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return require(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The configuration defines a date format using the date-fns (a datetime library) syntax
|
// The configuration defines a date format using the date-fns (a datetime library) syntax
|
||||||
// eg "yyyy-MM-dd"
|
// eg "yyyy-MM-dd"
|
||||||
// this converts unix time (an integer) into a string that is formatted according to config.js
|
// this converts unix time (an integer) into a string that is formatted according to config.js
|
||||||
// uses date-fns's fromUnixTime() and format() functions
|
// uses date-fns's fromUnixTime() and format() functions
|
||||||
// returns the formatted date (string)
|
// returns the formatted date (string)
|
||||||
export function unix_time_to_date_format(unix_time) {
|
export function unix_time_to_date_format(unix_time)
|
||||||
|
{
|
||||||
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
||||||
const config = require("../config.json")
|
|
||||||
let date = fromUnixTime(unix_time)
|
let date = fromUnixTime(unix_time)
|
||||||
let formatted_date = format(date, config.date_format)
|
let formatted_date = format(date, config.date_format)
|
||||||
return formatted_date
|
return formatted_date
|
||||||
@@ -18,18 +37,19 @@ export function unix_time_to_date_format(unix_time) {
|
|||||||
// eg "Mon, 23 May 2025 18:59:59 +0100"
|
// eg "Mon, 23 May 2025 18:59:59 +0100"
|
||||||
// accepts unix time (int)
|
// accepts unix time (int)
|
||||||
// returns the formatted date (string)
|
// returns the formatted date (string)
|
||||||
export function unix_time_to_rss_date(unix_time) {
|
export function unix_time_to_rss_date(unix_time)
|
||||||
|
{
|
||||||
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
||||||
const config = require("../config.json")
|
|
||||||
let date = fromUnixTime(unix_time)
|
let date = fromUnixTime(unix_time)
|
||||||
let formatted_date = format(date, "EEE, dd MMM yyyy HH:mm:ss")
|
let formatted_date = format(date, "EEE, dd MMM yyyy HH:mm:ss")
|
||||||
return `${formatted_date} ${config.time_zone}`
|
return `${formatted_date} ${config.time_zone}`
|
||||||
}
|
}
|
||||||
// And again with atom's date format
|
// And again with atom's date format
|
||||||
export function unix_time_to_atom_date(unix_time) {
|
export function unix_time_to_atom_date(unix_time)
|
||||||
|
{
|
||||||
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
||||||
let date = fromUnixTime(unix_time)
|
let date = fromUnixTime(unix_time)
|
||||||
let formatted_date = format(date, "yyyy-MM-dd\\THH:mm:ss\\Z")
|
let formatted_date = format(date, "yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||||
return `${formatted_date}`
|
return `${formatted_date}`
|
||||||
}
|
}
|
||||||
// This function accepts a list of strings eg ["string1","string2,"string3"] (any length)
|
// This function accepts a list of strings eg ["string1","string2,"string3"] (any length)
|
||||||
@@ -37,12 +57,18 @@ export function unix_time_to_atom_date(unix_time) {
|
|||||||
// eg "<a href="/tag/string1">string1</a>, <a href="/tag/string2">string2</a>, <a href="/tag/string3">string3</a>"
|
// eg "<a href="/tag/string1">string1</a>, <a href="/tag/string2">string2</a>, <a href="/tag/string3">string3</a>"
|
||||||
// this is so you can have a list of tags that each point to their individual tag page
|
// this is so you can have a list of tags that each point to their individual tag page
|
||||||
// returns: string
|
// returns: string
|
||||||
export function hyperlink_tags(tags) {
|
export function render_tags(tags)
|
||||||
|
{
|
||||||
let string = "" // Initialises the string
|
let string = "" // Initialises the string
|
||||||
for (let tag_index = 0; tag_index < tags.length; tag_index++) { // Loop over each tag
|
if (tags.length == 1 && tags[0] == "")
|
||||||
string += `<a href="/tag/${tags[tag_index]}">${tags[tag_index]}</a>` // 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 = ''; // If there are no tags, output nothing
|
||||||
string += ", ";
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (let tag_index = 0; tag_index < tags.length; tag_index++)
|
||||||
|
{ // Loop over each tag
|
||||||
|
string += `<a href="/tag/${tags[tag_index].trim()}">#${tags[tag_index].trim()}</a> ` // Adds the tag to the string as a HTML href
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
@@ -52,21 +78,27 @@ export function hyperlink_tags(tags) {
|
|||||||
// This function returns the username for a given userID by looping over every user
|
// 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 present, it returns the index of the user (integer)
|
||||||
// if the user is not present it returns -1
|
// if the user is not present it returns -1
|
||||||
export function get_userID(username) {
|
export function get_userID(username)
|
||||||
const users = require("../data/users.json")
|
{
|
||||||
for (let i = 0; i < users.length; i++) { // Loop over every user
|
const users = require_module("../data/users.json")
|
||||||
if (users[i]['username'] == username) {
|
for (let i = 0; i < users.length; i++)
|
||||||
|
{ // Loop over every user
|
||||||
|
if (users[i]['username'] == username)
|
||||||
|
{
|
||||||
return i // If the username matches then return the index of that user
|
return i // If the username matches then return the index of that user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1 // If user is not present, return -1
|
return -1 // If user is not present, return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// This escapes some potentially dangerous HTML characters with their HTML entities
|
// This escapes some potentially dangerous HTML characters with their HTML entities
|
||||||
// https://www.freeformatter.com/html-entities.html
|
// https://www.freeformatter.com/html-entities.html
|
||||||
// accepts a string
|
// accepts a string
|
||||||
// returns a string with some character replaced by their entities
|
// returns a string with some character replaced by their entities
|
||||||
export function escape_input(input) {
|
export function escape_input(input)
|
||||||
|
{
|
||||||
let output = input
|
let output = input
|
||||||
|
.replaceAll("&", "&") // This must be first
|
||||||
.replaceAll("<", "<")
|
.replaceAll("<", "<")
|
||||||
.replaceAll(">", ">")
|
.replaceAll(">", ">")
|
||||||
.replaceAll("\\", "\")
|
.replaceAll("\\", "\")
|
||||||
@@ -76,3 +108,41 @@ export function escape_input(input) {
|
|||||||
.replaceAll("%", "%")
|
.replaceAll("%", "%")
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render comment content by replacing the >> int with a url link to that comment
|
||||||
|
// Syntax: ">> postID-commentID"
|
||||||
|
export function render_comment(comment_content)
|
||||||
|
{
|
||||||
|
return comment_content
|
||||||
|
.replaceAll(/>> ([0-9]*)-([0-9]*)/g, "<a href='/comment/$1-$2'>>> $1-$2</a>")
|
||||||
|
.replaceAll(/>>([0-9]*)-([0-9]*)/g, "<a href='/comment/$1-$2'>>>$1-$2</a>")
|
||||||
|
.replaceAll(/>> ([0-9]*)-([0-9]*)/g, "<a href='/comment/$1-$2'>>> $1-$2</a>")
|
||||||
|
.replaceAll(/>>([0-9]*)-([0-9]*)/g, "<a href='/comment/$1-$2'>>>$1-$2</a>")
|
||||||
|
.replaceAll("\n", "<br/>")
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Renders a string into markdown using markdown-it library
|
||||||
|
export function render_md(content)
|
||||||
|
{
|
||||||
|
const markdownit = require("markdown-it")
|
||||||
|
const md = markdownit
|
||||||
|
({ // this is just defining some options for markdown-it, should I add this to config.json?
|
||||||
|
html: false,
|
||||||
|
xhtmlOut: false,
|
||||||
|
breaks: true,
|
||||||
|
linkify: false,
|
||||||
|
typographer: true,
|
||||||
|
quotes: locale.quotes,
|
||||||
|
})
|
||||||
|
return md.render(content)
|
||||||
|
};
|
||||||
|
|
||||||
|
export function find_key_value_pair(data_array, key, value) {
|
||||||
|
for (let i = 0; i < data_array.length; i++) {
|
||||||
|
if (data_array[i][key] == value) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
// 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("../data/users.js");
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log("Creating users file")
|
|
||||||
fs.writeFileSync(`../data/users.json`, `{\n"users": []\n}`)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const posts = require("../data/posts.json");
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log("Creating posts file")
|
|
||||||
fs.writeFileSync(`../data/posts.json`, `{\n"posts": []\n}`)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const comments = require("../data/comments.json");
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log("Creating comments file")
|
|
||||||
fs.writeFileSync(`../data/comments.json`, `{\n"comments": [],\n"counter": 0}`)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const config = require("../data/config.js");
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log("Copying the example config to config.js")
|
|
||||||
console.log("!!! PLEASE MODIFY config.js TO YOUR NEEDS !!!")
|
|
||||||
fs.copyFile('example-config.js', 'config.js', (err) => {
|
|
||||||
console.log("Error copying file")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log("Successfully initialised")
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
229
src/routes/form_actions.js
Normal file
229
src/routes/form_actions.js
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const config = require('../../config')
|
||||||
|
const data = require('../data')
|
||||||
|
const func = require('../functions')
|
||||||
|
|
||||||
|
let users = require('../../data/users.json');
|
||||||
|
let posts = require('../../data/posts.json');
|
||||||
|
let comments = require('../../data/comments.json');
|
||||||
|
let other_data = require('../../data/data.json');
|
||||||
|
|
||||||
|
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
||||||
|
const fs = require('fs')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
////////////////////// Form actions /////////////////////////
|
||||||
|
router.post("/submit_comment", (req,res) => {
|
||||||
|
const unix_timestamp = getUnixTime(new Date())
|
||||||
|
const postID = parseInt(req.body.post_index)
|
||||||
|
const content = func.escape_input(req.body.content)
|
||||||
|
let name = func.escape_input(req.body.name)
|
||||||
|
// Give the user the default username if they left that bit blank
|
||||||
|
if (name == "" || typeof name == 'undefined') {
|
||||||
|
name = config.default_commenter_username
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check there is actually content in the comment
|
||||||
|
if (content != '' && typeof content != 'undefined') {
|
||||||
|
let comments = data.getdata('comments')
|
||||||
|
|
||||||
|
new_comment = {
|
||||||
|
"name": name,
|
||||||
|
"content": content,
|
||||||
|
"id": comments[postID]['comments'].length,
|
||||||
|
"pubdate": unix_timestamp,
|
||||||
|
};
|
||||||
|
comments[postID]['comments'].push(new_comment);
|
||||||
|
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`, 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect(301,`/post/${req.body.post_index}`)
|
||||||
|
}); // /submit_comment
|
||||||
|
|
||||||
|
router.post("/submit_post", (req,res) => {
|
||||||
|
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
||||||
|
const username = func.escape_input(req.body.username)
|
||||||
|
const title = func.escape_input(req.body.title)
|
||||||
|
const content = req.body.content
|
||||||
|
const tags = func.escape_input(req.body.tags).split(',').map(str => str.trim());
|
||||||
|
const unix_timestamp = getUnixTime(new Date())
|
||||||
|
|
||||||
|
if (func.get_userID(username) == -1) {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.user_doesnt_exit,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (users[func.get_userID(username)]['hash'] == password) { // Password matches
|
||||||
|
console.log(username, "is submitting a post titled:", title);
|
||||||
|
id = posts.length
|
||||||
|
posts.push({
|
||||||
|
"id": id,
|
||||||
|
"userID": func.get_userID(username),
|
||||||
|
"title": title,
|
||||||
|
"content": content,
|
||||||
|
"pubdate": unix_timestamp,
|
||||||
|
"editdate": unix_timestamp,
|
||||||
|
"tags": tags,
|
||||||
|
})
|
||||||
|
fs.writeFileSync(`../data/posts.json`, `${JSON.stringify(posts)}`, 'utf-8');
|
||||||
|
comments.push({'id': id, 'comments': []})
|
||||||
|
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`)
|
||||||
|
res.redirect(302, "/");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.incorrect_password,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}); // /submit_post
|
||||||
|
|
||||||
|
router.post("/submit_signup", (req,res) => {
|
||||||
|
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
||||||
|
const username = func.escape_input(req.body.username)
|
||||||
|
const prettyname = func.escape_input(req.body.prettyname)
|
||||||
|
const description = req.body.description
|
||||||
|
|
||||||
|
// Check that signups are allowed
|
||||||
|
if (config.allow_signup == true) {
|
||||||
|
// func.get_userID will return -1 if the user does not exist
|
||||||
|
// so this checks that the user does not exist
|
||||||
|
if (func.get_userID(username) == -1) {
|
||||||
|
users.push({
|
||||||
|
"id": users.length,
|
||||||
|
"username": username,
|
||||||
|
"prettyname": prettyname,
|
||||||
|
"hash": password,
|
||||||
|
"description": description,
|
||||||
|
})
|
||||||
|
fs.writeFileSync(`../data/users.json`, `${JSON.stringify(users)}`, 'utf-8');
|
||||||
|
res.redirect(301, `/user/${username}`)
|
||||||
|
}
|
||||||
|
// if the user does exist then
|
||||||
|
else {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.user_exists,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (config.allow_signup == false) {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.signups_unavailable,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// If allow_signup is undefined or not a boolean, error
|
||||||
|
else {
|
||||||
|
res.redirect(301,"/")
|
||||||
|
console.log("Error, invalid value for allow_signup (bool)")
|
||||||
|
}
|
||||||
|
}); // /submit_signup
|
||||||
|
|
||||||
|
router.post("/submit_edit_user", (req,res) => {
|
||||||
|
// Get the form info
|
||||||
|
const password = crypto.createHash("sha512").update(req.body.password).digest("hex");
|
||||||
|
const userID = func.escape_input(req.body.userID)
|
||||||
|
const description = req.body.description
|
||||||
|
const prettyname = func.escape_input(req.body.prettyname)
|
||||||
|
const delete_bool = req.body.delete
|
||||||
|
|
||||||
|
if (userID >= 0) { // The user exists
|
||||||
|
if (password == users[userID]['hash']) { // password matches
|
||||||
|
console.log(userID, " (userID) is modifying their account")
|
||||||
|
users[userID]["prettyname"] = prettyname;
|
||||||
|
users[userID]["description"] = description;
|
||||||
|
|
||||||
|
if (delete_bool == true) {
|
||||||
|
// Delete the user
|
||||||
|
users[userID] = {"id": userID,"deleted": true}
|
||||||
|
// Delete all their posts
|
||||||
|
for (let postid = 0; postid < posts.length; postid++) { // loop over all posts
|
||||||
|
if (posts[postid]['userID'] == userID) { // if userID matches
|
||||||
|
posts[postid] = {"id": postid, "deleted": true} // delete the post
|
||||||
|
comments[postid] = [] // the comments for this post should also be deleted
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Write these changes
|
||||||
|
fs.writeFileSync(`../data/users.json`, `${JSON.stringify(users)}`, 'utf-8');
|
||||||
|
fs.writeFileSync(`../data/posts.json`, `${JSON.stringify(posts)}`, 'utf-8');
|
||||||
|
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`, 'utf-8');
|
||||||
|
res.redirect(301,`/user/${users[userID]["username"]}`)
|
||||||
|
}
|
||||||
|
else { // password does not match
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.incorrect_password,
|
||||||
|
config
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.user_doesnt_exist,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}); // /submit_delete_account
|
||||||
|
|
||||||
|
router.post("/submit_edit_post", (req,res) => {
|
||||||
|
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
||||||
|
const postID = req.body.postID
|
||||||
|
const userID = req.body.userID
|
||||||
|
const title = func.escape_input(req.body.title)
|
||||||
|
const content = req.body.content
|
||||||
|
const tags = func.escape_input(req.body.tags).split(",").map(str => str.trim());
|
||||||
|
const delete_bool = req.body.delete
|
||||||
|
const unix_timestamp = getUnixTime(new Date())
|
||||||
|
console.log(users[userID]['prettyname'], "is editting the post titled:", title);
|
||||||
|
|
||||||
|
if (users[userID]['hash'] == password) { // password matches
|
||||||
|
let post = posts[postID]
|
||||||
|
post['title'] = title
|
||||||
|
post['content'] = content
|
||||||
|
post['tags'] = tags
|
||||||
|
post['editdate'] = unix_timestamp
|
||||||
|
if (typeof delete_bool != "undefined") {
|
||||||
|
console.log("Deleting post!")
|
||||||
|
posts[postID] = {"id": post["id"], "deleted": true}
|
||||||
|
comments[postID] = [];
|
||||||
|
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`, 'utf-8');
|
||||||
|
}
|
||||||
|
fs.writeFileSync(`../data/posts.json`, `${JSON.stringify(posts)}`, 'utf-8');
|
||||||
|
res.redirect(302, "/");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.incorrect_password,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}); // /submit_edit
|
||||||
|
|
||||||
|
router.get('/search', (req, res) => {
|
||||||
|
const search_term = func.escape_input(req.query.q); // 'q' is the parameter name
|
||||||
|
let search_type = req.query.type; // eg 'post', 'user'
|
||||||
|
if (typeof search_type == 'string') { // Make the search_term an array
|
||||||
|
search_type = [ search_type ]
|
||||||
|
}
|
||||||
|
if (typeof search_type == 'undefined') { // Default to all of the types
|
||||||
|
search_type = ['user', 'post'];
|
||||||
|
}
|
||||||
|
console.log('searching for: ', search_term);
|
||||||
|
const search_results = data.searchdata(search_term, search_type); // data.searchdata returns an array of search results
|
||||||
|
|
||||||
|
res.render('pages/search', {
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
search_results,
|
||||||
|
search_term,
|
||||||
|
search_type,
|
||||||
|
})
|
||||||
|
|
||||||
|
}); // /search
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
58
src/routes/forms.js
Normal file
58
src/routes/forms.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const config = require('../../config')
|
||||||
|
const data = require('../data')
|
||||||
|
const func = require('../functions')
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
///////////////////// Form pages ////////////////////////////
|
||||||
|
router.get(config.new_post_url, (req,res) => {
|
||||||
|
res.render("forms/new_post", {
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
});
|
||||||
|
}); // /post
|
||||||
|
router.get(config.signup_url, (req,res) => {
|
||||||
|
// if the server does allow signup
|
||||||
|
if (config.allow_signup == true) {
|
||||||
|
// Send the page for signing up to the server
|
||||||
|
res.render("forms/signup", {
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// if the server does not allow signup
|
||||||
|
else if (config.allow_signup == false) {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.signups_unavailable,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// If allow_signup is undefined or not a boolean, error
|
||||||
|
else {
|
||||||
|
res.redirect(301,"/")
|
||||||
|
console.log("Error, invalid value for allow_signup (bool)")
|
||||||
|
}
|
||||||
|
}); // /signup
|
||||||
|
router.get(`${config.edit_account_base_url}/:user_id`, (req,res) => {
|
||||||
|
const userID = parseInt(req.params.user_id);
|
||||||
|
res.render("forms/edit_account", {
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
user: data.getdata('users', 'id', userID),
|
||||||
|
userID
|
||||||
|
});
|
||||||
|
}); // /delete_account
|
||||||
|
router.get(`${config.edit_post_base_url}/:post_id`, (req,res) => {
|
||||||
|
const postID = req.params.post_id
|
||||||
|
const post = data.getdata('posts','id', postID)
|
||||||
|
const user = data.getdata('users', 'id', post.userID)
|
||||||
|
res.render("forms/edit_post", {
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
post,
|
||||||
|
postID,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
}); // /edit/:post_id
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
37
src/routes/indexes.js
Normal file
37
src/routes/indexes.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const config = require('../../config')
|
||||||
|
const data = require('../data')
|
||||||
|
const func = require('../functions')
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
///////////////////// Page index's ///////////////////////
|
||||||
|
router.get("/index/pages", (req,res) => {
|
||||||
|
res.render("indexes/all_pages", {
|
||||||
|
config,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
users: data.getdata('users'),
|
||||||
|
comments: data.getdata('comments'),
|
||||||
|
});
|
||||||
|
}); // /index/pages
|
||||||
|
router.get("/index/posts", (req,res) => {
|
||||||
|
res.render("indexes/posts", {
|
||||||
|
config,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
});
|
||||||
|
}); // /index/posts
|
||||||
|
router.get("/index/users", (req,res) => {
|
||||||
|
res.render("indexes/users", {
|
||||||
|
config,
|
||||||
|
users: data.getdata('users'),
|
||||||
|
});
|
||||||
|
}); // /index/users
|
||||||
|
router.get("/index/comments", (req,res) => {
|
||||||
|
res.render("indexes/comments", {
|
||||||
|
config,
|
||||||
|
comments: data.getdata('comments'),
|
||||||
|
});
|
||||||
|
}); // /index/comments
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
151
src/routes/standard_pages.js
Normal file
151
src/routes/standard_pages.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const config = require('../../config')
|
||||||
|
const data = require('../data')
|
||||||
|
const func = require('../functions')
|
||||||
|
|
||||||
|
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
///////////////////// Standard Pages //////////////////////
|
||||||
|
|
||||||
|
// Timeline
|
||||||
|
router.get("/", (req,res) => {
|
||||||
|
// Increment the hitcount
|
||||||
|
if (config.enable_hitcount) {
|
||||||
|
data.increment_hitcount()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render("pages/timeline",
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
posts: data.getdata("posts"),
|
||||||
|
users: data.getdata("users"),
|
||||||
|
comments: data.getdata("comments"),
|
||||||
|
hitcount: data.getdata("hitcount"),
|
||||||
|
fromUnixTime,
|
||||||
|
format,
|
||||||
|
getUnixTime,
|
||||||
|
func,
|
||||||
|
})
|
||||||
|
}); // /
|
||||||
|
|
||||||
|
// Users
|
||||||
|
router.get("/user/:username", (req, res) => {
|
||||||
|
const userID = func.get_userID(req.params.username)
|
||||||
|
let user = data.getdata('users', 'id', userID)
|
||||||
|
if (userID != -1) {
|
||||||
|
res.render("pages/user",
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
user,
|
||||||
|
userID: userID,
|
||||||
|
comments: data.getdata('comments'),
|
||||||
|
fromUnixTime,
|
||||||
|
format,
|
||||||
|
getUnixTime,
|
||||||
|
func,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (userID == -1) {
|
||||||
|
res.render("partials/message",
|
||||||
|
{
|
||||||
|
message: locale.user_doesnt_exist,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}); // /user/:username
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
router.get("/post/:post_index", (req, res) => {
|
||||||
|
const postID = parseInt(req.params.post_index)
|
||||||
|
let post = data.getdata('posts','id', postID)
|
||||||
|
if (post == 1) { // data.getdata returns error code 1 if nothing is available
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.post_doesnt_exist,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (typeof post["deleted"] == "undefined" || post["deleted"] == false) {
|
||||||
|
if (config.enable_hitcount) {
|
||||||
|
data.increment_hitcount(postID)
|
||||||
|
}
|
||||||
|
res.render("pages/post",
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
post,
|
||||||
|
postID,
|
||||||
|
user: data.getdata('users','id', post.userID),
|
||||||
|
comments: data.getdata('comments','id', postID)["comments"],
|
||||||
|
fromUnixTime,
|
||||||
|
format,
|
||||||
|
getUnixTime,
|
||||||
|
func,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Error loading page")
|
||||||
|
res.redirect(301,"/")
|
||||||
|
}
|
||||||
|
}); // /post/:post_index
|
||||||
|
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
router.get("/tag/:tag", (req,res) => {
|
||||||
|
const tag = req.params.tag
|
||||||
|
res.render("pages/tag",
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
tag,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
users: data.getdata('users'),
|
||||||
|
comments: data.getdata('comments'),
|
||||||
|
fromUnixTime,
|
||||||
|
format,
|
||||||
|
getUnixTime,
|
||||||
|
func,
|
||||||
|
})
|
||||||
|
}); // /tag/:tag
|
||||||
|
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
router.get("/comment/:postID-:commentID", (req,res) => {
|
||||||
|
const commentID = parseInt(req.params.commentID);
|
||||||
|
const postID = parseInt(req.params.postID);
|
||||||
|
|
||||||
|
let posts_comments = data.getdata('comments', 'id', postID)["comments"]
|
||||||
|
let comment = 1
|
||||||
|
// For loop to find the comment with matching ID
|
||||||
|
posts_comments.forEach((current_comment, index) => {
|
||||||
|
if (current_comment.id == commentID) {
|
||||||
|
comment = posts_comments[index]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// If comment doesn't exist, show error
|
||||||
|
if (comment == 1 || posts_comments == 1) { // Comment of this ID was not found
|
||||||
|
res.render("partials/message", {
|
||||||
|
config,
|
||||||
|
message: locale.comment_doesnt_exist,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.render("pages/comment",
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
locale,
|
||||||
|
comment,
|
||||||
|
postID,
|
||||||
|
commentID,
|
||||||
|
fromUnixTime,
|
||||||
|
format,
|
||||||
|
getUnixTime,
|
||||||
|
func,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
88
src/routes/syndication.js
Normal file
88
src/routes/syndication.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const config = require('../../config')
|
||||||
|
const data = require('../data')
|
||||||
|
const func = require('../functions')
|
||||||
|
|
||||||
|
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
////////////////////// SYNDICATION ////////////////////////
|
||||||
|
// global RSS protocol gets
|
||||||
|
router.get("/rss", (req,res) => {
|
||||||
|
if (config.rss == false) {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.rss_disabled,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.setHeader('content-type', 'application/rss+xml');
|
||||||
|
res.render("syndication/global_rss", {
|
||||||
|
config,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
func,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// user RSS protocol gets
|
||||||
|
router.get("/user/:username/rss", (req,res) => {
|
||||||
|
const username = req.params.username;
|
||||||
|
const userID = func.get_userID(username);
|
||||||
|
if (config.rss == false) {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.rss_disabled,
|
||||||
|
config: config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.setHeader('content-type', 'application/rss+xml');
|
||||||
|
res.render("syndication/user_rss", {
|
||||||
|
config,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
func,
|
||||||
|
userID,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// global ATOM protocol gets
|
||||||
|
router.get("/atom", (req,res) => {
|
||||||
|
if (config.atom == false) {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.atom_disabled,
|
||||||
|
config: config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.setHeader('content-type', 'application/rss+xml');
|
||||||
|
res.render("syndication/global_atom", {
|
||||||
|
config,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
func,
|
||||||
|
getUnixTime,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// user ATOM protocol gets
|
||||||
|
router.get("/user/:username/atom", (req,res) => {
|
||||||
|
const username = req.params.username;
|
||||||
|
const userID = func.get_userID(username);
|
||||||
|
if (config.atom == false) {
|
||||||
|
res.render("partials/message", {
|
||||||
|
message: locale.atom_disabled,
|
||||||
|
config: config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.setHeader('content-type', 'application/rss+xml');
|
||||||
|
res.render("syndication/user_atom", {
|
||||||
|
config,
|
||||||
|
posts: data.getdata('posts'),
|
||||||
|
func,
|
||||||
|
userID,
|
||||||
|
getUnixTime,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
413
src/server.js
413
src/server.js
@@ -1,7 +1,6 @@
|
|||||||
// Get the libraries
|
// Get the libraries
|
||||||
const fs = require('fs'); // For modifying and reading files
|
const fs = require('fs'); // For modifying and reading files
|
||||||
const express = require('express'); // For running a webserver in nodejs
|
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
|
const crypto = require('crypto'); // For encrypting passwords, I use sha512
|
||||||
// fromUnixTime(): Create a date from a Unix timestamp (in seconds). Decimal values will be discarded.
|
// fromUnixTime(): Create a date from a Unix timestamp (in seconds). Decimal values will be discarded.
|
||||||
// format(): Return the formatted date string in the given format. The result may vary by locale.
|
// format(): Return the formatted date string in the given format. The result may vary by locale.
|
||||||
@@ -10,46 +9,19 @@ const crypto = require('crypto'); // For encrypting passwords, I use sha512
|
|||||||
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library
|
||||||
const ejs = require("ejs")
|
const ejs = require("ejs")
|
||||||
const func = require("./functions.js")
|
const func = require("./functions.js")
|
||||||
const init = require("./initialise.js")
|
const data = require("./data.js")
|
||||||
|
|
||||||
// There's only one possible argument, so we can just check if the user passed that one
|
config = require('../config.json');
|
||||||
// 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") {
|
|
||||||
init.initialise() // Creates any files such users.js, posts.js, comments.js or config.js if they are not present
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 // contains a list of comments
|
|
||||||
let config // contains a set of configuration for the site, see example-config.js for an example
|
|
||||||
|
|
||||||
|
// Import the locale
|
||||||
try {
|
try {
|
||||||
// We're going to try and import the modules,
|
locale = require(`../locales/${config.locale}.json`);
|
||||||
users = require('../data/users.json');
|
|
||||||
posts = require('../data/posts.json');
|
|
||||||
comments = require('../data/comments.json');
|
|
||||||
config = require('../config.json');
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
// if they don't all import then
|
console.log("This locale doesn't exist, if you want to create it then you can create a PR")
|
||||||
// inform the user to pass --first-time and exit with an error code
|
console.log("Locale selected: ", config.locale)
|
||||||
console.log("A file is missing!")
|
|
||||||
console.log("Run with --first-time to initialise the program")
|
|
||||||
console.log(error)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://showdownjs.com/docs/available-options
|
|
||||||
let converter = new showdown.Converter({
|
|
||||||
simpleLineBreaks: true, // Parse line breaks as <br/> 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
|
|
||||||
headerLevelStart: 3, //Set starting level for the heading tags.
|
|
||||||
})
|
|
||||||
|
|
||||||
// Define stuff to do with express (nodejs webserver)
|
// Define stuff to do with express (nodejs webserver)
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
@@ -59,341 +31,58 @@ app.use(express.static(config.root_path));
|
|||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.set('views', '../views')
|
app.set('views', '../views')
|
||||||
|
|
||||||
////////////////////// SYNDICATION ////////////////////////
|
// Express JS routes
|
||||||
// RSS protocol gets
|
const syndication_routes = require('./routes/syndication.js');
|
||||||
app.get("/rss", (req,res) => {
|
const indexes_routes = require('./routes/indexes.js');
|
||||||
if (config.rss == false) {
|
const standard_pages_routes = require('./routes/standard_pages.js');
|
||||||
res.render("partials/message", {
|
const forms_routes = require('./routes/forms.js');
|
||||||
message: config.string.rss_disabled,
|
const form_actions_routes = require('./routes/form_actions.js');
|
||||||
config: config,
|
|
||||||
})
|
app.use('/', syndication_routes);
|
||||||
|
app.use('/', indexes_routes);
|
||||||
|
app.use('/', standard_pages_routes);
|
||||||
|
app.use('/', forms_routes);
|
||||||
|
app.use('/', form_actions_routes);
|
||||||
|
|
||||||
|
function perform_checks()
|
||||||
|
{
|
||||||
|
console.log("Performing startup checks...")
|
||||||
|
exit_flag = false
|
||||||
|
required_values = ['site_admin','seperator','site_name','site_url','locale','port','cache_data','allow_signup','site_description','request_data_limit','enable_hitcount','charset','root_path','edit_account_base_url','new_post_url','signup_url','default_commenter_username','rss','atom','date_format','time_zone','css']
|
||||||
|
// Perform some standard checks:
|
||||||
|
|
||||||
|
// data_storage
|
||||||
|
switch (config.data_storage)
|
||||||
|
{
|
||||||
|
case 'mysql':
|
||||||
|
case 'json':
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log("[ ERROR ] invalid value in `data_storage`\nPlease modify config.json. Value should be 'mysql' or 'json'.")
|
||||||
|
exit_flag = true
|
||||||
}
|
}
|
||||||
else {
|
// auto_generated
|
||||||
res.setHeader('content-type', 'application/rss+xml');
|
if (config.auto_generated)
|
||||||
res.render("syndication/global_rss", {
|
{
|
||||||
config,
|
console.log("[ ERROR ] `autogenerated` option set to true\nplease edit the config.json file to include your relevant information, then set to false or remove the autogenerated option.")
|
||||||
posts,
|
exit_flag = true
|
||||||
converter,
|
|
||||||
func,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// ATOM protocol gets
|
|
||||||
app.get("/atom", (req,res) => {
|
|
||||||
if (config.rss == false) {
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.rss_disabled,
|
|
||||||
config: config,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
else {
|
// Check required values are present
|
||||||
res.setHeader('content-type', 'application/rss+xml');
|
required_values.forEach((value) => { // Use a loop to check each required value is present
|
||||||
res.render("syndication/global_atom", {
|
if (typeof config[value] == 'undefined') {
|
||||||
config,
|
exit_flag = true
|
||||||
posts,
|
console.log(`[ ERROR ] \`${value}\` is undefined\nPlease set it to something, read the documentation for help.`)
|
||||||
converter,
|
|
||||||
func,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////// Standard Pages //////////////////////
|
|
||||||
app.get("/", (req,res) => {
|
|
||||||
// Increment the hitcount
|
|
||||||
if (config.enable_hitcount) {
|
|
||||||
let hitcount = parseInt(fs.readFileSync('../data/hitcount.txt'))
|
|
||||||
hitcount += 1
|
|
||||||
console.log(`/ Is loaded, hitcount: ${hitcount}`)
|
|
||||||
fs.writeFileSync(`../data/hitcount.txt`, `${hitcount}`, 'utf-8');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render("pages/timeline",
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
posts,
|
|
||||||
users,
|
|
||||||
comments: comments.comments,
|
|
||||||
hitcount: fs.readFileSync("../data/hitcount.txt"),
|
|
||||||
fromUnixTime,
|
|
||||||
format,
|
|
||||||
getUnixTime,
|
|
||||||
func,
|
|
||||||
converter,
|
|
||||||
})
|
|
||||||
}); // /
|
|
||||||
app.get("/user/:username", (req, res) => {
|
|
||||||
const userID = func.get_userID(req.params.username)
|
|
||||||
console.log(userID)
|
|
||||||
console.log(users[userID].prettyname)
|
|
||||||
res.render("pages/user",
|
|
||||||
{
|
|
||||||
config: config,
|
|
||||||
posts: posts,
|
|
||||||
user: users[userID],
|
|
||||||
userID: userID,
|
|
||||||
comments: comments.comments,
|
|
||||||
fromUnixTime: fromUnixTime,
|
|
||||||
format: format,
|
|
||||||
getUnixTime: getUnixTime,
|
|
||||||
func,
|
|
||||||
converter,
|
|
||||||
})
|
|
||||||
}); // /user/:username
|
|
||||||
app.get("/post/:post_index", (req, res) => {
|
|
||||||
const postID = req.params.post_index
|
|
||||||
res.render("pages/post",
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
post: posts[postID],
|
|
||||||
postID: postID,
|
|
||||||
user: users[posts[postID].userID],
|
|
||||||
comments: comments.comments[postID],
|
|
||||||
fromUnixTime,
|
|
||||||
format,
|
|
||||||
getUnixTime,
|
|
||||||
func,
|
|
||||||
converter,
|
|
||||||
})
|
|
||||||
}); // /post/:post_index
|
|
||||||
app.get("/tag/:tag", (req,res) => {
|
|
||||||
const tag = req.params.tag
|
|
||||||
res.render("pages/tag",
|
|
||||||
{
|
|
||||||
config: config,
|
|
||||||
tag: tag,
|
|
||||||
posts: posts,
|
|
||||||
users: users,
|
|
||||||
comments: comments.comments,
|
|
||||||
fromUnixTime: fromUnixTime,
|
|
||||||
format: format,
|
|
||||||
getUnixTime: getUnixTime,
|
|
||||||
func,
|
|
||||||
converter,
|
|
||||||
})
|
|
||||||
}); // /tag/:tag
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////// Form pages ////////////////////////////
|
|
||||||
app.get(config.new_post_url, (req,res) => {
|
|
||||||
res.render("forms/new_post", {
|
|
||||||
config
|
|
||||||
});
|
|
||||||
}); // /post
|
|
||||||
app.get(config.signup_url, (req,res) => {
|
|
||||||
// if the server does allow signup
|
|
||||||
if (config.allow_signup == true) {
|
|
||||||
// Send the page for signing up to the server
|
|
||||||
res.render("forms/signup", {
|
|
||||||
config
|
|
||||||
});
|
});
|
||||||
|
if (exit_flag)
|
||||||
|
{
|
||||||
|
console.log("Exiting due to errors.")
|
||||||
|
process.exit(1)
|
||||||
}
|
}
|
||||||
// if the server does not allow signup
|
}
|
||||||
else if (config.allow_signup == false) {
|
perform_checks()
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.signups_unavailable,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// If allow_signup is undefined or not a boolean, error
|
|
||||||
else {
|
|
||||||
res.redirect(301,"/")
|
|
||||||
console.log("Error, invalid value for allow_signup (bool)")
|
|
||||||
}
|
|
||||||
}); // /signup
|
|
||||||
app.get(`${config.edit_account_base_url}/:user_id`, (req,res) => {
|
|
||||||
const userID = parseInt(req.params.user_id);
|
|
||||||
res.render("forms/edit_account", { config, user: users[userID], userID });
|
|
||||||
}); // /delete_account
|
|
||||||
app.get(`${config.edit_post_base_url}/:post_id`, (req,res) => {
|
|
||||||
const post_id = req.params.post_id
|
|
||||||
const post = posts[post_id]
|
|
||||||
const user = users[post['userID']]
|
|
||||||
res.render("forms/edit_post", {
|
|
||||||
config,
|
|
||||||
post,
|
|
||||||
post_id,
|
|
||||||
user,
|
|
||||||
});
|
|
||||||
}); // /edit/:post_id
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////// Form actions /////////////////////////
|
|
||||||
app.post("/submit_comment", (req,res) => {
|
|
||||||
const unix_timestamp = getUnixTime(new Date())
|
|
||||||
let name = func.escape_input(req.body.name)
|
|
||||||
if (name == "") {
|
|
||||||
name = config.default_commenter_username
|
|
||||||
}
|
|
||||||
new_comment = {
|
|
||||||
"name": name,
|
|
||||||
"content": func.escape_input(req.body.content),
|
|
||||||
"id": comments.counter,
|
|
||||||
"pubdate": unix_timestamp
|
|
||||||
};
|
|
||||||
let counter = comments.counter+1;
|
|
||||||
comments.comments[req.body.post_index].push(new_comment);
|
|
||||||
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`, 'utf-8');
|
|
||||||
|
|
||||||
res.redirect(301,`/post/${req.body.post_index}`)
|
|
||||||
}); // /submit_comment
|
|
||||||
app.post("/submit_post", (req,res) => {
|
|
||||||
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
|
||||||
const username = func.escape_input(req.body.username)
|
|
||||||
const title = func.escape_input(req.body.title)
|
|
||||||
const content = func.escape_input(req.body.content)
|
|
||||||
const tags = func.escape_input(req.body.tags).split(',');
|
|
||||||
const unix_timestamp = getUnixTime(new Date())
|
|
||||||
|
|
||||||
if (func.get_userID(username) == -1) {
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.user_doesnt_exit,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (users[func.get_userID(username)]['hash'] == password) { // Password matches
|
|
||||||
console.log(username, "is submitting a post titled:", title);
|
|
||||||
posts.push({
|
|
||||||
"userID": func.get_userID(username),
|
|
||||||
"title": title,
|
|
||||||
"content": content,
|
|
||||||
"pubdate": unix_timestamp,
|
|
||||||
"editdate": unix_timestamp,
|
|
||||||
"tags": tags,
|
|
||||||
})
|
|
||||||
fs.writeFileSync(`../data/posts.json`, `${JSON.stringify(posts)}`, 'utf-8');
|
|
||||||
comments.comments.push([])
|
|
||||||
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`)
|
|
||||||
res.redirect(302, "/");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.incorrect_password,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}); // /submit_post
|
|
||||||
app.post("/submit_signup", (req,res) => {
|
|
||||||
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
|
||||||
const username = func.escape_input(req.body.username)
|
|
||||||
const prettyname = func.escape_input(req.body.prettyname)
|
|
||||||
const description = func.escape_input(req.body.description)
|
|
||||||
|
|
||||||
// Check that signups are allowed
|
|
||||||
if (config.allow_signup == true) {
|
|
||||||
// func.get_userID will return -1 if the user does not exist
|
|
||||||
// so this checks that the user does not exist
|
|
||||||
if (func.get_userID(username) == -1) {
|
|
||||||
users.push({
|
|
||||||
"username": username,
|
|
||||||
"prettyname": prettyname,
|
|
||||||
"hash": password,
|
|
||||||
"description": description,
|
|
||||||
})
|
|
||||||
fs.writeFileSync(`../data/users.json`, `${JSON.stringify(users)}`, 'utf-8');
|
|
||||||
res.redirect(301, `/user/${username}`)
|
|
||||||
}
|
|
||||||
// if the user does exist then
|
|
||||||
else {
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.user_exists,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (config.allow_signup == false) {
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.signups_unavailable,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// If allow_signup is undefined or not a boolean, error
|
|
||||||
else {
|
|
||||||
res.redirect(301,"/")
|
|
||||||
console.log("Error, invalid value for allow_signup (bool)")
|
|
||||||
}
|
|
||||||
}); // /submit_signup
|
|
||||||
app.post("/submit_edit_user", (req,res) => {
|
|
||||||
// Get the form info
|
|
||||||
const password = crypto.createHash("sha512").update(req.body.password).digest("hex");
|
|
||||||
const userID = func.escape_input(req.body.userID)
|
|
||||||
const description = func.escape_input(req.body.description)
|
|
||||||
const prettyname = func.escape_input(req.body.prettyname)
|
|
||||||
const delete_bool = req.body.delete
|
|
||||||
|
|
||||||
if (userID >= 0) { // The user exists
|
|
||||||
if (password == users[userID]['hash']) { // password matches
|
|
||||||
console.log(userID, " (userID) is modifying their account")
|
|
||||||
users[userID]["prettyname"] = prettyname;
|
|
||||||
users[userID]["description"] = description;
|
|
||||||
|
|
||||||
if (delete_bool == true) {
|
|
||||||
// Delete the user
|
|
||||||
users[userID] = {"deleted": true}
|
|
||||||
// Delete all their posts
|
|
||||||
for (let postid = 0; postid < posts.length; postid++) { // loop over all posts
|
|
||||||
if (posts[postid]['userID'] == userID) { // if userID matches
|
|
||||||
posts[postid] = {"deleted": true} // delete the post
|
|
||||||
comments.comments[postid] = {"deleted": true} // the comments for this post should also be deleted
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Write these changes
|
|
||||||
fs.writeFileSync(`../data/users.json`, `${JSON.stringify(users)}`, 'utf-8');
|
|
||||||
fs.writeFileSync(`../data/posts.json`, `${JSON.stringify(posts)}`, 'utf-8');
|
|
||||||
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`, 'utf-8');
|
|
||||||
res.redirect(301,`/user/${users[userID]["username"]}`)
|
|
||||||
}
|
|
||||||
else { // password does not match
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.incorrect_password,
|
|
||||||
config
|
|
||||||
}
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.user_doesnt_exist,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}); // /submit_delete_account
|
|
||||||
app.post("/submit_edit_post", (req,res) => {
|
|
||||||
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
|
||||||
const postID = req.body.postID
|
|
||||||
const userID = req.body.userID
|
|
||||||
const title = req.body.title
|
|
||||||
const content = req.body.content
|
|
||||||
const tags = req.body.tags.split(',');
|
|
||||||
const delete_bool = req.body.delete
|
|
||||||
const unix_timestamp = getUnixTime(new Date())
|
|
||||||
console.log(users[userID]['prettyname'], "is editting the post titled:", title);
|
|
||||||
|
|
||||||
if (users[userID]['hash'] == password) { // password matches
|
|
||||||
let post = posts[postID]
|
|
||||||
post['title'] = title
|
|
||||||
post['content'] = content
|
|
||||||
post['tags'] = tags
|
|
||||||
post['editdate'] = unix_timestamp
|
|
||||||
if (typeof delete_bool != "undefined") {
|
|
||||||
console.log("Deleting post!")
|
|
||||||
posts[postID] = {"deleted": true}
|
|
||||||
comments.comments[postID] = {"deleted": true}
|
|
||||||
fs.writeFileSync(`../data/comments.json`, `${JSON.stringify(comments)}`, 'utf-8');
|
|
||||||
}
|
|
||||||
fs.writeFileSync(`../data/posts.json`, `${JSON.stringify(posts)}`, 'utf-8');
|
|
||||||
res.redirect(302, "/");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.render("partials/message", {
|
|
||||||
message: config.string.incorrect_password,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}); // /submit_edit
|
|
||||||
|
|
||||||
app.listen(config.port, () => {
|
app.listen(config.port, () => {
|
||||||
console.log(`Server is running at http://localhost:${config.port} webroot: ${config.root_path}`);
|
console.log(`Server is running at http://localhost:${config.port} webroot: ${config.root_path}`);
|
||||||
console.log("Running in: ", __dirname)
|
console.log("Running in: ", __dirname)
|
||||||
|
|||||||
@@ -6,10 +6,16 @@
|
|||||||
<body>
|
<body>
|
||||||
<form action="/submit_edit_user" method="POST">
|
<form action="/submit_edit_user" method="POST">
|
||||||
<input name="userID" type="hidden" value="<%= userID %>">
|
<input name="userID" type="hidden" value="<%= userID %>">
|
||||||
<input placeholder="<%= user.prettyname %>'s password" type="password" required id="password" name="password"><br/>
|
<label><%= locale.password %>:</label><br/>
|
||||||
<input placeholder="Pretty Name" name="prettyname" value="<%= user.prettyname %>"><br/>
|
<input type="password" required id="password" name="password"><br/><br/>
|
||||||
<textarea placeholder="Description" name="description"><%= user.description %></textarea><br/>
|
|
||||||
<label><%- config.string.delete_account_confirmation %>: </label><input type="checkbox" name="agreement"><br/>
|
<label><%= locale.prettyname %>:</label><br/>
|
||||||
|
<input name="prettyname" value="<%= user.prettyname %>"><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.description %>:</label><br/>
|
||||||
|
<textarea name="description"><%= user.description %></textarea><br/><br/>
|
||||||
|
|
||||||
|
<label><%- locale.delete_account_confirmation %>: </label><input type="checkbox" name="agreement"><br/>
|
||||||
<input type="submit" value="Submit"><br/>
|
<input type="submit" value="Submit"><br/>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -6,14 +6,22 @@
|
|||||||
<body>
|
<body>
|
||||||
<form action="/submit_edit_post" method="POST" onsubmit="sha512password()">
|
<form action="/submit_edit_post" method="POST" onsubmit="sha512password()">
|
||||||
<input name="userID" type="hidden" value="<%= post['userID'] %>">
|
<input name="userID" type="hidden" value="<%= post['userID'] %>">
|
||||||
<input name="postID" type="hidden" value="<%= post_id %>">
|
<input name="postID" type="hidden" value="<%= postID %>">
|
||||||
<input placeholder="<%= user['prettyname'] %>'s password" type="password" required id="password" name="password"><br/>
|
|
||||||
<input placeholder="title" value=" <%=post['title'] %>" required name="title"><br/>
|
<label><%= locale.password %>:</label><br/>
|
||||||
<textarea placeholder="content" required name="content"><%= post['content'] %></textarea><br/>
|
<input type="password" required id="password" name="password"><br/><br/>
|
||||||
<input placeholder="tags (comma seperated)" value="<%= post['tags'] %>" name="tags"><br/>
|
|
||||||
|
<label><%= locale.title %>:</label><br/>
|
||||||
|
<input value="<%=post['title'] %>" required name="title"><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.post_content %>:</label><br/>
|
||||||
|
<textarea required name="content"><%= post['content'] %></textarea><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.tags %>:</label><br/>
|
||||||
|
<input value="<%= post['tags'] %>" name="tags"><br/><br/>
|
||||||
|
|
||||||
<label>Delete forever (no undo): </label><input name="delete" type="checkbox"><br/>
|
<label>Delete forever (no undo): </label><input name="delete" type="checkbox"><br/>
|
||||||
<input type="submit" value="Submit"><br/>
|
<input type="submit" value="Submit"><br/>
|
||||||
<small>* Markdown supported</small>
|
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,12 +5,21 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<form action="/submit_post" method="POST">
|
<form action="/submit_post" method="POST">
|
||||||
<input placeholder="username" required name="username"><br/>
|
<label><%= locale.username %>:</label><br/>
|
||||||
<input placeholder="password" type="password" required id="password" name="password"><br/>
|
<input required name="username"><br/><br/>
|
||||||
<input placeholder="title" required name="title"><br/>
|
|
||||||
<textarea placeholder="post content*" required name="content"></textarea><br/>
|
<label><%= locale.password %>:</label><br/>
|
||||||
<input placeholder="Tags (comma seperated)" name="tags"><br/>
|
<input type="password" required id="password" name="password"><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.title %>:</label><br/>
|
||||||
|
<input required name="title"><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.post_content %>:</label><br/>
|
||||||
|
<textarea required name="content"></textarea><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.tags %>:</label><br/>
|
||||||
|
<input name="tags"><br/><br/>
|
||||||
|
|
||||||
<input type="submit" value="Submit"><br/>
|
<input type="submit" value="Submit"><br/>
|
||||||
<small>* Markdown supported</small>
|
|
||||||
</body>
|
</body>
|
||||||
</form></html>
|
</form></html>
|
||||||
|
|||||||
@@ -5,11 +5,19 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<form action="/submit_signup" method="POST">
|
<form action="/submit_signup" method="POST">
|
||||||
<input placeholder="username" required name="username"><br/>
|
<label><%= locale.username %></label><br/>
|
||||||
<input placeholder="prettyname" required name="prettyname"><br/>
|
<input required name="username"><br/><br/>
|
||||||
<input placeholder="password" type="password" required id="password" name="password"><br/>
|
|
||||||
<textarea placeholder="description (social links, what you do etc), supports markdown" id="description" name="description"></textarea><br/>
|
<label><%= locale.prettyname %></label><br/>
|
||||||
<label><%- config.string.signup_agreement %>: </label><input type="checkbox" name="agreement" required><br/>
|
<input required name="prettyname"><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.password %></label><br/>
|
||||||
|
<input type="password" required id="password" name="password"><br/><br/>
|
||||||
|
|
||||||
|
<label><%= locale.description %></label><br/>
|
||||||
|
<textarea id="description" name="description"></textarea><br/><br/>
|
||||||
|
|
||||||
|
<label><%- locale.signup_agreement %>: </label><input type="checkbox" name="agreement" required><br/>
|
||||||
<input type="submit" value="Submit"><br/>
|
<input type="submit" value="Submit"><br/>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
30
views/headers/site_wide.ejs
Normal file
30
views/headers/site_wide.ejs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<a id='home-page-link' href="/"><%= locale.home_page %></a>
|
||||||
|
/
|
||||||
|
<% if (config.rss == true) { %>
|
||||||
|
<a id='rss-link' href="/rss">
|
||||||
|
<img class='icon' src='/icons/rss.png' alt="<%= locale.rss_feed %>" title='<%= locale.rss_feed %>'>
|
||||||
|
</a>
|
||||||
|
<% } %>
|
||||||
|
<% if (config.atom == true) { %>
|
||||||
|
<a id='atom-link' href="/atom">
|
||||||
|
<img class='icon' src='/icons/atom.png' alt='<%= locale.atom_feed %>' title='<%= locale.atom_feed %>'>
|
||||||
|
</a>
|
||||||
|
<% } %>
|
||||||
|
/
|
||||||
|
<a id='new-post-link' href="<%= config.new_post_url %>"><%= locale.new_post %></a>
|
||||||
|
/
|
||||||
|
<% if (config.allow_signup == true) { %>
|
||||||
|
<a id='signup-link' href="<%= config.signup_url %>"><%= locale.sign_up %></a>
|
||||||
|
<% } %>
|
||||||
|
/
|
||||||
|
<form id='search-form' method="GET" action="/search" style="display: inline">
|
||||||
|
<input type="text" placeholder="🔍" name="q"><input type="submit" value="Search">
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
<div id='site-name'>
|
||||||
|
<h1><a id='home-page-link' href="/"><%= config.site_name %></a></h1>
|
||||||
|
</div>
|
||||||
|
<div id='site-description'>
|
||||||
|
<h2><%- config.site_description %></h2>
|
||||||
|
</div>
|
||||||
|
<%- config.seperator %>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<h1>
|
<div id='tag-page-title'>
|
||||||
Post's tagged "<%- tag %>":
|
<h1>
|
||||||
</h1>
|
#<%- tag %>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
<%- config.seperator %>
|
<%- config.seperator %>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<h1>
|
|
||||||
<%- config.site_name %>
|
|
||||||
</h1>
|
|
||||||
<h2>
|
|
||||||
<%- config.site_description %>
|
|
||||||
</h2>
|
|
||||||
<a href="<%= config.rss_url %>">RSS Feed</a><br/>
|
|
||||||
<a href="<%= config.new_post_url %>">New post</a><br/>
|
|
||||||
<a href="<%= config.signup_url %>">Sign Up</a><br/>
|
|
||||||
<% if (config.enable_hitcount == true) { %>
|
|
||||||
Hitcount: <%= hitcount %>
|
|
||||||
<% } %>
|
|
||||||
<%- config.seperator %>
|
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
<h1>
|
<div id='user-page-title'>
|
||||||
<%= user.prettyname %>'s posts
|
<h1>
|
||||||
</h1>
|
<%= user.prettyname %>
|
||||||
<p><%- converter.makeHtml(user.description) %></p>
|
</h1>
|
||||||
<a href="<%= config.edit_account_base_url %>/<%= userID %>">edit account</a>
|
</div>
|
||||||
|
<p><%- func.render_md(user.description) %></p>
|
||||||
|
<a if='edit-account-link' href="<%= config.edit_account_base_url %>/<%= userID %>">
|
||||||
|
<img class='icon' src='/icons/edit.png' alt='<%= locale.edit_account %>' title='<%= locale.edit_account %>'>
|
||||||
|
</a>
|
||||||
|
|
|
||||||
|
<a id='rss-link' href="/user/<%= user.username %>/rss">
|
||||||
|
<img class='icon' src='/icons/rss.png' alt="<%= locale.rss_feed %>" title='<%= locale.rss_feed %>'>
|
||||||
|
</a>
|
||||||
|
<a id='atom-link' href="/user/<%= user.username %>/atom">
|
||||||
|
<img class='icon' src='/icons/atom.png' alt='<%= locale.atom_feed %>' title='<%= locale.atom_feed %>'>
|
||||||
|
</a>
|
||||||
<%- config.seperator %>
|
<%- config.seperator %>
|
||||||
|
|||||||
47
views/indexes/all_pages.ejs
Normal file
47
views/indexes/all_pages.ejs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= config.charset %>">
|
||||||
|
<head>
|
||||||
|
<%- include("../partials/head") %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Misc:<br/>
|
||||||
|
<a href="/">Home Page</a><br/>
|
||||||
|
<a href="<%= config.new_post_url %>">New Post Form</a><br/>
|
||||||
|
<a href="<%= config.signup_url %>">Signup Form</a><br/>
|
||||||
|
Indexes:<br/>
|
||||||
|
<a href="/index/posts">Posts Index</a><br/>
|
||||||
|
<a href="/index/users">Users Index</a><br/>
|
||||||
|
<a href="/index/comments">Comments Index</a><br/>
|
||||||
|
Posts:<br/>
|
||||||
|
<% for (let postID = 0; postID < posts.length; postID++) { %>
|
||||||
|
<% if (posts[postID]["deleted"] != true) { %>
|
||||||
|
<a href="/post/<%= postID %>"><%= posts[postID]["title"] %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
Comments:<br/>
|
||||||
|
<% for (let postID = 0; postID < comments.length; postID++) { %>
|
||||||
|
<% for (let comment_index = 0; comment_index < comments[postID]['comments'].length; comment_index++) { %>
|
||||||
|
<a href="/comment/<%= postID %>-<%= comment_index %>"><%= postID %>-<%= comment_index %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
Users:<br/>
|
||||||
|
<% for (let userID = 0; userID < users.length; userID++) { %>
|
||||||
|
<% if (users[userID]["deleted"] != true) { %>
|
||||||
|
<a href="/user/<%= users[userID]["username"] %>"><%= users[userID]["username"] %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
Edit Posts:<br/>
|
||||||
|
<% for (let postID = 0; postID < posts.length; postID++) { %>
|
||||||
|
<% if (posts[postID]["deleted"] != true) { %>
|
||||||
|
<a href="<%= config.edit_post_base_url %>/<%= postID %>">Edit <%= posts[postID]["title"] %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
Edit Users:<br/>
|
||||||
|
<% for (let userID = 0; userID < users.length; userID++) { %>
|
||||||
|
<% if (users[userID]["deleted"] != true) { %>
|
||||||
|
<a href="<%= config.edit_account_base_url %>/<%= users[userID]["username"] %>">Edit <%= users[userID]["username"] %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
<!-- TODO add tags -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
views/indexes/comments.ejs
Normal file
13
views/indexes/comments.ejs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= config.charset %>">
|
||||||
|
<head>
|
||||||
|
<%- include("../partials/head") %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<% for (let postID = 0; postID < comments.length; postID++) { %>
|
||||||
|
<% for (let comment_index = 0; comment_index < comments[postID]['comments'].length; comment_index++) { %>
|
||||||
|
<a href="/comment/<%= postID %>-<%= comment_index %>"><%= postID %>-<%= comment_index %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
views/indexes/posts.ejs
Normal file
13
views/indexes/posts.ejs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= config.charset %>">
|
||||||
|
<head>
|
||||||
|
<%- include("../partials/head") %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<% for (let postID = 0; postID < posts.length; postID++) { %>
|
||||||
|
<% if (posts[postID]["deleted"] != true) { %>
|
||||||
|
<a href="/post/<%= postID %>"><%= posts[postID]["title"] %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
views/indexes/users.ejs
Normal file
13
views/indexes/users.ejs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= config.charset %>">
|
||||||
|
<head>
|
||||||
|
<%- include("../partials/head") %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<% for (let userID = 0; userID < users.length; userID++) { %>
|
||||||
|
<% if (users[userID]["deleted"] != true) { %>
|
||||||
|
<a href="/user/<%= users[userID]["username"] %>"><%= users[userID]["username"] %></a><br/>
|
||||||
|
<% }; %>
|
||||||
|
<% }; %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
26
views/pages/comment.ejs
Normal file
26
views/pages/comment.ejs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<%- include('../partials/head'); %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header id="site-header">
|
||||||
|
<%- include("../headers/site_wide") %>
|
||||||
|
</header>
|
||||||
|
<div id="comment">
|
||||||
|
<%- include("../partials/comment"); %>
|
||||||
|
</div>
|
||||||
|
<%- config.seperator %>
|
||||||
|
<b><%= locale.reply %>:</b>
|
||||||
|
<div id="post-commentform">
|
||||||
|
<!-- Comment form -->
|
||||||
|
<form method="POST" action="/submit_comment">
|
||||||
|
<input type="hidden" name="post_index" value="<%= postID %>">
|
||||||
|
<label><%= locale.username %>:</label><br/><input name="name"><br/><br/>
|
||||||
|
<label><%= locale.comment %>:</label><br/><textarea name="content">>> <%- postID %>-<%- commentID %>
|
||||||
|
</textarea><br/>
|
||||||
|
<button type="submit"><%= locale.submit %></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,10 +4,13 @@
|
|||||||
<%- include('../partials/head'); %>
|
<%- include('../partials/head'); %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<header id="site-header">
|
||||||
|
<%- include("../headers/site_wide") %>
|
||||||
|
</header>
|
||||||
<div id="posts">
|
<div id="posts">
|
||||||
<%- include('../posts/post'); %>
|
<%- include('../posts/post'); %>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer id='footer'>
|
||||||
<%- include('../partials/footer'); %>
|
<%- include('../partials/footer'); %>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
37
views/pages/search.ejs
Normal file
37
views/pages/search.ejs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang='<%- config.locale %>'>
|
||||||
|
<head>
|
||||||
|
<%- include('../partials/head'); %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='site-header'>
|
||||||
|
<%- include('../headers/site_wide'); %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='advanced-search'>
|
||||||
|
<form method="GET" action="/search">
|
||||||
|
<label>Search Term:</label>
|
||||||
|
<input type='text' placeholder='🔍' name='q' value='<%- search_term %>'><br/>
|
||||||
|
|
||||||
|
<label>Search for:</label><br/>
|
||||||
|
<label>Post:</label>
|
||||||
|
<input type="checkbox" name="type" value="post" <% if (search_type.includes('post')) {%>checked<% } %>><br/>
|
||||||
|
<label>User:</label>
|
||||||
|
<input type="checkbox" name="type" value="user" <% if (search_type.includes('user')) {%>checked<% } %>><br/>
|
||||||
|
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%- config.seperator %>
|
||||||
|
|
||||||
|
<div id='results'>
|
||||||
|
<% search_results.posts.forEach((result, index) => { %>
|
||||||
|
<a href="/post/<%- result.id %>"><%- result.title %></a><br/>
|
||||||
|
<% }); %>
|
||||||
|
<% search_results.users.forEach((result, index) => { %>
|
||||||
|
<a href="/user/<%- result.username %>"><%- result.prettyname %></a><br/>
|
||||||
|
<% }); %>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,19 +4,24 @@
|
|||||||
<%- include('../partials/head'); %>
|
<%- include('../partials/head'); %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="header">
|
<header id="site-header">
|
||||||
|
<%- include('../headers/site_wide'); %>
|
||||||
|
</header>
|
||||||
|
<header id='page-header'>
|
||||||
<%- include('../headers/tag'); %>
|
<%- include('../headers/tag'); %>
|
||||||
</div>
|
</header>
|
||||||
<div id="posts">
|
<div id="posts">
|
||||||
<% for (let index = posts.length - 1; index >= 0; index--) { %>
|
<% for (let index = posts.length - 1; index >= 0; index--) { %>
|
||||||
|
<% if ( posts[index].deleted != true) { %>
|
||||||
<% posts[index].tags.forEach((current_tag, tag_index) => { %>
|
<% posts[index].tags.forEach((current_tag, tag_index) => { %>
|
||||||
<% if (current_tag == tag) { %>
|
<% if (current_tag.toLowerCase() == tag.toLowerCase()) { %>
|
||||||
<%- include('../posts/tag', {post: posts[index], postID: index}); %>
|
<%- include('../posts/tag', {post: posts[index], postID: index, user: users[posts[index].userID], comments: comments[index]}); %>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer id='footer'>
|
||||||
<%- include('../partials/footer'); %>
|
<%- include('../partials/footer'); %>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,17 +4,34 @@
|
|||||||
<%- include('../partials/head'); %>
|
<%- include('../partials/head'); %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="header">
|
<header id="site-header">
|
||||||
|
<%- include("../headers/site_wide") %>
|
||||||
|
</header>
|
||||||
|
<header id='page-header'>
|
||||||
<%- include('../headers/timeline'); %>
|
<%- include('../headers/timeline'); %>
|
||||||
</div>
|
</header>
|
||||||
|
<form method="POST" action="/submit_nothing" style="display:none">
|
||||||
|
<!-- Form is used to help mitigate spam as it is the first form on the front page -->
|
||||||
|
<input type="hidden" name="post_index" value="0">
|
||||||
|
<input placeholder="username" name="name"><br/>
|
||||||
|
<textarea placeholder="comment" name="content"></textarea><br/>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
<div id="posts">
|
<div id="posts">
|
||||||
<% for (let index = posts.length - 1; index >= 0; index--) { %>
|
<% for (let index = posts.length - 1; index >= 0; index--) { %>
|
||||||
<% if (posts[index]["deleted"] != true) { %>
|
<% if (posts[index]["deleted"] != true) { %>
|
||||||
<%- include('../posts/timeline', {post: posts[index], index: index, user: users[posts[index].userID]}); %>
|
<%- include('../posts/timeline', {post: posts[index], postID: index, user: users[posts[index].userID], comments: comments[index]}); %>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<form method="POST" action="/submit_nothing" style="display:none">
|
||||||
|
<!-- Form is used to help mitigate spam as it is the last form on the front page -->
|
||||||
|
<input type="hidden" name="post_index" value="0">
|
||||||
|
<input placeholder="username" name="name"><br/>
|
||||||
|
<textarea placeholder="comment" name="content"></textarea><br/>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
<footer id='footer'>
|
||||||
<%- include('../partials/footer'); %>
|
<%- include('../partials/footer'); %>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,17 +4,20 @@
|
|||||||
<%- include('../partials/head'); %>
|
<%- include('../partials/head'); %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="header">
|
<header id="site-header">
|
||||||
|
<%- include("../headers/site_wide") %>
|
||||||
|
</header>
|
||||||
|
<header id='page-header'>
|
||||||
<%- include('../headers/user'); %>
|
<%- include('../headers/user'); %>
|
||||||
</div>
|
</header>
|
||||||
<div id="posts">
|
<div id="posts">
|
||||||
<% for (let index = posts.length - 1; index >= 0; index--) { %>
|
<% for (let index = posts.length - 1; index >= 0; index--) { %>
|
||||||
<% if (posts[index].userID == userID) { %>
|
<% if (posts[index].userID == userID) { %>
|
||||||
<%- include('../posts/user', {post: posts[index], index: index, user: user}); %>
|
<%- include('../posts/user', {post: posts[index], postID: index, user: user, comments: comments[index]}); %>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer id='footer'>
|
||||||
<%- include('../partials/footer'); %>
|
<%- include('../partials/footer'); %>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,2 +1,13 @@
|
|||||||
<b><%= comment.name %></b> <%= func.unix_time_to_date_format(comment.pubdate) %> <i>No. <%= comment.id %></i>:<br/>
|
<span id='comment-name'>
|
||||||
<%= comment.content %>
|
<b><%= comment.name %></b>
|
||||||
|
</span>
|
||||||
|
<span id='comment-date' class='date'>
|
||||||
|
<%= func.unix_time_to_date_format(comment.pubdate) %>
|
||||||
|
</span>
|
||||||
|
<span id='comment-id' style='display: inline'>
|
||||||
|
<a href='/comment/<%= postID %>-<%= comment.id %>'>No. <%= postID %>-<%= comment.id %></a>:<br/>
|
||||||
|
</span>
|
||||||
|
<span id='comment-content'>
|
||||||
|
<%- func.render_comment(comment.content) %>
|
||||||
|
</span>
|
||||||
|
<br/>
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
Site is ran by deadvey<br/>
|
<a id='site-index-link' href="/index/pages"><%= locale.site_index %></a><br/>
|
||||||
<%- config.string.attribution %>
|
<%= locale.site_ran_by %> <%= config.site_admin %><br/>
|
||||||
|
<%- locale.attribution %><br/>
|
||||||
|
<%= locale.AI_consent %> <!-- remove consent for AI scrapers -->
|
||||||
|
|||||||
@@ -5,4 +5,5 @@
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/default.css">
|
||||||
<link rel="stylesheet" href="/custom.css">
|
<link rel="stylesheet" href="/custom.css">
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="<%=config.language%>
|
<html lang="<%= config.language %>">
|
||||||
<head>
|
<head>
|
||||||
<%- include('head') %>
|
<%- include('head') %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<%- include("../headers/site_wide") %>
|
||||||
|
</div>
|
||||||
<%- message %>
|
<%- message %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,28 +1,60 @@
|
|||||||
<h1>
|
<div id="post-header">
|
||||||
<%= post.title %>
|
<h2>
|
||||||
</h1>
|
<a href='/post/<%= post['id'] %>'><%= post.title %></a>
|
||||||
<%- converter.makeHtml(post.content) %><br/>
|
</h2>
|
||||||
<i>
|
</div>
|
||||||
By <a href="/user/<%= user.username %>"><%= user.username %></a><br/>
|
|
||||||
</i>
|
|
||||||
<%- func.hyperlink_tags(post.tags) %><br/>
|
|
||||||
<a href="<%= config.edit_post_base_url %>/<%= postID %>">Edit</a><br/>
|
|
||||||
<i>Published: <%= func.unix_time_to_date_format(post.pubdate) %></i><br/>
|
|
||||||
<i>Last Modified: <%= func.unix_time_to_date_format(post.pubdate) %></i><br/>
|
|
||||||
|
|
||||||
<%- config.seperator %>
|
<div id="post-tags">
|
||||||
|
<%- func.render_tags(post.tags) %><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Comment form -->
|
<div id="post-content">
|
||||||
<form method="POST" action="/submit_comment">
|
<p>
|
||||||
<input type="hidden" name="post_index" value="<%= postID %>">
|
<%- func.render_md(post.content) %><br/>
|
||||||
<input placeholder="username" name="name"><br/>
|
</p>
|
||||||
<textarea placeholder="comment" name="content"></textarea><br/>
|
</div>
|
||||||
<button type="submit">Submit</button>
|
<div id="post-details">
|
||||||
</form>
|
<span id="post-author">
|
||||||
|
<i><%= locale.written_by %> <a href="/user/<%= user.username %>"><%= user.prettyname %></a></i>
|
||||||
|
</span>
|
||||||
|
-
|
||||||
|
<span id="post-pubdate">
|
||||||
|
<i><%= locale.published %>: <%= func.unix_time_to_date_format(post.pubdate) %></i><br/>
|
||||||
|
</span>
|
||||||
|
<div id="post-editdate">
|
||||||
|
<i><%= locale.last_modified %>: <%= func.unix_time_to_date_format(post.pubdate) %></i><br/>
|
||||||
|
</div>
|
||||||
|
<div id="post-edit">
|
||||||
|
<a href="<%= config.edit_post_base_url %>/<%= post["id"] %>">
|
||||||
|
<img class='icon' src='/icons/edit.png' alt='<%= locale.edit_post %>' title='<%= locale.edit_post %>'>
|
||||||
|
</a><br/>
|
||||||
|
</div>
|
||||||
|
<% if (config.enable_hitcount == true) { %>
|
||||||
|
<div id='post-hitcount'>
|
||||||
|
Hitcount: <%- post.hitcount %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% comments.forEach((comment, postID) => { %>
|
<hr/>
|
||||||
|
|
||||||
|
<div id="post-commentform">
|
||||||
|
<!-- Comment form -->
|
||||||
|
<form method="POST" action="/submit_comment">
|
||||||
|
<input type="hidden" name="post_index" value="<%= post["id"] %>">
|
||||||
|
<label><%= locale.username %>:</label><br/><input name="name"><br/><br/>
|
||||||
|
<label><%= locale.comment %>:</label><br/><textarea name="content"></textarea><br/>
|
||||||
|
<button type="submit"><%= locale.submit %></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="post-comments">
|
||||||
|
<% comments.forEach((comment, index) => { %>
|
||||||
|
<div id="comment-no-<%= post.id %>-<%= comment.id %>" class="comment post-comment-<%= index %>">
|
||||||
<%- include('../partials/comment', {comment: comment}) %>
|
<%- include('../partials/comment', {comment: comment}) %>
|
||||||
<% }) %>
|
</div>
|
||||||
|
<% }) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%- config.seperator %>
|
<%- config.seperator %>
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
<h3>
|
<div id="post-header">
|
||||||
<%= post.title %>
|
<h2>
|
||||||
</h3>
|
<a href='/post/<%= post['id'] %>'><%= post.title %></a>
|
||||||
<%- converter.makeHtml(post.content) %><br/>
|
</h2>
|
||||||
<a href="/post/<%- postID %>">Permalink</a><br/>
|
</div>
|
||||||
<%- func.hyperlink_tags(post.tags) %>
|
|
||||||
|
|
||||||
<!-- Comment form -->
|
<div id="post-tags">
|
||||||
<form method="POST" action="/submit_comment">
|
<%- func.render_tags(post.tags) %><br/>
|
||||||
<input type="hidden" name="post_index" value="<%= postID %>">
|
</div>
|
||||||
<input placeholder="username" name="name"><br/>
|
|
||||||
<textarea placeholder="comment" name="content"></textarea><br/>
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<% comments[postID].forEach((comment) => { %>
|
<div id="post-content">
|
||||||
<%- include('../partials/comment', {comment: comment}) %>
|
<p>
|
||||||
<% }) %>
|
<%- func.render_md(post.content) %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div id="post-details">
|
||||||
|
<span id="post-author">
|
||||||
|
<i><a href="/user/<%= user.username %>"><%= user.prettyname %></a></i>
|
||||||
|
</span>
|
||||||
|
-
|
||||||
|
<span id="post-pubdate">
|
||||||
|
<i><%= func.unix_time_to_date_format(post.pubdate) %></i><br/>
|
||||||
|
</span>
|
||||||
|
<br/>
|
||||||
|
<div id="post-edit">
|
||||||
|
<a href="<%= config.edit_post_base_url %>/<%= post["id"] %>">
|
||||||
|
<img class='icon' src='/icons/edit.png' alt='<%= locale.edit_post %>' title='<%= locale.edit_post %>'>
|
||||||
|
</a><br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%- config.seperator %>
|
<%- config.seperator %>
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
<h3>
|
<div id="post-header">
|
||||||
<%= post.title %>
|
<h2>
|
||||||
</h3>
|
<a href='/post/<%= post['id'] %>'><%= post.title %></a>
|
||||||
<%- converter.makeHtml(post.content) %><br/>
|
</h2>
|
||||||
<a href="/post/<%- index %>">Permalink</a><br/>
|
</div>
|
||||||
<i>
|
|
||||||
By <a href="/user/<%= user.username %>"><%= user.username %></a><br/>
|
|
||||||
</i>
|
|
||||||
|
|
||||||
<!-- Comment form -->
|
<div id="post-tags">
|
||||||
<form method="POST" action="/submit_comment">
|
<%- func.render_tags(post.tags) %><br/>
|
||||||
<input type="hidden" name="post_index" value="<%= index %>">
|
</div>
|
||||||
<input placeholder="username" name="name"><br/>
|
|
||||||
<textarea placeholder="comment" name="content"></textarea><br/>
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<% comments[index].forEach((comment, index) => { %>
|
<div id="post-content">
|
||||||
<%- include('../partials/comment', {comment: comment}) %>
|
<p>
|
||||||
<% }) %>
|
<%- func.render_md(post.content) %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div id="post-details">
|
||||||
|
<span id="post-author">
|
||||||
|
<i><a href="/user/<%= user.username %>"><%= user.prettyname %></a></i>
|
||||||
|
</span>
|
||||||
|
-
|
||||||
|
<span id="post-pubdate">
|
||||||
|
<i><%= func.unix_time_to_date_format(post.pubdate) %></i><br/>
|
||||||
|
</span>
|
||||||
|
<br/>
|
||||||
|
<div id="post-edit">
|
||||||
|
<a href="<%= config.edit_post_base_url %>/<%= post["id"] %>">
|
||||||
|
<img class='icon' src='/icons/edit.png' alt='<%= locale.edit_post %>' title='<%= locale.edit_post %>'>
|
||||||
|
</a><br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%- config.seperator %>
|
<%- config.seperator %>
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,32 @@
|
|||||||
<h3>
|
<div id="post-header">
|
||||||
<%= post.title %>
|
<h2>
|
||||||
</h3>
|
<a href='/post/<%= post['id'] %>'><%= post.title %></a>
|
||||||
<%- converter.makeHtml(post.content) %><br/>
|
</h2>
|
||||||
<a href="/post/<%- index %>">Permalink</a><br/>
|
</div>
|
||||||
|
|
||||||
<!-- Comment form -->
|
<div id="post-tags">
|
||||||
<form method="POST" action="/submit_comment">
|
<%- func.render_tags(post.tags) %><br/>
|
||||||
<input type="hidden" name="post_index" value="<%= index %>">
|
</div>
|
||||||
<input placeholder="username" name="name"><br/>
|
|
||||||
<textarea placeholder="comment" name="content"></textarea><br/>
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<% comments[index].forEach((comment, index) => { %>
|
<div id="post-content">
|
||||||
<%- include('../partials/comment', {comment: comment}) %>
|
<p>
|
||||||
<% }) %>
|
<%- func.render_md(post.content) %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div id="post-details">
|
||||||
|
<span id="post-author">
|
||||||
|
<i><a href="/user/<%= user.username %>"><%= user.prettyname %></a></i>
|
||||||
|
</span>
|
||||||
|
-
|
||||||
|
<span id="post-pubdate">
|
||||||
|
<i><%= func.unix_time_to_date_format(post.pubdate) %></i><br/>
|
||||||
|
</span>
|
||||||
|
<br/>
|
||||||
|
<div id="post-edit">
|
||||||
|
<a href="<%= config.edit_post_base_url %>/<%= post["id"] %>">
|
||||||
|
<img class='icon' src='/icons/edit.png' alt='<%= locale.edit_post %>' title='<%= locale.edit_post %>'>
|
||||||
|
</a><br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%- config.seperator %>
|
<%- config.seperator %>
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
<title><%= config.site_name %></title>
|
<title><%= config.site_name %></title>
|
||||||
<link><%= config.site_url %></title>
|
<link><%= config.site_url %></title>
|
||||||
<description><%= config.site_description %></description>
|
<description><%= config.site_description %></description>
|
||||||
<updated><%= new Date() %></updated>
|
<updated><%= func.unix_time_to_atom_date(getUnixTime(new Date())) %></updated>
|
||||||
<id><%= config.site_url %></id>
|
<id><%= config.site_url %></id>
|
||||||
<% for (let postID = posts.length-1; postID >= 0; postID--) { %>
|
<% for (let postID = posts.length-1; postID >= 0; postID--) { %>
|
||||||
<% if (posts[postID]["deleted"] != true) { %>
|
<% if (posts[postID]["deleted"] != true) { %>
|
||||||
<entry>
|
<entry>
|
||||||
<title><%= posts[postID]["title"] %></title>
|
<title><%= posts[postID]["title"] %></title>
|
||||||
<link><%= config.site_url %>/post/<%= postID %></link>
|
<link><%= config.site_url %>/post/<%= postID %></link>
|
||||||
<summary><![CDATA[<%- converter.makeHtml(posts[postID]["content"]) %>]]></summary>
|
<summary><![CDATA[<%- func.render_md(posts[postID]["content"]) %>]]></summary>
|
||||||
<guid isPermaLink="true"><%= config.site_url %>/post/<%= postID %></guid>
|
<guid isPermaLink="true"><%= config.site_url %>/post/<%= postID %></guid>
|
||||||
<pubDate><%# func.unix_time_to_atom_date(posts[postID]['pubdate']) %></pubDate>
|
<pubDate><%# func.unix_time_to_atom_date(posts[postID]['pubdate']) %></pubDate>
|
||||||
<% for (let tag_index = 0; tag_index < posts[postID]['tags'].length; tag_index++) { %>
|
<% for (let tag_index = 0; tag_index < posts[postID]['tags'].length; tag_index++) { %>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<title><%= posts[postID]["title"] %></title>
|
<title><%= posts[postID]["title"] %></title>
|
||||||
<link><%= config.site_url %>/post/<%= postID %></link>
|
<link><%= config.site_url %>/post/<%= postID %></link>
|
||||||
<description><![CDATA[<%- converter.makeHtml(posts[postID]["content"]) %>]]></description>
|
<description><![CDATA[<%- func.render_md(posts[postID]["content"]) %>]]></description>
|
||||||
<guid isPermaLink="true"><%= config.site_url %>/post/<%= postID %></guid>
|
<guid isPermaLink="true"><%= config.site_url %>/post/<%= postID %></guid>
|
||||||
<pubDate><%= func.unix_time_to_rss_date(posts[postID]['pubdate']) %></pubDate>
|
<pubDate><%= func.unix_time_to_rss_date(posts[postID]['pubdate']) %></pubDate>
|
||||||
<% for (let tag_index = 0; tag_index < posts[postID]['tags'].length; tag_index++) { %>
|
<% for (let tag_index = 0; tag_index < posts[postID]['tags'].length; tag_index++) { %>
|
||||||
|
|||||||
24
views/syndication/user_atom.ejs
Normal file
24
views/syndication/user_atom.ejs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="<%= config.charset %>" ?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||||
|
<title><%= config.site_name %></title>
|
||||||
|
<link><%= config.site_url %></title>
|
||||||
|
<description><%= config.site_description %></description>
|
||||||
|
<updated><%= func.unix_time_to_atom_date(getUnixTime(new Date())) %></updated>
|
||||||
|
<id><%= config.site_url %></id>
|
||||||
|
<% for (let postID = posts.length-1; postID >= 0; postID--) { %>
|
||||||
|
<% if (posts[postID]["userID"] == userID) { %>
|
||||||
|
<% if (posts[postID]["deleted"] != true) { %>
|
||||||
|
<entry>
|
||||||
|
<title><%= posts[postID]["title"] %></title>
|
||||||
|
<link><%= config.site_url %>/post/<%= postID %></link>
|
||||||
|
<summary><![CDATA[<%- func.render_md(posts[postID]["content"]) %>]]></summary>
|
||||||
|
<guid isPermaLink="true"><%= config.site_url %>/post/<%= postID %></guid>
|
||||||
|
<pubDate><%# func.unix_time_to_atom_date(posts[postID]['pubdate']) %></pubDate>
|
||||||
|
<% for (let tag_index = 0; tag_index < posts[postID]['tags'].length; tag_index++) { %>
|
||||||
|
<category><![CDATA[<%= posts[postID]['tags'][tag_index] %>]]></category>
|
||||||
|
<% } %>
|
||||||
|
</entry>
|
||||||
|
<% } %>
|
||||||
|
<% } %>
|
||||||
|
<% } %>
|
||||||
|
</feed>
|
||||||
@@ -5,10 +5,12 @@
|
|||||||
<link><%= config.site_url %></title>
|
<link><%= config.site_url %></title>
|
||||||
<description><%= config.site_description %></description>
|
<description><%= config.site_description %></description>
|
||||||
<% for (let postID = posts.length-1; postID >= 0; postID--) { %>
|
<% for (let postID = posts.length-1; postID >= 0; postID--) { %>
|
||||||
|
<% if (posts[postID]["userID"] == userID) { %>
|
||||||
|
<% if (posts[postID]["deleted"] != true) { %>
|
||||||
<item>
|
<item>
|
||||||
<title><%= posts[postID]["title"] %></title>
|
<title><%= posts[postID]["title"] %></title>
|
||||||
<link><%= config.site_url %>/post/<%= postID %></link>
|
<link><%= config.site_url %>/post/<%= postID %></link>
|
||||||
<description><![CDATA[<%= converter.makeHtml(posts[postID]["content"]) %>]]></description>
|
<description><![CDATA[<%- func.render_md(posts[postID]["content"]) %>]]></description>
|
||||||
<guid isPermaLink="true"><%= config.site_url %>/post/<%= postID %></guid>
|
<guid isPermaLink="true"><%= config.site_url %>/post/<%= postID %></guid>
|
||||||
<pubDate><%= func.unix_time_to_rss_date(posts[postID]['pubdate']) %></pubDate>
|
<pubDate><%= func.unix_time_to_rss_date(posts[postID]['pubdate']) %></pubDate>
|
||||||
<% for (let tag_index = 0; tag_index < posts[postID]['tags'].length; tag_index++) { %>
|
<% for (let tag_index = 0; tag_index < posts[postID]['tags'].length; tag_index++) { %>
|
||||||
@@ -16,5 +18,7 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</item>
|
</item>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% } %>
|
||||||
|
<% } %>
|
||||||
</channel>
|
</channel>
|
||||||
</rss>
|
</rss>
|
||||||
73
webroot/default.css
Normal file
73
webroot/default.css
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
body {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
#site-header {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#footer {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
input, textarea, button {
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
field-sizing: content; /* Only supported by Chromium */
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#search-form input {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background-color: #333333;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #546BEB;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: #EF46F1;
|
||||||
|
}
|
||||||
|
input, textarea, button {
|
||||||
|
background-color: #1E1E1E;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.comment:nth-child(even) {
|
||||||
|
background: #1E1E1E;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
input, textarea, button {
|
||||||
|
background-color: #DEDEDE;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.comment:nth-child(even) {
|
||||||
|
background: #EEEEEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
webroot/icons/atom.png
Normal file
BIN
webroot/icons/atom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 429 B |
BIN
webroot/icons/edit.png
Normal file
BIN
webroot/icons/edit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 454 B |
BIN
webroot/icons/rss.png
Normal file
BIN
webroot/icons/rss.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 421 B |
Reference in New Issue
Block a user