* docs: migrate useful wiki content into the VitePress manual (#7990) The GitHub wiki is being retired; documentation should ship with the software. This migrates the still-accurate, non-duplicate wiki pages into the published VitePress site (doc/**/*.md + the sidebar in doc/.vitepress/config.mts) so they are versioned, searchable and portable: - deployment.md: reverse-proxy configs (Nginx/Apache/Caddy/Traefik/ HAProxy) with the WebSocket-upgrade rules, subdirectory hosting via X-Proxy-Path, native HTTPS via the ssl block, a systemd unit, and the Istio manifest (with the Redis-adapter multi-replica caveat). - accessibility.md: editor keyboard shortcuts (verified against ace2_inner.ts / broadcast_slider.ts / pad_editbar.ts), toolbar navigation, NVDA notes. - faq.md: install methods, URL-path reference, listing/deleting pads (API-first), backup/restore, and history pruning. - development.md: source-tree tour, the pad<->format conversion pipeline, the internal DB API, and the Fontello toolbar-icon workflow. - database.md: the key/value schema plus connecting MySQL/PostgreSQL/Redis backends and a pgloader MySQL->PostgreSQL migration (database docs were previously absent from the VitePress site). Every page was checked against the current source before inclusion: corrected the apt instructions to the live signed repo (stable/main, signed-by key), dropped the unpublished snap, fixed the Redis dbSettings (flat host/port/password or url, not the obsolete client_options), dropped charset from the PostgreSQL example, and removed a phantom getEtherpad API reference. The VitePress site builds cleanly (pnpm run docs:build) with the dead-link checker enabled. Closes #7990 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: add verified hands-on changeset/atext walkthrough (#7990) Migrate the practical Changeset-library tutorial from the wiki into changeset_library.md, rewritten against the current API: unpack(), deserializeOps() (replacing the deprecated opIterator) and new AttributePool() (replacing the removed AttributePoolFactory). Every example output was produced by running the code against the current Changeset.ts / AttributePool.ts, not copied from the wiki. Also fixes a stale ether/etherpad-lite source link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 KiB
Development
This page is a contributor-oriented tour of the Etherpad source tree and of a few internals that plugin authors and core contributors commonly need to understand: how the source is laid out, how pads are converted to and from other formats, and how to access the database from server-side code.
The Etherpad server is written in TypeScript (.ts). Most server code lives
under src/node/ and most client code under src/static/js/.
Source tree overview
The repository root contains, among others, the following directories:
etherpad/
|- bin/ # maintenance and build scripts (run.sh, pad tools, docs, release)
|- doc/ # this manual, in AsciiDoc and Markdown
|- src/ # the Etherpad source code
|- packaging/ # OS/distribution packaging helpers
|- var/ # runtime data (e.g. the dirty.db database file)
bin/ contains scripts for running and maintaining Etherpad. For example
bin/run.sh starts the server, and there are TypeScript utilities such as
bin/checkPad.ts, bin/deletePad.ts, bin/repairPad.ts,
bin/rebuildPad.ts, bin/migrateDB.ts and bin/make_docs.ts.
The HTML manual is built from the AsciiDoc sources in doc/ by
bin/make_docs.ts (exposed as the makeDocs script), which shells out to
asciidoctor and writes the result to out/doc/. From the repository root you
can run it with pnpm run makeDocs. (asciidoctor must be installed.)
The src/ directory looks like this:
src/
|- locales/ # translations, managed via https://translatewiki.net
|- node/ # server-side code
|- static/ # client-side code, CSS and fonts
|- templates/ # server-rendered page templates
|- ep.json # core plugin/hook registration
|- package.json # package name: ep_etherpad-lite
src/node/ (server side)
src/node/
|- db/ # database access and pad/author/group/session state
|- eejs/ # server-side embedded-JS templating
|- handler/ # import/export and collaboration message handling
|- hooks/ # express route registration and i18n
|- security/ # crypto, OAuth2/OIDC, secret rotation
|- types/ # shared TypeScript types
|- updater/ # in-place self-update machinery
|- utils/ # settings, import/export format helpers, toolbar, minification
|- server.ts # entry point
db/ contains the modules that read and write pad state. Pad.ts manages an
individual pad; PadManager.ts, AuthorManager.ts, GroupManager.ts,
SessionManager.ts and ReadOnlyManager.ts manage the corresponding records;
DB.ts exposes the low-level key/value store (see
Accessing the database from server code / plugins); and API.ts implements
the public HTTP API.
handler/ contains the request and message handlers. PadMessageHandler.ts
drives real-time collaboration, while ImportHandler.ts and ExportHandler.ts
handle import and export.
hooks/ contains mostly Express-related code. i18n.ts builds the translation
files and registers routes to serve them, and hooks/express/ registers the
routes that serve pads, the timeslider, static assets and the admin pages.
utils/ contains the import/export format converters (ImportHtml.ts,
ExportHtml.ts, ExportTxt.ts, ExportEtherpad.ts, ImportEtherpad.ts,
ExportHelper.ts, and native converters such as ExportPdfNative.ts and
ImportDocxNative.ts), the settings parser (Settings.ts), the toolbar builder
(toolbar.ts) and the asset minifier (Minify.ts).
src/static/ (client side)
src/static/
|- css/ # stylesheets, including css/pad/icons.css
|- font/ # web fonts, including the fontawesome-etherpad icon font
|- img/
|- js/ # client-side TypeScript
|- skins/ # bundled UI skins
|- vendor/
js/ contains the client-side editor code. Notable modules include
ace2_inner.ts and ace2_common.ts (the editor core), contentcollector.ts,
linestylefilter.ts and domline.ts (content/attribute processing, shared
with the server import/export pipeline), Changeset.ts and AttributePool.ts
(the changeset and attribute model), and collab_client.ts (the
client side of real-time collaboration).
src/templates/
templates/ contains the server-rendered page templates for the index, the
pad, the timeslider and the admin pages, plus the bootstrap scripts that load
the client bundles. The templates expose named eejs blocks that plugins can
hook into to inject custom HTML.
How Etherpad converts pads to and from other formats
Internally a pad is not stored as HTML. A pad is a sequence of lines, and each
line carries attributes (for example heading1, bullet or a list number).
The set of attributes that a pad can use is stored in its attribute pool; the
pool only records which attributes exist, not where they are applied. The
pool grows over the history of the pad.
Where an attribute is applied to a line is recorded in an attribute string,
and a line that carries a line-level attribute is prefixed with a line marker
(lmkr). Attribute strings and changesets are defined by
src/static/js/Changeset.ts and src/static/js/AttributePool.ts.
Collecting content
src/static/js/contentcollector.ts is the shared starting point for both the
client (when content is typed or pasted) and the server (when content is
imported). It walks the incoming DOM/HTML, decides which attributes apply to
each line, adds the discovered attributes to the attribute pool, and emits the
resulting attribute strings. On import, src/node/utils/ImportHtml.ts calls
contentcollector.makeContentCollector(...) to do exactly this, and the HTML
import path in src/node/handler/ImportHandler.ts ultimately drives it.
From attributes to HTML/text (export)
On export the flow is, conceptually:
contentcollector.ts
-> linestylefilter.ts
-> ExportHtml.ts / ExportTxt.ts (helped by ExportHelper.ts)
-> ExportHandler.ts
-> the HTTP API / /export/* route
src/static/js/linestylefilter.tswalks each line, reads its attributes, and turns them into the classes/markup the line should render with.src/node/utils/ExportHelper.tsadds export-only logic that does not belong in the live editor. The clearest example is lists: in the editor each list item is rendered as its own line-level block, but a clean export needs the items collapsed into a single properly nested list. The helper performs that reshaping for export only.src/node/utils/ExportHtml.tsandsrc/node/utils/ExportTxt.ts(andExportEtherpad.tsfor the native.etherpadformat) turn the attributed text (atext) into the final HTML or plain text.src/node/handler/ExportHandler.tsreceives the export request and dispatches on the requested format — for instance, office formats such as.docxand.pdfare routed through the native converters / LibreOffice rather than through the plain HTML/text path.
On the client side, edits are turned into changesets by the editor, attributes
are translated into CSS classes (so heading2 becomes
class="heading2"), and src/static/js/domline.ts (createDomLine) renders
the final DOM for each line.
Accessing the database from server code / plugins
Etherpad stores everything in a single key/value store backed by
ueberDB, which abstracts over the
configured database (dirtyDB, MySQL/MariaDB, PostgreSQL, SQLite, MongoDB, Redis,
and others). Server-side code and plugins access it through
src/node/db/DB.ts.
The package name of the core module is, for historical reasons, still
ep_etherpad-lite, so plugins import the database module like this:
const db = require('ep_etherpad-lite/node/db/DB');
The exposed methods are asynchronous and return promises (use await), not the
old callback style. The available methods are get, set, remove, getSub,
setSub, findKeys and findKeysPaged:
// Read a record (returns undefined/null if it does not exist)
const value = await db.get('record_key');
// Create or replace a record
await db.set('record_key', data);
// Read or write a nested value inside a record
const colorId = await db.getSub('author_key', ['colorId']);
await db.setSub('author_key', ['email'], 'tutti@frutti.org');
// Delete a record
await db.remove('record_key');
For example, given the author record:
{"colorId":"#79d9d9","name":"tutti","timestamp":1364832712430,"padIDs":{"mypad":1}}
calling await db.setSub('author_key', ['email'], 'tutti@frutti.org') yields:
{"colorId":"#79d9d9","name":"tutti","timestamp":1364832712430,"padIDs":{"mypad":1},"email":"tutti@frutti.org"}
::: warning
Keys are namespaced (for example pad:<padId>,
pad:<padId>:revs:<rev>, globalAuthor:<authorId>). Prefer the high-level
managers (Pad.ts, AuthorManager.ts, etc.) over direct DB access where one
exists; reach for DB directly only for data your plugin owns, and use a key
prefix unique to your plugin to avoid collisions.
:::
Adding a toolbar icon
Etherpad's toolbar icons come from the bundled fontawesome-etherpad icon
font in src/static/font/. Toolbar buttons reference an icon by a
buttonicon-<name> CSS class (see src/node/utils/toolbar.ts, which builds
each button's class as buttonicon buttonicon-<name>), and those classes are
defined in src/static/css/pad/icons.css. The font itself is generated with
Fontello from src/static/font/config.json (whose
css_prefix_text is buttonicon-).
To add a new icon:
- Go to Fontello and import the existing
src/static/font/config.json(Fontello's "import" loads the current icon set and pre-selects the icons it contains). - Select the additional icon(s) you want, then click Download webfont.
- From the unzipped download, copy
config.jsonand thefont/fontawesome-etherpad.*files over the ones insrc/static/font/. - From the unzipped
css/fontawesome-etherpad.css, copy the new.buttonicon-<name>:before { content: '\\eXXX'; }rules intosrc/static/css/pad/icons.css, replacing the existing block of icon rules.
The icon is then available wherever a buttonicon-<name> class can be used,
including toolbar button definitions.