diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index eaf004e54..94dd1ac75 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -6,6 +6,8 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [develop] + paths-ignore: + - 'doc/**' schedule: - cron: '0 13 * * 1' diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8f62eee2f..c01bcc065 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,8 @@ name: Docker on: pull_request: + paths-ignore: + - 'doc/**' push: branches: - 'develop' diff --git a/.github/workflows/lint-package-lock.yml b/.github/workflows/lint-package-lock.yml deleted file mode 100644 index f1073172a..000000000 --- a/.github/workflows/lint-package-lock.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: "Lint" - -# any branch is useful for testing before a PR is submitted -on: [push, pull_request] - -permissions: - contents: read - -jobs: - lint-package-lock: - # run on pushes to any branch - # run on PRs from external forks - if: | - (github.event_name != 'pull_request') - || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id) - name: package-lock.json - runs-on: ubuntu-latest - steps: - - - name: Checkout repository - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Run lockfile-lint on package-lock.json - run: > - npx lockfile-lint - --path src/package-lock.json - --allowed-hosts npm - --allowed-schemes https: - --allowed-schemes github: - --allowed-urls github:mapbox/node-sqlite3#593c9d498be2510d286349134537e3bf89401c4a diff --git a/doc/.vitepress/config.mts b/doc/.vitepress/config.mts index b4e190e9c..c217b572f 100644 --- a/doc/.vitepress/config.mts +++ b/doc/.vitepress/config.mts @@ -9,6 +9,9 @@ export default defineConfig({ description: "Next Generation Collaborative Document Editing", base: '/etherpad-lite/', themeConfig: { + search: { + provider: 'local' + }, // https://vitepress.dev/reference/default-theme-config nav: [ { text: 'Home', link: '/' }, @@ -49,8 +52,8 @@ export default defineConfig({ { text: 'Old Docs', items: [ - { text: 'Easysync description', link: '/easysync/easysync-full-description.pdf' }, - { text: 'Easysync notes', link: '/easysync/easysync-notes.pdf' } + { text: 'Easysync description', link: '/etherpad-lite/easysync/easysync-full-description.pdf' }, + { text: 'Easysync notes', link: '/etherpad-lite/easysync/easysync-notes.pdf' } ] } ], diff --git a/doc/api/editbar.md b/doc/api/editbar.md index f448a218a..58f5e70f9 100644 --- a/doc/api/editbar.md +++ b/doc/api/editbar.md @@ -1,14 +1,22 @@ # Editbar -src/static/js/pad_editbar.js + +Located in `src/static/js/pad_editbar.js` ## isEnabled() +If the editorbar contains the class `enabledtoolbar`, it is enabled. + + ## disable() +Disables the editorbar. This is done by adding the class `disabledtoolbar` and removing the enabledtoolbar + ## toggleDropDown(dropdown) + Shows the dropdown `div.popup` whose `id` equals `dropdown`. ## registerCommand(cmd, callback) + Register a handler for a specific command. Commands are fired if the corresponding button is clicked or the corresponding select is changed. ## registerAceCommand(cmd, callback) diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md index 834f5ac3c..7b3c27153 100644 --- a/doc/api/editorInfo.md +++ b/doc/api/editorInfo.md @@ -1,43 +1,141 @@ -# editorInfo +# EditorInfo + +Location: `src/static/js/ace2_inner.js` ## editorInfo.ace_replaceRange(start, end, text) This function replaces a range (from `start` to `end`) with `text`. ## editorInfo.ace_getRep() -Returns the `rep` object. + +Returns the `rep` object. The rep object consists of the following properties: + +- `lines`: Implemented as a skip list +- `selStart`: The start of the selection +- `selEnd`: The end of the selection +- `selFocusAtStart`: Whether the selection is focused at the start +- `alltext`: The entire text of the document +- `alines`: The entire text of the document, split into lines +- `apool`: The pool of attributes ## editorInfo.ace_getAuthor() + +Returns the authors of the pad. If the pad has no authors, it returns an empty object. + + ## editorInfo.ace_inCallStack() + +Returns true if the editor is in the call stack. + ## editorInfo.ace_inCallStackIfNecessary(?) + +Executes the function if the editor is in the call stack. + ## editorInfo.ace_focus(?) + +Focuses the editor. + ## editorInfo.ace_importText(?) + +Imports text into the editor. + ## editorInfo.ace_importAText(?) + +Imports text and attributes into the editor. + ## editorInfo.ace_exportText(?) + +Exports the text from the editor. + ## editorInfo.ace_editorChangedSize(?) + +Changes the size of the editor. + ## editorInfo.ace_setOnKeyPress(?) + +Sets the key press event. + ## editorInfo.ace_setOnKeyDown(?) + +Sets the key down event. + ## editorInfo.ace_setNotifyDirty(?) + +Sets the dirty notification. + ## editorInfo.ace_dispose(?) + +Disposes the editor. + ## editorInfo.ace_setEditable(bool) + +Sets the editor to be editable or not. + ## editorInfo.ace_execCommand(?) + +Executes a command. + ## editorInfo.ace_callWithAce(fn, callStack, normalize) + +Calls a function with the ace instance. + ## editorInfo.ace_setProperty(key, value) + +Sets a property. + ## editorInfo.ace_setBaseText(txt) + +Sets the base text. + ## editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj) + +Sets the base attributed text. + ## editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj) + +Applies changes to the base. + ## editorInfo.ace_prepareUserChangeset() + +Prepares the user changeset. + ## editorInfo.ace_applyPreparedChangesetToBase() + +Applies the prepared changeset to the base. + ## editorInfo.ace_setUserChangeNotificationCallback(f) + +Sets the user change notification callback. + ## editorInfo.ace_setAuthorInfo(author, info) + +Sets the author info. + ## editorInfo.ace_fastIncorp(?) + +Incorporates changes quickly. + ## editorInfo.ace_isCaret(?) + +Returns true if the caret is at the specified position. + ## editorInfo.ace_getLineAndCharForPoint(?) + +Returns the line and character for a point. + ## editorInfo.ace_performDocumentApplyAttributesToCharRange(?) + +Applies attributes to a character range. + ## editorInfo.ace_setAttributeOnSelection(attribute, enabled) + Sets an attribute on current range. Example: `call.editorInfo.ace_setAttributeOnSelection("turkey::balls", true); // turkey is the attribute here, balls is the value Notes: to remove the attribute pass enabled as false + ## editorInfo.ace_toggleAttributeOnSelection(?) + +Toggles an attribute on the current range. + ## editorInfo.ace_getAttributeOnSelection(attribute, prevChar) Returns a boolean if an attribute exists on a selected range. prevChar value should be true if you want to get the previous Character attribute instead of the current selection for example @@ -48,32 +146,63 @@ Example `var isItThere = documentAttributeManager.getAttributeOnSelection("turke See the ep_subscript plugin for an example of this function in action. Notes: Does not work on first or last character of a line. Suffers from a race condition if called with aceEditEvent. + ## editorInfo.ace_performSelectionChange(?) + +Performs a selection change. + ## editorInfo.ace_doIndentOutdent(?) + +Indents or outdents the selection. + ## editorInfo.ace_doUndoRedo(?) + +Undoes or redoes the last action. + ## editorInfo.ace_doInsertUnorderedList(?) + +Inserts an unordered list. + ## editorInfo.ace_doInsertOrderedList(?) + +Inserts an ordered list. + ## editorInfo.ace_performDocumentApplyAttributesToRange() +Applies attributes to a range. + ## editorInfo.ace_getAuthorInfos() Returns an info object about the author. Object key = author_id and info includes author's bg color value. Use to define your own authorship. + ## editorInfo.ace_performDocumentReplaceRange(start, end, newText) This function replaces a range (from [x1,y1] to [x2,y2]) with `newText`. + ## editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText) This function replaces a range (from y1 to y2) with `newText`. + ## editorInfo.ace_renumberList(lineNum) If you delete a line, calling this method will fix the line numbering. + ## editorInfo.ace_doReturnKey() Forces a return key at the current caret position. + ## editorInfo.ace_isBlockElement(element) -Returns true if your passed element is registered as a block element +Returns true if your passed element is registered as a block element. + ## editorInfo.ace_getLineListType(lineNum) Returns the line's html list type. + ## editorInfo.ace_caretLine() Returns X position of the caret. + ## editorInfo.ace_caretColumn() Returns Y position of the caret. + ## editorInfo.ace_caretDocChar() + Returns the Y offset starting from [x=0,y=0] + ## editorInfo.ace_isWordChar(?) + +Returns true if the character is a word character. diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 47156a920..05a66209f 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -485,7 +485,7 @@ You can pass the following values to the provided callback: Example: -``` +```js exports.authorize = (hookName, context, cb) => { const user = context.req.session.user; const path = context.req.path; // or context.resource @@ -533,7 +533,7 @@ object should come from global settings (`context.users[username]`). Example: -``` +```js exports.authenticate = (hook_name, context, cb) => { if (notApplicableToThisPlugin(context)) { return cb([]); // Let the next authentication plugin decide @@ -587,7 +587,7 @@ failure or a generic 403 page for an authorization failure). Example: -``` +```js exports.authFailure = (hookName, context, cb) => { if (notApplicableToThisPlugin(context)) { return cb([]); // Let the next plugin handle the error @@ -618,7 +618,7 @@ another plugin (if any, otherwise fall back to a generic 403 error page). Example: -``` +```js exports.preAuthzFailure = (hookName, context, cb) => { if (notApplicableToThisPlugin(context)) return cb([]); context.res.status(403).send(renderFancy403Page(context.req)); @@ -650,7 +650,7 @@ another plugin (if any, otherwise fall back to the deprecated authFailure hook). Example: -``` +```js exports.authnFailure = (hookName, context, cb) => { if (notApplicableToThisPlugin(context)) return cb([]); context.res.redirect(makeLoginURL(context.req)); @@ -678,7 +678,7 @@ another plugin (if any, otherwise fall back to the deprecated authFailure hook). Example: -``` +```js exports.authzFailure = (hookName, context, cb) => { if (notApplicableToThisPlugin(context)) return cb([]); if (needsPremiumAccount(context.req) && !context.req.session.user.premium) { @@ -792,7 +792,7 @@ If needed, you can access the user's account information (if authenticated) via Examples: -``` +```js // Using an async function exports.clientVars = async (hookName, context) => { const user = context.socket.client.request.session.user || {}; @@ -850,7 +850,7 @@ the body of the exported HTML. Example: -``` +```js exports.exportHTMLAdditionalContent = async (hookName, {padId}) => { return 'I am groot in ' + padId; }; @@ -867,7 +867,7 @@ This hook will allow a plug-in developer to append Styles to the Exported HTML. Example: -``` +```js exports.stylesForExport = function(hook, padId, cb){ cb("body{font-size:13.37em !important}"); } @@ -885,7 +885,7 @@ or provide an object whose properties will be assigned to the attributes object. Example: -``` +```js exports.aceAttribClasses = (hookName, attrs, cb) => { return cb([{ sub: 'tag:sub', @@ -904,7 +904,7 @@ This hook will allow a plug-in developer to modify the file name of an exported Example: -``` +```js exports.exportFileName = function(hook, padId, callback){ callback("newFileName"+padId); } @@ -920,7 +920,7 @@ Things in context: This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. If tags are stored as `['color', 'red']` on the attribute pool, use `exportHtmlAdditionalTagsWithData` instead. An Array should be returned. Example: -``` +```js // Add the props to be supported in export exports.exportHtmlAdditionalTags = function(hook, pad, cb){ var padId = pad.id; @@ -938,7 +938,7 @@ Things in context: Identical to `exportHtmlAdditionalTags`, but for tags that are stored with a specific value (not simply `true`) on the attribute pool. For example `['color', 'red']`, instead of `['bold', true]`. This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array of arrays should be returned. The exported HTML will contain tags like `` for the content where attributes are `['color', 'red']`. Example: -``` +```js // Add the props to be supported in export exports.exportHtmlAdditionalTagsWithData = function(hook, pad, cb){ var padId = pad.id; @@ -962,7 +962,7 @@ Context properties: None. Example: -``` +```js // Add support for exporting comments metadata exports.exportEtherpadAdditionalContent = () => ['comments']; ``` diff --git a/doc/docker.md b/doc/docker.md index 0c807746f..024eab35b 100644 --- a/doc/docker.md +++ b/doc/docker.md @@ -204,7 +204,6 @@ For the editor container, you can also make it full width by adding `full-width- | `MAX_AGE` | How long may clients use served javascript code (in seconds)? Not setting this may cause problems during deployment. Set to 0 to disable caching. | `21600` (6 hours) | | `ABIWORD` | Absolute path to the Abiword executable. Abiword is needed to get advanced import/export features of pads. Setting it to null disables Abiword and will only allow plain text and HTML import/exports. | `null` | | `SOFFICE` | This is the absolute path to the soffice executable. LibreOffice can be used in lieu of Abiword to export pads. Setting it to null disables LibreOffice exporting. | `null` | -| `TIDY_HTML` | Path to the Tidy executable. Tidy is used to improve the quality of exported pads. Setting it to null disables Tidy. | `null` | | `ALLOW_UNKNOWN_FILE_ENDS` | Allow import of file types other than the supported ones: txt, doc, docx, rtf, odt, html & htm | `true` | | `REQUIRE_AUTHENTICATION` | This setting is used if you require authentication of all users. Note: "/admin" always requires authentication. | `false` | | `REQUIRE_AUTHORIZATION` | Require authorization by a module, or a user with is_admin set, see below. | `false` | @@ -219,6 +218,24 @@ For the editor container, you can also make it full width by adding `full-width- | `DUMP_ON_UNCLEAN_EXIT` | Enable dumping objects preventing a clean exit of Node.js. WARNING: this has a significant performance impact. | `false` | | `EXPOSE_VERSION` | Expose Etherpad version in the web interface and in the Server http header. Do not enable on production machines. | `false` | +### Add plugin configurations + +It is possible to add arbitrary configurations for plugins by setting the `EP__PLUGIN____` environment variable. It is important to separate paths with a double underscore `__`. + +For example, to configure the `ep_comments` plugin to use the `comments` database, you can set the following environment variables: + +The original config looks like this: +```json +"ep_comments_page": { + "highlightSelectedText": true +}, +``` +We have two paths ep_comments_page and highlightSelectedText, so we need to set the following environment variable: + + +```yaml +EP__ep_comments_page__highlightSelectedText=true +``` ### Examples @@ -249,9 +266,79 @@ docker run -d \ Run a test instance running DirtyDB on a persistent volume: -``` +```shell docker run -d \ -v etherpad_data:/opt/etherpad-lite/var \ -p 9001:9001 \ etherpad/etherpad ``` + + + +## Ready to use Docker Compose + +```yaml +version: "3.8" + +# Add this file to extend the docker-compose setup, e.g.: +# docker-compose build --no-cache +# docker-compose up -d --build --force-recreate + +services: + app: + build: + context: . + args: + ETHERPAD_PLUGINS: + # change from development to production if needed + target: development + tty: true + stdin_open: true + volumes: + # no volume mapping of node_modules as otherwise the build-time installed plugins will be overwritten with the mount + # the same applies to package.json and pnpm-lock.yaml in root dir as these would also get overwritten and build time installed plugins will be removed + - ./src:/opt/etherpad-lite/src + - ./bin:/opt/etherpad-lite/bin + depends_on: + - postgres + environment: + # change from development to production if needed + NODE_ENV: development + ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_DEV_ADMIN_PASSWORD} + DB_CHARSET: ${DOCKER_COMPOSE_APP_DEV_ENV_DB_CHARSET:-utf8mb4} + DB_HOST: postgres + DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?} + DB_PASS: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?} + DB_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432} + DB_TYPE: "postgres" + DB_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?} + # For now, the env var DEFAULT_PAD_TEXT cannot be unset or empty; it seems to be mandatory in the latest version of etherpad + DEFAULT_PAD_TEXT: ${DOCKER_COMPOSE_APP_DEV_ENV_DEFAULT_PAD_TEXT:- } + DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DEV_ENV_DISABLE_IP_LOGGING:-true} + SOFFICE: ${DOCKER_COMPOSE_APP_DEV_ENV_SOFFICE:-null} + TRUST_PROXY: ${DOCKER_COMPOSE_APP_DEV_ENV_TRUST_PROXY:-true} + restart: always + ports: + - "${DOCKER_COMPOSE_APP_DEV_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_DEV_PORT_TARGET:-9001}" + + postgres: + image: postgres:15-alpine + # Pass config parameters to the mysql server. + # Find more information below when you need to generate the ssl-relevant file your self + environment: + POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?} + POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?} + POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432} + POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?} + PGDATA: /var/lib/postgresql/data/pgdata + restart: always + # Exposing the port is not needed unless you want to access this database instance from the host. + # Be careful when other postgres docker container are running on the same port + # ports: + # - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data/pgdata + +volumes: + postgres_data: +```