SeriesWriting7

Writing and publishing

Writing — part 1 of 7

Day-to-day usage of an installed site: writing, deploying, cron, backup and what to do when something fails. For the zero-to-deployed path see Getting started.

./blog.sh with no arguments opens a numbered menu; every command also works directly (./blog.sh add, edit, publish, ...). The flow is built around drafts:

  1. add opens $EDITOR with a frontmatter template (title, tags, type) -- the in-editor hint links to the /markdown/ syntax reference on your own site. Saving always creates a draft, and the draft appears at a hidden /draft/<token>/<slug>/ address with noindex -- invisible in every listing, shareable by URL (that's the point: open the preview on a phone or send it to someone before publishing).
Read more

Writing a post in another language

Writing — part 2 of 7

Only on a site that publishes more than one -- site.locales in Getting started is what says so.

./blog.sh translate my-post --lang de

Or from the wizard, which is where the rest of the work happens: pick the post, and the crossroads that offers its text and its properties offers the languages too. With one other language it names it ([l] language: Deutsch); with several it opens a picker that says what is already written, what is only started, and what has nothing yet -- the same three states check --languages prints. The properties screen carries a languages row for the same reason. (As data the two say them in different words: props --json has written, started and none, check --languages --json has written, title_only and missing.)

The editor that opens holds a title and a body, and nothing else. That is the whole of what a translation may carry: the date, the tags, the series, the pin and the state belong to the POST and are true in every language at once, so they are changed with props or edit and never here. A translation cannot quietly disagree with the post it belongs to.

Read more

Writing from a phone

Writing — part 3 of 7

The trick is that a bare filename in an image line resolves against the incoming/ staging directory:

  1. Shoot a photo, upload it via any SFTP client into <repo>/incoming/ (setup: Getting started).
  2. SSH into the server, ./blog.sh add, and write ![caption](photo.jpg) -- no path.
  3. If the photo hasn't finished uploading yet, the CLI waits and re-checks on Enter instead of failing.
  4. On save the photo is copied into media.nosync/<year>/<slug>/ and its incoming/ copy is removed -- an empty incoming/ means nothing is pending.
  5. Editing that post later doesn't need the photo again: a bare filename is looked for in the post's own media.nosync/<year>/<slug>/ first and in incoming/ only after that, so a file that's already been saved resolves without any upload (and without being copied again).
Read more

Driving the engine from a program

Writing — part 4 of 7

Every screen a person reads before acting, and every action those screens offer, has a --json form: one object on standard output, nothing else on it, every key always present -- a consumer that has to test for a missing key is a consumer writing its own parser. A refusal is an object too, {"ok": false, "error": "<code>", "message": "<a sentence>"}, with a zero exit: the status answers whether an answer arrived at all, the object says what it was. (The engine falling over is the one thing that still leaves with 1 and prose on stderr.) This is the contract add --json and publish --yes --json have kept since the phone first needed them; the rest of the engine keeps it now.

Three rules follow from "nobody is at the keyboard":

Read more

Pinning a post to the front page

Writing — part 5 of 7

The [c] action in ./blog.sh props <slug> pins a published post -- or unpins it again; pinned: true in the post's header (./blog.sh edit <slug>) does the same thing the long way round. A pinned post is held at the top of the front page, marked with a pin in the corner of its date badge. It appears there once: while it is still on the front page anyway it is lifted to the top rather than shown twice, and once it has aged onto /page/2/ the front page keeps the copy at the top while page 2 lists it in its normal place, unmarked.

Only the front page. Type and tag listings, the RSS feed, the sitemap and the search index stay strictly chronological -- a pin is a statement about the front page, not about the archive. Toggling it is a small deploy, because pagination is anchored and the front page is the only flexible one. Pin a second post and the newest of them wins, with a warning in the build output.

Publishing slots

Writing — part 6 of 7

Set the times posts usually go out and [s] stops asking for a date:

publishing:
  slots:
    - "mon 09:30"
    - "wed 09:30"
    - "fri 09:30"
    # or a single "daily 09:00"

The draft dialog then names the next FREE slot in the [s] choice itself, and the prompt offers it: Enter accepts, typing a date overrides it, the cancel word backs out. Free means no other scheduled post is aimed at that exact time, so three drafts written in one evening queue onto three consecutive slots instead of publishing together, and the confirmation says which post goes out before this one.

The offer also names the earlier slots it had to walk past and the post sitting in each, so a queue that seems to skip a day explains itself. The properties dialog of a scheduled draft prints the whole queue for the same reason, with an arrow on the post you are looking at.

Read more

Attachments and the document type

Writing — part 7 of 7

A line that is nothing but [label](handbook.pdf) -- a bare filename, whitelisted extension -- makes the file part of the post: it is picked up from incoming/ exactly like a photo, stored in media.nosync/<year>/<slug>/, and rendered as a download card showing the label, the extension and the file's size. A link to an address stays a link; the engine can only publish files it was handed.

Whitelisted: .pdf .zip .tgz .epub .txt .md .ics .gpx .csv (.tar.gz is not, because only the last suffix survives the rename -- use .tgz). A post whose text is a short line plus attachments is filed under DOCUMENTS, which appears in the nav once the first such post exists; a longer article that attaches its data stays an article with a file on it.