mirror of
https://github.com/facundoolano/jorge.git
synced 2024-12-26 21:58:51 +01:00
Tutorial (#12)
* rearrange tutorial outline * tweak code style in docs and initfiles * inform of written config and readme files in init * add the first couple of tutorial pages * code background * tweak file descriptions * introduce delay to prevent burst of rebuilds * remove redundant rebuild * refactor serve code some more * jorge serve tutorial chapter * add intro * style tables * code style tweaks * fix footnotes heading * separate intro and install * comment out blog * nav links * fix footer link * docs layout tweaks * zero pad date format in slug * jorge post tutorial page * right align tutorial links * build tutorial * grammar corrections
This commit is contained in:
parent
263610807b
commit
9a782bedac
19 changed files with 442 additions and 93 deletions
|
@ -60,10 +60,15 @@ func Init(projectDir string) error {
|
||||||
// creating config and readme files manually, since I want to use the supplied config values in their
|
// creating config and readme files manually, since I want to use the supplied config values in their
|
||||||
// contents. (I don't want to render liquid templates in the WalkDir below since some of the initfiles
|
// contents. (I don't want to render liquid templates in the WalkDir below since some of the initfiles
|
||||||
// are actual templates that should be left as is).
|
// are actual templates that should be left as is).
|
||||||
|
configDir := filepath.Join(projectDir, "config.yml")
|
||||||
configFile := fmt.Sprintf(INIT_CONFIG, siteName, siteAuthor, siteUrl)
|
configFile := fmt.Sprintf(INIT_CONFIG, siteName, siteAuthor, siteUrl)
|
||||||
|
os.WriteFile(configDir, []byte(configFile), site.FILE_RW_MODE)
|
||||||
|
fmt.Println("added", configDir)
|
||||||
|
|
||||||
|
readmeDir := filepath.Join(projectDir, "README.md")
|
||||||
readmeFile := fmt.Sprintf(INIT_README, siteName, siteAuthor)
|
readmeFile := fmt.Sprintf(INIT_README, siteName, siteAuthor)
|
||||||
os.WriteFile(filepath.Join(projectDir, "config.yml"), []byte(configFile), site.FILE_RW_MODE)
|
os.WriteFile(readmeDir, []byte(readmeFile), site.FILE_RW_MODE)
|
||||||
os.WriteFile(filepath.Join(projectDir, "README.md"), []byte(readmeFile), site.FILE_RW_MODE)
|
fmt.Println("added", readmeDir)
|
||||||
|
|
||||||
// walk over initfiles fs
|
// walk over initfiles fs
|
||||||
// copy create directories and copy files at target
|
// copy create directories and copy files at target
|
||||||
|
@ -115,9 +120,9 @@ func Post(root string, title string) error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
slug := slugify(title)
|
slug := slugify(title)
|
||||||
filename := strings.ReplaceAll(config.PostFormat, ":title", slug)
|
filename := strings.ReplaceAll(config.PostFormat, ":title", slug)
|
||||||
filename = strings.ReplaceAll(filename, ":year", fmt.Sprint(now.Year()))
|
filename = strings.ReplaceAll(filename, ":year", fmt.Sprintf("%d", now.Year()))
|
||||||
filename = strings.ReplaceAll(filename, ":month", fmt.Sprint(int(now.Month())))
|
filename = strings.ReplaceAll(filename, ":month", fmt.Sprintf("%02d", now.Month()))
|
||||||
filename = strings.ReplaceAll(filename, ":day", fmt.Sprint(now.Day()))
|
filename = strings.ReplaceAll(filename, ":day", fmt.Sprintf("%02d", now.Day()))
|
||||||
path := filepath.Join(config.SrcDir, filename)
|
path := filepath.Join(config.SrcDir, filename)
|
||||||
|
|
||||||
// ensure the dir already exists
|
// ensure the dir already exists
|
||||||
|
|
|
@ -130,10 +130,26 @@ img.cover-img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.src pre {
|
.src pre {
|
||||||
font-size: 1rem;
|
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding-left: 1rem
|
padding: 1rem;
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.src pre, code {
|
||||||
|
background: whitesmoke;
|
||||||
|
font-size: 1rem;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
|
.src pre::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
|
@ -185,7 +201,7 @@ blockquote {
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tr {
|
table tr {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/facundoolano/jorge/config"
|
"github.com/facundoolano/jorge/config"
|
||||||
"github.com/facundoolano/jorge/site"
|
"github.com/facundoolano/jorge/site"
|
||||||
|
@ -21,10 +22,6 @@ func Serve(rootDir string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rebuildSite(config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch for changes in src and layouts, and trigger a rebuild
|
// watch for changes in src and layouts, and trigger a rebuild
|
||||||
watcher, broker, err := setupWatcher(config)
|
watcher, broker, err := setupWatcher(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -42,7 +39,6 @@ func Serve(rootDir string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", config.ServerHost, config.ServerPort)
|
addr := fmt.Sprintf("%s:%d", config.ServerHost, config.ServerPort)
|
||||||
fmt.Printf("serving at http://%s\n", addr)
|
|
||||||
return http.ListenAndServe(addr, nil)
|
return http.ListenAndServe(addr, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +79,13 @@ func setupWatcher(config *config.Config) (*fsnotify.Watcher, *EventBroker, error
|
||||||
|
|
||||||
broker := newEventBroker()
|
broker := newEventBroker()
|
||||||
|
|
||||||
|
// the rebuild is handled after some delay to prevent bursts of events to trigger repeated rebuilds
|
||||||
|
// which can cause the browser to refresh while another unfinished build is in progress (refreshing to
|
||||||
|
// a missing file). The initial build is done immediately.
|
||||||
|
rebuildAfter := time.AfterFunc(0, func() {
|
||||||
|
rebuildSite(config, watcher, broker)
|
||||||
|
})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -97,22 +100,11 @@ func setupWatcher(config *config.Config) (*fsnotify.Watcher, *EventBroker, error
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\nFile %s changed, rebuilding site.\n", event.Name)
|
// Schedule a rebuild to trigger after a delay. If there was another one pending
|
||||||
|
// it will be canceled.
|
||||||
// since new nested directories could be triggering this change, and we need to watch those too
|
fmt.Printf("\nfile %s changed\n", event.Name)
|
||||||
// and since re-watching files is a noop, I just re-add the entire src everytime there's a change
|
rebuildAfter.Stop()
|
||||||
if err := addAll(watcher, config); err != nil {
|
rebuildAfter.Reset(100 * time.Millisecond)
|
||||||
fmt.Println("couldn't add watchers:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rebuildSite(config); err != nil {
|
|
||||||
fmt.Println("build error:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
broker.publish("rebuild")
|
|
||||||
|
|
||||||
fmt.Println("done\nserving at", config.SiteUrl)
|
|
||||||
|
|
||||||
case err, ok := <-watcher.Errors:
|
case err, ok := <-watcher.Errors:
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -144,17 +136,29 @@ func addAll(watcher *fsnotify.Watcher, config *config.Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func rebuildSite(config *config.Config) error {
|
func rebuildSite(config *config.Config, watcher *fsnotify.Watcher, broker *EventBroker) {
|
||||||
|
fmt.Printf("building site\n")
|
||||||
|
|
||||||
|
// since new nested directories could be triggering this change, and we need to watch those too
|
||||||
|
// and since re-watching files is a noop, I just re-add the entire src everytime there's a change
|
||||||
|
if err := addAll(watcher, config); err != nil {
|
||||||
|
fmt.Println("couldn't add watchers:", err)
|
||||||
|
}
|
||||||
|
|
||||||
site, err := site.Load(*config)
|
site, err := site.Load(*config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
fmt.Println("load error:", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := site.Build(); err != nil {
|
if err := site.Build(); err != nil {
|
||||||
return err
|
fmt.Println("build error:", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
broker.publish("rebuild")
|
||||||
|
|
||||||
|
fmt.Println("done\nserving at", config.SiteUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tweaks the http file system to construct a server that hides the .html suffix from requests.
|
// Tweaks the http file system to construct a server that hides the .html suffix from requests.
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
<a href="/docs/">docs</a>
|
<a href="/docs/">docs</a>
|
||||||
|
|
||||||
<div class="nav-right hidden-mobile">
|
<div class="nav-right hidden-mobile">
|
||||||
<a href="/feed.xml">feed</a>
|
<a href="https://github.com/facundoolano/jorge">github</a>
|
||||||
<a href="/blog/tags">tags</a>
|
<a href="https://github.com/facundoolano/jorge/releases/latest">download</a>
|
||||||
<a href="https://github.com/facundoolano/jorge">source</a>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -48,8 +48,7 @@
|
||||||
<body>
|
<body>
|
||||||
{{ content }}
|
{{ content }}
|
||||||
<p class="center-block footer">
|
<p class="center-block footer">
|
||||||
{% if page.date or page.tags %}
|
{% if page.date %}<span class="date">{{ page.date | date: "%d/%m/%Y" }}</span>
|
||||||
<span class="date">{{ page.date | date: "%d/%m/%Y" }}</span>
|
|
||||||
<span class="tags">
|
<span class="tags">
|
||||||
{% for tag in page.tags %}
|
{% for tag in page.tags %}
|
||||||
<a href="/blog/tags#{{tag}}">#{{tag}}</a>
|
<a href="/blog/tags#{{tag}}">#{{tag}}</a>
|
||||||
|
@ -57,7 +56,7 @@
|
||||||
</span>
|
</span>
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
powered by <a href="https://jorge.olano.dev">jorge</a> | <a href="https://github.com/facundoolano/jorge/tree/main/{{page.src_path}}">source</a>
|
powered by <a href="https://jorge.olano.dev">jorge</a> | <a href="https://github.com/facundoolano/jorge/tree/main/docs/{{page.src_path}}">source</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -30,7 +30,7 @@ body {
|
||||||
nav {
|
nav {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
border-bottom: 1px solid;
|
border-bottom: 1px solid lightgray;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.nav-right {
|
.nav-right {
|
||||||
|
@ -133,7 +133,24 @@ img.cover-img {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding-left: 1rem
|
padding: 1rem;
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-post .src pre, .layout-post code {
|
||||||
|
background: whitesmoke;
|
||||||
|
font-size: 1rem;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
|
.src pre::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
|
@ -185,7 +202,7 @@ blockquote {
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tr {
|
table tr {
|
||||||
|
@ -243,4 +260,5 @@ code.index-sample {
|
||||||
|
|
||||||
code.index-sample pre {
|
code.index-sample pre {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
padding: 0 0 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
layout: default
|
layout: default
|
||||||
title: Devlog
|
title: Docs
|
||||||
---
|
---
|
||||||
|
|
||||||
Comming soon!
|
Comming soon!
|
||||||
|
|
|
@ -28,28 +28,38 @@ $ jorge serve
|
||||||
<span class="silver">serving at http://localhost:4001</span>
|
<span class="silver">serving at http://localhost:4001</span>
|
||||||
</pre>
|
</pre>
|
||||||
</code>
|
</code>
|
||||||
<p><a href="https://github.com/facundoolano/jorge">Open source</a>, Inspired by <a href="https://jekyllrb.com/">Jekyll</a>, with <a href="https://orgmode.org">org-mode</a> and <a href="https://daringfireball.net/projects/markdown/">Markdown</a> support.</p>
|
<p>Inspired by <a href="https://jekyllrb.com/">Jekyll</a>, written in Go, with <a href="https://orgmode.org">org-mode</a> and markdown support.</p>
|
||||||
</center>
|
</center>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<hr/>
|
<nav>
|
||||||
|
<div class="nav-right hidden-mobile">
|
||||||
|
<a href="https://github.com/facundoolano/jorge">github</a>
|
||||||
|
<a href="https://github.com/facundoolano/jorge/releases/latest">download</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<h2><a href="/tutorial" class="title" id="tutorial">Tutorial</a></h2>
|
<h2><a href="/tutorial" class="title" id="tutorial">Tutorial</a></h2>
|
||||||
<ol>
|
<ol start='0'>
|
||||||
{% for page in site.pages|where:"dir", "/tutorial"|sort:"index" %}
|
{% for page in site.pages|where:"dir", "/tutorial"|sort:"index" %}
|
||||||
<li>
|
<li>
|
||||||
<a class="title" href="{{ page.url }}">{{ page.title }}</a>
|
<a class="title" href="{{ page.url }}">{{ page.title }}{%if page.subtitle%}: {{page.subtitle|downcase}}{%endif%}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2><a href="/blog" class="title" id="devlog">Devlog</a></h2>
|
<h2><a href="/blog" class="title" id="devlog">Devlog</a></h2>
|
||||||
|
|
||||||
|
Coming soon!
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
{% for post in site.posts limit:3 %}
|
{% for post in site.posts limit:3 %}
|
||||||
{% include post_preview.html %}
|
{% include post_preview.html %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<p>See the full <a href="/blog">blog archive</a> or subscribe to the <a href="/feed.xml">feed</a>.</p>
|
<p>See the full <a href="/blog">blog archive</a> or subscribe to the <a href="/feed.xml">feed</a>.</p>
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
<h2><a href="/docs" class="title" id="docs">Docs</a></h2>
|
<h2><a href="/docs" class="title" id="docs">Docs</a></h2>
|
||||||
Coming soon!
|
Coming soon!
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
title: Building and deploying
|
|
||||||
layout: post
|
|
||||||
lang: en
|
|
||||||
tags: [tutorial]
|
|
||||||
index: 4
|
|
||||||
---
|
|
||||||
#+OPTIONS: toc:nil num:nil
|
|
||||||
#+LANGUAGE: en
|
|
||||||
|
|
||||||
Coming soon!
|
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
title: Creating a new site
|
|
||||||
layout: post
|
|
||||||
lang: en
|
|
||||||
tags: [tutorial]
|
|
||||||
index: 2
|
|
||||||
---
|
|
||||||
#+OPTIONS: toc:nil num:nil
|
|
||||||
#+LANGUAGE: en
|
|
||||||
|
|
||||||
Coming soon!
|
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
title: Getting started
|
|
||||||
layout: post
|
|
||||||
lang: en
|
|
||||||
tags: [tutorial]
|
|
||||||
index: 1
|
|
||||||
---
|
|
||||||
#+OPTIONS: toc:nil num:nil
|
|
||||||
#+LANGUAGE: en
|
|
||||||
|
|
||||||
Coming soon!
|
|
|
@ -3,10 +3,10 @@ layout: default
|
||||||
title: Tutorial
|
title: Tutorial
|
||||||
---
|
---
|
||||||
|
|
||||||
<ol>
|
<ol start='0'>
|
||||||
{% for page in site.pages|where:"dir", "/tutorial"|sort:"index" %}
|
{% for page in site.pages|where:"dir", "/tutorial"|sort:"index" %}
|
||||||
<li>
|
<li>
|
||||||
<a class="title" href="{{ page.url }}">{{ page.title }}</a>
|
<a class="title" href="{{ page.url }}">{{ page.title }}{%if page.subtitle%}: {{page.subtitle|downcase}}{%endif%}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
30
docs/src/tutorial/installation.org
Executable file
30
docs/src/tutorial/installation.org
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
title: Installation
|
||||||
|
layout: post
|
||||||
|
lang: en
|
||||||
|
tags: [tutorial]
|
||||||
|
index: 0
|
||||||
|
---
|
||||||
|
#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: en
|
||||||
|
|
||||||
|
To start using jorge, download the latest binary for your platform from the [[https://github.com/facundoolano/jorge/releases/latest][releases page]], and put it somewhere on your path. For example:
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
$ wget https://github.com/facundoolano/jorge/releases/latest/download/jorge-linux-amd64 -O jorge
|
||||||
|
$ chmod +x jorge
|
||||||
|
$ mv jorge /usr/local/bin
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Alternatively, if you have Go available in your system, you can install with:
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
$ go install github.com/facundoolano/jorge@latest
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
|
||||||
|
TODO: switch to cli and show usage output
|
||||||
|
|
||||||
|
#+HTML: <br>
|
||||||
|
#+ATTR_HTML: :align right
|
||||||
|
Next: [[file:introduction][Introduction]].
|
24
docs/src/tutorial/introduction.org
Normal file
24
docs/src/tutorial/introduction.org
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
title: Introduction
|
||||||
|
layout: post
|
||||||
|
lang: en
|
||||||
|
tags: [tutorial]
|
||||||
|
index: 1
|
||||||
|
---
|
||||||
|
#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: en
|
||||||
|
|
||||||
|
jorge is a personal static site generator, inspired by Jekyll. In this context, I[fn:1] use personal as "small and opinionated". In practical terms, this means that jorge implements the subset of features from Jekyll that I find most useful and that I defaulted to Jekyll's design decisions except in the specific cases where I had strong differing preferences.
|
||||||
|
|
||||||
|
jorge started as a Golang learning project, aimed at streamlining my blogging workflow. It adds native org-mode syntax support and, at least for my website, it turned out to be much faster than Jekyll. jorge should /almost/ work as a drop-in replacement for Jekyll: the directory structure is a bit different but most of the content should render with few changes. Additionally, I tried to add support to generate a fully functional (albeit minimalist and kind of ugly) website from scratch.
|
||||||
|
|
||||||
|
This tutorial covers the basics of using jorge, from starting a site to deploying it. I tried to keep it accessible, but you may need to consult with Jekyll or Hugo documentation if you never used a static site generator, want to get the finer-grained details of template syntax, etc.
|
||||||
|
|
||||||
|
|
||||||
|
#+HTML: <br>
|
||||||
|
#+ATTR_HTML: :align right
|
||||||
|
Next: [[file:jorge-init][start a website]].
|
||||||
|
|
||||||
|
*** Notes
|
||||||
|
|
||||||
|
[fn:1] Facundo Olano 👋
|
64
docs/src/tutorial/jorge-build.org
Executable file
64
docs/src/tutorial/jorge-build.org
Executable file
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
title: jorge build
|
||||||
|
subtitle: Prepare for production and deploy
|
||||||
|
layout: post
|
||||||
|
lang: en
|
||||||
|
tags: [tutorial]
|
||||||
|
index: 5
|
||||||
|
---
|
||||||
|
#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: en
|
||||||
|
|
||||||
|
So far you've seen how to [[file:jorge-init][start a project]], [[file:jorge-serve][serve it locally]] and [[file:jorge-post][add some content]] to it. The last part of job is to prepare your site for the public, and ~jorge build~ will help with that:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
$ jorge build
|
||||||
|
wrote target/2024-02-23-another-kind-of-post.html
|
||||||
|
wrote target/blog/my-own-blog-post.html
|
||||||
|
wrote target/blog/goodbye-markdown.html
|
||||||
|
wrote target/assets/css/main.css
|
||||||
|
wrote target/blog/hello-org.html
|
||||||
|
wrote target/blog/tags.html
|
||||||
|
wrote target/index.html
|
||||||
|
wrote target/feed.xml
|
||||||
|
wrote target/blog/index.html
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Just like ~jorge serve~ did before, ~jorge build~ walks your ~src/~ files and renders them into ~target/~, but with a few differences:
|
||||||
|
|
||||||
|
- Static files are copied over to ~target/~ instead of just linked.
|
||||||
|
- The ~url~ from your ~config.yml~ is used as the root when rendering absolute urls (instead of the ~http://localhost:4001~ used when serving locally).
|
||||||
|
- The HTML, XML, CSS and JavaScript files are minified.
|
||||||
|
|
||||||
|
Once you have your static site rendered, it's just a matter to putting it out in the internet. There are many ways to publish a static site, and covering them is out of the scope of this tutorial[fn:1]. I suggest going through the [[https://jekyllrb.com/docs/deployment/][Jekyll]] and [[https://gohugo.io/hosting-and-deployment/][Hugo]] docs for inspiration.
|
||||||
|
|
||||||
|
But for the sake of completenss, this is how this site is deployed: I have a VPS box running Debian Linux and with the [[https://www.nginx.com/][nginx]] server installed on it. I added this configuration to ~/etc/nginx/sites-enabled/jorge~:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
server {
|
||||||
|
charset utf-8;
|
||||||
|
root /var/www/jorge;
|
||||||
|
server_name jorge.olano.dev;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# First attempt to serve request as file, then as directory. Otherwise respond 404.
|
||||||
|
try_files $uri $uri.html $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
I instructed my DNS provider to point ~jorge.olano.dev~ to the box and I ran [[https://certbot.eff.org/instructions?ws=nginx&os=debianbuster][certbot]] to generate certificates for that subdomain.
|
||||||
|
|
||||||
|
I then created the ~/var/www/jorge~ directory in the server, and deployed from my laptop using ~rsync~:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
$ jorge build
|
||||||
|
$ rsync -vPrz --delete target/ root@olano.dev:/var/www/jorge
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
And that's it!
|
||||||
|
|
||||||
|
*** Notes
|
||||||
|
|
||||||
|
[fn:1] [[https://github.com/facundoolano/jorge/pulls][PRs]] welcome!
|
67
docs/src/tutorial/jorge-init.org
Executable file
67
docs/src/tutorial/jorge-init.org
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
---
|
||||||
|
title: jorge init
|
||||||
|
subtitle: Start a website
|
||||||
|
layout: post
|
||||||
|
lang: en
|
||||||
|
tags: [tutorial]
|
||||||
|
index: 2
|
||||||
|
---
|
||||||
|
#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: en
|
||||||
|
|
||||||
|
Once jorge is [[file:installation][installed]], you can start a new site with the ~init~ command, specifying a project directory (~myblog~ in the example):
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
$ jorge init myblog
|
||||||
|
site name: My Blog
|
||||||
|
site url: https://myblog.example.com
|
||||||
|
author: Jorge Blog
|
||||||
|
|
||||||
|
added myblog/config.yml
|
||||||
|
added myblog/README.md
|
||||||
|
added myblog/.gitignore
|
||||||
|
added myblog/includes/nav.html
|
||||||
|
added myblog/includes/post_preview.html
|
||||||
|
added myblog/layouts/base.html
|
||||||
|
added myblog/layouts/default.html
|
||||||
|
added myblog/layouts/post.html
|
||||||
|
added myblog/src/assets/css/main.css
|
||||||
|
added myblog/src/blog/goodbye-markdown.md
|
||||||
|
added myblog/src/blog/hello-org.org
|
||||||
|
added myblog/src/blog/index.html
|
||||||
|
added myblog/src/blog/tags.html
|
||||||
|
added myblog/src/feed.xml
|
||||||
|
added myblog/src/index.html
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
The command will first prompt for some information necessary to fill some content in the default project files:
|
||||||
|
|
||||||
|
- The site name will be used for the HTML title of the pages.
|
||||||
|
- The URL will be used when rendering absolute URLs for links, for instance in the default atom feed.
|
||||||
|
- The author is used in the site's HTML metadata and the atom feed.
|
||||||
|
|
||||||
|
You can change those values anytime by editing the ~config.yml~ file, so it doesn't really matter if you're not sure what to put in there.
|
||||||
|
|
||||||
|
Let's look at the files created by init:
|
||||||
|
| ~config.yml~ | a YAML file with configuration keys. Some affect how jorge works, and all will be available as variables for rendering templates. |
|
||||||
|
| ~README.md~ | the standard markdown file for a repository README. |
|
||||||
|
| ~.gitignore~ | the git ignore patterns, initialized to ignore jorge generated files. Both this and the readme are added under the assumption that you'll check your project code into a git repository. | |
|
||||||
|
| ~src/~ | the root of your website. Anything you put in here will, in some way or another, be included in your public site. The source directory is the most important part of a jorge project; in fact, it's the only thing required to build your site. | |
|
||||||
|
| ~src/index.html~ | an HTML template for your website root. | |
|
||||||
|
| ~src/feed.xml~ | a template for an atom feed of the website's most recent posts. | |
|
||||||
|
| ~src/assets/css/main.css~ | the default CSS styles. | |
|
||||||
|
| ~src/blog/hello-org.org~ | an example blog post using org-mode syntax. | |
|
||||||
|
| ~src/blog/goodbye-markdown.md~ | an example blog post using markdown syntax. | |
|
||||||
|
| ~src/blog/index.html~ | an HTML template for the full blog archive. | |
|
||||||
|
| ~src/blog/tags.html~ | an HTML template for the blog archive organized by tags. | |
|
||||||
|
| ~layouts/*.html~ | HTML templates that can be used by other templates to "fill the blanks" of a default HTML structure. For example, ~base.html~ defines the default layout for the entire website, while ~posts.html~ extends it to determine the layout specific to blog posts. | |
|
||||||
|
| ~includes/*.html~ | HTML template fragments that can be injected into other templates, to reduce duplication. | |
|
||||||
|
|
||||||
|
Note how jorge assumes that index files are to be served as URL directories (~src/blog/index.html~ will be served at ~/blog/)~ and that the HTML extensions will be omitted (~src/blog/tags.html~ will be served at ~/blog/tags~).
|
||||||
|
|
||||||
|
If you prefer to build your site from scratch, you can skip running ~jorge init~ altogether; the rest of the commands only expect a ~src/~ directory to work with.
|
||||||
|
|
||||||
|
|
||||||
|
#+HTML: <br>
|
||||||
|
#+ATTR_HTML: :align right
|
||||||
|
Next: [[file:jorge-serve][browse the site locally]].
|
92
docs/src/tutorial/jorge-post.org
Executable file
92
docs/src/tutorial/jorge-post.org
Executable file
|
@ -0,0 +1,92 @@
|
||||||
|
---
|
||||||
|
title: jorge post
|
||||||
|
subtitle: Adding a blog post
|
||||||
|
layout: post
|
||||||
|
lang: en
|
||||||
|
tags: [tutorial]
|
||||||
|
index: 4
|
||||||
|
---
|
||||||
|
#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: en
|
||||||
|
|
||||||
|
** Posts and pages
|
||||||
|
|
||||||
|
When jorge walks your ~src/~ directory, building a website to ~target/~ for serving locally or publishing in production, it distinguishes between 3 types of files:
|
||||||
|
|
||||||
|
1. Static files: any file that's not a template. These are files that don't contain a front matter header (that don't start with a ~---~ line).
|
||||||
|
2. Template files, which can be further divided into:
|
||||||
|
a. Posts: templates that include a ~date~ field in their front matter, and thus can be sorted chronologically.
|
||||||
|
b. Pages: any other template file.
|
||||||
|
|
||||||
|
As you can see, the difference between posts and pages is very subtle. The reason why jorge distinguishes posts is that you may want to leverage the chronological nature of posts for things like building a blog archive page or publishing the most recent posts to an RSS feeds[fn:1]. In practical terms, this difference only affects how posts and pages are exposed to templates as liquid variables:
|
||||||
|
|
||||||
|
1. Pages are listed without a particular order in the ~site.pages~ variable[fn:2].
|
||||||
|
2. Posts are listed in reverse chronological order (most recent first) in the ~site.posts~ variable.
|
||||||
|
3. Additionally, posts that declare ~tags~ in their front matter, are included in the ~site.tags~ map.
|
||||||
|
4. Each post contains an ~excerpt~ property with a summary of its contents. If ~excerpt~ is included as a key in the front matter, its value will be used; otherwise (assuming it renders to HTML), the post's first paragraph will be used. Excerpts are useful for post previews in the blog archive, in social media links, and in RSS feeds.
|
||||||
|
|
||||||
|
** jorge post
|
||||||
|
Each website has its own layout so it's hard to predict what you may need to do with a page template. But blogs are different: once the site structure is in place, you more or less repeat the same boilerplate every time you need to add a new blog post. For this reason, jorge provides the ~jorge post~ command to initialize blog post template files. Let's try it out.
|
||||||
|
|
||||||
|
From the root directory of your project run ~jorge post "My own blog post"~:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
$ jorge post "My own blog post"
|
||||||
|
added src/blog/my-own-blog-post.org
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
If you open the new file with your editor, you should see something like this:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
---
|
||||||
|
title: My own blog post
|
||||||
|
date: 2024-02-23 11:45:30
|
||||||
|
layout: post
|
||||||
|
lang: en
|
||||||
|
tags: []
|
||||||
|
---
|
||||||
|
#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: en
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Let's look at what the command did for us:
|
||||||
|
{% raw %}
|
||||||
|
| ~src/blog/my-own-blog-post.org~ | The added file includes a URL-friendly version of the post tile (a "slug"), such that the post will be served at ~/blog/my-own-blog-post~ |
|
||||||
|
| ~title: My own blog post~ | The title we passed to jorge. This will be available to templates as ~{{page.title}}~ and will be used by the post layout to render the header of the page. |
|
||||||
|
| ~date: 2024-02-23 11:45:30~ | The date this post was created, will affect the position it shows up in in ~{{site.posts}}~ |
|
||||||
|
| ~layout: post~ | The rendered HTML of this template will be embedded as the ~{{contents}}~ of the layout defined in ~layouts/post.html~. |
|
||||||
|
| ~lang: en~ | The language code for the post. This is used by some of the default templates, for instance, to determine how to hyphenate the post content. |
|
||||||
|
| ~tags: []~ | The post tags, initially empty. the words added to this list will affect which keys of the ~{{site.tags}}~ map this post will be included in.
|
||||||
|
| ~#+OPTIONS: toc:nil num:nil~, ~#+LANGUAGE: en~ | Some default org mode options, to skip the table of contents and define the post language. |
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
With ~jorge serve~ running, you can start filling in some content on this new post and see it show up in the browser at http://localhost:4001/blog/my-own-blog-post.
|
||||||
|
|
||||||
|
** Customizing the post format
|
||||||
|
As you may have noticed, the ~jorge post~ command makes a lot of assumptions: where to put it, how to name it, and what format to use. You can control some of these decisions by setting the ~post_format~ configuration key. The default is:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
post_format: "blog/:title.org"
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Let's say that you want to put posts in the root folder, include the date in them, and use the markdown format by default. Add this key to your ~config.yml~:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
post_format: ":year-:month-:day-:title.md"
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
The, if you add a new post:
|
||||||
|
#+begin_src
|
||||||
|
$ jorge post "Another kind of post"
|
||||||
|
added src/2024-02-23-another-kind-of-post.md
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+HTML: <br>
|
||||||
|
#+ATTR_HTML: :align right
|
||||||
|
Next: [[file:jorge-build][prepare for production and deploy]].
|
||||||
|
|
||||||
|
*** Notes
|
||||||
|
|
||||||
|
[fn:1] Both a blog archive and the RSS feed (technically [[https://en.wikipedia.org/wiki/Atom_(web_standard)][Atom]]) are already implemented in the default site generated by ~jorge init~.
|
||||||
|
|
||||||
|
[fn:2] ~index.html~ pages are excluded from this list.
|
65
docs/src/tutorial/jorge-serve.org
Executable file
65
docs/src/tutorial/jorge-serve.org
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
title: jorge serve
|
||||||
|
subtitle: Browse the site locally
|
||||||
|
layout: post
|
||||||
|
lang: en
|
||||||
|
tags: [tutorial]
|
||||||
|
index: 3
|
||||||
|
---
|
||||||
|
#+OPTIONS: toc:nil num:nil
|
||||||
|
#+LANGUAGE: en
|
||||||
|
|
||||||
|
Now that you have some [[file:jorge-init][default files]] in place, let's see how the website looks. Run ~jorge serve~ on the website directory:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
$ cd myblog
|
||||||
|
$ jorge serve
|
||||||
|
building site
|
||||||
|
wrote target/feed.xml
|
||||||
|
wrote target/blog/goodbye-markdown.html
|
||||||
|
wrote target/blog/hello-org.html
|
||||||
|
wrote target/index.html
|
||||||
|
wrote target/blog/index.html
|
||||||
|
wrote target/blog/tags.html
|
||||||
|
done
|
||||||
|
serving at http://localhost:4001
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
As you can see, jorge reads the files located in your ~src/~ directory and replicates them (with a few changes) at the ~target/~ one.
|
||||||
|
If you open your browser at http://localhost:4001 you'll see the website we just created.
|
||||||
|
|
||||||
|
|
||||||
|
If you open ~src/index.html~ in your editor, you should see something roughly matching what you see on the browser:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
{% raw %}
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
<h2><a href="#about" class="title" id="about">About</a></h2>
|
||||||
|
<p>Welcome to {{ site.config.name }} by {{ site.config.author }}.</p>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
...
|
||||||
|
{% endraw %}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
This file is a [[https://shopify.github.io/][liquid template]] for an HTML file. jorge treats any file inside ~src/~ that begins with this ~---~ header as a template, regardless of its format. This means that:
|
||||||
|
|
||||||
|
1. The header (called front matter by site generators like [[https://jekyllrb.com/docs/front-matter/][Jekyll]] and [[https://gohugo.io/content-management/front-matter/][Hugo]]) will be parsed as YAML and interpreted as post metadata.
|
||||||
|
2. The rest of the file contents will be rendered according to the liquid template syntax.
|
||||||
|
3. If it's an org-mode or markdown file, its contents will be converted to their corresponding HTML in the target.
|
||||||
|
|
||||||
|
In the example above, the ~layout: default~ instructs jorge to embed the index.html rendered output inside the layout defined at ~layouts/default.html~. And the liquid variables expressed by ~{{ site.config.name }}~ and ~{{ site.config.author }}~ will be replaced by the values found at ~config.yml~.
|
||||||
|
|
||||||
|
If you change the markup in ~src/index.html~, you should see your browser tab refresh automatically to reflect those changes. Try, for instance, changing the title in the first header:
|
||||||
|
|
||||||
|
#+begin_src
|
||||||
|
<h2><a href="#home" class="title" id="home">Home sweet home</a></h2>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
The new title should show up on the browser page.
|
||||||
|
|
||||||
|
#+HTML: <br>
|
||||||
|
#+ATTR_HTML: :align right
|
||||||
|
Next: [[file:jorge-post][add a blog post]].
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
title: Posts and pages
|
|
||||||
layout: post
|
|
||||||
lang: en
|
|
||||||
tags: [tutorial]
|
|
||||||
index: 3
|
|
||||||
---
|
|
||||||
#+OPTIONS: toc:nil num:nil
|
|
||||||
#+LANGUAGE: en
|
|
||||||
|
|
||||||
Coming soon!
|
|
Loading…
Reference in a new issue