Merge branch 'develop'

This commit is contained in:
SamTV12345 2024-03-17 12:21:33 +01:00
commit 8a7dfce330
421 changed files with 18315 additions and 16207 deletions

View file

@ -24,3 +24,4 @@ Dockerfile
settings.json settings.json
src/node_modules src/node_modules
admin/node_modules

18
.env.default Normal file
View 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

View file

@ -14,8 +14,3 @@ updates:
schedule: schedule:
interval: "daily" interval: "daily"
versioning-strategy: "increase" versioning-strategy: "increase"
- package-ecosystem: "npm"
directory: "/src/bin/doc"
schedule:
interval: "daily"
versioning-strategy: "increase"

View file

@ -27,22 +27,42 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install libreoffice name: Install libreoffice
run: | uses: awalsh128/cache-apt-pkgs-action@v1.4.2
sudo add-apt-repository -y ppa:libreoffice/ppa with:
sudo apt update packages: libreoffice libreoffice-pdfimport
sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport version: 1.0
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- name: Install admin ui
working-directory: admin
run: pnpm install
- name: Build admin ui
working-directory: admin
run: pnpm build
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: pnpm test
withpluginsLinux: withpluginsLinux:
# run on pushes to any branch # run on pushes to any branch
@ -64,22 +84,43 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install libreoffice name: Install libreoffice
run: | uses: awalsh128/cache-apt-pkgs-action@v1.4.2
sudo add-apt-repository -y ppa:libreoffice/ppa with:
sudo apt update packages: libreoffice libreoffice-pdfimport
sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport version: 1.0
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: bin/installDeps.sh
- name: Install admin ui
working-directory: admin
run: pnpm install
- name: Build admin ui
working-directory: admin
run: pnpm build
- -
name: Install Etherpad plugins name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps pnpm install --workspace-root
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -93,21 +134,9 @@ jobs:
ep_spellcheck ep_spellcheck
ep_subscript_and_superscript ep_subscript_and_superscript
ep_table_of_contents ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: pnpm test
withoutpluginsWindows: withoutpluginsWindows:
# run on pushes to any branch # run on pushes to any branch
@ -125,13 +154,33 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installOnWindows.bat run: bin/installOnWindows.bat
- name: Install admin ui
working-directory: admin
run: pnpm install
- name: Build admin ui
working-directory: admin
run: pnpm build
- -
name: Fix up the settings.json name: Fix up the settings.json
run: | run: |
@ -139,7 +188,7 @@ jobs:
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: cd src && pnpm test
withpluginsWindows: withpluginsWindows:
# run on pushes to any branch # run on pushes to any branch
@ -157,17 +206,37 @@ jobs:
- -
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 21
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- name: Install admin ui
working-directory: admin
run: pnpm install
- name: Build admin ui
working-directory: admin
run: pnpm build
- -
name: Install Etherpad plugins name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm # The --legacy-peer-deps flag is required to work around a bug in npm
# v7: https://github.com/npm/cli/issues/2199 # v7: https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps pnpm install --workspace-root
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -192,7 +261,7 @@ jobs:
# rules. # rules.
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installOnWindows.bat run: bin/installOnWindows.bat
- -
name: Fix up the settings.json name: Fix up the settings.json
run: | run: |
@ -200,4 +269,4 @@ jobs:
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: cd src && pnpm test

View file

@ -30,6 +30,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
target: production
load: true load: true
tags: ${{ env.TEST_TAG }} tags: ${{ env.TEST_TAG }}
cache-from: type=gha cache-from: type=gha
@ -39,16 +40,28 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- -
name: Test name: Test
run: | run: |
docker run --rm -d -p 9001:9001 --name test ${{ env.TEST_TAG }} docker run --rm -d -p 9001:9001 --name test ${{ env.TEST_TAG }}
./bin/installDeps.sh
docker logs -f test & docker logs -f test &
./src/bin/installDeps.sh
while true; do while true; do
echo "Waiting for Docker container to start..." echo "Waiting for Docker container to start..."
status=$(docker container inspect -f '{{.State.Health.Status}}' test) || exit 1 status=$(docker container inspect -f '{{.State.Health.Status}}' test) || exit 1
@ -58,7 +71,7 @@ jobs:
*) printf %s\\n "unexpected status: ${status}" >&2; exit 1;; *) printf %s\\n "unexpected status: ${status}" >&2; exit 1;;
esac esac
done done
(cd src && npm run test-container) (cd src && pnpm run test-container)
git clean -dxf . git clean -dxf .
- -
name: Docker meta name: Docker meta
@ -85,6 +98,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
target: production
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
@ -96,4 +110,4 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: etherpad/etherpad repository: etherpad/etherpad
enable-url-completion: true enable-url-completion: true

View file

