944 lines
71 KiB
Markdown
944 lines
71 KiB
Markdown
# racket-wiki
|
|
|
|
Version 0.2.31 adds page namespaces as database metadata and extends wiki references to forms such as `RWS:ModelTreeWalker` and `[roadmap](racket:roadmap)`. Todo items and bookmarks are grouped by namespace. The source has also been documented more thoroughly, especially `static/js/wiki.js`.
|
|
|
|
Current development version: **0.2.99**.
|
|
|
|
A small self-hosted wiki with a Racket backend and an HTML5/CSS/JavaScript frontend.
|
|
|
|
Version 0.2.9 uses PostgreSQL as the wiki's complete content store. Users, sessions, pages, immutable page versions, attachment metadata and attachment bytes live in PostgreSQL. PostgreSQL full-text search is built into the page table and exposed by the wiki search UI.
|
|
|
|
The first-run flow is web based. An unconfigured wiki redirects immediately to `/setup`. Setup asks for the PostgreSQL connection, creates the schema, downloads the pinned frontend libraries and creates the first administrator. After setup the browser is sent to `/login`.
|
|
|
|
## Components
|
|
|
|
The backend uses Racket `web-server-lib` for HTTP routing, `db-lib` for PostgreSQL and `crypto-lib` for password hashing. Racket's PostgreSQL support implements the wire protocol directly, so no PostgreSQL client library is required by racket-wiki.
|
|
|
|
Passwords use PBKDF2-HMAC-SHA256 through the libcrypto/OpenSSL provider. Session tokens are random client values whose SHA-256 hashes are stored in PostgreSQL; authenticated write requests also use a per-session CSRF token.
|
|
|
|
The editor uses EasyMDE 2.21.0. EasyMDE bundles CodeMirror for editing and Marked for Markdown rendering. The application uses `easyMDE.markdown(...)` for the editor preview, normal page display and historical page display. DOMPurify sanitizes rendered Markdown, highlight.js highlights fenced code blocks, and diff2html renders page-version differences.
|
|
|
|
Lucide supplies the sidebar and EasyMDE toolbar icons. Its pinned browser build is installed locally by the setup procedure, so normal wiki use does not contact a CDN.
|
|
|
|
## Roles
|
|
|
|
`reader` can read pages, page history and uploaded files.
|
|
|
|
`editor` inherits `reader` and can create, edit and archive pages and upload files.
|
|
|
|
`admin` inherits `editor` and can administer users.
|
|
|
|
There is no anonymous page access. Setup and login are server-rendered pages. The normal JavaScript wiki application is only served when the request has an authenticated session.
|
|
|
|
## Data layout
|
|
|
|
PostgreSQL contains the relational/textual wiki state:
|
|
|
|
```text
|
|
users
|
|
sessions
|
|
pages
|
|
page_versions
|
|
attachments
|
|
concept_maps
|
|
wiki_schema
|
|
```
|
|
|
|
`pages.markdown` contains the current Markdown source. Every save increments `current_version` and writes the matching immutable snapshot to `page_versions` in the same PostgreSQL transaction. Optimistic locking prevents an editor from silently overwriting a newer version.
|
|
|
|
The page search vector gives titles a higher weight than body Markdown and is indexed with a PostgreSQL GIN index. Concept maps are searched by their title, slug and the labels and synopses in their JSON document. The `simple` text search configuration is used deliberately because a technical wiki commonly mixes Dutch, English, identifiers and package names.
|
|
|
|
The filesystem contains only installation/configuration assets during normal 0.2.9 operation:
|
|
|
|
```text
|
|
wiki-data/
|
|
database.rktd
|
|
static/
|
|
vendor/
|
|
... downloaded browser libraries ...
|
|
```
|
|
|
|
`database.rktd` contains the PostgreSQL connection settings, including the password when one is supplied. Protect the data directory accordingly. Attachment metadata and bytes are stored in PostgreSQL. An `uploads/` directory may remain after migration from schema 1; it is retained only as a safety copy and is no longer read by 0.2.9.
|
|
|
|
Archiving a page marks it archived in PostgreSQL and keeps its history. Restore UI is a later addition.
|
|
|
|
## First-run web setup
|
|
|
|
Start the server normally:
|
|
|
|
```text
|
|
racket main.rkt --data ./wiki-data --port 8080
|
|
```
|
|
|
|
Open `http://127.0.0.1:8080/`. An incomplete installation is immediately redirected to `/setup`.
|
|
|
|
On a fresh installation the setup page asks for:
|
|
|
|
```text
|
|
PostgreSQL server localhost
|
|
PostgreSQL port 5432
|
|
Database racket_wiki
|
|
User <database role>
|
|
Password <optional according to PostgreSQL authentication>
|
|
TLS/SSL no / optional / required
|
|
|
|
Administrator username
|
|
Display name
|
|
Administrator password
|
|
```
|
|
|
|
The PostgreSQL database itself must already exist and the supplied role must be allowed to connect and create the racket-wiki tables and indexes in the selected database. Setup tests the connection before storing the settings.
|
|
|
|
Setup creates the schema, including users, sessions, pages, page versions, attachments and their binary content, the schema migration history and the page full-text-search GIN index. It then downloads the pinned frontend libraries and creates the first administrator. After all setup conditions are satisfied, the browser is redirected to `/login`; setup deliberately does not log the administrator in automatically.
|
|
|
|
Setup is considered complete only when the PostgreSQL schema is reachable, at least one enabled administrator exists and all required browser vendor files are installed. `/setup` therefore also acts as a repair path when frontend assets are missing.
|
|
|
|
## Frontend libraries
|
|
|
|
The web setup downloads these pinned browser builds:
|
|
|
|
```text
|
|
lucide.min.js
|
|
easymde.min.js
|
|
easymde.min.css
|
|
purify.min.js
|
|
highlight.min.js
|
|
highlight-scheme.min.js
|
|
highlight-github.min.css
|
|
diff2html-ui-base.min.js
|
|
diff2html.min.css
|
|
```
|
|
|
|
EasyMDE already bundles CodeMirror and Marked, so racket-wiki does not download a separate Markdown renderer or CodeMirror build.
|
|
|
|
The setup downloader validates HTTPS certificates and hostnames. The running wiki serves these files locally from the configured data directory, so no CDN is required after setup.
|
|
|
|
`setup-vendor.rkt` remains available as a manual/recovery command. It now writes to the same data directory as the web setup:
|
|
|
|
```text
|
|
racket setup-vendor.rkt --data ./wiki-data
|
|
```
|
|
|
|
Normal first-run installation does not require this command.
|
|
|
|
## Development bootstrap
|
|
|
|
The bootstrap URL used for this development snapshot is:
|
|
|
|
```text
|
|
https://rktsndbx.dijkewijk.nl/bootstrap-racket?next=opper-vrolijk-fresh-pocket
|
|
```
|
|
|
|
When development is performed in an isolated sandbox without a Racket installation, use that Racket bootstrap URL. The bootstrap page lists a numbered set of `text/plain` files. Each file contains the base64 representation of one binary part of `racket.zip`.
|
|
|
|
Use network access only through the environment web fetch/download facility. Do not use `curl`, `wget`, Python networking or shell networking for the bootstrap.
|
|
|
|
Use these fixed paths:
|
|
|
|
```text
|
|
work directory : /tmp/racket_bootstrap
|
|
zip file : /tmp/racket.zip
|
|
Racket runtime : /mnt/data/racket
|
|
Racket binary : /mnt/data/racket/bin/racket
|
|
```
|
|
|
|
The bootstrap procedure is:
|
|
|
|
```text
|
|
1. Open the supplied bootstrap URL with the web fetcher.
|
|
2. Read the number of parts and the exact part URLs from that page.
|
|
3. Download every part to /tmp/racket_bootstrap/part-NNNNNN.txt.
|
|
4. Decode every text file separately with base64 -d to part-NNNNNN.
|
|
5. Concatenate the binary parts in numeric order to /tmp/racket.zip.
|
|
6. Unpack the zip at filesystem root: unzip /tmp/racket.zip -d /
|
|
7. Use /mnt/data/racket/bin/racket and /mnt/data/racket/bin/raco.
|
|
```
|
|
|
|
The archive intentionally contains the path `mnt/data/racket`, so unpacking at `/` places the runtime at `/mnt/data/racket`. Additional Racket packages should be obtained through the package/fetch facilities exposed by the bootstrap environment rather than by adding unrelated shell-network tooling.
|
|
|
|
This bootstrap is development tooling only. A normal installation can use an existing Racket installation and the dependencies declared in `info.rkt`.
|
|
|
|
## Starting from DrRacket
|
|
|
|
`main.rkt` can be run directly from DrRacket. Racket treats its `main` submodule in the same way as when the file is run with the `racket` executable, so pressing **Run** starts the wiki with the normal defaults when no command-line arguments are configured.
|
|
|
|
For interactive development, `main.rkt` also provides `start`. Require the module from an interactions file or another development module and supply keyword arguments directly:
|
|
|
|
```racket
|
|
(require "main.rkt")
|
|
|
|
(start #:data-dir "wiki-data"
|
|
#:port 8080
|
|
#:listen-ip "127.0.0.1"
|
|
#:site-title "Racket Wiki"
|
|
#:secure-cookie? #f)
|
|
```
|
|
|
|
Use `#:listen-ip "*"` or `#:listen-ip #f` to listen on all interfaces. `#:session-seconds` can be supplied when the default twelve-hour session lifetime is not desired.
|
|
|
|
The same web setup procedure applies when the server is started from DrRacket.
|
|
|
|
## Starting
|
|
|
|
Normally only the server start command is required:
|
|
|
|
```text
|
|
racket main.rkt --data ./wiki-data --port 8080
|
|
```
|
|
|
|
Open `http://127.0.0.1:8080/`. A fresh data directory redirects to `/setup`; after setup the browser goes to `/login`.
|
|
|
|
To listen on all interfaces:
|
|
|
|
```text
|
|
racket main.rkt --data ./wiki-data --listen '*' --port 8080
|
|
```
|
|
|
|
When the site is served through HTTPS, add `--secure-cookie` so the session cookie gets the Secure attribute.
|
|
|
|
The `--create-admin` command-line option remains available for recovery or scripted administration, but it is no longer part of the normal first-run procedure.
|
|
|
|
|
|
## Page namespaces
|
|
|
|
Every page has a namespace and a page slug. Existing pages are migrated to the root namespace. The external page reference is compact:
|
|
|
|
```text
|
|
roadmap
|
|
racket:roadmap
|
|
RWS:model-tree-walker
|
|
```
|
|
|
|
The editor exposes the namespace as page metadata. The slug is still generated from the title when a page is first created and remains stable afterwards. The combination of namespace and slug is unique in PostgreSQL.
|
|
|
|
Explicit Markdown links may use the compact form directly:
|
|
|
|
```markdown
|
|
[roadmap](racket:roadmap)
|
|
```
|
|
|
|
Classic WikiWords may also be namespace-qualified:
|
|
|
|
```text
|
|
RWS:ModelTreeWalker
|
|
```
|
|
|
|
The WikiWord portion still follows the strict letter-only classic rule. Todo and Bookmark views group their entries by namespace; bookmark user sections remain available as a second grouping level.
|
|
|
|
## Editing
|
|
|
|
EasyMDE supplies the Markdown editor, Markdown-aware styling while editing, line numbers, keyboard shortcuts, preview, side-by-side mode and fullscreen mode. On a normal desktop-sized window racket-wiki opens the editor in side-by-side mode by default. EasyMDE is configured with `sideBySideFullscreen: false`, so split editing remains inside the normal wiki page rather than taking over the complete browser window.
|
|
|
|
The toolbar uses Lucide SVG icons, installed locally during setup. It includes formatting, headings, lists, checklists, code blocks, tables, links, image upload, general file upload, undo/redo, preview, split view and fullscreen.
|
|
|
|
The sidebar gives the current page table of contents priority. The global page list is secondary and collapsible. The search box performs real PostgreSQL full-text search across pages and concept maps rather than filtering titles in the browser.
|
|
|
|
A fenced block such as:
|
|
|
|
````text
|
|
```racket
|
|
(define (hello name)
|
|
(displayln name))
|
|
```
|
|
````
|
|
|
|
is highlighted in the preview through highlight.js. highlight.js has a Scheme grammar rather than a dedicated Racket grammar; racket-wiki registers `racket` as an alias for that grammar.
|
|
|
|
|
|
## Page titles, slugs and missing links
|
|
|
|
For a normal new page the editor asks only for the page title. The Racket backend derives the slug when the page is first saved. Whitespace and punctuation become hyphens, the slug is lower-cased, and Unicode compatibility decomposition is used so common accented Latin titles produce readable addresses. A later title change does not rename the slug, which keeps existing links stable.
|
|
|
|
A link can explicitly name a page slug. In rendered Markdown, simple relative links such as `[Install](install)` and root links such as `[Install](/install)` are treated as wiki-page links; `#/install` also remains supported. External URLs and upload URLs are left alone.
|
|
|
|
When an editor or administrator follows a wiki link whose target does not exist, racket-wiki opens a new empty editor at that requested slug instead of exposing an API/Racket error. The title is still entered by the editor. A reader gets a normal `Page not found` view. An unauthenticated request never reaches the wiki page application: it is redirected to `/login` first.
|
|
|
|
## Full-text search
|
|
|
|
The sidebar search field calls the authenticated `/api/search` endpoint. PostgreSQL builds a `tsquery` with `websearch_to_tsquery('simple', ...)`, ranks matches with `ts_rank`, and returns a short `ts_headline` excerpt. Page results use the indexed search vector that is updated atomically whenever a page is created or saved. Concept-map results derive a weighted vector from the stored map title, slug, concept labels and synopses without indexing embedded image data. Both result sets are merged by relevance, marked as `Wiki page` or `Concept map`, and limited to fifty results together.
|
|
|
|
## Uploads
|
|
|
|
EasyMDE handles image upload through its normal image-upload hook. PNG, JPEG, GIF and WebP can therefore be inserted by its image button, drag/drop or clipboard paste. The upload itself still goes to the Racket API and the returned page-local URL is inserted into the Markdown.
|
|
|
|
The additional `File` toolbar button is for arbitrary attachments such as PDF, ZIP or source files. Dropping one or more non-image files on the editor is handled by racket-wiki and inserts Markdown links. A mixed drop containing images and other files uses this general file path so every dropped file is retained.
|
|
|
|
For a new page, save it once before uploading attachments. This is intentional: uploads always belong to an existing page directory.
|
|
|
|
Uploads are limited to 50 MiB per request in this first setup.
|
|
|
|
## Rendering
|
|
|
|
There is no separate application Markdown renderer. EasyMDE's `markdown(...)` method is the single rendering path for the live editor preview, normal reader view and rendering a selected historical version.
|
|
|
|
That method uses EasyMDE's bundled Marked renderer and the same rendering configuration, including syntax highlighting and DOMPurify sanitization. This avoids subtle differences between what an editor previews and what a reader sees after saving.
|
|
|
|
## Versioning
|
|
|
|
Every create or save writes a new immutable row to `page_versions` in the same PostgreSQL transaction as the current page update. Updates use optimistic locking: the browser sends the version it originally opened. If somebody else saved the page in the meantime, the API returns HTTP 409 instead of silently overwriting their version.
|
|
|
|
The History view can render an old version or compare two consecutive versions. The browser creates a unified textual diff and diff2html renders it side by side.
|
|
|
|
## Security notes
|
|
|
|
Markdown rendering is sanitized with DOMPurify before it is inserted into the page. This also means raw HTML embedded in Markdown is only retained to the extent that DOMPurify permits it.
|
|
|
|
Session cookies are HttpOnly and SameSite=Strict. Authenticated write operations also require a per-session CSRF token.
|
|
|
|
Untrusted uploads are not blindly rendered inline. Only common raster image formats are served inline. Other files use `Content-Disposition: attachment`; SVG is therefore not treated as an inline image.
|
|
|
|
For internet-facing deployment, put the server behind a TLS terminating reverse proxy, use `--secure-cookie`, and add normal operational controls such as backups, access logging and upload limits appropriate to the installation.
|
|
|
|
## Architecture documentation import
|
|
|
|
The package contains a coherent Dutch architecture documentation set under
|
|
`architecture/`: twelve namespaced wiki pages and two native concept maps. The
|
|
pages cover structure, behaviour, data and versioning, modularity,
|
|
maintainability, analyzability, testability, long-term performance, security,
|
|
coding rules, and architecture evolution.
|
|
|
|
Validate and preview the import first:
|
|
|
|
```text
|
|
racket architecture/import.rkt --data ./wiki-data --dry-run
|
|
```
|
|
|
|
Import the set and record a recognizable author in page and CMap history:
|
|
|
|
```text
|
|
racket architecture/import.rkt --data ./wiki-data --author "Hans Dijkema"
|
|
```
|
|
|
|
From DrRacket or another module, the equivalent convenience call is:
|
|
|
|
```racket
|
|
(require racket-wiki/architecture/import)
|
|
|
|
(import-racket-wiki-architecture-from-data-directory!
|
|
"wiki-data"
|
|
#:author "Hans Dijkema")
|
|
```
|
|
|
|
Imported pages use namespace `racket-wiki`. The concept maps use the stable
|
|
slugs `racket-wiki-architectuur` and `racket-wiki-kwaliteitskenmerken` and are
|
|
embedded in the overview pages with `{{cmap:...}}`.
|
|
|
|
Every imported item carries a source hash. A later import updates an item only
|
|
when its imported source has not been edited locally. Modified items are
|
|
reported as `skipped-modified`; use `--overwrite-modified` only after reviewing
|
|
their version history. Re-importing identical sources does not create needless
|
|
versions.
|
|
|
|
## Next useful steps
|
|
|
|
The backend and storage API remain independent of EasyMDE. Useful next additions include internal wiki-link syntax, page namespaces/navigation, restore-from-archive, site settings, per-page ACLs and a richer admin console.
|
|
|
|
## Version 0.2.7
|
|
|
|
Version 0.2.7 stores all wiki content in PostgreSQL: users, sessions, pages, immutable page versions and binary attachments. Page writes and version snapshots are transactional. PostgreSQL full-text search with a weighted `tsvector` and GIN index is exposed through a real wiki search field. Database schema changes are handled by ordered migrations recorded in `wiki_schema`.
|
|
|
|
The same release also makes the page-local contents list the primary sidebar navigation, adds a DokuWiki-style breadcrumb, adopts restrained Racket/Scribble-inspired document typography, installs Font Awesome for the EasyMDE toolbar, and makes source-editor heading sizes match the rendered heading scale.
|
|
|
|
## Page contents and editor appearance
|
|
|
|
The sidebar shows a page-local table of contents before the global page list. The
|
|
table of contents is derived from Markdown headings. In the editor it updates
|
|
while the Markdown source changes; selecting an entry moves the editor cursor to
|
|
that heading. In page view, selecting an entry scrolls to the rendered heading.
|
|
|
|
The wiki stylesheet is intentionally document-oriented and takes visual cues
|
|
from Racket's Scribble/manual help pages without depending on Scribble's
|
|
internal CSS classes. EasyMDE preview and the normal rendered page share the
|
|
same heading and body sizes. CodeMirror heading tokens use the same heading
|
|
scale, so a Markdown heading is not larger in the source editor than in the
|
|
rendered preview.
|
|
|
|
Font Awesome 4.7 is installed locally by the web setup together with EasyMDE
|
|
and the other browser dependencies. No CDN access is needed after setup.
|
|
|
|
|
|
## 0.2.6 setup and missing-page behaviour
|
|
|
|
When PostgreSQL explicitly requests cleartext `password` authentication, the setup page now explains that `scram-sha-256` is preferred and reminds the administrator that PostgreSQL uses the first matching `pg_hba.conf` rule. A failed setup POST preserves server, port, database, user, SSL mode, administrator username and display name. Password fields are deliberately cleared.
|
|
|
|
For editors and administrators, a missing wiki page is now shown as an empty page target with one `Edit` action. Editing that target creates the page on first save. The separate global `New page` action is hidden while such a missing target is open, avoiding simultaneous create/edit choices for the same page.
|
|
|
|
## Database schema migrations
|
|
|
|
The PostgreSQL schema has its own version history in table `wiki_schema`. Each applied schema version is recorded with `applied_at`. On startup racket-wiki detects the existing schema and applies every missing migration in order before serving the normal application. A database whose schema is newer than the running racket-wiki version is refused rather than silently downgraded.
|
|
|
|
Schema 1 is the PostgreSQL layout introduced by 0.2.5/0.2.6: pages and page versions are in PostgreSQL while attachment bytes are on disk. Schema 2, introduced by 0.2.7, adds `attachments.mime_type` and `attachments.content BYTEA`. During migration 1 -> 2, existing files below `wiki-data/uploads/<slug>/` are copied into the matching attachment rows. The migration runs transactionally and is only recorded as version 2 when every attachment can be migrated. Legacy upload files are deliberately left on disk after a successful migration as a safety copy; 0.2.7 no longer reads them at runtime. They may be removed after the migrated attachments have been verified.
|
|
|
|
The application libraries downloaded by setup and the local database connection settings remain in the data directory; they are installation/configuration data rather than wiki content.
|
|
|
|
## Version 0.2.9
|
|
|
|
Version 0.2.9 fixes the Racket regexp used by the server-side `todo(...)` extractor. The extractor processes one Markdown source line at a time, so the regexp now excludes only parentheses; it no longer contains the invalid alphabetic regexp escapes for carriage return and newline.
|
|
|
|
## Version 0.2.8
|
|
|
|
Version 0.2.8 adds wiki-wide todo markers, offline detection and translatable frontend labels.
|
|
|
|
A todo marker is written directly in Markdown as `todo(text)`. Markers inside fenced code blocks are ignored. On every page save the current markers are indexed in PostgreSQL. The **Todo** view gathers the unresolved markers from all active pages and links each item back to its source page. Standard Markdown task lists such as `- [ ] item` remain available for ordinary page-local checklists.
|
|
|
|
The browser sends a lightweight request to `/api/ping` every 15 seconds. A failed or timed-out request shows an **Offline** indicator. When the site becomes reachable again the indicator briefly reports **Online** and then disappears.
|
|
|
|
The old **New page** button has been removed. New pages are created by following or entering a wiki address that does not exist yet and choosing **Edit**.
|
|
|
|
UI text is routed through `private/translate.rkt`. The server can be started with a language, for example:
|
|
|
|
```
|
|
racket main.rkt --data ./wiki-data --language nl
|
|
```
|
|
|
|
Built-in English and Dutch translations are supplied. Administrators also get a **Translations** action. It opens the single special page `wiki-translations`. When that page does not exist yet, the editor is prefilled with every built-in translation key. Each line can contain several languages, for example `save = nl:Opslaan, en:Save, de:Speichern`. Values containing commas or quotes can be quoted. The active language selects the matching value and falls back to `en` when the requested language is not present. Saving the page is enough; the overrides are loaded from PostgreSQL on the next frontend load. Setup and login retain their built-in translations and therefore do not depend on that special page.
|
|
|
|
Database schema 3 adds `todo_items`. Migration 2 -> 3 creates the table and indexes todo markers from all existing, non-archived pages. The migration is recorded in `wiki_schema` just like earlier schema changes.
|
|
|
|
The web setup also contains a UI-language selector. The selected language is stored in `wiki-data/language.rktd`; the command-line `--language` value is the default when that file does not yet exist.
|
|
|
|
|
|
## Version 0.2.12
|
|
|
|
Version 0.2.12 makes the first/start page title the visible wiki name and breadcrumb root. The account line is moved to the upper-right navigation area as compact links (`name · role · sign out · admin`), and page actions such as Edit, History and Delete use the same small link style as the breadcrumb. The breadcrumb/account/action chrome stays sticky while reading long pages. The sidebar keeps `* Todo list` directly below the wiki name; Users and Translations are reached through the Admin page.
|
|
|
|
Wiki image options now support placement in addition to width. Examples are `{width=50% center}`, `{width=320px right}`, `{width=320px right float}` and `{width=320px float=left}`. Without `float`, left/center/right controls block alignment. With float enabled, surrounding text can flow around left- or right-aligned images. Width remains limited to pixels and percentages and rendered images keep `max-width: 100%` and automatic height.
|
|
|
|
The special translations page is now one page named `wiki-translations`. It is prefilled from the built-in language tables and accepts several languages on one line, for example `save = nl:Opslaan, en:Save, de:Speichern`.
|
|
|
|
|
|
## Version 0.2.13
|
|
|
|
Version 0.2.13 centers the wiki name in the sidebar and makes it a link to the first/start page. The sticky top chrome now starts without top padding (`padding-top: 0`).
|
|
|
|
|
|
## 0.2.15
|
|
|
|
The page table of contents now includes a `(context)` link. It opens a graph containing the current page and all pages that link directly to it or are linked directly from it. The current page is highlighted. Heading anchors also reserve space for the sticky top navigation so a selected heading remains visible.
|
|
|
|
|
|
## Recent pages and bookmarks
|
|
|
|
The sidebar provides dynamic Recent and Bookmarks views. Recent shows current pages ordered by their last edit time. Bookmarks are stored per user in PostgreSQL and can be grouped into user-defined sections. The database migration to schema version 4 creates the bookmarks table automatically.
|
|
|
|
## 0.2.19
|
|
|
|
Todo markers are case-insensitive. `todo(...)`, `Todo(...)` and `TODO(...)` are all indexed and rendered as todo items. Database migration 4 -> 5 rebuilds the todo index for existing pages so already-saved mixed-case markers become visible without re-saving the pages.
|
|
|
|
Editors can start section editing from the small edit link beside a heading in the page contents. The whole page is still saved as one version; section edit only opens the editor at the selected heading.
|
|
|
|
Pages whose slug starts with `template-` are available in the editor Template dropdown. Selecting a template copies that page's Markdown into the current editor. Existing non-empty content is only replaced after confirmation.
|
|
|
|
|
|
|
|
## 0.2.20
|
|
|
|
Inline `todo(...)` markers render as a yellow `Todo: text` link. Clicking the marker opens the Todo view at the matching item and highlights it. Todo markers inside fenced code blocks remain untouched.
|
|
|
|
|
|
## 0.2.23
|
|
|
|
Breadcrumbs now keep a per-browser-tab history of visited wiki pages. The start page remains the fixed root. Earlier pages in the trail are clickable; selecting one truncates the trail at that page, after which navigation continues from there. Returning to the wiki name/Home clears the trail and opens the start page. Special views such as Todo, Recent, Bookmarks, Graph and Admin do not become entries in the page-history breadcrumb. The trail is stored in browser session storage and is limited to the most recent eight non-home wiki pages.
|
|
|
|
|
|
## 0.2.23
|
|
|
|
CamelCase and normalized unlinked page mentions are rendered as automatic links to existing wiki pages. Section edit temporarily highlights the target source line. The EasyMDE toolbar contains a Raw Markdown toggle; the preference is kept in the browser.
|
|
|
|
|
|
## 0.2.24
|
|
|
|
Attachment references are tracked in database schema 6. Admin contains an Orphaned uploads page showing uploads no current page references, together with their last historical page/version when available.
|
|
|
|
## 0.2.26
|
|
|
|
CamelCase references to an existing, uniquely matching wiki page are normalized when a page is saved. For example `SmokeTestDiffSysteemspecificatie` is stored as `[Smoke Test Diff Systeemspecificatie](smoke-test-diff-systeemspecificatie)`. The conversion is idempotent: existing Markdown links, URLs, inline code, fenced code blocks, indented code and Todo markers are left unchanged. The link text is taken from the actual page title.
|
|
|
|
|
|
## 0.2.27
|
|
|
|
CamelCase wiki references are no longer rewritten when a page is saved. The Markdown source stays compact and unchanged. During rendering, an unlinked CamelCase or normalized page mention that uniquely resolves to an existing page is rendered as a wiki link using the actual page title as its visible text. For example `SmoketestDiffSysteemspecificatie` can render as `Smoke Test Diff Systeemspecificatie` while the stored Markdown remains `SmoketestDiffSysteemspecificatie`. Existing Markdown links, inline code, fenced code and other protected rendered elements are not modified.
|
|
|
|
## 0.2.29
|
|
|
|
CamelCase is now wiki syntax during rendering. Every CamelCase token becomes an implicit wiki link outside code, existing Markdown links/images, URLs and Todo markers. If a matching page exists, its real title and slug are used. If no page exists, the display text and slug are derived from the CamelCase token; following that link opens the normal missing-page view where an editor can create it. Stored Markdown is not changed.
|
|
|
|
## 0.2.28
|
|
|
|
Implicit CamelCase/wiki mention linking now runs as a lightweight Markdown pre-render step instead of walking the rendered DOM. The stored Markdown is unchanged. Ordinary text is resolved before Marked renders it, so implicit links work consistently in paragraphs, lists, tables, blockquotes and headings. Existing Markdown links and images, URLs, inline code, indented code, fenced code blocks and Todo markers are excluded. A unique match is rendered with the actual target page title.
|
|
|
|
|
|
|
|
## Alias cleanup (0.2.34)
|
|
|
|
Admin -> Page aliases now shows current and historical uses of each retained page alias.
|
|
`Clean up references` replaces recognized references in current Markdown with the canonical page address.
|
|
Every changed page receives a normal immutable history version; historical versions themselves are never rewritten.
|
|
An alias can be deleted only after no current page still contains a recognized reference. If historical
|
|
versions still use the alias, the administration page warns that deleting it may make those old links stop
|
|
resolving.
|
|
|
|
The cleanup recognizes explicit Markdown destinations, old WikiWord references derived from the retained
|
|
title, and upload URLs. Fenced code, indented code and lines containing inline code are deliberately left
|
|
untouched.
|
|
|
|
0.2.34 also fixes the Windows path-separator regexp in `uploaded-file` (`#px"[/\\]"` at regexp level).
|
|
|
|
## Page rename and aliases (0.2.33)
|
|
|
|
Editors can rename a page or move it to another namespace without changing its database identity. The old namespace/slug is retained as an alias, so old page links and upload URLs continue to work. Ordinary Edit no longer changes the namespace; address changes go through Rename so the alias is always recorded. Admin shows the running software version and lists retained aliases together with current pages that still contain the old address.
|
|
|
|
## 0.2.36 context graph
|
|
|
|
The context graph now shows both incoming and outgoing direct wiki links explicitly. The current page is placed in the center, incoming pages on the left, outgoing pages on the right, and pages linked in both directions above the focus page. Directed arrows make the relation direction visible.
|
|
|
|
The `(context)` link beside Contents opens the context graph as an overlay without leaving the page. From the overlay it can be docked to the right of the page or above the page, or opened in the normal full-size graph view. A docked context graph stays available while reading and refreshes when navigating to another page.
|
|
|
|
## 0.2.37 concept-map prototype
|
|
|
|
This release adds an isolated in-memory concept-map prototype based on the MIT-licensed
|
|
`ionstage/cmap` browser library. The frontend dependency is installed by the normal web setup
|
|
as `cmap.js`; no npm or client build step is required.
|
|
|
|
The prototype is deliberately not stored in PostgreSQL yet. It is intended to validate the
|
|
editor model before a permanent CMap schema is designed. The CMaps view demonstrates draggable
|
|
free concepts, wiki-page concepts, sub-CMap placeholders, labelled directed relations, a short
|
|
synopsis, background colour and font settings. Double-clicking a wiki-page concept opens its
|
|
page. Sub-CMap nodes are placeholders in this prototype.
|
|
|
|
The existing automatic wiki graph remains separate: it is derived from actual wiki links,
|
|
whereas a CMap is intended to become a manually modelled semantic map with stable positions and
|
|
named relations.
|
|
|
|
On wide screens, docking the automatic context graph to the right now keeps the established
|
|
900px document width and uses otherwise unused horizontal space for a wider context panel.
|
|
|
|
|
|
## 0.2.40
|
|
|
|
Fixed serving of the bundled `/vendor-extensions/` frontend directory so the racket-wiki CMap interaction layer is loaded next to upstream `cmap.js`.
|
|
|
|
|
|
### 0.2.40
|
|
|
|
CMap concept selection now uses delegated pointer-up selection with a drag threshold, so selecting and dragging no longer compete with ionstage/cmap event handling.
|
|
|
|
### 0.2.42
|
|
|
|
Concept selection now also works with the original cmap 0.1.3 rendering model. During setup and at startup, racket-wiki adjusts the installed `cmap.js` so concept DOM nodes accept pointer events; cmap's existing coordinate-based drag handling continues to work. The generic renderer remains in `cmap.js`, while `cmap-racket-wiki.js` contains only the wiki-specific editor behaviour.
|
|
|
|
Both CMap script URLs receive the same fresh numeric `id` query parameter whenever the application HTML is loaded. This prevents a browser or caching proxy from silently mixing an older `cmap.js` or `cmap-racket-wiki.js` with the current application version during development and testing.
|
|
|
|
### 0.2.43
|
|
|
|
CMap selection is now a separate, immediate interaction. Pointer-down selects a concept before cmap starts moving it, while pointer-down on empty canvas space clears the selection. The selected concept receives a CMapTools-like purple selection ring, a compact relation handle above the concept and a resize handle at its lower-right corner. All concept-map presentation and interaction rules have moved from `wiki.css` to the separate `cmap.css`. Both stylesheets receive the same per-page cache id as both CMap scripts so the visual selection state cannot remain hidden by an older cached stylesheet.
|
|
|
|
### 0.2.44
|
|
|
|
The CMap prototype now reports its complete selection path to the browser console with prefixes `[racket-wiki:cmap 0.2.44]` and `[racket-wiki:cmap-host 0.2.44]`. Logging covers script and stylesheet loading, editor and item creation, pointer targets, item matching, selection and clearing, installed handles and the computed pointer, outline, shadow, overflow and z-index styles before and after browser redraw. `RacketWikiCmap.debugSelection()` can be entered manually in the console to inspect the current selection.
|
|
|
|
### 0.2.45
|
|
|
|
The concept-map renderer is now maintained as an application component below `static/cmap/` instead of being downloaded and patched as a vendor dependency. The directory contains `cmap.js`, the wiki interaction layer `cmap-racket-wiki.js`, its separate `cmap.css`, and notes about the local component boundary.
|
|
|
|
Selection now follows cmap's own coordinate hit test. The renderer exposes an `onSelection` callback for the public node or link wrapper it found, and `onRendered` callbacks report when the asynchronously drawn DOM element is actually available. The wiki layer therefore no longer has to infer selection from a DOM pointer target or decorate a node before it exists. Clicking a concept applies the CMapTools-like selection ring and controls; clicking empty map space clears it.
|
|
|
|
`cmap.js`, `cmap-racket-wiki.js`, and `cmap.css` share the same fresh numeric cache id in every served HTML response.
|
|
|
|
### 0.2.46
|
|
|
|
New concepts without an explicit width or height now receive a compact initial size based on their rendered title and synopsis. Longer text wraps at a practical maximum width instead of producing an excessively wide concept. Content-based sizing remains active while editing such a concept, but stops as soon as the user resizes it manually.
|
|
|
|
### 0.2.47
|
|
|
|
Selected concepts now have a separate edit handle at the lower-left, opposite the resize handle. It opens a modal editor for the visible concept label, synopsis, background colour, font and font size. A page concept keeps its stable `pageSlug` while its visible concept text can be changed independently; double-clicking the concept still opens the linked wiki page.
|
|
|
|
The concept editor also accepts an image file, shows a preview and can remove the image again. In this in-memory prototype the browser stores the image as a data URL with the node. This can be migrated to the normal wiki upload storage when CMaps themselves become persistent.
|
|
|
|
### 0.2.48
|
|
|
|
Double-click activation is now recognized by cmap's own hit-test and drag lifecycle instead of depending on the browser's DOM `dblclick` event. Two stationary clicks on the same component within 500 milliseconds produce an `onActivation` callback. This reliably opens the linked wiki page for a page concept while movement beyond a small tolerance remains a drag.
|
|
|
|
The concept editor now offers a font-family selection instead of a free-form field. Font size is shown, entered and stored in typographic points (`pt`); existing pixel values are converted to points when opened.
|
|
|
|
### 0.2.49
|
|
|
|
The concept editor now includes a text-colour picker and an explicit wiki-page selector. A free concept can be linked to any existing page and becomes a page concept without forcing its visible label to match the page title. Removing the link turns it back into a free concept. Double-click navigation calls `openPage` directly, so opening also works when the browser hash already names that same page.
|
|
|
|
Linking phrases no longer look like bordered concept blocks. They are compact, borderless labels placed in a visual gap between the two connector segments. A phrase can be dragged along its source-to-target line; cmap projects the requested position onto that line and keeps ordinary concept dragging unconstrained.
|
|
|
|
### 0.2.50
|
|
|
|
Dragging a relation handle onto empty map space now opens the normal concept dialog. Saving creates the concept at the drop position, connects it to the source and immediately opens the new linking phrase for editing. The toolbar's Add concept action uses that same dialog.
|
|
|
|
The CMap work surface grows to the right and downward when concepts or a relation draft reach its edge. Zoom controls in the upper-left provide minus, plus, an editable percentage and a 100% reset. Zoom is implemented in the bundled cmap layer and its pointer coordinates are normalized, so selection, double-clicking, dragging and resizing keep working between 25% and 300%.
|
|
|
|
### 0.2.51
|
|
|
|
New connections are straightened after both endpoints are attached and again when an endpoint or automatically sized linking phrase changes. This removes the unnecessary control-point bends inherited from the original cmap implementation. The growing map no longer has its own scrollbars: its element expands and normal page scrolling exposes the additional work area.
|
|
|
|
Sub-CMaps can now be expanded and collapsed with their plus/minus control or by double-clicking. Their concepts and relations appear in place inside a cloud-like boundary and remain fully selectable, draggable, editable and connectable. Child visibility is handled by cmap's own hit testing, so collapsed contents cannot accidentally be selected. The sample includes a nested sub-CMap to exercise recursive expansion.
|
|
|
|
A selected inline submap can also be promoted to a separately named concept map with **Make separate CMap**. In this prototype that reference and its contents remain in memory; durable concept-map pages and wiki embedding belong to the next storage-model step.
|
|
|
|
### 0.2.52
|
|
|
|
Connected links now calculate their node-edge endpoints and midpoint synchronously before their first render. This removes the remaining lines that incorrectly ran through the upper-left corner. Every later node movement rebuilds a straight connection from the current endpoints.
|
|
|
|
Submap membership is spatial. A concept created or dropped inside the deepest expanded cloud becomes part of that submap. A member dragged beyond the cloud's boundary is detached, while dropping it inside another cloud reparents it. The cloud boundary is frozen during the drag so it does not expand ahead of a departing node. Multiple external relations remain allowed: relations may connect to the collapsed submap head, and while expanded individual internal concepts may also connect outside.
|
|
|
|
Promoting a submap now collapses the inline instance and immediately opens its separate in-memory concept-map view. A persistent navigation control returns to the parent map and also supports nested separate maps. The former header toolbar has become a right-click context menu, also reachable through the vertical-dots button beside the sticky zoom controls.
|
|
|
|
While the CMap view is active, only the wiki's central `#main` content area scrolls. The sidebar remains outside that scroll container, and the expanding map itself no longer has a private scrollbar.
|
|
|
|
### 0.2.53
|
|
|
|
The submap entry is now also the main concept of its expanded or separately opened map; expansion no longer creates a duplicate main-concept node. Dragging a member beyond its current cloud asks for confirmation. Confirming detaches or reparents it, while declining preserves membership so the cloud grows around its new position.
|
|
|
|
Cross-boundary relations retain their real internal endpoint. When the submap is collapsed that endpoint is drawn from the visible submap entry, and when expanded it is rebound to the original concept. This also works recursively for nested submaps. Linking phrases are no longer projected along a straight connection: their free position forms the intentional bend point between the two relation segments.
|
|
|
|
### 0.2.54
|
|
|
|
Moving the head concept while its separate CMap view is open no longer asks whether that concept should leave its original parent submap. The head remains the stable link between parent and child maps while its position in the active map can still be changed normally.
|
|
|
|
### 0.2.55
|
|
|
|
F2 edits the selected CMap item. Concepts and submap heads open the existing concept dialog with the label selected; linking phrases switch directly to their inline name editor. The shortcut is active only in the CMap view and does not intercept F2 while a form field or editable element already has focus.
|
|
|
|
### 0.2.56
|
|
|
|
A promoted submap now has one unambiguous presentation. In its parent map the arrow opens the separate CMap and inline expansion is disabled. Inside that separate map the same entry is the main concept, so its self-referential navigation arrow is removed. Stale expanded state is cleared defensively, preventing a separate CMap and its inline cloud from being displayed at the same time. Ordinary, non-promoted submaps keep their plus/minus expansion control.
|
|
|
|
### 0.2.57
|
|
|
|
Concept maps are now durable wiki data. Schema migration 9 adds the `concept_maps` table with relational identity, audit fields, soft deletion and optimistic version numbers; the evolving editor document is stored as validated JSONB. The database operations live in the separate `private/cmap-storage.rkt` module, while `private/storage.rkt` remains focused on wiki pages and their existing metadata. Reader endpoints list and retrieve CMaps, and editor endpoints create, update and archive them.
|
|
|
|
The CMap view selects stored maps and lets editors create and save them. Loading restores concept positions and formatting, directed connectors, nested submap membership and promoted-map references. A normal concept can now link either to a wiki page or to an independently stored CMap. Inside a separately opened child map it can instead link back to its parent CMap. Activating either CMap link navigates without incorrectly expanding the node as an inline submap.
|
|
|
|
The concept dialog now places searchable filter fields above both the wiki-page and CMap selectors. The two link types are mutually exclusive, and CMap-linked concepts receive their own visual link indicator.
|
|
|
|
### 0.2.58
|
|
|
|
The stored-map selector and the wiki-page and CMap link selectors are now searchable comboboxes. Typing filters the browser's suggestion list directly, while the displayed title and stable slug remain unambiguous. An arbitrary value is not silently stored as a link: the value must be selected from the list or the field must be cleared.
|
|
|
|
Selecting a stored CMap now follows one explicit asynchronous load path. The UI reports loading and completion, stale responses from quickly changed selections are ignored, and the console reports the received and reconstructed item and connector counts. This also fixes the earlier selector path that could leave the canvas showing the previous map.
|
|
|
|
### 0.2.59
|
|
|
|
PostgreSQL JSONB parameters are now sent explicitly as text before conversion to JSONB. This prevents Racket's database driver from treating an already serialized CMap document as a JSON string value. Existing double-encoded rows remain readable: the storage layer recognizes and decodes them, and the next save writes them back as a normal JSON object.
|
|
|
|
The three CMap selectors now use a local, dependency-free styled combobox rather than the browser's visually limited `datalist`. The control follows the input-plus-choice-list pattern of Thibault Jan Beyer's MIT-licensed ComboBox.js, but uses a modern ARIA listbox, separate stable values and labels, title/slug descriptions, contains filtering and keyboard control tailored to racket-wiki. Both the combobox script and the main wiki script receive the per-request cache identifier.
|
|
|
|
The duplicate CSP meta element has been removed because the server already sends the complete policy as an HTTP header. This prevents Firefox's misleading `frame-ancestors` meta warning without weakening the policy. A blocked `sandbox eval code` inline script still comes from browser or extension-injected code and remains deliberately blocked by `script-src 'self'`.
|
|
|
|
### 0.2.60
|
|
|
|
Ctrl-S saves the CMap while the CMap workspace is active. Dirty-state detection compares the complete current editor document with the last successfully loaded or saved snapshot, so movement, resizing, formatting, relations and submap changes are all covered without maintaining a second incomplete list of change events.
|
|
|
|
Leaving a changed CMap, switching to another stored CMap or following a page/CMap link opens a three-way dialog: Save, Don't save or Cancel. Browser refresh and tab closing use the browser's native unsaved-data warning. Hash navigation is restored while the choice is pending, so Cancel genuinely keeps both the map and location in place.
|
|
|
|
The zoom percentage is stored in browser `localStorage`, keyed by stored CMap slug and by root or promoted child map. Because local storage is scoped to the wiki origin and browser profile, every device/browser environment naturally retains its own zoom value for each CMap without adding a tracking identifier or database state.
|
|
|
|
### 0.2.61
|
|
|
|
Expanded inline submaps now use a clean rounded rectangular frame instead of a cloud outline. The frame derives its background and border colours from the submap concept, encloses nested concepts and relations, and grows with its contents. Its collapse button sits halfway along the right border; the compact submap concept retains the plus button used to expand it.
|
|
|
|
### 0.2.62
|
|
|
|
An inline submap now behaves as one movable compound object. Moving its main concept, including while the submap is collapsed, applies the same displacement to every descendant concept, nested submap and linking phrase. Reopening therefore preserves the complete internal layout relative to the main concept.
|
|
|
|
The expanded frame is interactive rather than decorative. Clicking it selects the submap and exposes the existing concept editing controls, double-clicking opens the edit dialog directly, and dragging the frame moves the complete submap. Background and border colour changes made through the dialog are applied to both the main concept and its frame.
|
|
|
|
### 0.2.63
|
|
|
|
The main concept and its expanded sub-CMap frame now have independent presentation properties. Editing a submap shows separate colour controls for the main concept background, the sub-CMap background and the sub-CMap border. These values are persisted independently and survive the normal CMap document round trip. Older documents without the new frame fields receive the standard green submap frame without changing their main concept colour.
|
|
|
|
### 0.2.64
|
|
|
|
The main concept of an expanded sub-CMap now moves independently inside its frame, just like every other concept. Dragging the frame remains the single operation that moves the complete sub-CMap and all descendants. When the sub-CMap is collapsed, its visible main concept acts as the proxy for the hidden group, so moving that compact representation still preserves the complete relative layout.
|
|
|
|
### 0.2.65
|
|
|
|
Wiki pages can now link directly to stored concept maps. The EasyMDE toolbar contains a CMap-link action with the same searchable combobox used elsewhere in the editor. It inserts readable Markdown in the form `[Title](cmap:map-slug)`; selected editor text is retained as the link label.
|
|
|
|
During rendering, CMap targets are converted into durable `#cmap/map-slug` routes. Opening such a link switches to the CMap workspace and loads that exact stored map, including after a browser reload or when the URL is shared. A missing or archived target produces an explicit not-found status and is never silently replaced by another map.
|
|
|
|
### 0.2.66
|
|
|
|
Stored CMaps can also be referenced with wiki-style shorthand such as `cmap:DitIsEenCmap`. The renderer resolves the WikiWord against the known CMap titles and slugs and turns a unique match into a direct CMap link. Unknown or ambiguous names remain plain text; they are no longer misinterpreted as pages in a `cmap` namespace. Explicit links such as `[Andere tekst](cmap:dit-is-een-cmap)` remain supported.
|
|
|
|
### 0.2.67
|
|
|
|
Page and CMap navigation now consistently passes through hash routes before content is loaded. Following a page concept from a CMap, following a CMap concept, selecting another stored map and opening the CMaps workspace therefore create normal browser-history entries. A sequence such as home page -> CMap -> linked page can be retraced one step at a time with Back and Forward.
|
|
|
|
Internal CMap navigation checks for unsaved changes before changing the URL, preventing the save/discard dialog from creating a spurious history entry. The general CMaps workspace also has the reproducible `#cmaps` route. Saving a previously unnamed map replaces that generic route with its durable `#cmap/map-slug` permalink.
|
|
|
|
### 0.2.68
|
|
|
|
The CMap workspace now uses one compact toolbar instead of a page title, database note and permanently visible instruction paragraph. Help is available behind the small question-mark control. Loading and save confirmations fade automatically, while errors remain visible. The zoom controls are fixed at the lower-right edge of the viewport so they remain reachable on a large map.
|
|
|
|
The decorative square grid has been replaced by subtle A4 landscape page boundaries. Their dimensions scale with the map zoom, providing a useful indication for printing and future image export without dominating the canvas. Page boundaries can be toggled from the CMap tools menu, and the preference is retained in browser local storage.
|
|
|
|
### 0.2.69
|
|
|
|
The CMap controls now form one compact, sticky toolbar above the workspace. Map selection, New and Save remain at the left, followed by the tools menu; flexible space keeps zoom and help aligned at the right. This restores the convenient top-edge zoom controls without letting them cover the map. On narrower screens the toolbar wraps to keep every control usable.
|
|
|
|
### 0.2.70
|
|
|
|
CMaps support persistent multi-selection and logical groups. Ctrl/Command-click and Shift-click add or remove a concept or an existing group from the selection; dragging over empty canvas space selects everything intersecting the selection rectangle. Dragging one selected element moves the complete selection, while grouped elements are selected and moved together after saving and reopening the map.
|
|
|
|
The tools menu contains Select all, Group selection, Ungroup and Delete selection. The same operations are available through Ctrl/Command+A, Ctrl/Command+G, Ctrl/Command+Shift+G, Delete/Backspace and Escape. Groups are deliberately limited to one sub-CMap level so that group membership cannot contradict submap ownership. Deleting a selected submap also removes its descendants and attached relations.
|
|
|
|
### 0.2.71
|
|
|
|
The sidebar's five text links are now one compact Lucide icon row: Bookmarks, Todo, Recent, Navigation graph and CMaps. Every icon retains a translated tooltip, accessible name and visually hidden label. The EasyMDE toolbar has also moved from the Font Awesome webfont to Lucide SVG icons, allowing the Font Awesome runtime dependency to be removed completely. Lucide 1.31.0 is pinned and installed locally through the existing setup/repair procedure.
|
|
|
|
The CMap question-mark popover now contains a complete shortcut reference for multi-selection, Select all, Group, Ungroup, F2 editing, deletion, clearing the selection and saving.
|
|
|
|
### 0.2.72
|
|
|
|
Deleting a concept now also removes a linking phrase when that phrase has lost its final incoming or outgoing concept. This removes both the orphaned relation name and every remaining line segment belonging to it, while retaining a genuinely branching relation until its last endpoint on either side disappears.
|
|
|
|
Grouping is now a structural CMap operation rather than an invisible movement flag. Group selection asks for the main concept name and creates an expanded sub-CMap containing the selected items. Ungrouping a selected sub-CMap dissolves its container into a normal concept and moves its direct contents one level outward; selected child items can also be detached one level from their current inline sub-CMap.
|
|
|
|
### 0.2.73
|
|
|
|
Reopening or resetting a CMap now disposes the previous editor's marquee-selection listener and any selection rectangle that is still active. Repeated map loads therefore no longer create overlapping blue selection rectangles.
|
|
|
|
Dropping a relation dragged from a linking phrase onto empty space now creates the new concept and only the outgoing line segment. It no longer inserts a second linking phrase between the existing phrase and the new concept.
|
|
|
|
Wiki search now combines normal page results with stored concept maps. CMaps are searched by title, slug, concept labels and concept synopses. Every result is explicitly marked as either a Wiki page or Concept map and follows the correct page or CMap route when opened.
|
|
|
|
### 0.2.74
|
|
|
|
Fixes a Racket reader error in `search-pages`: the combined page/CMap search change in 0.2.73 accidentally left one surplus closing parenthesis at the end of the page result mapping.
|
|
|
|
### 0.2.75
|
|
|
|
The navigation graph is now shared by wiki pages and stored CMaps. A wiki-page node links to the pages and CMaps referenced by its rendered Markdown; a CMap node links to the pages and other stored CMaps referenced by its concepts. Page and CMap identities remain separate even when they use the same slug.
|
|
|
|
Wiki pages retain their blue graph dot and CMaps use a green dot. Activating a green node opens the corresponding CMap. CMaps also appear as incoming or outgoing neighbours in the context graph of a wiki page, and the full-graph summary reports page and CMap counts separately.
|
|
|
|
### 0.2.76
|
|
|
|
Alias-reference cleanup no longer calls the nonexistent `showToast` function. The alias administration view now has its own accessible status line and reports the number of changed pages there after cleanup.
|
|
|
|
### 0.2.77
|
|
|
|
Navigation graph, Recent, Bookmarks, Todo, Search and every administration view now have real hash routes. They therefore create normal browser-history entries instead of only replacing the visible application section while leaving the previous page URL unchanged.
|
|
|
|
Opening a graph node uses the central hash-navigation function. Returning from the navigation graph to the page that was already active before opening the graph therefore reloads that page correctly. Browser Back and Forward now retrace page, special-view, main-admin and admin-detail transitions in order.
|
|
|
|
### 0.2.78
|
|
|
|
Recent changes now combines wiki pages and stored concept maps in one chronological list. Each row is marked as either a Wiki page or Concept map, and opens through the corresponding application route. The fifty newest changes are selected after merging both storage sources.
|
|
|
|
### 0.2.79
|
|
|
|
The CMap editor now keeps an Undo/Redo history of up to one hundred complete document states. Node edits, additions, deletion, relations, grouping, sub-CMap changes, movement and resizing can be reversed without special cases for each document field. One drag or resize gesture is recorded as one history step.
|
|
|
|
Undo and Redo are available from the CMap context menu. The keyboard shortcuts are `Ctrl/Cmd+Z` for Undo and both `Ctrl/Cmd+Y` and `Ctrl/Cmd+Shift+Z` for Redo; these shortcuts are listed in the CMap help popover.
|
|
|
|
### 0.2.80
|
|
|
|
Undo transactions now end with the current browser event instead of after a 50 millisecond debounce period. Synchronous parts of one operation, such as adding a concept together with its relation, remain one Undo step. A following click or dialog confirmation, such as renaming another concept, is always recorded as a separate step.
|
|
|
|
### 0.2.81
|
|
|
|
Stored CMaps can now be renamed or removed from the CMap tools menu. Renaming changes the visible title while deliberately retaining the stable CMap address, so existing `cmap:` links keep working. It uses a dedicated version-checked storage operation and therefore does not accidentally save other unsaved editor changes.
|
|
|
|
Deletion uses the existing soft-delete storage operation, asks for confirmation and explains that existing references will remain but no longer open the removed map. After deletion, the editor opens the next available CMap or an empty CMap workspace.
|
|
|
|
The tools menu now distinguishes **Redo** from **Reload CMap**. In Dutch these are labelled **Opnieuw uitvoeren** and **CMap opnieuw laden**, removing the previous duplicate **Opnieuw** entries.
|
|
|
|
### 0.2.82
|
|
|
|
The CMap toolbar now keeps only the map combobox and tools button on its left side. **New CMap** and **Save now** have moved into the tools menu together with rename and delete, so all map-level commands are grouped in one place while zoom remains immediately available at the right.
|
|
|
|
Stored CMaps are saved automatically 1.5 seconds after a completed editor operation. A later operation restarts that delay, and changes made while a save request is in flight are queued for a following save instead of being lost. The compact status text distinguishes pending changes, automatic saving and a completed automatic save. Ctrl/Cmd+S remains available as **Save now** and is documented under that name in the help popover. The existing leave-page warning remains as a safety net when a save is still pending or has failed.
|
|
|
|
### 0.2.83
|
|
|
|
Restores the scheduling contract of the central wiki navigation guard. Version 0.2.82 made this function itself asynchronous in order to flush a pending CMap autosave before navigation. Because the same function also drives normal wiki routes, that change could leave an ordinary wiki page refresh waiting indefinitely.
|
|
|
|
CMap autosave and Ctrl/Cmd+S remain available. Navigation again follows the proven 0.2.81 flow: clean transitions proceed through the original promise chain, while the existing save/discard dialog protects a CMap whose autosave is still pending or has failed.
|
|
|
|
### 0.2.84
|
|
|
|
Creating an item and editing another item are now guaranteed to form two separate Undo transactions. Item creation and content editing start their history transaction before the CMap node is redrawn. A synchronous render callback can therefore no longer replace the pre-edit history snapshot while automatically fitting the node to its text.
|
|
|
|
The same transaction ordering is used for inline linking-phrase edits. Automatic sizing remains part of the edit that caused it, rather than becoming a separate Undo step.
|
|
|
|
### 0.2.85
|
|
|
|
Users can manage their own display name and email address from **Profile**. A password change requires the current password, keeps the active browser session and revokes the user's other sessions. Administrators can also maintain email addresses in user administration.
|
|
|
|
The login page now links to a password-reset flow. Reset tokens are random, stored only as SHA-256 hashes, expire after one hour, work once and revoke all existing sessions when used. The public response never reveals whether an account exists. The number of reset links per user in a rolling hour is configurable from **Admin → Email and password reset** and defaults to two.
|
|
|
|
SMTP uses Racket's `net/smtp` library directly. Administrators configure the public wiki URL, SMTP server/port, sender, credentials and STARTTLS in the same admin screen. Values can alternatively be supplied through `RACKET_WIKI_PUBLIC_URL`, `RACKET_WIKI_SMTP_HOST`, `RACKET_WIKI_SMTP_PORT`, `RACKET_WIKI_SMTP_FROM`, `RACKET_WIKI_SMTP_USER`, `RACKET_WIKI_SMTP_PASSWORD`, `RACKET_WIKI_SMTP_TLS`, `RACKET_WIKI_SMTP_ACCEPT_UNTRUSTED_CERTIFICATES` and `RACKET_WIKI_RESET_LIMIT`. Environment variables act as fallbacks for values not stored by the administrator.
|
|
|
|
### 0.2.86
|
|
|
|
Concept maps now have immutable database-backed version history. Creation, manual saves, autosaves and renames each store a complete CMap snapshot with version number, title, author, action, summary and timestamp. Schema migration 11 creates `concept_map_versions` and records the current state of every existing CMap as its first available historical snapshot.
|
|
|
|
### 0.2.87
|
|
|
|
Fixed startup of the password-reset mail module by importing Racket's `db` library explicitly. The module uses `query-rows`, `query-exec` and `call-with-transaction` directly and therefore must require that library itself.
|
|
|
|
### 0.2.88
|
|
|
|
The SMTP administration form now includes a test recipient and a synchronous test-email action. The test uses the current form values without storing them and keeps using an already stored SMTP password when the password field is empty. SMTP acceptance or the concrete connection/authentication error is shown next to the form. Password-reset requests retain their account-enumeration-safe response, now show a clearer next step, and write a server diagnostic when SMTP has not been configured.
|
|
|
|
### 0.2.89
|
|
|
|
STARTTLS now uses a secure client context with automatic modern TLS negotiation and SMTP-hostname certificate verification. This avoids `net/smtp`'s legacy callback argument `'tls`, which selects TLS 1.0 and can produce OpenSSL's `no protocols available` error on current systems where TLS 1.0 is disabled.
|
|
|
|
Certificate and hostname verification remain enabled by default. An administrator can explicitly disable verification for a trusted local SMTP server with a self-signed or otherwise locally invalid certificate. This exception still uses modern TLS encryption, but it does not authenticate the SMTP server and should not be used for an untrusted network or public server.
|
|
|
|
### 0.2.90
|
|
|
|
The STARTTLS choice now follows the administration setting directly. With **Accept untrusted certificates** disabled, mail uses `ssl-secure-client-context` and verifies both the certificate chain and SMTP hostname. With the setting enabled, mail uses `ssl-make-client-context 'auto`; communication remains encrypted, but the server certificate is not authenticated.
|
|
|
|
### 0.2.91
|
|
|
|
Dragging the background frame of an expanded sub-CMap once again moves the complete sub-CMap, including its main concept, descendants and nested sub-CMaps. Dragging the main concept itself continues to move only that concept within the sub-CMap.
|
|
|
|
The CMap tools menu contains **CMap history**. Historical versions can be loaded visually into the normal editor. Loading does not overwrite the current version: the historical map remains an unsaved editor state until the user explicitly saves it, at which point a new current version is created.
|
|
|
|
### 0.2.92
|
|
|
|
The CMap tools menu can create an explicitly described snapshot. Unlike autosave, this always creates a new immutable CMap version, even when the document itself has not changed since the last automatic save.
|
|
|
|
Wiki Markdown can embed a stored concept map on its own line with `{{cmap:Test}}`. The page shows a compact read-only rendition without page guides; double-clicking it opens the full CMap editor. The Markdown CMap picker can insert either the ordinary link or the embedded form.
|
|
|
|
### 0.2.93
|
|
|
|
Adds a bundled architecture documentation set consisting of twelve linked wiki
|
|
pages under namespace `racket-wiki` and two native concept maps. It documents
|
|
the current structure and behaviour together with modularity, maintainability,
|
|
analyzability, testability, long-term performance expectations, security,
|
|
coding rules and architecture evolution.
|
|
|
|
`architecture/import.rkt` validates the complete set and imports it through the
|
|
normal page and CMap storage procedures. Source hashes make repeated imports
|
|
idempotent and protect locally edited imported content. The command supports
|
|
`--dry-run`, an explicit history author and deliberate
|
|
`--overwrite-modified`. Its test submodule validates internal page links, CMap
|
|
embeds, node ids, connector endpoints and source-change detection.
|
|
|
|
### 0.2.94
|
|
|
|
Fixes two overescaped line-ending expressions in the architecture importer.
|
|
The source-marker expression now uses Racket string escapes for CR and LF,
|
|
instead of passing the invalid alphabetic escapes `\\r` and `\\n` to the
|
|
regexp parser. The CMap-embed expression uses an actual newline character in
|
|
its exclusion class for the same reason. Regression tests cover LF and CRLF
|
|
source markers and single-line CMap embeds.
|
|
|
|
### 0.2.95
|
|
|
|
CMap concepts can link to missing wiki pages by entering a new title or address; opening
|
|
that link uses the normal missing-page creation flow. Every concept can also have aspects
|
|
and a Markdown description page, defaulting to the `cmap` namespace. Separate icons open
|
|
the description and linked target, while double-clicking opens the concept editor.
|
|
|
|
The CMap document now separates stable concept identity from diagram-specific placement.
|
|
Position, size, colours and typography are retained per root/child-map context. Linked
|
|
copies made with Ctrl/Cmd+C and Ctrl/Cmd+V share their label, synopsis, aspects and link
|
|
targets while keeping independent presentation. Existing version-1 documents are upgraded
|
|
in memory when loaded.
|
|
|
|
Holding Alt while drawing a relation creates a direct arrow without a linking phrase. The
|
|
tools menu is moved outside the drawing stacking context so it remains above large maps.
|
|
The active stored CMap now shows its title and `cmap:slug`; clicking it copies ready-to-use
|
|
Markdown link syntax.
|
|
|
|
Concept descriptions now appear as rendered Markdown previews when the serif `I` is hovered
|
|
or focused. Its light-grey/white state shows whether a description exists. The compact concept
|
|
dialog adds local colour palettes, reusable style presets, normal/bold and italic controls, and
|
|
wrapped synopsis text.
|
|
|
|
Switching between stored CMaps now destroys the previous drawing surface before another one is
|
|
created. Deferred redraws from the old map are ignored, preventing duplicate layers and relations
|
|
whose visual endpoints no longer match their concepts. CMap content also scrolls underneath the
|
|
sticky toolbar instead of over it.
|
|
|
|
The sticky CMap header now paints through the main view's former top padding, so scrolled concepts
|
|
are clipped underneath the complete header area and cannot remain visible above its controls.
|
|
|
|
Deleting a concept or relation now limits orphan cleanup to linking phrases directly affected by
|
|
that deletion. Unrelated incomplete relations already present in a document are left untouched,
|
|
so deleting one relation cannot unexpectedly remove other parts of the map.
|
|
|
|
A stored relation whose source or target concept is missing no longer prevents the complete CMap
|
|
from opening. Such a damaged relation is omitted from the drawing but retained verbatim in the
|
|
document, including across automatic saves, until its remaining endpoint is explicitly deleted.
|
|
|
|
Every ordinary concept placement shows a compact `(n)` usage indicator. It counts all placements
|
|
sharing that concept identity across every active stored CMap and updates immediately for unsaved
|
|
changes in the open map. New concepts receive wiki-wide UUID-based identities. Legacy document-local
|
|
IDs are qualified with their CMap slug so coincidentally equal old IDs are never conflated.
|
|
|
|
### 0.2.96
|
|
|
|
**Make separate CMap** now creates a stored derived view of a sub-CMap instead of copying or moving
|
|
its graph. The parent remains the canonical owner of every concept and relation. Its sub-CMap can
|
|
still expand and collapse in place, while a separate arrow opens the same elements with their own
|
|
page-specific layout at a normal `cmap:slug` address.
|
|
|
|
Elements inside an expanded nested view can be hidden only in the parent context. A toolbar dropdown
|
|
lists those hidden concepts and restores them individually. The standalone view continues to contain
|
|
all elements. Relations crossing the submap boundary remain in the canonical graph; on the standalone
|
|
page they appear from the viewport edge with the external concept name, which navigates back to the
|
|
source CMap when clicked.
|
|
|
|
An expanded sub-CMap has an independently movable group frame. Its concept remains at its anchor
|
|
position and a quiet grey line connects that anchor to the group. Collapsing removes only the frame
|
|
and line; expanding restores the group at its last position for that CMap context.
|
|
|
|
The tools menu can mark the current map as the browser's start CMap. The sidebar CMaps icon and the
|
|
plain `#cmaps` route open that preference, falling back to the first available map if it no longer
|
|
exists.
|
|
|
|
For a complete installation, `racket migrate-cmap-subpages.rkt` first reports every legacy internal
|
|
page and the target slug without changing data. Run `racket migrate-cmap-subpages.rkt --apply` to
|
|
perform the conversion in one PostgreSQL transaction. The parent retains its complete document and
|
|
receives only the child-view slugs; each stored child contains a `derivedView` reference to the parent
|
|
slug and submap root id. Every preceding parent and child version remains available. A target slug is
|
|
only reused when it belongs to the earlier subpage migration; an unrelated existing map aborts the
|
|
transaction.
|
|
|
|
### 0.2.97
|
|
|
|
CMap autosave now updates only the current document and optimistic version number. It no longer
|
|
creates a history row. Explicit saves from Ctrl/Cmd-S or **Save now** retain at most the five newest
|
|
versions per CMap, while explicitly named snapshots remain unlimited. The history dialog displays
|
|
only those manual versions and snapshots and allows an editor to delete either kind after a
|
|
confirmation; deleting history never changes the current CMap.
|
|
|
|
Database migration 12 removes legacy automatic-save history rows, reclassifies recognizable legacy
|
|
manual saves, and transactionally trims those manual saves to five per CMap. Migration, repair and
|
|
other internal safety versions remain stored outside the everyday history list.
|
|
|
|
### 0.2.98
|
|
|
|
Concept headings and synopsis text now have independent text colour, font family, point size, bold
|
|
and italic controls. These presentation fields belong to the placement and its CMap context; linked
|
|
concept identity remains shared while each view can keep its own typography. Legacy concepts inherit
|
|
their existing shared typography for both text parts until an editor changes either section.
|
|
|
|
The compact colour palette is now editable. A single click applies a palette colour to the current
|
|
form, while a double-click on a palette tile opens the browser's native HTML colour picker and
|
|
replaces that reusable tile. Double-clicking the current-colour swatch opens the same picker for a
|
|
one-off colour. The palette is stored locally in the browser, whereas every concept placement stores
|
|
its selected hexadecimal colour directly; changing a palette tile therefore never recolours existing
|
|
concepts.
|
|
|
|
### 0.2.99
|
|
|
|
A wiki page now shows a **Connected CMap concepts** panel when one or more current concepts link to
|
|
that page. Concepts are grouped by stable `conceptId`; each row shows the concept label, total
|
|
placement count and clickable chips for every CMap containing it. Repeated placements in one CMap
|
|
show their local count. The panel follows page aliases and uses only current, non-archived CMap
|
|
documents—not historical versions.
|