Release v1.0.0
This commit is contained in:
parent
7138680c91
commit
593780cbbd
13 changed files with 706 additions and 50 deletions
|
@ -1,5 +1,5 @@
|
|||
# Revision history for hablo
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
## 1.0.0.0 -- 2019-04-19
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
||||
* First version. Finally released by an unexpecting developer
|
||||
|
|
64
README.md
64
README.md
|
@ -1,3 +1,65 @@
|
|||
# Hablo
|
||||
|
||||
Hablo is a minimalist static blog generator. The idea is to keep all your content in Markdown and hablo will only generate the static pages needed to list them.
|
||||
Hablo is a minimalist tool written to generate a simple blog structure from markdown articles.
|
||||
|
||||
Its key principles are to keep your blog a collection of markdown files while being completely static, leveraging the fediverse to handle comments. The best image you can have of a hablo blog is a HTML/JS article viewer. Despite this heavy usage of JS, it strives to provide a clean site that can still be displayed if JS is disabled and used to get links to the markdown files themselves.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Installation
|
||||
|
||||
Hablo is written in [haskell](https://www.haskell.org) and uses [cabal](https://www.haskell.org/cabal) for installation. The following instructions assume your version of cabal is fairly recent (≥ 1.24) please [adapt them](https://www.haskell.org/cabal/users-guide/intro.html) if you're running an older version of it, for instance on a Debian Jessie (the same command without the `new-` prefix roughly work but you might want to use sandboxes — read about it).
|
||||
|
||||
#### Simple install with cabal
|
||||
|
||||
Just issue the following line in a terminal
|
||||
|
||||
```bash
|
||||
cabal new-install hablo
|
||||
```
|
||||
|
||||
Alternatively, if you prefer to do things yourself you can do a
|
||||
|
||||
#### Manual install from this repository
|
||||
|
||||
Get a copy of this repository
|
||||
|
||||
```bash
|
||||
git clone https://git.marvid.fr/Tissevert/hablo.git
|
||||
```
|
||||
|
||||
Build the binary
|
||||
|
||||
```bash
|
||||
cabal new-update
|
||||
cabal new-build
|
||||
```
|
||||
|
||||
Install the result
|
||||
```bash
|
||||
cabal new-install hablo
|
||||
```
|
||||
|
||||
### Using hablo (tutorials)
|
||||
|
||||
Wanna give it a try ? Start by [generating your blog](https://git.marvid.fr/Tissevert/hablo/wiki/Generating%20your%20blog)
|
||||
|
||||
Want to see your blog in a web browser ? How about reading a bit about [deployment](https://git.marvid.fr/Tissevert/hablo/wiki/Deploying%20a%20hablo%20blog) to get your local environment ready or setup a publicly available one ?
|
||||
|
||||
## A bit of stretching (how-to)
|
||||
|
||||
Got the basics ? Wondering how to turn the dummy blog project you've created into a magnificent blog ? You should read about [customization](https://git.marvid.fr/Tissevert/hablo/wiki/Customizing%20your%20blog).
|
||||
|
||||
## Exploring things in depth
|
||||
|
||||
If you know everything you need to start your blog and use hablo, here are some full references to help you when you need a precise description of how some option works or what metadata you can include in your articles.
|
||||
|
||||
### References
|
||||
|
||||
- [Command line](https://git.marvid.fr/Tissevert/hablo/wiki/Command-line) arguments and options
|
||||
- articles [metadata](https://git.marvid.fr/Tissevert/hablo/wiki/Metadata)
|
||||
- template [variables](https://git.marvid.fr/Tissevert/hablo/wiki/Template%20variables)
|
||||
|
||||
### Explanation
|
||||
|
||||
If you want a more general view of the project to understand its background and the logic behind the way the various options work, read about the [architectural choices](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices)
|
||||
|
|
53
doc/Architectural-choices.md
Normal file
53
doc/Architectural-choices.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Architectural choices
|
||||
|
||||
## Static and lazy
|
||||
|
||||
Hablo is a static blog generator which uses [markdown](https://daringfireball.net/projects/markdown/) for the articles content. This means it generates the web contents once and then no rendering is performed on the server when the clients request a page. Being fully static, the contents of the pages sent by the server won't change unless you call `hablo` again and regenerate your blog.
|
||||
|
||||
Actually, hablo even tries to do as little as possible during this phase. In particular, and this is a major difference with other static blog generators, it won't render your markdown. The aim of hablo is merely to provide the minimum shell of web contents around your articles necessary to display them in a web browser. You can see hablo as a kind of «web viewer» for a set of markdown files. It tries to keep your blog «only a directory with markdown files in it» as much as possible and apologizes for all the HTML generated.
|
||||
|
||||
Due to this laziness, a large part of the presentation is done in the web browser with Javascript, mostly intercepting the click on links to load the corresponding content with AJAX and modify the page's structure accordingly. A special attention is paid to not become too dependant on JS though; all the links intercepted point to real pages that can be visited normally so you should be able to browse a hablo blog with JS disabled. The only difference is you will see the markdown content of the article instead of a rendered HTML version.
|
||||
|
||||
## Reuse
|
||||
|
||||
In the same spirit of laziness, hablo tries to implement as little as needed and to reuse everything that is already available from UNIX systems : dates, symlinks, etc.
|
||||
|
||||
Files' modification dates are used to sort files chronologically, which allows articles that get modified after they were published to go back to the first pages and be listed as recent topics.
|
||||
|
||||
Articles are tagged by placing symlinks to them in the directory representing the tag. From a filesystem perspective, tags are really a selective «view» on your articles, making your blog easy to browse locally.
|
||||
|
||||
## Page types
|
||||
|
||||
As mentioned [earlier](#static-and-lazy), hablo tries to generate as little HTML as possible. It will still create two kind of pages : one for each article, and lists for each tag as well as a «general» list that includes all the articles found in the blog (even those untagged).
|
||||
|
||||
Pages include a banner, a navigation `<div>` with links to the various tags and a content.
|
||||
|
||||
### Article pages
|
||||
|
||||
Ideally, the markdown files would be enough and there wouldn't be any HTML generated for articles. Unfortunately, in order to share direct links to articles, some HTML is necessary to reach the blog's interface, loading some JS, to handle the navigation and not only display a markdown file. The additional HTML also allows to generate [open-graph](http://ogp.me/) cards to make the links look nicer on [social media](#fediverse).
|
||||
|
||||
The article pages' content is the body of the markdown file wrapped in a HTML `<pre>` element and its title.
|
||||
|
||||
### List pages
|
||||
|
||||
There's a list for each tag and a main one that contains all your articles with any tag, even those untagged. For each of these lists there are two HTML pages, one that contains all the articles for that list, and a «short» one that previews only the latest articles that belong to that list.
|
||||
|
||||
#### Latest pages
|
||||
|
||||
Those pages contain only the latest articles in each category, including the general one containing all articles, even those untagged. They are contained in the `index.html` of the directory corresponding to each category (the root directory for the general category and a subdirectory with the tag's name for each tag). With the convention of `index.html` being served by default for a given path, these are the page you reach by default for each category. In particular, it means the very first page people see when they write the URL of your blog in the navigation bar is the latest page for the general category.
|
||||
|
||||
#### Full pages
|
||||
|
||||
Those pages contain all the articles in each category. They are contained in the `all.html` of the directory corresponding to each category.
|
||||
|
||||
## Customization
|
||||
|
||||
Hablo doesn't have templates. We all know that «HTML is for structure», «CSS is for the style», and «it's wonderful because it's separate and you shouldn't mix the form and the content» but in practice, who hasn't ever changed the structure of their page because it was easier to write the CSS like that ? Who hasn't ever resorted to some ugly CSS trick with three nested phantom `div`s with absolutely no meaning in the document flow to make a cool effect ?
|
||||
|
||||
Hablo takes a stand against this. It aims at generating HTML files that are simple and semantic and you can write a CSS theme for them but not change their structure. It won't let you.
|
||||
|
||||
## Fediverse
|
||||
|
||||
Hablo is conceived from the start to interact with the [fediverse](https://fediverse.network/) and social media in general so all pages generated embed a set of basic [open-graph](http://ogp.me/) metadata in their header. This allows links posted on social media to appear in a nice box with a picture, the name of the blog and a short description.
|
||||
|
||||
Also, being static, hablo doesn't handle dynamic things like comments directly. Instead, comments are [toots](https://git.marvid.fr/Tissevert/hablo/wiki/Customizing%20your%20blog#how-do-i-customize-the-templates-) listed on a fediverse instance.
|
159
doc/Command-line.md
Normal file
159
doc/Command-line.md
Normal file
|
@ -0,0 +1,159 @@
|
|||
# Command line syntax
|
||||
|
||||
Hablo provides several arguments that can be used at runtime. You can view them by running
|
||||
|
||||
```bash
|
||||
hablo --help
|
||||
```
|
||||
|
||||
in a terminal but here's a more complete description.
|
||||
|
||||
## Optional argument
|
||||
|
||||
Hablo expects only one argument which is optional, the root directory of the blog. It is convenient to run hablo from within the root directory of your blog because many options take a relative path so the default value is `.`.
|
||||
|
||||
## Article path
|
||||
|
||||
`-a, --articles`
|
||||
|
||||
Remember when you generated your very first hablo blog ? I said articles went to a directory named `articles` but it could be named differently. This is the option that allows you to do that.
|
||||
|
||||
For instance, assume that you're gonna make a blog about turtles. Each post is going to be a detailed presentation of all you know about one particular species of turtles, so it makes sense to put all your markdown files in a directory called «turtles». Well simply add this option when you call `hablo`.
|
||||
|
||||
```bash
|
||||
hablo --articles turtles
|
||||
```
|
||||
|
||||
Note that this option is relative to the root of your blog so even if you hadn't run this command from within your blog you would have typed
|
||||
|
||||
```bash
|
||||
hablo --articles turtles /path/to/your/blog
|
||||
```
|
||||
|
||||
See ? It was still `turtles` and not ~~`/path/to/your/blog/turtles`~~.
|
||||
|
||||
## Banner
|
||||
|
||||
`-b, --banner`
|
||||
|
||||
By default hablo will generate a very simple banner for your blog with its name as a link to the main page and this option is the way to replace it with some arbitrary HTML.
|
||||
|
||||
The banner is processed when your blog is generated so it's not relative to the root of your blog, the banner file can totally be outside of your blog structure.
|
||||
|
||||
```bash
|
||||
hablo --banner /my/set/of/banner/turtles.html /path/to/your/blog
|
||||
```
|
||||
|
||||
## Card image
|
||||
|
||||
`-c, --card-image`
|
||||
|
||||
By default, hablo will try to find the image to use for cards [automatically](https://git.marvid.fr/Tissevert/hablo/src/branch/master/src/Blog/Skin.hs#L36). It will look for a file with the [name](#name) of your blog and the extension `.ico`, `.gif`, `.jpeg`, `.jpg`, `.png` or `.svg` located at the root of the blog or in a directory called `image`, `images`, `pictures`, `skin` or `static`.
|
||||
|
||||
This option allows you to skip the auto-discover step or to use an image that wouldn't be found with the above method and directly tell hablo what file to use.
|
||||
|
||||
Note that not all cards generated for your blog will necessarily contain an image. If none of the expected path for card images exists and you don't provide one with this option, then pages won't embed an [open-graph](http://ogp.me/) picture in their card by default but articles with a [featured image](https://git.marvid.fr/Tissevert/hablo/wiki/Metadata#featured-image) will still do (and the image used for the card will be the featured image of the article).
|
||||
|
||||
Card images in themselves are only relevant once your blog is deployed and accessed by a browser or linked to. Hablo only includes a link to them or not, so of course the value of this option must be a relative path within your blog's structure. If you put a file outside, your web server will of course return 404 errors whenever something will read the corresponding header and attempt to access it.
|
||||
|
||||
```bash
|
||||
hablo --card-image skin/defaultCardImage.png
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
`-C, --comments-at`
|
||||
|
||||
This option tells hablo on which [instance](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#fediverse) to look for the comments and expects a value containing a [link](https://en.wikipedia.org/wiki/URL#Syntax) to the instance itself as a scheme and an authority, no path.
|
||||
|
||||
```bash
|
||||
hablo --comments-at https://turtles.social
|
||||
```
|
||||
|
||||
See the [metadata](https://git.marvid.fr/Tissevert/hablo/wiki/Metadata#comments) reference for a detailed explanation of how comments are activated for each articles.
|
||||
|
||||
## Favicon
|
||||
|
||||
`-f, --favicon`
|
||||
|
||||
Hablo looks for a favicon for your blog just like it does for [card images](#card-image) except it scans for a file called `favicon` and not the name of your blog.
|
||||
|
||||
Just like `--card-image`, this option can be used to skip looking for the favicon or to use a favicon with an arbitrary path. If you don't choose a favicon with this option and none is found automatically, your pages simply won't include one.
|
||||
|
||||
And, again, like for card images because they behave in very similar ways, the value expected is a relative path within your blog's structure.
|
||||
|
||||
```bash
|
||||
hablo --favicon skin/eyeOfTheTurtle.jpg
|
||||
```
|
||||
|
||||
## Head file
|
||||
|
||||
`-H, --head`
|
||||
|
||||
The header section of the HTML pages includes several things such as the [open-graph](http://ogp.me/) metadata, the script inclusions for hablo's client code and for its [dependencies](https://git.marvid.fr/Tissevert/hablo/wiki/Deploying%20a%20hablo%20blog).
|
||||
|
||||
Use the `--head` option to add some arbitrary HTML elements to the header of your pages. This is the way to use a CSS theme for your blog, as is shown in the [customization](https://git.marvid.fr/Tissevert/hablo/wiki/Customizing%20your%20blog#how-do-i-use-a-custom-skin-) how-to.
|
||||
|
||||
## Name
|
||||
|
||||
`-n, --name`
|
||||
|
||||
Each blog has a name which is used in the default banner of your site and as the title of all pages (displayed in the tab's name and in the window name when your blog's tab is focused). Hablo infers it from the name of the directory containing it. It works on an absolute version the path it receives, so it won't suddenly think your blog is called `..` because you ran
|
||||
|
||||
```bash
|
||||
hablo ..
|
||||
```
|
||||
|
||||
from the path `turtles/articles`, it will still understand it's called «turtles».
|
||||
|
||||
You can use this option if you want to override this behaviour and provide a different name, for example because you simplified the name of your blog when you created a directory for it or because you want the name of your blog to contain characters prohibited in a directory name by your OS.
|
||||
|
||||
```bash
|
||||
hablo --name "Turtles/Paradize"
|
||||
```
|
||||
|
||||
## Pages
|
||||
|
||||
`-p, --pages`
|
||||
|
||||
This option doesn't work yet but hablo will support static pages in addition to articles in a future release. Like [articles](#article-path), they will be expected to be located in a sub-directory called `pages/` but this option will allow you to use an arbitrary path within your blog's structure.
|
||||
|
||||
## Number of articles previewed
|
||||
|
||||
`-A, --preview-articles`
|
||||
|
||||
On the page that [lists](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#list-pages) only the most recent articles, only 3 articles are previewed by default. This option lets you change this number.
|
||||
|
||||
```bash
|
||||
hablo --preview-articles 5
|
||||
```
|
||||
|
||||
will make all your short pages display 5 articles.
|
||||
|
||||
## Number of lines preview for articles
|
||||
|
||||
`-L, --preview-lines`
|
||||
|
||||
On each page that [lists](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#page-types) articles, only the first lines of each article are displayed. This option controls how many of them. By default, 10 lines will be displayed.
|
||||
|
||||
Note that this is a number of lines in the markdown files and has nothing to do with the amount of space used on the client screen once the blog gets rendered and viewed. Empty lines are included in the count, which starts right after the header of your markdown file (title and possible metadata). If you write articles with only a couple of very long one-line paragraphs, then all of them might get displayed.
|
||||
|
||||
## Remarkable config
|
||||
|
||||
`-r, --remarkable-config`
|
||||
|
||||
Hablo uses [remarkable](https://github.com/jonschlinkert/remarkable) to render your markdown articles to HTML. It calls it with very simple settings by default (`{html: true}`) but this option lets you set exactly what is passed to remarkable. This is useful to set a highlighter function for instance.
|
||||
|
||||
The file is read by hablo when the blog is generated and its content gets included into the client JS code so it can be located absolutely anywhere, even outside your blog's directory.
|
||||
|
||||
## Wording
|
||||
|
||||
`-w, --wording`
|
||||
|
||||
This option makes hablo look for the value of the texts used to [generate the pages](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural choices#customization) in an [arbitrary file](https://git.marvid.fr/Tissevert/hablo/wiki/Template variables). It is useful to translate your blog (all texts are in english by default) or to give it a particular feel.
|
||||
|
||||
```bash
|
||||
hablo --wording /blogs/translations/fr-ca.conf /path/to/your/blog
|
||||
```
|
||||
|
||||
The value of this option is used by hablo when your blog gets generated and is therefore completely independant from the path of your blog, the file it refers to can be located anywhere, including outside your blog structure.
|
107
doc/Customizing-your-blog.md
Normal file
107
doc/Customizing-your-blog.md
Normal file
|
@ -0,0 +1,107 @@
|
|||
# Customizing your blog
|
||||
|
||||
«Miss !» I hear you calling. «Miss, the blog we've generated… it's… it's pretty ugly actually ?!»
|
||||
|
||||
No it's not. It's just pure HTML. Hablo isn't there to provide you with a full-fledged website, it's just the tool that writes the HTML files of your static blog and updates them so you only have to concentrate on what matters : the contents of your articles.
|
||||
|
||||
## How do I use a custom skin ?
|
||||
|
||||
Hablo provides an option to embed arbitrary HTML in the `<head></head>` of the pages it generates. This is the perfect place to put a link to a CSS file you've written ! The idea behind hablo is to generate a part of your blog, the HTML files, but the rest of your blog's directory is all yours to put what you like in it.
|
||||
|
||||
Create a CSS file !
|
||||
|
||||
```bash
|
||||
echo > style.css <<EOF
|
||||
a {
|
||||
color: #0cc;
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
Then write a HTML header line that includes it :
|
||||
|
||||
```bash
|
||||
echo > head.html <<EOF
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
EOF
|
||||
```
|
||||
|
||||
And tell hablo to use this file when generating the blog:
|
||||
|
||||
```bash
|
||||
hablo --head head.html
|
||||
```
|
||||
|
||||
«Ok, now the skin is a bit better, but I don't like the default texts, and I can't fix that with CSS !»
|
||||
|
||||
## How do I customize the templates ?
|
||||
|
||||
Hablo doesn't allow you to change the [structure of the pages](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#customization) but what you can do is change the texts used by default. They are contained in the file `share/defaultWording.conf` and you can copy this file to your blog's directory and override its entries (you don't have to keep the ones you don't change). For example, let's do this:
|
||||
|
||||
```bash
|
||||
echo "latestPage = Recently on Turtles/paradize" > wording.conf
|
||||
```
|
||||
|
||||
Then generate your blog telling hablo to use this wording
|
||||
|
||||
```bash
|
||||
hablo -w wording.conf
|
||||
```
|
||||
|
||||
The complete list of the available template variables can be found in the [reference](https://git.marvid.fr/Tissevert/hablo/wiki/Template%20variables).
|
||||
|
||||
Another possible customization that would be done by means of template files in other blog generators is the header of your blog. Just like for the CSS header, write a custom file with your HTML and tell hablo about it, this time the option is called `banner`:
|
||||
|
||||
```bash
|
||||
echo > banner.html <<EOF
|
||||
<h1>## T·U·R·T·L·E·S / paradize##</h1>
|
||||
<div>
|
||||
<p>The amazing world of fashionable young turtles.</p>
|
||||
</div>
|
||||
EOF
|
||||
```
|
||||
Then run hablo and pass it the path to your file with the `--banner` option.
|
||||
|
||||
```bash
|
||||
hablo --banner banner.html
|
||||
```
|
||||
|
||||
If you'd like to read more details about all the available customization, you should read about hablo's [command-line arguments](https://git.marvid.fr/Tissevert/hablo/wiki/Command-line).
|
||||
|
||||
## How do I activate comments on my blog ?
|
||||
|
||||
Since hablo is [static](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural choices#static-and-lazy) there's no way to directly include the comments in the pages. On a blog generated with hablo comments are fetched with JS and dynamically added to the page when it gets rendered in the client.
|
||||
|
||||
Let's say you published an article, tell people about it from your fediverse instance by posting a link to that article. First, we need to find the status [`id`](https://docs.joinmastodon.org/api/entities#status) of your post.
|
||||
|
||||
Point your web browser to the page that focuses on it and shows the thread of its answers (possibly none yet if you've just posted it) and take the «code» component of the URL (a series of apparently meaningless digits or letters).
|
||||
|
||||
On a Mastodon instance the URL should look like
|
||||
|
||||
```
|
||||
https://turtles.social/users/fondofshells/statuses/101841418824885713
|
||||
```
|
||||
|
||||
The part that interests you is the end of the URL after `statuses/` (here, `101841418824885713`).
|
||||
|
||||
On a Pleroma instance URLs look like
|
||||
|
||||
```
|
||||
https://turtles.social/notice/9hK9GL3GcRbMw0CV0a
|
||||
```
|
||||
|
||||
And you still want the end, in this case after `notice/` (here, `9hK9GL3GcRbMw0CV0a`).
|
||||
|
||||
Now that you've found your post's `id`, let's add a `comments` [metadata](https://git.marvid.fr/Tissevert/hablo/wiki/Metadata#comments) to your article with this `id` as value in its metadata header :
|
||||
|
||||
```YAML
|
||||
comments: 101841418824885713
|
||||
```
|
||||
|
||||
Now, when you generate your blog, tell hablo to look for comments on the instance where you posted :
|
||||
|
||||
```bash
|
||||
hablo --comments-at https://turtles.social
|
||||
```
|
||||
|
||||
Be sure to clear the cache of your web browser before visiting your article again. A «comments» section now shows after your article, with possibly some comments if someone has already answered your post on the fediverse.
|
61
doc/Deploying-a-hablo-blog.md
Normal file
61
doc/Deploying-a-hablo-blog.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
# Deployment
|
||||
|
||||
Since hablo generates static blogs, deployment is a fairly easy step. The only detail to pay attention to is the handling of dependencies.
|
||||
|
||||
We show here a simple local deployment of your blog assuming you use NGinx but this is fairly easy to transpose to your favourite web server. First let's create an NGinx configuration file for your blog. Let's put the following basic configuration
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name blog.turtles.social;
|
||||
listen 80;
|
||||
|
||||
root "/path/to/Turtles paradize";
|
||||
}
|
||||
```
|
||||
|
||||
into `/etc/nginx/sites_available/turtles.conf`. Of course, the `server_name` used here is purely an exemple assuming you bought a domain name for your blog and you pointed it to the very host you're working on. This is in practice highly unlikely; I could've suggested `localhost` instead of `blog.turtles.social` but you may have other local virtual hosts configured, I can't know your particular setup. You may have simply added an alias like `turtles.local` in your `/etc/hosts` to bypass all DNS resolution. If you have no idea what I'm saying and this is the first time you've configured a virtual host in NGinx, just use `localhost`.
|
||||
|
||||
Enable the site
|
||||
|
||||
```bash
|
||||
sudo ln -s ../sites_available/turtles.conf /etc/nginx/sites_enabled
|
||||
```
|
||||
|
||||
and now reload the nginx server.
|
||||
|
||||
```bash
|
||||
sudo nginx -s reload
|
||||
```
|
||||
|
||||
Now let's install the dependencies.
|
||||
|
||||
## UnitJS
|
||||
|
||||
Hablo requires [UnitJS](https://git.marvid.fr/Tissevert/UnitJS). Go to some temporary work directory, clone it and generate the packed JS module.
|
||||
|
||||
```bash
|
||||
cd /tmp
|
||||
git clone https://git.marvid.fr/Tissevert/UnitJS.git
|
||||
cd UnitJS
|
||||
make
|
||||
```
|
||||
|
||||
It's in `dist/unit.js`. Let's go back to your blog's directory and copy it.
|
||||
|
||||
```bash
|
||||
cd "/path/to/My perfect life is better than yours"
|
||||
mkdir -p js
|
||||
cp /tmp/UnitJS/dist/unit.js js
|
||||
```
|
||||
|
||||
## Remarkable
|
||||
|
||||
The markdown is converted to HTML in the client browser with the JS library [remarkable](https://github.com/jonschlinkert/remarkable).
|
||||
|
||||
We can simply download it in your `js` directory.
|
||||
|
||||
```bash
|
||||
wget 'https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.7.1/remarkable.min.js' -O js/remarkable.min.js
|
||||
```
|
||||
|
||||
That's it ! Your blog should now be displayed when you point your web browser to `http://blog.turtles.social` (or `http://turtles.local` or `http://localhost` depending on what you put in your web server's configuration for `server_name`). You might now want to read about [tuning it](https://git.marvid.fr/Tissevert/hablo/wiki/Customizing%20your%20blog) to your taste.
|
48
doc/Generating-your-blog.md
Normal file
48
doc/Generating-your-blog.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Generating your blog
|
||||
|
||||
Ok, let's start your blog ! Create a directory for it and go inside it.
|
||||
|
||||
```bash
|
||||
mkdir "Turtles paradize"
|
||||
cd "Turtles paradize"
|
||||
```
|
||||
|
||||
Apparently it's a blog about turtles. It's empty right now, let's create a directory for your articles.
|
||||
|
||||
```bash
|
||||
mkdir articles
|
||||
```
|
||||
|
||||
You [could call it something else](https://git.marvid.fr/Tissevert/hablo/wiki/Command-line#article-path) but that's the name `hablo` expects it to be. Well let's write an article then !
|
||||
|
||||
```bash
|
||||
cat > articles/Olive\ ridley\ sea\ turtle.md <<EOF
|
||||
# Olive ridley sea turtle
|
||||
|
||||
I love Olive ridley sea turtles ! They are so cool and big and wow !
|
||||
EOF
|
||||
```
|
||||
|
||||
Ok, ok, not everyone uses heredocs to write their articles. Personally I don't. You're writing a blog so you probably already have a favourite text editor; use it. The only thing I care about is, at this point, that you've created the file `Olive\ ridley\ sea\ turtle.md` in the `articles` directory with some markdown content in it.
|
||||
|
||||
Ready ? Good news, we're almost done. The only thing left is to tag your first article. With hablo articles don't have to be put in a single category but they can be tagged this and that to indicate that they are somehow linked to one topic or another (they don't have to, you can perfectly leave an article untagged but the tags directory itself must exist). Tags live in a subdirectory of `articles`.
|
||||
|
||||
```bash
|
||||
mkdir -p articles/tags/Sea\ turtles
|
||||
```
|
||||
|
||||
You tag an article by putting a symbolic link to it in the tag folders of your choice.
|
||||
|
||||
```bash
|
||||
ln -s ../../Olive\ ridley\ sea\ turtle.md articles/tags/Sea\ turtles
|
||||
```
|
||||
|
||||
All that's left is to call hablo to look at your blog and do its job.
|
||||
|
||||
```bash
|
||||
hablo
|
||||
```
|
||||
|
||||
Yay !! Congratulations, you've successfully generated your first blog with hablo. Look, there are now HTML files and a JS directory in your `Turtles paradize` blog directory.
|
||||
|
||||
Now you might wonder how to view your blog in your browser so you can start tuning it. Read about [deployment](https://git.marvid.fr/Tissevert/hablo/wiki/Deploying%20a%20hablo%20blog) or [customization](https://git.marvid.fr/Tissevert/hablo/wiki/Customizing%20your%20blog).
|
60
doc/Metadata.md
Normal file
60
doc/Metadata.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Metadata
|
||||
|
||||
Markdown articles are rendered as late as possible into HTML, even the [article pages](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural choices#article-pages) only wrap the markdown content into a `<pre></pre>` element. But metadata are still read by hablo when it analyses your blog because some metadata trigger special behaviors.
|
||||
|
||||
## Format
|
||||
|
||||
Hablo, just like remarkable, supports YAML-style metadata between lines containing only three `'-'` like this
|
||||
|
||||
```
|
||||
---
|
||||
YAML values go here
|
||||
---
|
||||
```
|
||||
|
||||
The metadata header is expected to be located right before or right after the title, separated only by as many newlines as you want.
|
||||
|
||||
```markdown
|
||||
# Example title
|
||||
---
|
||||
author: me
|
||||
date: 2017-03-18
|
||||
---
|
||||
|
||||
```
|
||||
|
||||
## Metadata with a special meaning
|
||||
|
||||
You can of course put any metadata you like in your articles but some metadata will be watched by hablo.
|
||||
|
||||
### Comments
|
||||
|
||||
Comments on an article are enabled by setting a `comments` metadata with the [`id`](https://docs.joinmastodon.org/api/entities#status) of a `status` on the fediverse.
|
||||
|
||||
This means comments are enabled on a per-article basis and can be totally deactivated anytime (although in that case the whole «comment» section disappears so the previous comments won't show anymore). Remember that this per-article setting is not enough in itself, the [--comments-at](https://git.marvid.fr/Tissevert/hablo/wiki/Command-line#comments) option still acts as a «master-switch» : declaring the instance on which to look for the status by its `id` is obviously necessary.
|
||||
|
||||
```YAML
|
||||
comments: 101781535999718634
|
||||
```
|
||||
|
||||
### Date
|
||||
|
||||
Hablo associates to each article the date when it was written or last edited to sort articles chronologically and to display it. Trying to [reuse](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#reuse) as much as possible, this date is by default the «last modified» Unix date of the file that contains the article.
|
||||
|
||||
You can override this behaviour by setting a `date` metadata. It can contain a date with an optional time (hour and minute) and a timezone, also optional. If you don't set a timezone the current one will be used. Note that this means that some of your article might seem to have their dates «moving» a bit, if for example you set the date for one of your article (but not the timezone), generate your blog, then travel to a place with a very different timezone, and generate your blog again.
|
||||
|
||||
### Featured image
|
||||
|
||||
Every article can contain as many pictures as you like, anywhere you like in it. But you can choose one of them (or even one that doesn't appear in your article) to appear in the [open-graph](http://ogp.me/) card of your article, so that it is displayed and «represents» your article when you paste links to it on social media. The exact name of the metadata is `featuredImage`, camel-case, and it expects the site-root relative link of the picture (so the path as seen from your blog by a client accessing it).
|
||||
|
||||
```YAML
|
||||
featuredImage: /media/turtles/olive-ridley.jpg
|
||||
```
|
||||
|
||||
### Summary
|
||||
|
||||
You can write a short description of your article and use it as the value of its `summary` metadata and this text will be used in the open-graph [description](http://ogp.me/#optional) of the card generated for the article.
|
||||
|
||||
```YAML
|
||||
summary: This week, I'm gonna tell you everything about the olive ridley sea turtle !
|
||||
```
|
74
doc/Template-variables.md
Normal file
74
doc/Template-variables.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Template variables
|
||||
|
||||
Here is the full list of the available text template variables that you can customize in your wording file and the name of the variable parameters some of them expect.
|
||||
|
||||
Variables are prefixed by a `$` and may be enclosed in brackets `{ }` to lift any ambiguity and separate the variable from the surrounding characters (exemple : does the template `the $nth` refers to a `nth` variable or is it the variable `n` followed by the literal characters `th` ? the first interpretation prevails, and if you want the second one you should write `the ${n}th`).
|
||||
|
||||
Most of the templates are used «at [compile-time](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#static-and-lazy)» when the blog is generated and so errors, missing variables etc. are caught early but some like [metadata](#metadata) are only used client-side and hence need to be more resistant. If a variable present in a template is missing when the template is rendered, an `undefined` JS value is returned.
|
||||
|
||||
Now some contexts, especially article contexts may vary a bit so some templates like `metadata` need a way to «catch» those null values and keep up templating. For instance, an article may or may not have an author or tags. You could for instance decide that the base articles of your blog aren't signed because they obviously come from you or the organization that publish the blog but that when the blog publishes an article by a special guest it needs a special mention. To «harden» a template string against possible null values, just enclose the corresponding optional part between `${? ?}`.
|
||||
|
||||
## allLink
|
||||
|
||||
The text used in the link to the [full](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#full-pages) page on the [latest](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#latest-pages) page of the same category.
|
||||
|
||||
## allPage
|
||||
|
||||
The `<h2>` title used on the [full page for all the articles](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#list-pages).
|
||||
|
||||
## allTaggedPage
|
||||
|
||||
The template for the `<h2>` title used on the [full pages for all the articles tagged a given tag](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#list-pages).
|
||||
|
||||
It of course expects one variable named `$tag` : the name of the tag for the given page.
|
||||
|
||||
## commentsLink
|
||||
|
||||
The text displayed after the comments as a link to the toot that opens the comments section inviting visitors to comment the post.
|
||||
|
||||
## commentsSection
|
||||
|
||||
The content of the `<h2>` element at the begining of the comments on the pages of articles that have comments enabled.
|
||||
|
||||
## dateFormat
|
||||
|
||||
This isn't really a template per-se but impacts the way the dates are generated to use in the [metadata](#metadata) template. More precisely it contains the arguments passed to the [toLocaleDateString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString) method. It can thus only consist in a locale name, but since `toLocaleDateString` also accepts an object as second argument, you can write the whole thing using JSON like this :
|
||||
|
||||
```
|
||||
dateFormat = ["en-AU", {"month":"long", "day":"2-digit"}]
|
||||
```
|
||||
|
||||
## latestLink
|
||||
|
||||
The text used in the link to the [latest](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#latest-pages) page on the [full](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#full-pages) page of the same category.
|
||||
|
||||
## latestPage
|
||||
|
||||
The `<h2>` title used on the [latest page for all the articles](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#list-pages). This page is the main page of your blog so this is more or less the first title that people see when they come to your blog.
|
||||
|
||||
## latestTaggedPage
|
||||
|
||||
The template for the `<h2>` title used on the [latest pages for all the articles tagged a given tag](https://git.marvid.fr/Tissevert/hablo/wiki/Architectural%20choices#list-pages).
|
||||
|
||||
It of course expects one variable named `$tag` : the name of the tag for the given page.
|
||||
|
||||
## metadata
|
||||
|
||||
The template of the text used to present the metadata associated to each article. This template is used both in the preview of an article on any page that lists it and on the article's page itself. It expects three possible variables
|
||||
|
||||
- `$author`
|
||||
- `$date`
|
||||
- `$tags`
|
||||
|
||||
As mentioned in the introduction, some of them may be null so you may want to protect the whole `metadata` template with `${? ?}` like it is done in this variable's default value :
|
||||
|
||||
```
|
||||
metadata = ${?by ${author} ?}on ${date}${? tagged ${tags}?}
|
||||
```
|
||||
|
||||
If an article has an author, the rendered `metadata` string will start with «by <AUTHOR>», otherwise it will directly start with «on <SOME DATE>». Likewise all articles with tags will have their `metadata` end with « tagged » and then the list of comma-separated tags but if an article doesn't have tags, it will simply end after the date.
|
||||
|
||||
## tagsList
|
||||
|
||||
The content of the `<h2>` element in the navigation `<div>` that lists all the tags of your blog.
|
||||
|
39
hablo.cabal
39
hablo.cabal
|
@ -3,9 +3,16 @@ cabal-version: >= 1.10
|
|||
-- For further documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: hablo
|
||||
version: 0.1.0.0
|
||||
version: 1.0.0.0
|
||||
synopsis: A minimalist static blog generator
|
||||
-- description:
|
||||
description:
|
||||
Hablo is a fediverse-oriented static blog generator for articles written
|
||||
in Markdown. It tries to generate as little HTML as needed and uses
|
||||
Javascript to implement dynamic features in the browser.
|
||||
|
||||
Those features include the handling of comments and a cached navigation
|
||||
to minimize the queries to the server. Hablo also generate cards for all
|
||||
pages, including articles for prettier shares on social-networks.
|
||||
homepage: https://git.marvid.fr/Tissevert/hablo
|
||||
-- bug-reports:
|
||||
license: BSD3
|
||||
|
@ -37,20 +44,20 @@ executable hablo
|
|||
, Paths_hablo
|
||||
, Pretty
|
||||
-- other-extensions:
|
||||
build-depends: aeson
|
||||
, base <4.13.0.0
|
||||
, bytestring
|
||||
, containers
|
||||
, directory
|
||||
, filepath
|
||||
, lucid
|
||||
, mtl
|
||||
, optparse-applicative
|
||||
, parsec
|
||||
, template
|
||||
, text
|
||||
, time
|
||||
, unix
|
||||
build-depends: aeson >= 1.4.2 && < 1.5
|
||||
, base >= 4.12.0 && < 4.13
|
||||
, bytestring >= 0.10.8 && < 0.11
|
||||
, containers >= 0.6.0 && < 0.7
|
||||
, directory >= 1.3.3 && < 1.4
|
||||
, filepath >= 1.4.2 && < 1.5
|
||||
, lucid >= 2.9.11 && < 2.10
|
||||
, mtl >= 2.2.2 && < 2.3
|
||||
, optparse-applicative >= 0.14.3 && < 0.15
|
||||
, parsec >= 3.1.13 && < 3.2
|
||||
, template >= 0.2.0 && < 0.3
|
||||
, text >= 1.2.3 && < 1.3
|
||||
, time >= 1.8.0 && < 1.9
|
||||
, unix >= 2.7.2 && < 2.8
|
||||
ghc-options: -Wall -dynamic
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
allLink = See all
|
||||
allPage = All articles
|
||||
allTaggedPage = All articles tagged ${tag}
|
||||
commentsLink = Comment on the fediverse
|
||||
commentsSection = Comments
|
||||
dateFormat = en-US
|
||||
latestLink = See only latest
|
||||
|
|
|
@ -1,56 +1,77 @@
|
|||
function Metadata(modules) {
|
||||
var comments = modules.cache.make(function(threadId) {
|
||||
return modules.async.bind(
|
||||
modules.async.http({method: 'GET', url: url(threadId)}),
|
||||
function(queryResult) {
|
||||
if(queryResult.status == 200) {
|
||||
try {
|
||||
return modules.async.wrap(render(JSON.parse(queryResult.responseText)));
|
||||
} catch(e) {
|
||||
return modules.async.fail('Server returned invalid JSON for ' + url);
|
||||
}
|
||||
} else {
|
||||
return modules.async.fail('Could not load comments at ' + url);
|
||||
}
|
||||
}
|
||||
modules.async.parallel(
|
||||
getJSON(url(threadId)),
|
||||
getJSON(url(threadId) + '/context'),
|
||||
),
|
||||
modules.async.map(function(t) {
|
||||
return [renderLink(t[0]), renderAnswers(t[1])];
|
||||
})
|
||||
);
|
||||
})
|
||||
});
|
||||
return {
|
||||
get: get,
|
||||
getComments: getComments
|
||||
};
|
||||
|
||||
function url(threadId) {
|
||||
return blog.path.commentsAt + '/api/v1/statuses/' + threadId + '/context';
|
||||
return blog.path.commentsAt + '/api/v1/statuses/' + threadId;
|
||||
}
|
||||
|
||||
function getJSON(url) {
|
||||
return modules.async.bind(
|
||||
modules.async.http({method: 'GET', url: url}),
|
||||
function(queryResult) {
|
||||
if(queryResult.status == 200) {
|
||||
try {
|
||||
return modules.async.wrap(JSON.parse(queryResult.responseText));
|
||||
} catch(e) {
|
||||
return modules.async.fail('Server returned invalid JSON for ' + url);
|
||||
}
|
||||
} else {
|
||||
return modules.async.fail('Could not load page ' + url);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getComments(articleKey) {
|
||||
var threadId = blog.articles[articleKey].metadata.comments;
|
||||
if(blog.path.commentsAt != undefined && threadId != undefined) {
|
||||
var ul = modules.dom.make('ul');
|
||||
var div = emptySection(ul);
|
||||
modules.async.run(
|
||||
modules.async.bind(
|
||||
comments.get(threadId),
|
||||
modules.async.map(function(comments) {
|
||||
comments.forEach(function(comment) {ul.appendChild(comment);});
|
||||
})
|
||||
comments.get(threadId), modules.async.map(populateComments(div, ul))
|
||||
)
|
||||
);
|
||||
return emptySection(ul, threadId);
|
||||
return [div];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function emptySection(ul, threadId) {
|
||||
return [modules.dom.make('div', {class: 'comments'}, [
|
||||
function populateComments(div, ul) {
|
||||
return function(apiResults) {
|
||||
var post = apiResults[0], comments = apiResults[1];
|
||||
div.appendChild(post);
|
||||
comments.forEach(function(comment) {ul.appendChild(comment);});
|
||||
};
|
||||
}
|
||||
|
||||
function emptySection(ul) {
|
||||
return modules.dom.make('div', {class: 'comments'}, [
|
||||
modules.dom.make('h2', {innerText: blog.wording.commentsSection}),
|
||||
ul,
|
||||
modules.dom.make('a', {
|
||||
href: blog.path.commentsAt + '/notice/' + threadId,
|
||||
innerText: 'Comment on the fediverse'
|
||||
})
|
||||
])];
|
||||
ul
|
||||
]);
|
||||
}
|
||||
|
||||
function renderLink(post) {
|
||||
return modules.dom.make('a', {
|
||||
href: post.url,
|
||||
innerText: blog.wording.commentsLink
|
||||
});
|
||||
}
|
||||
|
||||
function getContent(descendant) {
|
||||
|
@ -66,7 +87,7 @@ function Metadata(modules) {
|
|||
});
|
||||
}
|
||||
|
||||
function render(comments) {
|
||||
function renderAnswers(comments) {
|
||||
return comments.descendants.map(function(descendant) {
|
||||
return modules.dom.make('li', {}, [
|
||||
modules.dom.make('a', {href: descendant.account.url}, [
|
||||
|
|
|
@ -27,6 +27,7 @@ data Wording = Wording {
|
|||
allLink :: Text
|
||||
, allPage :: Text
|
||||
, allTaggedPage :: Template
|
||||
, commentsLink :: Text
|
||||
, commentsSection :: Text
|
||||
, dateFormat :: Text
|
||||
, latestLink :: Text
|
||||
|
@ -38,13 +39,14 @@ data Wording = Wording {
|
|||
|
||||
keys :: [String]
|
||||
keys = [
|
||||
"allLink", "allPage", "allTaggedPage", "commentsSection", "dateFormat"
|
||||
, "latestLink", "latestPage", "latestTaggedPage", "metadata", "tagsList"
|
||||
"allLink", "allPage", "allTaggedPage", "commentsLink", "commentsSection"
|
||||
, "dateFormat", "latestLink", "latestPage", "latestTaggedPage", "metadata"
|
||||
, "tagsList"
|
||||
]
|
||||
|
||||
values :: [Wording -> Text]
|
||||
values = [
|
||||
allLink, allPage, showTemplate . allTaggedPage, commentsSection
|
||||
allLink, allPage, showTemplate . allTaggedPage, commentsLink, commentsSection
|
||||
, dateFormat, latestLink, latestPage, showTemplate . latestTaggedPage
|
||||
, metadata, tagsList
|
||||
]
|
||||
|
@ -99,6 +101,7 @@ build arguments = do
|
|||
allLink = wording ! "allLink"
|
||||
, allPage = wording ! "allPage"
|
||||
, allTaggedPage
|
||||
, commentsLink = wording ! "commentsLink"
|
||||
, commentsSection = wording ! "commentsSection"
|
||||
, dateFormat = wording ! "dateFormat"
|
||||
, latestLink = wording ! "latestLink"
|
||||
|
|
Loading…
Reference in a new issue