@ -12,11 +12,10 @@ jobs:
name: with plugins name: with plugins
runs-on: ubuntu-latest runs-on: ubuntu-latest
# node: [16, 19, 20] >> Disabled node 16 and 18 because they do not work
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
node: [19, 20, 21] node: [20, 21]
steps: steps:
- -
@ -32,16 +31,30 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
- run_install: false
name: Install etherpad plugins - name: Get pnpm store directory
# We intentionally install an old ep_align version to test upgrades to shell: bash
# the minor version number. The --legacy-peer-deps flag is required to run: |
# work around a bug in npm v7: https://github.com/npm/cli/issues/2199 echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
run: npm install --no-save --legacy-peer-deps ep_align@0.2.27 - uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
#-
# name: Install etherpad plugins
# # We intentionally install an old ep_align version to test upgrades to
# # the minor version number. The --legacy-peer-deps flag is required to
# # work around a bug in npm v7: https://github.com/npm/cli/issues/2199
# run: pnpm install --workspace-root ep_align@0.2.27
# Etherpad core dependencies must be installed after installing the # Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common # plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them # dependencies by removing them from src/node_modules and installing them
@ -53,10 +66,10 @@ jobs:
# rules. # rules.
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- #-
name: Install etherpad plugins # name: Install etherpad plugins
run: rm -Rf node_modules/ep_align/static/tests/* # run: rm -Rf node_modules/ep_align/static/tests/*
- -
name: export GIT_HASH to env name: export GIT_HASH to env
id: environment id: environment
@ -66,31 +79,67 @@ jobs:
run: cp settings.json.template settings.json run: cp settings.json.template settings.json
- -
name: Write custom settings.json that enables the Admin UI tests name: Write custom settings.json that enables the Admin UI tests
run: "sed -i 's/\"enableAdminUITests\": false/\"enableAdminUITests\": true,\\n\"users\":{\"admin\":{\"password\":\"changeme\",\"is_admin\":true}}/' settings.json" run: "sed -i 's/\"enableAdminUITests\": false/\"enableAdminUITests\": true,\\n\"users\":{\"admin\":{\"password\":\"changeme1\",\"is_admin\":true}}/' settings.json"
- -
name: increase maxHttpBufferSize name: increase maxHttpBufferSize
run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 100000/' settings.json" run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 10000000/' settings.json"
- -
name: Disable import/export rate limiting name: Disable import/export rate limiting
run: | run: |
sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 1000000/' -i settings.json sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 100000000/' -i settings.json
- - name: Build admin frontend
name: Remove standard frontend test files, so only admin tests are run working-directory: admin
run: mv src/tests/frontend/specs/* /tmp && mv /tmp/admin*.js src/tests/frontend/specs
-
uses: saucelabs/sauce-connect-action@v2.3.6
with:
username: ${{ secrets.SAUCE_USERNAME }}
accessKey: ${{ secrets.SAUCE_ACCESS_KEY }}
tunnelIdentifier: ${{ steps.sauce_strings.outputs.tunnel_id }}
-
name: Run the frontend admin tests
shell: bash
env:
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }}
TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }}
GIT_HASH: ${{ steps.environment.outputs.sha_short }}
run: | run: |
src/tests/frontend/travis/adminrunner.sh pnpm install
pnpm run build
# name: Run the frontend admin tests
# shell: bash
# env:
# SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
# SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
# SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }}
# TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }}
# GIT_HASH: ${{ steps.environment.outputs.sha_short }}
# run: |
# src/tests/frontend/travis/adminrunner.sh
#-
# uses: saucelabs/sauce-connect-action@v2.3.6
# with:
# username: ${{ secrets.SAUCE_USERNAME }}
# accessKey: ${{ secrets.SAUCE_ACCESS_KEY }}
# tunnelIdentifier: ${{ steps.sauce_strings.outputs.tunnel_id }}
#-
# name: Run the frontend admin tests
# shell: bash
# env:
# SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
# SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
# SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }}
# TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }}
# GIT_HASH: ${{ steps.environment.outputs.sha_short }}
# run: |
# src/tests/frontend/travis/adminrunner.sh
- name: Run the frontend admin tests
shell: bash
run: |
pnpm run dev &
connected=false
can_connect() {
curl -sSfo /dev/null http://localhost:9001/ || return 1
connected=true
}
now() { date +%s; }
start=$(now)
while [ $(($(now) - $start)) -le 15 ] && ! can_connect; do
sleep 1
done
cd src
pnpm exec playwright install
pnpm exec playwright install-deps
pnpm run test-admin
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.node }}
path: src/playwright-report/
retention-days: 30

View file

@ -7,10 +7,141 @@ permissions:
contents: read # to fetch code (actions/checkout) contents: read # to fetch code (actions/checkout)
jobs: jobs:
withoutplugins: playwright-chrome:
name: without plugins name: Playwright Chrome
runs-on: ubuntu-latest
steps:
-
name: Generate Sauce Labs strings
id: sauce_strings
run: |
printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}'
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}'
-
name: Checkout repository
uses: actions/checkout@v4
-
uses: actions/setup-node@v4
with:
node-version: 21
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
if: always()
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: bin/installDeps.sh
-
name: export GIT_HASH to env
id: environment
run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})"
-
name: Create settings.json
run: cp ./src/tests/settings.json settings.json
- name: Run the frontend tests
shell: bash
run: |
pnpm run dev &
connected=false
can_connect() {
curl -sSfo /dev/null http://localhost:9001/ || return 1
connected=true
}
now() { date +%s; }
start=$(now)
while [ $(($(now) - $start)) -le 15 ] && ! can_connect; do
sleep 1
done
cd src
pnpm exec playwright install chromium --with-deps
pnpm run test-ui --project=chromium
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.node }}-chrome
path: src/playwright-report/
retention-days: 30
playwright-firefox:
name: Playwright Firefox
runs-on: ubuntu-latest
steps:
- name: Generate Sauce Labs strings
id: sauce_strings
run: |
printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}'
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}'
- name: Checkout repository
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 21
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
if: always()
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- name: Install all dependencies and symlink for ep_etherpad-lite
run: bin/installDeps.sh
- name: export GIT_HASH to env
id: environment
run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})"
- name: Create settings.json
run: cp ./src/tests/settings.json settings.json
- name: Run the frontend tests
shell: bash
run: |
pnpm run dev &
connected=false
can_connect() {
curl -sSfo /dev/null http://localhost:9001/ || return 1
connected=true
}
now() { date +%s; }
start=$(now)
while [ $(($(now) - $start)) -le 15 ] && ! can_connect; do
sleep 1
done
cd src
pnpm exec playwright install firefox --with-deps
pnpm run test-ui --project=firefox
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.node }}-firefox
path: src/playwright-report/
retention-days: 30
playwright-webkit:
name: Playwright Webkit
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }}
steps: steps:
- -
@ -25,127 +156,59 @@ jobs:
- -
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 21
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
if: always()
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- -
name: export GIT_HASH to env name: export GIT_HASH to env
id: environment id: environment
run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})" run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})"
- -
name: Create settings.json name: Create settings.json
run: cp settings.json.template settings.json run: cp ./src/tests/settings.json settings.json
- - name: Run the frontend tests
name: Disable import/export rate limiting
run: |
sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 100000000/' -i settings.json
-
uses: saucelabs/sauce-connect-action@v2.3.6
with:
username: ${{ secrets.SAUCE_USERNAME }}
accessKey: ${{ secrets.SAUCE_ACCESS_KEY }}
tunnelIdentifier: ${{ steps.sauce_strings.outputs.tunnel_id }}
-
name: Run the frontend tests
shell: bash shell: bash
env:
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }}
TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }}
GIT_HASH: ${{ steps.environment.outputs.sha_short }}
run: | run: |
src/tests/frontend/travis/runner.sh pnpm run dev &
connected=false
withplugins: can_connect() {
name: with plugins curl -sSfo /dev/null http://localhost:9001/ || return 1
runs-on: ubuntu-latest connected=true
if: ${{ github.actor != 'dependabot[bot]' }} }
now() { date +%s; }
steps: start=$(now)
- while [ $(($(now) - $start)) -le 15 ] && ! can_connect; do
name: Generate Sauce Labs strings sleep 1
id: sauce_strings done
run: | cd src
printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}' pnpm exec playwright install webkit --with-deps
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}' pnpm run test-ui --project=webkit || true
- - uses: actions/upload-artifact@v4
name: Checkout repository if: always()
uses: actions/checkout@v4
-
uses: actions/setup-node@v4
with: with:
node-version: 20 name: playwright-report-${{ matrix.node }}-webkit
cache: 'npm' path: src/playwright-report/
cache-dependency-path: | retention-days: 30
src/package-lock.json
src/bin/doc/package-lock.json
-
name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199
run: >
npm install --no-save --legacy-peer-deps
ep_align
ep_author_hover
ep_cursortrace
ep_embedmedia
ep_font_size
ep_hash_auth
ep_headings2
ep_image_upload
ep_markdown
ep_readonly_guest
ep_set_title_on_pad
ep_spellcheck
ep_subscript_and_superscript
ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.20.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh
-
name: export GIT_HASH to env
id: environment
run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})"
-
name: Create settings.json
run: cp settings.json.template settings.json
-
name: Disable import/export rate limiting
run: |
sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 1000000/' -i settings.json
# XXX we should probably run all tests, because plugins could effect their results
-
name: Remove standard frontend test files, so only plugin tests are run
run: rm src/tests/frontend/specs/*
-
uses: saucelabs/sauce-connect-action@v2.3.6
with:
username: ${{ secrets.SAUCE_USERNAME }}
accessKey: ${{ secrets.SAUCE_ACCESS_KEY }}
tunnelIdentifier: ${{ steps.sauce_strings.outputs.tunnel_id }}
-
name: Run the frontend tests
shell: bash
env:
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }}
TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }}
GIT_HASH: ${{ steps.environment.outputs.sha_short }}
run: |
src/tests/frontend/travis/runner.sh

View file

@ -23,13 +23,6 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm'
cache-dependency-path: |
src/package-lock.json
src/bin/doc/package-lock.json
-
name: Install lockfile-lint
run: npm install --no-save lockfile-lint --legacy-peer-deps
- -
name: Run lockfile-lint on package-lock.json name: Run lockfile-lint on package-lock.json
run: > run: >

View file

@ -23,16 +23,30 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- -
name: Install etherpad-load-test name: Install etherpad-load-test
run: sudo npm install -g etherpad-load-test run: sudo npm install -g etherpad-load-test-socket-io
- -
name: Run load test name: Run load test
run: src/tests/frontend/travis/runnerLoadTest.sh 25 50 run: src/tests/frontend/travis/runnerLoadTest.sh 25 50
@ -53,19 +67,33 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install etherpad-load-test name: Install etherpad-load-test
run: sudo npm install -g etherpad-load-test run: pnpm install -g etherpad-load-test-socket-io
- -
name: Install etherpad plugins name: Install etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm v7: # The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199 # https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps pnpm install --workspace-root
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -89,7 +117,7 @@ jobs:
# rules. # rules.
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- -
name: Run load test name: Run load test
run: src/tests/frontend/travis/runnerLoadTest.sh 25 50 run: src/tests/frontend/travis/runnerLoadTest.sh 25 50
@ -110,16 +138,30 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- -
name: Install etherpad-load-test name: Install etherpad-load-test
run: sudo npm install -g etherpad-load-test run: sudo npm install -g etherpad-load-test-socket-io
- -
name: Run load test name: Run load test
run: src/tests/frontend/travis/runnerLoadTest.sh 5000 5 run: src/tests/frontend/travis/runnerLoadTest.sh 5000 5

View file

@ -0,0 +1,46 @@
name: "Perform type checks"
# any branch is useful for testing before a PR is submitted
on: [push, pull_request]
permissions:
contents: read
jobs:
performTypeCheck:
if: |
(github.event_name != 'pull_request')
|| (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id)
name: perform type check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: ./bin/installDeps.sh
- name: Perform type check
working-directory: ./src
run: npm run ts-check

View file

@ -23,17 +23,30 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- -
name: docker network name: docker network
run: docker network create --subnet=172.23.42.0/16 ep_net run: docker network create --subnet=172.23.42.0/16 ep_net
- -
name: build docker image name: build docker image
run: | run: |
docker build -f Dockerfile -t epl-debian-slim . docker build -f Dockerfile -t epl-debian-slim --build-arg NODE_ENV=develop .
docker build -f src/tests/ratelimit/Dockerfile.nginx -t nginx-latest . docker build -f src/tests/ratelimit/Dockerfile.nginx -t nginx-latest .
docker build -f src/tests/ratelimit/Dockerfile.anotherip -t anotherip . docker build -f src/tests/ratelimit/Dockerfile.anotherip -t anotherip .
- -
@ -44,7 +57,7 @@ jobs:
docker run --rm --network ep_net --ip 172.23.42.3 --name anotherip -dt anotherip docker run --rm --network ep_net --ip 172.23.42.3 --name anotherip -dt anotherip
- -
name: install dependencies and create symlink for ep_etherpad-lite name: install dependencies and create symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- -
name: run rate limit test name: run rate limit test
run: | run: |

View file

@ -29,14 +29,11 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
cache: 'npm'
cache-dependency-path: |
src/package-lock.json
src/bin/doc/package-lock.json
- -
name: Install Etherpad plugins name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm # Important: Installer for old master which does not have pnpm right now
# v7: https://github.com/npm/cli/issues/2199 # The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps npm install --no-save --legacy-peer-deps
ep_align ep_align
@ -52,15 +49,6 @@ jobs:
ep_spellcheck ep_spellcheck
ep_subscript_and_superscript ep_subscript_and_superscript
ep_table_of_contents ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
@ -82,18 +70,44 @@ jobs:
# For pull requests, ${GITHUB_SHA} is the automatically generated merge # For pull requests, ${GITHUB_SHA} is the automatically generated merge
# commit that merges the PR's source branch to its destination branch. # commit that merges the PR's source branch to its destination branch.
run: git checkout "${GITHUB_SHA}" run: git checkout "${GITHUB_SHA}"
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
run_install: false
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
-
name: Install libreoffice
uses: awalsh128/cache-apt-pkgs-action@v1.4.2
with:
packages: libreoffice libreoffice-pdfimport
version: 1.0
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: bin/installDeps.sh
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: pnpm test
- -
name: Install Cypress name: Install Cypress
run: cd src && npm install cypress --legacy-peer-deps working-directory: ./src
run: pnpm install cypress
- -
name: Run Etherpad & Test Frontend name: Run Etherpad & Test Frontend
working-directory: ./src
run: | run: |
node src/node/server.js & pnpm run dev &
curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test
./src/node_modules/cypress/bin/cypress run --config-file src/tests/frontend/cypress/cypress.config.js ./node_modules/cypress/bin/cypress run --config-file tests/frontend/cypress/cypress.config.js

View file

@ -28,23 +28,37 @@ jobs:
- -
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 21
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
src/package-lock.json with:
src/bin/doc/package-lock.json version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
shell: msys2 {0} shell: msys2 {0}
run: src/bin/installDeps.sh run: bin/installDeps.sh
- -
name: Run the backend tests name: Run the backend tests
shell: msys2 {0} shell: msys2 {0}
run: cd src && npm test run: cd src && pnpm test
- -
name: Build the .zip name: Build the .zip
shell: msys2 {0} shell: msys2 {0}
run: src/bin/buildForWindows.sh run: bin/buildForWindows.sh
- -
name: Archive production artifacts name: Archive production artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -75,9 +89,9 @@ jobs:
run: 7z x etherpad-win.zip -oetherpad-zip run: 7z x etherpad-win.zip -oetherpad-zip
- -
name: Create installer name: Create installer
uses: joncloud/makensis-action@v3.7 uses: joncloud/makensis-action@v4.1
with: with:
script-file: 'src/bin/nsis/etherpad.nsi' script-file: 'bin/nsis/etherpad.nsi'
- -
name: Archive production artifacts name: Archive production artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -109,27 +123,43 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: 'npm' - uses: pnpm/action-setup@v3
cache-dependency-path: | name: Install pnpm
etherpad/src/package-lock.json with:
etherpad/src/bin/doc/package-lock.json version: 8
- run_install: false
name: Install Cypress - name: Get pnpm store directory
run: cd etherpad && cd src && npm install cypress --legacy-peer-deps shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- name: Install all dependencies and symlink for ep_etherpad-lite
run: .\bin\installOnWindows.bat
working-directory: etherpad
- -
name: Run Etherpad name: Run Etherpad
working-directory: etherpad/src
run: | run: |
cd etherpad pnpm install cypress
node node_modules\ep_etherpad-lite\node\server.js & .\node_modules\.bin\cypress.cmd install --force
pnpm run prod &
curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test
src\node_modules\cypress\bin\cypress run --config-file src\tests\frontendcypress\cypress.config.js pnpm exec cypress run --config-file ./tests/frontend/cypress/cypress.config.js
# On release, upload windows zip to GitHub release tab # On release, upload windows zip to GitHub release tab
- -
name: Rename to etherpad-lite-win.zip name: Rename to etherpad-lite-win.zip
shell: powershell shell: powershell
run: mv etherpad-win.zip etherpad-lite-win.zip run: mv etherpad-win.zip etherpad-lite-win.zip
- name: upload binaries to release - name: upload binaries to release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
if: ${{startsWith(github.ref, 'refs/tags/v') }} if: ${{startsWith(github.ref, 'refs/tags/v') }}
with: with:
files: etherpad-lite-win.zip files: etherpad-lite-win.zip

7
.gitignore vendored
View file

@ -6,6 +6,7 @@ node_modules
APIKEY.txt APIKEY.txt
SESSIONKEY.txt SESSIONKEY.txt
var/dirty.db var/dirty.db
.env
*~ *~
*.patch *.patch
npm-debug.log npm-debug.log
@ -21,4 +22,8 @@ out/
/src/bin/convertSettings.json /src/bin/convertSettings.json
/src/bin/etherpad-1.deb /src/bin/etherpad-1.deb
/src/bin/node.exe /src/bin/node.exe
plugin_packages plugin_packages
/src/templates/admin
/src/test-results
playwright-report
state.json

1
.npmrc Normal file
View file

@ -0,0 +1 @@
auto-install-peers=false

View file

@ -55,7 +55,7 @@ jobs:
- *set_loglevel_warn - *set_loglevel_warn
- *enable_admin_tests - *enable_admin_tests
- "src/tests/frontend/travis/sauce_tunnel.sh" - "src/tests/frontend/travis/sauce_tunnel.sh"
- "src/bin/installDeps.sh" - "bin/installDeps.sh"
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)" - "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
script: script:
- "./src/tests/frontend/travis/runner.sh" - "./src/tests/frontend/travis/runner.sh"
@ -63,22 +63,22 @@ jobs:
install: install:
- *install_libreoffice - *install_libreoffice
- *set_loglevel_warn - *set_loglevel_warn
- "src/bin/installDeps.sh" - "bin/installDeps.sh"
- "cd src && npm install && cd -" - "cd src && pnpm install && cd -"
script: script:
- "cd src && npm test" - "cd src && pnpm test"
- name: "Test the Dockerfile" - name: "Test the Dockerfile"
install: install:
- "cd src && npm install && cd -" - "cd src && pnpm install && cd -"
script: script:
- "docker build -t etherpad:test ." - "docker build -t etherpad:test ."
- "docker run -d -p 9001:9001 etherpad:test && sleep 3" - "docker run -d -p 9001:9001 etherpad:test && sleep 3"
- "cd src && npm run test-container" - "cd src && pnpm run test-container"
- name: "Load test Etherpad without Plugins" - name: "Load test Etherpad without Plugins"
install: install:
- *set_loglevel_warn - *set_loglevel_warn
- "src/bin/installDeps.sh" - "bin/installDeps.sh"
- "cd src && npm install && cd -" - "cd src && pnpm install && cd -"
- "npm install -g etherpad-load-test" - "npm install -g etherpad-load-test"
script: script:
- "src/tests/frontend/travis/runnerLoadTest.sh" - "src/tests/frontend/travis/runnerLoadTest.sh"
@ -90,7 +90,7 @@ jobs:
- *set_loglevel_warn - *set_loglevel_warn
- *enable_admin_tests - *enable_admin_tests
- "src/tests/frontend/travis/sauce_tunnel.sh" - "src/tests/frontend/travis/sauce_tunnel.sh"
- "src/bin/installDeps.sh" - "bin/installDeps.sh"
- "rm src/tests/frontend/specs/*" - "rm src/tests/frontend/specs/*"
- *install_plugins - *install_plugins
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)" - "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
@ -105,22 +105,22 @@ jobs:
install: install:
- *install_libreoffice - *install_libreoffice
- *set_loglevel_warn - *set_loglevel_warn
- "src/bin/installDeps.sh" - "bin/installDeps.sh"
- *install_plugins - *install_plugins
- "cd src && npm install && cd -" - "cd src && pnpm install && cd -"
script: script:
- "cd src && npm test" - "cd src && pnpm test"
- name: "Test the Dockerfile" - name: "Test the Dockerfile"
install: install:
- "cd src && npm install && cd -" - "cd src && pnpm install && cd -"
script: script:
- "docker build -t etherpad:test ." - "docker build -t etherpad:test ."
- "docker run -d -p 9001:9001 etherpad:test && sleep 3" - "docker run -d -p 9001:9001 etherpad:test && sleep 3"
- "cd src && npm run test-container" - "cd src && pnpm run test-container"
- name: "Load test Etherpad with Plugins" - name: "Load test Etherpad with Plugins"
install: install:
- *set_loglevel_warn - *set_loglevel_warn
- "src/bin/installDeps.sh" - "bin/installDeps.sh"
- *install_plugins - *install_plugins
- "cd src && npm install && cd -" - "cd src && npm install && cd -"
- "npm install -g etherpad-load-test" - "npm install -g etherpad-load-test"
@ -135,7 +135,7 @@ jobs:
- "docker run -p 8081:80 --rm --network ep_net --ip 172.23.42.1 -d nginx-latest" - "docker run -p 8081:80 --rm --network ep_net --ip 172.23.42.1 -d nginx-latest"
- "docker run --name etherpad-docker -p 9000:9001 --rm --network ep_net --ip 172.23.42.2 -e 'TRUST_PROXY=true' epl-debian-slim &" - "docker run --name etherpad-docker -p 9000:9001 --rm --network ep_net --ip 172.23.42.2 -e 'TRUST_PROXY=true' epl-debian-slim &"
- "docker run --rm --network ep_net --ip 172.23.42.3 --name anotherip -dt anotherip" - "docker run --rm --network ep_net --ip 172.23.42.3 --name anotherip -dt anotherip"
- "./src/bin/installDeps.sh" - "./bin/installDeps.sh"
script: script:
- "cd src/tests/ratelimit && bash testlimits.sh" - "cd src/tests/ratelimit && bash testlimits.sh"

View file

@ -1,3 +1,27 @@
# 2.0.0
### Compatibility changes
- Socket io has been updated to 4.7.5. This means that the json.send function won't work anymore and needs to be changed to .emit('message', myObj)
- Deprecating npm version 6 in favor of pnpm: We have made the decision to switch to the well established pnpm (https://pnpm.io/). It works by symlinking dependencies into a global directory allowing you to have a cleaner and more reliable environment.
- Introducing Typescript to the Etherpad core: Etherpad core logic has been rewritten in Typescript allowing for compiler checking of errors.
- Rewritten Admin Panel: The Admin panel has been rewritten in React and now features a more pleasant user experience. It now also features an integrated pad searching with sorting functionality.
### Notable enhancements and fixes
* Bugfixes
- Live Plugin Manager: The live plugin manager caused problems when a plugin had depdendencies defined. This issue is now resolved.
* Enhancements
- pnpm Workspaces: In addition to pnpm we introduced workspaces. A clean way to manage multiple bounded contexts like the admin panel or the bin folder.
- Bin folder: The bin folder has been moved from the src folder to the root folder. This change was necessary as the contained scripts do not represent core functionality of the user.
- Starting Etherpad: Etherpad can now be started with a single command: `pnpm run prod` in the root directory.
- Installing Etherpad: Etherpad no longer symlinks itself in the root directory. This is now also taken care by pnpm, and it just creates a node_modules folder with the src directory`s ep_etherpad-lite folder
- Plugins can now be installed simply via the command: `pnpm run install-plugins first-plugin second-plugin` or if you want to install from path you can do:
`pnpm run install-plugins --path ../path-to-plugin`
# 1.9.7 # 1.9.7
### Notable enhancements and fixes ### Notable enhancements and fixes

View file

@ -4,9 +4,21 @@
# #
# Author: muxator # Author: muxator
FROM node:lts-alpine FROM node:alpine as adminBuild
WORKDIR /opt/etherpad-lite
COPY ./admin ./admin
RUN cd ./admin && npm install -g pnpm && pnpm install && pnpm run build --outDir ./dist
FROM node:alpine as build
LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite" LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite"
# Set these arguments when building the image from behind a proxy
ARG http_proxy=
ARG https_proxy=
ARG no_proxy=
ARG TIMEZONE= ARG TIMEZONE=
RUN \ RUN \
@ -44,11 +56,6 @@ ARG INSTALL_ABIWORD=
# INSTALL_LIBREOFFICE=true # INSTALL_LIBREOFFICE=true
ARG INSTALL_SOFFICE= ARG INSTALL_SOFFICE=
# By default, Etherpad container is built and run in "production" mode. This is
# leaner (development dependencies are not installed) and runs faster (among
# other things, assets are minified & compressed).
ENV NODE_ENV=production
ENV ETHERPAD_PRODUCTION=true
# Install dependencies required for modifying access. # Install dependencies required for modifying access.
RUN apk add shadow bash RUN apk add shadow bash
# Follow the principle of least privilege: run as unprivileged user. # Follow the principle of least privilege: run as unprivileged user.
@ -63,8 +70,6 @@ ARG EP_UID=5001
ARG EP_GID=0 ARG EP_GID=0
ARG EP_SHELL= ARG EP_SHELL=
ENV NODE_ENV=production
RUN groupadd --system ${EP_GID:+--gid "${EP_GID}" --non-unique} etherpad && \ RUN groupadd --system ${EP_GID:+--gid "${EP_GID}" --non-unique} etherpad && \
useradd --system ${EP_UID:+--uid "${EP_UID}" --non-unique} --gid etherpad \ useradd --system ${EP_UID:+--uid "${EP_UID}" --non-unique} --gid etherpad \
${EP_HOME:+--home-dir "${EP_HOME}"} --create-home \ ${EP_HOME:+--home-dir "${EP_HOME}"} --create-home \
@ -77,10 +82,11 @@ 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 npm@6 -g && \ npm install pnpm -g && \
apk update && apk upgrade && \ apk update && apk upgrade && \
apk add \ apk add \
ca-certificates \ ca-certificates \
curl \
git \ git \
${INSTALL_ABIWORD:+abiword abiword-plugin-command} \ ${INSTALL_ABIWORD:+abiword abiword-plugin-command} \
${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common} ${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common}
@ -89,32 +95,45 @@ USER etherpad
WORKDIR "${EP_DIR}" WORKDIR "${EP_DIR}"
COPY --chown=etherpad:etherpad ./ ./ # etherpads version feature requires this. Only copy what is really needed
COPY --chown=etherpad:etherpad ./.git/HEAD ./.git/HEAD
COPY --chown=etherpad:etherpad ./.git/refs ./.git/refs
COPY --chown=etherpad:etherpad ${SETTINGS} ./settings.json
COPY --chown=etherpad:etherpad ./var ./var
COPY --chown=etherpad:etherpad ./bin ./bin
COPY --chown=etherpad:etherpad ./pnpm-workspace.yaml ./package.json ./
FROM build as development
COPY --chown=etherpad:etherpad ./src/package.json .npmrc ./src/pnpm-lock.yaml ./src/
COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin
RUN bin/installDeps.sh && \
{ [ -z "${ETHERPAD_PLUGINS}" ] || pnpm run install-plugins ${ETHERPAD_PLUGINS}; }
FROM build as production
ENV NODE_ENV=production
ENV ETHERPAD_PRODUCTION=true
COPY --chown=etherpad:etherpad ./src ./src
COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin
RUN bin/installDeps.sh && rm -rf ~/.npm && \
{ [ -z "${ETHERPAD_PLUGINS}" ] || pnpm run install-plugins ${ETHERPAD_PLUGINS}; }
# Plugins must be installed before installing Etherpad's dependencies, otherwise
# npm will try to hoist common dependencies by removing them from
# src/node_modules and installing them in the top-level node_modules. As of
# v6.14.10, npm's hoist logic appears to be buggy, because it sometimes removes
# dependencies from src/node_modules but fails to add them to the top-level
# node_modules. Even if npm correctly hoists the dependencies, the hoisting
# seems to confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
RUN { [ -z "${ETHERPAD_PLUGINS}" ] || \
npm install --no-save --legacy-peer-deps ${ETHERPAD_PLUGINS}; } && \
src/bin/installDeps.sh && \
rm -rf ~/.npm
# Copy the configuration file. # Copy the configuration file.
COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json
# Fix group permissions # Fix group permissions
RUN chmod -R g=u . # Note: For some reason increases image size from 257 to 334.
# RUN chmod -R g=u .
USER root
RUN cd src && npm link
USER etherpad USER etherpad
HEALTHCHECK --interval=20s --timeout=3s CMD ["etherpad-healthcheck"] HEALTHCHECK --interval=5s --timeout=3s \
CMD curl --silent http://localhost:9001/health | grep -E "pass|ok|up" > /dev/null || exit 1
EXPOSE 9001 EXPOSE 9001
CMD ["etherpad"] CMD ["pnpm", "run", "prod"]

View file

@ -56,7 +56,7 @@ Install the latest Node.js LTS per [official install instructions](https://githu
```sh ```sh
git clone --branch master https://github.com/ether/etherpad-lite.git && git clone --branch master https://github.com/ether/etherpad-lite.git &&
cd etherpad-lite && cd etherpad-lite &&
src/bin/run.sh bin/run.sh
``` ```
#### Manual install #### Manual install
@ -70,10 +70,10 @@ You'll need Git and [Node.js](https://nodejs.org/) installed.
https://github.com/ether/etherpad-lite.git` https://github.com/ether/etherpad-lite.git`
3. Change into the new directory containing the cloned source code: `cd 3. Change into the new directory containing the cloned source code: `cd
etherpad-lite` etherpad-lite`
4. Run `src/bin/run.sh` and open http://127.0.0.1:9001 in your browser. 4. Run `bin/run.sh` and open http://127.0.0.1:9001 in your browser.
To update to the latest released version, execute `git pull origin`. The next To update to the latest released version, execute `git pull origin`. The next
start with `src/bin/run.sh` will update the dependencies. start with `bin/run.sh` will update the dependencies.
### Windows ### Windows
@ -98,17 +98,17 @@ git.
* or `git clone --branch master * or `git clone --branch master
https://github.com/ether/etherpad-lite.git` https://github.com/ether/etherpad-lite.git`
2. With a "Run as administrator" command prompt execute 2. With a "Run as administrator" command prompt execute
`src\bin\installOnWindows.bat` `bin\installOnWindows.bat`
Now, run `start.bat` and open http://localhost:9001 in your browser. Now, run `start.bat` and open http://localhost:9001 in your browser.
Update to the latest version with `git pull origin`, then run Update to the latest version with `git pull origin`, then run
`src\bin\installOnWindows.bat`, again. `bin\installOnWindows.bat`, again.
If cloning to a subdirectory within another project, you may need to do the If cloning to a subdirectory within another project, you may need to do the
following: following:
1. Start the server manually (e.g. `node src/node/server.js`) 1. Start the server manually (e.g. `node src/node/server.ts`)
2. Edit the db `filename` in `settings.json` to the relative directory with 2. Edit the db `filename` in `settings.json` to the relative directory with
the file (e.g. `application/lib/etherpad-lite/var/dirty.db`) the file (e.g. `application/lib/etherpad-lite/var/dirty.db`)
3. Add auto-generated files to the main project `.gitignore` 3. Add auto-generated files to the main project `.gitignore`
@ -139,9 +139,7 @@ Alternatively, you can install plugins from the command line:
```sh ```sh
cd /path/to/etherpad-lite cd /path/to/etherpad-lite
# The `--no-save` and `--legacy-peer-deps` arguments are necessary to work pnpm run install-plugins ep_${plugin_name}
# around npm quirks.
npm install --no-save --legacy-peer-deps ep_${plugin_name}
``` ```
Also see [the plugin wiki Also see [the plugin wiki
@ -153,7 +151,7 @@ Run the following command in your Etherpad folder to get all of the features
visible in the above demo gif: visible in the above demo gif:
```sh ```sh
npm install --no-save --legacy-peer-deps \ pnpm run install-plugins \
ep_align \ ep_align \
ep_comments_page \ ep_comments_page \
ep_embedded_hyperlinks2 \ ep_embedded_hyperlinks2 \
@ -182,7 +180,7 @@ following plugins:
### Tweak the settings ### Tweak the settings
You can modify the settings in `settings.json`. If you need to handle multiple You can modify the settings in `settings.json`. If you need to handle multiple
settings files, you can pass the path to a settings file to `src/bin/run.sh` settings files, you can pass the path to a settings file to `bin/run.sh`
using the `-s|--settings` option: this allows you to run multiple Etherpad using the `-s|--settings` option: this allows you to run multiple Etherpad
instances from the same installation. Similarly, `--credentials` can be used to instances from the same installation. Similarly, `--credentials` can be used to
give a settings override file, `--apikey` to give a different APIKEY.txt file give a settings override file, `--apikey` to give a different APIKEY.txt file
@ -226,11 +224,11 @@ Documentation can be found in `doc/`.
### Things you should know ### Things you should know
You can debug Etherpad using `src/bin/debugRun.sh`. You can debug Etherpad using `bin/debugRun.sh`.
You can run Etherpad quickly launching `src/bin/fastRun.sh`. It's convenient for You can run Etherpad quickly launching `bin/fastRun.sh`. It's convenient for
developers and advanced users. Be aware that it will skip the dependencies developers and advanced users. Be aware that it will skip the dependencies
update, so remember to run `src/bin/installDeps.sh` after installing a new update, so remember to run `bin/installDeps.sh` after installing a new
dependency or upgrading version. dependency or upgrading version.
If you want to find out how Etherpad's `Easysync` works (the library that makes If you want to find out how Etherpad's `Easysync` works (the library that makes

18
admin/.eslintrc.cjs Normal file
View file

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
admin/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

30
admin/README.md Normal file
View file

@ -0,0 +1,30 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list

14
admin/index.html Normal file
View file

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Etherpad Admin Dashboard</title>
<link rel="icon" href="/favicon.ico">
</head>
<body>
<div id="root"></div>
<div id="loading"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

39
admin/package.json Normal file
View file

@ -0,0 +1,39 @@
{
"name": "admin",
"private": true,
"version": "2.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {},
"devDependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-toast": "^1.1.5",
"i18next": "^23.10.1",
"i18next-browser-languagedetector": "^7.2.0",
"lucide-react": "^0.356.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.0",
"react-i18next": "^14.1.0",
"react-router-dom": "^6.22.3",
"zustand": "^4.5.2",
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"socket.io-client": "^4.7.4",
"typescript": "^5.2.2",
"vite": "^5.1.4",
"vite-plugin-static-copy": "^1.0.1",
"vite-plugin-svgr": "^4.2.0"
}
}

BIN
admin/public/Karla-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"Meno25",
"محمد أحمد عبد الفتاح"
]
},
"ep_adminpads2_action": "فعل",
"ep_adminpads2_autoupdate-label": "التحديث التلقائي على تغييرات الوسادة",
"ep_adminpads2_autoupdate.title": "لتمكين أو تعطيل التحديثات التلقائية للاستعلام الحالي.",
"ep_adminpads2_confirm": "هل تريد حقًا حذف الوسادة {{padID}}؟",
"ep_adminpads2_delete.value": "حذف",
"ep_adminpads2_last-edited": "آخر تعديل",
"ep_adminpads2_loading": "جارٍ التحميل...",
"ep_adminpads2_manage-pads": "إدارة الفوط",
"ep_adminpads2_no-results": "لا توجد نتائج.",
"ep_adminpads2_pad-user-count": "عدد المستخدمين الوسادة",
"ep_adminpads2_padname": "بادنام",
"ep_adminpads2_search-box.placeholder": "مصطلح البحث",
"ep_adminpads2_search-button.value": "بحث",
"ep_adminpads2_search-done": "اكتمل البحث",
"ep_adminpads2_search-error-explanation": "واجه الخادم خطأً أثناء البحث عن منصات:",
"ep_adminpads2_search-error-title": "فشل في الحصول على قائمة الوسادة",
"ep_adminpads2_search-heading": "ابحث عن الفوط",
"ep_adminpads2_title": "إدارة الوسادة",
"ep_adminpads2_unknown-error": "خطأ غير معروف",
"ep_adminpads2_unknown-status": "حالة غير معروفة"
}

View file

@ -0,0 +1,23 @@
{
"@metadata": {
"authors": [
"আজিজ",
"আফতাবুজ্জামান"
]
},
"ep_adminpads2_action": "কার্য",
"ep_adminpads2_delete.value": "মুছে ফেলুন",
"ep_adminpads2_last-edited": "সর্বশেষ সম্পাদিত",
"ep_adminpads2_loading": "লোড হচ্ছে...",
"ep_adminpads2_manage-pads": "প্যাড পরিচালনা করুন",
"ep_adminpads2_no-results": "ফলাফল নেই",
"ep_adminpads2_padname": "প্যাডের নাম",
"ep_adminpads2_search-button.value": "অনুসন্ধান",
"ep_adminpads2_search-done": "অনুসন্ধান সম্পূর্ণ",
"ep_adminpads2_search-error-explanation": "প্যাড অনুসন্ধান করার সময় সার্ভার একটি ত্রুটির সম্মুখীন হয়েছে:",
"ep_adminpads2_search-error-title": "প্যাডের তালিকা পেতে ব্যর্থ",
"ep_adminpads2_search-heading": "প্যাড অনুসন্ধান করুন",
"ep_adminpads2_title": "প্যাড প্রশাসন",
"ep_adminpads2_unknown-error": "অজানা ত্রুটি",
"ep_adminpads2_unknown-status": "অজানা অবস্থা"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Mguix"
]
},
"ep_adminpads2_action": "Acció",
"ep_adminpads2_autoupdate-label": "Actualització automàtica en cas de canvis de pad",
"ep_adminpads2_autoupdate.title": "Activa o desactiva les actualitzacions automàtiques per a la consulta actual.",
"ep_adminpads2_confirm": "Esteu segur que voleu suprimir el pad {{padID}}?",
"ep_adminpads2_delete.value": "Esborrar",
"ep_adminpads2_last-edited": "Darrera modificació",
"ep_adminpads2_loading": "Sestà carregant…",
"ep_adminpads2_manage-pads": "Gestiona els pads",
"ep_adminpads2_no-results": "No hi ha cap resultat",
"ep_adminpads2_pad-user-count": "Nombre d'usuaris de pads",
"ep_adminpads2_padname": "Nom del pad",
"ep_adminpads2_search-box.placeholder": "Terme de cerca",
"ep_adminpads2_search-button.value": "Cercar",
"ep_adminpads2_search-done": "Cerca completa",
"ep_adminpads2_search-error-explanation": "El servidor ha trobat un error mentre buscava pads:",
"ep_adminpads2_search-error-title": "No s'ha pogut obtenir la llista del pad",
"ep_adminpads2_search-heading": "Cerca pads",
"ep_adminpads2_title": "Administració del pad",
"ep_adminpads2_unknown-error": "Error desconegut",
"ep_adminpads2_unknown-status": "Estat desconegut"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Spotter"
]
},
"ep_adminpads2_action": "Akce",
"ep_adminpads2_autoupdate-label": "Automatická aktualizace změn Padu",
"ep_adminpads2_autoupdate.title": "Povolí nebo zakáže automatické aktualizace pro aktuální dotaz.",
"ep_adminpads2_confirm": "Opravdu chcete odstranit pad {{padID}}?",
"ep_adminpads2_delete.value": "Smazat",
"ep_adminpads2_last-edited": "Naposledy upraveno",
"ep_adminpads2_loading": "Načítání…",
"ep_adminpads2_manage-pads": "Spravovat pady",
"ep_adminpads2_no-results": "Žádné výsledky",
"ep_adminpads2_pad-user-count": "Počet uživatelů padu",
"ep_adminpads2_padname": "Název padu",
"ep_adminpads2_search-box.placeholder": "Hledaný výraz",
"ep_adminpads2_search-button.value": "Hledat",
"ep_adminpads2_search-done": "Hledání dokončeno",
"ep_adminpads2_search-error-explanation": "Při hledání padů došlo k chybě serveru:",
"ep_adminpads2_search-error-title": "Seznam padů se nepodařilo získat",
"ep_adminpads2_search-heading": "Hledat pady",
"ep_adminpads2_title": "Správa Padu",
"ep_adminpads2_unknown-error": "Neznámá chyba",
"ep_adminpads2_unknown-status": "Neznámý stav"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Robin Owain"
]
},
"ep_adminpads2_action": "Gweithred",
"ep_adminpads2_autoupdate-label": "Diweddaru newidiadau pad yn otomatig",
"ep_adminpads2_autoupdate.title": "Galluogi neu analluogi diweddaru'r ymholiad cyfredol.",
"ep_adminpads2_confirm": "Siwr eich bod am ddileu'r pad {{padID}}?",
"ep_adminpads2_delete.value": "Dileu",
"ep_adminpads2_last-edited": "Golygwyd ddiwethaf",
"ep_adminpads2_loading": "Wrthi'n llwytho...",
"ep_adminpads2_manage-pads": "Rheoli'r padiau",
"ep_adminpads2_no-results": "Dim canlyniad",
"ep_adminpads2_pad-user-count": "Cyfri defnyddiwr pad",
"ep_adminpads2_padname": "Enwpad",
"ep_adminpads2_search-box.placeholder": "Term chwilio",
"ep_adminpads2_search-button.value": "Chwilio",
"ep_adminpads2_search-done": "Wedi gorffen",
"ep_adminpads2_search-error-explanation": "Nam ar y gweinydd wrth chwilio'r padiau:",
"ep_adminpads2_search-error-title": "Methwyd a chael y rhestr pad",
"ep_adminpads2_search-heading": "Chwilio am badiau",
"ep_adminpads2_title": "Gweinyddiaeth y pad",
"ep_adminpads2_unknown-error": "Nam o ryw fath",
"ep_adminpads2_unknown-status": "Statws anhysbys"
}

View file

@ -0,0 +1,14 @@
{
"@metadata": {
"authors": [
"Saederup92"
]
},
"ep_adminpads2_action": "Handling",
"ep_adminpads2_delete.value": "Slet",
"ep_adminpads2_last-edited": "Sidst redigeret",
"ep_adminpads2_loading": "Indlæser...",
"ep_adminpads2_no-results": "Ingen resultater",
"ep_adminpads2_unknown-error": "Ukendt fejl",
"ep_adminpads2_unknown-status": "Ukendt status"
}

View file

@ -0,0 +1,32 @@
{
"@metadata": {
"authors": [
"Brettchenweber",
"Justman10000",
"Lorisobi",
"SamTV",
"Umlaut",
"Zunkelty"
]
},
"ep_adminpads2_action": "Aktion",
"ep_adminpads2_autoupdate-label": "Automatisch bei Pad-Änderungen updaten",
"ep_adminpads2_autoupdate.title": "Aktiviert oder deaktiviert automatische Aktualisierungen für die aktuelle Abfrage.",
"ep_adminpads2_confirm": "Willst du das Pad {{padID}} wirklich löschen?",
"ep_adminpads2_delete.value": "Löschen",
"ep_adminpads2_last-edited": "Zuletzt bearbeitet",
"ep_adminpads2_loading": "Lädt...",
"ep_adminpads2_manage-pads": "Pads verwalten",
"ep_adminpads2_no-results": "Keine Ergebnisse",
"ep_adminpads2_pad-user-count": "Nutzerzahl des Pads",
"ep_adminpads2_padname": "Padname",
"ep_adminpads2_search-box.placeholder": "Suchbegriff",
"ep_adminpads2_search-button.value": "Suche",
"ep_adminpads2_search-done": "Suche vollendet",
"ep_adminpads2_search-error-explanation": "Der Server ist bei der Suche nach Pads auf einen Fehler gestoßen:",
"ep_adminpads2_search-error-title": "Pad-Liste konnte nicht abgerufen werden",
"ep_adminpads2_search-heading": "Nach Pads suchen",
"ep_adminpads2_title": "Pad-Verwaltung",
"ep_adminpads2_unknown-error": "Unbekannter Fehler",
"ep_adminpads2_unknown-status": "Unbekannter Status"
}

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"1917 Ekim Devrimi",
"Mirzali"
]
},
"ep_adminpads2_action": "Hereketi",
"ep_adminpads2_autoupdate-label": "Vurnayışanê pedi otomatik rocane kerê",
"ep_adminpads2_autoupdate.title": "Persê mewcudi rê rocaneyışanê otomatika aktiv ke ya zi dewrê ra vecê",
"ep_adminpads2_confirm": ıma qayılê pedê {{padID}} bıesternê?",
"ep_adminpads2_delete.value": "Bestere",
"ep_adminpads2_last-edited": "Vurnayışo peyên",
"ep_adminpads2_loading": "Bar beno...",
"ep_adminpads2_manage-pads": "Pedan idare kerê",
"ep_adminpads2_no-results": "Netice çıniyo",
"ep_adminpads2_pad-user-count": "Amarê karberanê pedi",
"ep_adminpads2_padname": "Padname",
"ep_adminpads2_search-box.placeholder": "termê cıgêrayış",
"ep_adminpads2_search-button.value": "Cı geyre",
"ep_adminpads2_search-done": "Cıgeyrayışi temam",
"ep_adminpads2_search-error-explanation": "Server cıgeyrayışê pedan de yew xetaya raşt ame",
"ep_adminpads2_search-error-title": "Lista pedi nêgêriye",
"ep_adminpads2_search-heading": "Pedan cıgeyrayış",
"ep_adminpads2_title": "İdarey pedi",
"ep_adminpads2_unknown-error": "Xetaya nêzanıtiye",
"ep_adminpads2_unknown-status": "Weziyeto nêzanaye"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Michawiki"
]
},
"ep_adminpads2_action": "Akcija",
"ep_adminpads2_autoupdate-label": "Pśi změnach na zapisniku awtomatiski aktualizěrowaś",
"ep_adminpads2_autoupdate.title": "Zmóžnja abo znjemóžnja awtomatiske aktualizacije za aktualne wótpšašowanje.",
"ep_adminpads2_confirm": "Cośo napšawdu zapisnik {{padID}} lašowaś?",
"ep_adminpads2_delete.value": "Lašowaś",
"ep_adminpads2_last-edited": "Slědna změna",
"ep_adminpads2_loading": "Zacytujo se...",
"ep_adminpads2_manage-pads": "Zapisniki zastojaś",
"ep_adminpads2_no-results": "Žedne wuslědki",
"ep_adminpads2_pad-user-count": "Licba wužywarjow zapisnika",
"ep_adminpads2_padname": "Mě zapisnika",
"ep_adminpads2_search-box.placeholder": "Pytańske zapśimjeśe",
"ep_adminpads2_search-button.value": "Pytaś",
"ep_adminpads2_search-done": "Pytanje dokóńcone",
"ep_adminpads2_search-error-explanation": "Serwer jo starcył na zmólku, mjaztym až jo pytał za zapisnikami:",
"ep_adminpads2_search-error-title": "Lisćina zapisnikow njedajo se wobstaraś",
"ep_adminpads2_search-heading": "Za zapisnikami pytaś",
"ep_adminpads2_title": "Zapisnikowa administracija",
"ep_adminpads2_unknown-error": "Njeznata zmólka",
"ep_adminpads2_unknown-status": "Njeznaty status"
}

View file

@ -0,0 +1,16 @@
{
"@metadata": {
"authors": [
"Norhorn"
]
},
"ep_adminpads2_delete.value": "Διαγραφή",
"ep_adminpads2_last-edited": "Τελευταία απεξεργασία",
"ep_adminpads2_loading": "Φόρτωση…",
"ep_adminpads2_no-results": "Κανένα αποτέλεσμα",
"ep_adminpads2_search-box.placeholder": "Αναζήτηση όρων",
"ep_adminpads2_search-button.value": "Αναζήτηση",
"ep_adminpads2_search-done": "Ολοκλήρωση αναζήτησης",
"ep_adminpads2_unknown-error": "Άγνωστο σφάλμα",
"ep_adminpads2_unknown-status": "Άγνωστη κατάσταση"
}

View file

@ -0,0 +1,22 @@
{
"ep_adminpads2_action": "Action",
"ep_adminpads2_autoupdate-label": "Auto-update on pad changes",
"ep_adminpads2_autoupdate.title": "Enables or disables automatic updates for the current query.",
"ep_adminpads2_confirm": "Do you really want to delete the pad {{padID}}?",
"ep_adminpads2_delete.value": "Delete",
"ep_adminpads2_last-edited": "Last edited",
"ep_adminpads2_loading": "Loading…",
"ep_adminpads2_manage-pads": "Manage pads",
"ep_adminpads2_no-results": "No results",
"ep_adminpads2_pad-user-count": "Pad user count",
"ep_adminpads2_padname": "Padname",
"ep_adminpads2_search-box.placeholder": "Search term",
"ep_adminpads2_search-button.value": "Search",
"ep_adminpads2_search-done": "Search complete",
"ep_adminpads2_search-error-explanation": "The server encountered an error while searching for pads:",
"ep_adminpads2_search-error-title": "Failed to get pad list",
"ep_adminpads2_search-heading": "Search for pads",
"ep_adminpads2_title": "Pad administration",
"ep_adminpads2_unknown-error": "Unknown error",
"ep_adminpads2_unknown-status": "Unknown status"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Izendegi"
]
},
"ep_adminpads2_action": "Ekintza",
"ep_adminpads2_autoupdate-label": "Automatikoki eguneratu pad-aren aldaketak daudenean",
"ep_adminpads2_autoupdate.title": "Oraingo kontsultarako eguneratze automatikoak gaitu edo desgaitzen du.",
"ep_adminpads2_confirm": "Ziur zaude {{padID}} pad-a ezabatu nahi duzula?",
"ep_adminpads2_delete.value": "Ezabatu",
"ep_adminpads2_last-edited": "Azkenengoz editatua",
"ep_adminpads2_loading": "Kargatzen...",
"ep_adminpads2_manage-pads": "Kudeatu pad-ak",
"ep_adminpads2_no-results": "Emaitzarik ez",
"ep_adminpads2_pad-user-count": "Pad-erabiltzaile kopurua",
"ep_adminpads2_padname": "Pad-izena",
"ep_adminpads2_search-box.placeholder": "Bilaketa testua",
"ep_adminpads2_search-button.value": "Bilatu",
"ep_adminpads2_search-done": "Bilaketa osatu da",
"ep_adminpads2_search-error-explanation": "Zerbitzariak errore bat izan du pad-ak bilatzean:",
"ep_adminpads2_search-error-title": "Pad-zerrenda eskuratzeak huts egin du",
"ep_adminpads2_search-heading": "Bilatu pad-ak",
"ep_adminpads2_title": "Pad-en kudeaketa",
"ep_adminpads2_unknown-error": "Errore ezezaguna",
"ep_adminpads2_unknown-status": "Egoera ezezaguna"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Ibrahima Malal Sarr"
]
},
"ep_adminpads2_action": "Baɗal",
"ep_adminpads2_autoupdate-label": "Hesɗitin e jaajol tuma baylagol faɗo",
"ep_adminpads2_autoupdate.title": "Hurminat walla daaƴa kesɗitine jaaje wonannde ɗaɓɓitannde wonaande.",
"ep_adminpads2_confirm": "Aɗa yiɗi e jaati momtude faɗo {{padID}}?",
"ep_adminpads2_delete.value": "Momtu",
"ep_adminpads2_last-edited": "Taƴtaa sakket",
"ep_adminpads2_loading": "Nana loowa…",
"ep_adminpads2_manage-pads": "Toppito paɗe",
"ep_adminpads2_no-results": "Alaa njaltudi",
"ep_adminpads2_pad-user-count": "Limoore huutorɓe faɗo",
"ep_adminpads2_padname": "Innde faɗo",
"ep_adminpads2_search-box.placeholder": "Helmere njiilaw",
"ep_adminpads2_search-button.value": "Yiylo",
"ep_adminpads2_search-done": "Njiylaw timmii",
"ep_adminpads2_search-error-explanation": "Sarworde ndee hawrii e juumre tuma nde yiylotoo faɗo:",
"ep_adminpads2_search-error-title": "Horiima heɓde doggol paɗe",
"ep_adminpads2_search-heading": "Yiylo paɗe",
"ep_adminpads2_title": "Yiylorde paɗe",
"ep_adminpads2_unknown-error": "Juumre nde anndaaka",
"ep_adminpads2_unknown-status": "Ngonka ka anndaaka"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Artnay",
"Kyykaarme",
"MITO",
"Maantietäjä",
"Yupik"
]
},
"ep_adminpads2_action": "Toiminto",
"ep_adminpads2_delete.value": "Poista",
"ep_adminpads2_last-edited": "Viimeksi muokattu",
"ep_adminpads2_loading": "Ladataan...",
"ep_adminpads2_manage-pads": "Hallitse muistioita",
"ep_adminpads2_no-results": "Ei tuloksia",
"ep_adminpads2_pad-user-count": "Pad-käyttäjien määrä",
"ep_adminpads2_padname": "Muistion nimi",
"ep_adminpads2_search-box.placeholder": "Haettava teksti",
"ep_adminpads2_search-button.value": "Etsi",
"ep_adminpads2_search-done": "Haku valmis",
"ep_adminpads2_search-error-explanation": "Palvelimessa tapahtui virhe etsiessään muistioita:",
"ep_adminpads2_search-error-title": "Pad-luettelon hakeminen epäonnistui",
"ep_adminpads2_search-heading": "Etsi sisältöä",
"ep_adminpads2_unknown-error": "Tuntematon virhe",
"ep_adminpads2_unknown-status": "Tuntematon tila"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Verdy p"
]
},
"ep_adminpads2_action": "Action",
"ep_adminpads2_autoupdate-label": "Mise à jour automatique en cas de changements du bloc-notes",
"ep_adminpads2_autoupdate.title": "Active ou désactive les mises à jour automatiques pour la requête actuelle.",
"ep_adminpads2_confirm": "Voulez-vous vraiment supprimer le bloc-notes {{padID}}?",
"ep_adminpads2_delete.value": "Supprimer",
"ep_adminpads2_last-edited": "Dernière modification",
"ep_adminpads2_loading": "Chargement en cours...",
"ep_adminpads2_manage-pads": "Gérer les bloc-notes",
"ep_adminpads2_no-results": "Aucun résultat",
"ep_adminpads2_pad-user-count": "Nombre dutilisateurs du bloc-notes",
"ep_adminpads2_padname": "Nom du bloc-notes",
"ep_adminpads2_search-box.placeholder": "Terme de recherche",
"ep_adminpads2_search-button.value": "Rechercher",
"ep_adminpads2_search-done": "Recherche terminée",
"ep_adminpads2_search-error-explanation": "Le serveur a rencontré une erreur en cherchant des blocs-notes:",
"ep_adminpads2_search-error-title": "Échec dobtention de la liste de blocs-notes",
"ep_adminpads2_search-heading": "Rechercher des blocs-notes",
"ep_adminpads2_title": "Administration du bloc-notes",
"ep_adminpads2_unknown-error": "Erreur inconnue",
"ep_adminpads2_unknown-status": "État inconnu"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Ghose"
]
},
"ep_adminpads2_action": "Accións",
"ep_adminpads2_autoupdate-label": "Actualización automática dos cambios",
"ep_adminpads2_autoupdate.title": "Activa ou desactiva as actualizacións automáticas para a consulta actual.",
"ep_adminpads2_confirm": "Tes a certeza de querer eliminar o pad {{padID}}?",
"ep_adminpads2_delete.value": "Eliminar",
"ep_adminpads2_last-edited": "Última edición",
"ep_adminpads2_loading": "Cargando…",
"ep_adminpads2_manage-pads": "Xestionar pads",
"ep_adminpads2_no-results": "Sen resultados",
"ep_adminpads2_pad-user-count": "Usuarias neste pad",
"ep_adminpads2_padname": "Nome do pad",
"ep_adminpads2_search-box.placeholder": "Buscar termo",
"ep_adminpads2_search-button.value": "Buscar",
"ep_adminpads2_search-done": "Busca completa",
"ep_adminpads2_search-error-explanation": "O servidor atopou un fallo cando buscaba pads:",
"ep_adminpads2_search-error-title": "Non se obtivo a lista de pads",
"ep_adminpads2_search-heading": "Buscar pads",
"ep_adminpads2_title": "Administración do pad",
"ep_adminpads2_unknown-error": "Erro descoñecido",
"ep_adminpads2_unknown-status": "Estado descoñecido"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"YaronSh"
]
},
"ep_adminpads2_action": "פעולה",
"ep_adminpads2_autoupdate-label": "לעדכן אוטומטית כשהמחברת נערכת",
"ep_adminpads2_autoupdate.title": "הפעלה או השבתה של עדכונים אוטומטיים לשאילתה הנוכחית.",
"ep_adminpads2_confirm": "למחוק את המחברת {{padID}}?",
"ep_adminpads2_delete.value": "מחיקה",
"ep_adminpads2_last-edited": "עריכה אחרונה",
"ep_adminpads2_loading": "בטעינה…",
"ep_adminpads2_manage-pads": "ניהול מחברות",
"ep_adminpads2_no-results": "אין תוצאות",
"ep_adminpads2_pad-user-count": "ספירת משתמשים במחברת",
"ep_adminpads2_padname": "שם המחברת",
"ep_adminpads2_search-box.placeholder": "הביטוי לחיפוש",
"ep_adminpads2_search-button.value": "חיפוש",
"ep_adminpads2_search-done": "החיפוש הושלם",
"ep_adminpads2_search-error-explanation": "השרת נתקל בשגיאה בעת חיפוש מחברות:",
"ep_adminpads2_search-error-title": "קבלת רשימת המחברות נכשלה",
"ep_adminpads2_search-heading": "חיפוש אחר מחברות",
"ep_adminpads2_title": "ניהול מחברות",
"ep_adminpads2_unknown-error": "שגיאה בלתי־ידועה",
"ep_adminpads2_unknown-status": "מצב לא ידוע"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Michawiki"
]
},
"ep_adminpads2_action": "Akcija",
"ep_adminpads2_autoupdate-label": "Při změnach na zapisniku awtomatisce aktualizować",
"ep_adminpads2_autoupdate.title": "Zmóžnja abo znjemóžnja awtomatiske aktualizacije za aktualne wotprašowanje.",
"ep_adminpads2_confirm": "Chceće woprawdźe zapisnik {{padID}} zhašeć?",
"ep_adminpads2_delete.value": "Zhašeć",
"ep_adminpads2_last-edited": "Poslednja změna",
"ep_adminpads2_loading": "Začituje so...",
"ep_adminpads2_manage-pads": "Zapisniki rjadować",
"ep_adminpads2_no-results": "Žane wuslědki.",
"ep_adminpads2_pad-user-count": "Ličba wužiwarjow zapisnika",
"ep_adminpads2_padname": "Mjeno zapisnika",
"ep_adminpads2_search-box.placeholder": "Pytanske zapřijeće",
"ep_adminpads2_search-button.value": "Pytać",
"ep_adminpads2_search-done": "Pytanje dokónčene",
"ep_adminpads2_search-error-explanation": "Serwer je na zmylk storčił, mjeztym zo je za zapisnikami pytał:",
"ep_adminpads2_search-error-title": "Lisćina zapisnikow njeda so wobstarać",
"ep_adminpads2_search-heading": "Za zapisnikami pytać",
"ep_adminpads2_title": "Zapisnikowa administracija",
"ep_adminpads2_unknown-error": "Njeznaty zmylk",
"ep_adminpads2_unknown-status": "Njeznaty status"
}

View file

@ -0,0 +1,25 @@
{
"@metadata": {
"authors": []
},
"ep_adminpads2_action": "Művelet",
"ep_adminpads2_autoupdate-label": "Változáskor jegyzetfüzet önműködő frissítése",
"ep_adminpads2_autoupdate.title": "Önműködő frissítése az jelenlegi lekérdezéshez be- vagy kikapcsolása.",
"ep_adminpads2_confirm": "Biztosan törölni szeretné a(z) {{padID}} jegyzetfüzetet?",
"ep_adminpads2_delete.value": "Törlés",
"ep_adminpads2_last-edited": "Utoljára szerkesztve",
"ep_adminpads2_loading": "Betöltés folyamatban…",
"ep_adminpads2_manage-pads": "Jegyzetfüzetek kezelése",
"ep_adminpads2_no-results": "Nincs találat",
"ep_adminpads2_pad-user-count": "Jegyzetfüzet felhasználók száma",
"ep_adminpads2_padname": "Jegyzetfüzet név",
"ep_adminpads2_search-box.placeholder": "Keresési kifejezés",
"ep_adminpads2_search-button.value": "Keresés",
"ep_adminpads2_search-done": "Keresés befejezve",
"ep_adminpads2_search-error-explanation": "A kiszolgáló hibát észlelt a jegyzetfüzetek keresésekor:",
"ep_adminpads2_search-error-title": "Nem sikerült lekérni a jegyzetfüzet listát",
"ep_adminpads2_search-heading": "Jegyzetfüzetek keresése",
"ep_adminpads2_title": "Jegyzetfüzet felügyelete",
"ep_adminpads2_unknown-error": "Ismeretlen hiba",
"ep_adminpads2_unknown-status": "Ismeretlen állapot"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"McDutchie"
]
},
"ep_adminpads2_action": "Action",
"ep_adminpads2_autoupdate-label": "Actualisar automaticamente le pad in caso de cambiamentos",
"ep_adminpads2_autoupdate.title": "Activa o disactiva le actualisationes automatic pro le consulta actual.",
"ep_adminpads2_confirm": "Es tu secur de voler deler le pad {{padID}}?",
"ep_adminpads2_delete.value": "Deler",
"ep_adminpads2_last-edited": "Ultime modification",
"ep_adminpads2_loading": "Cargamento in curso…",
"ep_adminpads2_manage-pads": "Gerer pads",
"ep_adminpads2_no-results": "Nulle resultato",
"ep_adminpads2_pad-user-count": "Numero de usatores del pad",
"ep_adminpads2_padname": "Nomine del pad",
"ep_adminpads2_search-box.placeholder": "Termino de recerca",
"ep_adminpads2_search-button.value": "Cercar",
"ep_adminpads2_search-done": "Recerca terminate",
"ep_adminpads2_search-error-explanation": "Le servitor ha incontrate un error durante le recerca de pads:",
"ep_adminpads2_search-error-title": "Non poteva obtener le lista de pads",
"ep_adminpads2_search-heading": "Cercar pads",
"ep_adminpads2_title": "Administration de pads",
"ep_adminpads2_unknown-error": "Error incognite",
"ep_adminpads2_unknown-status": "Stato incognite"
}

View file

@ -0,0 +1,16 @@
{
"@metadata": {
"authors": [
"Beta16",
"Luca.favorido"
]
},
"ep_adminpads2_action": "Azione",
"ep_adminpads2_delete.value": "Cancella",
"ep_adminpads2_last-edited": "Ultima modifica",
"ep_adminpads2_loading": "Caricamento…",
"ep_adminpads2_no-results": "Nessun risultato",
"ep_adminpads2_search-button.value": "Cerca",
"ep_adminpads2_unknown-error": "Errore sconosciuto",
"ep_adminpads2_unknown-status": "Stato sconosciuto"
}

View file

@ -0,0 +1,13 @@
{
"@metadata": {
"authors": [
"ಮಲ್ನಾಡಾಚ್ ಕೊಂಕ್ಣೊ"
]
},
"ep_adminpads2_action": "ಕ್ರಿಯೆ",
"ep_adminpads2_delete.value": "ಅಳಿಸು",
"ep_adminpads2_loading": "ತುಂಬಿಸಲಾಗುತ್ತಿದೆ…",
"ep_adminpads2_no-results": "ಯಾವ ಫಲಿತಾಂಶಗಳೂ ಇಲ್ಲ",
"ep_adminpads2_search-button.value": "ಹುಡುಕು",
"ep_adminpads2_unknown-error": "ಅಪರಿಚಿತ ದೋಷ"
}

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"Ykhwong",
"그냥기여자"
]
},
"ep_adminpads2_action": "동작",
"ep_adminpads2_autoupdate-label": "패드 변경 시 자동 업데이트",
"ep_adminpads2_autoupdate.title": "현재 쿼리의 자동 업데이트를 활성화하거나 비활성화합니다.",
"ep_adminpads2_confirm": "{{padID}} 패드를 삭제하시겠습니까?",
"ep_adminpads2_delete.value": "삭제",
"ep_adminpads2_last-edited": "최근 편집",
"ep_adminpads2_loading": "불러오는 중...",
"ep_adminpads2_manage-pads": "패드 관리",
"ep_adminpads2_no-results": "결과 없음",
"ep_adminpads2_pad-user-count": "패드 사용자 수",
"ep_adminpads2_padname": "패드 이름",
"ep_adminpads2_search-box.placeholder": "검색어",
"ep_adminpads2_search-button.value": "검색",
"ep_adminpads2_search-done": "검색 완료",
"ep_adminpads2_search-error-explanation": "패드 검색 중 서버에 오류가 발생했습니다:",
"ep_adminpads2_search-error-title": "패드 목록 가져오기 실패",
"ep_adminpads2_search-heading": "패드 검색",
"ep_adminpads2_title": "패드 관리",
"ep_adminpads2_unknown-error": "알 수 없는 오류",
"ep_adminpads2_unknown-status": "알 수 없는 상태"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Къарачайлы"
]
},
"ep_adminpads2_action": "Этиу",
"ep_adminpads2_autoupdate-label": "Блокнот тюрлендириулеринде автомат халда джангыртыу",
"ep_adminpads2_autoupdate.title": "Баргъан излем ючюн автомат халда джангыртыуланы джандын неда джукълат.",
"ep_adminpads2_confirm": "{{padID}} блокнотну керти да кетерирге излеймисиз?",
"ep_adminpads2_delete.value": "Кетер",
"ep_adminpads2_last-edited": "Ахыр тюзетиу",
"ep_adminpads2_loading": "Джюклениу…",
"ep_adminpads2_manage-pads": "Блокнотланы оноуун эт",
"ep_adminpads2_no-results": "Эсебле джокъдула",
"ep_adminpads2_pad-user-count": "Блокнот хайырланыучуланы саны",
"ep_adminpads2_padname": "Блокнот ат",
"ep_adminpads2_search-box.placeholder": "Терминни изле",
"ep_adminpads2_search-button.value": "Изле",
"ep_adminpads2_search-done": "Излеу тамамланды",
"ep_adminpads2_search-error-explanation": "Сервер, блокнотланы излеген заманда халат табды:",
"ep_adminpads2_search-error-title": "Блокнот тизмеси алынамады",
"ep_adminpads2_search-heading": "Блокнотла ючюн излеу",
"ep_adminpads2_title": "Блокнот башчылыкъ",
"ep_adminpads2_unknown-error": "Билинмеген халат",
"ep_adminpads2_unknown-status": "Билинмеген турум"
}

View file

@ -0,0 +1,16 @@
{
"@metadata": {
"authors": [
"Robby",
"Volvox"
]
},
"ep_adminpads2_confirm": "Wëllt Dir de Pad {{padID}} wierklech läschen?",
"ep_adminpads2_delete.value": "Läschen",
"ep_adminpads2_loading": "Lueden...",
"ep_adminpads2_no-results": "Keng Resultater",
"ep_adminpads2_padname": "Padnumm",
"ep_adminpads2_search-box.placeholder": "Sichbegrëff",
"ep_adminpads2_search-button.value": "Sichen",
"ep_adminpads2_unknown-error": "Onbekannte Feeler"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Nokeoo"
]
},
"ep_adminpads2_action": "Veiksmas",
"ep_adminpads2_autoupdate-label": "Automatinis bloknoto keitimų naujinimas",
"ep_adminpads2_autoupdate.title": "Įjungia arba išjungia automatinius dabartinės užklausos atnaujinimus.",
"ep_adminpads2_confirm": "Ar tikrai norite ištrinti bloknotą {{padID}}?",
"ep_adminpads2_delete.value": "Ištrinti",
"ep_adminpads2_last-edited": "Paskutinis pakeitimas",
"ep_adminpads2_loading": "Įkeliama…",
"ep_adminpads2_manage-pads": "Tvarkyti bloknotą",
"ep_adminpads2_no-results": "Nėra rezultatų",
"ep_adminpads2_pad-user-count": "Bloknoto naudotojų skaičius",
"ep_adminpads2_padname": "Bloknoto pavadinimas",
"ep_adminpads2_search-box.placeholder": "Paieškos terminas",
"ep_adminpads2_search-button.value": "Paieška",
"ep_adminpads2_search-done": "Paieška baigta",
"ep_adminpads2_search-error-explanation": "Serveris susidūrė su klaida ieškant bloknotų:",
"ep_adminpads2_search-error-title": "Nepavyko gauti bloknotų sąrašo",
"ep_adminpads2_search-heading": "Ieškokite bloknotų",
"ep_adminpads2_title": "Bloknotų administravimas",
"ep_adminpads2_unknown-error": "Nežinoma klaida",
"ep_adminpads2_unknown-status": "Nežinoma būsena"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Bjankuloski06"
]
},
"ep_adminpads2_action": "Дејство",
"ep_adminpads2_autoupdate-label": "Самоподнова при измени во тетратката",
"ep_adminpads2_autoupdate.title": "Овозможува или оневозможува самоподнова на тековното барање.",
"ep_adminpads2_confirm": "Дали навистина сакате да ја избришете тетратката {{padID}}?",
"ep_adminpads2_delete.value": "Избриши",
"ep_adminpads2_last-edited": "Последно уредување",
"ep_adminpads2_loading": "Вчитувам…",
"ep_adminpads2_manage-pads": "Раководење со тетратки",
"ep_adminpads2_no-results": "Нема исход",
"ep_adminpads2_pad-user-count": "Корисници на тетратката",
"ep_adminpads2_padname": "Назив на тетратката",
"ep_adminpads2_search-box.placeholder": "Пребаран поим",
"ep_adminpads2_search-button.value": "Пребарај",
"ep_adminpads2_search-done": "Пребарувањето заврши",
"ep_adminpads2_search-error-explanation": "Опслужувачот наиде на грешка при пребарувањето на тетратки:",
"ep_adminpads2_search-error-title": "Не можев да го добијам списокот на тетратки",
"ep_adminpads2_search-heading": "Пребарај по тетратките",
"ep_adminpads2_title": "Администрација на тетратки",
"ep_adminpads2_unknown-error": "Непозната грешка",
"ep_adminpads2_unknown-status": "Непозната состојба"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Andibecker"
]
},
"ep_adminpads2_action": "လုပ်ဆောင်ချက်",
"ep_adminpads2_autoupdate-label": "pad အပြောင်းအလဲများတွင်အလိုအလျောက်အပ်ဒိတ်လုပ်ပါ",
"ep_adminpads2_autoupdate.title": "လက်ရှိမေးမြန်းမှုအတွက်အလိုအလျောက်အပ်ဒိတ်များကိုဖွင့်ပါသို့မဟုတ်ပိတ်ပါ။",
"ep_adminpads2_confirm": "pad {{padID}} ကိုသင်တကယ်ဖျက်ချင်လား။",
"ep_adminpads2_delete.value": "ဖျက်ပါ",
"ep_adminpads2_last-edited": "နောက်ဆုံးတည်းဖြတ်သည်",
"ep_adminpads2_loading": "ဖွင့်နေသည်…",
"ep_adminpads2_manage-pads": "pads များကိုစီမံပါ",
"ep_adminpads2_no-results": "ရလဒ်မရှိပါ",
"ep_adminpads2_pad-user-count": "Pad အသုံးပြုသူအရေအတွက်",
"ep_adminpads2_padname": "Padname",
"ep_adminpads2_search-box.placeholder": "ဝေါဟာရရှာဖွေပါ",
"ep_adminpads2_search-button.value": "ရှာဖွေပါ",
"ep_adminpads2_search-done": "ရှာဖွေမှုပြီးပါပြီ",
"ep_adminpads2_search-error-explanation": "pads များကိုရှာဖွေစဉ်ဆာဗာသည်အမှားတစ်ခုကြုံခဲ့သည်။",
"ep_adminpads2_search-error-title": "pad စာရင်းရယူရန်မအောင်မြင်ပါ",
"ep_adminpads2_search-heading": "pads များကိုရှာဖွေပါ",
"ep_adminpads2_title": "Pad စီမံခန့်ခွဲမှု",
"ep_adminpads2_unknown-error": "အမည်မသိအမှား",
"ep_adminpads2_unknown-status": "အခြေအနေမသိ"
}

View file

@ -0,0 +1,13 @@
{
"@metadata": {
"authors": [
"EdoAug"
]
},
"ep_adminpads2_action": "Handling",
"ep_adminpads2_last-edited": "Sist redigert",
"ep_adminpads2_loading": "Laster …",
"ep_adminpads2_no-results": "Ingen resultater",
"ep_adminpads2_search-button.value": "Søk",
"ep_adminpads2_search-done": "Søk fullført"
}

View file

@ -0,0 +1,29 @@
{
"@metadata": {
"authors": [
"Aranka",
"McDutchie",
"Spinster"
]
},
"ep_adminpads2_action": "Handeling",
"ep_adminpads2_autoupdate-label": "Automatisch bijwerken bij aanpassingen aan de pad",
"ep_adminpads2_autoupdate.title": "Schakelt automatische updates voor de huidige query in of uit.",
"ep_adminpads2_confirm": "Wil je de pad {{padID}} echt verwijderen?",
"ep_adminpads2_delete.value": "Verwijderen",
"ep_adminpads2_last-edited": "Laatst bewerkt",
"ep_adminpads2_loading": "Bezig met laden...",
"ep_adminpads2_manage-pads": "Pads beheren",
"ep_adminpads2_no-results": "Geen resultaten",
"ep_adminpads2_pad-user-count": "Aantal gebruikers van de pad",
"ep_adminpads2_padname": "Naam van de pad",
"ep_adminpads2_search-box.placeholder": "Zoekterm",
"ep_adminpads2_search-button.value": "Zoeken",
"ep_adminpads2_search-done": "Zoekopdracht voltooid",
"ep_adminpads2_search-error-explanation": "De server heeft een fout aangetroffen tijdens het zoeken naar pads:",
"ep_adminpads2_search-error-title": "Kan lijst met pads niet ophalen",
"ep_adminpads2_search-heading": "Pads zoeken",
"ep_adminpads2_title": "Administratie van pad",
"ep_adminpads2_unknown-error": "Onbekende fout",
"ep_adminpads2_unknown-status": "Onbekende status"
}

View file

@ -0,0 +1,21 @@
{
"@metadata": {
"authors": [
"Quentí"
]
},
"ep_adminpads2_action": "Accion",
"ep_adminpads2_delete.value": "Suprimir",
"ep_adminpads2_last-edited": "Darrièra edicion",
"ep_adminpads2_loading": "Cargament…",
"ep_adminpads2_manage-pads": "Gerir los pads",
"ep_adminpads2_no-results": "Pas cap de resultat",
"ep_adminpads2_padname": "Nom del pad",
"ep_adminpads2_search-box.placeholder": "Tèrme de recèrca",
"ep_adminpads2_search-button.value": "Recercar",
"ep_adminpads2_search-done": "Recèrca acabada",
"ep_adminpads2_search-heading": "Cercar de pads",
"ep_adminpads2_title": "Administracion de pad",
"ep_adminpads2_unknown-error": "Error desconeguda",
"ep_adminpads2_unknown-status": "Estat desconegut"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Borichèt"
]
},
"ep_adminpads2_action": "Assion",
"ep_adminpads2_autoupdate-label": "Agiornament automàtich an sle modìfiche ëd plancia",
"ep_adminpads2_autoupdate.title": "Abilité o disabilité j'agiornament automàtich për l'arcesta atual.",
"ep_adminpads2_confirm": "Veul-lo për da bon dëscancelé la plancia {{padID}}?",
"ep_adminpads2_delete.value": "Dëscancelé",
"ep_adminpads2_last-edited": "Modificà l'ùltima vira",
"ep_adminpads2_loading": "Cariament…",
"ep_adminpads2_manage-pads": "Gestì le plance",
"ep_adminpads2_no-results": "Gnun arzultà",
"ep_adminpads2_pad-user-count": "Conteur ëd plancia dl'utent",
"ep_adminpads2_padname": "Nòm ëd plancia",
"ep_adminpads2_search-box.placeholder": "Tèrmin d'arserca",
"ep_adminpads2_search-button.value": "Arserca",
"ep_adminpads2_search-done": "Arserca completà",
"ep_adminpads2_search-error-explanation": "Ël servent a l'ha rancontrà n'eror an sërcand dle plance:",
"ep_adminpads2_search-error-title": "Falì a oten-e la lista ëd plance",
"ep_adminpads2_search-heading": "Arserca ëd plance",
"ep_adminpads2_title": "Aministrassion ëd plance",
"ep_adminpads2_unknown-error": "Eror nen conossù",
"ep_adminpads2_unknown-status": "Statù nen conossù"
}

View file

@ -0,0 +1,30 @@
{
"@metadata": {
"authors": [
"Duke of Wikipädia",
"Eduardo Addad de Oliveira",
"Eduardoaddad",
"YuriNikolai"
]
},
"ep_adminpads2_action": "Ação",
"ep_adminpads2_autoupdate-label": "Atualizar notas automaticamente",
"ep_adminpads2_autoupdate.title": "Habilita ou desabilita atualizações automáticas para a consulta atual.",
"ep_adminpads2_confirm": "Você realmente deseja excluir a nota {{padID}}?",
"ep_adminpads2_delete.value": "Excluir",
"ep_adminpads2_last-edited": "Última edição",
"ep_adminpads2_loading": "Carregando…",
"ep_adminpads2_manage-pads": "Gerenciar notas",
"ep_adminpads2_no-results": "Sem resultados",
"ep_adminpads2_pad-user-count": "Número de utilizadores na nota",
"ep_adminpads2_padname": "Nome da nota",
"ep_adminpads2_search-box.placeholder": "Termo de pesquisa",
"ep_adminpads2_search-button.value": "Pesquisar",
"ep_adminpads2_search-done": "Busca completa",
"ep_adminpads2_search-error-explanation": "O servidor encontrou um erro enquanto procurava por notas:",
"ep_adminpads2_search-error-title": "Falha ao buscar lista de notas",
"ep_adminpads2_search-heading": "Pesquisar por notas",
"ep_adminpads2_title": "Administração de notas",
"ep_adminpads2_unknown-error": "Erro desconhecido",
"ep_adminpads2_unknown-status": "Status desconhecido"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Guilha"
]
},
"ep_adminpads2_action": "Ação",
"ep_adminpads2_autoupdate-label": "Atualizar automaticamente as notas",
"ep_adminpads2_autoupdate.title": "Ativa ou desativa atualizações automáticas na consulta atual.",
"ep_adminpads2_confirm": "Tencionas mesmo eliminar a nota {{padID}}?",
"ep_adminpads2_delete.value": "Eliminar",
"ep_adminpads2_last-edited": "Última edição",
"ep_adminpads2_loading": "A carregar...",
"ep_adminpads2_manage-pads": "Gerir notas",
"ep_adminpads2_no-results": "Sem resultados",
"ep_adminpads2_pad-user-count": "Número de utilizadores na nota",
"ep_adminpads2_padname": "Nome da nota",
"ep_adminpads2_search-box.placeholder": "Procurar termo",
"ep_adminpads2_search-button.value": "Procurar",
"ep_adminpads2_search-done": "Procura completa",
"ep_adminpads2_search-error-explanation": "O servidor encontrou um erro enquanto procurava por notas:",
"ep_adminpads2_search-error-title": "Falha ao obter lista de notas",
"ep_adminpads2_search-heading": "Procurar por notas",
"ep_adminpads2_title": "Administração da nota",
"ep_adminpads2_unknown-error": "Erro desconhecido",
"ep_adminpads2_unknown-status": "Estado desconhecido"
}

View file

@ -0,0 +1,10 @@
{
"@metadata": {
"authors": [
"BryanDavis"
]
},
"ep_adminpads2_action": "{{Identical|Action}}",
"ep_adminpads2_delete.value": "{{Identical|Delete}}",
"ep_adminpads2_search-button.value": "{{Identical|Search}}"
}

View file

@ -0,0 +1,31 @@
{
"@metadata": {
"authors": [
"DDPAT",
"Ice bulldog",
"Megakott",
"Okras",
"Pacha Tchernof"
]
},
"ep_adminpads2_action": "Действие",
"ep_adminpads2_autoupdate-label": "Автообновление при изменении документа",
"ep_adminpads2_autoupdate.title": "Включает или отключает автоматические обновления для текущего запроса.",
"ep_adminpads2_confirm": "Вы действительно хотите удалить документ {{padID}}?",
"ep_adminpads2_delete.value": "Удалить",
"ep_adminpads2_last-edited": "Последнее изменение",
"ep_adminpads2_loading": "Загружается…",
"ep_adminpads2_manage-pads": "Управление документами",
"ep_adminpads2_no-results": "Нет результатов",
"ep_adminpads2_pad-user-count": "Количество пользователей документа",
"ep_adminpads2_padname": "Название документа",
"ep_adminpads2_search-box.placeholder": "Искать термин",
"ep_adminpads2_search-button.value": "Найти",
"ep_adminpads2_search-done": "Поиск завершён",
"ep_adminpads2_search-error-explanation": "Сервер обнаружил ошибку при поиске документов:",
"ep_adminpads2_search-error-title": "Не удалось получить список документов",
"ep_adminpads2_search-heading": "Поиск документов",
"ep_adminpads2_title": "Администрирование документов",
"ep_adminpads2_unknown-error": "Неизвестная ошибка",
"ep_adminpads2_unknown-status": "Неизвестный статус"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Adr mm"
]
},
"ep_adminpads2_action": "Atzione",
"ep_adminpads2_autoupdate-label": "Atualizatzione automàtica de is modìficas de su pad",
"ep_adminpads2_autoupdate.title": "Ativat o disativat is atualizatziones automàticas pro sa chirca atuale.",
"ep_adminpads2_confirm": "Seguru chi boles cantzellare su pad {{padID}}?",
"ep_adminpads2_delete.value": "Cantzella",
"ep_adminpads2_last-edited": "Ùrtima modìfica",
"ep_adminpads2_loading": "Carrighende...",
"ep_adminpads2_manage-pads": "Gesti is pads",
"ep_adminpads2_no-results": "Nissunu resurtadu",
"ep_adminpads2_pad-user-count": "Nùmeru de utentes de pads",
"ep_adminpads2_padname": "Nòmine de su pad",
"ep_adminpads2_search-box.placeholder": "Tèrmine de chirca",
"ep_adminpads2_search-button.value": "Chirca",
"ep_adminpads2_search-done": "Chirca cumpleta",
"ep_adminpads2_search-error-explanation": "Su serbidore at agatadu un'errore chirchende pads:",
"ep_adminpads2_search-error-title": "Impossìbile otènnere sa lista de pads",
"ep_adminpads2_search-heading": "Chirca pads",
"ep_adminpads2_title": "Amministratzione de su pad",
"ep_adminpads2_unknown-error": "Errore disconnotu",
"ep_adminpads2_unknown-status": "Istadu disconnotu"
}

View file

@ -0,0 +1,14 @@
{
"@metadata": {
"authors": [
"F Samaritani"
]
},
"ep_adminpads2_action": "Azioni",
"ep_adminpads2_delete.value": "Canzella",
"ep_adminpads2_loading": "carrigghendi...",
"ep_adminpads2_no-results": "Nisciun risulthaddu",
"ep_adminpads2_search-button.value": "Zercha",
"ep_adminpads2_search-heading": "Zirchà dati",
"ep_adminpads2_unknown-error": "Errori ischunisciddu"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Yardom78"
]
},
"ep_adminpads2_action": "Akcia",
"ep_adminpads2_autoupdate-label": "Automatická aktualizácia zmien na poznámkovom bloku",
"ep_adminpads2_autoupdate.title": "Zapne alebo vypne automatickú aktualizáciu.",
"ep_adminpads2_confirm": "Skutočne chcete vymazať poznámkový blok {{padID}}?",
"ep_adminpads2_delete.value": "Vymazať",
"ep_adminpads2_last-edited": "Posledná úprava",
"ep_adminpads2_loading": "Načítavanie...",
"ep_adminpads2_manage-pads": "Spravovať poznámkové bloky",
"ep_adminpads2_no-results": "Žiadne výsledky",
"ep_adminpads2_pad-user-count": "Počet používateľov poznámkového bloku",
"ep_adminpads2_padname": "Názov poznámkového bloku",
"ep_adminpads2_search-box.placeholder": "Hľadať výraz",
"ep_adminpads2_search-button.value": "Hľadať",
"ep_adminpads2_search-done": "Hľadanie dokončené",
"ep_adminpads2_search-error-explanation": "Pri hľadaní poznámkového bloku došlo k chybe:",
"ep_adminpads2_search-error-title": "Nepodarilo sa získať zoznam poznámkových blokov",
"ep_adminpads2_search-heading": "Hľadať poznámkový blok",
"ep_adminpads2_title": "Správa poznámkového bloku",
"ep_adminpads2_unknown-error": "Neznáma chyba",
"ep_adminpads2_unknown-status": "Neznámy stav"
}

View file

@ -0,0 +1,20 @@
{
"@metadata": {
"authors": [
"Saraiki"
]
},
"ep_adminpads2_action": "عمل",
"ep_adminpads2_delete.value": "مٹاؤ",
"ep_adminpads2_last-edited": "چھیکڑی تبدیلی",
"ep_adminpads2_loading": "لوڈ تھین٘دا پئے۔۔۔",
"ep_adminpads2_manage-pads": "پیڈ منیج کرو",
"ep_adminpads2_no-results": "کوئی نتیجہ کائنی",
"ep_adminpads2_padname": "پیڈ ناں",
"ep_adminpads2_search-box.placeholder": "ٹرم ڳولو",
"ep_adminpads2_search-button.value": "ڳولو",
"ep_adminpads2_search-done": "ڳولݨ پورا تھیا",
"ep_adminpads2_search-heading": "پیڈاں دی ڳول",
"ep_adminpads2_unknown-error": "نامعلوم غلطی",
"ep_adminpads2_unknown-status": "نامعلوم حالت"
}

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"Eleassar",
"HairyFotr"
]
},
"ep_adminpads2_action": "Dejanje",
"ep_adminpads2_autoupdate-label": "Samodejno posodabljanje ob spremembah blokcev",
"ep_adminpads2_autoupdate.title": "Omogoči ali onemogoči samodejne posodobitve za trenutno poizvedbo.",
"ep_adminpads2_confirm": "Ali res želite izbrisati blokec {{padID}}?",
"ep_adminpads2_delete.value": "Izbriši",
"ep_adminpads2_last-edited": "Zadnje urejanje",
"ep_adminpads2_loading": "Nalaganje ...",
"ep_adminpads2_manage-pads": "Upravljanje blokcev",
"ep_adminpads2_no-results": "Ni zadetkov",
"ep_adminpads2_pad-user-count": "Število urejevalcev blokca",
"ep_adminpads2_padname": "Ime blokca",
"ep_adminpads2_search-box.placeholder": "Iskalni izraz",
"ep_adminpads2_search-button.value": "Išči",
"ep_adminpads2_search-done": "Iskanje končano",
"ep_adminpads2_search-error-explanation": "Strežnik je med iskanjem blokcev naletel na napako:",
"ep_adminpads2_search-error-title": "Ni bilo mogoče pridobiti seznama blokcev",
"ep_adminpads2_search-heading": "Iskanje blokcev",
"ep_adminpads2_title": "Upravljanje blokcev",
"ep_adminpads2_unknown-error": "Neznana napaka",
"ep_adminpads2_unknown-status": "Neznano stanje"
}

View file

@ -0,0 +1,13 @@
{
"@metadata": {
"authors": [
"Yupik"
]
},
"ep_adminpads2_delete.value": "Siho",
"ep_adminpads2_last-edited": "Majemustáá nubástittum",
"ep_adminpads2_search-box.placeholder": "Uuccâmsääni",
"ep_adminpads2_search-button.value": "Uusâ",
"ep_adminpads2_unknown-error": "Tubdâmettum feilâ",
"ep_adminpads2_unknown-status": "Tubdâmettum tile"
}

View file

@ -0,0 +1,16 @@
{
"@metadata": {
"authors": [
"Yupik"
]
},
"ep_adminpads2_delete.value": "Jaukkâd",
"ep_adminpads2_last-edited": "Mââimõssân muttum",
"ep_adminpads2_no-results": "Ij käunnʼjam ni mii",
"ep_adminpads2_padname": "Mošttʼtõspõʹmmai nõmm",
"ep_adminpads2_search-box.placeholder": "Ooccâmsääʹnn",
"ep_adminpads2_search-button.value": "Ooʒʒ",
"ep_adminpads2_search-heading": "Ooʒʒ mošttʼtõspõʹmmjid",
"ep_adminpads2_unknown-error": "Toobdteʹmes vââʹǩǩ",
"ep_adminpads2_unknown-status": "Toobdteʹmes status"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Besnik b"
]
},
"ep_adminpads2_action": "Veprim",
"ep_adminpads2_autoupdate-label": "Vetëpërditësohu, kur nga ndryshime blloku",
"ep_adminpads2_autoupdate.title": "Aktivizon ose çaktivizon përditësim të automatizuara për kërkesën e tanishme.",
"ep_adminpads2_confirm": "Doni vërtet të fshihet blloku {{padID}}?",
"ep_adminpads2_delete.value": "Fshije",
"ep_adminpads2_last-edited": "Përpunuar së fundi më",
"ep_adminpads2_loading": "Po ngarkohet…",
"ep_adminpads2_manage-pads": "Administroni blloqe",
"ep_adminpads2_no-results": "Ska përfundime",
"ep_adminpads2_pad-user-count": "Numër përdoruesish blloku",
"ep_adminpads2_padname": "Emër blloku",
"ep_adminpads2_search-box.placeholder": "Term kërkimi",
"ep_adminpads2_search-button.value": "Kërko",
"ep_adminpads2_search-done": "Kërkim i plotë",
"ep_adminpads2_search-error-explanation": "Shërbyesi hasi një gabim teksa kërkohej për blloqe:",
"ep_adminpads2_search-error-title": "Su arrit të merrej listë blloqesh",
"ep_adminpads2_search-heading": "Kërkoni për blloqe",
"ep_adminpads2_title": "Administrim blloku",
"ep_adminpads2_unknown-error": "Gabim i panjohur",
"ep_adminpads2_unknown-status": "Gjendje e panjohur"
}

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"Bengtsson96",
"WikiPhoenix"
]
},
"ep_adminpads2_action": "Åtgärd",
"ep_adminpads2_autoupdate-label": "Uppdatera automatiskt när blocket ändras",
"ep_adminpads2_autoupdate.title": "Aktivera eller inaktivera automatiska uppdatering för nuvarande förfrågan.",
"ep_adminpads2_confirm": "Vill du verkligen radera blocket {{padID}}?",
"ep_adminpads2_delete.value": "Radera",
"ep_adminpads2_last-edited": "Senast redigerad",
"ep_adminpads2_loading": "Läser in …",
"ep_adminpads2_manage-pads": "Hantera block",
"ep_adminpads2_no-results": "Inga resultat",
"ep_adminpads2_pad-user-count": "Antal blockanvändare",
"ep_adminpads2_padname": "Blocknamn",
"ep_adminpads2_search-box.placeholder": "Sökord",
"ep_adminpads2_search-button.value": "Sök",
"ep_adminpads2_search-done": "Sökning slutförd",
"ep_adminpads2_search-error-explanation": "Servern stötte på ett fel vid sökning efter block:",
"ep_adminpads2_search-error-title": "Misslyckades att hämta blocklista",
"ep_adminpads2_search-heading": "Sök efter block",
"ep_adminpads2_title": "Blockadministration",
"ep_adminpads2_unknown-error": "Okänt fel",
"ep_adminpads2_unknown-status": "Okänd status"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Andibecker"
]
},
"ep_adminpads2_action": "Hatua",
"ep_adminpads2_autoupdate-label": "Sasisha kiotomatiki kwenye mabadiliko ya pedi",
"ep_adminpads2_autoupdate.title": "Huwasha au kulemaza sasisho otomatiki kwa hoja ya sasa.",
"ep_adminpads2_confirm": "Je! Kweli unataka kufuta pedi {{padID}}?",
"ep_adminpads2_delete.value": "Futa",
"ep_adminpads2_last-edited": "Ilihaririwa mwisho",
"ep_adminpads2_loading": "Inapakia...",
"ep_adminpads2_manage-pads": "Dhibiti pedi",
"ep_adminpads2_no-results": "Hakuna matokeo",
"ep_adminpads2_pad-user-count": "Hesabu ya mtumiaji wa pedi",
"ep_adminpads2_padname": "Jina la utani",
"ep_adminpads2_search-box.placeholder": "Neno la utaftaji",
"ep_adminpads2_search-button.value": "Tafuta",
"ep_adminpads2_search-done": "Utafutaji umekamilika",
"ep_adminpads2_search-error-explanation": "Seva ilipata hitilafu wakati wa kutafuta pedi:",
"ep_adminpads2_search-error-title": "Imeshindwa kupata orodha ya pedi",
"ep_adminpads2_search-heading": "Tafuta pedi",
"ep_adminpads2_title": "Usimamizi wa pedi",
"ep_adminpads2_unknown-error": "Hitilafu isiyojulikana",
"ep_adminpads2_unknown-status": "Hali isiyojulikana"
}

View file

@ -0,0 +1,27 @@
{
"@metadata": {
"authors": [
"Andibecker"
]
},
"ep_adminpads2_action": "การกระทำ",
"ep_adminpads2_autoupdate-label": "อัปเดตอัตโนมัติเมื่อเปลี่ยนแผ่น",
"ep_adminpads2_autoupdate.title": "เปิดหรือปิดการอัปเดตอัตโนมัติสำหรับคิวรีปัจจุบัน",
"ep_adminpads2_confirm": "คุณต้องการลบแพด {{padID}} จริงหรือไม่",
"ep_adminpads2_delete.value": "ลบ",
"ep_adminpads2_last-edited": "แก้ไขล่าสุด",
"ep_adminpads2_loading": "กำลังโหลด…",
"ep_adminpads2_manage-pads": "จัดการแผ่นรอง",
"ep_adminpads2_no-results": "ไม่มีผลลัพธ์",
"ep_adminpads2_pad-user-count": "จำนวนผู้ใช้แพด",
"ep_adminpads2_padname": "นามแฝง",
"ep_adminpads2_search-box.placeholder": "คำที่ต้องการค้นหา",
"ep_adminpads2_search-button.value": "ค้นหา",
"ep_adminpads2_search-done": "ค้นหาเสร็จสมบูรณ์",
"ep_adminpads2_search-error-explanation": "เซิร์ฟเวอร์พบข้อผิดพลาดขณะค้นหาแผ่นอิเล็กโทรด:",
"ep_adminpads2_search-error-title": "ไม่สามารถรับรายการแผ่นรอง",
"ep_adminpads2_search-heading": "ค้นหาแผ่นรอง",
"ep_adminpads2_title": "การบริหารแผ่น",
"ep_adminpads2_unknown-error": "ข้อผิดพลาดที่ไม่รู้จัก",
"ep_adminpads2_unknown-status": "ไม่ทราบสถานะ"
}

View file

@ -0,0 +1,17 @@
{
"@metadata": {
"authors": [
"Mrkczr"
]
},
"ep_adminpads2_action": "Kilos",
"ep_adminpads2_delete.value": "Burahin",
"ep_adminpads2_last-edited": "Huling binago",
"ep_adminpads2_loading": "Naglo-load...",
"ep_adminpads2_no-results": "Walang mga resulta",
"ep_adminpads2_search-box.placeholder": "Mga katagang hahanapin:",
"ep_adminpads2_search-button.value": "Hanapin",
"ep_adminpads2_search-done": "Natapos na ang paghahanap",
"ep_adminpads2_unknown-error": "Hindi nalalamang kamalian",
"ep_adminpads2_unknown-status": "Hindi alam na katayuan"
}

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"Hedda",
"MuratTheTurkish"
]
},
"ep_adminpads2_action": "Eylem",
"ep_adminpads2_autoupdate-label": "Bloknot değişikliklerinde otomatik güncelleme",
"ep_adminpads2_autoupdate.title": "Mevcut sorgu için otomatik güncellemeleri etkinleştirir veya devre dışı bırakır.",
"ep_adminpads2_confirm": "{{padID}} bloknotunu gerçekten silmek istiyor musunuz?",
"ep_adminpads2_delete.value": "Sil",
"ep_adminpads2_last-edited": "Son düzenleme",
"ep_adminpads2_loading": "Yükleniyor...",
"ep_adminpads2_manage-pads": "Bloknotları yönet",
"ep_adminpads2_no-results": "Sonuç yok",
"ep_adminpads2_pad-user-count": "Bloknot kullanıcı sayısı",
"ep_adminpads2_padname": "Bloknot adı",
"ep_adminpads2_search-box.placeholder": "Terimi ara",
"ep_adminpads2_search-button.value": "Ara",
"ep_adminpads2_search-done": "Arama tamamlandı",
"ep_adminpads2_search-error-explanation": "Sunucu, bloknotları ararken bir hatayla karşılaştı:",
"ep_adminpads2_search-error-title": "Bloknot listesi alınamadı",
"ep_adminpads2_search-heading": "Bloknotları ara",
"ep_adminpads2_title": "Bloknot yönetimi",
"ep_adminpads2_unknown-error": "Bilinmeyen hata",
"ep_adminpads2_unknown-status": "Bilinmeyen durum"
}

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"DDPAT",
"Ice bulldog"
]
},
"ep_adminpads2_action": "Дія",
"ep_adminpads2_autoupdate-label": "Автоматичне оновлення при зміні майданчика",
"ep_adminpads2_autoupdate.title": "Вмикає або вимикає автоматичне оновлення поточного запиту.",
"ep_adminpads2_confirm": "Ви дійсно хочете видалити панель {{padID}}?",
"ep_adminpads2_delete.value": "Видалити",
"ep_adminpads2_last-edited": "Останнє редагування",
"ep_adminpads2_loading": "Завантаження…",
"ep_adminpads2_manage-pads": "Управління майданчиками",
"ep_adminpads2_no-results": "Немає результатів",
"ep_adminpads2_pad-user-count": "Кількість майданчиків користувача",
"ep_adminpads2_padname": "Назва майданчика",
"ep_adminpads2_search-box.placeholder": "Пошуковий термін",
"ep_adminpads2_search-button.value": "Пошук",
"ep_adminpads2_search-done": "Пошук завершено",
"ep_adminpads2_search-error-explanation": "Під час пошуку педів сервер виявив помилку:",
"ep_adminpads2_search-error-title": "Не вдалося отримати список панелей",
"ep_adminpads2_search-heading": "Пошук майданчиків",
"ep_adminpads2_title": "Введення майданчиків",
"ep_adminpads2_unknown-error": "Невідома помилка",
"ep_adminpads2_unknown-status": "Невідомий статус"
}

View file

@ -0,0 +1,29 @@
{
"@metadata": {
"authors": [
"GuoPC",
"Lakejason0",
"沈澄心"
]
},
"ep_adminpads2_action": "操作",
"ep_adminpads2_autoupdate-label": "在记事本更改时自动更新",
"ep_adminpads2_autoupdate.title": "启用或禁用目前查询的自动更新",
"ep_adminpads2_confirm": "您确定要删除记事本 {{padID}}",
"ep_adminpads2_delete.value": "删除",
"ep_adminpads2_last-edited": "上次编辑于",
"ep_adminpads2_loading": "正在加载…",
"ep_adminpads2_manage-pads": "管理记事本",
"ep_adminpads2_no-results": "没有结果",
"ep_adminpads2_pad-user-count": "记事本用户数",
"ep_adminpads2_padname": "记事本名称",
"ep_adminpads2_search-box.placeholder": "搜索关键词",
"ep_adminpads2_search-button.value": "搜索",
"ep_adminpads2_search-done": "搜索完成",
"ep_adminpads2_search-error-explanation": "搜索记事本时服务器发生错误:",
"ep_adminpads2_search-error-title": "获取记事本列表失败",
"ep_adminpads2_search-heading": "搜索记事本",
"ep_adminpads2_title": "记事本管理",
"ep_adminpads2_unknown-error": "未知错误",
"ep_adminpads2_unknown-status": "未知状态"
}

View file

@ -0,0 +1,28 @@
{
"@metadata": {
"authors": [
"HellojoeAoPS",
"Kly"
]
},
"ep_adminpads2_action": "操作",
"ep_adminpads2_autoupdate-label": "在記事本更改時自動更新",
"ep_adminpads2_autoupdate.title": "啟用或停用目前查詢的自動更新。",
"ep_adminpads2_confirm": "您確定要刪除記事本 {{padID}}",
"ep_adminpads2_delete.value": "刪除",
"ep_adminpads2_last-edited": "上一次編輯",
"ep_adminpads2_loading": "載入中…",
"ep_adminpads2_manage-pads": "管理記事本",
"ep_adminpads2_no-results": "沒有結果",
"ep_adminpads2_pad-user-count": "記事本使用者數",
"ep_adminpads2_padname": "記事本名稱",
"ep_adminpads2_search-box.placeholder": "搜尋關鍵字",
"ep_adminpads2_search-button.value": "搜尋",
"ep_adminpads2_search-done": "搜尋完成",
"ep_adminpads2_search-error-explanation": "當搜尋記事本時伺服器發生錯誤:",
"ep_adminpads2_search-error-title": "取得記事本清單失敗",
"ep_adminpads2_search-heading": "搜尋記事本",
"ep_adminpads2_title": "記事本管理",
"ep_adminpads2_unknown-error": "不明錯誤",
"ep_adminpads2_unknown-status": "不明狀態"
}

BIN
admin/public/fond.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

0
admin/src/App.css Normal file
View file

110
admin/src/App.tsx Normal file
View file

@ -0,0 +1,110 @@
import {useEffect} from 'react'
import './App.css'
import {connect} from 'socket.io-client'
import {isJSONClean} from './utils/utils.ts'
import {NavLink, Outlet, useNavigate} from "react-router-dom";
import {useStore} from "./store/store.ts";
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
import {Trans, useTranslation} from "react-i18next";
import {Cable, Construction, Crown, NotepadText, Wrench} from "lucide-react";
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
export const App = ()=> {
const setSettings = useStore(state => state.setSettings);
const {t} = useTranslation()
const navigate = useNavigate()
useEffect(() => {
fetch('/admin-auth/', {
method: 'POST'
}).then((value)=>{
if(!value.ok){
navigate('/login')
}
}).catch(()=>{
navigate('/login')
})
}, []);
useEffect(() => {
document.title = t('admin.page-title')
useStore.getState().setShowLoading(true);
const settingSocket = connect(`${WS_URL}/settings`, {
transports: ['websocket'],
});
const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
transports: ['websocket'],
})
pluginsSocket.on('connect', () => {
useStore.getState().setPluginsSocket(pluginsSocket);
});
settingSocket.on('connect', () => {
useStore.getState().setSettingsSocket(settingSocket);
useStore.getState().setShowLoading(false)
settingSocket.emit('load');
console.log('connected');
});
settingSocket.on('disconnect', (reason) => {
// The settingSocket.io client will automatically try to reconnect for all reasons other than "io
// server disconnect".
useStore.getState().setShowLoading(true)
if (reason === 'io server disconnect') {
settingSocket.connect();
}
});
settingSocket.on('settings', (settings) => {
/* Check whether the settings.json is authorized to be viewed */
if (settings.results === 'NOT_ALLOWED') {
console.log('Not allowed to view settings.json')
return;
}
/* Check to make sure the JSON is clean before proceeding */
if (isJSONClean(settings.results)) {
setSettings(settings.results);
} else {
alert('Invalid JSON');
}
useStore.getState().setShowLoading(false);
});
settingSocket.on('saveprogress', (status)=>{
console.log(status)
})
return () => {
settingSocket.disconnect();
pluginsSocket.disconnect()
}
}, []);
return <div id="wrapper">
<LoadingScreen/>
<div className="menu">
<div className="inner-menu">
<span>
<Crown width={40} height={40}/>
<h1>Etherpad</h1>
</span>
<ul>
<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={"/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>
</ul>
</div>
</div>
<div className="innerwrapper">
<Outlet/>
</div>
</div>
}
export default App

