mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-19 14:13:34 +01:00
Merge branch 'develop'
This commit is contained in:
commit
8fa708a374
54 changed files with 5925 additions and 4418 deletions
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
end_of_line = lf
|
||||||
|
# editorconfig-tools is unable to ignore longs strings or urls
|
||||||
|
max_line_length = off
|
||||||
|
|
||||||
|
[CHANGELOG.md]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.bat]
|
||||||
|
end_of_line = crlf
|
12
.env.default
12
.env.default
|
@ -4,15 +4,15 @@
|
||||||
# Always ensure to load the env variables in every terminal session.
|
# Always ensure to load the env variables in every terminal session.
|
||||||
# Otherwise the env variables will not be available
|
# Otherwise the env variables will not be available
|
||||||
|
|
||||||
DOCKER_COMPOSE_APP_DEV_PORT_PUBLISHED=9001
|
DOCKER_COMPOSE_APP_PORT_PUBLISHED=9001
|
||||||
DOCKER_COMPOSE_APP_DEV_PORT_TARGET=9001
|
DOCKER_COMPOSE_APP_PORT_TARGET=9001
|
||||||
|
|
||||||
# IMPORTANT: When the env var DEFAULT_PAD_TEXT is unset or empty, then the pad is not established (not the landing page).
|
# IMPORTANT: When the env var DEFAULT_PAD_TEXT is unset or empty, then the pad is not established (not the landing page).
|
||||||
# The env var DEFAULT_PAD_TEXT seems to be mandatory in the latest version of etherpad.
|
# The env var DEFAULT_PAD_TEXT seems to be mandatory in the latest version of etherpad.
|
||||||
DOCKER_COMPOSE_APP_DEV_ENV_DEFAULT_PAD_TEXT="Welcome to etherpad"
|
DOCKER_COMPOSE_APP_DEV_ENV_DEFAULT_PAD_TEXT="Welcome to etherpad"
|
||||||
|
|
||||||
DOCKER_COMPOSE_APP_DEV_ADMIN_PASSWORD=
|
DOCKER_COMPOSE_APP_ADMIN_PASSWORD=
|
||||||
|
|
||||||
DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE=db
|
DOCKER_COMPOSE_POSTGRES_DATABASE=db
|
||||||
DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD=etherpad-lite-password
|
DOCKER_COMPOSE_POSTGRES_PASSWORD=etherpad-lite-password
|
||||||
DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER=etherpad-lite-user
|
DOCKER_COMPOSE_POSTGRES_USER=etherpad-lite-user
|
||||||
|
|
18
.env.dev.default
Normal file
18
.env.dev.default
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Please copy and rename this file.
|
||||||
|
#
|
||||||
|
# !Attention!
|
||||||
|
# Always ensure to load the env variables in every terminal session.
|
||||||
|
# Otherwise the env variables will not be available
|
||||||
|
|
||||||
|
DOCKER_COMPOSE_APP_DEV_PORT_PUBLISHED=9001
|
||||||
|
DOCKER_COMPOSE_APP_DEV_PORT_TARGET=9001
|
||||||
|
|
||||||
|
# IMPORTANT: When the env var DEFAULT_PAD_TEXT is unset or empty, then the pad is not established (not the landing page).
|
||||||
|
# The env var DEFAULT_PAD_TEXT seems to be mandatory in the latest version of etherpad.
|
||||||
|
DOCKER_COMPOSE_APP_DEV_ENV_DEFAULT_PAD_TEXT="Welcome to etherpad"
|
||||||
|
|
||||||
|
DOCKER_COMPOSE_APP_DEV_ADMIN_PASSWORD=
|
||||||
|
|
||||||
|
DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE=db
|
||||||
|
DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD=etherpad-lite-password
|
||||||
|
DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER=etherpad-lite-user
|
8
.github/workflows/backend-tests.yml
vendored
8
.github/workflows/backend-tests.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -93,7 +93,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -163,7 +163,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -216,7 +216,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
2
.github/workflows/build-and-deploy-docs.yml
vendored
2
.github/workflows/build-and-deploy-docs.yml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -47,7 +47,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
2
.github/workflows/frontend-admin-tests.yml
vendored
2
.github/workflows/frontend-admin-tests.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
6
.github/workflows/frontend-tests.yml
vendored
6
.github/workflows/frontend-tests.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -96,7 +96,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -163,7 +163,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
6
.github/workflows/load-test.yml
vendored
6
.github/workflows/load-test.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -76,7 +76,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -147,7 +147,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
2
.github/workflows/perform-type-check.yml
vendored
2
.github/workflows/perform-type-check.yml
vendored
|
@ -29,7 +29,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
2
.github/workflows/rate-limit.yml
vendored
2
.github/workflows/rate-limit.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
@ -38,7 +38,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Only install direct dependencies
|
- name: Only install direct dependencies
|
||||||
run: pnpm config set auto-install-peers false
|
run: pnpm config set auto-install-peers false
|
||||||
|
|
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -132,7 +132,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9.0.4
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,3 +1,13 @@
|
||||||
|
# 2.0.3
|
||||||
|
|
||||||
|
### Notable enhancements and fixes
|
||||||
|
|
||||||
|
- Added documentation for replacing apikeys with oauth2
|
||||||
|
- Bumped live plugin manager to 0.20.0. Thanks to @fgreinacher
|
||||||
|
- Added better documentation for using docker-compose with Etherpad
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 2.0.2
|
# 2.0.2
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
### Notable enhancements and fixes
|
||||||
|
|
|
@ -8,7 +8,7 @@ FROM node:alpine as adminBuild
|
||||||
|
|
||||||
WORKDIR /opt/etherpad-lite
|
WORKDIR /opt/etherpad-lite
|
||||||
COPY ./ ./
|
COPY ./ ./
|
||||||
RUN cd ./admin && npm install -g pnpm && pnpm install && pnpm run build --outDir ./dist
|
RUN cd ./admin && npm install -g pnpm@9.0.4 && pnpm install && pnpm run build --outDir ./dist
|
||||||
RUN cd ./ui && pnpm install && pnpm run build --outDir ./dist
|
RUN cd ./ui && pnpm install && pnpm run build --outDir ./dist
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ RUN mkdir -p "${EP_DIR}" && chown etherpad:etherpad "${EP_DIR}"
|
||||||
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
|
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
|
||||||
RUN \
|
RUN \
|
||||||
mkdir -p /usr/share/man/man1 && \
|
mkdir -p /usr/share/man/man1 && \
|
||||||
npm install pnpm -g && \
|
npm install pnpm@9.0.4 -g && \
|
||||||
apk update && apk upgrade && \
|
apk update && apk upgrade && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
|
57
README.md
57
README.md
|
@ -43,6 +43,63 @@ We're looking for maintainers and have some funding available. Please contact J
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Docker-Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
user: "0:0"
|
||||||
|
image: etherpad/etherpad:latest
|
||||||
|
tty: true
|
||||||
|
stdin_open: true
|
||||||
|
volumes:
|
||||||
|
- plugins:/opt/etherpad-lite/src/plugin_packages
|
||||||
|
- etherpad-var:/opt/etherpad-lite/var
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_ADMIN_PASSWORD:-admin}
|
||||||
|
DB_CHARSET: ${DOCKER_COMPOSE_APP_DB_CHARSET:-utf8mb4}
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
|
||||||
|
DB_PASS: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
|
||||||
|
DB_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
|
||||||
|
DB_TYPE: "postgres"
|
||||||
|
DB_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
|
||||||
|
# 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_DEFAULT_PAD_TEXT:- }
|
||||||
|
DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DISABLE_IP_LOGGING:-false}
|
||||||
|
SOFFICE: ${DOCKER_COMPOSE_APP_SOFFICE:-null}
|
||||||
|
TRUST_PROXY: ${DOCKER_COMPOSE_APP_TRUST_PROXY:-true}
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "${DOCKER_COMPOSE_APP_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_PORT_TARGET:-9001}"
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
|
||||||
|
POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
|
||||||
|
POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
|
||||||
|
POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
|
||||||
|
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:
|
||||||
|
plugins:
|
||||||
|
etherpad-var:
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
[Node.js](https://nodejs.org/) >= **18.18.2**.
|
[Node.js](https://nodejs.org/) >= **18.18.2**.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "admin",
|
"name": "admin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -9,31 +9,33 @@
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"@radix-ui/react-switch": "^1.0.3"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@radix-ui/react-toast": "^1.1.5",
|
||||||
"i18next": "^23.10.1",
|
"@types/react": "^18.2.79",
|
||||||
"i18next-browser-languagedetector": "^7.2.1",
|
"@types/react-dom": "^18.2.25",
|
||||||
"lucide-react": "^0.365.0",
|
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||||
"react": "^18.2.0",
|
"@typescript-eslint/parser": "^7.7.0",
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-hook-form": "^7.51.2",
|
|
||||||
"react-i18next": "^14.1.0",
|
|
||||||
"react-router-dom": "^6.22.3",
|
|
||||||
"zustand": "^4.5.2",
|
|
||||||
"@types/react": "^18.2.74",
|
|
||||||
"@types/react-dom": "^18.2.24",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
|
||||||
"@typescript-eslint/parser": "^7.5.0",
|
|
||||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
|
"i18next": "^23.11.2",
|
||||||
|
"i18next-browser-languagedetector": "^7.2.1",
|
||||||
|
"lucide-react": "^0.372.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.51.3",
|
||||||
|
"react-i18next": "^14.1.0",
|
||||||
|
"react-router-dom": "^6.22.3",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
"typescript": "^5.4.4",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.2.8",
|
"vite": "^5.2.9",
|
||||||
"vite-plugin-static-copy": "^1.0.2",
|
"vite-plugin-static-copy": "^1.0.3",
|
||||||
"vite-plugin-svgr": "^4.2.0"
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
|
"zustand": "^4.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {NavLink, Outlet, useNavigate} from "react-router-dom";
|
||||||
import {useStore} from "./store/store.ts";
|
import {useStore} from "./store/store.ts";
|
||||||
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import {Cable, Construction, Crown, NotepadText, Wrench} from "lucide-react";
|
import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall} from "lucide-react";
|
||||||
|
|
||||||
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
||||||
export const App = ()=> {
|
export const App = ()=> {
|
||||||
|
@ -97,7 +97,9 @@ export const App = ()=> {
|
||||||
<li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
|
<li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
|
||||||
<li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
|
<li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
|
||||||
<li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
<li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
||||||
<li><NavLink to={"/pads"}><NotepadText/><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
<li><NavLink to={"/pads"}><NotepadText/><Trans
|
||||||
|
i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
||||||
|
<li><NavLink to={"/shout"}><PhoneCall/>Communication</NavLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
13
admin/src/components/ShoutType.ts
Normal file
13
admin/src/components/ShoutType.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export type ShoutType = {
|
||||||
|
type: string,
|
||||||
|
data:{
|
||||||
|
type: string,
|
||||||
|
payload: {
|
||||||
|
message: {
|
||||||
|
message: string,
|
||||||
|
sticky: boolean
|
||||||
|
},
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -250,10 +250,18 @@ td, th {
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.settings {
|
.settings {
|
||||||
|
flex-grow: max(1, 1);
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 80vh;
|
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,6 +604,25 @@ pre {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.send-message {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-message input {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-message {
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-message svg {
|
||||||
|
position: absolute;
|
||||||
|
right: 3px;
|
||||||
|
bottom: -3px;
|
||||||
|
left: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
.search-field svg {
|
.search-field svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 3px;
|
left: 3px;
|
||||||
|
@ -725,3 +752,52 @@ input, button, select, optgroup, textarea {
|
||||||
right: 10px;
|
right: 10px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.SwitchRoot {
|
||||||
|
align-self: center;
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: 9999px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 2px 10px var(--black-a7);
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.SwitchRoot:focus {
|
||||||
|
box-shadow: 0 0 0 2px black;
|
||||||
|
}
|
||||||
|
.SwitchRoot[data-state='checked'] {
|
||||||
|
background-color: var(--etherpad-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.SwitchThumb {
|
||||||
|
display: block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 9999px;
|
||||||
|
box-shadow: 0 2px 2px var(--black-a7);
|
||||||
|
transition: transform 100ms;
|
||||||
|
transform: translateX(2px);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.SwitchThumb[data-state='checked'] {
|
||||||
|
transform: translateX(25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Label {
|
||||||
|
color: white;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
margin: 10px 20px 10px 10px;
|
||||||
|
border-radius: 10px 0 10px 10px;
|
||||||
|
background-color: var(--etherpad-color);
|
||||||
|
color: white
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {I18nextProvider} from "react-i18next";
|
||||||
import i18n from "./localization/i18n.ts";
|
import i18n from "./localization/i18n.ts";
|
||||||
import {PadPage} from "./pages/PadPage.tsx";
|
import {PadPage} from "./pages/PadPage.tsx";
|
||||||
import {ToastDialog} from "./utils/Toast.tsx";
|
import {ToastDialog} from "./utils/Toast.tsx";
|
||||||
|
import {ShoutPage} from "./pages/ShoutPage.tsx";
|
||||||
|
|
||||||
const router = createBrowserRouter(createRoutesFromElements(
|
const router = createBrowserRouter(createRoutesFromElements(
|
||||||
<><Route element={<App/>}>
|
<><Route element={<App/>}>
|
||||||
|
@ -20,6 +21,7 @@ const router = createBrowserRouter(createRoutesFromElements(
|
||||||
<Route path="/settings" element={<SettingsPage/>}/>
|
<Route path="/settings" element={<SettingsPage/>}/>
|
||||||
<Route path="/help" element={<HelpPage/>}/>
|
<Route path="/help" element={<HelpPage/>}/>
|
||||||
<Route path="/pads" element={<PadPage/>}/>
|
<Route path="/pads" element={<PadPage/>}/>
|
||||||
|
<Route path="/shout" element={<ShoutPage/>}/>
|
||||||
</Route><Route path="/login">
|
</Route><Route path="/login">
|
||||||
<Route index element={<LoginScreen/>}/>
|
<Route index element={<LoginScreen/>}/>
|
||||||
</Route></>
|
</Route></>
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const HelpPage = () => {
|
||||||
return <div key={hookName+i}>
|
return <div key={hookName+i}>
|
||||||
<h3>{hookName}</h3>
|
<h3>{hookName}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.keys(hooks[hookName]).map((hook, i) => <li>{hook}
|
{Object.keys(hooks[hookName]).map((hook, i) => <li key={hook+i}>{hook}
|
||||||
<ul key={hookName+hook+i}>
|
<ul key={hookName+hook+i}>
|
||||||
{Object.keys(hooks[hookName][hook]).map((subHook, i) => <li key={i}>{subHook}</li>)}
|
{Object.keys(hooks[hookName][hook]).map((subHook, i) => <li key={i}>{subHook}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -46,12 +46,12 @@ export const HelpPage = () => {
|
||||||
</div>
|
</div>
|
||||||
<h2><Trans i18nKey="admin_plugins.installed"/></h2>
|
<h2><Trans i18nKey="admin_plugins.installed"/></h2>
|
||||||
<ul>
|
<ul>
|
||||||
{helpData.installedPlugins.map((plugin, i) => <li key={i}>{plugin}</li>)}
|
{helpData.installedPlugins.map((plugin, i) => <li key={plugin+i}>{plugin}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2><Trans i18nKey="admin_plugins_info.parts"/></h2>
|
<h2><Trans i18nKey="admin_plugins_info.parts"/></h2>
|
||||||
<ul>
|
<ul>
|
||||||
{helpData.installedParts.map((part, i) => <li key={i}>{part}</li>)}
|
{helpData.installedParts.map((part, i) => <li key={part+i}>{part}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2><Trans i18nKey="admin_plugins_info.hooks"/></h2>
|
<h2><Trans i18nKey="admin_plugins_info.hooks"/></h2>
|
||||||
|
|
|
@ -100,7 +100,15 @@ export const HomePage = () => {
|
||||||
pluginsSocket!.on('results:search', (data: {
|
pluginsSocket!.on('results:search', (data: {
|
||||||
results: PluginDef[]
|
results: PluginDef[]
|
||||||
}) => {
|
}) => {
|
||||||
|
if (Array.isArray(data.results) && data.results.length > 0) {
|
||||||
setPlugins(data.results)
|
setPlugins(data.results)
|
||||||
|
} else {
|
||||||
|
useStore.getState().setToastState({
|
||||||
|
open: true,
|
||||||
|
title: "Error retrieving plugins",
|
||||||
|
success: false
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -142,7 +150,7 @@ export const HomePage = () => {
|
||||||
<tbody style={{overflow: 'auto'}}>
|
<tbody style={{overflow: 'auto'}}>
|
||||||
{sortedInstalledPlugins.map((plugin, index) => {
|
{sortedInstalledPlugins.map((plugin, index) => {
|
||||||
return <tr key={index}>
|
return <tr key={index}>
|
||||||
<td>{plugin.name}</td>
|
<td><a rel="noopener noreferrer" href={`https://npmjs.com/${plugin.name}`} target="_blank">{plugin.name}</a></td>
|
||||||
<td>{plugin.version}</td>
|
<td>{plugin.version}</td>
|
||||||
<td>
|
<td>
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const SettingsPage = ()=>{
|
||||||
const settingsSocket = useStore(state=>state.settingsSocket)
|
const settingsSocket = useStore(state=>state.settingsSocket)
|
||||||
const settings = useStore(state=>state.settings)
|
const settings = useStore(state=>state.settings)
|
||||||
|
|
||||||
return <div>
|
return <div className="settings-page">
|
||||||
<h1><Trans i18nKey="admin_settings.current"/></h1>
|
<h1><Trans i18nKey="admin_settings.current"/></h1>
|
||||||
<textarea value={settings} className="settings" onChange={v => {
|
<textarea value={settings} className="settings" onChange={v => {
|
||||||
useStore.getState().setSettings(v.target.value)
|
useStore.getState().setSettings(v.target.value)
|
||||||
|
|
76
admin/src/pages/ShoutPage.tsx
Normal file
76
admin/src/pages/ShoutPage.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {SendHorizonal} from 'lucide-react'
|
||||||
|
import {useStore} from "../store/store.ts";
|
||||||
|
import * as Switch from '@radix-ui/react-switch';
|
||||||
|
import {ShoutType} from "../components/ShoutType.ts";
|
||||||
|
|
||||||
|
export const ShoutPage = ()=>{
|
||||||
|
const [totalUsers, setTotalUsers] = useState(0);
|
||||||
|
const [message, setMessage] = useState<string>("");
|
||||||
|
const [sticky, setSticky] = useState<boolean>(false);
|
||||||
|
const socket = useStore(state => state.settingsSocket);
|
||||||
|
const [shouts, setShouts] = useState<ShoutType[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/stats')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => setTotalUsers(data.totalUsers));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(socket) {
|
||||||
|
socket.on('shout', (shout) => {
|
||||||
|
setShouts([...shouts, shout])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [socket, shouts])
|
||||||
|
|
||||||
|
const sendMessage = () => {
|
||||||
|
socket?.emit('shout', {
|
||||||
|
message,
|
||||||
|
sticky
|
||||||
|
});
|
||||||
|
setMessage('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Communication</h1>
|
||||||
|
{totalUsers > 0 && <p>There {totalUsers>1?"are":"is"} currently {totalUsers} user{totalUsers>1?"s":""} online</p>}
|
||||||
|
<div style={{height: '80vh', display: 'flex', flexDirection: 'column'}}>
|
||||||
|
<div style={{flexGrow: 1, backgroundColor: 'white', overflowY: "auto"}}>
|
||||||
|
{
|
||||||
|
shouts.map((shout) => {
|
||||||
|
return (
|
||||||
|
<div key={shout.data.payload.timestamp} className="message">
|
||||||
|
<div>{shout.data.payload.message.message}</div>
|
||||||
|
<div style={{display: 'flex'}}>
|
||||||
|
<div style={{flexGrow: 1}}></div>
|
||||||
|
<div
|
||||||
|
style={{color: "lightgray"}}>{new Date(shout.data.payload.timestamp).toLocaleTimeString()
|
||||||
|
+ " " + new Date(shout.data.payload.timestamp).toLocaleDateString()}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<form onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
sendMessage()
|
||||||
|
}} className="send-message search-field" style={{display: 'flex', gap: '10px'}}>
|
||||||
|
<Switch.Root title="Change sticky message" className="SwitchRoot" checked={sticky}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
setSticky(!sticky);
|
||||||
|
}}>
|
||||||
|
<Switch.Thumb className="SwitchThumb"/>
|
||||||
|
</Switch.Root>
|
||||||
|
<input required value={message} onChange={v=>setMessage(v.target.value)}
|
||||||
|
style={{width: '100%', paddingRight: '55px', backgroundColor: '#e0e0e0', flexGrow: 1}}/>
|
||||||
|
<SendHorizonal style={{bottom: '5px', right: '9px', color: '#0f775b'}} onClick={()=>sendMessage()}/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -28,7 +28,10 @@ export default defineConfig({
|
||||||
'/admin-auth/': {
|
'/admin-auth/': {
|
||||||
target: 'http://localhost:9001',
|
target: 'http://localhost:9001',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/admin-prox/, '/admin/')
|
},
|
||||||
|
'/stats': {
|
||||||
|
target: 'http://localhost:9001',
|
||||||
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "bin",
|
"name": "bin",
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "checkAllPads.js",
|
"main": "checkAllPads.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
@ -15,9 +15,9 @@
|
||||||
"ueberdb2": "^4.2.63"
|
"ueberdb2": "^4.2.63"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.12.5",
|
"@types/node": "^20.12.7",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"typescript": "^5.4.4"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"checkPad": "node --import tsx checkPad.ts",
|
"checkPad": "node --import tsx checkPad.ts",
|
||||||
|
|
|
@ -28,7 +28,7 @@ Portal maps the internal userid to an etherpad author.
|
||||||
#### Request
|
#### Request
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7
|
GET /api/1/createAuthorIfNotExistsFor?name=Michael&authorMapper=7
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ GET /api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7
|
||||||
> Portal maps the internal userid to an etherpad group:
|
> Portal maps the internal userid to an etherpad group:
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET http://pad.domain/api/1/createGroupIfNotExistsFor?apikey=secret&groupMapper=7
|
GET http://pad.domain/api/1/createGroupIfNotExistsFor?groupMapper=7
|
||||||
```
|
```
|
||||||
|
|
||||||
### Response
|
### Response
|
||||||
|
@ -56,7 +56,7 @@ GET http://pad.domain/api/1/createGroupIfNotExistsFor?apikey=secret&groupMapper=
|
||||||
#### Request
|
#### Request
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET http://pad.domain/api/1/createGroupPad?apikey=secret&groupID=g.s8oes9dhwrvt0zif&padName=samplePad&text=This is the first sentence in the pad
|
GET http://pad.domain/api/1/createGroupPad?groupID=g.s8oes9dhwrvt0zif&padName=samplePad&text=This is the first sentence in the pad
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Response
|
#### Response
|
||||||
|
@ -70,7 +70,7 @@ GET http://pad.domain/api/1/createGroupPad?apikey=secret&groupID=g.s8oes9dhwrvt0
|
||||||
#### Request
|
#### Request
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET http://pad.domain/api/1/createSession?apikey=secret&groupID=g.s8oes9dhwrvt0zif&authorID=a.s8oes9dhwrvt0zif&validUntil=1312201246
|
GET http://pad.domain/api/1/createSession?groupID=g.s8oes9dhwrvt0zif&authorID=a.s8oes9dhwrvt0zif&validUntil=1312201246
|
||||||
```
|
```
|
||||||
|
|
||||||
### Response
|
### Response
|
||||||
|
@ -87,7 +87,7 @@ A portal (such as WordPress) wants to transform the contents of a pad that multi
|
||||||
|
|
||||||
Portal retrieves the contents of the pad for entry into the db as a blog post:
|
Portal retrieves the contents of the pad for entry into the db as a blog post:
|
||||||
|
|
||||||
> Request: `http://pad.domain/api/1/getText?apikey=secret&padID=g.s8oes9dhwrvt0zif$123`
|
> Request: `http://pad.domain/api/1/getText?&padID=g.s8oes9dhwrvt0zif$123`
|
||||||
>
|
>
|
||||||
> Response: `{code: 0, message:"ok", data: {text:"Welcome Text"}}`
|
> Response: `{code: 0, message:"ok", data: {text:"Welcome Text"}}`
|
||||||
|
|
||||||
|
@ -108,23 +108,23 @@ The API is accessible via HTTP. Starting from **1.8**, API endpoints can be invo
|
||||||
|
|
||||||
The URL of the HTTP request is of the form: `/api/$APIVERSION/$FUNCTIONNAME`. $APIVERSION depends on the endpoint you want to use. Depending on the verb you use (GET or POST) **parameters** can be passed differently.
|
The URL of the HTTP request is of the form: `/api/$APIVERSION/$FUNCTIONNAME`. $APIVERSION depends on the endpoint you want to use. Depending on the verb you use (GET or POST) **parameters** can be passed differently.
|
||||||
|
|
||||||
When invoking via GET (mandatory until **1.7.5** included), parameters must be included in the query string (example: `/api/$APIVERSION/$FUNCTIONNAME?apikey=<APIKEY>¶m1=value1`). Please note that starting with nodejs 8.14+ the total size of HTTP request headers has been capped to 8192 bytes. This limits the quantity of data that can be sent in an API request.
|
When invoking via GET (mandatory until **1.7.5** included), parameters must be included in the query string (example: `/api/$APIVERSION/$FUNCTIONNAME?param1=value1`). Please note that starting with nodejs 8.14+ the total size of HTTP request headers has been capped to 8192 bytes. This limits the quantity of data that can be sent in an API request.
|
||||||
|
|
||||||
Starting from Etherpad **1.8** it is also possible to invoke the HTTP API via POST. In this case, querystring parameters will still be accepted, but **any parameter with the same name sent via POST will take precedence**. If you need to send large chunks of text (for example, for `setText()`) it is advisable to invoke via POST.
|
Starting from Etherpad **1.8** it is also possible to invoke the HTTP API via POST. In this case, querystring parameters will still be accepted, but **any parameter with the same name sent via POST will take precedence**. If you need to send large chunks of text (for example, for `setText()`) it is advisable to invoke via POST.
|
||||||
|
|
||||||
Example with cURL using GET (toy example, no encoding):
|
Example with cURL using GET (toy example, no encoding):
|
||||||
```
|
```
|
||||||
curl "http://pad.domain/api/1/setText?apikey=secret&padID=padname&text=this_text_will_NOT_be_encoded_by_curl_use_next_example"
|
curl "http://pad.domain/api/1/setText?padID=padname&text=this_text_will_NOT_be_encoded_by_curl_use_next_example"
|
||||||
```
|
```
|
||||||
|
|
||||||
Example with cURL using GET (better example, encodes text):
|
Example with cURL using GET (better example, encodes text):
|
||||||
```
|
```
|
||||||
curl "http://pad.domain/api/1/setText?apikey=secret&padID=padname" --get --data-urlencode "text=Text sent via GET with proper encoding. For big documents, please use POST"
|
curl "http://pad.domain/api/1/setText?padID=padname" --get --data-urlencode "text=Text sent via GET with proper encoding. For big documents, please use POST"
|
||||||
```
|
```
|
||||||
|
|
||||||
Example with cURL using POST:
|
Example with cURL using POST:
|
||||||
```
|
```
|
||||||
curl "http://pad.domain/api/1/setText?apikey=secret&padID=padname" --data-urlencode "text=Text sent via POST with proper encoding. For big texts (>8 KB), use this method"
|
curl "http://pad.domain/api/1/setText?padID=padname" --data-urlencode "text=Text sent via POST with proper encoding. For big texts (>8 KB), use this method"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Response Format
|
### Response Format
|
||||||
|
@ -161,7 +161,49 @@ Responses are valid JSON in the following format:
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
Authentication works via a token that is sent with each request as a post parameter. There is a single token per Etherpad deployment. This token will be random string, generated by Etherpad at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad. Only Etherpad and the requesting application knows this key. Token management will not be exposed through this API.
|
Authentication works via an OAuth token that is sent with each request as an Authorization header, i.e. `Authorization: Bearer YOUR_TOKEN`. You can add new clients that can sign in via the API by adding new entries to the sso section in the settings.json.
|
||||||
|
|
||||||
|
|
||||||
|
#### Example for browser login clients
|
||||||
|
|
||||||
|
This example illustrates how to add a new client that can sign in via the API using the browser login method. This method is used for users trying to sign in to the API via the browser. You can log in with the users in the settings.json file. The redirect URI is the URL where the user is redirected after the login. This is normally your etherpad instance url.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"client_id": "admin_client",
|
||||||
|
"client_secret": "admin",
|
||||||
|
"grant_types": ["authorization_code"],
|
||||||
|
"response_types": ["code"],
|
||||||
|
"redirect_uris": ["http://my-etherpad-instance.com"],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Example for services
|
||||||
|
|
||||||
|
This example illustrates how to add a new client that can sign in via the API using the client credentials method. This method is used for services trying to sign in to the API where there is no browser.
|
||||||
|
E.g. a service that creates a pad for a user or a service that inserts a text into a pad. Just make sure that the secret is complex enough as anybody who knows the secret can access the API.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"client_id": "client_credentials",
|
||||||
|
"redirect_uris": [],
|
||||||
|
"response_types": [],
|
||||||
|
"grant_types": ["code"],
|
||||||
|
"client_secret": "client_credentials",
|
||||||
|
"extraParams": [
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"value": "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Obtain a Bearer token:
|
||||||
|
|
||||||
|
`curl --request POST --url 'https://your.server.tld/oidc/token' --header 'content-type: application/x-www-form-urlencoded' --data grant_type=client_credentials --data client_id=client_credentials --data client_secret=client_credentials`
|
||||||
|
|
||||||
|
|
||||||
### Node Interoperability
|
### Node Interoperability
|
||||||
|
|
||||||
|
|
|
@ -278,58 +278,43 @@ docker run -d \
|
||||||
## Ready to use Docker Compose
|
## Ready to use Docker Compose
|
||||||
|
|
||||||
```yaml
|
```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:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
user: "0:0"
|
||||||
context: .
|
image: etherpad/etherpad:latest
|
||||||
args:
|
|
||||||
ETHERPAD_PLUGINS:
|
|
||||||
# change from development to production if needed
|
|
||||||
target: development
|
|
||||||
tty: true
|
tty: true
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
volumes:
|
volumes:
|
||||||
# no volume mapping of node_modules as otherwise the build-time installed plugins will be overwritten with the mount
|
- plugins:/opt/etherpad-lite/src/plugin_packages
|
||||||
# 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
|
- etherpad-var:/opt/etherpad-lite/var
|
||||||
- ./src:/opt/etherpad-lite/src
|
|
||||||
- ./bin:/opt/etherpad-lite/bin
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
environment:
|
environment:
|
||||||
# change from development to production if needed
|
NODE_ENV: production
|
||||||
NODE_ENV: development
|
ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_ADMIN_PASSWORD:-admin}
|
||||||
ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_DEV_ADMIN_PASSWORD}
|
DB_CHARSET: ${DOCKER_COMPOSE_APP_DB_CHARSET:-utf8mb4}
|
||||||
DB_CHARSET: ${DOCKER_COMPOSE_APP_DEV_ENV_DB_CHARSET:-utf8mb4}
|
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?}
|
DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
|
||||||
DB_PASS: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?}
|
DB_PASS: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
|
||||||
DB_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432}
|
DB_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
|
||||||
DB_TYPE: "postgres"
|
DB_TYPE: "postgres"
|
||||||
DB_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?}
|
DB_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
|
||||||
# For now, the env var DEFAULT_PAD_TEXT cannot be unset or empty; it seems to be mandatory in the latest version of etherpad
|
# 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:- }
|
DEFAULT_PAD_TEXT: ${DOCKER_COMPOSE_APP_DEFAULT_PAD_TEXT:- }
|
||||||
DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DEV_ENV_DISABLE_IP_LOGGING:-true}
|
DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DISABLE_IP_LOGGING:-false}
|
||||||
SOFFICE: ${DOCKER_COMPOSE_APP_DEV_ENV_SOFFICE:-null}
|
SOFFICE: ${DOCKER_COMPOSE_APP_SOFFICE:-null}
|
||||||
TRUST_PROXY: ${DOCKER_COMPOSE_APP_DEV_ENV_TRUST_PROXY:-true}
|
TRUST_PROXY: ${DOCKER_COMPOSE_APP_TRUST_PROXY:-true}
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "${DOCKER_COMPOSE_APP_DEV_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_DEV_PORT_TARGET:-9001}"
|
- "${DOCKER_COMPOSE_APP_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_PORT_TARGET:-9001}"
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
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:
|
environment:
|
||||||
POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?}
|
POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
|
||||||
POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?}
|
POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
|
||||||
POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432}
|
POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
|
||||||
POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?}
|
POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
|
||||||
PGDATA: /var/lib/postgresql/data/pgdata
|
PGDATA: /var/lib/postgresql/data/pgdata
|
||||||
restart: always
|
restart: always
|
||||||
# Exposing the port is not needed unless you want to access this database instance from the host.
|
# Exposing the port is not needed unless you want to access this database instance from the host.
|
||||||
|
@ -341,4 +326,6 @@ services:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
plugins:
|
||||||
|
etherpad-var:
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vitepress": "^1.0.2"
|
"vitepress": "^1.1.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docs:dev": "vitepress dev",
|
"docs:dev": "vitepress dev",
|
||||||
|
|
63
docker-compose.dev.yml
Normal file
63
docker-compose.dev.yml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
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:
|
|
@ -1,55 +1,40 @@
|
||||||
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:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
user: "0:0"
|
||||||
context: .
|
image: etherpad/etherpad:latest
|
||||||
args:
|
|
||||||
ETHERPAD_PLUGINS:
|
|
||||||
# change from development to production if needed
|
|
||||||
target: development
|
|
||||||
tty: true
|
tty: true
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
volumes:
|
volumes:
|
||||||
# no volume mapping of node_modules as otherwise the build-time installed plugins will be overwritten with the mount
|
- plugins:/opt/etherpad-lite/src/plugin_packages
|
||||||
# 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
|
- etherpad-var:/opt/etherpad-lite/var
|
||||||
- ./src:/opt/etherpad-lite/src
|
|
||||||
- ./bin:/opt/etherpad-lite/bin
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
environment:
|
environment:
|
||||||
# change from development to production if needed
|
NODE_ENV: production
|
||||||
NODE_ENV: development
|
ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_ADMIN_PASSWORD:-admin}
|
||||||
ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_DEV_ADMIN_PASSWORD}
|
DB_CHARSET: ${DOCKER_COMPOSE_APP_DB_CHARSET:-utf8mb4}
|
||||||
DB_CHARSET: ${DOCKER_COMPOSE_APP_DEV_ENV_DB_CHARSET:-utf8mb4}
|
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?}
|
DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
|
||||||
DB_PASS: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?}
|
DB_PASS: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
|
||||||
DB_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432}
|
DB_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
|
||||||
DB_TYPE: "postgres"
|
DB_TYPE: "postgres"
|
||||||
DB_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?}
|
DB_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
|
||||||
# For now, the env var DEFAULT_PAD_TEXT cannot be unset or empty; it seems to be mandatory in the latest version of etherpad
|
# 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:- }
|
DEFAULT_PAD_TEXT: ${DOCKER_COMPOSE_APP_DEFAULT_PAD_TEXT:- }
|
||||||
DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DEV_ENV_DISABLE_IP_LOGGING:-true}
|
DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DISABLE_IP_LOGGING:-false}
|
||||||
SOFFICE: ${DOCKER_COMPOSE_APP_DEV_ENV_SOFFICE:-null}
|
SOFFICE: ${DOCKER_COMPOSE_APP_SOFFICE:-null}
|
||||||
TRUST_PROXY: ${DOCKER_COMPOSE_APP_DEV_ENV_TRUST_PROXY:-true}
|
TRUST_PROXY: ${DOCKER_COMPOSE_APP_TRUST_PROXY:-true}
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "${DOCKER_COMPOSE_APP_DEV_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_DEV_PORT_TARGET:-9001}"
|
- "${DOCKER_COMPOSE_APP_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_PORT_TARGET:-9001}"
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
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:
|
environment:
|
||||||
POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?}
|
POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
|
||||||
POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?}
|
POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
|
||||||
POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432}
|
POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
|
||||||
POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?}
|
POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
|
||||||
PGDATA: /var/lib/postgresql/data/pgdata
|
PGDATA: /var/lib/postgresql/data/pgdata
|
||||||
restart: always
|
restart: always
|
||||||
# Exposing the port is not needed unless you want to access this database instance from the host.
|
# Exposing the port is not needed unless you want to access this database instance from the host.
|
||||||
|
@ -61,3 +46,5 @@ services:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
plugins:
|
||||||
|
etherpad-var:
|
||||||
|
|
|
@ -43,6 +43,6 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/ether/etherpad-lite.git"
|
"url": "https://github.com/ether/etherpad-lite.git"
|
||||||
},
|
},
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
}
|
}
|
||||||
|
|
9120
pnpm-lock.yaml
9120
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -669,5 +669,16 @@
|
||||||
"redirect_uris": ["${USER_REDIRECT:http://localhost:9001/}"]
|
"redirect_uris": ["${USER_REDIRECT:http://localhost:9001/}"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Set the time to live for the tokens
|
||||||
|
This is the time of seconds a user is logged into Etherpad
|
||||||
|
"ttl": {
|
||||||
|
"AccessToken": 3600,
|
||||||
|
"AuthorizationCode": 600,
|
||||||
|
"ClientCredentials": 3600,
|
||||||
|
"IdToken": 3600,
|
||||||
|
"RefreshToken": 86400
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -671,4 +671,15 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set the time to live for the tokens
|
||||||
|
This is the time of seconds a user is logged into Etherpad
|
||||||
|
"ttl": {
|
||||||
|
"AccessToken": 3600,
|
||||||
|
"AuthorizationCode": 600,
|
||||||
|
"ClientCredentials": 3600,
|
||||||
|
"IdToken": 3600,
|
||||||
|
"RefreshToken": 86400
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,12 @@
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"expressPreSession": "ep_etherpad-lite/node/hooks/express/openapi"
|
"expressPreSession": "ep_etherpad-lite/node/hooks/express/openapi"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ep_message_all",
|
||||||
|
"client_hooks": {
|
||||||
|
"handleClientMessage_shoutMessage": "ep_etherpad-lite/static/js/messageHandler"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"MuratTheTurkish",
|
"MuratTheTurkish",
|
||||||
"Mushviq Abdulla",
|
"Mushviq Abdulla",
|
||||||
"NMW03",
|
"NMW03",
|
||||||
|
"Nemoralis",
|
||||||
"Neriman2003",
|
"Neriman2003",
|
||||||
"Vesely35",
|
"Vesely35",
|
||||||
"Wertuose"
|
"Wertuose"
|
||||||
|
|
|
@ -55,10 +55,10 @@
|
||||||
"pad.toolbar.import_export.title": "Файлланы башха форматларын (а/дан) импорт/экспорт",
|
"pad.toolbar.import_export.title": "Файлланы башха форматларын (а/дан) импорт/экспорт",
|
||||||
"pad.toolbar.timeslider.title": "Заман шкала",
|
"pad.toolbar.timeslider.title": "Заман шкала",
|
||||||
"pad.toolbar.savedRevision.title": "Версияны сакъла",
|
"pad.toolbar.savedRevision.title": "Версияны сакъла",
|
||||||
"pad.toolbar.settings.title": "Джарашдырыула",
|
"pad.toolbar.settings.title": "Джарашдырыўла",
|
||||||
"pad.toolbar.embed.title": "Бу блокнотну Джай эмда Ичине сал",
|
"pad.toolbar.embed.title": "Бу блокнотну Джай эмда Ичине сал",
|
||||||
"pad.toolbar.showusers.title": "Хайырланыучуланы бу блокнотда кёргюзт",
|
"pad.toolbar.showusers.title": "Хайырланыучуланы бу блокнотда кёргюзт",
|
||||||
"pad.colorpicker.save": "Сакъла",
|
"pad.colorpicker.save": "Сакъландыр",
|
||||||
"pad.colorpicker.cancel": "Ызына ал",
|
"pad.colorpicker.cancel": "Ызына ал",
|
||||||
"pad.loading": "Джюклениу...",
|
"pad.loading": "Джюклениу...",
|
||||||
"pad.noCookie": "Куки табылмадыла. Бразуеригизде кукилени бир джандырсагъыз! Сизни кириулеригизни арасында сессиягъыз эмда джарашдырыуларыгъыз сакъланныкъ тюлдюле. Буну чуруму, бир къауум браузерледе Etherpad iFrame ичинде болгъаны болургъа болур. Тилейбиз, Etherpad эмда аны башындагъы iFrame бир тюбдоменде/доменде болгъанындан ишексиз болугъуз.",
|
"pad.noCookie": "Куки табылмадыла. Бразуеригизде кукилени бир джандырсагъыз! Сизни кириулеригизни арасында сессиягъыз эмда джарашдырыуларыгъыз сакъланныкъ тюлдюле. Буну чуруму, бир къауум браузерледе Etherpad iFrame ичинде болгъаны болургъа болур. Тилейбиз, Etherpad эмда аны башындагъы iFrame бир тюбдоменде/доменде болгъанындан ишексиз болугъуз.",
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
"pad.modals.reconnecting": "Блокнотугъузгъа джангыдан байлана турады...",
|
"pad.modals.reconnecting": "Блокнотугъузгъа джангыдан байлана турады...",
|
||||||
"pad.modals.forcereconnect": "Джангыдан зор бла байланыу",
|
"pad.modals.forcereconnect": "Джангыдан зор бла байланыу",
|
||||||
"pad.modals.reconnecttimer": "Джангыдан байланыргъа кюрешеди",
|
"pad.modals.reconnecttimer": "Джангыдан байланыргъа кюрешеди",
|
||||||
"pad.modals.cancel": "Ызына алыу",
|
"pad.modals.cancel": "Ызына ал",
|
||||||
"pad.modals.userdup": "Башха терезеде ачыкъды",
|
"pad.modals.userdup": "Башха терезеде ачыкъды",
|
||||||
"pad.modals.userdup.explanation": "Бу блокнот, бу компьютерде бирден аслам бразуре терезеде ачылгъаннга ушайды.",
|
"pad.modals.userdup.explanation": "Бу блокнот, бу компьютерде бирден аслам бразуре терезеде ачылгъаннга ушайды.",
|
||||||
"pad.modals.userdup.advice": "Бу терезени хайырланыб джангыдан байлан",
|
"pad.modals.userdup.advice": "Бу терезени хайырланыб джангыдан байлан",
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
"pad.settings.chatandusers": "Prikaži klepet in uporabnike",
|
"pad.settings.chatandusers": "Prikaži klepet in uporabnike",
|
||||||
"pad.settings.colorcheck": "Barve avtorstva",
|
"pad.settings.colorcheck": "Barve avtorstva",
|
||||||
"pad.settings.linenocheck": "Številke vrstic",
|
"pad.settings.linenocheck": "Številke vrstic",
|
||||||
"pad.settings.rtlcheck": "Ali naj se vsebina bera od desne proti levi?",
|
"pad.settings.rtlcheck": "Ali naj se vsebina bere od desne proti levi?",
|
||||||
"pad.settings.fontType": "Vrsta pisave:",
|
"pad.settings.fontType": "Vrsta pisave:",
|
||||||
"pad.settings.fontType.normal": "Normalno",
|
"pad.settings.fontType.normal": "Normalno",
|
||||||
"pad.settings.language": "Jezik:",
|
"pad.settings.language": "Jezik:",
|
||||||
|
|
|
@ -88,7 +88,7 @@ exports.socketio = () => {
|
||||||
const sessioninfos:MapArrayType<any> = {};
|
const sessioninfos:MapArrayType<any> = {};
|
||||||
exports.sessioninfos = sessioninfos;
|
exports.sessioninfos = sessioninfos;
|
||||||
|
|
||||||
stats.gauge('totalUsers', () => socketio ? socketio.sockets.size : 0);
|
stats.gauge('totalUsers', () => socketio ? socketio.engine.clientsCount : 0);
|
||||||
stats.gauge('activePads', () => {
|
stats.gauge('activePads', () => {
|
||||||
const padIds = new Set();
|
const padIds = new Set();
|
||||||
for (const {padId} of Object.values(sessioninfos)) {
|
for (const {padId} of Object.values(sessioninfos)) {
|
||||||
|
|
|
@ -87,6 +87,7 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
socket.on('uninstall', (pluginName:string) => {
|
socket.on('uninstall', (pluginName:string) => {
|
||||||
uninstall(pluginName, (err:ErrorCaused) => {
|
uninstall(pluginName, (err:ErrorCaused) => {
|
||||||
if (err) console.warn(err.stack || err.toString());
|
if (err) console.warn(err.stack || err.toString());
|
||||||
|
|
|
@ -45,6 +45,28 @@ exports.socketio = (hookName:string, {io}:any) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
type ShoutMessage = {
|
||||||
|
message: string,
|
||||||
|
sticky: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('shout', (message: ShoutMessage) => {
|
||||||
|
const messageToSend = {
|
||||||
|
type: "COLLABROOM",
|
||||||
|
data: {
|
||||||
|
type: "shoutMessage",
|
||||||
|
payload: {
|
||||||
|
message: message,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.of('/settings').emit('shout', messageToSend);
|
||||||
|
io.sockets.emit('shout', messageToSend);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
socket.on('help', () => {
|
socket.on('help', () => {
|
||||||
const gitCommit = settings.getGitCommit();
|
const gitCommit = settings.getGitCommit();
|
||||||
const epVersion = settings.getEpVersion();
|
const epVersion = settings.getEpVersion();
|
||||||
|
@ -130,7 +152,8 @@ exports.socketio = (hookName:string, {io}:any) => {
|
||||||
lastEdited,
|
lastEdited,
|
||||||
userCount,
|
userCount,
|
||||||
revisionNumber
|
revisionNumber
|
||||||
}}));
|
}
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
const currentWinners: PadQueryResult[] = []
|
const currentWinners: PadQueryResult[] = []
|
||||||
let queryOffsetCounter = 0
|
let queryOffsetCounter = 0
|
||||||
|
@ -204,7 +227,6 @@ exports.socketio = (hookName:string, {io}:any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const searchPad = async (query: PadSearchQuery) => {
|
const searchPad = async (query: PadSearchQuery) => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import express, {Request, Response} from 'express';
|
||||||
import {format} from 'url'
|
import {format} from 'url'
|
||||||
import {ParsedUrlQuery} from "node:querystring";
|
import {ParsedUrlQuery} from "node:querystring";
|
||||||
import {Http2ServerRequest, Http2ServerResponse} from "node:http2";
|
import {Http2ServerRequest, Http2ServerResponse} from "node:http2";
|
||||||
|
import {MapArrayType} from "../types/MapType";
|
||||||
|
|
||||||
const configuration: Configuration = {
|
const configuration: Configuration = {
|
||||||
scopes: ['openid', 'profile', 'email'],
|
scopes: ['openid', 'profile', 'email'],
|
||||||
|
@ -19,7 +20,6 @@ const configuration: Configuration = {
|
||||||
is_admin: boolean;
|
is_admin: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersArray1 = Object.keys(users).map((username) => ({
|
const usersArray1 = Object.keys(users).map((username) => ({
|
||||||
username,
|
username,
|
||||||
...users[username]
|
...users[username]
|
||||||
|
@ -47,13 +47,7 @@ const configuration: Configuration = {
|
||||||
} as Account
|
} as Account
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ttl:{
|
ttl: settings.ttl,
|
||||||
AccessToken: 1 * 60 * 60, // 1 hour in seconds
|
|
||||||
AuthorizationCode: 10 * 60, // 10 minutes in seconds
|
|
||||||
ClientCredentials: 1 * 60 * 60, // 1 hour in seconds
|
|
||||||
IdToken: 1 * 60 * 60, // 1 hour in seconds
|
|
||||||
RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
|
|
||||||
},
|
|
||||||
claims: {
|
claims: {
|
||||||
openid: ['sub'],
|
openid: ['sub'],
|
||||||
email: ['email'],
|
email: ['email'],
|
||||||
|
@ -99,28 +93,29 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
||||||
features:{
|
features:{
|
||||||
userinfo: {enabled: true},
|
userinfo: {enabled: true},
|
||||||
claimsParameter: {enabled: true},
|
claimsParameter: {enabled: true},
|
||||||
|
clientCredentials: {enabled: true},
|
||||||
devInteractions: {enabled: false},
|
devInteractions: {enabled: false},
|
||||||
resourceIndicators: {enabled: true, defaultResource(ctx) {
|
resourceIndicators: {enabled: true, defaultResource(ctx) {
|
||||||
return ctx.origin;
|
return ctx.origin;
|
||||||
},
|
},
|
||||||
getResourceServerInfo(ctx, resourceIndicator, client) {
|
getResourceServerInfo(ctx, resourceIndicator, client) {
|
||||||
return {
|
return {
|
||||||
scope: client.scope as string,
|
scope: "openid",
|
||||||
audience: 'account',
|
audience: 'account',
|
||||||
accessTokenFormat: 'jwt',
|
accessTokenFormat: 'jwt',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
useGrantedResource(ctx, model) {
|
useGrantedResource(ctx, model) {
|
||||||
return true;
|
return true;
|
||||||
},},
|
},
|
||||||
|
},
|
||||||
jwtResponseModes: {enabled: true},
|
jwtResponseModes: {enabled: true},
|
||||||
},
|
},
|
||||||
clientBasedCORS: (ctx, origin, client) => {
|
clientBasedCORS: (ctx, origin, client) => {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
extraParams: [],
|
||||||
extraTokenClaims: async (ctx, token) => {
|
extraTokenClaims: async (ctx, token) => {
|
||||||
|
|
||||||
|
|
||||||
if(token.kind === 'AccessToken') {
|
if(token.kind === 'AccessToken') {
|
||||||
// Add your custom claims here. For example:
|
// Add your custom claims here. For example:
|
||||||
const users = settings.users as {
|
const users = settings.users as {
|
||||||
|
@ -139,6 +134,19 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
||||||
return {
|
return {
|
||||||
admin: account?.is_admin
|
admin: account?.is_admin
|
||||||
};
|
};
|
||||||
|
} else if (token.kind === "ClientCredentials") {
|
||||||
|
let extraParams: MapArrayType<string> = {}
|
||||||
|
|
||||||
|
settings.sso.clients
|
||||||
|
.filter((client:any) => client.client_id === token.clientId)
|
||||||
|
.forEach((client:any) => {
|
||||||
|
if(client.extraParams !== undefined) {
|
||||||
|
client.extraParams.forEach((param:any) => {
|
||||||
|
extraParams[param.name] = param.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return extraParams
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clients: settings.sso.clients
|
clients: settings.sso.clients
|
||||||
|
@ -252,7 +260,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
||||||
|
|
||||||
args.app.use('/views/', express.static(path.join(settings.root,'src','static', 'oidc'), {maxAge: 1000 * 60 * 60 * 24}));
|
args.app.use('/views/', express.static(path.join(settings.root,'src','static', 'oidc'), {maxAge: 1000 * 60 * 60 * 24}));
|
||||||
|
|
||||||
/*
|
|
||||||
oidc.on('authorization.error', (ctx, error) => {
|
oidc.on('authorization.error', (ctx, error) => {
|
||||||
console.log('authorization.error', error);
|
console.log('authorization.error', error);
|
||||||
})
|
})
|
||||||
|
@ -268,7 +276,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
||||||
})
|
})
|
||||||
oidc.on('revocation.error', (ctx, error) => {
|
oidc.on('revocation.error', (ctx, error) => {
|
||||||
console.log('revocation.error', error);
|
console.log('revocation.error', error);
|
||||||
})*/
|
})
|
||||||
args.app.use("/oidc", oidc.callback());
|
args.app.use("/oidc", oidc.callback());
|
||||||
//cb();
|
//cb();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {ErrorCaused} from "./types/ErrorCaused";
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import pkg from '../package.json';
|
import pkg from '../package.json';
|
||||||
import {checkForMigration} from "../static/js/pluginfw/installer";
|
import {checkForMigration} from "../static/js/pluginfw/installer";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const settings = require('./utils/Settings');
|
const settings = require('./utils/Settings');
|
||||||
|
|
||||||
|
@ -37,6 +38,28 @@ if (settings.dumpOnUncleanExit) {
|
||||||
wtfnode = require('wtfnode');
|
wtfnode = require('wtfnode');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const addProxyToAxios = (url: URL) => {
|
||||||
|
axios.defaults.proxy = {
|
||||||
|
host: url.hostname,
|
||||||
|
port: Number(url.port),
|
||||||
|
protocol: url.protocol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(process.env['http_proxy']) {
|
||||||
|
console.log("Using proxy: " + process.env['http_proxy'])
|
||||||
|
addProxyToAxios(new URL(process.env['http_proxy']));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (process.env['https_proxy']) {
|
||||||
|
console.log("Using proxy: " + process.env['https_proxy'])
|
||||||
|
addProxyToAxios(new URL(process.env['https_proxy']));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* early check for version compatibility before calling
|
* early check for version compatibility before calling
|
||||||
* any modules that require newer versions of NodeJS
|
* any modules that require newer versions of NodeJS
|
||||||
|
|
|
@ -98,6 +98,16 @@ exports.title = 'Etherpad';
|
||||||
*/
|
*/
|
||||||
exports.favicon = null;
|
exports.favicon = null;
|
||||||
|
|
||||||
|
exports.ttl = {
|
||||||
|
AccessToken: 1 * 60 * 60, // 1 hour in seconds
|
||||||
|
AuthorizationCode: 10 * 60, // 10 minutes in seconds
|
||||||
|
ClientCredentials: 1 * 60 * 60, // 1 hour in seconds
|
||||||
|
IdToken: 1 * 60 * 60, // 1 hour in seconds
|
||||||
|
RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Skin name.
|
* Skin name.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const settings = require('./Settings');
|
const settings = require('./Settings');
|
||||||
const axios = require('axios');
|
import axios from 'axios';
|
||||||
const headers = {
|
const headers = {
|
||||||
'User-Agent': 'Etherpad/' + settings.getEpVersion(),
|
'User-Agent': 'Etherpad/' + settings.getEpVersion(),
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ let lastLoadingTime: number | null = null;
|
||||||
|
|
||||||
const loadEtherpadInformations = () => {
|
const loadEtherpadInformations = () => {
|
||||||
if (lastLoadingTime !== null && Date.now() - lastLoadingTime < updateInterval) {
|
if (lastLoadingTime !== null && Date.now() - lastLoadingTime < updateInterval) {
|
||||||
return Promise.resolve(infos);
|
return infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
return axios.get('https://static.etherpad.org/info.json', {headers: headers})
|
return axios.get('https://static.etherpad.org/info.json', {headers: headers})
|
||||||
|
@ -29,10 +29,10 @@ const loadEtherpadInformations = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
lastLoadingTime = Date.now();
|
lastLoadingTime = Date.now();
|
||||||
return await Promise.resolve(infos);
|
return infos;
|
||||||
})
|
})
|
||||||
.catch(async (err: Error) => {
|
.catch(async (err: Error) => {
|
||||||
return await Promise.reject(err);
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,15 +43,15 @@ exports.getLatestVersion = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.needsUpdate = async (cb?: Function) => {
|
exports.needsUpdate = async (cb?: Function) => {
|
||||||
await loadEtherpadInformations()
|
try {
|
||||||
.then((info:Infos) => {
|
const info = await loadEtherpadInformations()
|
||||||
if (semver.gt(info.latestVersion, settings.getEpVersion())) {
|
if (semver.gt(info!.latestVersion, settings.getEpVersion())) {
|
||||||
if (cb) return cb(true);
|
if (cb) return cb(true);
|
||||||
}
|
}
|
||||||
}).catch((err: Error) => {
|
} catch (err) {
|
||||||
console.error(`Can not perform Etherpad update check: ${err}`);
|
console.error(`Can not perform Etherpad update check: ${err}`);
|
||||||
if (cb) return cb(false);
|
if (cb) return cb(false);
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.check = () => {
|
exports.check = () => {
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
"clean-css": "^5.3.3",
|
"clean-css": "^5.3.3",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cross-spawn": "^7.0.3",
|
"cross-spawn": "^7.0.3",
|
||||||
"ejs": "^3.1.9",
|
"ejs": "^3.1.10",
|
||||||
"etherpad-require-kernel": "^1.0.16",
|
"etherpad-require-kernel": "^1.0.16",
|
||||||
"etherpad-yajsml": "0.0.12",
|
"etherpad-yajsml": "0.0.12",
|
||||||
"express": "4.19.2",
|
"express": "4.19.2",
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
"jsonminify": "0.4.2",
|
"jsonminify": "0.4.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"languages4translatewiki": "0.1.3",
|
"languages4translatewiki": "0.1.3",
|
||||||
"live-plugin-manager": "^0.19.0",
|
"live-plugin-manager": "^0.20.0",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"measured-core": "^2.0.0",
|
"measured-core": "^2.0.0",
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
"etherpad-lite": "node/server.ts"
|
"etherpad-lite": "node/server.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.43.0",
|
"@playwright/test": "^1.43.1",
|
||||||
"@types/async": "^3.2.24",
|
"@types/async": "^3.2.24",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/formidable": "^3.4.5",
|
"@types/formidable": "^3.4.5",
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
"@types/jsdom": "^21.1.6",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/mocha": "^10.0.6",
|
||||||
"@types/node": "^20.12.5",
|
"@types/node": "^20.12.7",
|
||||||
"@types/oidc-provider": "^8.4.4",
|
"@types/oidc-provider": "^8.4.4",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/sinon": "^17.0.3",
|
"@types/sinon": "^17.0.3",
|
||||||
|
@ -108,7 +108,7 @@
|
||||||
"sinon": "^17.0.1",
|
"sinon": "^17.0.1",
|
||||||
"split-grid": "^1.0.11",
|
"split-grid": "^1.0.11",
|
||||||
"supertest": "^6.3.4",
|
"supertest": "^6.3.4",
|
||||||
"typescript": "^5.4.4"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.2",
|
"node": ">=18.18.2",
|
||||||
|
@ -132,6 +132,6 @@
|
||||||
"test-admin": "npx playwright test tests/frontend-new/admin-spec --workers 1",
|
"test-admin": "npx playwright test tests/frontend-new/admin-spec --workers 1",
|
||||||
"test-admin:ui": "npx playwright test tests/frontend-new/admin-spec --ui --workers 1"
|
"test-admin:ui": "npx playwright test tests/frontend-new/admin-spec --ui --workers 1"
|
||||||
},
|
},
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -534,7 +534,7 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author)
|
||||||
// As the concept of parent's doesn't exist when processing each domline...
|
// As the concept of parent's doesn't exist when processing each domline...
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Below needs more testin if it's neccesary as _exitList should take care of this.
|
// Below needs more testin if it's necessary as _exitList should take care of this.
|
||||||
// delete state.start;
|
// delete state.start;
|
||||||
// delete state.listNesting;
|
// delete state.listNesting;
|
||||||
// _recalcAttribString(state);
|
// _recalcAttribString(state);
|
||||||
|
|
|
@ -167,7 +167,7 @@ const getUrlVars = () => new URL(window.location.href).searchParams;
|
||||||
|
|
||||||
const sendClientReady = (isReconnect) => {
|
const sendClientReady = (isReconnect) => {
|
||||||
let padId = document.location.pathname.substring(document.location.pathname.lastIndexOf('/') + 1);
|
let padId = document.location.pathname.substring(document.location.pathname.lastIndexOf('/') + 1);
|
||||||
// unescape neccesary due to Safari and Opera interpretation of spaces
|
// unescape necessary due to Safari and Opera interpretation of spaces
|
||||||
padId = decodeURIComponent(padId);
|
padId = decodeURIComponent(padId);
|
||||||
|
|
||||||
if (!isReconnect) {
|
if (!isReconnect) {
|
||||||
|
@ -213,7 +213,7 @@ const sendClientReady = (isReconnect) => {
|
||||||
const handshake = async () => {
|
const handshake = async () => {
|
||||||
let receivedClientVars = false;
|
let receivedClientVars = false;
|
||||||
let padId = document.location.pathname.substring(document.location.pathname.lastIndexOf('/') + 1);
|
let padId = document.location.pathname.substring(document.location.pathname.lastIndexOf('/') + 1);
|
||||||
// unescape neccesary due to Safari and Opera interpretation of spaces
|
// unescape necessary due to Safari and Opera interpretation of spaces
|
||||||
padId = decodeURIComponent(padId);
|
padId = decodeURIComponent(padId);
|
||||||
|
|
||||||
// padId is used here for sharding / scaling. We prefix the padId with padId: so it's clear
|
// padId is used here for sharding / scaling. We prefix the padId with padId: so it's clear
|
||||||
|
@ -250,11 +250,27 @@ const handshake = async () => {
|
||||||
socket.on('disconnect', (reason) => {
|
socket.on('disconnect', (reason) => {
|
||||||
// The socket.io client will automatically try to reconnect for all reasons other than "io
|
// The socket.io client will automatically try to reconnect for all reasons other than "io
|
||||||
// server disconnect".
|
// server disconnect".
|
||||||
if (reason !== 'io server disconnect') return;
|
console.log(`Socket disconnected: ${reason}`)
|
||||||
|
if (reason !== 'io server disconnect' || reason !== 'ping timeout') return;
|
||||||
socketReconnecting();
|
socketReconnecting();
|
||||||
socket.connect();
|
socket.connect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
socket.on('shout', (obj) => {
|
||||||
|
if(obj.type === "COLLABROOM") {
|
||||||
|
let date = new Date(obj.data.payload.timestamp);
|
||||||
|
$.gritter.add({
|
||||||
|
// (string | mandatory) the heading of the notification
|
||||||
|
title: 'Admin message',
|
||||||
|
// (string | mandatory) the text inside the notification
|
||||||
|
text: '[' + date.toLocaleTimeString() + ']: ' + obj.data.payload.message.message,
|
||||||
|
// (bool | optional) if you want it to fade out on its own or just sit there
|
||||||
|
sticky: obj.data.payload.message.sticky
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
socket.on('reconnecting', socketReconnecting);
|
socket.on('reconnecting', socketReconnecting);
|
||||||
|
|
||||||
socket.on('reconnect_failed', (error) => {
|
socket.on('reconnect_failed', (error) => {
|
||||||
|
|
|
@ -225,7 +225,7 @@ exports.callAll = (hookName, context) => {
|
||||||
// provides settles (resolves or rejects). If a hook function attempts to settle again (e.g., call
|
// provides settles (resolves or rejects). If a hook function attempts to settle again (e.g., call
|
||||||
// the callback again, or return a value and also call the callback) then the second attempt has no
|
// the callback again, or return a value and also call the callback) then the second attempt has no
|
||||||
// effect except either an error message is logged or an Error object is thrown depending on whether
|
// effect except either an error message is logged or an Error object is thrown depending on whether
|
||||||
// the the subsequent attempt is a duplicate (same value or error) or different, respectively.
|
// the subsequent attempt is a duplicate (same value or error) or different, respectively.
|
||||||
//
|
//
|
||||||
// See the tests in src/tests/backend/specs/hooks.js for examples of supported and prohibited
|
// See the tests in src/tests/backend/specs/hooks.js for examples of supported and prohibited
|
||||||
// behaviors.
|
// behaviors.
|
||||||
|
|
|
@ -60,6 +60,7 @@ const migratePluginsFromNodeModules = async () => {
|
||||||
const cmd = ['pnpm', 'ls', '--long', '--json', '--depth=0', '--no-production'];
|
const cmd = ['pnpm', 'ls', '--long', '--json', '--depth=0', '--no-production'];
|
||||||
const [{dependencies = {}}] = JSON.parse(await runCmd(cmd,
|
const [{dependencies = {}}] = JSON.parse(await runCmd(cmd,
|
||||||
{stdio: [null, 'string']}));
|
{stdio: [null, 'string']}));
|
||||||
|
|
||||||
await Promise.all(Object.entries(dependencies)
|
await Promise.all(Object.entries(dependencies)
|
||||||
.filter(([pkg, info]) => pkg.startsWith(plugins.prefix) && pkg !== 'ep_etherpad-lite')
|
.filter(([pkg, info]) => pkg.startsWith(plugins.prefix) && pkg !== 'ep_etherpad-lite')
|
||||||
.map(async ([pkg, info]) => {
|
.map(async ([pkg, info]) => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ test('Shows troubleshooting page manager', async ({page}) => {
|
||||||
await page.goto('http://localhost:9001/admin/help')
|
await page.goto('http://localhost:9001/admin/help')
|
||||||
await page.waitForSelector('.menu')
|
await page.waitForSelector('.menu')
|
||||||
const menu = page.locator('.menu');
|
const menu = page.locator('.menu');
|
||||||
await expect(menu.locator('li')).toHaveCount(4);
|
await expect(menu.locator('li')).toHaveCount(5);
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Shows a version number', async function ({page}) {
|
test('Shows a version number', async function ({page}) {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.4.4",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.2.8"
|
"vite": "^5.2.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue