do_blog.js
Look at this thing! I made it in a number of days and it brings me joy!
More seriously, this a script for individuals who want a blog but don't want to go through the hassle of updating it, either through some third party or (more annoyingly) by hand. Oh, the horror of website making (affectionate).
Basically, it takes in a page that looks like this:
<script defer src=do_blog.js></script>
<div id=blog></div>
<div id=blog-archive hidden>
# Hello, this is my first post!
2024-12-16
[tag] hello world, first post
Taking the time to inform everyone that Rise of the Teenage Mutant Ninja Turtles exists. That is all
[tag] rottmnt
[sign]
</div>
And turns it into this:
Example, no CSS
# Hello, this is my first post!
2024-12-16
[tag] hello world, first post
Taking the time to inform everyone that Rise of the Teenage Mutant Ninja Turtles exists. That is all
[tag] rottmnt
[sign]
For transparency's sake, I'm leaving this example here to show the result with a single post.
It doesn't look like much, but it's functional and much nicer when it has its own webpage and CSS. And, of course, you can build more webpage around the blog. Sort of like I built this page around the example.
The script, however, does assume that the page's main content is the blog, so it changes the title
to the blog's title, which is why this page is titled Blog instead of do_blog.js.
I tried to make it as customizable as I could, so if you want to see how well it really works out as a blogging tool, check out this example blog or my blog!
Another example, with CSS
The rest of this page functions as documentation for its usage and customization.
Get it
EDIT: Due to popular demand (from myself, I demanded it), I've made a stylesheet for the souls who have no more CSSing in them for the day (or their life). Understandable.
To see how the CSS looks, check out the Example Blog I made.
Download:
Or copy the code below:
- do_blog.js
-
- do_blog.css
-
Put the script at the end of your body
or in your head
with the defer
attribute. The only set up you need is the script and two divs.
<script src="do_blog.js" defer></script>
<div id=blog></div>
<div id=blog-archive hidden></div>
Blog it
The only div
you actually need to touch is the #blog-archive
. Skip lines whenever you want to make a new paragraph, a new post, or use a command.
The basic format of a blog post:
# Blog Title
date time
The rest of the post
The # and date time are mandatory!
The # indicates a new blog post.
Date time can be in date time string format (YYYY-MM-DDTHH:mm:ss.sssZ) or in milliseconds since the epoch. For example, it is Monday, December 16, 2024 13:47 for me.
- YYYY-MM-DDTHH:mm:ss.sssZ
- 2024-12-16T18:47:22.874Z
- Console:
new Date().toISOString()
- Highly recommended, as it is standard for browsers to read this format
- Note: Just YYYY-MM-DD works fine too, but the browser assumes the local timezone. Time can be iffy in the web.
- Milliseconds since the epoch
- 1734374842874
- Console:
new Date().getTime()
- Not as recommended, not mandatory for a browser to read this and return a valid date.
I have provided two commands.
- [tag] words, more words, so many words
- Adds the list of comma separated words/phrases to the post's tags. This way, a reader may search the posts by tag if they wish.
- [sign]
- If the line is otherwise empty, this displays your signature in its place. The next section goes about how you can customize your signature.
The commands may be used as many times per post as you want.
Customize it
Finally, there are quite a few things to customize, though there is no real need to if one doesn't want to.
JavaScript
All you need to do is make an object named MY_BLOG
in a script
that loads before do_blog.js. The following example displays all the customizable options and their defaults.
<script>
MY_BLOG = {
// blogging experience
new_post: '#',
tag_cmd: '[tag]',
sign_cmd: '[sign]',
title: 'Blog',
no_posts_msg: 'This person has not posted yet! Come back later :3',
none_tagged_msg: 'There are no posts under this tag!',
// blog post
title_is_link: true,
link_text: '[link]', // if title is not link
tags_in_footer: false,
cut_long_posts: .75 * window.innerHeight,
img_height: 300,
read_more: 'Read more',
replacers: [],
newTitle: (post) => { // when post has no title
const temp = document.createElement('div');
DO_BLOG.replacers.forEach ( e => {
const arr = post.body.split(e[0]);
temp.innerHTML = arr.join(e[1]);
});
const arr = temp.textContent.slice(0,20).split(/\s+/);
arr.pop();
return arr.join(' ') + '…';
},
getDate: (date) => {
return date.toUTCString().slice(0,16);
},
signature: (post) => {
return 'Posted ' + post.date.toLocaleDateString();
},
// nav
links_heading: 'Links',
skip_to_content: 'Skip to content',
open_navs: 2,
months: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
],
navTitle: (post) => {
return post.date.toLocaleDateString();
},
// tag form
tag_label: 'Tag: ',
all_tags: '--All tags--',
tag_search: 'Go',
// sort form
sort_old_to_new: 'Sort old to new',
sort_new_to_old: 'Sort new to old',
// search pages nav
load_limit: 10,
pagination_limit: 6,
skip_pagination: 'Skip pagination',
previous_page: 'Prev',
page_label: 'Page: ',
next_page: 'Next',
// breadcrumb nav
breadcrumb_trail: '/',
breadcrumb_article: 'Article',
breadcrumb_tag: 'Tag',
// article nav
next_post: '← Next',
post_nav_separator: ' | ',
previous_post: 'Prev →',
// aria-label (for pagination and tags)
aria_goto_page: 'Goto page ',
aria_current_page: 'Current page, page ',
aria_page_of: ['Page ', ' of '],
aria_pagination: 'Pagination',
aria_breadcrumbs: 'Breadcrumbs',
aria_tags: 'Tags',
do_onload: (posts) => {},
}
</script>
Let's go over the details of each option
new_post: '#'
- String. Put this at the beginning of a line to indicate a new post. The rest of the line is the post's title, though you don't need to include a title if you don't want to. Keep in mind, posts are split using regex. This is put through
RegExp
(regex), so some characters may need to be escaped with a double backslash (\\).
tag_cmd: '[tag]'
- String. No regex. Put this at the beginning of a line to indicate the rest of the line is a comma separated list of tags.
sign_cmd: '[sign]'
- String. No regex. Put this and only this on a line and it will output a paragraph with class
signature
containing your signature, which you can change in the signature
property.
title: 'Blog'
- String. The blog's title. This will be used in the breadcrumbs navigation that appears when you filter by tag or click on an article link. The
title
element's text will also be set to this.
no_posts_msg: 'This person has not posted yet! Come back later :3'
- String. Message appears when your
#blog-archive
is empty or does not exist.
none_tagged_msg: 'There are no posts under this tag!'
- String. Message appears when (somehow) the reader has filtered by a tag that has no related posts.
title_is_link: true
- Boolean. By default, blog titles double as links to the blog article's page. If this is set to
false
, then a separate link will be created in the header.
link_text: '[link]'
- String. If a blog post has an empty title or title_is_link is false, then a link with this inner HTML will be shown in the article's header.
tags_in_footer: false
- Boolean. By default, tags are shown in the header. If this is set to true, the tags are shown in the footer, at the bottom of the post.
cut_long_posts: .75 * window.innerHeight
- Number. The height (in pixels) at which blog posts are cut in the search page. Once exceeded, a read more button appears at the bottom of the article's body. If this is set to zero (0), blog posts will not be cut off.
img_height: 300
- Number or String. Images can take some time to load, so their height will look like zero when the function is deciding if the post should be cut off. If an image has no height attribute and has not loaded yet, its height attribute will temporarily be set to this.
show_more: 'Show more'
- String. The show more button added at the end of an article's body when it has been cut off. This is mostly here in case you want to change the language, though you can also put an image if you think that would look better on your blog!
replacers: []
- Array: [String, String]
- Array: [Regex, String]
- Just before creating the post article, the post's body is split by the
replacers
' first element and then joined back together with the second.
- Example with
['foo','bar']
:
-
'This foo is a bar foo bar of bar.'
-
['This ', ' is a bar ', ' bar of bar.']
-
'This bar is a bar bar bar of bar.'
newTitle: (post) => {
...
},
- String or Function. By default, when a post does not have a title, its visible title is automatically decided using the first 20 characters of its text content. You can change this so it instead becomes something, like 'Untitled' or the post's date. The
post
argument is a Post object, which has the following properties and methods:
- id: String
- title: String
- date: Date
- body: String
- tags: Array: [String]
- visible_tags: Array: [String]
- getArticle: Function: returns HTMLElement
- getBodyHTML: Function: returns String
- getTitleHTML: Function: returns String (note: this method calls newTitle)
getDate: (date) => {
return date.toUTCString().slice(0,16);
}
- Function. The function should return a String. This is what the date looks like inside an article post.
signature: (post) => {
return 'Posted ' + post.date.toLocaleDateString();
}
- String or Function. If a function, it should return a String to act as the blogger's signature. The
post
argument is an object with the following properties:
- title: String
- date: Date
- id: String
links_heading: 'Links'
- String. Navigation links heading. Mostly here for language.
skip_to_content: 'Skip to content'
- String. Skip to content link below the Nav links heading. Mostly here for language.
open_navs: 2
- Number. The number of
details
elements to open in the navigation. These are opened in order, so the details
opened will be the first year to appear in the nav
, and then the months in that year, then the second year, and so on.
months: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
- Array: [String x 12]
- The months in the navigation and in the search page will be printed like this. Mostly here for language.
navTitle: (post) => {
return post.date.toLocaleDateString();
}
- Function. Should return a String. The post's title will be printed like this in the navigation. The
post
argument is a Post object, so one may choose to use the post.title
or follow the default's example and use post.date
to change how the blog post's link appears.
tag_label: 'Tag: '
all_tags: '--All tags--'
tag_search: 'Go'
sort_old_to_new: 'Sort old to new'
sort_new_to_old: 'Sort new to old'
- String. All of these are here for language. Feel free to go wild anyways.
load_limit: 10
- Number. The number of blog posts that can appears in one search page at a time. If the number of posts exceeds this number, the results are paginated.
pagination_limit: 6
- Number. The number of links allowed in the pagination navigation. If the number is exceeded, the navigation becomes a
select
element with previous and next links.
skip_pagination: 'Skip pagination'
previous_page: 'Prev'
page_label: 'Page: '
next_page: 'Next'
- String. Here for language.
breadcrumb_trail: '/'
- String. Breadcrumb navigation appears when filtering by tag or after clicking on an article link. This is the separator between breadcrumbs.
breadcrumb_article: 'Article'
- String. Here for language. An article page has a breadcrumb navigation that looks something like:
- Blog / Article / Blog Title
breadcrumb_tag: 'Tag'
- String. Here for language. When filtering by tag, a search page has a breadcrumb navigation that looks like:
- Blog / Tag / tag name
next_post: '← Next'
post_nav_separator: ' | '
previous_post: 'Prev →'
- String. Here for language or style. These appear at the top and bottom of an article post's page, to travel between posts without needing to return to the blog page.
aria_goto_page: 'Goto page '
aria_current_page: 'Current page, page '
aria_page_of: ['Page ', ' of ']
aria_pagination: 'Pagination'
aria_breadcrumbs: 'Breadcrumbs'
aria_tags: 'Tags'
- String (and an Array of two Strings). Here for language. Pagination is not super intuitive for screen readers, but using the
aria-label
attribute, the pagination nav and links should read:
- Navigation page 1 of 8; Pagination. Link current page, page 1. Link goto page 2. Link goto page 3.
- And so on. (Or something like that)
- The attribute is also used for the list of tags, so screen readers know why there's suddenly a list of random words.
do_onload: (posts) => {}
- Function. This function is is called once the page finishes loading. The
posts
argument is an Array of all the Post objects (every blog post). Perhaps you want to pin a post? Then you can search for that post by id, date, title, or tag, and use the Post.getArticle
method to append it wherever your pinned posts go. For example:
- I want to search for all the posts tagged 'pin' and insert their article in the search results, after the navigation and before the actual results:
-
do_onload: posts => {
const pinned = posts.filter( p => {
return p.tags.indexOf('pin') > -1
});
const firstSection = document.querySelector('#blog-content .section');
pinned.forEach( p => {
firstSection.parentElement.insertBefore(p, firstSection);
});
}
CSS
Here's the general format do_blog should result in. I've included, hopefully, an example of everything, so just know that before wondering why there are two page navs or why there are tags in the header and the footer.
#blog.blog
div.blog-bar
nav#blog-nav
h2.links "Links"
a.skip-to-content "Skip to content"
details.year
summary "2000"
ul.months
li
details.month
summary "January"
ul.posts
li
a "1/1/2000"
hr
form#tag-form
label#tag-search "Tag: "
select
button "Go"
form#sort-form
button "Sort old to new"
div#blog-content.blog-main
nav.breadcrumbs
a.breadcrumb "Blog"
span.trail "/"
span.breadcrumb "Tag"
span.trail "/"
a.current.breadcrumb "hashtag"
nav.top.pagination.link-navigation
a.skip-to-content "Skip pagination"
a.current "1"
a "2"
hr
nav.top.pagination.select-navigation
a.skip-to-content "Skip pagination"
a.previous-page "Prev"
label "Page: "
select
a.next-page "Next"
hr
h2.year.section "2000"
h3.month.section "January"
article.post.cut-off
header.header
h1.title
a "Blog Title"
p.date
time "Sat, 01 Jan 2000"
a.blog-link "[link]"
ul.tags
li.tag
a "hashtag"
div.body.cut-off
p "This is a cut off blog post paragraph"
button.show-more "Show more"
footer
ul.tags
li.tag
a "hashtag"
nav.bottom.pagination.link-navigation
hr
a.current "1"
a "2"
nav.bottom.pagination.page-navigation
hr
a.previous-page "Prev"
label "Page: "
select
a.next-page "Next"
#blog.article
nav.breadcrumbs
a.breadcrumb "Blog"
span.trail "/"
span.breadcrumb "Article"
span.trail "/"
a.current.breadcrumb "Blog Title"
nav.top.post-navigation
a.next-post "← Next"
span.separator " | "
a.previous-post "Prev →"
article.post
header.header
h1.title "Blog Title"
p.date
time "Sat, 01 Jan 2000"
ul.tags
li.tag
a "hashtag"
div.body
p "This is a full blog post paragraph"
footer
ul.tags
li.tag
a "hashtag"
nav.bottom.post-navigation
a.next-post "← Next"
span.separator " | "
a.previous-post "Prev →"