feat: articles format option with markdown new default
This commit is contained in:
1 parent
68401d66e0
commit
7e9bd9ba30
61 files changed
+2269
-243
No files matched your search
@@ -0,0 +1,97 @@
|
||||
# Reading articles
|
||||
|
||||
Every route that returns an article accepts the optional `format` query parameter and returns the article body in that format. Articles are stored as HTML (Tiptap output). Storage and the request bodies of `POST /v1/article/` and `PATCH /v1/article/{id}` stay HTML.
|
||||
|
||||
## Routes that honour `format`
|
||||
|
||||
- `GET /v1/article/`
|
||||
- `GET /v1/article/{id}`
|
||||
- `POST /v1/article/`
|
||||
- `PATCH /v1/article/{id}`
|
||||
- `PATCH /v1/article/{id}/publish`
|
||||
- `PATCH /v1/article/{id}/unpublish`
|
||||
|
||||
Visibility is unchanged: anonymous and unverified callers only see online articles, and an offline article answers `404 Article not found`.
|
||||
|
||||
## The `format` parameter
|
||||
|
||||
| Value | `body` | `body_format` |
|
||||
| --- | --- | --- |
|
||||
| empty or absent | markdown string | `markdown` |
|
||||
| `markdown` | markdown string | `markdown` |
|
||||
| `html` | the stored HTML string | `html` |
|
||||
| `json` | JSON array of blocks | `json` |
|
||||
|
||||
Values are case-insensitive. When `format` is repeated, the first value is used. Any other value answers `400` with the plain-text message `The format must be one of: markdown, html, json.` before any article is read or written. An invalid id is reported first, ahead of the format.
|
||||
|
||||
Breaking change: the default used to be HTML. Clients that need HTML must send `?format=html`. The web interface does so.
|
||||
|
||||
## Article object
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Backyard Renovation Update",
|
||||
"body": "## Plan\n\nReplace the **old** deck.",
|
||||
"body_format": "markdown",
|
||||
"is_online": true,
|
||||
"created_at": "2026-03-12T09:14:00Z",
|
||||
"updated_at": "2026-09-10T07:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Markdown output
|
||||
|
||||
Headings, bold, italic, links, bullet and ordered lists, blockquotes, fenced code blocks, rules, images and inline code are converted. Anything else is reduced to its text. `script` and `style` content is removed, and the source HTML is never passed through; the only HTML in the output is the alignment wrapper described below. List indentation attributes are dropped. Links with a scheme other than http, https, mailto or tel are reduced to their text. Characters that markdown would read as formatting (`*`, `_`, `` ` ``, `[`, `]`, `<`, `\`) and line starts that would read as a heading, list, quote or rule (`#`, `-`, `+`, `>`, `1.`) are backslash-escaped, so plain text stays plain text. A line break becomes a backslash followed by a newline.
|
||||
|
||||
### Text alignment
|
||||
|
||||
Markdown has no syntax for alignment, so centered, right-aligned and justified paragraphs and headings are wrapped in a `div` with an inline style. The blank lines around the content make CommonMark parse the inside as markdown:
|
||||
|
||||
```markdown
|
||||
<div style="text-align: center">
|
||||
|
||||
A **centered** paragraph
|
||||
|
||||
</div>
|
||||
```
|
||||
|
||||
Left alignment is the default and produces no wrapper. Your markdown renderer must allow raw HTML for the alignment to show (`html: true` in markdown-it); otherwise the wrapper tags appear as text, and a client that does not want them can request `?format=json` instead, where the alignment is a field.
|
||||
|
||||
## JSON block schema
|
||||
|
||||
`body` is a native JSON array. Every block has a `type`.
|
||||
|
||||
| Block | Fields |
|
||||
| --- | --- |
|
||||
| `heading` | `level` (1 to 6), `align` (only when `center`, `right` or `justify`), `children` (text nodes) |
|
||||
| `paragraph` | `align` (only when `center`, `right` or `justify`), `children` (text nodes, empty array for an empty paragraph) |
|
||||
| `list` | `ordered` (boolean), `items` (array of items, each an array of blocks) |
|
||||
| `blockquote` | `children` (blocks) |
|
||||
| `code` | `language` (empty string when unknown), `text` |
|
||||
| `hr` | none |
|
||||
| `image` | `src`, `alt` (empty string when missing) |
|
||||
|
||||
Text node: `{"type":"text","text":"...","marks":[...]}`. `marks` is always an array, listed outermost first. A mark is `{"type":"bold"}`, `{"type":"italic"}` or `{"type":"link","href":"..."}`. A line break becomes a `"\n"` inside the text. Adjacent text with identical marks is merged. Inline code, underline, strike-through and other formatting degrade to plain text. An image inside a paragraph becomes its own block and splits the paragraph. An empty body is `[]`.
|
||||
|
||||
Example for `<h2>Plan</h2><p>Replace the <strong>old</strong> deck.</p>`:
|
||||
|
||||
```json
|
||||
[
|
||||
{"type":"heading","level":2,"children":[{"type":"text","text":"Plan","marks":[]}]},
|
||||
{"type":"paragraph","children":[
|
||||
{"type":"text","text":"Replace the ","marks":[]},
|
||||
{"type":"text","text":"old","marks":[{"type":"bold"}]},
|
||||
{"type":"text","text":" deck.","marks":[]}
|
||||
]}
|
||||
]
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Body |
|
||||
| --- | --- |
|
||||
| `400` | `The server expects the ID to be in the format of an unsigned 32-bit integer (uint32).` for an invalid id |
|
||||
| `400` | `The format must be one of: markdown, html, json.` for an unsupported format |
|
||||
| `404` | `Article not found` |
|
||||
| `500` | `Internal server error` when the body cannot be converted |
|
||||
@@ -29,3 +29,4 @@ Each body lives in `docs/releases/<tag>.md`. Publish with `tea releases create -
|
||||
| `v2.5.0-beta` | v2.5.0-beta - Media library & image picker |
|
||||
| `v2.6.0-beta` | v2.6.0-beta - Signed updates and browser restart |
|
||||
| `v2.6.2-beta` | v2.6.2-beta - English and French interface |
|
||||
| `v2.7.0-beta` | v2.7.0-beta - Markdown and block article bodies |
|
||||
@@ -0,0 +1,35 @@
|
||||
This release lets your website read article bodies as markdown, HTML or structured blocks, and fixes text alignment in the editor.
|
||||
|
||||
## New
|
||||
|
||||
**Article body formats**
|
||||
|
||||
- Read an article body as markdown, HTML or a typed block array with the `format` query parameter, so a headless frontend can render it with markdown-it, comark or its own components.
|
||||
- Keep text alignment in every format: markdown wraps centered, right-aligned and justified blocks in a `div` with an inline style, and the block array carries an `align` field.
|
||||
- Keep plain text plain: text that merely looks like markdown, such as `# not a heading` or `- not a list`, is escaped in the markdown output.
|
||||
- See the Integration page snippets and [the article API reference](../api/articles.md) for the full contract.
|
||||
|
||||
## Improvements and fixes
|
||||
|
||||
- Fix the text alignment buttons in the article editor, which were disabled and showed a blocked cursor. Paragraphs and headings can be aligned left, center, right or justified again, and unaligned text stays free of inline styles.
|
||||
- The web interface requests HTML explicitly when it loads, saves, publishes, unpublishes or creates an article, so the editor and the image counts behave exactly as before.
|
||||
|
||||
## API
|
||||
|
||||
- Changed: `GET /v1/article/`, `GET /v1/article/{id}`, `POST /v1/article/`, `PATCH /v1/article/{id}`, `PATCH /v1/article/{id}/publish` and `PATCH /v1/article/{id}/unpublish` now return `body` as markdown by default. Pass `?format=html` to keep the previous HTML body.
|
||||
- Every article object now includes `body_format` (`markdown`, `html` or `json`), which names the format of `body`.
|
||||
- `?format=json` on the same routes returns `body` as a JSON array of typed blocks instead of a string.
|
||||
- An unsupported `format` answers `400` before anything is read or written.
|
||||
- Storage and the request bodies of `POST` and `PATCH` stay HTML.
|
||||
|
||||
## Under the hood
|
||||
|
||||
- Add the article body renderer port in the domain, with a markdown and JSON block adapter, and tests for every supported element.
|
||||
- Update `golang.org/x/crypto` to v0.57.0 and add `golang.org/x/net` as a direct dependency for HTML parsing.
|
||||
- Add `@tiptap/extension-text-align` to the web interface.
|
||||
|
||||
## Upgrading
|
||||
|
||||
- Replace the binary. No database migration or new environment variable is required.
|
||||
- Clients that read an article `body` as HTML must add `?format=html` to their article requests, because the default is now markdown.
|
||||
- A markdown renderer needs raw HTML enabled for text alignment to show, for example `html: true` in markdown-it.
|
||||
Reference in new issue
Block a user