bloggy 0.1.40__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bloggy/__init__.py +5 -0
- bloggy/build.py +608 -0
- bloggy/config.py +134 -0
- bloggy/core.py +1618 -0
- bloggy/main.py +96 -0
- bloggy/static/scripts.js +584 -0
- bloggy/static/sidenote.css +21 -0
- bloggy-0.1.40.dist-info/METADATA +926 -0
- bloggy-0.1.40.dist-info/RECORD +13 -0
- bloggy-0.1.40.dist-info/WHEEL +5 -0
- bloggy-0.1.40.dist-info/entry_points.txt +2 -0
- bloggy-0.1.40.dist-info/licenses/LICENSE +201 -0
- bloggy-0.1.40.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bloggy
|
|
3
|
+
Version: 0.1.40
|
|
4
|
+
Summary: A lightweight, elegant blogging platform built with FastHTML
|
|
5
|
+
Home-page: https://github.com/yeshwanth/bloggy
|
|
6
|
+
Author: Yeshwanth
|
|
7
|
+
Author-email:
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Project-URL: Homepage, https://github.com/yeshwanth/bloggy
|
|
10
|
+
Project-URL: Repository, https://github.com/yeshwanth/bloggy
|
|
11
|
+
Project-URL: Issues, https://github.com/yeshwanth/bloggy/issues
|
|
12
|
+
Keywords: fasthtml,blog,markdown,htmx,mermaid,sidenotes
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Framework :: FastAPI
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: python-fasthtml>=0.6.9
|
|
28
|
+
Requires-Dist: mistletoe>=1.4.0
|
|
29
|
+
Requires-Dist: python-frontmatter>=1.1.0
|
|
30
|
+
Requires-Dist: uvicorn>=0.30.0
|
|
31
|
+
Requires-Dist: monsterui>=0.0.37
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.5.0; extra == "dev"
|
|
36
|
+
Dynamic: home-page
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
title: Bloggy - A FastHTML Blogging Platform
|
|
42
|
+
---
|
|
43
|
+
# Bloggy
|
|
44
|
+
|
|
45
|
+
A lightweight, elegant blogging platform built with FastHTML that renders Markdown files into beautiful web pages with advanced features.
|
|
46
|
+
|
|
47
|
+
## Architecture Overview
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
---
|
|
51
|
+
width: 80vw
|
|
52
|
+
---
|
|
53
|
+
graph TB
|
|
54
|
+
subgraph "User Interface"
|
|
55
|
+
Browser[Web Browser]
|
|
56
|
+
Theme[Light/Dark Theme Toggle]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
subgraph "FastHTML Application"
|
|
60
|
+
App[FastHTML App<br/>core.py]
|
|
61
|
+
Router[URL Router]
|
|
62
|
+
Layout[Layout Engine]
|
|
63
|
+
|
|
64
|
+
subgraph "Route Handlers"
|
|
65
|
+
Index[Index Route<br/>/]
|
|
66
|
+
PostDetail[Post Detail Route<br/>/posts/path]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
subgraph "Markdown Processing"
|
|
71
|
+
MDParser[Mistletoe Parser]
|
|
72
|
+
Renderer[ContentRenderer]
|
|
73
|
+
|
|
74
|
+
subgraph "Custom Renderers"
|
|
75
|
+
Footnotes[Footnote Renderer<br/>Sidenotes]
|
|
76
|
+
Mermaid[Mermaid Diagram Renderer<br/>Zoom/Pan Controls]
|
|
77
|
+
Links[Link Renderer<br/>HTMX Integration]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
subgraph "File System"
|
|
82
|
+
MDFiles[Markdown Files<br/>.md]
|
|
83
|
+
Tree[Folder Tree Builder<br/>build_post_tree]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
subgraph "Frontend Assets"
|
|
87
|
+
Static[Static Files]
|
|
88
|
+
JS[scripts.js<br/>Mermaid + Interactions]
|
|
89
|
+
CSS[Styles<br/>TailwindCSS + MonsterUI]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Browser -->|HTTP Request| Router
|
|
93
|
+
Theme -->|Toggle Dark Mode| JS
|
|
94
|
+
|
|
95
|
+
Router --> Index
|
|
96
|
+
Router --> PostDetail
|
|
97
|
+
|
|
98
|
+
Index --> Tree
|
|
99
|
+
Tree --> MDFiles
|
|
100
|
+
Index --> Layout
|
|
101
|
+
|
|
102
|
+
PostDetail --> MDFiles
|
|
103
|
+
PostDetail --> MDParser
|
|
104
|
+
|
|
105
|
+
MDParser --> Renderer
|
|
106
|
+
Renderer --> Footnotes
|
|
107
|
+
Renderer --> Mermaid
|
|
108
|
+
Renderer --> Links
|
|
109
|
+
|
|
110
|
+
Footnotes -->|Marginal Notes| Layout
|
|
111
|
+
Mermaid -->|Interactive Diagrams| Layout
|
|
112
|
+
Links -->|HTMX Navigation| Layout
|
|
113
|
+
|
|
114
|
+
Layout --> Browser
|
|
115
|
+
|
|
116
|
+
JS -->|Theme Change| Mermaid
|
|
117
|
+
JS -->|Zoom/Pan/Reset| Mermaid
|
|
118
|
+
|
|
119
|
+
Static --> CSS
|
|
120
|
+
Static --> JS
|
|
121
|
+
|
|
122
|
+
App -.->|Serves| Static
|
|
123
|
+
|
|
124
|
+
style Browser fill:#e1f5ff
|
|
125
|
+
style App fill:#fff3cd
|
|
126
|
+
style MDParser fill:#d4edda
|
|
127
|
+
style Static fill:#f8d7da
|
|
128
|
+
style Mermaid fill:#cce5ff
|
|
129
|
+
style Footnotes fill:#cce5ff
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## How Bloggy Works
|
|
133
|
+
|
|
134
|
+
### 1. Request Flow
|
|
135
|
+
|
|
136
|
+
```mermaid
|
|
137
|
+
---
|
|
138
|
+
width: 80vw
|
|
139
|
+
---
|
|
140
|
+
sequenceDiagram
|
|
141
|
+
participant User
|
|
142
|
+
participant Browser
|
|
143
|
+
participant FastHTML
|
|
144
|
+
participant Router
|
|
145
|
+
participant FileSystem
|
|
146
|
+
participant Renderer
|
|
147
|
+
participant HTMX
|
|
148
|
+
|
|
149
|
+
User->>Browser: Visit /
|
|
150
|
+
Browser->>FastHTML: GET /
|
|
151
|
+
FastHTML->>Router: Route to index()
|
|
152
|
+
Router->>FileSystem: Scan for .md files
|
|
153
|
+
FileSystem-->>Router: Return file tree
|
|
154
|
+
Router->>Browser: Render post list + layout
|
|
155
|
+
|
|
156
|
+
User->>Browser: Click post link
|
|
157
|
+
Browser->>HTMX: hx-get="/posts/demo"
|
|
158
|
+
HTMX->>FastHTML: GET /posts/demo
|
|
159
|
+
FastHTML->>Router: Route to post_detail()
|
|
160
|
+
Router->>FileSystem: Read demo.md
|
|
161
|
+
FileSystem-->>Router: Markdown content
|
|
162
|
+
Router->>Renderer: Parse & render markdown
|
|
163
|
+
|
|
164
|
+
rect rgb(200, 220, 250)
|
|
165
|
+
Note over Renderer: Process custom syntax:<br/>- Footnotes [^1]<br/>- Mermaid diagrams<br/>- Internal links
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
Renderer-->>Router: Rendered HTML
|
|
169
|
+
Router->>HTMX: Return HTML fragment
|
|
170
|
+
HTMX->>Browser: Swap content (#main-content)
|
|
171
|
+
Browser->>User: Display post
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 2. Markdown Processing Pipeline
|
|
175
|
+
|
|
176
|
+
```mermaid
|
|
177
|
+
---
|
|
178
|
+
width: 80vw
|
|
179
|
+
---
|
|
180
|
+
flowchart LR
|
|
181
|
+
A[Raw Markdown] --> B{Extract Footnotes}
|
|
182
|
+
B -->|Content| C[Preprocess Super/Sub]
|
|
183
|
+
B -->|Footnote Defs| D[Store in Dict]
|
|
184
|
+
|
|
185
|
+
C --> E[Preprocess Tabs]
|
|
186
|
+
E -->|Content + Placeholders| F[Mistletoe Parser]
|
|
187
|
+
E -->|Tab Data| G[Tab Store]
|
|
188
|
+
|
|
189
|
+
F --> H[Token Stream]
|
|
190
|
+
|
|
191
|
+
H --> I{ContentRenderer}
|
|
192
|
+
|
|
193
|
+
I -->|BlockCode + 'mermaid'| J[Mermaid Renderer]
|
|
194
|
+
I -->|Link| K[Link Renderer]
|
|
195
|
+
I -->|FootnoteRef| L[Footnote Renderer]
|
|
196
|
+
I -->|Other| M[Standard HTML]
|
|
197
|
+
|
|
198
|
+
J --> N[Diagram with Controls]
|
|
199
|
+
K --> O{Relative/Internal?}
|
|
200
|
+
O -->|Relative| P[Resolve Path]
|
|
201
|
+
P --> Q[Add HTMX attrs]
|
|
202
|
+
O -->|Internal| Q
|
|
203
|
+
O -->|External| R[Add target=_blank]
|
|
204
|
+
|
|
205
|
+
L --> S[Sidenote Component]
|
|
206
|
+
D --> S
|
|
207
|
+
|
|
208
|
+
N --> T[Initial HTML]
|
|
209
|
+
Q --> T
|
|
210
|
+
R --> T
|
|
211
|
+
S --> T
|
|
212
|
+
M --> T
|
|
213
|
+
|
|
214
|
+
T --> U[Postprocess Tabs]
|
|
215
|
+
G --> U
|
|
216
|
+
U -->|Render Each Tab| V[ContentRenderer]
|
|
217
|
+
V --> U
|
|
218
|
+
|
|
219
|
+
U --> W[Apply CSS Classes]
|
|
220
|
+
W --> X[Final HTML]
|
|
221
|
+
|
|
222
|
+
style J fill:#ffe6cc
|
|
223
|
+
style L fill:#d1ecf1
|
|
224
|
+
style O fill:#fff3cd
|
|
225
|
+
style U fill:#e7d4ff
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 3. Mermaid Diagram Lifecycle
|
|
229
|
+
|
|
230
|
+
```mermaid
|
|
231
|
+
---
|
|
232
|
+
width: 60vw
|
|
233
|
+
---
|
|
234
|
+
stateDiagram-v2
|
|
235
|
+
[*] --> Rendered: Page Load
|
|
236
|
+
|
|
237
|
+
state Rendered {
|
|
238
|
+
[*] --> Initialize
|
|
239
|
+
Initialize --> AddControls: Create buttons
|
|
240
|
+
AddControls --> StoreCode: Save original code
|
|
241
|
+
StoreCode --> EnableInteraction: Mouse events
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
state EnableInteraction {
|
|
245
|
+
[*] --> Idle
|
|
246
|
+
Idle --> Panning: Mouse drag
|
|
247
|
+
Idle --> Zooming: Mouse wheel
|
|
248
|
+
Idle --> ButtonZoom: +/- buttons
|
|
249
|
+
ButtonZoom --> Idle
|
|
250
|
+
Zooming --> Idle
|
|
251
|
+
Panning --> Idle
|
|
252
|
+
|
|
253
|
+
state "Reset Button" as Reset
|
|
254
|
+
Idle --> Reset: Click reset
|
|
255
|
+
Reset --> Idle: Restore defaults
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
Rendered --> ThemeChange: Dark/Light toggle
|
|
259
|
+
|
|
260
|
+
state ThemeChange {
|
|
261
|
+
[*] --> DetectTheme
|
|
262
|
+
DetectTheme --> GetOriginalCode: Read data attribute
|
|
263
|
+
GetOriginalCode --> ClearWrapper
|
|
264
|
+
ClearWrapper --> ReinitMermaid: New theme
|
|
265
|
+
ReinitMermaid --> ReRender: mermaid.run()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
ThemeChange --> Rendered: Re-rendered
|
|
269
|
+
|
|
270
|
+
note right of ThemeChange
|
|
271
|
+
MutationObserver watches
|
|
272
|
+
HTML class changes
|
|
273
|
+
end note
|
|
274
|
+
|
|
275
|
+
note right of EnableInteraction
|
|
276
|
+
Transform state stored
|
|
277
|
+
per diagram ID
|
|
278
|
+
end note
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Key Features
|
|
282
|
+
|
|
283
|
+
### ✨ Advanced Markdown Features
|
|
284
|
+
- **Footnotes as Sidenotes**: `[^1]` references become elegant margin notes on desktop, expandable on mobile with smooth animations
|
|
285
|
+
- **YouTube Embeds**: Use `[yt:VIDEO_ID]` or `[yt:VIDEO_ID|Caption]` for responsive iframe cards with aspect-ratio containers
|
|
286
|
+
- **Task Lists**: `- [ ]` / `- [x]` render as custom styled checkboxes (green for checked, gray for unchecked) with SVG checkmarks
|
|
287
|
+
- **Mermaid Diagrams**: Full support for flowcharts, sequence diagrams, state diagrams, Gantt charts, etc.
|
|
288
|
+
- **Interactive Diagrams**:
|
|
289
|
+
- Zoom with mouse wheel (zooms towards cursor position)
|
|
290
|
+
- Pan by dragging with mouse
|
|
291
|
+
- Built-in controls: fullscreen, reset, zoom in/out buttons
|
|
292
|
+
- Auto-scaling based on diagram aspect ratio (wide diagrams like Gantt charts get special handling)
|
|
293
|
+
- Fullscreen modal viewer with dark mode support
|
|
294
|
+
- **Theme-aware Rendering**: Diagrams automatically re-render when switching light/dark mode via MutationObserver
|
|
295
|
+
- **Mermaid Frontmatter**: Configure diagram size with YAML frontmatter (width, height, min-height)
|
|
296
|
+
- **Tabbed Content**: Create multi-tab sections using `:::tabs` and `::tab{title="..."}` syntax with smooth transitions
|
|
297
|
+
- **Relative Links**: Full support for relative markdown links (`./file.md`, `../other.md`) with automatic path resolution
|
|
298
|
+
- **Math Notation**: KaTeX support for inline `$E=mc^2$` and block `$$` math equations, auto-renders after HTMX swaps
|
|
299
|
+
- **Superscript & Subscript**: Use `^text^` for superscript and `~text~` for subscript (preprocessed before rendering)
|
|
300
|
+
- **Strikethrough**: Use `~~text~~` for strikethrough formatting
|
|
301
|
+
- **Pandoc-style Attributes**: Add classes to inline text with `` `text`{.class #id} `` syntax for semantic markup (renders as `<span>` tags, not `<code>`)
|
|
302
|
+
- **Cascading Custom CSS**: Add `custom.css` or `style.css` files at multiple levels (root, folders) with automatic scoping
|
|
303
|
+
|
|
304
|
+
### 🎨 Modern UI
|
|
305
|
+
- **Responsive Design**: Works beautifully on all screen sizes with mobile-first approach
|
|
306
|
+
- **Three-Panel Layout**: Posts sidebar, main content, and table of contents for easy navigation
|
|
307
|
+
- **Dark Mode**: Automatic theme switching with localStorage persistence and instant visual feedback
|
|
308
|
+
- **HTMX Navigation**: Fast, SPA-like navigation without full page reloads using `hx-get`, `hx-target`, and `hx-push-url`
|
|
309
|
+
- **Collapsible Folders**: Organize posts in nested directories with chevron indicators and smooth expand/collapse
|
|
310
|
+
- **Auto-Generated TOC**: Table of contents automatically extracted from headings with scroll-based active highlighting
|
|
311
|
+
- **Mobile Menus**: Slide-in panels for posts and TOC on mobile devices with smooth transitions
|
|
312
|
+
- **Sticky Navigation**: Navbar stays at top while scrolling, with mobile menu toggles
|
|
313
|
+
- **Active Link Highlighting**: Current post and TOC section highlighted with blue accents
|
|
314
|
+
- **Auto-Reveal in Sidebar**: Active post automatically expanded and scrolled into view when opening sidebar
|
|
315
|
+
- **Ultra-Thin Scrollbars**: Custom styled 3px scrollbars that adapt to light/dark theme
|
|
316
|
+
- **Frosted Glass Sidebars**: Backdrop blur and transparency effects on sidebar components
|
|
317
|
+
|
|
318
|
+
### 🚀 Technical Highlights
|
|
319
|
+
- Built on **FastHTML** for modern Python web development with integrated HTMX
|
|
320
|
+
- Uses **Mistletoe** for extensible Markdown parsing with custom token renderers
|
|
321
|
+
- **TailwindCSS** + **MonsterUI** for utility-first styling and UI components
|
|
322
|
+
- **Hyperscript** for declarative interactive behaviors (theme toggle, sidenote interactions)
|
|
323
|
+
- **Mermaid.js v11** for diagram rendering with custom zoom/pan/fullscreen controls via ES modules
|
|
324
|
+
- **KaTeX** for mathematical notation rendering with auto-render on content swaps
|
|
325
|
+
- **Smart Link Resolution**: Automatically converts relative links to proper routes with HTMX attributes
|
|
326
|
+
- **Frontmatter Caching**: LRU cache for parsed frontmatter based on file modification time
|
|
327
|
+
- **Lazy Sidebar Loading**: Posts sidebar loaded progressively via HTMX endpoint for faster initial load
|
|
328
|
+
- **Performance Logging**: Debug-level logging tracks render times and bottlenecks to `/tmp/bloggy_core.log`
|
|
329
|
+
- **Custom 404 Page**: Elegant error page with navigation options and helpful tips
|
|
330
|
+
- **Static File Serving**: Serves images and assets from blog directories via `/posts/{path}.{ext}` routes
|
|
331
|
+
- **Optional Authentication**: Session-based auth with Beforeware when username/password configured
|
|
332
|
+
|
|
333
|
+
## Content Writing Features
|
|
334
|
+
|
|
335
|
+
### Footnotes as Sidenotes
|
|
336
|
+
`[^1]` references compile into margin sidenotes on desktop (xl breakpoint: 1280px+) and touch-friendly expandables on smaller screens, powered by the `sidenote.css` stylesheet. On desktop, clicking a footnote reference highlights the corresponding sidenote with a temporary border animation. On mobile, clicking toggles the visibility with smooth fade-in/out. Continue to define footnotes with the standard `[^label]: ...` syntax—the renderer keeps the footnote text close to the reference and gracefully handles missing definitions with helpful placeholders. Sidenotes have amber/blue borders (light/dark) and appear in the right margin with proper spacing.
|
|
337
|
+
|
|
338
|
+
### Task Lists & YouTube Embeds
|
|
339
|
+
Checklists begin with `- [ ]` (open) or `- [x]` (done) and render as custom styled items with inline flex layout. Checked items display a green background with SVG checkmark, while unchecked items show a gray background. No bullet points are shown (`list-style: none`), and the checkbox is aligned to the start of the text. Embeds are just as easy: `[yt:VIDEO_ID]` or `[yt:VIDEO_ID|Caption]` drops in a responsive YouTube iframe with aspect-video ratio (16:9), rounded corners, and border. Optional captions appear below in smaller gray text. No extra HTML wrappers required—just use the simple bracket syntax.
|
|
340
|
+
|
|
341
|
+
### Tabbed Content
|
|
342
|
+
Group related snippets with the `:::tabs` container and `::tab{title="Label"}` blocks. Each tab is rendered into a fully interactive panel using `switchTab()` JavaScript function. Tabs feature:
|
|
343
|
+
- Clean header with active state (bold border-bottom, darker text)
|
|
344
|
+
- Smooth fade-in animation when switching tabs (0.2s ease-in)
|
|
345
|
+
- Height stabilization: all panels measured, container set to max height to prevent layout shifts
|
|
346
|
+
- Absolute positioning for inactive panels (hidden but measured for height calculation)
|
|
347
|
+
- Full markdown rendering support within each tab panel
|
|
348
|
+
- Frosted glass aesthetic matching the sidebar design
|
|
349
|
+
|
|
350
|
+
The client script stabilizes the height on DOMContentLoaded and after HTMX swaps, ensuring smooth transitions without content jumps.
|
|
351
|
+
|
|
352
|
+
````markdown
|
|
353
|
+
:::tabs
|
|
354
|
+
::tab{title="Rust"}
|
|
355
|
+
```rust
|
|
356
|
+
// tab content
|
|
357
|
+
```
|
|
358
|
+
::tab{title="Python"}
|
|
359
|
+
```python
|
|
360
|
+
# tab content
|
|
361
|
+
```
|
|
362
|
+
:::
|
|
363
|
+
````
|
|
364
|
+
|
|
365
|
+
### Relative Links & Asset Helpers
|
|
366
|
+
Relative references like `[Next chapter](../chapter-02.md)` automatically resolve to `/posts/...`, strip the `.md` extension, and gain `hx-get`, `hx-target="#main-content"`, `hx-push-url="true"`, and `hx-swap="innerHTML show:window:top"` attributes for seamless HTMX navigation. The renderer uses the current post's path to resolve relative links correctly, handling both `./` and `../` navigation. External links (starting with `http://`, `https://`, `mailto:`, etc.) automatically get `target="_blank"` and `rel="noopener noreferrer"` for security.
|
|
367
|
+
|
|
368
|
+
Images and other assets can live under the blog tree and are served through `/posts/{path}.{ext}` via the `serve_post_static` route. The `FrankenRenderer.render_image()` method rewrites relative image URLs based on the current post path (using `img_dir` calculated from `current_path`), so folder-specific assets stay tidy and work correctly. Images are styled with `max-w-full h-auto rounded-lg mb-6` classes for responsive display.
|
|
369
|
+
|
|
370
|
+
### Inline Formatting & Math
|
|
371
|
+
Use `^sup^`, `~sub~`, `~~strike~~`, and Pandoc-style `` `code`{.class #id lang=python} `` tokens for fine-grained styling. The preprocessing stage converts `^text^` to `<sup>text</sup>` and `~text~` to `<sub>text</sub>` before markdown parsing (but preserves `~~strikethrough~~`). The `InlineCodeAttr` token (precedence 8) parses backtick code with curly-brace attributes, supporting:
|
|
372
|
+
- Classes: `.variable`, `.emphasis`, `.keyword`
|
|
373
|
+
- IDs: `#unique-id`
|
|
374
|
+
- Key-value pairs: `lang=python`
|
|
375
|
+
|
|
376
|
+
These render as `<span>` tags (not `<code>`) when attributes are present, making them perfect for semantic styling without monospace fonts.
|
|
377
|
+
|
|
378
|
+
KaTeX handles `$inline$` or `$$block$$` math with `renderMathInElement()` configured for both display styles. The bundled script at `bloggy/static/scripts.js` runs the auto-renderer on both `DOMContentLoaded` and `htmx:afterSwap` events, so math keeps rendering correctly even when HTMX swaps fragments or the theme flips. The configuration uses `throwOnError: false` for graceful degradation.
|
|
379
|
+
|
|
380
|
+
## Interactive Diagrams & Visualizations
|
|
381
|
+
|
|
382
|
+
### Mermaid Frontmatter & Controls
|
|
383
|
+
Mermaid blocks can opt into frontmatter to tweak the container size with YAML-style key-value pairs:
|
|
384
|
+
|
|
385
|
+
```mermaid
|
|
386
|
+
---
|
|
387
|
+
width: 85vw
|
|
388
|
+
height: 60vh
|
|
389
|
+
---
|
|
390
|
+
graph LR
|
|
391
|
+
A --> B
|
|
392
|
+
B --> C
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Supported frontmatter keys:
|
|
396
|
+
- **width**: Container width (default: `65vw`). Can use viewport units (`vw`), pixels (`px`), or percentages
|
|
397
|
+
- **height**: Container height (default: `auto`)
|
|
398
|
+
- **min-height**: Minimum container height (default: `400px`)
|
|
399
|
+
|
|
400
|
+
These hints set the wrapper dimensions; viewport-based widths (`vw`) trigger a CSS breakout that centers the diagram using `position: relative; left: 50%; transform: translateX(-50%)`.
|
|
401
|
+
|
|
402
|
+
Every diagram includes a control bar (top-right corner with frosted glass background):
|
|
403
|
+
- **⛶ Fullscreen**: Opens diagram in modal overlay with dark backdrop
|
|
404
|
+
- **Reset**: Restores original scale and pan position
|
|
405
|
+
- **+ / −**: Zoom in/out buttons (10% increments, clamped 0.1x to 15x)
|
|
406
|
+
|
|
407
|
+
The controls are wired to JavaScript functions in `bloggy/static/scripts.js`:
|
|
408
|
+
- `openMermaidFullscreen(id)`: Creates modal with close button and ESC key handler
|
|
409
|
+
- `resetMermaidZoom(id)`: Resets transform to `translate(0px, 0px) scale(1)`
|
|
410
|
+
- `zoomMermaidIn(id)` / `zoomMermaidOut(id)`: Adjusts scale by 1.1x / 0.9x factors
|
|
411
|
+
|
|
412
|
+
### Scripted Interaction & Resilience
|
|
413
|
+
`bloggy/static/scripts.js` drives the interactive layer with sophisticated features:
|
|
414
|
+
|
|
415
|
+
#### Mermaid Zoom & Pan
|
|
416
|
+
- **Mouse wheel zoom**: Zooms toward cursor position with 1% intensity per wheel event (prevents jarring jumps)
|
|
417
|
+
- **Mouse drag panning**: Click and drag to pan, cursor changes to `grabbing` during drag
|
|
418
|
+
- **Smart initial scaling**:
|
|
419
|
+
- Wide diagrams (aspect ratio > 3): Scale to fit width, allow vertical scroll
|
|
420
|
+
- Normal diagrams: Fit to smaller dimension (width or height), max 3x upscaling
|
|
421
|
+
- Accounts for padding: 32px total (16px each side from `p-4` class)
|
|
422
|
+
- **State management**: Per-diagram state stored in `mermaidStates` object with scale, translateX, translateY, isPanning, startX, startY
|
|
423
|
+
- **Transform origin**: Center-center for natural zoom behavior
|
|
424
|
+
|
|
425
|
+
#### Theme-Aware Re-rendering
|
|
426
|
+
- **MutationObserver** watches `<html>` class changes for dark mode toggle
|
|
427
|
+
- `reinitializeMermaid()` function:
|
|
428
|
+
- Detects theme via `getCurrentTheme()` (checks for `.dark` class)
|
|
429
|
+
- Preserves wrapper height before clearing to prevent layout shifts
|
|
430
|
+
- Deletes old state and re-creates fresh diagram
|
|
431
|
+
- Decodes HTML entities from `data-mermaid-code` attribute
|
|
432
|
+
- Re-runs `mermaid.run()` and `initMermaidInteraction()` after 100ms delay
|
|
433
|
+
- Skips reinit on initial load (uses `isInitialLoad` flag)
|
|
434
|
+
|
|
435
|
+
#### HTMX Integration
|
|
436
|
+
- **`htmx:afterSwap` event listener**: Re-runs Mermaid, updates active links, reinitializes mobile menus
|
|
437
|
+
- **Sidebar auto-reveal**: Expands parent `<details>` elements and scrolls active post into view
|
|
438
|
+
- **Active link highlighting**: Adds blue ring and background to current post in sidebar
|
|
439
|
+
- **TOC scroll tracking**: Updates active TOC link based on scroll position with `requestAnimationFrame` throttling
|
|
440
|
+
|
|
441
|
+
#### Mobile Menu Handling
|
|
442
|
+
- Slide-in panels for posts and TOC with `transform` transitions (`-translate-x-full` / `translate-x-full`)
|
|
443
|
+
- Auto-close panels when link clicked (100ms delay for smooth transition)
|
|
444
|
+
- Toggle buttons in navbar show/hide panels (only one open at a time)
|
|
445
|
+
|
|
446
|
+
#### Additional Features
|
|
447
|
+
- **Fullscreen modal**: Dark backdrop with blur, ESC key and background click to close
|
|
448
|
+
- **Tab height stabilization**: Measures all tab panels, sets container to max height on load and after swaps
|
|
449
|
+
- **Math rendering**: Auto-runs KaTeX after swaps and on initial load
|
|
450
|
+
|
|
451
|
+
## Navigation & Layout Details
|
|
452
|
+
|
|
453
|
+
The `layout()` helper builds the complete page structure with intelligent HTMX optimization:
|
|
454
|
+
|
|
455
|
+
### Layout Components
|
|
456
|
+
- **Navbar**: Sticky header with blog title, theme toggle, and mobile menu buttons (posts/TOC toggles)
|
|
457
|
+
- **Three-panel layout**:
|
|
458
|
+
- Left sidebar (72 width): Posts file tree with lazy HTMX loading
|
|
459
|
+
- Main content (flex-1): Swappable content area with section-specific CSS classes
|
|
460
|
+
- Right sidebar (72 width): Auto-generated TOC from headings
|
|
461
|
+
- **Mobile panels**: Fullscreen overlays for posts and TOC with smooth slide transitions
|
|
462
|
+
- **Footer**: "Powered by Bloggy" right-aligned in max-width container
|
|
463
|
+
|
|
464
|
+
### HTMX Optimization
|
|
465
|
+
When `htmx.request` is detected, `layout()` returns only swappable fragments:
|
|
466
|
+
- Main content container with `id="main-content"`
|
|
467
|
+
- TOC sidebar with `hx_swap_oob="true"` for out-of-band swap
|
|
468
|
+
- CSS container with `hx_swap_oob="true"` for scoped styles
|
|
469
|
+
- `Title` element for browser tab title
|
|
470
|
+
- Skips navbar, posts sidebar, footer, mobile panels (already in DOM)
|
|
471
|
+
|
|
472
|
+
### Sidebar Features
|
|
473
|
+
- **Left sidebar** (`build_post_tree`):
|
|
474
|
+
- Recursive folder tree with chevron indicators
|
|
475
|
+
- Folders: Blue folder icon, clickable summary, nested `<ul>` with border
|
|
476
|
+
- Files: Gray file-text icon, HTMX-enhanced links with `data-path` attribute
|
|
477
|
+
- Cached via `@lru_cache` based on max modification time fingerprint
|
|
478
|
+
- Lazy loaded via `/_sidebar/posts` endpoint with loading spinner placeholder
|
|
479
|
+
- **Right sidebar** (`extract_toc`):
|
|
480
|
+
- Parses headings from markdown (excludes code blocks)
|
|
481
|
+
- Generates anchor slugs matching heading IDs
|
|
482
|
+
- Indentation based on heading level (`ml-{(level-1)*3}`)
|
|
483
|
+
- Active tracking based on scroll position
|
|
484
|
+
|
|
485
|
+
### CSS Scoping
|
|
486
|
+
`get_custom_css_links()` discovers and loads custom CSS:
|
|
487
|
+
1. **Root CSS**: Applies globally (`/posts/custom.css` or `/posts/style.css`)
|
|
488
|
+
2. **Folder CSS**: Automatically scoped to section via wrapper class (e.g., `#main-content.section-demo-flat-land`)
|
|
489
|
+
3. Content wrapped in `<Style>` tag with scoped selector to prevent cross-section leakage
|
|
490
|
+
4. All CSS changes swapped via `#scoped-css-container` for HTMX compatibility
|
|
491
|
+
|
|
492
|
+
### Performance Logging
|
|
493
|
+
- Debug logs track timing for each phase (section class, TOC build, CSS resolution, container build)
|
|
494
|
+
- Logs written to `/tmp/bloggy_core.log` with rotation (10 MB, 10 days retention)
|
|
495
|
+
- Request start/complete markers for easy debugging
|
|
496
|
+
|
|
497
|
+
## Authentication (optional)
|
|
498
|
+
|
|
499
|
+
Set `username` and `password` in your `.bloggy` file or via `BLOGGY_USER` / `BLOGGY_PASSWORD` environment variables to enable session-based authentication. When enabled:
|
|
500
|
+
|
|
501
|
+
- **Beforeware middleware**: Intercepts all requests (except login page, static files, and CSS/JS)
|
|
502
|
+
- **Login flow**:
|
|
503
|
+
- Unauthenticated users redirected to `/login` with `next` URL saved in session
|
|
504
|
+
- Login form uses MonsterUI styling (UK input classes, styled buttons)
|
|
505
|
+
- Successful login stores username in `request.session["auth"]`
|
|
506
|
+
- Redirects to original `next` URL after authentication
|
|
507
|
+
- **Error handling**: Invalid credentials show red error message below form
|
|
508
|
+
- **Route exclusions**: RegEx patterns skip auth for `^/login$`, `^/_sidebar/.*`, `^/static/.*`, `.*\.css`, `.*\.js`
|
|
509
|
+
|
|
510
|
+
Authentication is completely optional—if no credentials configured, Beforeware is set to `None` and all routes are publicly accessible.
|
|
511
|
+
|
|
512
|
+
## Project Structure
|
|
513
|
+
|
|
514
|
+
```mermaid
|
|
515
|
+
graph LR
|
|
516
|
+
subgraph bloggy/
|
|
517
|
+
A[__init__.py]
|
|
518
|
+
B[core.py<br/>Main App Logic]
|
|
519
|
+
C[main.py<br/>Entry Point]
|
|
520
|
+
|
|
521
|
+
subgraph static/
|
|
522
|
+
D[scripts.js<br/>Mermaid + Interactions]
|
|
523
|
+
E[sidenote.css<br/>Footnote Styles]
|
|
524
|
+
F[favicon.png]
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
subgraph demo/
|
|
529
|
+
G[*.md Files<br/>Your Blog Posts]
|
|
530
|
+
|
|
531
|
+
subgraph guides/
|
|
532
|
+
H[*.md Files<br/>Nested Content]
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
B --> D
|
|
537
|
+
B --> E
|
|
538
|
+
B --> F
|
|
539
|
+
B -.reads.-> G
|
|
540
|
+
B -.reads.-> H
|
|
541
|
+
|
|
542
|
+
style B fill:#ffe6cc
|
|
543
|
+
style D fill:#d1ecf1
|
|
544
|
+
style G fill:#d4edda
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## Installation
|
|
548
|
+
|
|
549
|
+
### From PyPI (recommended)
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
pip install bloggy
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### From source
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
git clone https://github.com/yeshwanth/bloggy.git
|
|
559
|
+
cd bloggy
|
|
560
|
+
pip install -e .
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
## Quick Start
|
|
564
|
+
|
|
565
|
+
1. Create a directory with your markdown files:
|
|
566
|
+
```bash
|
|
567
|
+
mkdir my-blog
|
|
568
|
+
cd my-blog
|
|
569
|
+
echo "# Hello World" > hello.md
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
2. Run Bloggy:
|
|
573
|
+
```bash
|
|
574
|
+
bloggy .
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
3. Open your browser at `http://127.0.0.1:5001`
|
|
578
|
+
|
|
579
|
+
## Configuration
|
|
580
|
+
|
|
581
|
+
Bloggy supports three ways to configure your blog (in priority order):
|
|
582
|
+
|
|
583
|
+
1. **`.bloggy` configuration file** (TOML format) - Highest priority
|
|
584
|
+
2. **Environment variables** - Fallback
|
|
585
|
+
3. **Default values** - Final fallback
|
|
586
|
+
|
|
587
|
+
### Using a `.bloggy` Configuration File
|
|
588
|
+
|
|
589
|
+
Create a `.bloggy` file in your blog directory or current directory:
|
|
590
|
+
|
|
591
|
+
```toml
|
|
592
|
+
# Blog title (default: derives from root folder name via slug_to_title)
|
|
593
|
+
title = "My Awesome Blog"
|
|
594
|
+
|
|
595
|
+
# Root folder containing markdown files (default: current directory)
|
|
596
|
+
root = "."
|
|
597
|
+
|
|
598
|
+
# Server host (default: 127.0.0.1)
|
|
599
|
+
# Use "0.0.0.0" to make the server accessible from network
|
|
600
|
+
host = "127.0.0.1"
|
|
601
|
+
|
|
602
|
+
# Server port (default: 5001)
|
|
603
|
+
port = 5001
|
|
604
|
+
|
|
605
|
+
# Optional authentication credentials (enables Beforeware middleware)
|
|
606
|
+
username = "admin"
|
|
607
|
+
password = "hunter2"
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
All settings in the `.bloggy` file are optional. The configuration is managed by the `Config` class in `bloggy/config.py`.
|
|
611
|
+
|
|
612
|
+
### Custom Sidebar Ordering
|
|
613
|
+
|
|
614
|
+
Place a `.bloggy` file in any folder to control the sidebar order for that folder. `.bloggy` uses TOML format. Use `order` to pin items first, then `sort` and `folders_first` for the remainder.
|
|
615
|
+
|
|
616
|
+
```toml
|
|
617
|
+
# Items listed in order are shown first. Use exact names (include extensions).
|
|
618
|
+
order = ["todo.md", "static-build.md", "docs"]
|
|
619
|
+
|
|
620
|
+
# Sorting for items not listed in order
|
|
621
|
+
sort = "name_asc" # name_asc, name_desc, mtime_asc, mtime_desc
|
|
622
|
+
folders_first = true
|
|
623
|
+
folders_always_first = false
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Notes:
|
|
627
|
+
- `folders_first` only affects the items not listed in `order`.
|
|
628
|
+
- `folders_always_first` moves all folders to the top after ordering/sorting, while preserving their relative order.
|
|
629
|
+
|
|
630
|
+
### Environment Variables
|
|
631
|
+
|
|
632
|
+
You can also use environment variables as a fallback:
|
|
633
|
+
|
|
634
|
+
- `BLOGGY_ROOT`: Path to your markdown files (default: current directory)
|
|
635
|
+
- `BLOGGY_TITLE`: Your blog's title (default: folder name converted via `slug_to_title()`)
|
|
636
|
+
- `BLOGGY_HOST`: Server host (default: 127.0.0.1)
|
|
637
|
+
- `BLOGGY_PORT`: Server port (default: 5001)
|
|
638
|
+
- `BLOGGY_USER`: Optional username to enable session-based authentication
|
|
639
|
+
- `BLOGGY_PASSWORD`: Optional password paired with `BLOGGY_USER`
|
|
640
|
+
|
|
641
|
+
### Examples
|
|
642
|
+
|
|
643
|
+
**Using a `.bloggy` file:**
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
# Create a .bloggy file in your blog directory
|
|
647
|
+
title = "My Tech Blog"
|
|
648
|
+
port = 8000
|
|
649
|
+
host = "0.0.0.0"
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Using environment variables:**
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
export BLOGGY_ROOT=/path/to/your/markdown/files
|
|
656
|
+
export BLOGGY_TITLE="My Awesome Blog"
|
|
657
|
+
export BLOGGY_PORT=8000
|
|
658
|
+
export BLOGGY_HOST="0.0.0.0"
|
|
659
|
+
bloggy
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Passing directory as argument:**
|
|
663
|
+
|
|
664
|
+
```bash
|
|
665
|
+
bloggy /path/to/your/markdown/files
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
**Enabling authentication:**
|
|
669
|
+
|
|
670
|
+
```.env
|
|
671
|
+
# Via .bloggy file
|
|
672
|
+
title = "Private Blog"
|
|
673
|
+
username = "admin"
|
|
674
|
+
password = "secret123"
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
# Or via environment variables
|
|
679
|
+
export BLOGGY_USER="admin"
|
|
680
|
+
export BLOGGY_PASSWORD="secret123"
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**Configuration priority example:**
|
|
684
|
+
|
|
685
|
+
If you have both a `.bloggy` file with `port = 8000` and an environment variable `BLOGGY_PORT=9000`, the `.bloggy` file takes precedence and port 8000 will be used.
|
|
686
|
+
|
|
687
|
+
## Custom Styling with Cascading CSS
|
|
688
|
+
|
|
689
|
+
Bloggy supports **cascading custom CSS** at multiple levels, allowing you to style your entire blog globally or customize specific sections:
|
|
690
|
+
|
|
691
|
+
### CSS Loading Order
|
|
692
|
+
|
|
693
|
+
1. **Framework CSS** (`bloggy/static/custom.css`) - Core styling for Bloggy itself
|
|
694
|
+
2. **Blog-wide CSS** (`/your-blog-root/custom.css`) - Applies to all posts
|
|
695
|
+
3. **Folder-specific CSS** (`/your-blog-root/section/custom.css`) - Applies only to posts in that folder
|
|
696
|
+
|
|
697
|
+
Each level can override styles from previous levels, following standard CSS cascade rules.
|
|
698
|
+
|
|
699
|
+
### Pandoc-style Inline Attributes
|
|
700
|
+
|
|
701
|
+
Use backticks with attributes to add semantic classes to inline text:
|
|
702
|
+
|
|
703
|
+
:::tabs
|
|
704
|
+
::tab{title="Markdown Example"}
|
|
705
|
+
The variables `x`{.abcd}, `y`{.abcd}, and `z`{.abcd} represent coordinates.
|
|
706
|
+
Use `important`{.emphasis} for highlighted terms.
|
|
707
|
+
::tab{title="CSS"}
|
|
708
|
+
```css
|
|
709
|
+
span.abcd {
|
|
710
|
+
font-family: monospace;
|
|
711
|
+
background-color: #f0f0f0;
|
|
712
|
+
padding: 2px 4px;
|
|
713
|
+
border-radius: 4px;
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
:::
|
|
717
|
+
|
|
718
|
+
Attributes support:
|
|
719
|
+
- **Classes**: `.variable`, `.emphasis`, `.keyword`
|
|
720
|
+
- **IDs**: `#unique-id`
|
|
721
|
+
- **Key-value pairs**: `lang=python`
|
|
722
|
+
|
|
723
|
+
Classes like `.variable`, `.emphasis`, and `.keyword` render as `<span>` tags (not `<code>`), making them perfect for semantic styling without monospace fonts.
|
|
724
|
+
|
|
725
|
+
### Example: Multi-level Custom CSS
|
|
726
|
+
|
|
727
|
+
**Root level** (`/blog/custom.css`) - Global styles:
|
|
728
|
+
```css
|
|
729
|
+
/* Base variable styling for all posts */
|
|
730
|
+
span.variable {
|
|
731
|
+
color: #e06c75;
|
|
732
|
+
font-weight: 500;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
span.emphasis {
|
|
736
|
+
background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
|
|
737
|
+
padding: 2px 6px;
|
|
738
|
+
border-radius: 3px;
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**Section level** (`/blog/tutorials/custom.css`) - Tutorial-specific (auto-scoped to `#main-content.section-tutorials`):
|
|
743
|
+
```css
|
|
744
|
+
/* Override for tutorial section - use blue variables */
|
|
745
|
+
span.variable {
|
|
746
|
+
color: #61afef;
|
|
747
|
+
position: relative;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/* Add overline to variables in tutorials */
|
|
751
|
+
span.variable::before {
|
|
752
|
+
content: '';
|
|
753
|
+
position: absolute;
|
|
754
|
+
top: -3px;
|
|
755
|
+
left: 50%;
|
|
756
|
+
transform: translateX(-50%);
|
|
757
|
+
width: 80%;
|
|
758
|
+
height: 2px;
|
|
759
|
+
background-color: currentColor;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
span.emphasis {
|
|
763
|
+
background: #ffd93d;
|
|
764
|
+
color: #333;
|
|
765
|
+
font-weight: bold;
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**Chapter level** (`/blog/tutorials/advanced/custom.css`) - Advanced chapter styling (auto-scoped to `#main-content.section-tutorials-advanced`):
|
|
770
|
+
```css
|
|
771
|
+
/* Different style for advanced tutorials */
|
|
772
|
+
span.variable {
|
|
773
|
+
color: #c678dd;
|
|
774
|
+
font-style: italic;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* Add special marker for keywords */
|
|
778
|
+
span.keyword {
|
|
779
|
+
color: #e5c07b;
|
|
780
|
+
text-transform: uppercase;
|
|
781
|
+
font-size: 0.85em;
|
|
782
|
+
letter-spacing: 1px;
|
|
783
|
+
}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Note: Folder-specific CSS is automatically wrapped by Bloggy with the appropriate section scope, so you don't need to write the `#main-content.section-*` wrapper yourself.
|
|
787
|
+
|
|
788
|
+
### Real Example from Demo
|
|
789
|
+
|
|
790
|
+
See the `demo/flat-land/` folder for a working example:
|
|
791
|
+
|
|
792
|
+
**Markdown** (`demo/flat-land/chapter-02.md`):
|
|
793
|
+
```markdown
|
|
794
|
+
The two Northern sides `RO`{.variable}, `OF`{.variable}, constitute the roof.
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
**CSS** (`demo/flat-land/custom.css`):
|
|
798
|
+
```css
|
|
799
|
+
span.variable {
|
|
800
|
+
color: #e06c75;
|
|
801
|
+
position: relative;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
span.variable::before {
|
|
805
|
+
content: '';
|
|
806
|
+
position: absolute;
|
|
807
|
+
top: -3px;
|
|
808
|
+
left: 50%;
|
|
809
|
+
transform: translateX(-50%);
|
|
810
|
+
width: 80%;
|
|
811
|
+
height: 2px;
|
|
812
|
+
background-color: currentColor;
|
|
813
|
+
}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
This renders `RO` and `OF` in red with a line above them, perfect for mathematical or geometric notation!
|
|
817
|
+
|
|
818
|
+
## Technical Implementation Details
|
|
819
|
+
|
|
820
|
+
### Markdown Processing Pipeline
|
|
821
|
+
1. **Frontmatter extraction**: `parse_frontmatter()` with file mtime-based caching
|
|
822
|
+
2. **Footnote extraction**: `extract_footnotes()` using regex to find `[^label]:` definitions
|
|
823
|
+
3. **Superscript/subscript preprocessing**: `preprocess_super_sub()` converts `^text^` and `~text~` to HTML
|
|
824
|
+
4. **Tab preprocessing**: `preprocess_tabs()` replaces `:::tabs` blocks with placeholders, stores tab data
|
|
825
|
+
5. **Mistletoe parsing**: Custom `ContentRenderer` with registered tokens:
|
|
826
|
+
- `YoutubeEmbed` (precedence 6): `[yt:VIDEO_ID|caption]` syntax
|
|
827
|
+
- `FootnoteRef`: `[^label]` references
|
|
828
|
+
- `InlineCodeAttr` (precedence 8): `` `code`{.class} `` syntax
|
|
829
|
+
- `Superscript` (precedence 7): `^text^` (if not preprocessed)
|
|
830
|
+
- `Subscript` (precedence 7): `~text~` (if not preprocessed)
|
|
831
|
+
- `Strikethrough` (precedence 7): `~~text~~`
|
|
832
|
+
6. **Token rendering**: Each token has custom `render_*` method in `ContentRenderer`
|
|
833
|
+
7. **Tab postprocessing**: `postprocess_tabs()` replaces placeholders with rendered tab HTML
|
|
834
|
+
8. **CSS class application**: `apply_classes()` adds Tailwind classes to HTML elements
|
|
835
|
+
|
|
836
|
+
### Custom Renderers
|
|
837
|
+
- **`render_list_item`**: Detects `[ ]` / `[x]` patterns, renders custom checkboxes
|
|
838
|
+
- **`render_youtube_embed`**: Creates responsive iframe with aspect-video container
|
|
839
|
+
- **`render_footnote_ref`**: Generates sidenote with hyperscript toggle behavior
|
|
840
|
+
- **`render_heading`**: Adds anchor ID using `text_to_anchor()` function
|
|
841
|
+
- **`render_block_code`**: Special handling for `mermaid` language, parses frontmatter
|
|
842
|
+
- **`render_link`**: Resolves relative paths, adds HTMX attributes or `target="_blank"`
|
|
843
|
+
- **`render_inline_code_attr`**: Parses Pandoc attributes, renders as `<span>` with classes
|
|
844
|
+
- **`render_image`**: Resolves relative image paths using `img_dir`
|
|
845
|
+
|
|
846
|
+
### Caching Strategy
|
|
847
|
+
- **Frontmatter cache**: `_frontmatter_cache` dict with `(mtime, data)` tuples
|
|
848
|
+
- **Posts tree cache**: `@lru_cache(maxsize=1)` on `_cached_build_post_tree(fingerprint)`
|
|
849
|
+
- **Sidebar HTML cache**: `@lru_cache(maxsize=1)` on `_cached_posts_sidebar_html(fingerprint)`
|
|
850
|
+
- **Fingerprint**: Max mtime of all `.md` files via `root.rglob("*.md")`
|
|
851
|
+
- Cache invalidation: Automatic when fingerprint changes (file modified)
|
|
852
|
+
|
|
853
|
+
### HTMX Integration
|
|
854
|
+
- **Main content swap**: `hx-get="/posts/path" hx-target="#main-content" hx-swap="innerHTML show:window:top"`
|
|
855
|
+
- **Out-of-band swaps**: TOC sidebar and CSS container use `hx_swap_oob="true"`
|
|
856
|
+
- **Push URL**: `hx-push-url="true"` updates browser history
|
|
857
|
+
- **Lazy loading**: Posts sidebar uses `hx-get="/_sidebar/posts" hx-trigger="load"`
|
|
858
|
+
- **Event handling**: JavaScript listens to `htmx:afterSwap` for re-initialization
|
|
859
|
+
|
|
860
|
+
### Logging & Debugging
|
|
861
|
+
- **Loguru**: Two handlers - stdout (INFO+) and file (DEBUG+)
|
|
862
|
+
- **Log file**: `/tmp/bloggy_core.log` with 10 MB rotation, 10 days retention
|
|
863
|
+
- **Performance tracking**: `time.time()` checkpoints throughout request handling
|
|
864
|
+
- **Debug groups**: `console.group()` in JavaScript for Mermaid operations
|
|
865
|
+
- **Request markers**: `########## REQUEST START/COMPLETE ##########` for easy grepping
|
|
866
|
+
|
|
867
|
+
### Security Features
|
|
868
|
+
- **HTML escaping**: Code blocks automatically escaped via `html.escape()`
|
|
869
|
+
- **External link protection**: `rel="noopener noreferrer"` on external links
|
|
870
|
+
- **Path validation**: Relative path resolution checks if resolved path is within root
|
|
871
|
+
- **Session-based auth**: Uses Starlette sessions, not exposed in URLs
|
|
872
|
+
- **CSRF protection**: Forms use POST with `enctype="multipart/form-data"`
|
|
873
|
+
|
|
874
|
+
### Responsive Breakpoints
|
|
875
|
+
- **Mobile**: < 768px (md breakpoint) - Shows mobile menu buttons, hides sidebars
|
|
876
|
+
- **Tablet**: 768px - 1279px - Shows sidebars but no sidenote margins
|
|
877
|
+
- **Desktop**: 1280px+ (xl breakpoint) - Full three-panel layout with sidenotes in margin
|
|
878
|
+
|
|
879
|
+
### Font Stack
|
|
880
|
+
- **Body text**: IBM Plex Sans (weights: 400, 500, 600, 700)
|
|
881
|
+
- **Code**: IBM Plex Mono (monospace)
|
|
882
|
+
- **Fallback**: System font stack via TailwindCSS defaults
|
|
883
|
+
- **Loading**: Preconnect to Google Fonts with `crossorigin` for speed
|
|
884
|
+
|
|
885
|
+
## Advanced Features
|
|
886
|
+
|
|
887
|
+
### Index/README Files
|
|
888
|
+
Place an `index.md` or `README.md` (case-insensitive) in your blog root directory to customize the home page. If neither exists, Bloggy shows a default welcome message. The index file:
|
|
889
|
+
- Takes precedence over README if both exist
|
|
890
|
+
- Renders with full sidebar and TOC support
|
|
891
|
+
- Uses the file's frontmatter `title` or blog title as page title
|
|
892
|
+
- Supports all markdown features (tabs, diagrams, footnotes, etc.)
|
|
893
|
+
|
|
894
|
+
### Smart 404 Page
|
|
895
|
+
When a route doesn't exist, Bloggy shows a custom 404 page with:
|
|
896
|
+
- Large "404" heading in gray
|
|
897
|
+
- Helpful error message explaining the situation
|
|
898
|
+
- Action buttons: "Go to Home" and "Go Back" with icons
|
|
899
|
+
- Tip section suggesting to check the sidebar
|
|
900
|
+
- Full sidebar included for easy navigation to correct page
|
|
901
|
+
|
|
902
|
+
### Code Highlighting
|
|
903
|
+
Code blocks are styled with proper language classes (`class="language-{lang}"`) for syntax highlighting. HTML/XML code is automatically escaped for display, while markdown code blocks preserve raw source. IBM Plex Mono font provides clear, readable monospace text.
|
|
904
|
+
|
|
905
|
+
### Heading Anchors
|
|
906
|
+
All headings (`h1` through `h6`) automatically get `id` attributes based on their text content using the `text_to_anchor()` function:
|
|
907
|
+
- Removes special characters
|
|
908
|
+
- Converts to lowercase
|
|
909
|
+
- Replaces spaces with hyphens
|
|
910
|
+
- Enables direct linking via `#anchor-slug`
|
|
911
|
+
- Powers TOC navigation with smooth scrolling (scroll margin 7rem for navbar offset)
|
|
912
|
+
|
|
913
|
+
### Image Handling
|
|
914
|
+
The `FrankenRenderer` class provides smart image handling:
|
|
915
|
+
- Responsive styling: `max-w-full h-auto rounded-lg mb-6`
|
|
916
|
+
- Relative path resolution: prepends `img_dir` based on post location
|
|
917
|
+
- Protocol detection: skips prepending for absolute URLs (`http://`, `https://`, `attachment:`, `blob:`, `data:`)
|
|
918
|
+
- Title attribute support: renders `title` if present in markdown
|
|
919
|
+
- Alt text: extracted from markdown image syntax
|
|
920
|
+
|
|
921
|
+
### Frontmatter Support
|
|
922
|
+
All markdown files support YAML frontmatter for metadata:
|
|
923
|
+
- `title`: Override default title (derived from filename)
|
|
924
|
+
- Parsed with `python-frontmatter` library
|
|
925
|
+
- Cached based on file modification time
|
|
926
|
+
- Missing frontmatter gracefully handled (empty dict + raw content)
|