View file

@ -0,0 +1,16 @@
import {FC, ReactElement} from "react";
export type IconButtonProps = {
icon: JSX.Element,
title: string|ReactElement,
onClick: ()=>void,
className?: string,
disabled?: boolean
}
export const IconButton:FC<IconButtonProps> = ({icon,className,onClick,title, disabled})=>{
return <button onClick={onClick} className={"icon-button "+ className} disabled={disabled}>
{icon}
<span>{title}</span>
</button>
}

View file

@ -0,0 +1,14 @@
import {ChangeEventHandler, FC} from "react";
import {Search} from 'lucide-react'
export type SearchFieldProps = {
value: string,
onChange: ChangeEventHandler<HTMLInputElement>,
placeholder?: string
}
export const SearchField:FC<SearchFieldProps> = ({onChange,value, placeholder})=>{
return <span className="search-field">
<input value={value} onChange={onChange} placeholder={placeholder}/>
<Search/>
</span>
}

727
admin/src/index.css Normal file
View file

@ -0,0 +1,727 @@
:root {
--etherpad-color: #0f775b;
--etherpad-comp: #9C8840;
--etherpad-light: #99FF99;
}
@font-face {
font-family: Karla;
src: url(/Karla-Regular.ttf);
}
html, body, #root {
box-sizing: border-box;
height: 100%;
font-family: "Karla", sans-serif;
}
*, *:before, *:after {
box-sizing: inherit;
font-size: 16px;
}
body {
margin: 0;
color: #333;
font: 14px helvetica, sans-serif;
background: #eee;
}
div.menu {
height: 100vh;
font-size: 16px;
font-weight: bolder;
display: flex;
align-items: center;
justify-content: center;
max-width: 20%;
min-width: 20%;
}
.icon-button{
display: flex;
gap: 10px;
background-color: var(--etherpad-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
.icon-button svg {
align-self: center;
}
.icon-button span {
align-self: center;
}
div.menu span:first-child {
display: flex;
justify-content: center;
}
div.menu span:first-child svg {
margin-right: 10px;
align-self: center;
}
div.menu h1 {
font-size: 50px;
text-align: center;
}
.inner-menu {
border-radius: 0 20px 20px 0;
padding: 10px;
flex-grow: 100;
background-color: var(--etherpad-comp);
color: white;
height: 100vh;
}
div.menu ul {
color: white;
padding: 0;
}
div.menu li a {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
div.menu svg {
align-self: center;
}
div.menu li {
padding: 10px;
color: white;
list-style: none;
margin-left: 3px;
line-height: 3;
}
div.menu li:has(.active) {
background-color: #9C885C ;
}
div.menu li a {
color: lightgray;
}
div.innerwrapper {
background-color: #F0F0F0;
overflow: auto;
height: 100vh;
flex-grow: 100;
padding: 20px;
}
div.innerwrapper-err {
display: none;
}
#wrapper {
display: flex;
background: none repeat scroll 0px 0px #FFFFFF;
box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
min-height: 100%;/*always display a scrollbar*/
}
h1 {
font-size: 29px;
}
h2 {
font-size: 24px;
}
.separator {
margin: 10px 0;
height: 1px;
background: #aaa;
background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
}
form {
margin-bottom: 0;
}
#inner {
width: 300px;
margin: 0 auto;
}
input {
font-weight: bold;
font-size: 15px;
}
.sort {
cursor: pointer;
}
.sort:after {
content: '▲▼'
}
.sort.up:after {
content:'▲'
}
.sort.down:after {
content:'▼'
}
#installed-plugins thead tr th:nth-child(3) {
width: 15%;
}
table {
border: 1px solid #ddd;
border-radius: 3px;
border-spacing: 0;
width: 100%;
margin: 20px 0;
}
#available-plugins th:first-child, #available-plugins th:nth-child(2){
text-align: center;
}
td, th {
padding: 5px;
}
.template {
display: none;
}
#installed-plugins td>div {
position: relative;/* Allows us to position the loading indicator relative to this row */
display: inline-block; /*make this fill the whole cell*/
width:100%;
}
.messages {
height: 5em;
}
.messages * {
display: none;
text-align: center;
}
.messages .fetching {
display: block;
}
.progress {
position: absolute;
top: 0; left: 0; bottom:0; right:0;
padding: auto;
background: rgb(255,255,255);
display: none;
}
#search-progress.progress {
padding-top: 20%;
background: rgba(255,255,255,0.3);
}
.progress * {
display: block;
margin: 0 auto;
text-align: center;
color: #666;
}
.settings {
outline: none;
width: 100%;
min-height: 80vh;
resize: none;
}
#response {
display: inline;
}
a:link, a:visited, a:hover, a:focus {
color: #333333;
text-decoration: none;
}
a:focus, a:hover {
text-decoration: underline;
}
.installed-results a:link,
.search-results a:link,
.installed-results a:visited,
.search-results a:visited,
.installed-results a:hover,
.search-results a:hover,
.installed-results a:focus,
.search-results a:focus {
text-decoration: underline;
}
.installed-results a:focus,
.search-results a:focus,
.installed-results a:hover,
.search-results a:hover {
text-decoration: none;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
@media (max-width: 800px) {
div.innerwrapper {
padding: 0 15px 15px 15px;
}
div.menu {
padding: 1px 15px 0 15px;
position: static;
height: auto;
border-right: none;
width: auto;
float: left;
}
table {
border: none;
}
table, thead, tbody, td, tr {
display: block;
}
thead tr {
display: none;
}
tr {
border: 1px solid #ccc;
margin-bottom: 5px;
border-radius: 3px;
}
td {
border: none;
border-bottom: 1px solid #eee;
position: relative;
padding-left: 50%;
white-space: normal;
text-align: left;
}
td.name {
word-wrap: break-word;
}
td:before {
position: absolute;
top: 6px;
left: 6px;
text-align: left;
padding-right: 10px;
white-space: nowrap;
font-weight: bold;
content: attr(data-label);
}
td:last-child {
border-bottom: none;
}
table input[type="button"] {
float: none;
}
}
.settings-button-bar {
margin-top: 10px;
display: flex;
gap: 10px;
}
.login-background {
background-image: url("/fond.jpg");
background-repeat: no-repeat;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
.login-inner-box div {
margin-top: 1rem;
}
.login-inner-box [type=submit]{
margin-top: 2rem;
}
.login-textinput {
width: 100%;
padding: 10px;
background-color: #fffacc;
border-radius: 5px;
border: 1px solid #ccc;
margin-bottom: 10px;
}
.login-box {
width: 20%;
padding: 20px;
border-radius: 40px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
.login-inner-box{
position: relative;
padding: 20px;
}
.login-title {
padding: 0;
margin: 0;
text-align: center;
color: var(--etherpad-color);
font-size: 4rem;
font-weight: 1000;
}
.login-button {
padding: 10px;
background-color: var(--etherpad-color);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
width: 100%;
height: 40px;
}
.dialog-overlay {
position: fixed;
inset: 0;
background-color: white;
z-index: 100;
}
.dialog-confirm-overlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100;
}
.dialog-confirm-content {
position: fixed;
top: 50%;
left: 50%;
background-color: white;
transform: translate(-50%, -50%);
padding: 20px;
z-index: 101;
}
.dialog-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
z-index: 101;
}
.dialog-title {
color: var(--etherpad-color);
font-size: 2em;
margin-bottom: 20px;
}
.ToastViewport {
position: fixed;
top: 10px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
width: 390px;
max-width: 100vw;
margin: 0;
list-style: none;
z-index: 2147483647;
outline: none;
}
.ToastRootSuccess {
background-color: lawngreen;
}
.ToastRootFailure {
background-color: red;
}
.ToastRootFailure > .ToastTitle {
color: white;
}
.ToastRoot {
border-radius: 20px;
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
padding: 15px;
display: grid;
grid-template-areas: 'title action' 'description action';
grid-template-columns: auto max-content;
column-gap: 15px;
align-items: center;
}
.ToastRoot[data-state='open'] {
animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ToastRoot[data-state='closed'] {
animation: hide 100ms ease-in;
}
.ToastRoot[data-swipe='move'] {
transform: translateX(var(--radix-toast-swipe-move-x));
}
.ToastRoot[data-swipe='cancel'] {
transform: translateX(0);
transition: transform 200ms ease-out;
}
.ToastRoot[data-swipe='end'] {
animation: swipeOut 100ms ease-out;
}
@keyframes hide {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes slideIn {
from {
transform: translateX(calc(100% + var(--viewport-padding)));
}
to {
transform: translateX(0);
}
}
@keyframes swipeOut {
from {
transform: translateX(var(--radix-toast-swipe-end-x));
}
to {
transform: translateX(calc(100% + var(--viewport-padding)));
}
}
.ToastTitle {
grid-area: title;
margin-bottom: 5px;
font-weight: 500;
color: var(--slate-12);
padding: 10px;
font-size: 15px;
}
.ToastDescription {
grid-area: description;
margin: 0;
color: var(--slate-11);
font-size: 13px;
line-height: 1.3;
}
.ToastAction {
grid-area: action;
}
.help-block {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px
}
.search-field {
position: relative;
}
.search-field input {
border-color: transparent;
border-radius: 20px;
height: 2.5rem;
width: 100vh;
padding: 5px 5px 5px 30px;
}
.search-field input:focus {
outline: none;
}
.search-field svg {
position: absolute;
left: 3px;
bottom: -3px;
}
.search-field svg {
color: gray
}
table {
margin: 25px 0;
font-size: 0.9em;
font-family: sans-serif;
min-width: 400px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
th:first-child {
border-top-left-radius: 10px;
}
th:last-child {
border-top-right-radius: 10px;
}
table thead tr {
font-size: 25px;
background-color: var(--etherpad-color);
color: #ffffff;
text-align: left;
}
table tbody tr {
border-bottom: 1px solid #dddddd;
}
table tr:nth-child(even) td {
background-color: lightgray;
}
table tr td {
padding: 12px 15px;
}
table tbody tr:nth-of-type(even) {
background-color: #f3f3f3;
}
table tbody tr:last-of-type {
border-bottom: 2px solid #009879;
}
table tbody tr.active-row {
font-weight: bold;
color: #009879;
}
.pad-pagination{
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.pad-pagination button {
display: flex;
padding: 10px 20px;
border-radius: 5px;
border: none;
color: black;
cursor: pointer;
}
.pad-pagination button:disabled {
background: transparent;
color: lightgrey;
cursor: not-allowed;
}
.pad-pagination span {
align-self: center;
}
.pad-pagination >span {
font-size: 20px;
}
.login-page .login-form .input-control input[type=text], .login-page .login-form .input-control input[type=email], .login-page .login-form .input-control input[type=password], .login-page .signup-form .input-control input[type=text], .login-page .signup-form .input-control input[type=email], .login-page .signup-form .input-control input[type=password], .login-page .forgot-form .input-control input[type=text], .login-page .forgot-form .input-control input[type=email], .login-page .forgot-form .input-control input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border-bottom: 2px solid #ccc;
border-top: 0;
border-left: 0;
border-right: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border-radius: 5px;
font-size: 14px;
color: #666;
background-color: #f8f8f8;
-webkit-transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
}
input, button, select, optgroup, textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.icon-input {
position: relative;
}
.icon-input svg {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 10px;
color: #666;
}

View file

@ -0,0 +1,57 @@
import i18n from 'i18next'
import {initReactI18next} from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector'
import { BackendModule } from 'i18next';
const LazyImportPlugin: BackendModule = {
type: 'backend',
init: function () {
},
read: async function (language, namespace, callback) {
let baseURL = import.meta.env.BASE_URL
if(namespace === "translation") {
// If default we load the translation file
baseURL+=`/locales/${language}.json`
} else {
// Else we load the former plugin translation file
baseURL+=`/${namespace}/${language}.json`
}
const localeJSON = await fetch(baseURL, {
cache: "force-cache"
})
let json;
try {
json = JSON.parse(await localeJSON.text())
} catch(e) {
callback(new Error("Error loading"), null);
}
callback(null, json);
},
save: function () {
},
create: function () {
/* save the missing translation */
},
};
i18n
.use(LanguageDetector)
.use(LazyImportPlugin)
.use(initReactI18next)
.init(
{
ns: ['translation','ep_admin_pads'],
fallbackLng: 'en'
}
)
export default i18n

40
admin/src/main.tsx Normal file
View file

@ -0,0 +1,40 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import {createBrowserRouter, createRoutesFromElements, Route, RouterProvider} from "react-router-dom";
import {HomePage} from "./pages/HomePage.tsx";
import {SettingsPage} from "./pages/SettingsPage.tsx";
import {LoginScreen} from "./pages/LoginScreen.tsx";
import {HelpPage} from "./pages/HelpPage.tsx";
import * as Toast from '@radix-ui/react-toast'
import {I18nextProvider} from "react-i18next";
import i18n from "./localization/i18n.ts";
import {PadPage} from "./pages/PadPage.tsx";
import {ToastDialog} from "./utils/Toast.tsx";
const router = createBrowserRouter(createRoutesFromElements(
<><Route element={<App/>}>
<Route index element={<HomePage/>}/>
<Route path="/plugins" element={<HomePage/>}/>
<Route path="/settings" element={<SettingsPage/>}/>
<Route path="/help" element={<HelpPage/>}/>
<Route path="/pads" element={<PadPage/>}/>
</Route><Route path="/login">
<Route index element={<LoginScreen/>}/>
</Route></>
), {
basename: import.meta.env.BASE_URL
})
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<I18nextProvider i18n={i18n}>
<Toast.Provider>
<ToastDialog/>
<RouterProvider router={router}/>
</Toast.Provider>
</I18nextProvider>
</React.StrictMode>,
)

View file

@ -0,0 +1,70 @@
import {Trans} from "react-i18next";
import {useStore} from "../store/store.ts";
import {useEffect, useState} from "react";
import {HelpObj} from "./Plugin.ts";
export const HelpPage = () => {
const settingsSocket = useStore(state=>state.settingsSocket)
const [helpData, setHelpData] = useState<HelpObj>();
useEffect(() => {
if(!settingsSocket) return;
settingsSocket?.on('reply:help', (data) => {
setHelpData(data)
});
settingsSocket?.emit('help');
}, [settingsSocket]);
const renderHooks = (hooks:Record<string, Record<string, string>>) => {
return Object.keys(hooks).map((hookName, i) => {
return <div key={hookName+i}>
<h3>{hookName}</h3>
<ul>
{Object.keys(hooks[hookName]).map((hook, i) => <li>{hook}
<ul key={hookName+hook+i}>
{Object.keys(hooks[hookName][hook]).map((subHook, i) => <li key={i}>{subHook}</li>)}
</ul>
</li>)}
</ul>
</div>
})
}
if (!helpData) return <div></div>
return <div>
<h1><Trans i18nKey="admin_plugins_info.version"/></h1>
<div className="help-block">
<div><Trans i18nKey="admin_plugins_info.version_number"/></div>
<div>{helpData?.epVersion}</div>
<div><Trans i18nKey="admin_plugins_info.version_latest"/></div>
<div>{helpData.latestVersion}</div>
<div>Git sha</div>
<div>{helpData.gitCommit}</div>
</div>
<h2><Trans i18nKey="admin_plugins.installed"/></h2>
<ul>
{helpData.installedPlugins.map((plugin, i) => <li key={i}>{plugin}</li>)}
</ul>
<h2><Trans i18nKey="admin_plugins_info.parts"/></h2>
<ul>
{helpData.installedParts.map((part, i) => <li key={i}>{part}</li>)}
</ul>
<h2><Trans i18nKey="admin_plugins_info.hooks"/></h2>
{
renderHooks(helpData.installedServerHooks)
}
<h2>
<Trans i18nKey="admin_plugins_info.hooks_client"/>
{
renderHooks(helpData.installedClientHooks)
}
</h2>
</div>
}

View file

@ -0,0 +1,188 @@
import {useStore} from "../store/store.ts";
import {useEffect, useMemo, useState} from "react";
import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts";
import {useDebounce} from "../utils/useDebounce.ts";
import {Trans, useTranslation} from "react-i18next";
import {SearchField} from "../components/SearchField.tsx";
import {Download, Trash} from "lucide-react";
import {IconButton} from "../components/IconButton.tsx";
export const HomePage = () => {
const pluginsSocket = useStore(state=>state.pluginsSocket)
const [plugins,setPlugins] = useState<PluginDef[]>([])
const [installedPlugins, setInstalledPlugins] = useState<InstalledPlugin[]>([])
const sortedInstalledPlugins = useMemo(()=>{
return installedPlugins.sort((a, b)=>{
if(a.name < b.name){
return -1
}
if(a.name > b.name){
return 1
}
return 0
})
} ,[installedPlugins])
const [searchParams, setSearchParams] = useState<SearchParams>({
offset: 0,
limit: 99999,
sortBy: 'name',
sortDir: 'asc',
searchTerm: ''
})
const [searchTerm, setSearchTerm] = useState<string>('')
const {t} = useTranslation()
useEffect(() => {
if(!pluginsSocket){
return
}
pluginsSocket.on('results:installed', (data:{
installed: InstalledPlugin[]
})=>{
setInstalledPlugins(data.installed)
})
pluginsSocket.on('results:updatable', (data) => {
data.updatable.forEach((pluginName: string) => {
setInstalledPlugins(installedPlugins.map(plugin => {
if (plugin.name === pluginName) {
return {
...plugin,
updatable: true
}
}
return plugin
}))
})
})
pluginsSocket.on('finished:install', () => {
pluginsSocket!.emit('getInstalled');
})
pluginsSocket.on('finished:uninstall', () => {
console.log("Finished uninstall")
})
// Reload on reconnect
pluginsSocket.on('connect', ()=>{
// Initial retrieval of installed plugins
pluginsSocket.emit('getInstalled');
pluginsSocket.emit('search', searchParams)
})
pluginsSocket.emit('getInstalled');
// check for updates every 5mins
const interval = setInterval(() => {
pluginsSocket.emit('checkUpdates');
}, 1000 * 60 * 5);
return ()=>{
clearInterval(interval)
}
}, [pluginsSocket]);
useEffect(() => {
if (!pluginsSocket) {
return
}
pluginsSocket?.emit('search', searchParams)
pluginsSocket!.on('results:search', (data: {
results: PluginDef[]
}) => {
setPlugins(data.results)
})
}, [searchParams, pluginsSocket]);
const uninstallPlugin = (pluginName: string)=>{
pluginsSocket!.emit('uninstall', pluginName);
// Remove plugin
setInstalledPlugins(installedPlugins.filter(i=>i.name !== pluginName))
}
const installPlugin = (pluginName: string)=>{
pluginsSocket!.emit('install', pluginName);
setPlugins(plugins.filter(plugin=>plugin.name !== pluginName))
}
useDebounce(()=>{
setSearchParams({
...searchParams,
offset: 0,
searchTerm: searchTerm
})
}, 500, [searchTerm])
return <div>
<h1><Trans i18nKey="admin_plugins"/></h1>
<h2><Trans i18nKey="admin_plugins.installed"/></h2>
<table id="installed-plugins">
<thead>
<tr>
<th><Trans i18nKey="admin_plugins.name"/></th>
<th><Trans i18nKey="admin_plugins.version"/></th>
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
</tr>
</thead>
<tbody style={{overflow: 'auto'}}>
{sortedInstalledPlugins.map((plugin, index) => {
return <tr key={index}>
<td>{plugin.name}</td>
<td>{plugin.version}</td>
<td>
{
plugin.updatable ?
<button onClick={() => installPlugin(plugin.name)}>Update</button>
: <IconButton disabled={plugin.name == "ep_etherpad-lite"} icon={<Trash/>} title={<Trans i18nKey="admin_plugins.installed_uninstall.value"/>} onClick={() => uninstallPlugin(plugin.name)}/>
}
</td>
</tr>
})}
</tbody>
</table>
<h2><Trans i18nKey="admin_plugins.available"/></h2>
<SearchField onChange={v=>{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
<table id="available-plugins">
<thead>
<tr>
<th><Trans i18nKey="admin_plugins.name"/></th>
<th style={{width: '30%'}}><Trans i18nKey="admin_plugins.description"/></th>
<th><Trans i18nKey="admin_plugins.version"/></th>
<th><Trans i18nKey="admin_plugins.last-update"/></th>
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
</tr>
</thead>
<tbody style={{overflow: 'auto'}}>
{plugins.map((plugin) => {
return <tr key={plugin.name}>
<td><a rel="noopener noreferrer" href={`https://npmjs.com/${plugin.name}`} target="_blank">{plugin.name}</a></td>
<td>{plugin.description}</td>
<td>{plugin.version}</td>
<td>{plugin.time}</td>
<td>
<IconButton icon={<Download/>} onClick={() => installPlugin(plugin.name)} title={<Trans i18nKey="admin_plugins.available_install.value"/>}/>
</td>
</tr>
})}
</tbody>
</table>
</div>
}

Some files were not shown because too many files have changed in this diff Show more