π¦ EaglePress v2.18 is here!
π¦
EaglePress v2.18 is here! Hot off the Press!
Download link: EaglePress Latest Version v2.18 eaglepress.zip
I am Stan Switaj, the programmer of EaglePress and owner of EaglePress.org. If you need paid support of EaglePress CMS for any reason. Need an update, ok ask. Need a feature to a plugin or have an idea for a new theme, I'm ready to help, my email is opensource3@yahoo.com.
β’ β’ β’
EaglePress v2.18 β A (980.5 kB size) zip file with 8 themes included (firstTheme, secondTheme, americanTheme, USAtheme, NewsTheme, RedWhiteBlueTheme, plus the ModernTheme and EaglePressTheme showcase themes), speaking 13 languages out of the box (122 opt-in from the Dashboard). Python 3, PostgreSQL as the database. No framework. A mountain of CMS features β more themes and plugins coming soon. New in v2.18: you decide what greets your readers first. A front page ordered by date can only ever lead with whatever you published last, so an announcement you wanted at the top had to be written last and lost its place the moment anything else went out. Any post β or a page, which a posts-only front page would never have shown at all β can now be featured, and it sits at the top as though it were the newest thing you had written, whatever is published after it. The pin is a sort, not a rewrite: the real publication date is left untouched, so unfeaturing drops the item straight back into chronological order where it belongs. One item is featured at a time, and choosing another simply sets the first aside β still listed, still yours, back with a single click. Give it a featured image and Stretch image spanning page and it becomes the front-page hero, with an optional Featured badge the core draws over whichever theme you are running, so nothing in your theme has to be modified to get it. Also new in v2.17: your writing is never lost, and never published before you mean it. EaglePress now keeps a history of every post. Auto-save writes into that history instead of into the live page, so you can rework a published article at your own pace while readers carry on seeing the version you last chose to publish β and step back to any earlier draft whenever you want one. Also included since v2.16: code blocks arrive beautifully highlighted the moment a reader lands on the page, in the colour scheme your site actually chose; auto-save now notices everything you change, from the excerpt and tags to categories and the featured image; and a published post keeps its web address, so polishing a headline never moves the page or breaks the links pointing at it. And since v2.15, a full web installer is part of EaglePress itself. Not a companion script to fetch, not a setup file to download alongside it β the installer ships inside every copy of EaglePress and is simply there, waiting, the first time you open the site. Unzip EaglePress onto your server, visit it in any browser, and it introduces itself: it speaks to you in your own language from a list of 135, creates your PostgreSQL database for you, proves the connection works before it trusts it, and helps you build your own administrator account. Minutes from download to your first post, without ever leaving the browser you already had open β and it has been a permanent, first-class component of the platform ever since. One PostgreSQL database can host as many independent EaglePress sites as you like, each kept cleanly apart by its own table prefix.
πͺ Installs In Your Browser β Three Questions and You Are Publishing
Unzip it onto your server, open the site, and EaglePress installs itself. A three-step wizard takes over in your browser: choose your language, hand it your database details, create your account. It builds the database, writes its own configuration, prepares your upload folders and language tables, and leaves you standing in the Dashboard of a working site with your name on it. Start to finish in a few minutes, in the browser you already had open.
1οΈβ£ Pick your language β all 135 of them, each written in its own script:
νκ΅μ΄ Β· Ψ§ΩΨΉΨ±Ψ¨ΩΨ© Β· ΰ€Ήΰ€Ώΰ€¨ΰ₯ΰ€¦ΰ₯ Β· δΈζ Β· EspaΓ±ol Β· PortuguΓͺs Β· Π ΡΡΡΠΊΠΈΠΉ Β· ζ₯ζ¬θͺ Β· Χ’ΧΧ¨ΧΧͺ Β· Polski
Every one of them written the way its readers write it, so you find yours by recognising it. Thirteen ship fully translated and work the moment you choose one; the rest render in English phrase by phrase until you translate them, so they are useful from the first day and improve as you go. Your choice becomes the site default and is switched on for you.
2οΈβ£ Give it your PostgreSQL details β and it does the rest:
- ποΈ Creates the database for you β hand it a username and password and EaglePress does the rest itself
- π Test database connection β a button that genuinely connects, reports your PostgreSQL server version back, and tells you exactly what it found
- π§Ύ Site title, tagline and address, so your site knows its own name before you ever reach the Dashboard
- ποΈ One database, many sites β install alongside other EaglePress sites with a table prefix, and it refuses to install over an existing one
3οΈβ£ Create your account β your administrator username, password and email, hashed with PBKDF2-HMAC-SHA256 at 310,000 iterations.
π Your credentials are yours from the first second. Every EaglePress site starts life with an administrator only you know, chosen by you during installation β so there is nothing published, guessable or shared to find. The installer asks you to write it down and tells you plainly why: your password is stored scrambled, and even EaglePress cannot read it back.
And it looks after you while you do it:
π It checks your connection before you type a password. You are about to enter a database password and choose an administrator password, so EaglePress looks at how you arrived first. Reached over HTTPS, it gets straight on with the job. Reached over plain
http://on a public server, it tells you up front, while you can still fix it β and onlocalhostit recognises a private connection and stays out of your way.
π It earns the right to ask before it asks. The very first thing EaglePress does is confirm it can write its own configuration. If permissions need adjusting it tells you immediately and precisely β the file, its owner, the account your web server runs as, and the exact
chownandchmodthat will settle it β so you fix it in one command and begin with everything already in place.
β’ β’ β’
π Translations note: the multi-language system is server-side and needs one extra pip package β
pip install pyquery. EaglePress runs perfectly without it (the core still depends only onpsycopg2); installpyqueryonly if you want the 13 built-in / 122 opt-in language translations. The installer checks for it and shows you the exact command if it is missing β it never blocks the install.
SHA-512 hash of EaglePress v2.18:
running
sha512sum eaglepress.zip
shows
ccf7937853ab057cda27c38876fddbd8af6d24e44d9f8662007b1b2ea27012de14c5d3e0139f1a0eab133e1fad5d399823589a51e0f38768d10f7037cfad3d8e
β’ β’ β’
example of a Poll_Vote plugin from EaglePress marketplace β notice the map of the United States displaying votes by choice and by state totals
β’ β’ β’
π¦ Welcome to EaglePress

EaglePress is a self-hosted, open-source CMS and blog platform built entirely from scratch in Python 3 backed by PostgreSQL. There is no Django, Flask, or any web framework underneath β just a single CGI router, clean SQL, and a theme engine that gives you full control over every rendered pixel. It runs on any Apache or Nginx server with CGI support, installs in minutes, and ships in a zip file under 1 MB.
β’ β’ β’
π¦ Why EaglePress?
Most open-source CMS platforms carry enormous dependency trees, opaque plugin hooks, and years of legacy compatibility debt. EaglePress is the opposite: a lean, readable, auditable codebase where every feature was written deliberately, every SQL query is parameterized, and every line of code belongs to you.
- π¦ One dependency β only
psycopg2. Everything else is Python standard library. (Optional: addpyqueryβpip install pyqueryβ only if you want the multi-language translation system.) - π Fully readable β the entire router lives in
index.py. No magic, no metaprogramming. - π‘οΈ Security by design β 100% parameterized SQL, PBKDF2-HMAC-SHA256 passwords, XSS escaping throughout.
- β‘ Fast installs β one SQL file + one Python script and you're live.
- π¨ Theme freedom β drop a directory into
themes/and you have a new theme.
β’ β’ β’
β¨ Feature Highlights
π Choose What Leads Your Front Page β New in v2.18
Your front page has always shown newest first. Now you decide what sits at the top of it.
Feature any post β or any page. Open something you have written, tick Feature This Post, and it moves to the top of your front page as though it were the newest thing you had published. Publish ten more posts afterwards and it stays exactly where you put it, for as long as you want it there.
Your publication dates are never touched. Featuring changes the order and nothing else, so the day a post went out stays the day it went out. Unfeature it and it drops straight back into its proper place in the timeline β nothing to undo, nothing rewritten, no dates to repair.
One at a time, and always clear which one. Feature something else and the previous one steps aside, marked Deactivated in a list that keeps it β Make Active puts it back in a single click. Remove from list clears it out of that list only; the post or page itself is never deleted and stays under Posts or Pages exactly as it was.
Pages can lead as well. A page normally never appears in your blog listing at all. Feature one and it does β useful for an announcement, an opening date, or anything that deserves the front page without being a blog post.
Give it the full-width treatment. Add a Featured Image and tick Stretch image spanning page, and your featured item becomes the front-page hero, spanning the page edge to edge.
β An optional badge for readers. Switch on Show a Featured badge to readers and a small badge marks the item on your front page, so visitors can see it leads by choice rather than by date. It is drawn over whichever theme you are using β no theme is modified β and only one item carries it at a time.
Where to find it. A Featured entry in the admin sidebar, directly under Drafts, lists everything you have ever featured with its state at a glance, and the same list sits on your dashboard beneath the Drafts table.
β’ β’ β’
β Plugins and Themes Now Tell You What They Need
Every plugin and theme declares the minimum EaglePress version it works with. That has always been in the manifest β now it is actually checked. A plugin that needs a newer EaglePress than the one you are running shows β Needs v2.16 in the marketplace, so you know exactly what it wants while you are still browsing β and everything offered with an Install button is guaranteed to run on what you have.
β’ β’ β’
π A History of Everything You Write β New in v2.17
Your work is kept as you go, and your published page stays exactly as readers left it.
Every post now carries a history. As you write, EaglePress quietly stores versions of your work; press Save and that version is marked as the one you chose to publish. Both kinds sit together in a Revision History panel beside the editor β newest first, each showing when it was written, how long it is and who wrote it, with a preview of how it opened.
Rework a live article at your own pace. While you edit a published post your readers keep seeing the version you last published. Your changes accumulate safely in the history and go live the moment you press Save β so a half-written sentence is never on the page, and you can leave an article half-reworked for a week without anyone noticing.
Step back whenever you want to. Pick any version in the panel and it opens in the editor, exactly as it was. Read it, keep what you like, and press Save when you are happy β restoring puts the words back in front of you and leaves the decision to you.
Pick a version the quick way. A dropdown lists every version you have β newest first, each showing whether you saved it or EaglePress kept it for you, how long ago, and who wrote it. Choose one and it tells you exactly what it is: the time it was written, its length, and how it opened. Press Load this version into the editor and it appears in front of you, ready to read.
Choosing from the list changes nothing on its own β only the button does β so you can browse freely without ever putting your current writing at risk. And because loading is not saving, your readers keep seeing the published article until you press Save.
βοΈ You choose how much to keep. Settings offers the last 5, 10, 20 or 50 versions, or every one ever written. Ten is the default and suits most writing; older versions beyond your choice are cleared away for you.
β’ β’ β’
π¨ Code That Arrives Beautifully Highlighted β New in v2.16
Code on the published page now looks the way it did when you wrote it β in the colours you chose, from the first paint.
The colours you pick are the colours readers get. Set a block to Monokai, Nord, Xcode, Kate or any of the ten built-in schemes and that is exactly what appears on the published page. The stylesheet is resolved at render time through theme_css_url(), so your choice travels with the block β through the core and all eight themes alike.
And every block is coloured the moment the page loads. EaglePress runs two highlighting passes on each page β one from the theme, one from the content script β and they now cooperate: whichever gets there first does the work, and the second leaves it standing. Previously the second pass rebuilt the block's text before handing it to highlight.js, which erased what the first pass had drawn.
It showed up on blocks containing an English contraction β it's, wasn't, the file's name β since EaglePress adjusts that text so an apostrophe is read as punctuation rather than the start of a string. A Bash script with a chatty comment was the classic case. Those blocks now highlight exactly like every other, first time, every time.
β’ β’ β’
πΎ Auto-Save That Notices Everything You Change β New in v2.16
Auto-save now watches every field it saves. Adjust your excerpt, add tags, edit the slug, tick a category, or change the featured image, and the countdown starts exactly as it does when you type in the body β with β‘ Smart picking it up two seconds later and telling you the moment it is safely stored.
Previously it listened only to the title and the body, while quietly sending thirteen fields to the server, so a carefully written excerpt could sit unsaved while the editor looked perfectly content.
π‘οΈ Your post's status is still yours alone to change. Selecting Published in the dropdown never triggers an auto-save, so a post goes live when you press the button and not a moment before.
β’ β’ β’
π Your Published Links Stay Put β New in v2.16
Once a post is published, its web address belongs to your readers. Polish the headline as often as you like and the link keeps working β the bookmarks people saved, the posts other sites linked to, and everything search engines have already indexed all continue to point exactly where they did.
You stay in charge of it: type a different slug, or switch how slugs are made, and EaglePress moves the post to the address you asked for. Drafts still follow their titles freely while you are still shaping them, and publishing is the moment the address is settled.
βοΈ Why it matters with auto-save on. Auto-save writes for you every couple of seconds, so an address tied to the headline could move while you were mid-sentence. Now it holds steady, and only a deliberate change moves it.
β’ β’ β’
ποΈ Several Sites, One Database β New in v2.15
A single PostgreSQL database can now host as many EaglePress sites as you like, each completely independent. The installer asks the question in plain language:
- Just this one site β tables are named
ep_posts,ep_usersand so on. Simplest, and what almost everyone wants. - Shared with other EaglePress sites β each site gets its own table prefix, so the second site uses
ep2_posts, the thirdep3_posts, and none of them can see or touch another's content.
Press Test database connection and it inspects the database, tells you what is already using a prefix, and offers a free one at the touch of a button. It will not install over an existing site β if it finds tables using your chosen prefix it stops, lists them, and asks you to choose another.
The clever part is that nothing else had to change. Themes and plugins keep writing ep_posts exactly as before; the prefix is applied in one place, as the query is handed to PostgreSQL. Every theme ever written for EaglePress keeps working unmodified.
π Plugin authors: your own tables need the prefix too β
_table('my_votes')gives youep2_my_votes. It is available inplugin.py,install.pyanduninstall.py, andplugins/readme.txtdocuments it with a worked migration for a plugin that already shipped without one. The bundled Polling & Voting plugin has been updated and renames your existing tables, so every vote you have already collected comes with you.
β’ β’ β’
π One-Click Automatic Updates
The Distribution Manager and Auto-Update System make upgrades completely effortless. The Dashboard displays a live version-check banner any time a new release is available. One click downloads the new release from eaglepress.org, verifies the SHA-512 checksum, extracts the archive, deploys all files, and runs any pending database migrations automatically β no SSH session, no manual file copying, no downtime.
β’ β’ β’
π¨ Theme Marketplace with Live Previews
Install and switch themes entirely from the admin panel. Dashboard β Themes β Browse Repository lists every available theme with:
- πΌοΈ Live preview thumbnail images β see exactly how the theme looks before installing
- β Star ratings β community-rated quality scores
- π₯ Download counts β see how popular each theme is
- π One-click install and uninstall β no file management required
Five themes bundled in every install:
| Theme | Style |
|---|---|
| π― firstTheme | Clean, minimal blog layout β ideal starting point |
| π° secondTheme | Two-column editorial style with sidebar |
| πΊπΈ americanTheme | Patriotic accent colors with bold typography |
| π΄ USAtheme | Red-dominant americanTheme variant |
| ποΈ NewsTheme | News magazine layout β breaking-news ticker, hero card, 3-column post grid, right sidebar, 8-swatch color picker, full dark mode |
Available in the marketplace:
| Theme | Style |
|---|---|
| π ModernTheme | Bold magazine/news theme β full-width image carousel, trending ticker, 2Γ2 sidebar grid, 8 switchable color palettes, dark mode |
β’ β’ β’
π Plugin Marketplace β Polling & Voting States
One-click plugin installs from Dashboard β Plugins β Browse Repository. Plugin assets load on every theme β including third-party marketplace themes β because EaglePress injects plugin CSS and JavaScript centrally via index.py, never relying on theme templates to know about plugins.
π Polling & Voting States β the first published EaglePress plugin
Embed live, interactive polls anywhere in a post or page using a single copy-paste shortcode block.
Core features:
| Feature | Description |
|---|---|
| π³οΈ Multiple question types | yes/no, multiple choice, and open-ended polls β mix types in the same post |
| πΊοΈ United States vote map | Results visualized on a color-coded SVG map of all 50 states β per-choice and total vote counts per state |
| π Real-time vote counters | Tallies update live without a page reload |
| π Copy-paste shortcode | The π Poll toolbar button inserts the block β no syntax to memorize |
| ποΈ Delete question | Remove individual poll blocks without affecting others |
| π₯ Download counter | Optional per-poll download tracking |
| ποΈ Accurate admin preview | _epPreviewTransforms hook injects temporary data so the Preview tab matches the live result, even before saving |
| β‘ jQuery 4.0 compatible | All DOM attribute reads use .attr() for full jQuery 4.0 compatibility |
β’ β’ β’
βοΈ Powerful Markdown Editor
- ποΈ Live split-pane Markdown preview β renders as you type, 2-second debounce
- β‘ Smart auto-save β first keystroke creates a draft in the database and assigns a live URL immediately
- π Stale-tab guard (v1.94) β dirty flag + staleness detection prevents old tabs from overwriting your work
- π οΈ Clean installer (v1.95) β fresh-database installs complete without errors
- π Emoji picker β 1,500+ emojis across 10 categories with skin-tone selector and live keyword search
- β Markdown Help β toolbar
?button opens a Quick Reference cheat sheet or full Markdown Tutorial modal - π Custom slug control β title-derived, numeric ID, or fully custom URL slugs per post or page
- πΌοΈ Media library picker β insert images and files directly from the toolbar without leaving the editor
- π Poll toolbar button β one-click shortcode insertion when the Polling plugin is active
- π Word count β live word counter in the editor status bar
β’ β’ β’
π‘ Custom Syntax Highlighting Engine
EaglePress ships a 100% Python-native syntax highlighter with zero CDN or third-party package dependency:
- π 17 languages with score-based auto-detection: Python 3, JavaScript, CSS, HTML, PHP, Ada, Bash, C, C++, Java, Kotlin, Go, Rust, Lua, Perl, SQL, Markdown
- π¨ 13 colour themes: github-dark, atom-one-dark, monokai, dracula, nord, tokyo-night-dark, github, atom-one-light, solarized-light, vs, xcode, Notepad++, Kate (both custom, no CDN)
- π§ Per-block toolbar: language selector, theme selector, word-wrap toggle, copy button
- π·οΈ Inline language hint β force any language with
=python3as the first line of a fenced block
β’ β’ β’
π Security-First Architecture
| Layer | Protection |
|---|---|
| π Passwords | PBKDF2-HMAC-SHA256 β 310,000 iterations (NIST-recommended) |
| π Sessions | 64-character hex tokens, stored in PostgreSQL, 30-day expiry with IP + user-agent logging |
| ποΈ Database | 100% parameterized queries via psycopg2 β zero SQL injection surface |
| π§Ή Output | All user content routed through html.escape() β zero XSS surface |
| π Uploads | Extension + MIME-type whitelist enforced server-side; 10 MB body / 5 MB file hard limits |
| π Error logs | Passwords, tokens, cookies, and DB cursors auto-redacted in all CGI tracebacks |
| π Log permissions | Error log files set to 0o600 β readable only by the server process owner |
β’ β’ β’
π₯ User Roles & Permissions
EaglePress has a five-tier role system giving you fine-grained control over who can do what:
| Role | What They Can Do |
|---|---|
| π Admin | Full access β manage all posts, pages, media, users, settings, themes, plugins, and the Distribution Manager |
| π Editor | Publish and edit all posts and pages regardless of author; manage categories, tags, and comments; access the media library |
| βοΈ Author | Create, edit, and publish their own posts; upload media; manage their profile |
| π¬ Contributor | Write posts and submit them for review; cannot publish directly β an admin or editor must approve |
| π€ Subscriber | Read content, leave comments, manage their own profile and avatar β no content creation access |
Role changes take effect immediately from Admin β Users. Each role restriction is enforced server-side on every AJAX endpoint β client-side UI changes alone cannot bypass them.
β’ β’ β’
π Rich Reader Experience
- π Light/dark mode toggle β OS preference detected on first visit, then user-controlled; zero-flash anti-FOUC inline script
- π Image lightbox with keyboard navigation (arrow keys + Escape)
- π Reading progress bar β smooth scroll-position indicator
- β€οΈ Like button β localStorage-backed, no login required
- π Social share β Copy link, Twitter/X, Facebook
- π¬ Threaded comments with moderation queue
- π€ User profiles with avatar upload and Gravatar fallback
- π Lazy image loading with fade-in scroll animations
β’ β’ β’
βοΈ Admin Panel
- π Dashboard β live stats (posts, pages, comments, media, users), drafts panel, pending-review count, and version-check banner
- π Post & page editor β categories, tags, featured image picker, comment toggle, slug control, status workflow
- πΌοΈ Media library β upload, browse, delete; paginated 24-item grid with thumbnail previews and file-type badges
- π₯ User management β list all users, change roles, delete accounts
- π‘οΈ Comment moderation β approve, mark as spam, or trash with one click
- ποΈ Category management β create, rename, delete; live post counts
- π§ Site settings β auto-save interval, active theme, default slug type, site name, and more
- π¨ Theme manager β installed themes with preview, one-click activate, uninstall
- π Plugin manager β installed plugins with enable/disable toggle
- π¦ Distribution Manager β upload new releases, manage the update feed (eaglepress.org only)
β’ β’ β’
Screen Images of EaglePress
View some images of EaglePress: image1 , image2, image3 from my blog post of EaglePress Screen Images of the One Click Automatic Update
Images of the GUI editor, and others
Images of EaglePress Editor and Dashboard
Configuration of EaglePress
--- how to configuration of Apache2 / Nginx
--- GitHub EaglePress v1.83 <-- version is outdated, though simply update the version if you try it with the one-click automatic update feature to version 2.17.
# EaglePress β Summary Project Press
**Platform:** Python 3 CGI + PostgreSQL Web Publishing System
**Current Version:** 2.18
**Date:** August 2026
---
## What Is EaglePress?
A self-hosted, open-source CMS and blog platform built entirely in Python 3 and PostgreSQL. No web framework. No ORM. Runs on any Apache or Nginx server with CGI support.
---
## Core Highlights
- Pure Python 3 CGI router β zero external framework dependency
- PostgreSQL backend β 10 normalized tables, FK constraints, indexed queries
- Zero-config installer β one SQL file + one Python script sets up everything
- Clean URL routing via `PATH_INFO` with `?r=` fallback
- Full theme system β drop-in theme directories, exec-based renderer
---
## Security
- PBKDF2-HMAC-SHA256 passwords β 310,000 iterations (NIST-recommended)
- Session tokens β 64-character hex, stored in DB, 30-day expiry with IP + user-agent logging
- 100% parameterized queries β no SQL injection surface
- XSS protection β all output routed through `html.escape()`
- Upload safety β extension + MIME-type whitelist, server-side enforced
- CGI errors isolated to `tmp_errors/` β never exposed to visitors
---
## CGI Library β `cgi_improved2.py`
A custom drop-in replacement for Python's deprecated `cgi` and `cgitb` standard library modules, written specifically for EaglePress.
**Parsing architecture:**
- Streaming 64 KB-chunk multipart parser β peak memory scales with the largest single part, not the total body
- Replaces the legacy approach of reading the entire POST body into RAM before parsing
**Hard request limits:**
- 10 MB maximum total POST body
- 5 MB maximum per uploaded file
- 200 maximum form fields per request
- 70-byte maximum boundary length (RFC 2046)
- 1024-byte maximum uploaded filename length
**Security measures built into the library:**
- RFC 5987 filename decoding β handles `filename*=UTF-8''encoded` form
- Charset validation for text parts against a safe-charsets allowlist
- Error log redaction β sensitive local variable names (`password`, `password_hash`, `token`, `session`, `USERNAME`, `PASSWORD`, `cc_number`, database cursors, raw request body) are replaced with `<redacted>` in all traceback logs
- HTTP header redaction β `HTTP_COOKIE`, `HTTP_AUTHORIZATION`, `HTTP_PROXY_AUTHORIZATION` stripped from logged environment dumps
- Log file permissions set to `0o600` β readable only by the server process owner
- Traceback output suppressed from HTTP responses by default (`display=0`)
- Variable repr truncation β log entries capped at 200 characters per value, 30 variables per frame
**Drop-in compatible** β same `cgi.FieldStorage()` / `cgitb.enable()` interface as the standard library.
---
## Content Management
- Posts and pages β draft, published, preview states; soft-delete to trash
- Auto-generated slugs β deduplicated with `-2`, `-3` suffixes
- Categories (hierarchical) and tags
- Featured images, view counter, comment toggle per post
- Comment threading with moderation queue
- User profiles with avatar upload and Gravatar fallback
---
## Markdown Parser
Custom server-side Markdown-to-HTML with no third-party packages:
- ATX headings, bold, italic, strikethrough
- Fenced code blocks with syntax highlighting
- Ordered and unordered lists, blockquotes, horizontal rules
- Extended image syntax β size (`=WxH`), alignment, title
- Download buttons for non-image file links
- Shortcodes for embedding post/page links
- External links auto-tagged `rel="noopener"`
- Raw HTML pass-through
---
## Syntax Highlighting
Custom pure-Python engine (`syntax_highlight.py` + `syntax_themes.py`) β no external packages.
**17 languages** + plain text: Python 3, JavaScript, CSS, HTML, PHP, Ada, Bash, C, C++, Java, Kotlin, Go, Rust, Lua, Perl, SQL, Markdown + HTML
**13 colour themes**: github-dark, atom-one-dark, monokai, dracula, nord, tokyo-night-dark, github, atom-one-light, solarized-light, vs, xcode, Notepad++, Kate
**Per-block features:** language label, language selector drop-down, theme selector drop-down, copy button, keyboard navigation
**Score-based auto-detection** β no lang hint needed; fenced block content stripped before scoring to prevent embedded language examples from polluting outer document detection
---
## Frontend Reader Experience
- Image lightbox with keyboard navigation
- Reading progress bar
- Back-to-top button
- Like button (localStorage)
- Social share β Copy link, Twitter, Facebook
- Lazy image loading + fade-in scroll animations
---
## Admin Panel
- Dashboard with live stats
- Post / page editor with live Markdown preview
- Media library β upload, browse, delete
- User management β five roles: admin, editor, author, contributor, subscriber
- Comment moderation queue
- Category management
- Site settings
---
## User Roles
| Role | Permissions |
|---|---|
| Admin | Full access β all content, all users, all settings, themes, plugins, Distribution Manager |
| Editor | Publish/edit all posts and pages; manage categories, tags, comments; media library |
| Author | Create, edit, and publish own posts; upload media; manage profile |
| Contributor | Write posts, submit for review; cannot publish β requires admin/editor approval |
| Subscriber | Read, comment, manage own profile and avatar |
---
## Theme System
Themes are self-contained directories. The active theme's `theme.py` is exec'd by the router with all page variables pre-populated. Bundled themes: `firstTheme`, `secondTheme`, `americanTheme`, `USAtheme`, `NewsTheme`. Additional themes available via the marketplace (e.g. `ModernTheme` β bold magazine layout with carousel, ticker, 8 color palettes).
---
## Version History (overview)
| Version | Title |
|---|---|
| v1.0 | Foundation Release |
| v1.01 | Theme Expansion |
| v1.02 | Syntax Highlighting Engine |
| v1.03 | Markdown Parser Overhaul |
| v1.04 | SQL Language Support |
| v1.05 | Per-Block Theme Scoping |
| v1.06 | Reader Theme Persistence |
| v1.07 | Frontend Reader Experience |
| v1.08 | Streaming CGI Parser & Security (`cgi_improved2.py`) |
| v1.09 | Project Documentation & Archive Policy |
| v1.10 | Markdown + HTML Language & SQL Detection Fix |
| v1.11 | Markdown Auto-Detection Fix β fenced block stripping |
| v1.12 | Markdown Detection Hardened β ATX heading rule, tilde fence signal |
| v1.13 | highlight.js Markdown Grammar Fix β underscore in identifiers (`highlight_custom_improved.js`) |
| v1.14 | Markdown Highlighting β server-side Python tokenizer in `syntax_highlight.py`; JS grammar `h.regex.concat()` fixed |
| v1.15 | Markdown Highlighting β JS tokenizer in `highlight_custom_improved.js` replaces Python; patches `hljs.highlightElement()` for consistent single code path; all themes now load custom script |
| v1.16 | USAtheme + "Register" β "Create Account" |
| v1.17 | Code Block Word Wrap Toggle |
| v1.18 | Admin Password Change + Theme Preview |
| v1.19 | Firefox Android Word Wrap Fix |
| v1.20 | Custom Arbitrary Slug Mode |
| v1.21 | Dynamic sys.path in AJAX Scripts |
| v1.22 | Database Migration System |
| v1.23 | Image Alignment CSS Fix |
| v1.24 | Post Delete Count Correction |
| v1.25 | Editable Display Name + Profile Author Fix |
| v1.26 | Profile Page Featured Image Fix |
| v1.27 | Light/Dark Mode Toggle + Full Dark Theme |
| v1.28 | Dark Mode Post Title + Category Count Accuracy |
| v1.29 | Profile Page Post Count Accuracy |
| v1.30 | Admin Categories Post Count Accuracy |
| v1.31 | Admin Users Post Count + Posts/Pages/Frontend Comment Count Accuracy |
| v1.32 | Sidebar Tags Cloud Count Accuracy |
| v1.33 | Dashboard Drafts Panel + Configurable Auto-save |
| v1.34 | Syntax Highlighter β Markdown Detection Fix + Inline Language Hint |
| v1.35 | Emoji Picker |
| v1.36 | Emoji Picker Expansion β Skin Tones, Sports, Education & Tech |
| v1.37 | Markdown Italic/Bold Underscore Fix |
| v1.38 | Apostrophe Fix in Code Block Highlighting |
| v1.39 | Emoji Expansion + Markdown Help + Smart Auto-save + Light Mode Default + User Deletion |
| v1.40 | Code Block Markdown Rendering + Theme Fixes |
| v1.41 | NewsTheme + secondTheme LIVE Removal |
| v1.42 | NewsTheme URL Fix + Slug-Type Support |
| v1.43 | NewsTheme Home Page "No Posts" Fix |
| v1.44 | Category Delete + NewsTheme Toggle Icon + Ticker Empty State |
| v1.45 | Auto-Update System β one-click updates, version API, SQL migration runner |
| v1.46 | Update Check Force-Refresh + Comment Newline Preservation |
| v1.47 | Auto-Update: Permissions Fix + VERSION Bump |
| v1.48 | Update Failure Hint Line |
| v1.49 | Auto-Update: Preserve Distribution Zip |
| v1.50 | Media Library Modal: Immediate Upload Refresh |
| v1.51 | Polling Plugin: Map Totals Toggle, Copy Block Button, Delete Question, Download Counter, ep_counters table |
| v1.52 | Plugin & Theme Marketplace: repository system, one-click install, API endpoints, star ratings, likes, comments |
| v1.53 | ModernTheme: bold magazine/news theme with full-width carousel, 2Γ2 sidebar grid, trending ticker, dark mode, 8 color palettes |
| v1.54 | Fix one-click update migrations: run_migrations splits multi-statement SQL files |
| v1.55 | Fix login failure on older installs: 1.41.sql migration adds missing ep_users columns |
| v1.56 | Browse Repository stability: Polling_Voting_States to repository-only; star_avg Decimalβfloat fix |
| v1.57 | Version bump / archive rebuild for server sync |
| v1.58 | Marketplace UI: installed tab count increments on install; interactive star ratings; download count in footer |
| v1.59 | Plugin install migration: 1.53.sql creates ep_plugins table on existing installs |
| v1.60 | Plugin install dynamic card: no page refresh |
| v1.61 | Plugin delete: Installed tab count decrements after card fade-out |
| v1.62 | Plugin delete: restores Browse Repository Install button |
| v1.63 | Theme Browse Repository tab fix; ModernTheme preview.svg added |
| v1.64 | Theme repo cards: interactive star ratings; download count in footer |
| v1.65 | (skipped) |
| v1.66 | Auto-sync repository on Browse tab open: sync_themes_repo + sync_plugins_repo walk repo dirs, upsert DB |
| v1.67 | Consolidated marketplace release: dark mode emoji fix; migration runner PL/pgSQL fix |
| v1.68 | Theme uninstall: ep_theme_uninstall.py; Uninstall button; dynamic install card |
| v1.69 | Theme card has_manifest flag; bundled themes show info icon; apiV1 _api_not_found() helper |
| v1.70 | Fix missing timezone import; ModernTheme login fix |
| v1.71 | Bundled theme cards show author/created info rows; custom CSS tooltip system |
| v1.72 | Theme card tooltip z-index, box-shadow, CSS arrow polish |
| v1.73 | Theme install card preview: correct /themes/1_ModernTheme/preview.svg path |
| v1.74 | Installed marketplace theme cards show download count badge |
| v1.75 | Archive top-level fixed to www/ β one-click updater correctly deploys files |
| v1.76 | Version bump to trigger one-click update on eaglepress.org |
| v1.77 | Download count extended to non-prefixed manifest themes |
| v1.78 | Download count badge uses ep-repo-dl class |
| v1.79 | Remove bundled ModernTheme from CMS archive |
| v1.80 | Fix hardcoded themes/ModernTheme paths β theme works under any installed folder name |
| v1.81 | Theme-Independent Content Rendering β `.ep-prose` universal stylesheet; output buffering injects styles into every theme |
| v1.82 | Distribution Manager Overhaul + One-Click Updater Label |
| v1.83 | Distribution Manager Access Control β `DISTRIBUTION_SERVER` config variable |
| v1.84 | SyntaxWarning Fix β JavaScript regex escape sequences corrected in Python f-strings |
| v1.85 | Graceful DISTRIBUTION_SERVER import β `try/except ImportError` fallback; existing installs no longer crash on update |
| v1.86 | Theme preview images in Browse Repository β `sync_themes_repo` extracts `preview.svg` beside each theme zip; new `themes_repository` fastcgi route serves SVGs via symlink; Browse Repository cards show theme previews |
| v1.87 | Plugin Active folder on fresh installs β `ep_plugin_install.py` creates `plugins/Active/` alongside `plugin_list/` on first plugin install |
| v1.88 | Poll plugin slug detection fix β poll-ID injection now matches numeric-prefixed slugs (e.g. `1_Polling_Voting_States`) |
| v1.89 | Poll editor preview β styled visual mock for unsaved polls in admin Preview tab |
| v1.90 | Bare-name plugin symlink β legacy plugin builds resolve assets without reinstall |
| v1.91 | Plugin URL path patching β bare-name asset URLs auto-rewritten to numbered-slug path |
| v1.92 | `_epPreviewTransforms` hook β plugins inject temporary data into Preview tab before AJAX render |
| v1.93 | Theme-independent plugin loading β plugin CSS/JS injected centrally by `index.py`; jQuery 4.0 auto-injected when absent; `pvs_widget.js` `.attr()` fix |
| v1.94 | Stale-tab auto-save guard β dirty flag + `visibilitychange` staleness check; yellow banner prompts "Fetch latest version" on outdated tabs; auto-save paused until reload |
| v1.95 | Fresh-install fix β `idx_plugins_active` and `idx_plugins_slug` moved in `setup.sql` to after the `ep_plugins` CREATE TABLE; previously caused `relation "ep_plugins" does not exist` on every clean install |
| v1.96 | Rust Syntax Highlighting β Rust language added to score-based auto-detection engine, editor language selector dropdown, and fence hint aliases (`rust`, `rs`); lifetime notation `'a` guarded via `_APOSTROPHE_LANGS` |
| v1.97 | Notepad++ Syntax Theme β custom light theme added to code block theme selector; 34 token rules; f-strings detected via `ep-fstring` JS class; CSS in `styles_content.css` (no CDN) |
| v1.98 | Kate Syntax Theme β Kate editor-inspired light colour theme; KDE KSyntaxHighlighting palette (keywords bold #1f1c1b, strings #bf0303 red, f-strings #ff5500 bold orange, numbers #b08000 ochre, built-ins/functions #644a9b purple, literals/types/variables #0057ae blue); CSS in `styles_content.css` (no CDN) |
| v2.00 | **Reader Defaults** (Dashboard settings for default page theme, code block theme & mode); anti-FOUC dynamic default in all 5 bundled themes; **EaglePressTheme** β deep navy + gold showcase theme with /themes gallery, /plugins directory, theme preview route; **Global Lists** system (ep_update_global_lists.py scans repos β global_theme_list.json + global_plugin_list.json); new /themes, /plugins, /theme-preview/<slug> core routes; _ep_is_distrib_server flag available to all themes |
| v1.99 | Per-Block Light/Dark Mode Toggle β π‘/π
emoji toggle (yellow/red border); 10 unified themes each with light+dark; dual CDN CSS scoping per mode; custom palettes for monokai-light, nord-light, agate-light, xcode-dark, classic-dark, notepadpp-dark, kate-dark; legacy keys auto-migrated; ModernTheme: "Register" β "Create Account", header hover glow effect |
| v2.00 | **Major release.** Reader Defaults settings card β three new site settings: Default Page Theme (light/dark), Default Code Block Theme (syntax theme key), Default Code Block Mode (light/dark); applied server-side so every new page/code block starts with the Dashboard-chosen defaults instead of always light/GitHub. **Anti-FOUC dynamic default** β all 5 bundled themes (firstTheme, secondTheme, americanTheme, USAtheme, NewsTheme) + admin panel now use `_settings.get('default_page_theme','light')` in their inline `<script>` anti-FOUC tag instead of hardcoded `'light'`. **New core routes** β `/themes` β theme showcase (distribution server) or redirect page to eaglepress.org/themes (other installs); `/plugins` β plugin directory (distribution server) or redirect to eaglepress.org/plugins; `/theme-preview/<slug>/post/<post-slug>` β execs any installed or repo theme with a live post as preview content. **EaglePressTheme** β new installable showcase theme with deep navy + gold colour scheme (Playfair Display + Inter fonts); home page with hero post + card grid + sidebar; `/themes` gallery with preview image, version badge, Download + Preview buttons; `/plugins` directory; Marketplace nav group (ποΈ Marketplace + π¨ Themes + π Plugins) with gold border, before Home; notice banner on non-distribution installs; standard page types; dark mode; full syntax highlighting; responsive; published to `themes_repository/2026/05/27/2_EaglePressTheme.zip`. **Global Lists system** β `global_theme_list.json` and `global_plugin_list.json`; `ep_update_global_lists.py` AJAX endpoint; "Update Global Lists" button in Distribution Manager. **Distribution-server flag** β `_ep_is_distrib_server` boolean. |
| v2.01 | **Go language support** (14th canonical language, 16-signal auto-detect, `~~~go`/`~~~golang`). **EaglePressTheme mobile hamburger redesigned** (proper dropdown below header; π¨ Themes + π Plugins as accessible links). **Code block toolbar mobile** (π» emoji + tap tooltip replaces lang label; β΅ replaces Wrap). **Smart updater fingerprinting** (SHA-512 + zip_mtime detection; content_updated state; installed SHA shown on Dashboard). **RedWhiteBlueTheme** bundled (navy/crimson/gold, stars, dark mode, β/π toggle). **EaglePressTheme v1.1** (preview.svg corrected β EaglePress.org inline logo, widened marketplace border). |
| v2.02 | **Java language support** β Java added as a canonical language; score-based auto-detection (14 weighted signals: `public static void main(String`, `System.out.println`, `import java.β¦;`, dotted `package β¦;`, annotations like `@Override`, access-modified declarations, `extends`/`implements`, generics, `throws β¦Exception`); fence hint `~~~java`; ATX-heading penalty. **Go detection tightened** β strong `package` signal now requires the bare `package main` form so Java's dotted `package com.foo.bar;` is no longer misread as Go. |
| v2.03 | **Code block line numbers** β every code block can show a line-number gutter, toggled per block from a new control in the toolbar right of the language indicator (desktop: a "Line numbers" checkbox; mobile: a `#οΈβ£` button); sticky right-aligned gutter, numbers rendered server-side, state remembered per block. **Reader Default** β new "Default Code Block Line Numbers" (Off/On) in Dashboard β Settings β Reader Defaults. **Mobile toolbar overhaul** β controls no longer overlap; single left-aligned row that scrolls sideways with a visible scrollbar (order: π» language Β· `#οΈβ£` line numbers Β· β΅ wrap Β· π‘ light/dark Β· theme Β· language format); word wrap is a grey-bordered β΅ box; a vertical divider sets the theme controls apart; tapping any toggle shows a state tooltip that auto-fades. |
| v2.04 | **Code block line numbers β slight change.** Line numbers now render one row per source line, so they stay aligned on wrapped and tall lines and work together with word wrap. The `---` separator (a horizontal line or `β’ β’ β’` dots) shows correctly with line numbers on, evenly spaced. On mobile the word-wrap button border now matches the code theme β white in dark mode, black in light mode. |
| v2.05 | **Language Translations and Polish (13th language) + translated marketplace, theme & logout fixes, duplicate-theme cleanup.** **Polish** added as the 13th default-enabled language and brought to full parity; the reader/front-end UI is now translated across **all 122 opt-in languages**, with **109 languages at full admin + front-end parity** (machine-generated, flagged for native review). **Marketplace translated** β the admin Themes/Plugins manager (sort dropdown, Pages/Infinite view toggle, pagination, search, loading) and the public `/themes` & `/plugins` showcase gallery (hero headings, stat blocks, card labels β By, Download, Preview, Source) now localize via the i18n engine. **Featured-image hero fix** β five themes (USAtheme, RedWhiteBlueTheme, EaglePressTheme, americanTheme, firstTheme) wrapped the hero image URL in double quotes inside a double-quoted `style` attribute, closing it early so the image never loaded; switched to single-quoted `url('β¦')`. **Nice logout page** β new `/logout` route renders a styled "You've been signed out" confirmation (animated check, draining countdown, light/dark aware) and destroys the session, instead of dumping the raw JSON of `ajax/www_logout.py`; the admin Dashboard and all 8 themes route there. **Duplicate theme/plugin cleanup** β the Dashboard lists collapse duplicate cards (bundled `Name` + numeric-prefixed marketplace `<repo_id>_Name`) and the updater prunes the redundant prefixed folder when a bundled twin exists β never the active theme or active plugins β so duplicates stop reappearing after an update. |
| v2.06 | **Post image display polish β show the whole picture, no clipping (all 8 themes).** Post images were being cropped along the **top** edge in listings and on the front page. Resolved per element type. **Post-listing cards β full image:** the smaller post cards keep their uniform `16/9` box but switched from `object-fit: cover` to **`object-fit: contain`** (centered), so the entire card image is shown; the card container's themed `background-color` fills any letterbox area (a `background` was added to NewsTheme and ModernTheme card containers that lacked one). **Single-post featured/hero `<img>` and sidebar/latest/recent/small thumbnails:** kept `object-fit: cover` but anchored with **`object-position: top center;`** so the crop falls at the bottom and the top stays visible; **avatars stay centered**. **Homepage `background-image` heroes (firstTheme, americanTheme, USAtheme, RedWhiteBlueTheme, EaglePressTheme):** because `object-fit` doesn't apply to CSS backgrounds, these switched from `background-size: cover` to **`background-size: contain`** (centered, no-repeat, themed `background-color` letterbox) so the full hero image shows. Bundled theme zips (EaglePressTheme, ModernTheme) updated to match. |
| v2.07 | **"Stretch image spanning page" β per-post hero toggle (core feature).** A new checkbox in the post editor's Featured Image box (under "Display in article") lets each post choose how it appears as the front-page main image: **off (default)** keeps the v2.06 whole-image (`contain`) look; **on** stretches the hero **full-bleed edge-to-edge** across the page (`background-size: cover`), like pre-v2.06. The per-post flag (`ep_posts.stretch_featured_image`) is applied **entirely in the core** β `index.py` injects a tiny `<style>` for the home hero at the same theme-independent layer that injects shared content CSS/plugin assets, so **no theme files change** and third-party themes reusing the `.ep-hero`/`.rwb-hero`/`.ept-hero` classes are covered too. Also fixed: the editor save/autosave now persists `show_featured_image` (the "Display in article" toggle) alongside the new flag. Migration `sql/migrations/2.07.sql` adds the column on existing installs via the one-click updater. |
| v2.08 | **Kotlin syntax-highlighting support (core code feature).** Kotlin is now a first-class code-block language β it shows up in the language combo box on every fenced and inline code block and in the admin editor's picker, the ` ```kotlin ` / ` ```kt ` / ` ```kts ` fence hints resolve to it, and untagged snippets are auto-detected via new scoring in `detect_language` (keyed on `fun`, `val`, `data class`, `companion object`, `when {β¦}`, the Elvis operator `?:`, `import kotlin.*`, `listOf(β¦)`, nullable types like `String?`) without stealing detection from Java/JavaScript. Highlighting is rendered by the already-loaded highlight.js 11.9.0 **common** bundle (which ships Kotlin), so the block emits `<code class="language-kotlin">` with **no new library**. Done **entirely in the shared core** (`www/lib/syntax_highlight.py`) β **no theme files change** and **no database migration**. |
| v2.09 | **Mobile two-line header when the language combo box is on (all 8 themes) + ModernTheme menu fix.** With translations enabled (`pyquery` installed β the language switcher renders), the switcher was too wide to fit beside the logo and toggles on phones. Now, on mobile only and only when the combo box is present, the header wraps to two lines β **line 1: logo + language combo; line 2: right-aligned dark/light, search and menu (login / create account / admin) toggles.** Scoped with the CSS `:has()` selector so desktop and combo-off sites are unchanged. **ModernTheme fix:** the mobile menu links were invisible when toggled β the opened menu is now a full-width dropdown anchored below the navbar. **EaglePressTheme accent bar:** the second line, previously a transparent gap, is now a full-width **navy + gold accent bar** holding the toggles. **Mobile menu drop-position fix (firstTheme, RedWhiteBlueTheme, EaglePressTheme):** the hamburger menu was opening *over* the toggle button on the taller two-line header; it now drops cleanly **below** the header (`top: 100%`). Per-theme CSS only β **no database migration**. The two marketplace theme zips (ModernTheme, EaglePressTheme) were updated. |
| v2.10 | **Featured image in the article shows the whole picture (all 8 themes) + it now appears in the editor Preview.** The in-article featured image (the "Display in article" image) used `object-fit: cover`, cropping the bottom of taller images. It now uses **`object-fit: contain`** (centered) so the **whole image shows β nothing clipped top or bottom** β with a **25px top margin**. On RedWhiteBlueTheme and EaglePressTheme the image also used a negative-margin full-bleed with rounded top corners that clipped the top; converted to a clean **25px inset** card. **Editor Preview tab:** the live Preview now shows the featured image at the top (whole image) when the **"Display in article"** toggle is checked (and updates live as you toggle), so it matches where the image lands in the article. **Uninstall fix:** RedWhiteBlueTheme is a bundled **core** theme but had a `manifest.json` that made the admin show it an Uninstall button β removed, so only the marketplace/zip themes (EaglePressTheme, ModernTheme) are uninstallable; the uninstall endpoint also now refuses bundled core themes. Per-theme CSS + core `app_core.js`/`styles_core.css`/`ep_theme_uninstall.py` β **no database migration**. The two marketplace theme zips (ModernTheme, EaglePressTheme) were rebuilt to match and must be uploaded to the server's `themes_repository` manually. |
| v2.11 | **Ada syntax-highlighting support (core code feature).** Ada is now a first-class code-block language β it appears in the language combo box on every fenced and inline code block and in the admin editor's picker, positioned **right after PHP and before Bash**, the ` ```ada ` / ` ```adb ` / ` ```ads ` fence hints resolve to it, and untagged Ada is auto-detected via new scoring in `detect_language` (keyed on `with Ada.β¦`, `Ada.<Unit>`, `Put_Line`/`Get_Line`, `procedure Name is`, `function β¦ return β¦ is`, `package [body] Name is`, `when others =>`, `end Name;`/`end loop;`, attribute refs like `Integer'Image`) β all anchored to structural Ada syntax so it doesn't steal detection from Go (`:=`), Lua/JavaScript (`function β¦`), SQL/Lua (`--`) or Markdown prose. highlight.js's **common** bundle ships no Ada grammar, so a self-contained Ada grammar was registered in `highlight_custom_improved.js` (case-insensitive keywords, `--` comments, strings with `""` escapes, `'x'` char literals, based/decimal numbers, `'Attribute` refs) so both auto-detected and hand-picked Ada blocks colourise with **no new library**. Done **entirely in the shared core** (`www/lib/syntax_highlight.py` + the shared `highlight_custom_improved.js`) β **no theme files change** and **no database migration**. |
| v2.12 | **Kate dark-mode syntax-highlighting readability (all languages).** In the **Kate** syntax theme's **dark mode**, keywords were the same grey as the body text (bold only), so keyword-heavy code (Ada `procedure`/`is`/`begin`/`end`, Python `def`/`class`/`return`) looked nearly unhighlighted, and several tokens fell below WCAG-AA contrast on Kate's dark `#232629` background. Keeping the authentic Kate/Breeze-Dark look (keywords stay hueless, just brighter), the palette was tuned so **every code colour now clears AA (β₯ 4.5:1)**: keywords `#cfcfc2`β**`#f4f4ef`** (near-white bold), literals `True`/`False`/`None` `#2980b9`β**`#3daee9`**, comments/quotes `#7a7c7d`β**`#8f9398`**, doctags `#607880`β**`#8aa7ae`**, strings/attrs/code/deletions `#f44f4f`β**`#f96565`**, function-names/built-ins/selector-class `#b06ece`β**`#c281dd`** (also brightens Ada attribute refs like `Integer'Image`). Shared-core `.hljs-*` rules in `styles_content.css`, so it improves **every language at once** on both the reader and the admin editor; **Kate light mode and all other syntax themes are unchanged**. Core-only β **no theme files change** and **no database migration**. |
| v2.13 | **New Post always opens blank + auto-saved versions now land in Drafts.** Clicking **New Post** / **New Page** kept showing **"Draft restored from auto-save"** and pre-filling the editor with text from an unrelated, already-finished post. Cause: an unsaved post has no id, so the auto-save `localStorage` key was the shared literal `ep_autosave_<type>_new`, and that entry was never reliably removed β smart auto-save *switched* `EP.autoSave.key` to `ep_autosave_<type>_<id>` once the post existed server-side but left the `β¦_new` entry behind, the manual save handler set `#ep-post-id` without updating the key at all, and `restore()` had no expiry. The rule is now unconditional: **the New Post form never restores anything.** Two further causes had to be found before it held. First, the new-vs-existing test read the *live* `$('#ep-post-id').val()`; browsers restore form state β *including hidden inputs* β when you return to a page in the same tab, so a page the server rendered blank could be holding the previous post's id, which also meant "New Post" could auto-save **on top of an existing post**. That test now reads the hidden id's **`defaultValue`** (the server-rendered `value=""` attribute), which restoration cannot touch. Second, and decisively: **restoration happens *after* the editor clears the form** β `EP.editor.init()` runs at `$(document).ready`, while the browser restores form state after DOMContentLoaded and again on bfcache back/forward β so every earlier fix was correct and then silently undone a moment later (confirmed in the field: the deployed script was byte-identical to the build and the content still appeared). Fixed with **`autocomplete="off"`** on both editor forms and their id/title/content/excerpt fields (`index.py:2160`, `:3635` β server-side, so no script timing can undo it) plus a **`pageshow` re-clean** that runs after `load` and after bfcache restore, guarded by a `touched` flag so it can only ever remove restored text, never anything typed. `startClean()` now resets **every control in the form** to its server-rendered state β `defaultValue` for inputs/textareas, `defaultChecked` for checkboxes/radios, `defaultSelected` for selects β so tags, status, slug-type radios, the featured-image toggles and the category checkboxes are cleared too, not just the text fields; new `purge()` sweeps `localStorage` of both `β¦_new` slots and every unstamped or expired entry; `restore()` is reached only for a post the server rendered with an id. Supporting fixes: **`keyFor()` / `rotateKey()`** so the key follows the post and the `β¦_new` entry is dropped the moment an id is assigned (from **both** save paths), plus a **`savedAt` timestamp** and **24-hour `maxAge`** so stale local entries self-clear. **Recovery moved to the server:** previously only **β‘ Smart (2s)** saved to the database while the timed settings (5 s β¦ 10 min) wrote to `localStorage` only β every interval now calls the same `saveDraft()`, so auto-saved versions appear in **Dashboard β Drafts** in every mode and are recoverable from any browser (dirty-tracking lifted out of the smart-mode branch so an idle editor still never posts; the `localStorage`-only `save()` removed). **Two bugs found and fixed along the way:** auto-save hard-coded `status: 'draft'` on every call, which `www_post_save.py` writes straight to `ep_posts` β so auto-saving a **published** post silently **unpublished** it and knocked a **pending** submission back to draft (an existing post now keeps its own status); and both save paths rewrote the URL to `/admin/edit/<id>` even in the user dashboard, an address an author cannot load, so a reload locked them out of their own post (new `EP.editUrl()` resolves `/user/edit/<id>` in user mode). Both editors share `app_core.js`, so both are fixed. Core-only (`www/html/pyEaglePress/js/app_core.js`) β **no theme files change** and **no database migration**. β οΈ `/js/app_core.js` carries no version query string and nginx sets `expires 30d` on `.js`, so a **cache purge / `Ctrl+Shift+R`** is required for the fix to reach browsers. |
| v2.14 | **Cache-busted CSS/JS across the core and all 8 themes.** v2.13 exposed the underlying problem: assets were referenced with bare URLs (`/js/app_core.js`) and served with a long cache lifetime, so after an update a browser could keep running the **previous** file β deploying v2.13 required manually forcing a re-download on the front page *and* separately on `/admin`. A new `_core_asset()` helper in `index.py` now appends each file's **mtime** to its URL (`/js/app_core.js?v=1785358900`), so the URL changes whenever the *file* changes β a hotfix deployed without a `VERSION` bump still busts the cache, which a `VERSION`-only stamp would not have done. The helper is per-request memoised and falls back to `?v={VERSION}` if a file cannot be stat'd, so a missing asset degrades instead of raising. It is deliberately named `_core_asset`, not `_asset`: themes are `exec`'d in `index.py`'s namespace and **ModernTheme already defines its own `_asset()`**, which would have silently overwritten a generically-named helper β and since the core's `styles_content.css` / `highlight_custom_improved.js` / `ep_content.js` injection runs *after* the theme executes, those three would then have been rewritten to `/themes/ModernTheme/β¦`, breaking content styling and highlighting on that theme alone. **32 references updated:** 11 in `index.py` (admin header, user-dashboard header, and the theme-independent `</head>`/`</body>` injection block, the latter converted from plain concatenation to f-strings), 19 across the 7 literal-path themes (each theme's own `styles.css` / `app.js` plus the shared core files they reference), and 2 in ModernTheme via its existing `_asset()`, which now delegates to `_core_asset()`. All 21 distinct asset paths were verified to resolve to real files and stamp with a real mtime β none hit the fallback. The two marketplace theme zips (ModernTheme, EaglePressTheme) in `themes_repository` were rebuilt to match and **must be uploaded to the server's `themes_repository` manually**. No database migration. βΉοΈ Deploying v2.14 itself still needs one hard refresh (the browser must fetch the new HTML carrying the stamped URLs); after that, updated CSS/JS is picked up automatically. |
| v2.15 | **EaglePress installs in your browser, and several sites can share one database.** Installing meant editing a credentials file by hand and running `sql/install.py` from a shell β without a terminal you could not install it at all. Now a **three-step wizard** does it: pick your language from all **135** in their own script, hand it your PostgreSQL details, name the site, create your account. It **creates the database for you**, has a **Test Connection** button that really connects and reports your server version, **warns you when the page is not encrypted** (you are about to type two passwords), and **checks it can write `config.py` before asking you anything** β naming the file, its owner, the web server's user and the exact `chown`/`chmod` if it cannot. **There are no default credentials any more:** the old `admin` / `admin123` is gone, you choose your own, and the installer tells you to write them down and why. `INSTALLATION_STATUS` in `config.py` is the only switch β delete the word `success` and the installer runs again; no sample file to rename. **A single database can now host as many EaglePress sites as you like,** kept apart by a **table prefix** (`ep_`, `ep2_`, `ep3_`), asked as a plain question rather than a technical field. It refuses to install over an existing site, listing what it found and offering a free prefix. Nothing else had to change: themes and plugins keep writing `ep_posts` and the prefix is applied in one place as the query reaches PostgreSQL, so **every theme ever written for EaglePress keeps working unmodified**. Plugin authors get `_table('name')` for their own tables, documented in `plugins/readme.txt`; the bundled **Polling & Voting** plugin was updated and **renames** existing tables rather than stranding your data. And **`min_eaglepress_version` is finally enforced** β every plugin and theme has always declared one and nothing ever checked it, so a plugin needing a newer EaglePress was offered, downloaded, and failed at activation; the marketplace now shows **β Needs v2.16** in place of Install. **Upgrading is safe and needs nothing from you:** `TABLE_PREFIX` defaults to `ep_`, which produces byte-identical SQL to v2.14, and there is no database migration. |
| v2.16 | **Syntax highlighting that arrives right, every time.** Every theme named a single highlight.js stylesheet directly in the page (`github-dark`) regardless of the syntax theme the site or the block was set to, so a block set to Monokai or Nord was styled by whatever that hard-coded sheet said; the stylesheet now resolves through `theme_css_url()` across the core and all eight themes. Separately, **some published blocks arrived with no highlighting at all** and lit up only when the language selector was re-picked. highlight.js refuses to re-highlight an element carrying `data-highlighted`, and EaglePress runs two passes per page β `EP.syntaxHighlight()` from the theme and `_doInit()` from `ep_content.js`. The second pass rewrote `el.textContent` **before** calling highlight.js (the guard that stops an apostrophe in `it's` being read as a string delimiter), which destroyed the spans the first pass had added; highlight.js then declined to replace them. It only bit blocks containing an **English contraction**, which is why it looked language-specific β an identical block without one highlighted perfectly. The wrapper now returns untouched if the element is already highlighted. **The download also went from 2.4 MB back to 959 KB:** the archive had picked up one site's media library (a wallpaper, an eagle image, an avatar), eight `.bak` editor backups including a 224 KB `index.py.bak`, and a nested copy of an older `eaglepress.zip`. Empty upload directories are still shipped so a fresh install has somewhere to write. |
| v2.17 | **A history of everything you write.** Every post now keeps its earlier versions. Auto-save stores them in that history rather than in the live page, so you can rework a published article while readers carry on seeing the version you last chose to publish β your changes go live when you press Save and not before. A **Revision History** panel beside the editor lists them newest-first with the time, length and author of each; open any one and it loads back into the editor for you to read and keep or discard. Settings chooses how many to keep β the last 5, 10, 20, 50, or every version ever written. Two related fixes came with it: **auto-save now notices every field it saves**, so a reworked excerpt, new tags, a changed slug, categories or a swapped featured image are all stored just like body text; and **a published post keeps its web address**, so polishing a headline no longer moves the page and breaks the links pointing at it. |
| v2.18 | **Choose what leads your front page.** The front page always showed newest first, so the only way to lead with something was to publish it last β and it stayed on top until the next post went out. Any post, or any **page**, can now be **featured**: it sits at the top of your front page as though it were the newest thing published, and stays there however much you publish afterwards. Featuring changes the **order only** β publication dates are never rewritten, so unfeaturing drops an item straight back into its proper place in the timeline. **One item leads at a time**: featuring another moves the first to **Deactivated**, where it stays listed and returns with one click on **Make Active**, while **Remove from list** clears it from the list without ever deleting the post. Pair it with a Featured Image and *Stretch image spanning page* and the featured item becomes the full-width front-page hero. An optional **Featured badge** marks it for readers, drawn over whichever theme is active so **no theme is modified**, with one badge at a time. Managed from a **Featured** page in the admin sidebar under Drafts and a matching section on the dashboard. |
> **On every version update:** One-Click Update from the Dashboard handles everything β download, verify, deploy, and migrate automatically.
## Technical Stack
| Layer | Technology |
|---|---|
| Language | Python 3.8+ |
| Database | PostgreSQL 12+ β 10 normalized tables, FK constraints, indexed queries, parameterized throughout |
| Web server | Apache `mod_cgi` / Nginx `fcgiwrap` |
| Frontend JS | jQuery 4.0 (CDN) |
| Syntax highlighting | highlight.js (CDN) + custom JS tokenizer (`highlight_custom_improved.js`) |
| Dependencies | `psycopg2` (required) Β· `pyquery` (optional β only for the multi-language translation system) |π‘ Example of the Syntax Highlighting:
import os
for root, dirs, files in os.walk("."):
for name in files:
print(os.path.join(root, name)) # Prints the full path of every file
for name in dirs:
print(os.path.join(root, name)) # Prints the full path of every foldertraverse() {
for file in "$1"/*; do
if [ -d "$file" ]; then
echo "Directory: $file"
traverse "$file" # Recursive call
elif [ -f "$file" ]; then
echo "File: $file"
fi
done
}
# Usage
traverse "/your/start/path"with Ada.Text_IO; use Ada.Text_IO;
with Ada.Directories; use Ada.Directories;
procedure Traverse is
procedure Walk (Path : String) is
Search : Search_Type;
Item : Directory_Entry_Type;
begin
Start_Search (Search, Path, "");
while More_Entries (Search) loop
Get_Next_Entry (Search, Item);
declare
Name : constant String := Simple_Name (Item);
begin
if Name /= "." and then Name /= ".." then
Put_Line (Full_Name (Item)); -- Print the full path
if Kind (Item) = Directory then
Walk (Full_Name (Item)); -- Recursive call
end if;
end if;
end;
end loop;
End_Search (Search);
end Walk;
begin
Walk (".");
end Traverse;function listFiles($dir) {
$files = scandir($dir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
listFiles($path); // Recursive call
} else {
echo $path . PHP_EOL;
}
}
}#include <stdio.h>
#include <dirent.h>
int main() {
struct dirent *entry;
DIR *dp = opendir("."); // Open current directory
if (dp == NULL) {
perror("opendir");
return 1;
}
while ((entry = readdir(dp))) {
printf("%s\n", entry->d_name);
}
closedir(dp);
return 0;
}#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
std::string path = "./my_directory";
for (const auto& entry : fs::recursive_directory_iterator(path)) {
std::cout << entry.path() << std::endl;
}
return 0;
}local lfs = require("lfs")
local path = "."
for file in lfs.dir(path) do
if file ~= "." and file ~= ".." then
local full_path = path .. "/" .. file
print("Found: " .. full_path)
-- Check if it's a directory to recurse
local mode = lfs.attributes(full_path, "mode")
if mode == "directory" then
print(full_path .. " is a directory")
end
end
enduse strict;
use warnings;
use File::Find;
# Specify the directory you want to start from
my $dir = '.';
# find() takes a code reference and the starting directory
find(\&process_file, $dir);
sub process_file {
# $_ contains the current filename
# $File::Find::name contains the full path to the file
print "Found file: $File::Find::name\n" if -f $_;
}CREATE TABLE Inventory (
ProductID INT PRIMARY KEY, -- Unique identifier (Integer)
ProductName VARCHAR(100) NOT NULL, -- Variable-length text (Name)
Quantity INT DEFAULT 0, -- Whole number (Stock count)
UnitPrice DECIMAL(10, 2), -- Precise decimal for currency (Price)
IsActive BIT DEFAULT 1, -- Boolean flag (1 for true, 0 for false)
LastUpdated DATETIME DEFAULT CURRENT_TIMESTAMP -- Date and time stamp
);use std::fs;
use std::path::Path;
fn traverse(path: &Path) {
if let Ok(entries) = fs::read_dir(path) {
for entry in entries.flatten() {
let entry_path = entry.path();
println!("{}", entry_path.display());
if entry_path.is_dir() {
traverse(&entry_path);
}
}
}
}
fn main() {
traverse(Path::new("."));
}package main
import (
"fmt"
"os"
"path/filepath"
)
// walkDir recursively prints every file and directory under root
func walkDir(root string) error {
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
fmt.Printf("[dir] %s\n", path)
} else {
fmt.Printf("[file] %s (%d bytes)\n", path, info.Size())
}
return nil
})
}
func main() {
root := "."
if len(os.Args) > 1 {
root = os.Args[1]
}
if err := walkDir(root); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class WalkDir {
// Recursively prints every file and directory under the given root
public static void main(String[] args) throws IOException {
Path root = Paths.get(args.length > 0 ? args[0] : ".");
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
System.out.printf("[dir] %s%n", dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.printf("[file] %s (%d bytes)%n", file, attrs.size());
return FileVisitResult.CONTINUE;
}
});
}
}import java.io.File
// Recursively prints every file and directory under the given root
fun walkDir(root: File) {
root.listFiles()?.forEach { entry ->
if (entry.isDirectory) {
println("[dir] ${entry.path}")
walkDir(entry) // Recursive call
} else {
println("[file] ${entry.path} (${entry.length()} bytes)")
}
}
}
fun main(args: Array<String>) {
val root = File(if (args.isNotEmpty()) args[0] else ".")
walkDir(root)
}πΌοΈ Image Tag Reference
EaglePress Markdown supports an extended image syntax that lets authors control dimensions and layout alignment directly in content β no raw HTML needed.
Syntax
All parameters after the URL are optional and can be combined in any order (width/height must come before alignment if both are used).
Parameters
| Parameter | Format | Description |
|---|---|---|
alt text | ![alt text] | Alt text for accessibility / broken-image fallback |
url | (url | Absolute or relative URL to the image |
width Γ height | =800x600 | Sets width and height HTML attributes in pixels |
alignment | left / center / right | Applies CSS class for float or centering |
title | "tooltip text" | Sets the title tooltip attribute |
Alignment Classes
| Value | CSS Class Applied | Visual Effect |
|---|---|---|
left | ep-img ep-img-left | Float image left, text wraps right |
center | ep-img ep-img-center | Center image (block, no float) |
right | ep-img ep-img-right | Float image right, text wraps left |
| (none) | ep-img | Inline, no alignment override |
Examples
| Markdown | Output |
|---|---|
 | Basic inline image, no sizing |
 | Image with explicit 800Γ400 px dimensions |
 | Centered image, natural size |
 | 64Γ64 floated left |
 | Full-width centered image with tooltip |
 | Non-image file β renders as a styled download button |
π¦ Syntax Highlighting Language Reference
Usually the Syntax Highlighting Language is auto detected, however, it may be manually stated of which syntax language a code block is utilizing.
Identifier example
β In the EaglePress post/page editor, fenced code blocks work like standard
Markdown with an optional language identifier after the opening backticks:
Basic fenced code block:
```python
def greet(name):
print(f"Hello, {name}!")
```
will display as:
def greet(name):
print(f"Hello, {name}!")Supported language identifiers:
| Identifier(s) | Language |
|---|---|
| python, py, python3 | π Python 3 |
| javascript, js, jsx, ts, tsx | π¨ JavaScript |
| css, scss, sass, less | π¨ CSS |
| html, htm, xml, svg | π HTML |
| php | π PHP |
| ada, adb, ads | π’ Ada |
| bash, sh, shell, zsh | π₯οΈ Bash |
| c, h | βοΈ C |
| cpp, c++, cxx, cc, hpp | βοΈ C++ |
| java | β Java |
| kotlin, kt, kts | πͺ Kotlin |
| go, golang | πΉ Go |
| rust, rs | π¦ Rust |
| lua | π Lua |
| perl, pl, pm | πͺ Perl |
| sql, mysql, pgsql, postgresql, sqlite | ποΈ SQL |
| markdown, md, mdown, mkd | π Markdown |
| text, txt, plain, plaintext | π Plain Text |
Force language with inline hint (when the opening fence can't carry a label):
```
=python3
def hello():
pass
```
will display as:
def hello():
pass The first line =python3 is consumed as a language hint and not shown in the
output.
If you omit the identifier, the engine auto-detects the language from the
content using score-based heuristics. For short or ambiguous code areas you can
always force it explicitly with the language selector dropdown that appears in
the code block toolbar after the page renders.
β’ β’ β’
π Release Notes & Updates
Every release below is delivered automatically to existing installs via the Dashboard's One-Click Updater β no manual steps.
π¦ What's New in v2.14 β Updates Actually Arrive Now
π No More "Why Didn't My Update Change Anything?"
EaglePress styles and scripts were requested with plain addresses like /js/app_core.js, and browsers are told to hang on to those files for a long time. So after an update your browser could quietly keep running the old copy β the files on the server were new, the page you saw was not. Fixing the v2.13 editor bug meant forcing a manual re-download, once on the front page and again on /admin.
π Every File Now Carries Its Own Timestamp
Each stylesheet and script is now requested with its last-modified time attached β /js/app_core.js?v=1785358900. Change the file, the address changes, and the browser fetches the new one. Automatically, from now on.
It keys off the file, not the release number, so even a small patch shipped between versions arrives properly. And if a file ever can't be found, it quietly falls back to the release number instead of erroring.
π¨ The Themes Too, Not Just the Admin Panel
This covers 32 asset references: the admin panel, the user dashboard, and all 8 bundled themes β each theme's own stylesheet and script included. So a theme tweak shows up for your readers without asking them to do anything.
π‘ One last hard refresh. Deploying v2.14 itself still needs a
Ctrl+Shift+R, because your browser has to pick up the new page before it can see the new addresses. That's the last time you'll need to.π¨ If you installed ModernTheme or EaglePressTheme from the marketplace, their downloadable copies were rebuilt for this release too.
β’ β’ β’
π¦ What's New in v2.13 β New Post Opens Blank, Drafts Live on the Server
βοΈ A Blank Page, Every Time
Clicking New Post (or New Page) kept announcing "Draft restored from auto-save" and dropping text from some other, long-finished post into the editor. The rule is now absolute: a post that doesn't exist yet restores nothing. New Post opens blank β no notice, no leftovers, no exceptions.
π Why It Kept Happening
Two things, both living in your browser β which is why reinstalling the site never helped.
First, an unsaved post has no id yet, so its auto-save slot was a single shared "new" slot. Once the post was saved and got a real id, the editor moved on to a per-post slot but left the old shared one behind β and the next new post picked it up.
Second, browsers restore a form's contents when you return to a page in the same tab, hidden fields included. So a blank New Post page could quietly be holding the id of the last post you edited, and the editor believed it. It now reads that id from the page the server actually sent, which the browser cannot overwrite. This also closed a nastier problem: with a restored id in hand, "New Post" could auto-save straight on top of an existing post.
Third β and this is why it kept coming back even after the first two were fixed β the browser puts that text back after the editor has cleared it. The editor tidies up the moment the page is ready; the browser restores its remembered form values a beat later, right on top. The page now tells the browser not to remember the editor's contents at all, and the editor takes a second pass once the page has fully settled (including on Back/Forward), so anything restored is removed. It never touches text you've started typing.
And it's not just the title and body: tags, status, the slug options, the featured-image toggles and your category ticks are all reset to a clean slate now.
πΎ Your Recovery Copy Now Lives in Drafts
Auto-saved versions go to Dashboard β Drafts β in every auto-save mode. Previously only β‘ Smart (2s) saved to the database; the timed settings (5 seconds through 10 minutes) only stashed a copy in the one browser you happened to be using. Now every interval saves to the server, so your work is recoverable from any browser or any machine.
π« Auto-Save No Longer Unpublishes Your Post
A real bug, found while fixing the above: auto-save marked every save as a draft. Edit a published post, wait for one auto-save, and it quietly went offline; a post submitted for review dropped back to draft the same way. Auto-save now leaves an existing post's status exactly as it found it.
π€ Authors Stay in Their Own Dashboard
Also fixed: when an author created a post from the user dashboard, the address bar was rewritten to an admin URL they had no permission to open β so reloading the page locked them out of the post they'd just written. They now stay on their own /user/edit/ page.
π‘ Existing installations get the fix via the One-Click Updater. One extra step this time: the editor script is cached for up to 30 days, so press Ctrl+Shift+R on the editor page after updating (or purge your server/CDN cache) β otherwise your browser keeps running the old script and nothing appears to change.
β’ β’ β’
π¦ What's New in v2.12 β Clearer Kate Dark Mode
π Keywords Stop Blending In
In the Kate syntax theme's dark mode, keywords were the same grey as ordinary text β set apart only by bold β so keyword-heavy code looked almost flat. Now keywords are near-white and bold, so procedure / is / begin / end in Ada, def / class / return in Python, and the keywords of every other language clearly stand out from the code around them.
ποΈ Every Colour Meets Contrast
A few Kate dark colours β constants like True / False / None, comments, doc-tags, strings and function names β were too dim to read comfortably on the dark background. They've all been brightened so every code colour now passes WCAG-AA contrast (β₯ 4.5:1), while keeping Kate's authentic look.
π― One Fix, Every Language
This lives in EaglePress's shared code-highlighting core, so the improvement lands on every language at once β on both the published article and the admin editor's Preview β with no database migration. Kate light mode and all other syntax themes are untouched.
π‘ Existing installations get the clearer Kate dark mode automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.11 β Ada Joins the Code Highlighter
π’ Ada Is Now a First-Class Code-Block Language
EaglePress now syntax-highlights Ada β bringing the built-in language count to 17. Ada takes its place in the language combo box on every code block (and in the admin editor's picker) right after PHP and before Bash, so you can hand-pick it for any block.
π Auto-Detected, No Label Needed
Paste an Ada procedure, package or generic into a plain code fence and EaglePress recognises it automatically. The detector keys on unmistakable Ada syntax β the with Ada.β¦ context clause, Put_Line/Get_Line, procedure Name is, function β¦ return β¦ is, package [body] Name is, when others =>, end Name;/end loop;, and attribute references like Integer'Image β and it was checked so it doesn't clash with the other languages (Go's :=, Lua/JavaScript function β¦, SQL/Lua -- comments, or Markdown prose). Prefer a label? `ada (also adb / ads) forces it.
π¨ Colourised Everywhere
Because highlight.js doesn't bundle an Ada grammar, EaglePress now ships its own, so Ada blocks colourise the same whether the language was auto-detected or chosen from the combo box β keywords, comments, strings, character literals, numbers and attributes. Implemented entirely in the shared core β every theme gets it, with no database migration.
π‘ Existing installations get Ada automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.10 β See the Whole Featured Image
πΌοΈ No More Clipping in the Article
When you set a featured image and turn on "Display in article", that image appears at the top of the post. Previously it was cropped to fill the space (the bottom of taller images got cut off). Now, across all 8 themes, the featured image shows the whole picture β nothing clipped top or bottom β with a little breathing room above it.
π It Shows Up in Preview, Too
The editor's Preview tab now includes that featured image at the top β exactly where it will appear in the published article β whenever "Display in article" is checked. And it's live: toggle the checkbox while the Preview is open and the image appears or disappears instantly. Turn the toggle off and the preview shows just your content.
π·οΈ Tidier Marketplace Cards
The Browse Repository cards now read the same at a glance for both themes and plugins: each shows a consistent By: / Creator: / Created: block. The author line gained a colon (By:) to match the labels beneath it, and the plugin cards β which used to show only the author β now include the Creator and Created lines too.
π‘ Existing installations get all v2.10 improvements automatically via the One-Click Updater. (The two showcase theme downloads β ModernTheme & EaglePressTheme β are refreshed to match in the theme marketplace.)
β’ β’ β’
π¦ What's New in v2.09 β A Tidier Mobile Header for the Language Switcher
If you've turned on EaglePress's multi-language system (pip install pyquery), a language combo box appears in your site header so visitors can switch languages. On phones, that combo box left too little room β the logo, the language selector and the header buttons were all fighting for one narrow row.
π± Two Lines, Plenty of Room
Now, on mobile and only when the language combo box is present, the header splits into two neat lines:
- Line 1: your logo / site name + the language combo box
- Line 2: the menu (login / create account / admin), search, and light/dark toggles β right-aligned
On desktop, and on any site that doesn't show the language switcher, the header looks exactly as before β nothing changes.
π¨ EaglePressTheme β a Navy + Gold Accent Bar
On EaglePressTheme that second line used to be a bare, transparent gap. It's now a full-width navy accent bar with a gold top border that runs edge-to-edge and holds the toggles β so the two-line header looks deliberate and polished instead of empty.
π§ ModernTheme Mobile Menu β Now Visible
ModernTheme had a bug where tapping the β° menu on a phone opened a menu whose links were effectively invisible. That's fixed β the opened menu is now a full-width dropdown below the bar, so every link (Home, Login, Create Account, Admin, β¦) shows clearly.
π Menus Drop Below the Button
On a few themes (firstTheme, RedWhiteBlueTheme, EaglePressTheme) the opened mobile menu could appear on top of the hamburger button once the header was two lines tall. Now every theme's mobile menu drops cleanly below the header, right under the toggle button.
π‘ Existing installations get all v2.09 improvements automatically via the One-Click Updater. (The two showcase theme downloads β ModernTheme & EaglePressTheme β are refreshed to match in the theme marketplace.)
β’ β’ β’
π¦ What's New in v2.08 β Kotlin Code Highlighting
EaglePress already highlights code blocks in a long list of languages β Python, JavaScript, PHP, Bash, C, C++, Java, Go, Rust, SQL and more. v2.08 adds Kotlin to that list.
π» Kotlin in the Language Combo Box
Every code block carries a language selector, and Kotlin now appears in it β on fenced blocks, on inline code, and in the admin editor's language picker. Prefer to type it? Just open your fence with `kotlin (or `kt / `kts ) and EaglePress highlights the block as Kotlin.
π Auto-Detected, Too
Paste a Kotlin snippet with no language hint and EaglePress recognizes it automatically. The detector keys on unmistakable Kotlin signals β fun, val, data class, companion object, when { β¦ }, the Elvis operator ?:, import kotlin.*, collection builders like listOf(...), and nullable types such as String? β while leaving Java and JavaScript detection intact.
π§© Built Into the Core β Works on Every Theme
Like all code-block features, this is a shared-core change (www/lib/syntax_highlight.py), not a per-theme tweak. The highlighting itself is rendered by the syntax-highlight engine EaglePress already loads, so there's no new dependency, no theme files change, and no database migration.
π‘ Existing installations get all v2.08 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.07 β Stretch Image Spanning the Page
v2.06 made the front-page hero show the whole image (nothing cropped). Some sites want the big, edge-to-edge, full-bleed banner instead β so v2.07 makes it your choice, per post.
βοΈ A New "Stretch Image Spanning Page" Checkbox
In the post editor, open the Featured Image box and you'll find a new checkbox β "Stretch image spanning page" β right under "Display in article":
- Off (default): the post's image, when it's the front-page main image, shows the whole picture (the v2.06 contained look).
- On: that hero image stretches full-bleed across the entire desktop page (
cover), edge to edge, just like it did before v2.06.
Set it per post, so a wide, dramatic banner photo can span the page while a tall or detailed image still shows in full elsewhere.
π§© Built Into the Core β Works on Every Theme
This is a core feature, not a per-theme tweak. EaglePress stores the choice on the post and applies it centrally from index.py β the same theme-independent layer that already injects shared content styles and plugin assets. That means no theme files change, and the toggle works across all bundled themes (and third-party themes that reuse the standard hero classes). Existing posts are untouched; the new column is added automatically by the One-Click Updater's database migration.
π‘ Existing installations get all v2.07 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.06 β See the Whole Picture
v2.06 is a focused image-display release: across all 8 themes, post images now show the full picture without clipping the top. Three different image mechanisms were each given the right treatment.
πΌοΈ Post Cards β the Full Image, No Crop
On listing pages, the smaller post-card images used to be cropped to fill a fixed box, which clipped the top of the picture. The cards keep their tidy, even 16:9 grid, but the image now uses object-fit: contain β the whole image is shown, centered, and any leftover space is filled with a clean theme-color bar. Nothing is cut off.
β¬οΈ Featured Images & Thumbnails β Top Kept
The single-post featured/hero image and the sidebar / latest / recent thumbnails still fill their space edge-to-edge (no bars), but are now anchored to the top (object-position: top center) so the crop happens at the bottom instead of slicing off the top of the photo. Author, comment and profile avatars stay centered, so faces are never pushed off.
π¦ Homepage Hero β Full Banner Image
The big front-page hero in firstTheme, americanTheme, USAtheme, RedWhiteBlueTheme and EaglePressTheme is a CSS background image β where object-fit simply doesn't apply. Its background equivalent, background-size: contain, now lets the entire hero image display (centered, with a themed letterbox), so the top of your headline image is no longer hidden under the menu bar.
π Reminder: Translations Need pyquery
EaglePress's multi-language system runs server-side and uses the pyquery package. The core CMS needs only psycopg2; if you want the 13 built-in (122 opt-in) language translations, also run pip install pyquery.
π‘ Existing installations get all v2.06 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.05 β 13 Languages, a Translated Marketplace & a Polished Logout
π Langugage Translations β 13 Languages Built In
There are 13th languages enabled out of the box, selectable from the language combo box on every page. Pick a flag and the whole site re-renders in that language, server-side. Beyond the 13 defaults, 122 opt-in languages now cover 100% of the reader / front-end interface, and 109 languages are at full parity across the admin dashboard too. (The wider machine-generated translations are flagged for native review.)
ποΈ The Marketplace Speaks Your Language
The theme and plugin marketplace is now localized. In the Dashboard's Browse Repository, the sort dropdown (Most Popular, Newest, Name AβZ, Top Rated), the Pages / Infinite view toggle, pagination, the search box and loading states all translate. On the public /themes and /plugins showcase galleries at EaglePress.org, the hero headings, the stat blocks (Available Themes, Free, Always Open Source, License) and every card's labels (By, Download, Preview, Source) follow the selected language too.
πΌοΈ Featured-Image Hero Fix
Several themes (USAtheme, RedWhiteBlueTheme, EaglePressTheme, americanTheme, firstTheme) weren't showing the featured image in the front-page hero. The image URL was wrapped in double quotes inside a double-quoted style attribute, which closed the attribute early β switched to single quotes so the hero image now loads everywhere.
πͺ A Proper Logout Page
Logging out used to dump you on the raw JSON output of the logout endpoint on some themes. There is now a dedicated /logout page β an animated "You've been signed out" confirmation with a draining countdown bar that returns you home, in light or dark mode β and the session is destroyed server-side. Every theme and the admin Dashboard now use it.
π§Ή No More Duplicate Themes
After an update, the Dashboard sometimes showed the same theme or plugin twice (a bundled copy plus a marketplace copy). The lists now collapse those duplicates to a single card, and the updater prunes the redundant marketplace folder when a bundled twin exists β never touching your active theme or active plugins β so the duplicates stop coming back.
π‘ Existing installations get all v2.05 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.04 β Line-Number Polish
π’ Smoother Line Numbers
The code-block line numbers (added in v2.03) now render one number per line right beside its code, so they stay perfectly aligned even when a line wraps or a heading makes a line taller β and you can use line numbers and word wrap at the same time.
β Separators Play Nice With Line Numbers
A --- divider inside a code block (shown as a horizontal rule, or β’ β’ β’ dots) now displays correctly when line numbers are on, in every mode, with the line numbers evenly spaced.
π Word-Wrap Button Border
On mobile, the word-wrap button's border now matches the code block's theme β white in dark mode, black in light mode β so it is always easy to see.
π‘ Existing installations get all v2.04 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.03 β Code Block Line Numbers
π’ Line Numbers for Code Blocks
Every code block can now show a clean line-number gutter. A new toggle sits in the code-block toolbar, right next to the language indicator β on desktop it is a labelled "Line numbers" checkbox, and on mobile it is a #οΈβ£ button. The gutter is right-aligned, stays put while long lines scroll sideways, and the numbers are rendered on the server so they are correct the instant the page loads. Your choice is remembered per block.
βοΈ New Reader Default
Dashboard β Settings β Reader Defaults gains a "Default Code Block Line Numbers" (Off / On) option, so you can decide whether code blocks show line numbers out of the box for every visitor. Readers can still flip any individual block on or off.
π± Cleaner Mobile Toolbar
The code-block toolbar was reworked for small screens. The controls no longer overlap β they line up in a single row that scrolls sideways with a visible scrollbar when there are many of them, in the order π» language Β· #οΈβ£ line numbers Β· β΅ word wrap Β· π‘ light/dark Β· theme Β· language format. Word wrap is now a tidy grey-bordered β΅ box, a vertical divider sets the theme controls apart, and tapping any toggle pops a quick tooltip telling you its new state (e.g. Line Numbers On, Word Wrap Off, Dark Mode) that fades away on its own.
π‘ Existing installations get all v2.03 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.02 β Java Language Support
β Java Programming Language Support
EaglePress now supports Java as a canonical language in the syntax-highlighting engine β both for score-based auto-detection and in the code-block language selector. Fence with ~~~java. The detector uses 14 weighted signals unique to Java: public static void main(Stringβ¦), System.out.println, import java.β¦;, dotted package β¦; declarations, annotations such as @Override, access-modified type/method declarations, extends / implements, generic collections, and throws β¦Exception. An ATX-heading penalty keeps Markdown prose from being misread as Java.
πΉ Go Detection Tightened
The strong package signal now requires the bare, no-semicolon form (package main), so Java's dotted package com.foo.bar; declarations are no longer misclassified as Go.
π‘ Existing installations get all v2.02 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.01 β Go Language, Mobile UX, Smart Updater & New Theme
πΉ Go Programming Language Support
EaglePress now supports Go as the 14th canonical language in the syntax highlighting engine β both for auto-detection and in the code block language selector. Fence with ~~~go or ~~~golang. The score-based detector uses 16 weighted signals unique to Go: package declarations, := short variable declarations, defer statements, goroutine launches, channel operators, method receivers, and standard library package references (fmt, os, sync, net/http, etc.).
β’ β’ β’
π± EaglePressTheme Mobile Hamburger β Redesigned
The mobile hamburger dropdown on EaglePressTheme is now a proper slide-out menu pinned below the sticky header β full-width navy background, gold accent border, and large 15 px tap targets. π¨ Themes and π Plugins appear as plain, accessible links in the dropdown (the gold group border and "Marketplace" label only show on desktop where there is room for them).
β’ β’ β’
π§ Code Block Toolbar β Mobile Ready
On screens β€640 px the language text label is replaced by a π» emoji button that shows a tap-dismissible tooltip with the detected language name. The "Wrap" label becomes β΅ to save space. Desktop toolbar is unchanged.
β’ β’ β’
π Smart Updater β SHA-512 + Timestamp Fingerprinting
The one-click updater can now detect when you upload a new archive with the same version number β no more having to manually click Reinstall. The distribution API now returns the archive's zip_mtime (modification timestamp) alongside its SHA-512. After each install, both values are stored in the database. On the next check, if either differs from the stored values, the Dashboard shows "Updated Package Available β v2.01 (same version, new content)" with a π Apply Updated Package button. The Dashboard card also displays the installed SHA-512 (first 24 characters) so you can verify your running installation at a glance.
β’ β’ β’
π¨ RedWhiteBlueTheme β New Bundled Theme
RedWhiteBlueTheme is a new patriotic American Stars & Stripes theme bundled with EaglePress v2.01:
- Deep navy header with diagonal stripe texture and a 4 px crimson bottom border
- Crimson red buttons and links throughout (USATheme influence)
- Gold β stars in the logo, sidebar widget titles, and footer
- 3-colour stripe divider (red/white/blue) between the hero and the post grid
- Thin stars banner below the header
- Full dark mode, reading progress bar, image lightbox, and all standard theme features
- β / π light/dark mode toggle
π‘ Existing installations get all v2.01 improvements automatically via the One-Click Updater.
β’ β’ β’
π¦ What's New in v2.00 β Reader Defaults, EaglePressTheme & More
Reader Defaults
Three new Dashboard settings let you control the out-of-the-box experience for every visitor:
| Setting | Description |
|---|---|
| Default Page Theme | Light or dark β applied on first visit before any localStorage preference is saved |
| Default Code Block Theme | Which syntax-highlighting theme new code blocks start with |
| Default Code Block Mode | Light or dark mode for every new code block |
All five bundled themes (firstTheme, secondTheme, americanTheme, USAtheme, NewsTheme) and the admin panel now read this setting in their anti-FOUC inline script instead of hardcoding 'light'.
EaglePressTheme β Official Showcase Theme
A brand-new installable theme designed for eaglepress.org, now included in the bundle:
- Deep navy + gold colour scheme β Playfair Display headings, Inter body
- Home page with full-bleed hero post, card grid, and sidebar
- /themes gallery β preview images, version badges, Download + Preview buttons for every theme in the repository
- /plugins directory β Download + Source (GitHub) buttons for every plugin
- Notice banner on non-eaglepress.org installs explaining the theme's distribution purpose
- Dark mode, full syntax highlighting, responsive layout
- Available in the Themes marketplace as 2_EaglePressTheme
New Core Routes
| Route | Behaviour |
|---|---|
| Theme gallery (eaglepress.org) or redirect card to eaglepress.org/themes | |
| Plugin directory (eaglepress.org) or redirect card to eaglepress.org/plugins | |
| Live preview of any installed theme using the first published post |
Global Lists System
A new Update Global Lists button in the Distribution Manager scans all theme and plugin zips in the repository, reads their manifests, and writes global_theme_list.json + global_plugin_list.json β the JSON files that power the /themes and /plugins showcase pages.
π‘ Existing installations get all v2.00 improvements automatically via the One-Click Updater.
β’ β’ β’
Every code block now has an independent light/dark mode toggle button β the π‘ / π emoji button placed between the word-wrap checkbox and the theme dropdown.
| State | Button | Border |
|---|---|---|
| Light mode | π‘ lightbulb | Yellow |
| Dark mode | π low-brightness | Green |
Key features:
- Each code block is completely independent β toggling one block does not affect any other
- Default is always light mode on first visit
- Mode is persisted per block in
localStorage(keyed by page path + block index), just like word-wrap and theme preferences - All 10 themes now have both a light and a dark variant:
| Theme | Light | Dark |
|---|---|---|
| GitHub | CDN github | CDN github-dark |
| Atom One | CDN atom-one-light | CDN atom-one-dark |
| Monokai | Custom (cream #fafaf6) | CDN monokai |
| Visual Studio | CDN vs | CDN vs2015 |
| Nord | Custom (Snow Storm #eceff4) | CDN nord |
| Agate | Custom (#f5f5f5) | CDN agate |
| Xcode | CDN xcode | Custom (#1d1f20) |
| Classic | CDN default | Custom (#1e1e1e) |
| Notepad++ | Custom (white) | Custom (#1e1e1e) |
| Kate | Custom (white) | Custom (KDE Breeze #232629) |
Previously separate paired entries (github + github-dark, atom-one-dark + atom-one-light, etc.) are now a single unified entry β the theme dropdown went from 13 entries to 10.
ModernTheme: The "Register" navigation and footer link has been renamed to "Create Account". The site title / tagline hover now lifts with a smooth animation and glow instead of dimming.
π‘ Existing installations get all v2.00 improvements automatically via the One-Click Updater.
β’ β’ β’
π¨ What's New in v1.98 β Kate Syntax Theme
A new Kate colour theme is now available in every code block's theme selector dropdown β a clean light theme inspired by the KDE Kate editor's Default KSyntaxHighlighting palette.
Colour scheme:
| Token | Colour |
|---|---|
| π¬ Comments | Grey #898887 |
π΄ Plain strings ('β¦' / "β¦") | Red #bf0303 |
| π F-strings / template literals | Bold orange #ff5500 |
π΅ {interpolation} inside f-strings | Bold sky blue #3daee9 |
β« Keywords β if, for, while, def, class, fnβ¦ | Bold near-black #1f1c1b |
π· Literals β True / False / None / null | Blue #0057ae |
| π‘ Numbers | Ochre #b08000 |
| β« Operators | Near-black #1f1c1b |
| π£ Built-in functions & function names | Purple #644a9b |
| π· Class names & types | Blue #0057ae |
π’ Decorators / preprocessor (@property, #[derive], #include) | Green #006e28 |
π· Variables β $var, @arr (Bash/PHP/Perl) | Blue #0057ae |
| π£ Regular expressions | Purple #924c9d |
| β« HTML tag names | Near-black #1f1c1b |
| π· HTML attribute names | Blue #0057ae |
| π΄ HTML attribute values | Red #bf0303 |
| π· CSS tag selectors | Blue #0057ae |
| π£ CSS class selectors | Purple #644a9b |
| π΄ CSS id selectors | Red #bf0303 |
No CDN dependency β all colour rules live in styles_content.css; no extra network request is made when selecting this theme. Available on every code block in admin and frontend.
π‘ Existing installations get the Kate theme automatically via the One-Click Updater β no manual steps required.
β’ β’ β’
π¨ What's New in v1.97 β Notepad++ Syntax Theme
A new Notepad++ colour theme is now available in every code block's theme selector dropdown β a crisp light theme with distinct, high-contrast colours for every token type.
Colour scheme:
| Token | Colour |
|---|---|
| π¬ Comments | Grey #808080 |
π΄ Plain strings ('β¦' / "β¦") | Red #cc0000 |
| π F-strings / template literals | Bold orange #ff8c00 |
π΅ {interpolation} inside f-strings | Bold light blue #4fc3f7 |
β« Keywords β if, for, while, def, class, fnβ¦ | Bold black |
π· Literals β True / False / None / null | Dark blue #000080 |
| π£ Numbers | Magenta #c000c0 |
| π₯ Operators | Dark red #8b0000 |
| π’ Function & class names | Dark green #006400 |
| π£ Built-in functions | Dark purple #800080 |
π« Decorators / preprocessor (@property, #include, #[derive]) | Olive #808000 |
π©΅ Variables β $var, @arr, %hash (Bash/PHP/Perl) | Sky blue #87ceeb |
| π£ Regular expressions | Purple #800080 |
| π©΅ HTML tag names | Bold turquoise #00ced1 |
| π΄ HTML attributes | Red #cc0000 |
| π· CSS selectors | Dark blue #000080 |
F-string detection β the JS engine adds class ep-fstring to any .hljs-string span that contains a .hljs-subst child (interpolated expression), so plain strings and f-strings can be coloured differently without browser-specific CSS selectors. Works in all modern browsers.
No CDN dependency β all colour rules live in styles_content.css; no extra network request is made when selecting this theme. Available on every code block in admin and frontend.
π‘ Existing installations get the Notepad++ theme automatically via the One-Click Updater β no manual steps required.
β’ β’ β’
π¦ What's New in v1.96 β Rust Syntax Highlighting
EaglePress now includes Rust as a fully supported language in the syntax highlighting engine β both for score-based auto-detection and in the editor language selector dropdown.
Detection signals β the auto-detector accumulates weighted scores from Rust's most distinctive patterns:
let mutbindings,use std::imports,implblocks,fnsignatures with return types- Macro invocations:
println!(),vec![],panic!(),assert_eq!() - Type parameters:
Vec<T>,Option<T>,Result<T, E>,HashMap<K, V>,Arc<T> - Attribute macros:
#[derive(...)],#[allow(...)],#[cfg(...)],#[test] - Pattern matching:
match β¦ { Some(x) => β¦ },.unwrap(),.expect(),.is_some() - Lifetime notation:
'a(guarded: Rust added to_APOSTROPHE_LANGSalongside Python, Bash, SQL) - Anti-false-positive: ATX heading penalty (
-15) prevents Markdown documents from scoring as Rust
In the editor β "Rust" appears in the language selector dropdown in every code block toolbar, using the fence hint rust or the short alias rs.
Rendering β highlight.js handles Rust natively on the front end; no changes to highlight_custom_improved.js were needed.
π‘ Existing installations receive Rust support automatically via the One-Click Updater β no manual steps required.
β’ β’ β’
π What's New in v1.95 β Fresh-Install Fix
Running python3 sql/install.py on a brand-new database failed every time at step 3 with:
ERROR running setup.sql: relation "ep_plugins" does not exist
Root cause β setup.sql runs as a single transaction. The two index definitions for ep_plugins were placed before the ep_plugins CREATE TABLE, so PostgreSQL hit them first and aborted the entire install.
Fix β Both index lines moved to immediately after the CREATE TABLE ep_plugins block. Verified with a full clean install on a fresh database β all five steps complete without error.
π‘ Existing installations are unaffected. This only impacted fresh installs from v1.94 and earlier.
β’ β’ β’
π¦ Latest Archive
File: eaglepress_v2.18.zip
SHA-512: (used by the One-Click Updater to verify the download)
ccf7937853ab057cda27c38876fddbd8af6d24e44d9f8662007b1b2ea27012de14c5d3e0139f1a0eab133e1fad5d399823589a51e0f38768d10f7037cfad3d8eSHA-256:
17111b7fb0ab0a41f25225e94dab0a853ed2cab7922540f802d0b0ee57eceb7fSHA-1:
c66c1c5a02754506943c9cc574f5e402fd38f6d3Verify your download by running sha512sum eaglepress.zip (or sha256sum / sha1sum) and matching the value above.
0 Comments
Leave a Comment