mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
* 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>
240 lines
10 KiB
Markdown
240 lines
10 KiB
Markdown
# 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](#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.ts` walks each line, reads its attributes,
|
|
and turns them into the classes/markup the line should render with.
|
|
- `src/node/utils/ExportHelper.ts` adds 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.ts` and `src/node/utils/ExportTxt.ts` (and
|
|
`ExportEtherpad.ts` for the native `.etherpad` format) turn the attributed
|
|
text (`atext`) into the final HTML or plain text.
|
|
- `src/node/handler/ExportHandler.ts` receives the export request and dispatches
|
|
on the requested format — for instance, office formats such as `.docx` and
|
|
`.pdf` are 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](https://www.npmjs.com/package/ueberdb2), 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:
|
|
|
|
```javascript
|
|
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`:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```json
|
|
{"colorId":"#79d9d9","name":"tutti","timestamp":1364832712430,"padIDs":{"mypad":1}}
|
|
```
|
|
|
|
calling `await db.setSub('author_key', ['email'], 'tutti@frutti.org')` yields:
|
|
|
|
```json
|
|
{"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](http://fontello.com) from `src/static/font/config.json` (whose
|
|
`css_prefix_text` is `buttonicon-`).
|
|
|
|
To add a new icon:
|
|
|
|
1. Go to [Fontello](http://fontello.com) and import the existing
|
|
`src/static/font/config.json` (Fontello's "import" loads the current icon
|
|
set and pre-selects the icons it contains).
|
|
2. Select the additional icon(s) you want, then click **Download webfont**.
|
|
3. From the unzipped download, copy `config.json` and the
|
|
`font/fontawesome-etherpad.*` files over the ones in `src/static/font/`.
|
|
4. From the unzipped `css/fontawesome-etherpad.css`, copy the new
|
|
`.buttonicon-<name>:before { content: '\\eXXX'; }` rules into
|
|
`src/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.
|