diff --git a/.dockerignore b/.dockerignore
index d8d3a3ebe..f7accabfd 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -24,3 +24,4 @@ Dockerfile
settings.json
src/node_modules
+admin/node_modules
diff --git a/.env.default b/.env.default
new file mode 100644
index 000000000..b78b5599a
--- /dev/null
+++ b/.env.default
@@ -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
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 58200fd3c..263f5a783 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -14,8 +14,3 @@ updates:
schedule:
interval: "daily"
versioning-strategy: "increase"
- - package-ecosystem: "npm"
- directory: "/src/bin/doc"
- schedule:
- interval: "daily"
- versioning-strategy: "increase"
diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml
index 000d98ef4..0dd1000d8 100644
--- a/.github/workflows/backend-tests.yml
+++ b/.github/workflows/backend-tests.yml
@@ -27,22 +27,42 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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 libreoffice
- run: |
- sudo add-apt-repository -y ppa:libreoffice/ppa
- sudo apt update
- sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport
+ uses: awalsh128/cache-apt-pkgs-action@v1.4.2
+ with:
+ packages: libreoffice libreoffice-pdfimport
+ version: 1.0
-
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
- run: cd src && npm test
+ run: pnpm test
withpluginsLinux:
# run on pushes to any branch
@@ -64,22 +84,43 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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 libreoffice
- run: |
- sudo add-apt-repository -y ppa:libreoffice/ppa
- sudo apt update
- sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport
+ uses: awalsh128/cache-apt-pkgs-action@v1.4.2
+ with:
+ packages: 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
- # 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
+ pnpm install --workspace-root
ep_align
ep_author_hover
ep_cursortrace
@@ -93,21 +134,9 @@ jobs:
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.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
- run: cd src && npm test
+ run: pnpm test
withoutpluginsWindows:
# run on pushes to any branch
@@ -125,13 +154,33 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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: 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
run: |
@@ -139,7 +188,7 @@ jobs:
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
-
name: Run the backend tests
- run: cd src && npm test
+ run: cd src && pnpm test
withpluginsWindows:
# run on pushes to any branch
@@ -157,17 +206,37 @@ jobs:
-
uses: actions/setup-node@v4
with:
- node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ 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
+ 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
# 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
+ pnpm install --workspace-root
ep_align
ep_author_hover
ep_cursortrace
@@ -192,7 +261,7 @@ jobs:
# rules.
-
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
run: |
@@ -200,4 +269,4 @@ jobs:
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
-
name: Run the backend tests
- run: cd src && npm test
+ run: cd src && pnpm test
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index dde0d68d5..7e728ec64 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -30,6 +30,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
+ target: production
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
@@ -39,16 +40,28 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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: Test
run: |
docker run --rm -d -p 9001:9001 --name test ${{ env.TEST_TAG }}
+ ./bin/installDeps.sh
docker logs -f test &
- ./src/bin/installDeps.sh
while true; do
echo "Waiting for Docker container to start..."
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;;
esac
done
- (cd src && npm run test-container)
+ (cd src && pnpm run test-container)
git clean -dxf .
-
name: Docker meta
@@ -85,6 +98,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
+ target: production
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
@@ -96,4 +110,4 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: etherpad/etherpad
- enable-url-completion: true
\ No newline at end of file
+ enable-url-completion: true
diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml
index f3876816b..cb89d2b91 100644
--- a/.github/workflows/frontend-admin-tests.yml
+++ b/.github/workflows/frontend-admin-tests.yml
@@ -12,11 +12,10 @@ jobs:
name: with plugins
runs-on: ubuntu-latest
-# node: [16, 19, 20] >> Disabled node 16 and 18 because they do not work
strategy:
fail-fast: false
matrix:
- node: [19, 20, 21]
+ node: [20, 21]
steps:
-
@@ -32,16 +31,30 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
- -
- 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: npm install --no-save --legacy-peer-deps ep_align@0.2.27
+ - 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 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
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
@@ -53,10 +66,10 @@ jobs:
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
- run: src/bin/installDeps.sh
- -
- name: Install etherpad plugins
- run: rm -Rf node_modules/ep_align/static/tests/*
+ run: bin/installDeps.sh
+ #-
+ # name: Install etherpad plugins
+ # run: rm -Rf node_modules/ep_align/static/tests/*
-
name: export GIT_HASH to env
id: environment
@@ -66,31 +79,67 @@ jobs:
run: cp settings.json.template settings.json
-
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
- 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
run: |
- sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 1000000/' -i settings.json
- -
- name: Remove standard frontend test files, so only admin tests are run
- 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 }}
+ sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 100000000/' -i settings.json
+ - name: Build admin frontend
+ working-directory: admin
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
diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml
index e1524d732..93018ea0a 100644
--- a/.github/workflows/frontend-tests.yml
+++ b/.github/workflows/frontend-tests.yml
@@ -7,10 +7,141 @@ permissions:
contents: read # to fetch code (actions/checkout)
jobs:
- withoutplugins:
- name: without plugins
+ playwright-chrome:
+ 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
- if: ${{ github.actor != 'dependabot[bot]' }}
steps:
-
@@ -25,127 +156,59 @@ jobs:
-
uses: actions/setup-node@v4
with:
- node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ 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: src/bin/installDeps.sh
+ 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 settings.json.template settings.json
- -
- 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
+ run: cp ./src/tests/settings.json settings.json
+ - 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
-
- withplugins:
- name: with plugins
- runs-on: ubuntu-latest
- if: ${{ github.actor != 'dependabot[bot]' }}
-
- 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
+ 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 webkit --with-deps
+ pnpm run test-ui --project=webkit || true
+ - uses: actions/upload-artifact@v4
+ if: always()
with:
- node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- 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
+ name: playwright-report-${{ matrix.node }}-webkit
+ path: src/playwright-report/
+ retention-days: 30
+
+
+
diff --git a/.github/workflows/lint-package-lock.yml b/.github/workflows/lint-package-lock.yml
index eae4221a9..f1073172a 100644
--- a/.github/workflows/lint-package-lock.yml
+++ b/.github/workflows/lint-package-lock.yml
@@ -23,13 +23,6 @@ jobs:
uses: actions/setup-node@v4
with:
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
run: >
diff --git a/.github/workflows/load-test.yml b/.github/workflows/load-test.yml
index 9aa87eec4..d4f1a9b44 100644
--- a/.github/workflows/load-test.yml
+++ b/.github/workflows/load-test.yml
@@ -23,16 +23,30 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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: src/bin/installDeps.sh
+ run: bin/installDeps.sh
-
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
run: src/tests/frontend/travis/runnerLoadTest.sh 25 50
@@ -53,19 +67,33 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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 etherpad-load-test
- run: sudo npm install -g etherpad-load-test
+ run: pnpm install -g etherpad-load-test-socket-io
-
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
+ pnpm install --workspace-root
ep_align
ep_author_hover
ep_cursortrace
@@ -89,7 +117,7 @@ jobs:
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
- run: src/bin/installDeps.sh
+ run: bin/installDeps.sh
-
name: Run load test
run: src/tests/frontend/travis/runnerLoadTest.sh 25 50
@@ -110,16 +138,30 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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: src/bin/installDeps.sh
+ run: bin/installDeps.sh
-
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
run: src/tests/frontend/travis/runnerLoadTest.sh 5000 5
diff --git a/.github/workflows/perform-type-check.yml b/.github/workflows/perform-type-check.yml
new file mode 100644
index 000000000..81be2567d
--- /dev/null
+++ b/.github/workflows/perform-type-check.yml
@@ -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
diff --git a/.github/workflows/rate-limit.yml b/.github/workflows/rate-limit.yml
index 1e878dc44..dab99fff3 100644
--- a/.github/workflows/rate-limit.yml
+++ b/.github/workflows/rate-limit.yml
@@ -23,17 +23,30 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ - 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: docker network
run: docker network create --subnet=172.23.42.0/16 ep_net
-
name: build docker image
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.anotherip -t anotherip .
-
@@ -44,7 +57,7 @@ jobs:
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
- run: src/bin/installDeps.sh
+ run: bin/installDeps.sh
-
name: run rate limit test
run: |
diff --git a/.github/workflows/upgrade-from-latest-release.yml b/.github/workflows/upgrade-from-latest-release.yml
index cdd6d20b3..f2c713c47 100644
--- a/.github/workflows/upgrade-from-latest-release.yml
+++ b/.github/workflows/upgrade-from-latest-release.yml
@@ -29,14 +29,11 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- cache: 'npm'
- cache-dependency-path: |
- 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
+ # Important: Installer for old master which does not have pnpm right now
+ # 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
@@ -52,15 +49,6 @@ jobs:
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.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
@@ -82,18 +70,44 @@ jobs:
# For pull requests, ${GITHUB_SHA} is the automatically generated merge
# commit that merges the PR's source branch to its destination branch.
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
- run: src/bin/installDeps.sh
+ run: bin/installDeps.sh
-
name: Run the backend tests
- run: cd src && npm test
+ run: pnpm test
-
name: Install Cypress
- run: cd src && npm install cypress --legacy-peer-deps
+ working-directory: ./src
+ run: pnpm install cypress
-
name: Run Etherpad & Test Frontend
+ working-directory: ./src
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
- ./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
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 8dbd03339..99a779234 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -28,23 +28,37 @@ jobs:
-
uses: actions/setup-node@v4
with:
- node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
+ 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
+ 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
shell: msys2 {0}
- run: src/bin/installDeps.sh
+ run: bin/installDeps.sh
-
name: Run the backend tests
shell: msys2 {0}
- run: cd src && npm test
+ run: cd src && pnpm test
-
name: Build the .zip
shell: msys2 {0}
- run: src/bin/buildForWindows.sh
+ run: bin/buildForWindows.sh
-
name: Archive production artifacts
uses: actions/upload-artifact@v4
@@ -75,9 +89,9 @@ jobs:
run: 7z x etherpad-win.zip -oetherpad-zip
-
name: Create installer
- uses: joncloud/makensis-action@v3.7
+ uses: joncloud/makensis-action@v4.1
with:
- script-file: 'src/bin/nsis/etherpad.nsi'
+ script-file: 'bin/nsis/etherpad.nsi'
-
name: Archive production artifacts
uses: actions/upload-artifact@v4
@@ -109,27 +123,43 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- cache: 'npm'
- cache-dependency-path: |
- etherpad/src/package-lock.json
- etherpad/src/bin/doc/package-lock.json
- -
- name: Install Cypress
- run: cd etherpad && cd src && npm install cypress --legacy-peer-deps
+ - 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\installOnWindows.bat
+ working-directory: etherpad
-
name: Run Etherpad
+ working-directory: etherpad/src
run: |
- cd etherpad
- node node_modules\ep_etherpad-lite\node\server.js &
+ pnpm install cypress
+ .\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
- 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
-
name: Rename to etherpad-lite-win.zip
shell: powershell
run: mv etherpad-win.zip etherpad-lite-win.zip
- name: upload binaries to release
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v2
if: ${{startsWith(github.ref, 'refs/tags/v') }}
with:
files: etherpad-lite-win.zip
diff --git a/.gitignore b/.gitignore
index 6c94cbad2..f577330c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ node_modules
APIKEY.txt
SESSIONKEY.txt
var/dirty.db
+.env
*~
*.patch
npm-debug.log
@@ -21,4 +22,8 @@ out/
/src/bin/convertSettings.json
/src/bin/etherpad-1.deb
/src/bin/node.exe
-plugin_packages
\ No newline at end of file
+plugin_packages
+/src/templates/admin
+/src/test-results
+playwright-report
+state.json
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 000000000..f301fedf9
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+auto-install-peers=false
diff --git a/.travis.yml b/.travis.yml
index 44a8693bb..ca8c5380f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -55,7 +55,7 @@ jobs:
- *set_loglevel_warn
- *enable_admin_tests
- "src/tests/frontend/travis/sauce_tunnel.sh"
- - "src/bin/installDeps.sh"
+ - "bin/installDeps.sh"
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
script:
- "./src/tests/frontend/travis/runner.sh"
@@ -63,22 +63,22 @@ jobs:
install:
- *install_libreoffice
- *set_loglevel_warn
- - "src/bin/installDeps.sh"
- - "cd src && npm install && cd -"
+ - "bin/installDeps.sh"
+ - "cd src && pnpm install && cd -"
script:
- - "cd src && npm test"
+ - "cd src && pnpm test"
- name: "Test the Dockerfile"
install:
- - "cd src && npm install && cd -"
+ - "cd src && pnpm install && cd -"
script:
- "docker build -t etherpad:test ."
- "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"
install:
- *set_loglevel_warn
- - "src/bin/installDeps.sh"
- - "cd src && npm install && cd -"
+ - "bin/installDeps.sh"
+ - "cd src && pnpm install && cd -"
- "npm install -g etherpad-load-test"
script:
- "src/tests/frontend/travis/runnerLoadTest.sh"
@@ -90,7 +90,7 @@ jobs:
- *set_loglevel_warn
- *enable_admin_tests
- "src/tests/frontend/travis/sauce_tunnel.sh"
- - "src/bin/installDeps.sh"
+ - "bin/installDeps.sh"
- "rm src/tests/frontend/specs/*"
- *install_plugins
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
@@ -105,22 +105,22 @@ jobs:
install:
- *install_libreoffice
- *set_loglevel_warn
- - "src/bin/installDeps.sh"
+ - "bin/installDeps.sh"
- *install_plugins
- - "cd src && npm install && cd -"
+ - "cd src && pnpm install && cd -"
script:
- - "cd src && npm test"
+ - "cd src && pnpm test"
- name: "Test the Dockerfile"
install:
- - "cd src && npm install && cd -"
+ - "cd src && pnpm install && cd -"
script:
- "docker build -t etherpad:test ."
- "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"
install:
- *set_loglevel_warn
- - "src/bin/installDeps.sh"
+ - "bin/installDeps.sh"
- *install_plugins
- "cd src && npm install && cd -"
- "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 --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"
- - "./src/bin/installDeps.sh"
+ - "./bin/installDeps.sh"
script:
- "cd src/tests/ratelimit && bash testlimits.sh"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a8377712..1b5c29985 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
### Notable enhancements and fixes
diff --git a/Dockerfile b/Dockerfile
index 0f42ab832..5bfeca4f7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,9 +4,21 @@
#
# 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"
+# Set these arguments when building the image from behind a proxy
+ARG http_proxy=
+ARG https_proxy=
+ARG no_proxy=
+
ARG TIMEZONE=
RUN \
@@ -44,11 +56,6 @@ ARG INSTALL_ABIWORD=
# INSTALL_LIBREOFFICE=true
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.
RUN apk add shadow bash
# Follow the principle of least privilege: run as unprivileged user.
@@ -63,8 +70,6 @@ ARG EP_UID=5001
ARG EP_GID=0
ARG EP_SHELL=
-ENV NODE_ENV=production
-
RUN groupadd --system ${EP_GID:+--gid "${EP_GID}" --non-unique} etherpad && \
useradd --system ${EP_UID:+--uid "${EP_UID}" --non-unique} --gid etherpad \
${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
RUN \
mkdir -p /usr/share/man/man1 && \
- npm install npm@6 -g && \
+ npm install pnpm -g && \
apk update && apk upgrade && \
apk add \
ca-certificates \
+ curl \
git \
${INSTALL_ABIWORD:+abiword abiword-plugin-command} \
${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common}
@@ -89,32 +95,45 @@ USER etherpad
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 --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json
# 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
-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
-CMD ["etherpad"]
+CMD ["pnpm", "run", "prod"]
diff --git a/README.md b/README.md
index 41da517af..b31d1f1a2 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ Install the latest Node.js LTS per [official install instructions](https://githu
```sh
git clone --branch master https://github.com/ether/etherpad-lite.git &&
cd etherpad-lite &&
-src/bin/run.sh
+bin/run.sh
```
#### Manual install
@@ -70,10 +70,10 @@ You'll need Git and [Node.js](https://nodejs.org/) installed.
https://github.com/ether/etherpad-lite.git`
3. Change into the new directory containing the cloned source code: `cd
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
-start with `src/bin/run.sh` will update the dependencies.
+start with `bin/run.sh` will update the dependencies.
### Windows
@@ -98,17 +98,17 @@ git.
* or `git clone --branch master
https://github.com/ether/etherpad-lite.git`
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.
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
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
the file (e.g. `application/lib/etherpad-lite/var/dirty.db`)
3. Add auto-generated files to the main project `.gitignore`
@@ -139,9 +139,7 @@ Alternatively, you can install plugins from the command line:
```sh
cd /path/to/etherpad-lite
-# The `--no-save` and `--legacy-peer-deps` arguments are necessary to work
-# around npm quirks.
-npm install --no-save --legacy-peer-deps ep_${plugin_name}
+pnpm run install-plugins ep_${plugin_name}
```
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:
```sh
-npm install --no-save --legacy-peer-deps \
+pnpm run install-plugins \
ep_align \
ep_comments_page \
ep_embedded_hyperlinks2 \
@@ -182,7 +180,7 @@ following plugins:
### Tweak the settings
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
instances from the same installation. Similarly, `--credentials` can be used to
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
-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
-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.
If you want to find out how Etherpad's `Easysync` works (the library that makes
diff --git a/admin/.eslintrc.cjs b/admin/.eslintrc.cjs
new file mode 100644
index 000000000..d6c953795
--- /dev/null
+++ b/admin/.eslintrc.cjs
@@ -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 },
+ ],
+ },
+}
diff --git a/admin/.gitignore b/admin/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/admin/.gitignore
@@ -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?
diff --git a/admin/README.md b/admin/README.md
new file mode 100644
index 000000000..0d6babedd
--- /dev/null
+++ b/admin/README.md
@@ -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
diff --git a/admin/index.html b/admin/index.html
new file mode 100644
index 000000000..8863894ed
--- /dev/null
+++ b/admin/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Etherpad Admin Dashboard
+
+
+
+
+
+
+
+
diff --git a/admin/package.json b/admin/package.json
new file mode 100644
index 000000000..9a92b4e26
--- /dev/null
+++ b/admin/package.json
@@ -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"
+ }
+}
diff --git a/admin/public/Karla-Bold.ttf b/admin/public/Karla-Bold.ttf
new file mode 100644
index 000000000..2348e0072
Binary files /dev/null and b/admin/public/Karla-Bold.ttf differ
diff --git a/admin/public/Karla-BoldItalic.ttf b/admin/public/Karla-BoldItalic.ttf
new file mode 100644
index 000000000..3c0e045ec
Binary files /dev/null and b/admin/public/Karla-BoldItalic.ttf differ
diff --git a/admin/public/Karla-ExtraBold.ttf b/admin/public/Karla-ExtraBold.ttf
new file mode 100644
index 000000000..f18471195
Binary files /dev/null and b/admin/public/Karla-ExtraBold.ttf differ
diff --git a/admin/public/Karla-ExtraBoldItalic.ttf b/admin/public/Karla-ExtraBoldItalic.ttf
new file mode 100644
index 000000000..3799659c0
Binary files /dev/null and b/admin/public/Karla-ExtraBoldItalic.ttf differ
diff --git a/admin/public/Karla-ExtraLight.ttf b/admin/public/Karla-ExtraLight.ttf
new file mode 100644
index 000000000..0f8642c02
Binary files /dev/null and b/admin/public/Karla-ExtraLight.ttf differ
diff --git a/admin/public/Karla-ExtraLightItalic.ttf b/admin/public/Karla-ExtraLightItalic.ttf
new file mode 100644
index 000000000..bb328e175
Binary files /dev/null and b/admin/public/Karla-ExtraLightItalic.ttf differ
diff --git a/admin/public/Karla-Italic.ttf b/admin/public/Karla-Italic.ttf
new file mode 100644
index 000000000..1853cbe4e
Binary files /dev/null and b/admin/public/Karla-Italic.ttf differ
diff --git a/admin/public/Karla-Light.ttf b/admin/public/Karla-Light.ttf
new file mode 100644
index 000000000..46457ece7
Binary files /dev/null and b/admin/public/Karla-Light.ttf differ
diff --git a/admin/public/Karla-LightItalic.ttf b/admin/public/Karla-LightItalic.ttf
new file mode 100644
index 000000000..3b0f01ff1
Binary files /dev/null and b/admin/public/Karla-LightItalic.ttf differ
diff --git a/admin/public/Karla-Medium.ttf b/admin/public/Karla-Medium.ttf
new file mode 100644
index 000000000..9066b49c4
Binary files /dev/null and b/admin/public/Karla-Medium.ttf differ
diff --git a/admin/public/Karla-MediumItalic.ttf b/admin/public/Karla-MediumItalic.ttf
new file mode 100644
index 000000000..ea9535355
Binary files /dev/null and b/admin/public/Karla-MediumItalic.ttf differ
diff --git a/admin/public/Karla-Regular.ttf b/admin/public/Karla-Regular.ttf
new file mode 100644
index 000000000..c164d0047
Binary files /dev/null and b/admin/public/Karla-Regular.ttf differ
diff --git a/admin/public/Karla-SemiBold.ttf b/admin/public/Karla-SemiBold.ttf
new file mode 100644
index 000000000..82e0c5abf
Binary files /dev/null and b/admin/public/Karla-SemiBold.ttf differ
diff --git a/admin/public/Karla-SemiBoldItalic.ttf b/admin/public/Karla-SemiBoldItalic.ttf
new file mode 100644
index 000000000..77c19ef69
Binary files /dev/null and b/admin/public/Karla-SemiBoldItalic.ttf differ
diff --git a/admin/public/ep_admin_pads/ar.json b/admin/public/ep_admin_pads/ar.json
new file mode 100644
index 000000000..746946edf
--- /dev/null
+++ b/admin/public/ep_admin_pads/ar.json
@@ -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": "حالة غير معروفة"
+}
diff --git a/admin/public/ep_admin_pads/bn.json b/admin/public/ep_admin_pads/bn.json
new file mode 100644
index 000000000..0048b52bb
--- /dev/null
+++ b/admin/public/ep_admin_pads/bn.json
@@ -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": "অজানা অবস্থা"
+}
diff --git a/admin/public/ep_admin_pads/ca.json b/admin/public/ep_admin_pads/ca.json
new file mode 100644
index 000000000..1d4e34216
--- /dev/null
+++ b/admin/public/ep_admin_pads/ca.json
@@ -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": "S’està 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"
+}
diff --git a/admin/public/ep_admin_pads/cs.json b/admin/public/ep_admin_pads/cs.json
new file mode 100644
index 000000000..19e92894d
--- /dev/null
+++ b/admin/public/ep_admin_pads/cs.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/cy.json b/admin/public/ep_admin_pads/cy.json
new file mode 100644
index 000000000..02546da90
--- /dev/null
+++ b/admin/public/ep_admin_pads/cy.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/da.json b/admin/public/ep_admin_pads/da.json
new file mode 100644
index 000000000..a5303b9cb
--- /dev/null
+++ b/admin/public/ep_admin_pads/da.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/de.json b/admin/public/ep_admin_pads/de.json
new file mode 100644
index 000000000..afb553caf
--- /dev/null
+++ b/admin/public/ep_admin_pads/de.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/diq.json b/admin/public/ep_admin_pads/diq.json
new file mode 100644
index 000000000..983680965
--- /dev/null
+++ b/admin/public/ep_admin_pads/diq.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/dsb.json b/admin/public/ep_admin_pads/dsb.json
new file mode 100644
index 000000000..363732a20
--- /dev/null
+++ b/admin/public/ep_admin_pads/dsb.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/el.json b/admin/public/ep_admin_pads/el.json
new file mode 100644
index 000000000..77b6af3dd
--- /dev/null
+++ b/admin/public/ep_admin_pads/el.json
@@ -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": "Άγνωστη κατάσταση"
+}
diff --git a/admin/public/ep_admin_pads/en.json b/admin/public/ep_admin_pads/en.json
new file mode 100644
index 000000000..8a9044b1b
--- /dev/null
+++ b/admin/public/ep_admin_pads/en.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/eu.json b/admin/public/ep_admin_pads/eu.json
new file mode 100644
index 000000000..71d9dfe79
--- /dev/null
+++ b/admin/public/ep_admin_pads/eu.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/ff.json b/admin/public/ep_admin_pads/ff.json
new file mode 100644
index 000000000..8cb5aea99
--- /dev/null
+++ b/admin/public/ep_admin_pads/ff.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/fi.json b/admin/public/ep_admin_pads/fi.json
new file mode 100644
index 000000000..708b2bef8
--- /dev/null
+++ b/admin/public/ep_admin_pads/fi.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/fr.json b/admin/public/ep_admin_pads/fr.json
new file mode 100644
index 000000000..e6c8a8703
--- /dev/null
+++ b/admin/public/ep_admin_pads/fr.json
@@ -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 d’utilisateurs 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 d’obtention 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"
+}
diff --git a/admin/public/ep_admin_pads/gl.json b/admin/public/ep_admin_pads/gl.json
new file mode 100644
index 000000000..5e6b66549
--- /dev/null
+++ b/admin/public/ep_admin_pads/gl.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/he.json b/admin/public/ep_admin_pads/he.json
new file mode 100644
index 000000000..8b506946b
--- /dev/null
+++ b/admin/public/ep_admin_pads/he.json
@@ -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": "מצב לא ידוע"
+}
diff --git a/admin/public/ep_admin_pads/hsb.json b/admin/public/ep_admin_pads/hsb.json
new file mode 100644
index 000000000..a6c29611f
--- /dev/null
+++ b/admin/public/ep_admin_pads/hsb.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/hu.json b/admin/public/ep_admin_pads/hu.json
new file mode 100644
index 000000000..9210761bc
--- /dev/null
+++ b/admin/public/ep_admin_pads/hu.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/ia.json b/admin/public/ep_admin_pads/ia.json
new file mode 100644
index 000000000..f0e00e5ca
--- /dev/null
+++ b/admin/public/ep_admin_pads/ia.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/it.json b/admin/public/ep_admin_pads/it.json
new file mode 100644
index 000000000..493cbb4d5
--- /dev/null
+++ b/admin/public/ep_admin_pads/it.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/kn.json b/admin/public/ep_admin_pads/kn.json
new file mode 100644
index 000000000..1e9019611
--- /dev/null
+++ b/admin/public/ep_admin_pads/kn.json
@@ -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": "ಅಪರಿಚಿತ ದೋಷ"
+}
diff --git a/admin/public/ep_admin_pads/ko.json b/admin/public/ep_admin_pads/ko.json
new file mode 100644
index 000000000..9ab8feed3
--- /dev/null
+++ b/admin/public/ep_admin_pads/ko.json
@@ -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": "알 수 없는 상태"
+}
diff --git a/admin/public/ep_admin_pads/krc.json b/admin/public/ep_admin_pads/krc.json
new file mode 100644
index 000000000..2caf4f099
--- /dev/null
+++ b/admin/public/ep_admin_pads/krc.json
@@ -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": "Билинмеген турум"
+}
diff --git a/admin/public/ep_admin_pads/lb.json b/admin/public/ep_admin_pads/lb.json
new file mode 100644
index 000000000..61aa2588d
--- /dev/null
+++ b/admin/public/ep_admin_pads/lb.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/lt.json b/admin/public/ep_admin_pads/lt.json
new file mode 100644
index 000000000..59b2a13b3
--- /dev/null
+++ b/admin/public/ep_admin_pads/lt.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/mk.json b/admin/public/ep_admin_pads/mk.json
new file mode 100644
index 000000000..72affd86c
--- /dev/null
+++ b/admin/public/ep_admin_pads/mk.json
@@ -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": "Непозната состојба"
+}
diff --git a/admin/public/ep_admin_pads/my.json b/admin/public/ep_admin_pads/my.json
new file mode 100644
index 000000000..6b94ba702
--- /dev/null
+++ b/admin/public/ep_admin_pads/my.json
@@ -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": "အခြေအနေမသိ"
+}
diff --git a/admin/public/ep_admin_pads/nb.json b/admin/public/ep_admin_pads/nb.json
new file mode 100644
index 000000000..acd194397
--- /dev/null
+++ b/admin/public/ep_admin_pads/nb.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/nl.json b/admin/public/ep_admin_pads/nl.json
new file mode 100644
index 000000000..f4d97b351
--- /dev/null
+++ b/admin/public/ep_admin_pads/nl.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/oc.json b/admin/public/ep_admin_pads/oc.json
new file mode 100644
index 000000000..ae0169faf
--- /dev/null
+++ b/admin/public/ep_admin_pads/oc.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/pms.json b/admin/public/ep_admin_pads/pms.json
new file mode 100644
index 000000000..ac0542b85
--- /dev/null
+++ b/admin/public/ep_admin_pads/pms.json
@@ -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ù"
+}
diff --git a/admin/public/ep_admin_pads/pt-br.json b/admin/public/ep_admin_pads/pt-br.json
new file mode 100644
index 000000000..28a7874ee
--- /dev/null
+++ b/admin/public/ep_admin_pads/pt-br.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/pt.json b/admin/public/ep_admin_pads/pt.json
new file mode 100644
index 000000000..b7abf2f3f
--- /dev/null
+++ b/admin/public/ep_admin_pads/pt.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/qqq.json b/admin/public/ep_admin_pads/qqq.json
new file mode 100644
index 000000000..de36e2ae6
--- /dev/null
+++ b/admin/public/ep_admin_pads/qqq.json
@@ -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}}"
+}
diff --git a/admin/public/ep_admin_pads/ru.json b/admin/public/ep_admin_pads/ru.json
new file mode 100644
index 000000000..6d0d163d0
--- /dev/null
+++ b/admin/public/ep_admin_pads/ru.json
@@ -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": "Неизвестный статус"
+}
diff --git a/admin/public/ep_admin_pads/sc.json b/admin/public/ep_admin_pads/sc.json
new file mode 100644
index 000000000..a37bba5a2
--- /dev/null
+++ b/admin/public/ep_admin_pads/sc.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/sdc.json b/admin/public/ep_admin_pads/sdc.json
new file mode 100644
index 000000000..c4672fd7f
--- /dev/null
+++ b/admin/public/ep_admin_pads/sdc.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/sk.json b/admin/public/ep_admin_pads/sk.json
new file mode 100644
index 000000000..ab0392d4e
--- /dev/null
+++ b/admin/public/ep_admin_pads/sk.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/skr-arab.json b/admin/public/ep_admin_pads/skr-arab.json
new file mode 100644
index 000000000..08162f849
--- /dev/null
+++ b/admin/public/ep_admin_pads/skr-arab.json
@@ -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": "نامعلوم حالت"
+}
diff --git a/admin/public/ep_admin_pads/sl.json b/admin/public/ep_admin_pads/sl.json
new file mode 100644
index 000000000..3bebe1972
--- /dev/null
+++ b/admin/public/ep_admin_pads/sl.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/smn.json b/admin/public/ep_admin_pads/smn.json
new file mode 100644
index 000000000..9d57cc73c
--- /dev/null
+++ b/admin/public/ep_admin_pads/smn.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/sms.json b/admin/public/ep_admin_pads/sms.json
new file mode 100644
index 000000000..8d3cf5797
--- /dev/null
+++ b/admin/public/ep_admin_pads/sms.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/sq.json b/admin/public/ep_admin_pads/sq.json
new file mode 100644
index 000000000..cc4740763
--- /dev/null
+++ b/admin/public/ep_admin_pads/sq.json
@@ -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": "S’ka 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": "S’u 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"
+}
diff --git a/admin/public/ep_admin_pads/sv.json b/admin/public/ep_admin_pads/sv.json
new file mode 100644
index 000000000..e77aaf2c4
--- /dev/null
+++ b/admin/public/ep_admin_pads/sv.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/sw.json b/admin/public/ep_admin_pads/sw.json
new file mode 100644
index 000000000..f1beeecbb
--- /dev/null
+++ b/admin/public/ep_admin_pads/sw.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/th.json b/admin/public/ep_admin_pads/th.json
new file mode 100644
index 000000000..693e3f797
--- /dev/null
+++ b/admin/public/ep_admin_pads/th.json
@@ -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": "ไม่ทราบสถานะ"
+}
diff --git a/admin/public/ep_admin_pads/tl.json b/admin/public/ep_admin_pads/tl.json
new file mode 100644
index 000000000..238e01236
--- /dev/null
+++ b/admin/public/ep_admin_pads/tl.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/tr.json b/admin/public/ep_admin_pads/tr.json
new file mode 100644
index 000000000..7e2e9d402
--- /dev/null
+++ b/admin/public/ep_admin_pads/tr.json
@@ -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"
+}
diff --git a/admin/public/ep_admin_pads/uk.json b/admin/public/ep_admin_pads/uk.json
new file mode 100644
index 000000000..c5c95f722
--- /dev/null
+++ b/admin/public/ep_admin_pads/uk.json
@@ -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": "Невідомий статус"
+}
diff --git a/admin/public/ep_admin_pads/zh-hans.json b/admin/public/ep_admin_pads/zh-hans.json
new file mode 100644
index 000000000..cdf0d945f
--- /dev/null
+++ b/admin/public/ep_admin_pads/zh-hans.json
@@ -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": "未知状态"
+}
diff --git a/admin/public/ep_admin_pads/zh-hant.json b/admin/public/ep_admin_pads/zh-hant.json
new file mode 100644
index 000000000..daeed55f5
--- /dev/null
+++ b/admin/public/ep_admin_pads/zh-hant.json
@@ -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": "不明狀態"
+}
diff --git a/admin/public/fond.jpg b/admin/public/fond.jpg
new file mode 100644
index 000000000..81357c7bb
Binary files /dev/null and b/admin/public/fond.jpg differ
diff --git a/admin/src/App.css b/admin/src/App.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/admin/src/App.tsx b/admin/src/App.tsx
new file mode 100644
index 000000000..a1a1e4377
--- /dev/null
+++ b/admin/src/App.tsx
@@ -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
+}
+
+export default App
diff --git a/admin/src/components/IconButton.tsx b/admin/src/components/IconButton.tsx
new file mode 100644
index 000000000..e91f3e914
--- /dev/null
+++ b/admin/src/components/IconButton.tsx
@@ -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 = ({icon,className,onClick,title, disabled})=>{
+ return
+}
diff --git a/admin/src/components/SearchField.tsx b/admin/src/components/SearchField.tsx
new file mode 100644
index 000000000..62a965d40
--- /dev/null
+++ b/admin/src/components/SearchField.tsx
@@ -0,0 +1,14 @@
+import {ChangeEventHandler, FC} from "react";
+import {Search} from 'lucide-react'
+export type SearchFieldProps = {
+ value: string,
+ onChange: ChangeEventHandler,
+ placeholder?: string
+}
+
+export const SearchField:FC = ({onChange,value, placeholder})=>{
+ return
+
+
+
+}
diff --git a/admin/src/index.css b/admin/src/index.css
new file mode 100644
index 000000000..7a8e5df48
--- /dev/null
+++ b/admin/src/index.css
@@ -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;
+}
diff --git a/admin/src/localization/i18n.ts b/admin/src/localization/i18n.ts
new file mode 100644
index 000000000..67ae140e7
--- /dev/null
+++ b/admin/src/localization/i18n.ts
@@ -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
diff --git a/admin/src/main.tsx b/admin/src/main.tsx
new file mode 100644
index 000000000..03ec73104
--- /dev/null
+++ b/admin/src/main.tsx
@@ -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(
+ <>}>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+
+ }/>
+ >
+), {
+ basename: import.meta.env.BASE_URL
+})
+
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+
+
+ ,
+)
diff --git a/admin/src/pages/HelpPage.tsx b/admin/src/pages/HelpPage.tsx
new file mode 100644
index 000000000..6f06907e1
--- /dev/null
+++ b/admin/src/pages/HelpPage.tsx
@@ -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();
+
+ useEffect(() => {
+ if(!settingsSocket) return;
+ settingsSocket?.on('reply:help', (data) => {
+ setHelpData(data)
+ });
+
+ settingsSocket?.emit('help');
+ }, [settingsSocket]);
+
+ const renderHooks = (hooks:Record>) => {
+ return Object.keys(hooks).map((hookName, i) => {
+ return
+
{hookName}
+
+ {Object.keys(hooks[hookName]).map((hook, i) => - {hook}
+
+ {Object.keys(hooks[hookName][hook]).map((subHook, i) => - {subHook}
)}
+
+ )}
+
+
+ })
+ }
+
+
+ if (!helpData) return
+
+ return
+
+
+
+
{helpData?.epVersion}
+
+
{helpData.latestVersion}
+
Git sha
+
{helpData.gitCommit}
+
+
+
+ {helpData.installedPlugins.map((plugin, i) => - {plugin}
)}
+
+
+
+
+ {helpData.installedParts.map((part, i) => - {part}
)}
+
+
+
+ {
+ renderHooks(helpData.installedServerHooks)
+ }
+
+
+
+ {
+ renderHooks(helpData.installedClientHooks)
+ }
+
+
+
+}
diff --git a/admin/src/pages/HomePage.tsx b/admin/src/pages/HomePage.tsx
new file mode 100644
index 000000000..0ea1a26d4
--- /dev/null
+++ b/admin/src/pages/HomePage.tsx
@@ -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([])
+ const [installedPlugins, setInstalledPlugins] = useState([])
+ 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({
+ offset: 0,
+ limit: 99999,
+ sortBy: 'name',
+ sortDir: 'asc',
+ searchTerm: ''
+ })
+ const [searchTerm, setSearchTerm] = useState('')
+ 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
+
+
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+ {sortedInstalledPlugins.map((plugin, index) => {
+ return
+ {plugin.name} |
+ {plugin.version} |
+
+ {
+ plugin.updatable ?
+
+ : } title={} onClick={() => uninstallPlugin(plugin.name)}/>
+ }
+ |
+
+ })}
+
+
+
+
+
+
{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+
+
+ {plugins.map((plugin) => {
+ return
+ {plugin.name} |
+ {plugin.description} |
+ {plugin.version} |
+ {plugin.time} |
+
+ } onClick={() => installPlugin(plugin.name)} title={}/>
+ |
+
+ })}
+
+
+
+}
diff --git a/admin/src/pages/LoginScreen.tsx b/admin/src/pages/LoginScreen.tsx
new file mode 100644
index 000000000..ade83cd4c
--- /dev/null
+++ b/admin/src/pages/LoginScreen.tsx
@@ -0,0 +1,61 @@
+import {useStore} from "../store/store.ts";
+import {useNavigate} from "react-router-dom";
+import {SubmitHandler, useForm} from "react-hook-form";
+import {Eye, EyeOff} from "lucide-react";
+import {useState} from "react";
+
+type Inputs = {
+ username: string
+ password: string
+}
+
+export const LoginScreen = ()=>{
+ const navigate = useNavigate()
+ const [passwordVisible, setPasswordVisible] = useState(false)
+
+ const {
+ register,
+ handleSubmit} = useForm()
+
+ const login: SubmitHandler = ({username,password})=>{
+ fetch('/admin-auth/', {
+ method: 'POST',
+ headers:{
+ Authorization: `Basic ${btoa(`${username}:${password}`)}`
+ }
+ }).then(r=>{
+ if(!r.ok) {
+ useStore.getState().setToastState({
+ open: true,
+ title: "Login failed",
+ success: false
+ })
+ } else {
+ navigate('/')
+ }
+ }).catch(e=>{
+ console.error(e)
+ })
+ }
+
+ return
+}
diff --git a/admin/src/pages/PadPage.tsx b/admin/src/pages/PadPage.tsx
new file mode 100644
index 000000000..b5f2128bf
--- /dev/null
+++ b/admin/src/pages/PadPage.tsx
@@ -0,0 +1,178 @@
+import {Trans, useTranslation} from "react-i18next";
+import {useEffect, useMemo, useState} from "react";
+import {useStore} from "../store/store.ts";
+import {PadSearchQuery, PadSearchResult} from "../utils/PadSearch.ts";
+import {useDebounce} from "../utils/useDebounce.ts";
+import {determineSorting} from "../utils/sorting.ts";
+import * as Dialog from "@radix-ui/react-dialog";
+import {IconButton} from "../components/IconButton.tsx";
+import {ChevronLeft, ChevronRight, Eye, Trash2} from "lucide-react";
+import {SearchField} from "../components/SearchField.tsx";
+
+export const PadPage = ()=>{
+ const settingsSocket = useStore(state=>state.settingsSocket)
+ const [searchParams, setSearchParams] = useState({
+ offset: 0,
+ limit: 12,
+ pattern: '',
+ sortBy: 'padName',
+ ascending: true
+ })
+ const {t} = useTranslation()
+ const [searchTerm, setSearchTerm] = useState('')
+ const pads = useStore(state=>state.pads)
+ const [currentPage, setCurrentPage] = useState(0)
+ const [deleteDialog, setDeleteDialog] = useState(false)
+ const [padToDelete, setPadToDelete] = useState('')
+ const pages = useMemo(()=>{
+ if(!pads){
+ return [0]
+ }
+
+ return Math.ceil(pads!.total / searchParams.limit)
+ },[pads, searchParams.limit])
+
+ useDebounce(()=>{
+ setSearchParams({
+ ...searchParams,
+ pattern: searchTerm
+ })
+
+ }, 500, [searchTerm])
+
+ useEffect(() => {
+ if(!settingsSocket){
+ return
+ }
+
+ settingsSocket.emit('padLoad', searchParams)
+
+ }, [settingsSocket, searchParams]);
+
+ useEffect(() => {
+ if(!settingsSocket){
+ return
+ }
+
+ settingsSocket.on('results:padLoad', (data: PadSearchResult)=>{
+ useStore.getState().setPads(data);
+ })
+
+
+ settingsSocket.on('results:deletePad', (padID: string)=>{
+ const newPads = useStore.getState().pads?.results?.filter((pad)=>{
+ return pad.padName !== padID
+ })
+ useStore.getState().setPads({
+ total: useStore.getState().pads!.total-1,
+ results: newPads
+ })
+ })
+ }, [settingsSocket, pads]);
+
+ const deletePad = (padID: string)=>{
+ settingsSocket?.emit('deletePad', padID)
+ }
+
+
+
+ return
+
+
+
+
+
+
+ {t("ep_admin_pads:ep_adminpads2_confirm", {
+ padID: padToDelete,
+ })}
+
+
+
+
+
+
+
+
+
+
+
setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
+
+
+
+ {
+ setSearchParams({
+ ...searchParams,
+ sortBy: 'padName',
+ ascending: !searchParams.ascending
+ })
+ }}> |
+ {
+ setSearchParams({
+ ...searchParams,
+ sortBy: 'lastEdited',
+ ascending: !searchParams.ascending
+ })
+ }}> |
+ {
+ setSearchParams({
+ ...searchParams,
+ sortBy: 'userCount',
+ ascending: !searchParams.ascending
+ })
+ }}> |
+ {
+ setSearchParams({
+ ...searchParams,
+ sortBy: 'revisionNumber',
+ ascending: !searchParams.ascending
+ })
+ }}>Revision number |
+ |
+
+
+
+ {
+ pads?.results?.map((pad)=>{
+ return
+ {pad.padName} |
+ {pad.userCount} |
+ {new Date(pad.lastEdited).toLocaleString()} |
+ {pad.revisionNumber} |
+
+
+ } title={} onClick={()=>{
+ setPadToDelete(pad.padName)
+ setDeleteDialog(true)
+ }}/>
+ } title="view" onClick={()=>window.open(`/p/${pad.padName}`, '_blank')}/>
+
+ |
+
+ })
+ }
+
+
+
+
+ {currentPage+1} out of {pages}
+
+
+
+}
diff --git a/admin/src/pages/Plugin.ts b/admin/src/pages/Plugin.ts
new file mode 100644
index 000000000..3188c247f
--- /dev/null
+++ b/admin/src/pages/Plugin.ts
@@ -0,0 +1,36 @@
+export type PluginDef = {
+ name: string,
+ description: string,
+ version: string,
+ time: string,
+ official: boolean,
+}
+
+
+export type InstalledPlugin = {
+ name: string,
+ path: string,
+ realPath: string,
+ version:string,
+ updatable?: boolean
+}
+
+
+export type SearchParams = {
+ searchTerm: string,
+ offset: number,
+ limit: number,
+ sortBy: 'name'|'version',
+ sortDir: 'asc'|'desc'
+}
+
+
+export type HelpObj = {
+ epVersion: string
+ gitCommit: string
+ installedClientHooks: Record>,
+ installedParts: string[],
+ installedPlugins: string[],
+ installedServerHooks: Record,
+ latestVersion: string
+}
diff --git a/admin/src/pages/SettingsPage.tsx b/admin/src/pages/SettingsPage.tsx
new file mode 100644
index 000000000..2706ffa38
--- /dev/null
+++ b/admin/src/pages/SettingsPage.tsx
@@ -0,0 +1,50 @@
+import {useStore} from "../store/store.ts";
+import {isJSONClean} from "../utils/utils.ts";
+import {Trans} from "react-i18next";
+import {IconButton} from "../components/IconButton.tsx";
+import {RotateCw, Save} from "lucide-react";
+
+export const SettingsPage = ()=>{
+ const settingsSocket = useStore(state=>state.settingsSocket)
+ const settings = useStore(state=>state.settings)
+
+ return
+}
diff --git a/admin/src/store/store.ts b/admin/src/store/store.ts
new file mode 100644
index 000000000..d662dfdf3
--- /dev/null
+++ b/admin/src/store/store.ts
@@ -0,0 +1,47 @@
+import {create} from "zustand";
+import {Socket} from "socket.io-client";
+import {PadSearchResult} from "../utils/PadSearch.ts";
+
+type ToastState = {
+ description?:string,
+ title: string,
+ open: boolean,
+ success: boolean
+}
+
+
+type StoreState = {
+ settings: string|undefined,
+ setSettings: (settings: string) => void,
+ settingsSocket: Socket|undefined,
+ setSettingsSocket: (socket: Socket) => void,
+ showLoading: boolean,
+ setShowLoading: (show: boolean) => void,
+ setPluginsSocket: (socket: Socket) => void
+ pluginsSocket: Socket|undefined,
+ toastState: ToastState,
+ setToastState: (val: ToastState)=>void,
+ pads: PadSearchResult|undefined,
+ setPads: (pads: PadSearchResult)=>void
+}
+
+
+export const useStore = create()((set) => ({
+ settings: undefined,
+ setSettings: (settings: string) => set({settings}),
+ settingsSocket: undefined,
+ setSettingsSocket: (socket: Socket) => set({settingsSocket: socket}),
+ showLoading: false,
+ setShowLoading: (show: boolean) => set({showLoading: show}),
+ pluginsSocket: undefined,
+ setPluginsSocket: (socket: Socket) => set({pluginsSocket: socket}),
+ setToastState: (val )=>set({toastState: val}),
+ toastState: {
+ open: false,
+ title: '',
+ description:'',
+ success: false
+ },
+ pads: undefined,
+ setPads: (pads)=>set({pads})
+}));
diff --git a/admin/src/utils/AnimationFrameHook.ts b/admin/src/utils/AnimationFrameHook.ts
new file mode 100644
index 000000000..98ae324e8
--- /dev/null
+++ b/admin/src/utils/AnimationFrameHook.ts
@@ -0,0 +1,29 @@
+import {useCallback, useEffect, useRef} from "react";
+
+type Args = any[]
+
+export const useAnimationFrame = void>(
+ callback: Fn,
+ wait = 0
+): ((...args: Parameters)=>void)=>{
+ const rafId = useRef(0)
+ const render = useCallback(
+ (...args: Parameters)=>{
+ cancelAnimationFrame(rafId.current)
+ const timeStart = performance.now()
+
+ const renderFrame = (timeNow: number)=>{
+ if(timeNow-timeStartcancelAnimationFrame(rafId.current),[])
+ return render
+}
\ No newline at end of file
diff --git a/admin/src/utils/LoadingScreen.tsx b/admin/src/utils/LoadingScreen.tsx
new file mode 100644
index 000000000..b3ea51e13
--- /dev/null
+++ b/admin/src/utils/LoadingScreen.tsx
@@ -0,0 +1,19 @@
+import {useStore} from "../store/store.ts";
+import * as Dialog from '@radix-ui/react-dialog';
+import ReactComponent from './brand.svg?react';
+export const LoadingScreen = ()=>{
+ const showLoading = useStore(state => state.showLoading)
+
+ return
+
+
+
+
+
+
+}
diff --git a/admin/src/utils/PadSearch.ts b/admin/src/utils/PadSearch.ts
new file mode 100644
index 000000000..6491094a2
--- /dev/null
+++ b/admin/src/utils/PadSearch.ts
@@ -0,0 +1,20 @@
+export type PadSearchQuery = {
+ pattern: string;
+ offset: number;
+ limit: number;
+ ascending: boolean;
+ sortBy: string;
+}
+
+
+export type PadSearchResult = {
+ total: number;
+ results?: PadType[]
+}
+
+export type PadType = {
+ padName: string;
+ lastEdited: number;
+ userCount: number;
+ revisionNumber: number;
+}
diff --git a/admin/src/utils/Toast.tsx b/admin/src/utils/Toast.tsx
new file mode 100644
index 000000000..7b74b4d2b
--- /dev/null
+++ b/admin/src/utils/Toast.tsx
@@ -0,0 +1,26 @@
+import * as Toast from '@radix-ui/react-toast'
+import {useStore} from "../store/store.ts";
+import {useMemo} from "react";
+
+export const ToastDialog = ()=>{
+ const toastState = useStore(state => state.toastState)
+ const resultingClass = useMemo(()=> {
+ return toastState.success?'ToastRootSuccess':'ToastRootFailure'
+ }, [toastState.success])
+
+ console.log()
+ return <>
+ {
+ useStore.getState().setToastState({
+ ...toastState!,
+ open: !toastState?.open
+ })
+ }}>
+ {toastState.title}
+
+ {toastState.description}
+
+
+
+ >
+}
diff --git a/admin/src/utils/brand.svg b/admin/src/utils/brand.svg
new file mode 100644
index 000000000..3f42434c9
--- /dev/null
+++ b/admin/src/utils/brand.svg
@@ -0,0 +1,50 @@
+
+
diff --git a/admin/src/utils/sorting.ts b/admin/src/utils/sorting.ts
new file mode 100644
index 000000000..cc6a9a293
--- /dev/null
+++ b/admin/src/utils/sorting.ts
@@ -0,0 +1,6 @@
+export const determineSorting = (sortBy: string, ascending: boolean, currentSymbol: string) => {
+ if (sortBy === currentSymbol) {
+ return ascending ? 'sort up' : 'sort down';
+ }
+ return 'sort none';
+}
diff --git a/admin/src/utils/useDebounce.ts b/admin/src/utils/useDebounce.ts
new file mode 100644
index 000000000..fcf56a093
--- /dev/null
+++ b/admin/src/utils/useDebounce.ts
@@ -0,0 +1,22 @@
+import {DependencyList, EffectCallback, useMemo, useRef} from "react";
+import {useAnimationFrame} from "./AnimationFrameHook";
+
+const defaultDeps: DependencyList = []
+
+export const useDebounce = (
+ fn:EffectCallback,
+ wait = 0,
+ deps = defaultDeps
+):void => {
+ const isFirstRender = useRef(true)
+ const render = useAnimationFrame(fn, wait)
+
+ useMemo(()=>{
+ if(isFirstRender.current){
+ isFirstRender.current = false
+ return
+ }
+
+ render()
+ }, deps)
+}
\ No newline at end of file
diff --git a/admin/src/utils/utils.ts b/admin/src/utils/utils.ts
new file mode 100644
index 000000000..2e8f52a05
--- /dev/null
+++ b/admin/src/utils/utils.ts
@@ -0,0 +1,64 @@
+const minify = (json: string)=>{
+
+ let tokenizer = /"|(\/\*)|(\*\/)|(\/\/)|\n|\r/g,
+ in_string = false,
+ in_multiline_comment = false,
+ in_singleline_comment = false,
+ tmp, tmp2, new_str = [], ns = 0, from = 0, lc, rc
+ ;
+
+ tokenizer.lastIndex = 0;
+
+ while (tmp = tokenizer.exec(json)) {
+ lc = RegExp.leftContext;
+ rc = RegExp.rightContext;
+ if (!in_multiline_comment && !in_singleline_comment) {
+ tmp2 = lc.substring(from);
+ if (!in_string) {
+ tmp2 = tmp2.replace(/(\n|\r|\s)*/g,"");
+ }
+ new_str[ns++] = tmp2;
+ }
+ from = tokenizer.lastIndex;
+
+ if (tmp[0] == "\"" && !in_multiline_comment && !in_singleline_comment) {
+ tmp2 = lc.match(/(\\)*$/);
+ if (!in_string || !tmp2 || (tmp2[0].length % 2) == 0) { // start of string with ", or unescaped " character found to end string
+ in_string = !in_string;
+ }
+ from--; // include " character in next catch
+ rc = json.substring(from);
+ }
+ else if (tmp[0] == "/*" && !in_string && !in_multiline_comment && !in_singleline_comment) {
+ in_multiline_comment = true;
+ }
+ else if (tmp[0] == "*/" && !in_string && in_multiline_comment && !in_singleline_comment) {
+ in_multiline_comment = false;
+ }
+ else if (tmp[0] == "//" && !in_string && !in_multiline_comment && !in_singleline_comment) {
+ in_singleline_comment = true;
+ }
+ else if ((tmp[0] == "\n" || tmp[0] == "\r") && !in_string && !in_multiline_comment && in_singleline_comment) {
+ in_singleline_comment = false;
+ }
+ else if (!in_multiline_comment && !in_singleline_comment && !(/\n|\r|\s/.test(tmp[0]))) {
+ new_str[ns++] = tmp[0];
+ }
+ }
+ new_str[ns++] = rc;
+ return new_str.join("");
+}
+
+
+
+
+export const isJSONClean = (data: string) => {
+ let cleanSettings = minify(data);
+ // this is a bit naive. In theory some key/value might contain the sequences ',]' or ',}'
+ cleanSettings = cleanSettings.replace(',]', ']').replace(',}', '}');
+ try {
+ return typeof JSON.parse(cleanSettings) === 'object';
+ } catch (e) {
+ return false; // the JSON failed to be parsed
+ }
+};
diff --git a/admin/src/vite-env.d.ts b/admin/src/vite-env.d.ts
new file mode 100644
index 000000000..b1f45c786
--- /dev/null
+++ b/admin/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/admin/tsconfig.json b/admin/tsconfig.json
new file mode 100644
index 000000000..a7fc6fbf2
--- /dev/null
+++ b/admin/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/admin/tsconfig.node.json b/admin/tsconfig.node.json
new file mode 100644
index 000000000..97ede7ee6
--- /dev/null
+++ b/admin/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/admin/vite.config.ts b/admin/vite.config.ts
new file mode 100644
index 000000000..ff329032f
--- /dev/null
+++ b/admin/vite.config.ts
@@ -0,0 +1,34 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react-swc'
+import svgr from 'vite-plugin-svgr'
+import {viteStaticCopy} from "vite-plugin-static-copy";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react(), svgr(), viteStaticCopy({
+ targets: [
+ {
+ src: '../src/locales',
+ dest: ''
+ }
+ ]
+ })],
+ base: '/admin',
+ build:{
+ outDir: '../src/templates/admin'
+ },
+ server:{
+ proxy: {
+ '/socket.io/*': {
+ target: 'http://localhost:9001',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, '')
+ },
+ '/admin-auth/': {
+ target: 'http://localhost:9001',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/admin-prox/, '/admin/')
+ }
+ }
+ }
+})
diff --git a/bin b/bin
deleted file mode 120000
index 70feaa890..000000000
--- a/bin
+++ /dev/null
@@ -1 +0,0 @@
-src/bin
\ No newline at end of file
diff --git a/src/bin/buildDebian.sh b/bin/buildDebian.sh
similarity index 96%
rename from src/bin/buildDebian.sh
rename to bin/buildDebian.sh
index 241b3f751..f1f5675ec 100755
--- a/src/bin/buildDebian.sh
+++ b/bin/buildDebian.sh
@@ -14,7 +14,7 @@ rm -rf ${DIST}
mkdir -p ${DIST}/
rm -rf ${SRC}
-rsync -a src/bin/deb-src/ ${SRC}/
+rsync -a bin/deb-src/ ${SRC}/
mkdir -p ${SYSROOT}/opt/
rsync --exclude '.git' -a . ${SYSROOT}/opt/etherpad/ --delete
diff --git a/src/bin/buildForWindows.sh b/bin/buildForWindows.sh
similarity index 73%
rename from src/bin/buildForWindows.sh
rename to bin/buildForWindows.sh
index ef4512305..67f4eeae1 100755
--- a/src/bin/buildForWindows.sh
+++ b/bin/buildForWindows.sh
@@ -33,25 +33,46 @@ try export GIT_WORK_TREE=${TMP_FOLDER}; git checkout HEAD -f \
|| fatal "failed to copy etherpad to temporary folder"
try mkdir "${TMP_FOLDER}"/.git
try git rev-parse HEAD >${TMP_FOLDER}/.git/HEAD
-try cp -r ./src/node_modules "${TMP_FOLDER}"/src/node_modules
+# Disable symlinks to avoid problems with Windows
+#try pnpm i "${TMP_FOLDER}"/src/node_modules
try cd "${TMP_FOLDER}"
[ -f src/package.json ] || fatal "failed to copy etherpad to temporary folder"
# setting NODE_ENV=production ensures that dev dependencies are not installed,
# making the windows package smaller
-export NODE_ENV=production
+export NODE_ENV=development
+
+rm -rf node_modules || true
+rm -rf src/node_modules || true
+
+#log "do a normal unix install first..."
+#$(try cd ./bin/installDeps.sh)
+
+# Install admin frontend
+cd admin
+try pnpm install
+try pnpm run build
+cd ..
+
+
+
+
+# Nuke the admin folder as it is not needed anymore :D
+rm -rf admin
+
+export NODE_ENV=production
+try pnpm install --production
+
-log "do a normal unix install first..."
-try ./src/bin/installDeps.sh
log "copy the windows settings template..."
try cp settings.json.template settings.json
-log "resolve symbolic links..."
-try cp -rL node_modules node_modules_resolved
-try rm -rf node_modules
-try mv node_modules_resolved node_modules
+#log "resolve symbolic links..."
+#try cp -rL node_modules node_modules_resolved
+#try rm -rf node_modules
+#try mv node_modules_resolved node_modules
log "download windows node..."
try wget "https://nodejs.org/dist/latest-v20.x/win-x64/node.exe" -O node.exe
diff --git a/src/bin/checkAllPads.js b/bin/checkAllPads.ts
similarity index 69%
rename from src/bin/checkAllPads.js
rename to bin/checkAllPads.ts
index d2a5f837c..a967413d1 100644
--- a/src/bin/checkAllPads.js
+++ b/bin/checkAllPads.ts
@@ -2,22 +2,23 @@
/*
* This is a debug tool. It checks all revisions for data corruption
*/
+import process from "node:process";
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
-if (process.argv.length !== 2) throw new Error('Use: node src/bin/checkAllPads.js');
+if (process.argv.length !== 2) throw new Error('Use: node bin/checkAllPads.js');
(async () => {
- const db = require('../node/db/DB');
+ const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
- const padManager = require('../node/db/PadManager');
- await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId) => {
+ const padManager = require('ep_etherpad-lite/node/db/PadManager');
+ await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId: string) => {
const pad = await padManager.getPad(padId);
try {
await pad.check();
- } catch (err) {
+ } catch (err:any) {
console.error(`Error in pad ${padId}: ${err.stack || err}`);
return;
}
diff --git a/src/bin/checkPad.js b/bin/checkPad.ts
similarity index 69%
rename from src/bin/checkPad.js
rename to bin/checkPad.ts
index 6aa4b3034..09f3d30c5 100644
--- a/src/bin/checkPad.js
+++ b/bin/checkPad.ts
@@ -2,17 +2,19 @@
/*
* This is a debug tool. It checks all revisions for data corruption
*/
+import process from "node:process";
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
-if (process.argv.length !== 3) throw new Error('Use: node src/bin/checkPad.js $PADID');
+if (process.argv.length !== 3) throw new Error('Use: node bin/checkPad.js $PADID');
+// @ts-ignore
const padId = process.argv[2];
(async () => {
- const db = require('../node/db/DB');
+ const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
- const padManager = require('../node/db/PadManager');
+ const padManager = require('ep_etherpad-lite/node/db/PadManager');
if (!await padManager.doesPadExists(padId)) throw new Error('Pad does not exist');
const pad = await padManager.getPad(padId);
await pad.check();
diff --git a/src/bin/cleanRun.sh b/bin/cleanRun.sh
similarity index 85%
rename from src/bin/cleanRun.sh
rename to bin/cleanRun.sh
index 556180eb4..acff0d669 100755
--- a/src/bin/cleanRun.sh
+++ b/bin/cleanRun.sh
@@ -2,10 +2,10 @@
# Move to the Etherpad base directory.
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${MY_DIR}/../.." || exit 1
+cd "${MY_DIR}/.." || exit 1
# Source constants and useful functions
-. src/bin/functions.sh
+. bin/functions.sh
ignoreRoot=0
for ARG in "$@"
@@ -31,9 +31,9 @@ fi
rm -rf src/node_modules
#Prepare the environment
-src/bin/installDeps.sh "$@" || exit 1
+bin/installDeps.sh "$@" || exit 1
#Move to the node folder and start
echo "Starting Etherpad..."
-
-exec node src/node/server.js "$@"
+cd src
+exec node --import tsx ./node/server.ts "$@"
diff --git a/src/bin/convertSettings.json.template b/bin/convertSettings.json.template
similarity index 100%
rename from src/bin/convertSettings.json.template
rename to bin/convertSettings.json.template
diff --git a/src/bin/createRelease.sh b/bin/createRelease.sh
similarity index 99%
rename from src/bin/createRelease.sh
rename to bin/createRelease.sh
index 14e3cd337..e3b0eb0d3 100755
--- a/src/bin/createRelease.sh
+++ b/bin/createRelease.sh
@@ -134,7 +134,7 @@ function create_builds {
git clone $ETHER_WEB_REPO
echo "Creating windows build..."
cd etherpad-lite
- src/bin/buildForWindows.sh
+ bin/buildForWindows.sh
[[ $? != 0 ]] && echo "Aborting: Error creating build for windows" && exit 1
echo "Creating docs..."
make docs
diff --git a/bin/createUserSession.ts b/bin/createUserSession.ts
new file mode 100644
index 000000000..1dbab0d69
--- /dev/null
+++ b/bin/createUserSession.ts
@@ -0,0 +1,56 @@
+'use strict';
+
+/*
+ * A tool for generating a test user session which can be used for debugging configs
+ * that require sessions.
+ */
+
+// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
+// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
+import fs from "node:fs";
+
+import path from "node:path";
+
+import querystring from "node:querystring";
+
+import axios from 'axios'
+
+
+process.on('unhandledRejection', (err) => { throw err; });
+const settings = require('ep_etherpad-lite/node/utils/Settings');
+(async () => {
+ axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
+ const api = axios;
+
+ const filePath = path.join(__dirname, '../APIKEY.txt');
+ const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
+
+ let res;
+
+ res = await api.get('/api/');
+ const apiVersion = res.data.currentVersion;
+ if (!apiVersion) throw new Error('No version set in API');
+ console.log('apiVersion', apiVersion);
+ const uri = (cmd: string, args: querystring.ParsedUrlQueryInput ) => `/api/${apiVersion}/${cmd}?${querystring.stringify(args)}`;
+
+ res = await api.post(uri('createGroup', {apikey}));
+ if (res.data.code === 1) throw new Error(`Error creating group: ${res.data}`);
+ const groupID = res.data.data.groupID;
+ console.log('groupID', groupID);
+
+ res = await api.post(uri('createGroupPad', {apikey, groupID}));
+ if (res.data.code === 1) throw new Error(`Error creating group pad: ${res.data}`);
+ console.log('Test Pad ID ====> ', res.data.data.padID);
+
+ res = await api.post(uri('createAuthor', {apikey}));
+ if (res.data.code === 1) throw new Error(`Error creating author: ${res.data}`);
+ const authorID = res.data.data.authorID;
+ console.log('authorID', authorID);
+
+ const validUntil = Math.floor(new Date().getTime() / 1000) + 60000;
+ console.log('validUntil', validUntil);
+ res = await api.post(uri('createSession', {apikey, groupID, authorID, validUntil}));
+ if (res.data.code === 1) throw new Error(`Error creating session: ${JSON.stringify(res.data)}`);
+ console.log('Session made: ====> create a cookie named sessionID and set the value to',
+ res.data.data.sessionID);
+})();
diff --git a/src/bin/deb-src/DEBIAN/control b/bin/deb-src/DEBIAN/control
similarity index 100%
rename from src/bin/deb-src/DEBIAN/control
rename to bin/deb-src/DEBIAN/control
diff --git a/src/bin/deb-src/DEBIAN/postinst b/bin/deb-src/DEBIAN/postinst
similarity index 100%
rename from src/bin/deb-src/DEBIAN/postinst
rename to bin/deb-src/DEBIAN/postinst
diff --git a/src/bin/deb-src/DEBIAN/preinst b/bin/deb-src/DEBIAN/preinst
similarity index 100%
rename from src/bin/deb-src/DEBIAN/preinst
rename to bin/deb-src/DEBIAN/preinst
diff --git a/src/bin/deb-src/DEBIAN/prerm b/bin/deb-src/DEBIAN/prerm
similarity index 100%
rename from src/bin/deb-src/DEBIAN/prerm
rename to bin/deb-src/DEBIAN/prerm
diff --git a/src/bin/deb-src/sysroot/etc/init/etherpad.conf b/bin/deb-src/sysroot/etc/init/etherpad.conf
similarity index 79%
rename from src/bin/deb-src/sysroot/etc/init/etherpad.conf
rename to bin/deb-src/sysroot/etc/init/etherpad.conf
index 82706654d..ea8349d10 100644
--- a/src/bin/deb-src/sysroot/etc/init/etherpad.conf
+++ b/bin/deb-src/sysroot/etc/init/etherpad.conf
@@ -15,12 +15,12 @@ pre-start script
chown $EPUSER $EPLOGS ||true
chmod 0755 $EPLOGS ||true
chown -R $EPUSER $EPHOME/var ||true
- $EPHOME/src/bin/installDeps.sh >> $EPLOGS/error.log || { stop; exit 1; }
+ $EPHOME/bin/installDeps.sh >> $EPLOGS/error.log || { stop; exit 1; }
end script
script
cd $EPHOME/
- exec su -s /bin/sh -c 'exec "$0" "$@"' $EPUSER -- node src/node/server.js \
+ exec su -s /bin/sh -c 'exec "$0" "$@"' $EPUSER -- node --import tsx src/node/server.ts \
>> $EPLOGS/access.log \
2>> $EPLOGS/error.log
echo "Etherpad is running on http://localhost:9001 - To change settings edit /opt/etherpad/settings.json"
diff --git a/src/bin/debugRun.sh b/bin/debugRun.sh
similarity index 78%
rename from src/bin/debugRun.sh
rename to bin/debugRun.sh
index 2fae42eee..2bfe969c9 100755
--- a/src/bin/debugRun.sh
+++ b/bin/debugRun.sh
@@ -2,18 +2,19 @@
# Move to the Etherpad base directory.
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${MY_DIR}/../.." || exit 1
+cd "${MY_DIR}/.." || exit 1
# Source constants and useful functions
-. src/bin/functions.sh
+. bin/functions.sh
# Prepare the environment
-src/bin/installDeps.sh || exit 1
+bin/installDeps.sh || exit 1
echo "If you are new to debugging Node.js with Chrome DevTools, take a look at this page:"
echo "https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27"
echo "Open 'chrome://inspect' on Chrome to start debugging."
+cd src
# Use 0.0.0.0 to allow external connections to the debugger
# (ex: running Etherpad on a docker container). Use default port # (9229)
-exec node --inspect=0.0.0.0:9229 src/node/server.js "$@"
+exec node --import tsx --inspect=0.0.0.0:9229 ./node/server.ts "$@"
diff --git a/src/bin/deleteAllGroupSessions.js b/bin/deleteAllGroupSessions.ts
similarity index 50%
rename from src/bin/deleteAllGroupSessions.js
rename to bin/deleteAllGroupSessions.ts
index c95bf1075..23d21c594 100644
--- a/src/bin/deleteAllGroupSessions.js
+++ b/bin/deleteAllGroupSessions.ts
@@ -1,5 +1,3 @@
-'use strict';
-
/*
* A tool for deleting ALL GROUP sessions Etherpad user sessions from the CLI,
* because sometimes a brick is required to fix a face.
@@ -7,40 +5,47 @@
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
+import path from "node:path";
+
+import fs from "node:fs";
+import process from "node:process";
+
process.on('unhandledRejection', (err) => { throw err; });
-
-const path = require('path');
-const fs = require('fs');
-const supertest = require('supertest');
-
+import axios from 'axios'
// Set a delete counter which will increment on each delete attempt
// TODO: Check delete is successful before incrementing
let deleteCount = 0;
// get the API Key
-const filePath = path.join(__dirname, '../../APIKEY.txt');
+const filePath = path.join(__dirname, '../APIKEY.txt');
console.log('Deleting all group sessions, please be patient.');
+const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSettings();
(async () => {
- const settings = require('../tests/container/loadSettings').loadSettings();
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
- const api = supertest(`http://${settings.ip}:${settings.port}`);
+ axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
- const apiVersionResponse = await api.get('/api/');
- const apiVersion = apiVersionResponse.body.currentVersion; // 1.12.5
+ const apiVersionResponse = await axios.get('/api/');
+ const apiVersion = apiVersionResponse.data.currentVersion; // 1.12.5
+ console.log('apiVersion', apiVersion);
- const groupsResponse = await api.get(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
- const groups = groupsResponse.body.data.groupIDs; // ['whateverGroupID']
+ const groupsResponse = await axios.get(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
+ const groups = groupsResponse.data.data.groupIDs; // ['whateverGroupID']
for (const groupID of groups) {
const sessionURI = `/api/${apiVersion}/listSessionsOfGroup?apikey=${apikey}&groupID=${groupID}`;
- const sessionsResponse = await api.get(sessionURI);
- const sessions = sessionsResponse.body.data;
+ const sessionsResponse = await axios.get(sessionURI);
+ const sessions = sessionsResponse.data.data;
- for (const sessionID of Object.keys(sessions)) {
+ if(sessions == null) continue;
+
+ for (const [sessionID, val] of Object.entries(sessions)) {
+ if(val == null) continue;
const deleteURI = `/api/${apiVersion}/deleteSession?apikey=${apikey}&sessionID=${sessionID}`;
- await api.post(deleteURI); // delete
- deleteCount++;
+ await axios.post(deleteURI).then(c=>{
+ console.log(c.data)
+ deleteCount++;
+ }); // delete
}
}
console.log(`Deleted ${deleteCount} sessions`);
diff --git a/src/bin/deletePad.js b/bin/deletePad.ts
similarity index 56%
rename from src/bin/deletePad.js
rename to bin/deletePad.ts
index 51ea99639..20037bb33 100644
--- a/src/bin/deletePad.js
+++ b/bin/deletePad.ts
@@ -7,14 +7,17 @@
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
+import path from "node:path";
+
+import fs from "node:fs";
+import process from "node:process";
+import axios from "axios";
+
process.on('unhandledRejection', (err) => { throw err; });
-const settings = require('../tests/container/loadSettings').loadSettings();
-const path = require('path');
-const fs = require('fs');
-const supertest = require('supertest');
+const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSettings();
-const api = supertest(`http://${settings.ip}:${settings.port}`);
+axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
if (process.argv.length !== 3) throw new Error('Use: node deletePad.js $PADID');
@@ -22,17 +25,17 @@ if (process.argv.length !== 3) throw new Error('Use: node deletePad.js $PADID');
const padId = process.argv[2];
// get the API Key
-const filePath = path.join(__dirname, '../../APIKEY.txt');
+const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
(async () => {
- let apiVersion = await api.get('/api/');
- apiVersion = apiVersion.body.currentVersion;
+ let apiVersion = await axios.get('/api/');
+ apiVersion = apiVersion.data.currentVersion;
if (!apiVersion) throw new Error('No version set in API');
// Now we know the latest API version, let's delete pad
const uri = `/api/${apiVersion}/deletePad?apikey=${apikey}&padID=${padId}`;
- const deleteAttempt = await api.post(uri);
- if (deleteAttempt.body.code === 1) throw new Error(`Error deleting pad ${deleteAttempt.body}`);
- console.log('Deleted pad', deleteAttempt.body);
+ const deleteAttempt = await axios.post(uri);
+ if (deleteAttempt.data.code === 1) throw new Error(`Error deleting pad ${deleteAttempt.data}`);
+ console.log('Deleted pad', deleteAttempt.data);
})();
diff --git a/src/bin/extractPadData.js b/bin/extractPadData.ts
similarity index 82%
rename from src/bin/extractPadData.js
rename to bin/extractPadData.ts
index b0d0212d7..3c10a3acc 100644
--- a/src/bin/extractPadData.js
+++ b/bin/extractPadData.ts
@@ -8,10 +8,9 @@
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
+import util from "node:util";
+import process from "node:process";
process.on('unhandledRejection', (err) => { throw err; });
-
-const util = require('util');
-
if (process.argv.length !== 3) throw new Error('Use: node extractPadData.js $PADID');
// get the padID
@@ -19,13 +18,13 @@ const padId = process.argv[2];
(async () => {
// initialize database
- require('../node/utils/Settings');
- const db = require('../node/db/DB');
+ require('ep_etherpad-lite/node/utils/Settings');
+ const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
// load extra modules
const dirtyDB = require('dirty');
- const padManager = require('../node/db/PadManager');
+ const padManager = require('ep_etherpad-lite/node/db/PadManager');
// initialize output database
const dirty = dirtyDB(`${padId}.db`);
@@ -40,7 +39,7 @@ const padId = process.argv[2];
const pad = await padManager.getPad(padId);
// add all authors
- neededDBValues.push(...pad.getAllAuthors().map((author) => `globalAuthor:${author}`));
+ neededDBValues.push(...pad.getAllAuthors().map((author: string) => `globalAuthor:${author}`));
// add all revisions
for (let rev = 0; rev <= pad.head; ++rev) {
diff --git a/src/bin/fastRun.sh b/bin/fastRun.sh
similarity index 85%
rename from src/bin/fastRun.sh
rename to bin/fastRun.sh
index b2703316b..b18469edb 100755
--- a/src/bin/fastRun.sh
+++ b/bin/fastRun.sh
@@ -11,12 +11,12 @@ set -eu
# Move to the Etherpad base directory.
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${MY_DIR}/../.." || exit 1
+cd "${MY_DIR}/.." || exit 1
# Source constants and useful functions
-. src/bin/functions.sh
+. bin/functions.sh
echo "Running directly, without checking/installing dependencies"
# run Etherpad main class
-exec node src/node/server.js "$@"
+exec node --import tsx src/node/server.ts "$@"
diff --git a/src/bin/functions.sh b/bin/functions.sh
similarity index 100%
rename from src/bin/functions.sh
rename to bin/functions.sh
diff --git a/src/bin/importSqlFile.js b/bin/importSqlFile.ts
similarity index 70%
rename from src/bin/importSqlFile.js
rename to bin/importSqlFile.ts
index 148503e8d..23ad9b7d1 100644
--- a/src/bin/importSqlFile.js
+++ b/bin/importSqlFile.ts
@@ -2,20 +2,24 @@
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
+import util from "node:util";
+const fs = require('fs');
+import log4js from 'log4js';
+import readline from 'readline';
+import ueberDB from "ueberdb2";
+
+const settings = require('ep_etherpad-lite/node/utils/Settings');
process.on('unhandledRejection', (err) => { throw err; });
-
-const util = require('util');
-
const startTime = Date.now();
-const log = (str) => {
+const log = (str:string) => {
console.log(`${(Date.now() - startTime) / 1000}\t${str}`);
};
-const unescape = (val) => {
+const unescape = (val: string) => {
// value is a string
- if (val.substr(0, 1) === "'") {
- val = val.substr(0, val.length - 1).substr(1);
+ if (val.substring(0, 1) === "'") {
+ val = val.substring(0, val.length - 1).substring(1);
return val.replace(/\\[0nrbtZ\\'"]/g, (s) => {
switch (s) {
@@ -25,7 +29,7 @@ const unescape = (val) => {
case '\\b': return '\b';
case '\\t': return '\t';
case '\\Z': return '\x1a';
- default: return s.substr(1);
+ default: return s.substring(1);
}
});
}
@@ -46,18 +50,13 @@ const unescape = (val) => {
};
(async () => {
- const fs = require('fs');
- const log4js = require('log4js');
- const readline = require('readline');
- const settings = require('../node/utils/Settings');
- const ueberDB = require('ueberdb2');
const dbWrapperSettings = {
cache: 0,
writeInterval: 100,
json: false, // data is already json encoded
};
- const db = new ueberDB.database( // eslint-disable-line new-cap
+ const db = new ueberDB.Database( // eslint-disable-line new-cap
settings.dbType,
settings.dbSettings,
dbWrapperSettings,
@@ -69,7 +68,8 @@ const unescape = (val) => {
if (!sqlFile) throw new Error('Use: node importSqlFile.js $SQLFILE');
log('initializing db');
- await util.promisify(db.init.bind(db))();
+ const initDb = await util.promisify(db.init.bind(db));
+ await initDb(null);
log('done');
log(`Opening ${sqlFile}...`);
@@ -78,13 +78,14 @@ const unescape = (val) => {
log(`Reading ${sqlFile}...`);
let keyNo = 0;
for await (const l of readline.createInterface({input: stream, crlfDelay: Infinity})) {
- if (l.substr(0, 27) === 'REPLACE INTO store VALUES (') {
+ if (l.substring(0, 27) === 'REPLACE INTO store VALUES (') {
const pos = l.indexOf("', '");
- const key = l.substr(28, pos - 28);
- let value = l.substr(pos + 3);
- value = value.substr(0, value.length - 2);
+ const key = l.substring(28, pos - 28);
+ let value = l.substring(pos + 3);
+ value = value.substring(0, value.length - 2);
console.log(`key: ${key} val: ${value}`);
console.log(`unval: ${unescape(value)}`);
+ // @ts-ignore
db.set(key, unescape(value), null);
keyNo++;
if (keyNo % 1000 === 0) log(` ${keyNo}`);
@@ -94,6 +95,7 @@ const unescape = (val) => {
process.stdout.write('done. waiting for db to finish transaction. ' +
'depended on dbms this may take some time..\n');
- await util.promisify(db.close.bind(db))();
+ const closeDB = util.promisify(db.close.bind(db));
+ await closeDB(null);
log(`finished, imported ${keyNo} keys.`);
})();
diff --git a/src/bin/installDeps.sh b/bin/installDeps.sh
similarity index 60%
rename from src/bin/installDeps.sh
rename to bin/installDeps.sh
index d4f6b1e85..af2b5e30a 100755
--- a/src/bin/installDeps.sh
+++ b/bin/installDeps.sh
@@ -3,22 +3,18 @@
# Move to the Etherpad base directory.
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${MY_DIR}/../.." || exit 1
+cd "${MY_DIR}/.." || exit 1
# Source constants and useful functions
-. src/bin/functions.sh
+. bin/functions.sh
+
+is_cmd pnpm || npm install pnpm -g
+
# Is node installed?
# Not checking io.js, default installation creates a symbolic link to node
is_cmd node || fatal "Please install node.js ( https://nodejs.org )"
-# Is npm installed?
-is_cmd npm || fatal "Please install npm ( https://npmjs.org )"
-
-# Check npm version
-require_minimal_version "npm" "$(get_program_version "npm")" \
- "$REQUIRED_NPM_MAJOR" "$REQUIRED_NPM_MINOR"
-
# Check node version
require_minimal_version "nodejs" "$(get_program_version "node")" \
"$REQUIRED_NODE_MAJOR" "$REQUIRED_NODE_MINOR"
@@ -37,21 +33,13 @@ if [ ! -f "$settings" ]; then
cp settings.json.template "$settings" || exit 1
fi
-
log "Installing dependencies..."
-(mkdir -p node_modules &&
-cd node_modules &&
-{ [ -d ep_etherpad-lite ] || ln -sf ../src ep_etherpad-lite; } &&
-cd ep_etherpad-lite)
-
-cd src
-
if [ -z "${ETHERPAD_PRODUCTION}" ]; then
- log "Installing dev dependencies"
- npm ci --no-optional --omit=optional --include=dev --lockfile-version 1 || exit 1
+ log "Installing dev dependencies with pnpm"
+ pnpm --recursive i || exit 1
else
- log "Installing production dependencies"
- npm ci --no-optional --omit=optional --omit=dev --lockfile-version 1 --production || exit 1
+ log "Installing production dependencies with pnpm"
+ pnpm --recursive i --production || exit 1
fi
# Remove all minified data to force node creating it new
diff --git a/src/bin/installOnWindows.bat b/bin/installOnWindows.bat
similarity index 74%
rename from src/bin/installOnWindows.bat
rename to bin/installOnWindows.bat
index a6700a9e9..5b04919bd 100644
--- a/src/bin/installOnWindows.bat
+++ b/bin/installOnWindows.bat
@@ -1,7 +1,7 @@
@echo off
:: Change directory to etherpad-lite root
-cd /D "%~dp0\..\.."
+cd /D "%~dp0\.."
:: Is node installed?
cmd /C node -e "" || ( echo "Please install node.js ( https://nodejs.org )" && exit /B 1 )
@@ -9,14 +9,20 @@ cmd /C node -e "" || ( echo "Please install node.js ( https://nodejs.org )" && e
echo _
echo Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient.
-mkdir node_modules
-cd /D node_modules
-mklink /D "ep_etherpad-lite" "..\src"
-cd /D "ep_etherpad-lite"
-cmd /C npm ci --legacy-peer-deps || exit /B 1
+:: Install admin ui only if available
+IF EXIST admin (
+ cd /D .\admin
+ dir
+ cmd /C pnpm i || exit /B 1
+ cmd /C pnpm run build || exit /B 1
+ cd /D ..
+)
-cd /D "%~dp0\..\.."
+
+cmd /C pnpm i || exit /B 1
+
+cd /D "%~dp0\.."
echo _
echo Clearing cache...
diff --git a/bin/installPlugins.ts b/bin/installPlugins.ts
new file mode 100644
index 000000000..8a6a71345
--- /dev/null
+++ b/bin/installPlugins.ts
@@ -0,0 +1,53 @@
+'use strict';
+
+import {writeFileSync} from 'fs'
+import {linkInstaller, installedPluginsPath} from "ep_etherpad-lite/static/js/pluginfw/installer";
+import {PackageData} from "ep_etherpad-lite/node/types/PackageInfo";
+
+const pluginsModule = require('ep_etherpad-lite/static/js/pluginfw/plugins');
+if (process.argv.length === 2) {
+ console.error('Expected at least one argument!');
+ process.exit(1);
+}
+
+let plugins = process.argv.slice(2)
+let installFromPath = false;
+
+const thirdOptPlug = plugins[0]
+
+console.log("Third option: ", thirdOptPlug)
+if (thirdOptPlug && thirdOptPlug.includes('path')) {
+ installFromPath = true
+ plugins.splice(plugins.indexOf('--path'), 1);
+}
+
+
+const persistInstalledPlugins = async () => {
+ const plugins:PackageData[] = []
+ const installedPlugins = {plugins: plugins};
+ for (const pkg of Object.values(await pluginsModule.getPackages()) as PackageData[]) {
+ installedPlugins.plugins.push({
+ name: pkg.name,
+ version: pkg.version,
+ });
+ }
+ installedPlugins.plugins = [...new Set(installedPlugins.plugins)];
+ writeFileSync(installedPluginsPath, JSON.stringify(installedPlugins));
+};
+
+async function run() {
+ for (const plugin of plugins) {
+ if(installFromPath) {
+ console.log(`Installing plugin from path: ${plugin}`);
+ await linkInstaller.installFromPath(plugin);
+ continue;
+ }
+ console.log(`Installing plugin from registry: ${plugin}`)
+ await linkInstaller.installPlugin(plugin);
+ }
+}
+
+(async () => {
+ await run();
+ await persistInstalledPlugins();
+})();
diff --git a/src/bin/migrateDirtyDBtoRealDB.js b/bin/migrateDirtyDBtoRealDB.ts
similarity index 65%
rename from src/bin/migrateDirtyDBtoRealDB.js
rename to bin/migrateDirtyDBtoRealDB.ts
index 75f6cc677..144f6e88c 100644
--- a/src/bin/migrateDirtyDBtoRealDB.js
+++ b/bin/migrateDirtyDBtoRealDB.ts
@@ -1,5 +1,11 @@
'use strict';
+import process from 'node:process';
+import ueberDB from "ueberdb2";
+import log4js from 'log4js';
+import util from 'util';
+const settings = require('ep_etherpad-lite/node/utils/Settings');
+
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
@@ -12,17 +18,12 @@ process.on('unhandledRejection', (err) => { throw err; });
// It might be necessary to run the script using more memory:
// `node --max-old-space-size=4096 src/bin/migrateDirtyDBtoRealDB.js`
- const dirtyDb = require('dirty');
- const log4js = require('log4js');
- const settings = require('../node/utils/Settings');
- const ueberDB = require('ueberdb2');
- const util = require('util');
const dbWrapperSettings = {
cache: '0', // The cache slows things down when you're mostly writing.
writeInterval: 0, // Write directly to the database, don't buffer
};
- const db = new ueberDB.database( // eslint-disable-line new-cap
+ const db = new ueberDB.Database( // eslint-disable-line new-cap
settings.dbType,
settings.dbSettings,
dbWrapperSettings,
@@ -30,27 +31,30 @@ process.on('unhandledRejection', (err) => { throw err; });
await db.init();
console.log('Waiting for dirtyDB to parse its file.');
- const dirty = dirtyDb(`${__dirname}/../../var/dirty.db`);
- const length = await new Promise((resolve) => { dirty.once('load', resolve); });
+ const dirty = await new ueberDB.Database('dirty',`${__dirname}/../var/dirty.db`);
+ await dirty.init();
+ const keys = await dirty.findKeys('*', '')
- console.log(`Found ${length} records, processing now.`);
- const p = [];
+ console.log(`Found ${keys.length} records, processing now.`);
+ const p: Promise[] = [];
let numWritten = 0;
- dirty.forEach((key, value) => {
+ for (const key of keys) {
+ let value = await dirty.get(key);
let bcb, wcb;
p.push(new Promise((resolve, reject) => {
- bcb = (err) => { if (err != null) return reject(err); };
- wcb = (err) => {
+ bcb = (err:any) => { if (err != null) return reject(err); };
+ wcb = (err:any) => {
if (err != null) return reject(err);
if (++numWritten % 100 === 0) console.log(`Wrote record ${numWritten} of ${length}`);
resolve();
};
}));
db.set(key, value, bcb, wcb);
- });
+ }
await Promise.all(p);
console.log(`Wrote all ${numWritten} records`);
- await util.promisify(db.close.bind(db))();
+ await db.close(null);
+ await dirty.close(null);
console.log('Finished.');
})();
diff --git a/src/bin/nsis/README.md b/bin/nsis/README.md
similarity index 100%
rename from src/bin/nsis/README.md
rename to bin/nsis/README.md
diff --git a/src/bin/nsis/brand.ico b/bin/nsis/brand.ico
similarity index 100%
rename from src/bin/nsis/brand.ico
rename to bin/nsis/brand.ico
diff --git a/src/bin/nsis/etherpad.nsi b/bin/nsis/etherpad.nsi
similarity index 100%
rename from src/bin/nsis/etherpad.nsi
rename to bin/nsis/etherpad.nsi
diff --git a/bin/package.json b/bin/package.json
new file mode 100644
index 000000000..c00ac1e18
--- /dev/null
+++ b/bin/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "bin",
+ "version": "2.0.0",
+ "description": "",
+ "main": "checkAllPads.js",
+ "directories": {
+ "doc": "doc"
+ },
+ "dependencies": {
+ "axios": "^1.6.8",
+ "ep_etherpad-lite": "workspace:../src",
+ "log4js": "^6.9.1",
+ "semver": "^7.6.0",
+ "tsx": "^4.7.1",
+ "ueberdb2": "^4.2.63"
+ },
+ "devDependencies": {
+ "@types/node": "^20.11.27",
+ "@types/semver": "^7.5.8",
+ "typescript": "^5.4.2"
+ },
+ "scripts": {
+ "checkPad": "node --import tsx checkPad.ts",
+ "checkAllPads": "node --import tsx checkAllPads.ts",
+ "createUserSession": "node --import tsx createUserSession.ts",
+ "deletePad": "node --import tsx deletePad.ts",
+ "repairPad": "node --import tsx repairPad.ts",
+ "release": "node --import tsx release.ts",
+ "deleteAllGroupSessions": "node --import tsx deleteAllGroupSessions.ts",
+ "importSqlFile": "node --import tsx importSqlFile.ts",
+ "migrateDirtyDBtoRealDB": "node --import tsx migrateDirtyDBtoRealDB.ts",
+ "rebuildPad": "node --import tsx rebuildPad.ts",
+ "stalePlugins": "node --import tsx ./plugins/stalePlugins.ts",
+ "checkPlugin": "node --import tsx ./plugins/checkPlugin.ts",
+ "install-plugins": "node --import tsx ./installPlugins.ts"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/src/bin/plugins/README.md b/bin/plugins/README.md
similarity index 100%
rename from src/bin/plugins/README.md
rename to bin/plugins/README.md
diff --git a/src/bin/plugins/checkPlugin.js b/bin/plugins/checkPlugin.ts
old mode 100755
new mode 100644
similarity index 74%
rename from src/bin/plugins/checkPlugin.js
rename to bin/plugins/checkPlugin.ts
index 8f557c3c5..cdd78d791
--- a/src/bin/plugins/checkPlugin.js
+++ b/bin/plugins/checkPlugin.ts
@@ -1,30 +1,32 @@
-'use strict';
-
/*
* Usage -- see README.md
*
- * Normal usage: node src/bin/plugins/checkPlugin.js ep_whatever
- * Auto fix the things it can: node src/bin/plugins/checkPlugin.js ep_whatever autofix
- * Auto fix and commit: node src/bin/plugins/checkPlugin.js ep_whatever autocommit
+ * Normal usage: node bin/plugins/checkPlugin.js ep_whatever
+ * Auto fix the things it can: node bin/plugins/checkPlugin.js ep_whatever autofix
+ * Auto fix and commit: node bin/plugins/checkPlugin.js ep_whatever autocommit
* Auto fix, commit, push and publish to npm (highly dangerous):
- * node src/bin/plugins/checkPlugin.js ep_whatever autopush
+ * node bin/plugins/checkPlugin.js ep_whatever autopush
*/
-const process = require('process');
+import process from 'node:process';
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
-const assert = require('assert').strict;
-const fs = require('fs');
+import {strict as assert} from 'assert';
+import fs from 'node:fs';
const fsp = fs.promises;
-const childProcess = require('child_process');
-const log4js = require('log4js');
-const path = require('path');
+import childProcess from 'node:child_process';
+import log4js from 'log4js';
+import path from 'node:path';
+import semver from "semver";
const logger = log4js.getLogger('checkPlugin');
-
+log4js.configure({
+ appenders: { console: { type: "console" } },
+ categories: { default: { appenders: ["console"], level: "info" } },
+});
(async () => {
// get plugin name & path from user input
const pluginName = process.argv[2];
@@ -32,10 +34,10 @@ const logger = log4js.getLogger('checkPlugin');
if (!pluginName) throw new Error('no plugin name specified');
logger.info(`Checking the plugin: ${pluginName}`);
- const epRootDir = await fsp.realpath(path.join(await fsp.realpath(__dirname), '../../..'));
+ const epRootDir = await fsp.realpath(path.join(await fsp.realpath(__dirname), '../..'));
logger.info(`Etherpad root directory: ${epRootDir}`);
process.chdir(epRootDir);
- const pluginPath = await fsp.realpath(`node_modules/${pluginName}`);
+ const pluginPath = await fsp.realpath(`../${pluginName}`);
logger.info(`Plugin directory: ${pluginPath}`);
const epSrcDir = await fsp.realpath(path.join(epRootDir, 'src'));
@@ -44,23 +46,24 @@ const logger = log4js.getLogger('checkPlugin');
const autoCommit = autoPush || optArgs.includes('autocommit');
const autoFix = autoCommit || optArgs.includes('autofix');
- const execSync = (cmd, opts = {}) => (childProcess.execSync(cmd, {
+ const execSync = (cmd:string, opts = {}) => (childProcess.execSync(cmd, {
cwd: `${pluginPath}/`,
...opts,
}) || '').toString().replace(/\n+$/, '');
- const writePackageJson = async (obj) => {
+ const writePackageJson = async (obj: object) => {
+ console.log("writing package.json",obj)
let s = JSON.stringify(obj, null, 2);
if (s.length && s.slice(s.length - 1) !== '\n') s += '\n';
return await fsp.writeFile(`${pluginPath}/package.json`, s);
};
- const checkEntries = (got, want) => {
+ const checkEntries = (got: any, want:any) => {
let changed = false;
for (const [key, val] of Object.entries(want)) {
try {
assert.deepEqual(got[key], val);
- } catch (err) {
+ } catch (err:any) {
logger.warn(`${key} possibly outdated.`);
logger.warn(err.message);
if (autoFix) {
@@ -72,9 +75,22 @@ const logger = log4js.getLogger('checkPlugin');
return changed;
};
- const updateDeps = async (parsedPackageJson, key, wantDeps) => {
+ const updateDeps = async (parsedPackageJson: any, key: string, wantDeps: {
+ [key: string]: string | {ver?: string, overwrite?: boolean}|null
+ }|string) => {
const {[key]: deps = {}} = parsedPackageJson;
let changed = false;
+
+ if (typeof wantDeps === 'string') {
+ if (deps !== wantDeps) {
+ logger.warn(`Dependency mismatch in ${key}: '${wantDeps}' (current: ${deps})`);
+ if (autoFix) {
+ parsedPackageJson[key] = wantDeps;
+ await writePackageJson(parsedPackageJson);
+ }
+ }
+ return;
+ }
for (const [pkg, verInfo] of Object.entries(wantDeps)) {
const {ver, overwrite = true} =
typeof verInfo === 'string' || verInfo == null ? {ver: verInfo} : verInfo;
@@ -99,7 +115,12 @@ const logger = log4js.getLogger('checkPlugin');
const prepareRepo = () => {
const modified = execSync('git diff-files --name-status');
- if (modified !== '') throw new Error(`working directory has modifications:\n${modified}`);
+ if (modified !== '') {
+ logger.warn('working directory has modifications');
+ if (autoFix)
+ execSync('git stash', {stdio: 'inherit'})
+ //throw new Error(`working directory has modifications:\n${modified}`);
+ }
const untracked = execSync('git ls-files -o --exclude-standard');
if (untracked !== '') throw new Error(`working directory has untracked files:\n${untracked}`);
const indexStatus = execSync('git diff-index --cached --name-status HEAD');
@@ -109,20 +130,20 @@ const logger = log4js.getLogger('checkPlugin');
br = execSync('git symbolic-ref HEAD');
if (!br.startsWith('refs/heads/')) throw new Error('detached HEAD');
br = br.replace(/^refs\/heads\//, '');
- execSync('git rev-parse --verify -q HEAD^0 || ' +
+ execSync('git rev-parse --verify -q HEAD || ' +
`{ echo "Error: no commits on ${br}" >&2; exit 1; }`);
execSync('git config --get user.name');
execSync('git config --get user.email');
}
if (autoPush) {
- if (!['master', 'main'].includes(br)) throw new Error('master/main not checked out');
+ if (!['master', 'main'].includes(br!)) throw new Error('master/main not checked out');
execSync('git rev-parse --verify @{u}');
execSync('git pull --ff-only', {stdio: 'inherit'});
if (execSync('git rev-list @{u}...') !== '') throw new Error('repo contains unpushed commits');
}
};
- const checkFile = async (srcFn, dstFn, overwrite = true) => {
+ const checkFile = async (srcFn: string, dstFn:string, overwrite = true) => {
const outFn = path.join(pluginPath, dstFn);
const wantContents = await fsp.readFile(srcFn, {encoding: 'utf8'});
let gotContents = null;
@@ -131,7 +152,7 @@ const logger = log4js.getLogger('checkPlugin');
} catch (err) { /* treat as if the file doesn't exist */ }
try {
assert.equal(gotContents, wantContents);
- } catch (err) {
+ } catch (err:any) {
logger.warn(`File ${dstFn} does not match the default`);
logger.warn(err.message);
if (!overwrite && gotContents != null) {
@@ -157,11 +178,11 @@ const logger = log4js.getLogger('checkPlugin');
if (!files.includes('.git')) throw new Error('No .git folder, aborting');
prepareRepo();
- const workflows = ['backend-tests.yml', 'frontend-tests.yml', 'npmpublish.yml'];
+ const workflows = ['backend-tests.yml', 'frontend-tests.yml', 'npmpublish.yml', 'test-and-release.yml'];
await Promise.all(workflows.map(async (fn) => {
- await checkFile(`src/bin/plugins/lib/${fn}`, `.github/workflows/${fn}`);
+ await checkFile(`bin/plugins/lib/${fn}`, `.github/workflows/${fn}`);
}));
- await checkFile('src/bin/plugins/lib/dependabot.yml', '.github/dependabot.yml');
+ await checkFile('bin/plugins/lib/dependabot.yml', '.github/dependabot.yml');
if (!files.includes('package.json')) {
logger.warn('no package.json, please create');
@@ -171,10 +192,10 @@ const logger = log4js.getLogger('checkPlugin');
const parsedPackageJSON = JSON.parse(packageJSON);
await updateDeps(parsedPackageJSON, 'devDependencies', {
- 'eslint': '^8.14.0',
- 'eslint-config-etherpad': '^3.0.13',
+ 'eslint': '^8.57.0',
+ 'eslint-config-etherpad': '^4.0.4',
// Changing the TypeScript version can break plugin code, so leave it alone if present.
- 'typescript': {ver: '^4.6.4', overwrite: false},
+ 'typescript': {ver: '^5.4.2', overwrite: true},
// These were moved to eslint-config-etherpad's dependencies so they can be removed:
'@typescript-eslint/eslint-plugin': null,
'@typescript-eslint/parser': null,
@@ -189,13 +210,26 @@ const logger = log4js.getLogger('checkPlugin');
'eslint-plugin-you-dont-need-lodash-underscore': null,
});
+ const currentVersion = semver.parse(parsedPackageJSON.version)!;
+ const newVersion = currentVersion.inc('patch');
+
+ await updateDeps(parsedPackageJSON, 'version', newVersion.version)
+
+
await updateDeps(parsedPackageJSON, 'peerDependencies', {
- // Some plugins require a newer version of Etherpad so don't overwrite if already set.
- 'ep_etherpad-lite': {ver: '>=1.8.6', overwrite: false},
+ // These were moved to eslint-config-etherpad's dependencies so they can be removed:
+ 'ep_etherpad-lite': null,
});
+ /*await updateDeps(parsedPackageJSON, 'peerDependencies', {
+ // Some plugins require a newer version of Etherpad so don't overwrite if already set.
+ 'ep_etherpad-lite': {ver: '>=1.8.6', overwrite: false},
+ });*/
+
+ delete parsedPackageJSON.peerDependencies;
+
await updateDeps(parsedPackageJSON, 'engines', {
- node: '>=12.17.0',
+ node: '>=18.0.0',
});
if (parsedPackageJSON.eslintConfig != null && autoFix) {
@@ -214,7 +248,7 @@ const logger = log4js.getLogger('checkPlugin');
logger.error(`both ${from} and ${to} exist; delete ${from}`);
}
} else {
- checkFile('src/bin/plugins/lib/eslintrc.cjs', '.eslintrc.cjs', false);
+ await checkFile('bin/plugins/lib/eslintrc.cjs', '.eslintrc.cjs', false);
}
if (checkEntries(parsedPackageJSON, {
@@ -228,21 +262,34 @@ const logger = log4js.getLogger('checkPlugin');
if (checkEntries(parsedPackageJSON.scripts, {
'lint': 'eslint .',
'lint:fix': 'eslint --fix .',
- })) await writePackageJson(parsedPackageJSON);
+ }))
+ await writePackageJson(parsedPackageJSON);
}
- if (!files.includes('package-lock.json')) {
- logger.warn('package-lock.json not found');
+ if (!files.includes('pnpm-lock.yaml')) {
+ logger.warn('pnpm-lock.yaml not found');
if (!autoFix) {
- logger.warn('Run npm install in the plugin folder and commit the package-lock.json file.');
+ logger.warn('Run pnpm install in the plugin folder and commit the package-lock.json file.');
+ } else {
+ logger.info('Autofixing missing package-lock.json file');
+ try {
+ fs.statfsSync(`${pluginPath}/package-lock.json`)
+ fs.rmSync(`${pluginPath}/package-lock.json`)
+ } catch (e) {
+ // Nothing to do
+ }
+ execSync('pnpm install', {
+ cwd: `${pluginPath}/`,
+ stdio: 'inherit',
+ });
}
}
- const fillTemplate = async (templateFilename, outputFilename) => {
+ const fillTemplate = async (templateFilename: string, outputFilename: string) => {
const contents = (await fsp.readFile(templateFilename, 'utf8'))
.replace(/\[name of copyright owner\]/g, execSync('git config user.name'))
.replace(/\[plugin_name\]/g, pluginName)
- .replace(/\[yyyy\]/g, new Date().getFullYear());
+ .replace(/\[yyyy\]/g, new Date().getFullYear().toString());
await fsp.writeFile(outputFilename, contents);
};
@@ -251,7 +298,7 @@ const logger = log4js.getLogger('checkPlugin');
if (autoFix) {
logger.info('Autofixing missing README.md file');
logger.info('please edit the README.md file further to include plugin specific details.');
- await fillTemplate('src/bin/plugins/lib/README.md', `${pluginPath}/README.md`);
+ await fillTemplate('bin/plugins/lib/README.md', `${pluginPath}/README.md`);
}
}
@@ -260,7 +307,7 @@ const logger = log4js.getLogger('checkPlugin');
if (autoFix) {
logger.info('Autofixing missing CONTRIBUTING.md file, please edit the CONTRIBUTING.md ' +
'file further to include plugin specific details.');
- await fillTemplate('src/bin/plugins/lib/CONTRIBUTING.md', `${pluginPath}/CONTRIBUTING.md`);
+ await fillTemplate('bin/plugins/lib/CONTRIBUTING.md', `${pluginPath}/CONTRIBUTING.md`);
}
}
@@ -296,7 +343,7 @@ const logger = log4js.getLogger('checkPlugin');
logger.warn('LICENSE file not found, please create');
if (autoFix) {
logger.info('Autofixing missing LICENSE file (Apache 2.0).');
- await fsp.copyFile('src/bin/plugins/lib/LICENSE', `${pluginPath}/LICENSE`);
+ await fsp.copyFile('bin/plugins/lib/LICENSE', `${pluginPath}/LICENSE`);
}
}
@@ -306,7 +353,7 @@ const logger = log4js.getLogger('checkPlugin');
if (autoFix) {
logger.info('Autofixing missing .gitignore file');
const gitignore =
- await fsp.readFile('src/bin/plugins/lib/gitignore', {encoding: 'utf8', flag: 'r'});
+ await fsp.readFile('bin/plugins/lib/gitignore', {encoding: 'utf8', flag: 'r'});
await fsp.writeFile(`${pluginPath}/.gitignore`, gitignore);
}
} else {
@@ -358,50 +405,62 @@ const logger = log4js.getLogger('checkPlugin');
// Install dependencies so we can run ESLint. This should also create or update package-lock.json
// if autoFix is enabled.
- const npmInstall = `npm install${autoFix ? '' : ' --no-package-lock'}`;
+ const npmInstall = `pnpm install`;
execSync(npmInstall, {stdio: 'inherit'});
// Create the ep_etherpad-lite symlink if necessary. This must be done after running `npm install`
// because that command nukes the symlink.
- try {
+ /*try {
const d = await fsp.realpath(path.join(pluginPath, 'node_modules/ep_etherpad-lite'));
assert.equal(d, epSrcDir);
} catch (err) {
execSync(`${npmInstall} --no-save ep_etherpad-lite@file:${epSrcDir}`, {stdio: 'inherit'});
- }
+ }*/
// linting begins
try {
logger.info('Linting...');
- const lintCmd = autoFix ? 'npx eslint --fix .' : 'npx eslint';
+ const lintCmd = autoFix ? 'pnpm exec eslint --fix .' : 'npx eslint';
execSync(lintCmd, {stdio: 'inherit'});
} catch (e) {
// it is gonna throw an error anyway
- logger.info('Manual linting probably required, check with: npm run lint');
+ logger.info('Manual linting probably required, check with: pnpm run lint');
}
// linting ends.
if (autoFix) {
- const unchanged = JSON.parse(execSync(
+ /*const unchanged = JSON.parse(execSync(
'untracked=$(git ls-files -o --exclude-standard) || exit 1; ' +
- 'git diff-files --quiet && [ -z "$untracked" ] && echo true || echo false'));
- if (!unchanged) {
+ 'git diff-files --quiet && [ -z "$untracked" ] && echo true || echo false'));*/
+
+ if (true) {
// Display a diff of changes. Git doesn't diff untracked files, so they must be added to the
// index. Use a temporary index file to avoid modifying Git's default index file.
- execSync('git read-tree HEAD; git add -A && git diff-index -p --cached HEAD && echo ""', {
+ execSync('git read-tree HEAD', {
env: {...process.env, GIT_INDEX_FILE: '.git/checkPlugin.index'},
stdio: 'inherit',
});
+ execSync('git add -A', {
+ env: {...process.env, GIT_INDEX_FILE: '.git/checkPlugin.index'},
+ stdio: 'inherit',
+ });
+ execSync('git diff-index -p --cached HEAD', {
+ env: {...process.env, GIT_INDEX_FILE: '.git/checkPlugin.index'},
+ stdio: 'inherit',
+ });
+
await fsp.unlink(`${pluginPath}/.git/checkPlugin.index`);
const commitCmd = [
'git add -A',
'git commit -m "autofixes from Etherpad checkPlugin.js"',
- ].join(' && ');
+ ]
+
if (autoCommit) {
logger.info('Committing changes...');
- execSync(commitCmd, {stdio: 'inherit'});
+ execSync(commitCmd[0], {stdio: 'inherit'});
+ execSync(commitCmd[1], {stdio: 'inherit'});
} else {
logger.info('Fixes applied. Check the above git diff then run the following command:');
- logger.info(`(cd node_modules/${pluginName} && ${commitCmd})`);
+ logger.info(`(cd node_modules/${pluginName} && ${commitCmd.join(' && ')})`);
}
const pushCmd = 'git push';
if (autoPush) {
diff --git a/src/bin/plugins/getCorePlugins.sh b/bin/plugins/getCorePlugins.sh
similarity index 54%
rename from src/bin/plugins/getCorePlugins.sh
rename to bin/plugins/getCorePlugins.sh
index 85552ab14..737a1881b 100755
--- a/src/bin/plugins/getCorePlugins.sh
+++ b/bin/plugins/getCorePlugins.sh
@@ -11,29 +11,31 @@ error () { log "ERROR: $@" >&2; }
fatal () { error "$@"; exit 1; }
mydir=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${mydir}/../../.."
+cd "${mydir}/../.."
pdir=$(cd .. && pwd -P) || exit 1
plugins=$("${mydir}/listOfficialPlugins") || exit 1
+echo $plugins
for d in ${plugins}; do
+ echo $d
log "============================================================"
log "${d}"
log "============================================================"
fd=${pdir}/${d}
- repo=git@github.com:ether/${plugin}.git
+ repo=https://github.com/ether/${d}.git
[ -d "${fd}" ] || {
log "Cloning ${repo} to ${fd}..."
- (cd "${pdir}" && git clone "${repo}" "${d}") || exit 1
+ (cd "${pdir}" && git clone "${repo}" "${d}") || continue
} || exit 1
log "Fetching latest commits..."
(cd "${fd}" && git pull --ff-only) || exit 1
- log "Getting plugin name..."
- pn=$(cd "${fd}" && npx -c 'printf %s\\n "${npm_package_name}"') || exit 1
- [ -n "${pn}" ] || fatal "Unable to determine plugin name for ${d}"
- md=node_modules/${pn}
- [ -d "${md}" ] || {
- log "Installing plugin to ${md}..."
- ln -s ../../"${d}" "${md}"
- } || exit 1
- [ "${md}" -ef "${fd}" ] || fatal "${md} is not a symlink to ${fd}"
+ #log "Getting plugin name..."
+ #pn=$(cd "${fd}" && npx -c 'printf %s\\n "${npm_package_name}"') || exit 1
+ #[ -n "${pn}" ] || fatal "Unable to determine plugin name for ${d}"
+ #md=node_modules/${pn}
+ #[ -d "${md}" ] || {
+ # log "Installing plugin to ${md}..."
+ # ln -s ../../"${d}" "${md}"
+ #} || exit 1
+ #[ "${md}" -ef "${fd}" ] || fatal "${md} is not a symlink to ${fd}"
done
diff --git a/src/bin/plugins/lib/CONTRIBUTING.md b/bin/plugins/lib/CONTRIBUTING.md
similarity index 100%
rename from src/bin/plugins/lib/CONTRIBUTING.md
rename to bin/plugins/lib/CONTRIBUTING.md
diff --git a/src/bin/plugins/lib/LICENSE b/bin/plugins/lib/LICENSE
similarity index 100%
rename from src/bin/plugins/lib/LICENSE
rename to bin/plugins/lib/LICENSE
diff --git a/src/bin/plugins/lib/README.md b/bin/plugins/lib/README.md
similarity index 98%
rename from src/bin/plugins/lib/README.md
rename to bin/plugins/lib/README.md
index 2c50b538e..a3d0dc458 100755
--- a/src/bin/plugins/lib/README.md
+++ b/bin/plugins/lib/README.md
@@ -25,7 +25,7 @@ TODO
To run the backend tests, run the following from the Etherpad working directory:
```shell
-(cd src && npm test)
+(cd src && pnpm test)
```
To run the frontend tests, visit: http://localhost:9001/tests/frontend/
diff --git a/bin/plugins/lib/backend-tests.yml b/bin/plugins/lib/backend-tests.yml
new file mode 100644
index 000000000..7b69a00cd
--- /dev/null
+++ b/bin/plugins/lib/backend-tests.yml
@@ -0,0 +1,92 @@
+name: Backend Tests
+
+# any branch is useful for testing before a PR is submitted
+on:
+ workflow_call:
+
+jobs:
+ withplugins:
+ # run on pushes to any branch
+ # run on PRs from external forks
+ if: |
+ (github.event_name != 'pull_request')
+ || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id)
+ name: with Plugins
+ runs-on: ubuntu-latest
+ steps:
+ -
+ name: Install libreoffice
+ uses: awalsh128/cache-apt-pkgs-action@v1.4.2
+ with:
+ packages: libreoffice libreoffice-pdfimport
+ version: 1.0
+ -
+ name: Install etherpad core
+ uses: actions/checkout@v3
+ with:
+ repository: ether/etherpad-lite
+ path: etherpad-lite
+ - 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: Checkout plugin repository
+ uses: actions/checkout@v3
+ with:
+ path: plugin
+ -
+ name: Determine plugin name
+ id: plugin_name
+ working-directory: ./plugin
+ run: |
+ npx -c 'printf %s\\n "::set-output name=plugin_name::${npm_package_name}"'
+ -
+ name: Link plugin directory
+ working-directory: ./plugin
+ run: |
+ pnpm link --global
+ - name: Remove tests
+ working-directory: ./etherpad-lite
+ run: rm -rf ./src/tests/backend/specs
+ -
+ name: Install Etherpad core dependencies
+ working-directory: ./etherpad-lite
+ run: bin/installDeps.sh
+ - name: Link plugin to etherpad-lite
+ working-directory: ./etherpad-lite
+ run: |
+ pnpm link --global $PLUGIN_NAME
+ pnpm run install-plugins --path ../../plugin
+ env:
+ PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
+ - name: Link ep_etherpad-lite
+ working-directory: ./etherpad-lite/src
+ run: |
+ pnpm link --global
+ - name: Link etherpad to plugin
+ working-directory: ./plugin
+ run: |
+ pnpm link --global ep_etherpad-lite
+ -
+ name: Run the backend tests
+ working-directory: ./etherpad-lite
+ run: |
+ res=$(find .. -path "./node_modules/ep_*/static/tests/backend/specs/**" | wc -l)
+ if [ $res -eq 0 ]; then
+ echo "No backend tests found"
+ else
+ pnpm run test
+ fi
diff --git a/src/bin/plugins/lib/dependabot.yml b/bin/plugins/lib/dependabot.yml
similarity index 100%
rename from src/bin/plugins/lib/dependabot.yml
rename to bin/plugins/lib/dependabot.yml
diff --git a/src/bin/plugins/lib/eslintrc.cjs b/bin/plugins/lib/eslintrc.cjs
similarity index 100%
rename from src/bin/plugins/lib/eslintrc.cjs
rename to bin/plugins/lib/eslintrc.cjs
diff --git a/src/bin/plugins/lib/backend-tests.yml b/bin/plugins/lib/frontend-tests.yml
similarity index 50%
rename from src/bin/plugins/lib/backend-tests.yml
rename to bin/plugins/lib/frontend-tests.yml
index b80f57019..d1eaf8701 100644
--- a/src/bin/plugins/lib/backend-tests.yml
+++ b/bin/plugins/lib/frontend-tests.yml
@@ -1,35 +1,47 @@
-name: "Backend tests"
+# Publicly credit Sauce Labs because they generously support open source
+# projects.
+name: Frontend Tests
-# any branch is useful for testing before a PR is submitted
-on: [push, pull_request]
+on:
+ workflow_call:
jobs:
- withplugins:
- # run on pushes to any branch
- # run on PRs from external forks
- if: |
- (github.event_name != 'pull_request')
- || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id)
- name: with Plugins
+ test-frontend:
runs-on: ubuntu-latest
steps:
-
- name: Install libreoffice
- run: |
- sudo add-apt-repository -y ppa:libreoffice/ppa
- sudo apt update
- sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport
- -
- name: Install etherpad core
+ name: Check out Etherpad core
uses: actions/checkout@v3
with:
repository: ether/etherpad-lite
+ - 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: Checkout plugin repository
+ name: Check out the plugin
uses: actions/checkout@v3
with:
path: ./node_modules/__tmp
+ -
+ name: export GIT_HASH to env
+ id: environment
+ run: |
+ cd ./node_modules/__tmp
+ echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})"
-
name: Determine plugin name
id: plugin_name
@@ -38,26 +50,17 @@ jobs:
npx -c 'printf %s\\n "::set-output name=plugin_name::${npm_package_name}"'
-
name: Rename plugin directory
+ env:
+ PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
run: |
mv ./node_modules/__tmp ./node_modules/"${PLUGIN_NAME}"
- env:
- PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
- -
- uses: actions/setup-node@v3
- with:
- node-version: 12
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
- node_modules/${{ steps.plugin_name.outputs.plugin_name }}/package-lock.json
-
name: Install plugin dependencies
- run: |
- cd ./node_modules/"${PLUGIN_NAME}"
- npm ci
env:
PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
+ run: |
+ cd ./node_modules/"${PLUGIN_NAME}"
+ pnpm i
# 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
@@ -69,7 +72,23 @@ jobs:
# rules.
-
name: Install Etherpad core dependencies
- run: src/bin/installDeps.sh
- -
- name: Run the backend tests
- run: cd src && npm test
+ run: bin/installDeps.sh
+ - 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
diff --git a/src/bin/plugins/lib/gitignore b/bin/plugins/lib/gitignore
similarity index 100%
rename from src/bin/plugins/lib/gitignore
rename to bin/plugins/lib/gitignore
diff --git a/bin/plugins/lib/npmpublish.yml b/bin/plugins/lib/npmpublish.yml
new file mode 100644
index 000000000..6c2712cc4
--- /dev/null
+++ b/bin/plugins/lib/npmpublish.yml
@@ -0,0 +1,74 @@
+# This workflow will run tests using node and then publish a package to the npm registry when a release is created
+# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
+
+name: Node.js Package
+
+on:
+ workflow_call:
+
+jobs:
+ publish-npm:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ registry-url: https://registry.npmjs.org/
+ - name: Check out Etherpad core
+ uses: actions/checkout@v3
+ with:
+ repository: ether/etherpad-lite
+ - 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-
+ -
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ -
+ name: Bump version (patch)
+ run: |
+ LATEST_TAG=$(git describe --tags --abbrev=0) || exit 1
+ NEW_COMMITS=$(git rev-list --count "${LATEST_TAG}"..) || exit 1
+ [ "${NEW_COMMITS}" -gt 0 ] || exit 0
+ git config user.name 'github-actions[bot]'
+ git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
+ pnpm i
+ pnpm version patch
+ git push --follow-tags
+ # This is required if the package has a prepare script that uses something
+ # in dependencies or devDependencies.
+ -
+ run: pnpm i
+ # `npm publish` must come after `git push` otherwise there is a race
+ # condition: If two PRs are merged back-to-back then master/main will be
+ # updated with the commits from the second PR before the first PR's
+ # workflow has a chance to push the commit generated by `npm version
+ # patch`. This causes the first PR's `git push` step to fail after the
+ # package has already been published, which in turn will cause all future
+ # workflow runs to fail because they will all attempt to use the same
+ # already-used version number. By running `npm publish` after `git push`,
+ # back-to-back merges will cause the first merge's workflow to fail but
+ # the second's will succeed.
+ -
+ run: pnpm publish
+ env:
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
+ #-
+ # name: Add package to etherpad organization
+ # run: pnpm access grant read-write etherpad:developers
+ # env:
+ # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
diff --git a/bin/plugins/lib/test-and-release.yml b/bin/plugins/lib/test-and-release.yml
new file mode 100644
index 000000000..22d1c5ea5
--- /dev/null
+++ b/bin/plugins/lib/test-and-release.yml
@@ -0,0 +1,18 @@
+name: Node.js Package
+on: [push]
+
+
+jobs:
+ backend:
+ uses: ./.github/workflows/backend-tests.yml
+ secrets: inherit
+ frontend:
+ uses: ./.github/workflows/frontend-tests.yml
+ secrets: inherit
+ release:
+ if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
+ needs:
+ - backend
+ - frontend
+ uses: ./.github/workflows/npmpublish.yml
+ secrets: inherit
diff --git a/src/bin/plugins/listOfficialPlugins b/bin/plugins/listOfficialPlugins
similarity index 95%
rename from src/bin/plugins/listOfficialPlugins
rename to bin/plugins/listOfficialPlugins
index 322ad5d3b..61cd8b0ce 100755
--- a/src/bin/plugins/listOfficialPlugins
+++ b/bin/plugins/listOfficialPlugins
@@ -3,7 +3,7 @@ set -e
newline='
'
mydir=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${mydir}/../../.."
+cd "${mydir}/../.."
pdir=$(cd .. && pwd -P) || exit 1
plugins=
for p in "" "&page=2" "&page=3"; do
diff --git a/src/bin/plugins/reTestAllPlugins.sh b/bin/plugins/reTestAllPlugins.sh
similarity index 84%
rename from src/bin/plugins/reTestAllPlugins.sh
rename to bin/plugins/reTestAllPlugins.sh
index abe1bca80..ccabd1bc6 100755
--- a/src/bin/plugins/reTestAllPlugins.sh
+++ b/bin/plugins/reTestAllPlugins.sh
@@ -4,7 +4,7 @@ do
echo $dir
if [[ $dir == *"ep_"* ]]; then
if [[ $dir != "ep_etherpad-lite" ]]; then
- # node src/bin/plugins/checkPlugin.js $dir autopush
+ # node bin/plugins/checkPlugin.js $dir autopush
cd node_modules/$dir
git commit -m "Automatic update: bump update to re-run latest Etherpad tests" --allow-empty
git push origin master
diff --git a/bin/plugins/stalePlugins.ts b/bin/plugins/stalePlugins.ts
new file mode 100644
index 000000000..ce2b876ed
--- /dev/null
+++ b/bin/plugins/stalePlugins.ts
@@ -0,0 +1,22 @@
+'use strict';
+
+// Returns a list of stale plugins and their authors email
+
+import axios from 'axios'
+const currentTime = new Date();
+
+(async () => {
+ const res = await axios.get('https://static.etherpad.org/plugins.full.json');
+ for (const plugin of Object.keys(res.data)) {
+ // @ts-ignore
+ const name = res.data[plugin].data.name;
+ // @ts-ignore
+ const date = new Date(res.data[plugin].time);
+ const diffTime = Math.abs(currentTime.getTime() - date.getTime());
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+ if (diffDays > (365 * 2)) {
+ // @ts-ignore
+ console.log(`${name}, ${res.data[plugin].data.maintainers[0].email}`);
+ }
+ }
+})();
diff --git a/src/bin/plugins/updateAllPluginsScript.sh b/bin/plugins/updateAllPluginsScript.sh
similarity index 93%
rename from src/bin/plugins/updateAllPluginsScript.sh
rename to bin/plugins/updateAllPluginsScript.sh
index 79be4bc47..0605f1740 100755
--- a/src/bin/plugins/updateAllPluginsScript.sh
+++ b/bin/plugins/updateAllPluginsScript.sh
@@ -10,7 +10,7 @@ do
# echo $0
if [[ $dir == *"ep_"* ]]; then
if [[ $dir != "ep_etherpad-lite" ]]; then
- node src/bin/plugins/checkPlugin.js $dir autopush
+ pnpm run checkPlugins $dir autopush
fi
fi
# echo $dir
diff --git a/src/bin/plugins/updateCorePlugins.sh b/bin/plugins/updateCorePlugins.sh
similarity index 70%
rename from src/bin/plugins/updateCorePlugins.sh
rename to bin/plugins/updateCorePlugins.sh
index 3866b8444..4c5805e23 100755
--- a/src/bin/plugins/updateCorePlugins.sh
+++ b/bin/plugins/updateCorePlugins.sh
@@ -5,5 +5,5 @@ set -e
for dir in node_modules/ep_*; do
dir=${dir#node_modules/}
[ "$dir" != ep_etherpad-lite ] || continue
- node src/bin/plugins/checkPlugin.js "$dir" autopush
+ pnpm run checkPlugins "$dir" autopush
done
diff --git a/src/bin/push-after-release.sh b/bin/push-after-release.sh
old mode 100644
new mode 100755
similarity index 82%
rename from src/bin/push-after-release.sh
rename to bin/push-after-release.sh
index 8781196fb..b426f630b
--- a/src/bin/push-after-release.sh
+++ b/bin/push-after-release.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# Specify the path to your package.json file
-PACKAGE_JSON_PATH="./src//package.json"
+PACKAGE_JSON_PATH="./src/package.json"
# Check if the file exists
if [ ! -f "$PACKAGE_JSON_PATH" ]; then
diff --git a/src/bin/rebuildPad.js b/bin/rebuildPad.ts
similarity index 86%
rename from src/bin/rebuildPad.js
rename to bin/rebuildPad.ts
index 73f530889..0d77940c0 100644
--- a/src/bin/rebuildPad.js
+++ b/bin/rebuildPad.ts
@@ -10,19 +10,20 @@
process.on('unhandledRejection', (err) => { throw err; });
if (process.argv.length !== 4 && process.argv.length !== 5) {
- throw new Error('Use: node src/bin/repairPad.js $PADID $REV [$NEWPADID]');
+ throw new Error('Use: node bin/repairPad.js $PADID $REV [$NEWPADID]');
}
+// @ts-ignore
const padId = process.argv[2];
-const newRevHead = process.argv[3];
+const newRevHead = Number(process.argv[3]);
const newPadId = process.argv[4] || `${padId}-rebuilt`;
(async () => {
- const db = require('../node/db/DB');
+ const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
- const PadManager = require('../node/db/PadManager');
- const Pad = require('../node/db/Pad').Pad;
+ const PadManager = require('ep_etherpad-lite/node/db/PadManager');
+ const Pad = require('ep_etherpad-lite/node/db/Pad').Pad;
// Validate the newPadId if specified and that a pad with that ID does
// not already exist to avoid overwriting it.
if (!PadManager.isValidPadId(newPadId)) {
@@ -43,8 +44,8 @@ const newPadId = process.argv[4] || `${padId}-rebuilt`;
}));
// Rebuild Pad from revisions up to and including the new revision head
- const AuthorManager = require('../node/db/AuthorManager');
- const Changeset = require('../static/js/Changeset');
+ const AuthorManager = require('ep_etherpad-lite/node/db/AuthorManager');
+ const Changeset = require('ep_etherpad-lite/static/js/Changeset');
// Author attributes are derived from changesets, but there can also be
// non-author attributes with specific mappings that changesets depend on
// and, AFAICT, cannot be recreated any other way
diff --git a/src/bin/release.js b/bin/release.ts
similarity index 74%
rename from src/bin/release.js
rename to bin/release.ts
index ee5f245e7..24c5a7d8b 100644
--- a/src/bin/release.js
+++ b/bin/release.ts
@@ -2,14 +2,17 @@
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
+
+import process from 'node:process'
+
process.on('unhandledRejection', (err) => { throw err; });
-const fs = require('fs');
-const childProcess = require('child_process');
-const log4js = require('log4js');
-const path = require('path');
-const semver = require('semver');
-const {exec} = require('child_process');
+import fs from 'node:fs';
+import childProcess from 'node:child_process';
+import log4js from 'log4js';
+import path from 'node:path';
+import semver from 'semver';
+import {exec} from 'node:child_process';
log4js.configure({appenders: {console: {type: 'console'}},
categories: {
@@ -20,11 +23,11 @@ log4js.configure({appenders: {console: {type: 'console'}},
Usage
-node src/bin/release.js patch
+node bin/release.js patch
*/
const usage =
- 'node src/bin/release.js [patch/minor/major] -- example: "node src/bin/release.js patch"';
+ 'node bin/release.js [patch/minor/major] -- example: "node bin/release.js patch"';
const release = process.argv[2];
@@ -33,33 +36,45 @@ if (!release) {
throw new Error('No release type included');
}
-const cwd = path.join(fs.realpathSync(__dirname), '../../');
+if (release !== 'patch' && release !== 'minor' && release !== 'major') {
+ console.log(usage);
+ throw new Error('Invalid release type');
+}
+
+
+const cwd = path.join(fs.realpathSync(__dirname), '../');
process.chdir(cwd);
// Run command capturing stdout. Trailing newlines are stripped (like the shell does).
const runc =
- (cmd, opts = {}) => childProcess.execSync(cmd, {encoding: 'utf8', ...opts}).replace(/\n+$/, '');
+ (cmd:string, opts = {}) => childProcess.execSync(cmd, {encoding: 'utf8', ...opts}).replace(/\n+$/, '').trim();
// Run command without capturing stdout.
-const run = (cmd, opts = {}) => childProcess.execSync(cmd, {stdio: 'inherit', ...opts});
+const run = (cmd: string, opts = {}) => childProcess.execSync(cmd, {stdio: 'inherit', ...opts});
-const readJson = (filename) => JSON.parse(fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'}));
-const writeJson = (filename, obj) => {
- let json = JSON.stringify(obj, null, 2);
- if (json !== '' && !json.endsWith('\n')) json += '\n';
- fs.writeFileSync(filename, json);
-};
+const readJson = (filename: string) => JSON.parse(fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'}));
-const assertWorkDirClean = (opts = {}) => {
+const assertWorkDirClean = (opts:{
+ cwd?: string;
+} = {}) => {
opts.cwd = runc('git rev-parse --show-cdup', opts) || cwd;
const m = runc('git diff-files --name-status', opts);
- if (m !== '') throw new Error(`modifications in working directory ${opts.cwd}:\n${m}`);
+ console.log(">"+m.trim()+"<")
+ if (m.length !== 0) {
+ throw new Error(`modifications in working directory ${opts.cwd}:\n${m}`);
+ }
const u = runc('git ls-files -o --exclude-standard', opts);
- if (u !== '') throw new Error(`untracked files in working directory ${opts.cwd}:\n${u}`);
+ if (u.length !== 0) {
+ throw new Error(`untracked files in working directory ${opts.cwd}:\n${u}`);
+ }
const s = runc('git diff-index --cached --name-status HEAD', opts);
- if (s !== '') throw new Error(`uncommitted changes in working directory ${opts.cwd}:\n${s}`);
+ if (s.length !==0) {
+ throw new Error(`uncommitted changes in working directory ${opts.cwd}:\n${s}`);
+ }
};
-const assertBranchCheckedOut = (branch, opts = {}) => {
+const assertBranchCheckedOut = (branch: string, opts:{
+ cwd?: string;
+} = {}) => {
const b = runc('git symbolic-ref HEAD', opts);
if (b !== `refs/heads/${branch}`) {
const d = opts.cwd ? path.resolve(cwd, opts.cwd) : cwd;
@@ -67,21 +82,23 @@ const assertBranchCheckedOut = (branch, opts = {}) => {
}
};
-const assertUpstreamOk = (branch, opts = {}) => {
+const assertUpstreamOk = (branch: string, opts:{
+ cwd?: string;
+} = {}) => {
const upstream = runc(`git rev-parse --symbolic-full-name ${branch}@{u}`, opts);
if (!(new RegExp(`^refs/remotes/[^/]+/${branch}`)).test(upstream)) {
throw new Error(`${branch} should track origin/${branch}; see git branch --set-upstream-to`);
}
try {
run(`git merge-base --is-ancestor ${branch} ${branch}@{u}`);
- } catch (err) {
+ } catch (err:any) {
if (err.status !== 1) throw err;
throw new Error(`${branch} is ahead of origin/${branch}; do you need to push?`);
}
};
// Check if asciidoctor is installed
-exec('asciidoctor -v', (err, stdout) => {
+exec('asciidoctor -v', (err) => {
if (err) {
console.log('Please install asciidoctor');
console.log('https://asciidoctor.org/docs/install-toolchain/');
@@ -89,10 +106,10 @@ exec('asciidoctor -v', (err, stdout) => {
}
});
-const dirExists = (dir) => {
+const dirExists = (dir: string) => {
try {
return fs.statSync(dir).isDirectory();
- } catch (err) {
+ } catch (err:any) {
if (err.code !== 'ENOENT') throw err;
return false;
}
@@ -137,20 +154,16 @@ try {
console.log(`Bumping ${release} version (to ${newVersion})...`);
pkg.version = newVersion;
- writeJson('./src/package.json', pkg);
+ run(`echo "$(jq '. += {"version": "'${newVersion}'"}' src/package.json)" > src/package.json`)
+ run(`echo "$(jq '. += {"version": "'${newVersion}'"}' admin/package.json)" > admin/package.json`)
+ run(`echo "$(jq '. += {"version": "'${newVersion}'"}' bin/package.json)" > bin/package.json`)
+ run(`echo "$(jq '. += {"version": "'${newVersion}'"}' ./package.json)" > ./package.json`)
// run npm version `release` where release is patch, minor or major
- run('npm install --package-lock-only', {cwd: 'src/'});
+ run('pnpm install');
// run npm install --package-lock-only <-- required???
- // Many users will be using the latest LTS version of npm, and the latest LTS version of npm uses
- // lockfileVersion 1. Enforce v1 so that users don't see a (benign) compatibility warning.
- const pkglock = readJson('./src/package-lock.json');
- pkglock.lockfileVersion = 1;
- writeJson('./src/package-lock.json', pkglock);
-
- run('git add src/package.json');
- run('git add src/package-lock.json');
+ run('git add -A');
run('git commit -m "bump version"');
console.log('Switching to master...');
run('git checkout master');
@@ -165,7 +178,7 @@ try {
run('git checkout develop');
console.log('Merging master into develop...');
run('git merge --no-ff --no-edit master');
-} catch (err) {
+} catch (err:any) {
console.error(err.toString());
console.warn('Resetting repository...');
console.warn('Resetting master...');
@@ -189,10 +202,10 @@ try {
run('git pull --ff-only', {cwd: '../ether.github.com/'});
console.log('Committing documentation...');
run(`cp -R out/doc/ ../ether.github.com/public/doc/v'${newVersion}'`);
- run(`npm version ${newVersion}`, {cwd: '../ether.github.com'});
+ run(`pnpm version ${newVersion}`, {cwd: '../ether.github.com'});
run('git add .', {cwd: '../ether.github.com/'});
run(`git commit -m '${newVersion} docs'`, {cwd: '../ether.github.com/'});
-} catch (err) {
+} catch (err:any) {
console.error(err.toString());
console.warn('Resetting repository...');
console.warn('Resetting master...');
diff --git a/src/bin/repairPad.js b/bin/repairPad.ts
similarity index 79%
rename from src/bin/repairPad.js
rename to bin/repairPad.ts
index ed1d83659..848b7205f 100644
--- a/src/bin/repairPad.js
+++ b/bin/repairPad.ts
@@ -1,5 +1,7 @@
'use strict';
+import process from "node:process";
+
/*
* This is a repair tool. It extracts all datas of a pad, removes and inserts them again.
*/
@@ -10,7 +12,7 @@ process.on('unhandledRejection', (err) => { throw err; });
console.warn('WARNING: This script must not be used while etherpad is running!');
-if (process.argv.length !== 3) throw new Error('Use: node src/bin/repairPad.js $PADID');
+if (process.argv.length !== 3) throw new Error('Use: node ./src/bin/repairPad.js $PADID');
// get the padID
const padId = process.argv[2];
@@ -19,19 +21,19 @@ let valueCount = 0;
(async () => {
// initialize database
- require('../node/utils/Settings');
- const db = require('../node/db/DB');
+ require('ep_etherpad-lite/node/utils/Settings');
+ const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
// get the pad
- const padManager = require('../node/db/PadManager');
+ const padManager = require('ep_etherpad-lite/node/db/PadManager');
const pad = await padManager.getPad(padId);
// accumulate the required keys
const neededDBValues = [`pad:${padId}`];
// add all authors
- neededDBValues.push(...pad.getAllAuthors().map((author) => `globalAuthor:${author}`));
+ neededDBValues.push(...pad.getAllAuthors().map((author: string) => `globalAuthor:${author}`));
// add all revisions
for (let rev = 0; rev <= pad.head; ++rev) {
diff --git a/src/bin/run.sh b/bin/run.sh
similarity index 65%
rename from src/bin/run.sh
rename to bin/run.sh
index b655e9635..654897fa4 100755
--- a/src/bin/run.sh
+++ b/bin/run.sh
@@ -2,10 +2,10 @@
# Move to the Etherpad base directory.
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${MY_DIR}/../.." || exit 1
+cd "${MY_DIR}/.." || exit 1
# Source constants and useful functions
-. src/bin/functions.sh
+. bin/functions.sh
ignoreRoot=0
for ARG in "$@"; do
@@ -27,9 +27,20 @@ EOF
fi
# Prepare the environment
-src/bin/installDeps.sh "$@" || exit 1
+bin/installDeps.sh "$@" || exit 1
+
+
+## Create the admin ui
+if [ -z "$NODE_ENV" ] || [ "$NODE_ENV" = "development" ]; then
+ ADMIN_UI_PATH="$(dirname $0)/../admin"
+ log "Creating the admin UI..."
+ (cd $ADMIN_UI_PATH && pnpm run build)
+else
+ log "Cannot create the admin UI in production mode"
+fi
# Move to the node folder and start
log "Starting Etherpad..."
-exec node src/node/server.js "$@"
+# cd src
+exec pnpm run dev "$@"
diff --git a/src/bin/safeRun.sh b/bin/safeRun.sh
similarity index 95%
rename from src/bin/safeRun.sh
rename to bin/safeRun.sh
index d980b9300..72ac475be 100755
--- a/src/bin/safeRun.sh
+++ b/bin/safeRun.sh
@@ -25,7 +25,7 @@ LAST_EMAIL_SEND=0
# Move to the Etherpad base directory.
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${MY_DIR}/../.." || exit 1
+cd "${MY_DIR}/.." || exit 1
# Check if a logfile parameter is set
LOG="$1"
@@ -40,7 +40,7 @@ while true; do
[ -w "${LOG}" ] || fatal "Logfile '${LOG}' is not writeable"
# Start the application
- src/bin/run.sh "$@" >>${LOG} 2>>${LOG}
+ bin/run.sh "$@" >>${LOG} 2>>${LOG}
TIME_FMT=$(date +%Y-%m-%dT%H:%M:%S%z)
diff --git a/bin/tsconfig.json b/bin/tsconfig.json
new file mode 100644
index 000000000..e075f973c
--- /dev/null
+++ b/bin/tsconfig.json
@@ -0,0 +1,109 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+ // "resolveJsonModule": true, /* Enable importing .json files. */
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ // "outDir": "./", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}
diff --git a/src/bin/updatePlugins.sh b/bin/updatePlugins.sh
similarity index 81%
rename from src/bin/updatePlugins.sh
rename to bin/updatePlugins.sh
index 586ba1623..8d0f43fd5 100755
--- a/src/bin/updatePlugins.sh
+++ b/bin/updatePlugins.sh
@@ -1,11 +1,11 @@
#!/bin/sh
set -e
mydir=$(cd "${0%/*}" && pwd -P) || exit 1
-cd "${mydir}"/../..
+cd "${mydir}"/..
OUTDATED=$(npm outdated --depth=0 | awk '{print $1}' | grep '^ep_') || {
echo "All plugins are up-to-date"
exit 0
}
set -- ${OUTDATED}
echo "Updating plugins: $*"
-exec npm install --no-save "$@"
+exec pnpm install "$@"
diff --git a/doc/api/hooks_server-side.adoc b/doc/api/hooks_server-side.adoc
index a5e70ec02..d95d4ec6b 100644
--- a/doc/api/hooks_server-side.adoc
+++ b/doc/api/hooks_server-side.adoc
@@ -2,7 +2,7 @@
These hooks are called on server-side.
=== loadSettings
-Called from: src/node/server.js
+Called from: src/node/server.ts
Things in context:
@@ -11,7 +11,7 @@ Things in context:
Use this hook to receive the global settings in your plugin.
=== shutdown
-Called from: src/node/server.js
+Called from: src/node/server.ts
Things in context: None
diff --git a/doc/docker.adoc b/doc/docker.adoc
index 5f4b78120..e26323337 100644
--- a/doc/docker.adoc
+++ b/doc/docker.adoc
@@ -20,6 +20,37 @@ If you want to use a personalized settings file, **you will have to rebuild your
All of the following instructions are as a member of the `docker` group.
By default, the Etherpad Docker image is built and run in `production` mode: no development dependencies are installed, and asset bundling speeds up page load time.
+=== Building and running with docker compose
+A docker compose file is provided in the project. Please first copy `.env.default` to `.env` and adjust the variables to your preference.
+
+```
+docker compose up -d # will build and start the docker container on port 9001 with development settings.
+```
+
+Starting dev server:
+
+```
+docker compose exec app bash -c "./bin/run.sh"
+```
+
+For production, please create your own docker compose file and change the `target` property in the build section to `production`. In addition, change the NODE_ENV in environment to production. For instance:
+
+```
+docker compose -f docker-compose-production.yml up -d
+```
+
+For plugins, please add them in the build section under ETHERPAD_PLUGINS, for instance:
+
+```
+ args:
+ ETHERPAD_PLUGINS: >-
+ ep_image_upload
+ ep_embedded_hyperlinks2
+ ep_headings2
+ ep_align
+ ...
+```
+
==== Rebuilding with custom settings
Edit `/settings.json.docker` at your will. When rebuilding the image, this file will be copied inside your image and renamed to `settings.json`.
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..4efb74da4
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,63 @@
+version: "3.8"
+
+# Add this file to extend the docker-compose setup, e.g.:
+# docker-compose build --no-cache
+# docker-compose up -d --build --force-recreate
+
+services:
+ app:
+ build:
+ context: .
+ args:
+ ETHERPAD_PLUGINS:
+ # change from development to production if needed
+ target: development
+ tty: true
+ stdin_open: true
+ volumes:
+ # no volume mapping of node_modules as otherwise the build-time installed plugins will be overwritten with the mount
+ # the same applies to package.json and pnpm-lock.yaml in root dir as these would also get overwritten and build time installed plugins will be removed
+ - ./src:/opt/etherpad-lite/src
+ - ./bin:/opt/etherpad-lite/bin
+ depends_on:
+ - postgres
+ environment:
+ # change from development to production if needed
+ NODE_ENV: development
+ ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_DEV_ADMIN_PASSWORD}
+ DB_CHARSET: ${DOCKER_COMPOSE_APP_DEV_ENV_DB_CHARSET:-utf8mb4}
+ DB_HOST: postgres
+ DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?}
+ DB_PASS: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?}
+ DB_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432}
+ DB_TYPE: "postgres"
+ DB_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?}
+ # For now, the env var DEFAULT_PAD_TEXT cannot be unset or empty; it seems to be mandatory in the latest version of etherpad
+ DEFAULT_PAD_TEXT: ${DOCKER_COMPOSE_APP_DEV_ENV_DEFAULT_PAD_TEXT:- }
+ DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DEV_ENV_DISABLE_IP_LOGGING:-true}
+ SOFFICE: ${DOCKER_COMPOSE_APP_DEV_ENV_SOFFICE:-null}
+ TRUST_PROXY: ${DOCKER_COMPOSE_APP_DEV_ENV_TRUST_PROXY:-true}
+ restart: always
+ ports:
+ - "${DOCKER_COMPOSE_APP_DEV_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_DEV_PORT_TARGET:-9001}"
+
+ postgres:
+ image: postgres:15-alpine
+ # Pass config parameters to the mysql server.
+ # Find more information below when you need to generate the ssl-relevant file your self
+ environment:
+ POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_DATABASE:?}
+ POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PASSWORD:?}
+ POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_PORT:-5432}
+ POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_DEV_ENV_POSTGRES_USER:?}
+ PGDATA: /var/lib/postgresql/data/pgdata
+ restart: always
+ # Exposing the port is not needed unless you want to access this database instance from the host.
+ # Be careful when other postgres docker container are running on the same port
+ # ports:
+ # - "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data/pgdata
+
+volumes:
+ postgres_data:
\ No newline at end of file
diff --git a/make_docs.js b/make_docs.js
index 1a070fe41..657afa9a2 100644
--- a/make_docs.js
+++ b/make_docs.js
@@ -1,9 +1,9 @@
-const { exec } = require('child_process');
-const fs = require('fs')
-const path = require('path')
+import {exec} from 'child_process'
+import fs from 'fs'
+import path from 'path'
+import pjson from './src/package.json' assert {type: "json"}
-const pjson = require('./src/package.json')
const VERSION=pjson.version
console.log(`Building docs for version ${VERSION}`)
@@ -15,15 +15,15 @@ const createDirIfNotExists = (dir) => {
function copyFolderSync(from, to) {
- if(fs.existsSync(to)){
- const stat = fs.lstatSync(to)
- if (stat.isDirectory()){
- fs.rmSync(to, { recursive: true })
- }
- else{
- fs.rmSync(to)
- }
- }
+ if(fs.existsSync(to)){
+ const stat = fs.lstatSync(to)
+ if (stat.isDirectory()){
+ fs.rmSync(to, { recursive: true })
+ }
+ else{
+ fs.rmSync(to)
+ }
+ }
fs.mkdirSync(to);
fs.readdirSync(from).forEach(element => {
if (fs.lstatSync(path.join(from, element)).isFile()) {
diff --git a/node_modules/ep_etherpad-lite b/node_modules/ep_etherpad-lite
deleted file mode 120000
index 5cd551cf2..000000000
--- a/node_modules/ep_etherpad-lite
+++ /dev/null
@@ -1 +0,0 @@
-../src
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..bbb9d14b2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "etherpad",
+ "description": "A free and open source realtime collaborative editor",
+ "homepage": "https://etherpad.org",
+ "type": "module",
+ "keywords": [
+ "etherpad",
+ "realtime",
+ "collaborative",
+ "editor"
+ ],
+ "bin": {
+ "etherpad-healthcheck": "bin/etherpad-healthcheck"
+ },
+ "scripts": {
+ "lint": "pnpm --filter ep_etherpad-lite run lint",
+ "test": "pnpm --filter ep_etherpad-lite run test",
+ "test-container": "pnpm --filter ep_etherpad-lite run test-container",
+ "dev": "pnpm --filter ep_etherpad-lite run dev",
+ "prod": "pnpm --filter ep_etherpad-lite run prod",
+ "ts-check": "pnpm --filter ep_etherpad-lite run ts-check",
+ "ts-check:watch": "pnpm --filter ep_etherpad-lite run ts-check:watch",
+ "test-ui": "pnpm --filter ep_etherpad-lite run test-ui",
+ "test-ui:ui": "pnpm --filter ep_etherpad-lite run test-ui:ui",
+ "test-admin": "pnpm --filter ep_etherpad-lite run test-admin",
+ "test-admin:ui": "pnpm --filter ep_etherpad-lite run test-admin:ui",
+ "install-plugins": "pnpm --filter bin run install-plugins"
+ },
+ "dependencies": {
+ "ep_etherpad-lite": "workspace:./src"
+ },
+ "devDependencies": {
+ "admin": "workspace:./admin"
+ },
+ "engines": {
+ "node": ">=18.18.2",
+ "npm": ">=6.14.0",
+ "pnpm": ">=8.3.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ether/etherpad-lite.git"
+ },
+ "version": "2.0.0",
+ "license": "Apache-2.0"
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 000000000..d9b1bede2
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,6498 @@
+lockfileVersion: '6.0'
+
+settings:
+ autoInstallPeers: false
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ ep_etherpad-lite:
+ specifier: workspace:./src
+ version: link:src
+ devDependencies:
+ admin:
+ specifier: workspace:./admin
+ version: link:admin
+
+ admin:
+ devDependencies:
+ '@radix-ui/react-dialog':
+ specifier: ^1.0.5
+ version: 1.0.5(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-toast':
+ specifier: ^1.1.5
+ version: 1.1.5(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react':
+ specifier: ^18.2.56
+ version: 18.2.65
+ '@types/react-dom':
+ specifier: ^18.2.19
+ version: 18.2.21
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^7.0.2
+ version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/parser':
+ specifier: ^7.0.2
+ version: 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@vitejs/plugin-react-swc':
+ specifier: ^3.5.0
+ version: 3.6.0(vite@5.1.6)
+ eslint:
+ specifier: ^8.56.0
+ version: 8.57.0
+ eslint-plugin-react-hooks:
+ specifier: ^4.6.0
+ version: 4.6.0(eslint@8.57.0)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.5
+ version: 0.4.6(eslint@8.57.0)
+ i18next:
+ specifier: ^23.10.1
+ version: 23.10.1
+ i18next-browser-languagedetector:
+ specifier: ^7.2.0
+ version: 7.2.0
+ lucide-react:
+ specifier: ^0.356.0
+ version: 0.356.0(react@18.2.0)
+ react:
+ specifier: ^18.2.0
+ version: 18.2.0
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.2.0(react@18.2.0)
+ react-hook-form:
+ specifier: ^7.51.0
+ version: 7.51.0(react@18.2.0)
+ react-i18next:
+ specifier: ^14.1.0
+ version: 14.1.0(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
+ react-router-dom:
+ specifier: ^6.22.3
+ version: 6.22.3(react-dom@18.2.0)(react@18.2.0)
+ socket.io-client:
+ specifier: ^4.7.4
+ version: 4.7.4
+ typescript:
+ specifier: ^5.2.2
+ version: 5.4.2
+ vite:
+ specifier: ^5.1.4
+ version: 5.1.6
+ vite-plugin-static-copy:
+ specifier: ^1.0.1
+ version: 1.0.1(vite@5.1.6)
+ vite-plugin-svgr:
+ specifier: ^4.2.0
+ version: 4.2.0(typescript@5.4.2)(vite@5.1.6)
+ zustand:
+ specifier: ^4.5.2
+ version: 4.5.2(@types/react@18.2.65)(react@18.2.0)
+
+ bin:
+ dependencies:
+ axios:
+ specifier: ^1.6.8
+ version: 1.6.8
+ ep_etherpad-lite:
+ specifier: workspace:../src
+ version: link:../src
+ log4js:
+ specifier: ^6.9.1
+ version: 6.9.1
+ semver:
+ specifier: ^7.6.0
+ version: 7.6.0
+ tsx:
+ specifier: ^4.7.1
+ version: 4.7.1
+ ueberdb2:
+ specifier: ^4.2.63
+ version: 4.2.63
+ devDependencies:
+ '@types/node':
+ specifier: ^20.11.27
+ version: 20.11.27
+ '@types/semver':
+ specifier: ^7.5.8
+ version: 7.5.8
+ typescript:
+ specifier: ^5.4.2
+ version: 5.4.2
+
+ src:
+ dependencies:
+ async:
+ specifier: ^3.2.5
+ version: 3.2.5
+ axios:
+ specifier: ^1.6.8
+ version: 1.6.8
+ clean-css:
+ specifier: ^5.3.3
+ version: 5.3.3
+ cookie-parser:
+ specifier: ^1.4.6
+ version: 1.4.6
+ cross-spawn:
+ specifier: ^7.0.3
+ version: 7.0.3
+ ejs:
+ specifier: ^3.1.9
+ version: 3.1.9
+ etherpad-require-kernel:
+ specifier: ^1.0.16
+ version: 1.0.16
+ etherpad-yajsml:
+ specifier: 0.0.12
+ version: 0.0.12
+ express:
+ specifier: 4.18.3
+ version: 4.18.3
+ express-rate-limit:
+ specifier: ^7.2.0
+ version: 7.2.0(express@4.18.3)
+ express-session:
+ specifier: npm:@etherpad/express-session@^1.18.2
+ version: /@etherpad/express-session@1.18.2
+ fast-deep-equal:
+ specifier: ^3.1.3
+ version: 3.1.3
+ find-root:
+ specifier: 1.1.0
+ version: 1.1.0
+ formidable:
+ specifier: ^3.5.1
+ version: 3.5.1
+ http-errors:
+ specifier: ^2.0.0
+ version: 2.0.0
+ js-cookie:
+ specifier: ^3.0.5
+ version: 3.0.5
+ jsdom:
+ specifier: ^24.0.0
+ version: 24.0.0
+ jsonminify:
+ specifier: 0.4.2
+ version: 0.4.2
+ languages4translatewiki:
+ specifier: 0.1.3
+ version: 0.1.3
+ live-plugin-manager-pnpm:
+ specifier: ^0.18.1
+ version: 0.18.1
+ lodash.clonedeep:
+ specifier: 4.5.0
+ version: 4.5.0
+ log4js:
+ specifier: ^6.9.1
+ version: 6.9.1
+ measured-core:
+ specifier: ^2.0.0
+ version: 2.0.0
+ mime-types:
+ specifier: ^2.1.35
+ version: 2.1.35
+ openapi-backend:
+ specifier: ^5.10.6
+ version: 5.10.6
+ proxy-addr:
+ specifier: ^2.0.7
+ version: 2.0.7
+ rate-limiter-flexible:
+ specifier: ^5.0.0
+ version: 5.0.0
+ rehype:
+ specifier: ^13.0.1
+ version: 13.0.1
+ rehype-minify-whitespace:
+ specifier: ^6.0.0
+ version: 6.0.0
+ resolve:
+ specifier: 1.22.8
+ version: 1.22.8
+ security:
+ specifier: 1.0.0
+ version: 1.0.0
+ semver:
+ specifier: ^7.6.0
+ version: 7.6.0
+ socket.io:
+ specifier: ^4.7.5
+ version: 4.7.5
+ socket.io-client:
+ specifier: ^4.7.5
+ version: 4.7.5
+ superagent:
+ specifier: ^8.1.2
+ version: 8.1.2
+ terser:
+ specifier: ^5.29.2
+ version: 5.29.2
+ threads:
+ specifier: ^1.7.0
+ version: 1.7.0
+ tinycon:
+ specifier: 0.6.8
+ version: 0.6.8
+ tsx:
+ specifier: ^4.7.1
+ version: 4.7.1
+ ueberdb2:
+ specifier: ^4.2.63
+ version: 4.2.63
+ underscore:
+ specifier: 1.13.6
+ version: 1.13.6
+ unorm:
+ specifier: 1.6.0
+ version: 1.6.0
+ wtfnode:
+ specifier: ^0.9.1
+ version: 0.9.1
+ devDependencies:
+ '@playwright/test':
+ specifier: ^1.42.1
+ version: 1.42.1
+ '@types/async':
+ specifier: ^3.2.24
+ version: 3.2.24
+ '@types/express':
+ specifier: ^4.17.21
+ version: 4.17.21
+ '@types/http-errors':
+ specifier: ^2.0.4
+ version: 2.0.4
+ '@types/jsdom':
+ specifier: ^21.1.6
+ version: 21.1.6
+ '@types/mocha':
+ specifier: ^10.0.6
+ version: 10.0.6
+ '@types/node':
+ specifier: ^20.11.28
+ version: 20.11.28
+ '@types/semver':
+ specifier: ^7.5.8
+ version: 7.5.8
+ '@types/sinon':
+ specifier: ^17.0.3
+ version: 17.0.3
+ '@types/supertest':
+ specifier: ^6.0.2
+ version: 6.0.2
+ '@types/underscore':
+ specifier: ^1.11.15
+ version: 1.11.15
+ eslint:
+ specifier: ^8.57.0
+ version: 8.57.0
+ eslint-config-etherpad:
+ specifier: ^3.0.22
+ version: 3.0.22(eslint@8.57.0)(typescript@5.4.2)
+ etherpad-cli-client:
+ specifier: ^3.0.2
+ version: 3.0.2
+ mocha:
+ specifier: ^10.3.0
+ version: 10.3.0
+ mocha-froth:
+ specifier: ^0.2.10
+ version: 0.2.10
+ nodeify:
+ specifier: ^1.0.1
+ version: 1.0.1
+ openapi-schema-validation:
+ specifier: ^0.4.2
+ version: 0.4.2
+ set-cookie-parser:
+ specifier: ^2.6.0
+ version: 2.6.0
+ sinon:
+ specifier: ^17.0.1
+ version: 17.0.1
+ split-grid:
+ specifier: ^1.0.11
+ version: 1.0.11
+ supertest:
+ specifier: ^6.3.4
+ version: 6.3.4
+ typescript:
+ specifier: ^5.4.2
+ version: 5.4.2
+
+packages:
+
+ /@aashutoshrathi/word-wrap@1.2.6:
+ resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /@ampproject/remapping@2.3.0:
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
+ dev: true
+
+ /@apidevtools/json-schema-ref-parser@11.1.0:
+ resolution: {integrity: sha512-g/VW9ZQEFJAOwAyUb8JFf7MLiLy2uEB4rU270rGzDwICxnxMlPy0O11KVePSgS36K1NI29gSlK84n5INGhd4Ag==}
+ engines: {node: '>= 16'}
+ dependencies:
+ '@jsdevtools/ono': 7.1.3
+ '@types/json-schema': 7.0.15
+ '@types/lodash.clonedeep': 4.5.9
+ js-yaml: 4.1.0
+ lodash.clonedeep: 4.5.0
+ dev: false
+
+ /@babel/code-frame@7.23.5:
+ resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/highlight': 7.23.4
+ chalk: 2.4.2
+ dev: true
+
+ /@babel/compat-data@7.23.5:
+ resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/core@7.24.0:
+ resolution: {integrity: sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@babel/code-frame': 7.23.5
+ '@babel/generator': 7.23.6
+ '@babel/helper-compilation-targets': 7.23.6
+ '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0)
+ '@babel/helpers': 7.24.0
+ '@babel/parser': 7.24.0
+ '@babel/template': 7.24.0
+ '@babel/traverse': 7.24.0
+ '@babel/types': 7.24.0
+ convert-source-map: 2.0.0
+ debug: 4.3.4(supports-color@8.1.1)
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/generator@7.23.6:
+ resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.24.0
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.22
+ jsesc: 2.5.2
+ dev: true
+
+ /@babel/helper-compilation-targets@7.23.6:
+ resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/compat-data': 7.23.5
+ '@babel/helper-validator-option': 7.23.5
+ browserslist: 4.23.0
+ lru-cache: 5.1.1
+ semver: 6.3.1
+ dev: true
+
+ /@babel/helper-environment-visitor@7.22.20:
+ resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-function-name@7.23.0:
+ resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.24.0
+ '@babel/types': 7.24.0
+ dev: true
+
+ /@babel/helper-hoist-variables@7.22.5:
+ resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.24.0
+ dev: true
+
+ /@babel/helper-module-imports@7.22.15:
+ resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.24.0
+ dev: true
+
+ /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0):
+ resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ dependencies:
+ '@babel/core': 7.24.0
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-module-imports': 7.22.15
+ '@babel/helper-simple-access': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/helper-validator-identifier': 7.22.20
+ dev: true
+
+ /@babel/helper-simple-access@7.22.5:
+ resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.24.0
+ dev: true
+
+ /@babel/helper-split-export-declaration@7.22.6:
+ resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.24.0
+ dev: true
+
+ /@babel/helper-string-parser@7.23.4:
+ resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-validator-identifier@7.22.20:
+ resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-validator-option@7.23.5:
+ resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helpers@7.24.0:
+ resolution: {integrity: sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.24.0
+ '@babel/traverse': 7.24.0
+ '@babel/types': 7.24.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/highlight@7.23.4:
+ resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.22.20
+ chalk: 2.4.2
+ js-tokens: 4.0.0
+ dev: true
+
+ /@babel/parser@7.24.0:
+ resolution: {integrity: sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.24.0
+ dev: true
+
+ /@babel/runtime@7.24.0:
+ resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ regenerator-runtime: 0.14.1
+ dev: true
+
+ /@babel/template@7.24.0:
+ resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/parser': 7.24.0
+ '@babel/types': 7.24.0
+ dev: true
+
+ /@babel/traverse@7.24.0:
+ resolution: {integrity: sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/generator': 7.23.6
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-function-name': 7.23.0
+ '@babel/helper-hoist-variables': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/parser': 7.24.0
+ '@babel/types': 7.24.0
+ debug: 4.3.4(supports-color@8.1.1)
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/types@7.24.0:
+ resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.23.4
+ '@babel/helper-validator-identifier': 7.22.20
+ to-fast-properties: 2.0.0
+ dev: true
+
+ /@esbuild/aix-ppc64@0.19.12:
+ resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/android-arm64@0.19.12:
+ resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/android-arm@0.19.12:
+ resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/android-x64@0.19.12:
+ resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/darwin-arm64@0.19.12:
+ resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/darwin-x64@0.19.12:
+ resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/freebsd-arm64@0.19.12:
+ resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/freebsd-x64@0.19.12:
+ resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-arm64@0.19.12:
+ resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-arm@0.19.12:
+ resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-ia32@0.19.12:
+ resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-loong64@0.19.12:
+ resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-mips64el@0.19.12:
+ resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-ppc64@0.19.12:
+ resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-riscv64@0.19.12:
+ resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-s390x@0.19.12:
+ resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-x64@0.19.12:
+ resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/netbsd-x64@0.19.12:
+ resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/openbsd-x64@0.19.12:
+ resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/sunos-x64@0.19.12:
+ resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/win32-arm64@0.19.12:
+ resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/win32-ia32@0.19.12:
+ resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/win32-x64@0.19.12:
+ resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0):
+ resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ dependencies:
+ eslint: 8.57.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /@eslint-community/regexpp@4.10.0:
+ resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+ dev: true
+
+ /@eslint/eslintrc@2.1.4:
+ resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.3.4(supports-color@8.1.1)
+ espree: 9.6.1
+ globals: 13.24.0
+ ignore: 5.3.1
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@eslint/js@8.57.0:
+ resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dev: true
+
+ /@etherpad/express-session@1.18.2:
+ resolution: {integrity: sha512-5S+mdCxqSR5AX9jj1G3Qzxv/EX4jyWr9ikHzwtv+uV9fPu7EPFF6oNkhrUCZ19RJ5975KulPyrBB35hUK5x7LA==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ cookie: 0.4.2
+ cookie-signature: 1.0.6
+ debug: 2.6.9
+ depd: 2.0.0
+ on-headers: 1.0.2
+ parseurl: 1.3.3
+ safe-buffer: 5.2.1
+ uid-safe: 2.1.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@humanwhocodes/config-array@0.11.14:
+ resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
+ engines: {node: '>=10.10.0'}
+ dependencies:
+ '@humanwhocodes/object-schema': 2.0.2
+ debug: 4.3.4(supports-color@8.1.1)
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@humanwhocodes/module-importer@1.0.1:
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+ dev: true
+
+ /@humanwhocodes/object-schema@2.0.2:
+ resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
+ dev: true
+
+ /@jridgewell/gen-mapping@0.3.3:
+ resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/set-array': 1.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/trace-mapping': 0.3.22
+ dev: true
+
+ /@jridgewell/gen-mapping@0.3.5:
+ resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/trace-mapping': 0.3.25
+
+ /@jridgewell/resolve-uri@3.1.2:
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ /@jridgewell/set-array@1.1.2:
+ resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
+ engines: {node: '>=6.0.0'}
+ dev: true
+
+ /@jridgewell/set-array@1.2.1:
+ resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+ engines: {node: '>=6.0.0'}
+
+ /@jridgewell/source-map@0.3.5:
+ resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
+ dev: false
+
+ /@jridgewell/sourcemap-codec@1.4.15:
+ resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+
+ /@jridgewell/trace-mapping@0.3.22:
+ resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+ dev: true
+
+ /@jridgewell/trace-mapping@0.3.25:
+ resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+
+ /@jsdevtools/ono@7.1.3:
+ resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
+ dev: false
+
+ /@nodelib/fs.scandir@2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+ dev: true
+
+ /@nodelib/fs.stat@2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /@nodelib/fs.walk@1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.17.1
+ dev: true
+
+ /@playwright/test@1.42.1:
+ resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dependencies:
+ playwright: 1.42.1
+ dev: true
+
+ /@radix-ui/primitive@1.0.1:
+ resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
+ dependencies:
+ '@babel/runtime': 7.24.0
+ dev: true
+
+ /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-context@1.0.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ aria-hidden: 1.2.3
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-remove-scroll: 2.5.5(@types/react@18.2.65)(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-id@1.0.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-slot': 1.0.2(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-slot@1.0.2(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-toast@1.1.5(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.65)(react@18.2.0)
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@types/react': 18.2.65
+ react: 18.2.0
+ dev: true
+
+ /@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.21)(@types/react@18.2.65)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.65
+ '@types/react-dom': 18.2.21
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /@remix-run/router@1.15.3:
+ resolution: {integrity: sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==}
+ engines: {node: '>=14.0.0'}
+ dev: true
+
+ /@rollup/pluginutils@5.1.0:
+ resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ dependencies:
+ '@types/estree': 1.0.5
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /@rollup/rollup-android-arm-eabi@4.13.0:
+ resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-android-arm64@4.13.0:
+ resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-arm64@4.13.0:
+ resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-x64@4.13.0:
+ resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-gnueabihf@4.13.0:
+ resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-gnu@4.13.0:
+ resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-musl@4.13.0:
+ resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-gnu@4.13.0:
+ resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-gnu@4.13.0:
+ resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-musl@4.13.0:
+ resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-arm64-msvc@4.13.0:
+ resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-ia32-msvc@4.13.0:
+ resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-msvc@4.13.0:
+ resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rushstack/eslint-patch@1.7.2:
+ resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==}
+ dev: true
+
+ /@sinonjs/commons@2.0.0:
+ resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
+ dependencies:
+ type-detect: 4.0.8
+ dev: true
+
+ /@sinonjs/commons@3.0.1:
+ resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
+ dependencies:
+ type-detect: 4.0.8
+ dev: true
+
+ /@sinonjs/fake-timers@11.2.2:
+ resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==}
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+ dev: true
+
+ /@sinonjs/samsam@8.0.0:
+ resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==}
+ dependencies:
+ '@sinonjs/commons': 2.0.0
+ lodash.get: 4.4.2
+ type-detect: 4.0.8
+ dev: true
+
+ /@sinonjs/text-encoding@0.7.2:
+ resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==}
+ dev: true
+
+ /@socket.io/component-emitter@3.1.0:
+ resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
+
+ /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ dev: true
+
+ /@svgr/babel-preset@8.1.0(@babel/core@7.24.0):
+ resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.24.0
+ '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.24.0)
+ '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.24.0)
+ '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.24.0)
+ '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.24.0)
+ '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.24.0)
+ '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.24.0)
+ '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.24.0)
+ '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.0)
+ dev: true
+
+ /@svgr/core@8.1.0(typescript@5.4.2):
+ resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
+ engines: {node: '>=14'}
+ dependencies:
+ '@babel/core': 7.24.0
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.24.0)
+ camelcase: 6.3.0
+ cosmiconfig: 8.3.6(typescript@5.4.2)
+ snake-case: 3.0.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
+ /@svgr/hast-util-to-babel-ast@8.0.0:
+ resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==}
+ engines: {node: '>=14'}
+ dependencies:
+ '@babel/types': 7.24.0
+ entities: 4.5.0
+ dev: true
+
+ /@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0):
+ resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@svgr/core': '*'
+ dependencies:
+ '@babel/core': 7.24.0
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.24.0)
+ '@svgr/core': 8.1.0(typescript@5.4.2)
+ '@svgr/hast-util-to-babel-ast': 8.0.0
+ svg-parser: 2.0.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@swc/core-darwin-arm64@1.4.7:
+ resolution: {integrity: sha512-IhfP2Mrrh9WcdlBJQbPNBhfdOhW/SC910SiuzvxaLgJmzq1tw6TVDNUz4Zf85TbK5uzgR0emtPc9hTGxynl57A==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-darwin-x64@1.4.7:
+ resolution: {integrity: sha512-MO01pnxJDS6st5IiqyTnAOz9kpAPP/O4lzEUH9E80XdXBzwptS5hNTM0egBlqueWDFrPM26RI81JLtyTU7kR8w==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-arm-gnueabihf@1.4.7:
+ resolution: {integrity: sha512-+cDaXW6PZqGhXIq9C4xE+/QuyUsLkXf8d8uSXep+rZYDl4YHS9Fi7HpZQnqLX6al/iVhwe3VnxHMGw50gxcr/g==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-arm64-gnu@1.4.7:
+ resolution: {integrity: sha512-RNnVHRKhEtA3pM34wgb3Vumf5M6/XlWzFdkHEMZIkOKyNSUhZiv8X3tsEK+n1rZQWIDkvlw4YyHtB8vK18WdCA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-arm64-musl@1.4.7:
+ resolution: {integrity: sha512-p7Xm4Pib02d1SFS9XXMoOcCTDIkFWMspspptPX00VcjAdZYnXWujWGuD2W+KN1gq5syHB1g3TsYs9LP2dGsKqw==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-x64-gnu@1.4.7:
+ resolution: {integrity: sha512-ViI5jy03cFYPETsye1J+oPbHE4v8oIDN34qebzvgHUlNKOXfc1ig0Zha5oQnKp3zj1rmjcSLIMqK++WR021G5A==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-x64-musl@1.4.7:
+ resolution: {integrity: sha512-Nf3Axcx/ILl7XE44eidNNPF39rg/KIeqg2545vrOXJG02iu7pEjZuu8wm6w+23BpP4COjZJymlg9LzPT1ZBD5Q==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-win32-arm64-msvc@1.4.7:
+ resolution: {integrity: sha512-MFkJEaC59AO2HpndmHhCkaj8NJus5etjMtBphOe9em7jmmfdQ7mLenKHbZ/CspHNl8yNPO9Qzpa/at2838x+RQ==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-win32-ia32-msvc@1.4.7:
+ resolution: {integrity: sha512-nwrfERocUei9sxqd6URrWcEC3KDcTBD+beMerB9idvuzy4rcm5k1O1ClUlZ9pJOZn+vMN1tqZjLze4hJMT9STQ==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-win32-x64-msvc@1.4.7:
+ resolution: {integrity: sha512-d5T8Z/axAml8FTA+T9RS2mwJDNIbSSz5jcEiWaGuKVDIoSZib2HpMvnMydOGsIrmjfS1Z4ZhdAawivPhAZ3M8Q==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core@1.4.7:
+ resolution: {integrity: sha512-I7a9sUxB+z+UCf6KudqrQH/RgLal/S+E+t4uBdbggycLyJe7WvBgPrQlcN5UpEuD9YC2PJ0CN6kgD6ARStg+pg==}
+ engines: {node: '>=10'}
+ requiresBuild: true
+ peerDependencies:
+ '@swc/helpers': ^0.5.0
+ peerDependenciesMeta:
+ '@swc/helpers':
+ optional: true
+ dependencies:
+ '@swc/counter': 0.1.3
+ '@swc/types': 0.1.5
+ optionalDependencies:
+ '@swc/core-darwin-arm64': 1.4.7
+ '@swc/core-darwin-x64': 1.4.7
+ '@swc/core-linux-arm-gnueabihf': 1.4.7
+ '@swc/core-linux-arm64-gnu': 1.4.7
+ '@swc/core-linux-arm64-musl': 1.4.7
+ '@swc/core-linux-x64-gnu': 1.4.7
+ '@swc/core-linux-x64-musl': 1.4.7
+ '@swc/core-win32-arm64-msvc': 1.4.7
+ '@swc/core-win32-ia32-msvc': 1.4.7
+ '@swc/core-win32-x64-msvc': 1.4.7
+ dev: true
+
+ /@swc/counter@0.1.3:
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+ dev: true
+
+ /@swc/types@0.1.5:
+ resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==}
+ dev: true
+
+ /@types/async@3.2.24:
+ resolution: {integrity: sha512-8iHVLHsCCOBKjCF2KwFe0p9Z3rfM9mL+sSP8btyR5vTjJRAqpBYD28/ZLgXPf0pjG1VxOvtCV/BgXkQbpSe8Hw==}
+ dev: true
+
+ /@types/body-parser@1.19.5:
+ resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
+ dependencies:
+ '@types/connect': 3.4.38
+ '@types/node': 20.11.28
+ dev: true
+
+ /@types/connect@3.4.38:
+ resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
+ dependencies:
+ '@types/node': 20.11.28
+ dev: true
+
+ /@types/cookie@0.4.1:
+ resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
+ dev: false
+
+ /@types/cookiejar@2.1.5:
+ resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
+ dev: true
+
+ /@types/cors@2.8.17:
+ resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
+ dependencies:
+ '@types/node': 20.11.28
+ dev: false
+
+ /@types/debug@4.1.12:
+ resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+ dependencies:
+ '@types/ms': 0.7.34
+ dev: false
+
+ /@types/estree@1.0.5:
+ resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+ dev: true
+
+ /@types/express-serve-static-core@4.17.43:
+ resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==}
+ dependencies:
+ '@types/node': 20.11.28
+ '@types/qs': 6.9.11
+ '@types/range-parser': 1.2.7
+ '@types/send': 0.17.4
+ dev: true
+
+ /@types/express@4.17.21:
+ resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
+ dependencies:
+ '@types/body-parser': 1.19.5
+ '@types/express-serve-static-core': 4.17.43
+ '@types/qs': 6.9.11
+ '@types/serve-static': 1.15.5
+ dev: true
+
+ /@types/fs-extra@9.0.13:
+ resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
+ dependencies:
+ '@types/node': 20.11.28
+ dev: false
+
+ /@types/hast@3.0.4:
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
+ /@types/http-errors@2.0.4:
+ resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
+ dev: true
+
+ /@types/jsdom@21.1.6:
+ resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==}
+ dependencies:
+ '@types/node': 20.11.28
+ '@types/tough-cookie': 4.0.5
+ parse5: 7.1.2
+ dev: true
+
+ /@types/json-schema@7.0.15:
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ /@types/json5@0.0.29:
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ dev: true
+
+ /@types/lockfile@1.0.4:
+ resolution: {integrity: sha512-Q8oFIHJHr+htLrTXN2FuZfg+WXVHQRwU/hC2GpUu+Q8e3FUM9EDkS2pE3R2AO1ZGu56f479ybdMCNF1DAu8cAQ==}
+ dev: false
+
+ /@types/lodash.clonedeep@4.5.9:
+ resolution: {integrity: sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==}
+ dependencies:
+ '@types/lodash': 4.14.202
+ dev: false
+
+ /@types/lodash@4.14.202:
+ resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==}
+ dev: false
+
+ /@types/mdast@4.0.3:
+ resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
+ /@types/methods@1.1.4:
+ resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
+ dev: true
+
+ /@types/mime@1.3.5:
+ resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
+ dev: true
+
+ /@types/mime@3.0.4:
+ resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
+ dev: true
+
+ /@types/mocha@10.0.6:
+ resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==}
+ dev: true
+
+ /@types/ms@0.7.34:
+ resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
+ dev: false
+
+ /@types/node-fetch@2.6.11:
+ resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
+ dependencies:
+ '@types/node': 20.11.28
+ form-data: 4.0.0
+ dev: false
+
+ /@types/node@20.11.27:
+ resolution: {integrity: sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==}
+ dependencies:
+ undici-types: 5.26.5
+ dev: true
+
+ /@types/node@20.11.28:
+ resolution: {integrity: sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==}
+ dependencies:
+ undici-types: 5.26.5
+
+ /@types/prop-types@15.7.11:
+ resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
+ dev: true
+
+ /@types/qs@6.9.11:
+ resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
+ dev: true
+
+ /@types/range-parser@1.2.7:
+ resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
+ dev: true
+
+ /@types/react-dom@18.2.21:
+ resolution: {integrity: sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==}
+ dependencies:
+ '@types/react': 18.2.65
+ dev: true
+
+ /@types/react@18.2.65:
+ resolution: {integrity: sha512-98TsY0aW4jqx/3RqsUXwMDZSWR1Z4CUlJNue8ueS2/wcxZOsz4xmW1X8ieaWVRHcmmQM3R8xVA4XWB3dJnWwDQ==}
+ dependencies:
+ '@types/prop-types': 15.7.11
+ '@types/scheduler': 0.16.8
+ csstype: 3.1.3
+ dev: true
+
+ /@types/scheduler@0.16.8:
+ resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
+ dev: true
+
+ /@types/semver@7.5.8:
+ resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
+
+ /@types/send@0.17.4:
+ resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
+ dependencies:
+ '@types/mime': 1.3.5
+ '@types/node': 20.11.28
+ dev: true
+
+ /@types/serve-static@1.15.5:
+ resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==}
+ dependencies:
+ '@types/http-errors': 2.0.4
+ '@types/mime': 3.0.4
+ '@types/node': 20.11.28
+ dev: true
+
+ /@types/sinon@17.0.3:
+ resolution: {integrity: sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==}
+ dependencies:
+ '@types/sinonjs__fake-timers': 8.1.1
+ dev: true
+
+ /@types/sinonjs__fake-timers@8.1.1:
+ resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
+ dev: true
+
+ /@types/superagent@8.1.3:
+ resolution: {integrity: sha512-R/CfN6w2XsixLb1Ii8INfn+BT9sGPvw74OavfkW4SwY+jeUcAwLZv2+bXLJkndnimxjEBm0RPHgcjW9pLCa8cw==}
+ dependencies:
+ '@types/cookiejar': 2.1.5
+ '@types/methods': 1.1.4
+ '@types/node': 20.11.28
+ dev: true
+
+ /@types/supertest@6.0.2:
+ resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==}
+ dependencies:
+ '@types/methods': 1.1.4
+ '@types/superagent': 8.1.3
+ dev: true
+
+ /@types/tar@6.1.11:
+ resolution: {integrity: sha512-ThA1WD8aDdVU4VLuyq5NEqriwXErF5gEIJeyT6gHBWU7JtSmW2a5qjNv3/vR82O20mW+1vhmeZJfBQPT3HCugg==}
+ dependencies:
+ '@types/node': 20.11.28
+ minipass: 4.2.8
+ dev: false
+
+ /@types/tough-cookie@4.0.5:
+ resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
+ dev: true
+
+ /@types/underscore@1.11.15:
+ resolution: {integrity: sha512-HP38xE+GuWGlbSRq9WrZkousaQ7dragtZCruBVMi0oX1migFZavZ3OROKHSkNp/9ouq82zrWtZpg18jFnVN96g==}
+ dev: true
+
+ /@types/unist@3.0.2:
+ resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
+ dev: false
+
+ /@types/url-join@4.0.1:
+ resolution: {integrity: sha512-wDXw9LEEUHyV+7UWy7U315nrJGJ7p1BzaCxDpEoLr789Dk1WDVMMlf3iBfbG2F8NdWnYyFbtTxUn2ZNbm1Q4LQ==}
+ dev: false
+
+ /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^5.0.0
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@eslint-community/regexpp': 4.10.0
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.2)
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ natural-compare-lite: 1.4.0
+ semver: 7.6.0
+ tsutils: 3.21.0(typescript@5.4.2)
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^7.0.0
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@eslint-community/regexpp': 4.10.0
+ '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.2.0
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ natural-compare: 1.4.0
+ semver: 7.6.0
+ ts-api-utils: 1.3.0(typescript@5.4.2)
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.2)
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.2.0
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/scope-manager@5.62.0:
+ resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/visitor-keys': 5.62.0
+ dev: true
+
+ /@typescript-eslint/scope-manager@7.2.0:
+ resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/visitor-keys': 7.2.0
+ dev: true
+
+ /@typescript-eslint/type-utils@5.62.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.2)
+ '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.2)
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ tsutils: 3.21.0(typescript@5.4.2)
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ ts-api-utils: 1.3.0(typescript@5.4.2)
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/types@5.62.0:
+ resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dev: true
+
+ /@typescript-eslint/types@7.2.0:
+ resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dev: true
+
+ /@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.2):
+ resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/visitor-keys': 5.62.0
+ debug: 4.3.4(supports-color@8.1.1)
+ globby: 11.1.0
+ is-glob: 4.0.3
+ semver: 7.6.0
+ tsutils: 3.21.0(typescript@5.4.2)
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.2):
+ resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/visitor-keys': 7.2.0
+ debug: 4.3.4(supports-color@8.1.1)
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.3
+ semver: 7.6.0
+ ts-api-utils: 1.3.0(typescript@5.4.2)
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.8
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.2)
+ eslint: 8.57.0
+ eslint-scope: 5.1.1
+ semver: 7.6.0
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
+ /@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.8
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ eslint: 8.57.0
+ semver: 7.6.0
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
+ /@typescript-eslint/visitor-keys@5.62.0:
+ resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /@typescript-eslint/visitor-keys@7.2.0:
+ resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 7.2.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /@ungap/structured-clone@1.2.0:
+ resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
+
+ /@vitejs/plugin-react-swc@3.6.0(vite@5.1.6):
+ resolution: {integrity: sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==}
+ peerDependencies:
+ vite: ^4 || ^5
+ dependencies:
+ '@swc/core': 1.4.7
+ vite: 5.1.6
+ transitivePeerDependencies:
+ - '@swc/helpers'
+ dev: true
+
+ /accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+ dev: false
+
+ /acorn-jsx@5.3.2(acorn@8.11.3):
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+ dependencies:
+ acorn: 8.11.3
+ dev: true
+
+ /acorn@8.11.3:
+ resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ /agent-base@7.1.0:
+ resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
+ engines: {node: '>= 14'}
+ dependencies:
+ debug: 4.3.4(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /ajv-formats@2.1.1(ajv@8.12.0):
+ resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+ dependencies:
+ ajv: 8.12.0
+ dev: false
+
+ /ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+ dev: true
+
+ /ajv@8.12.0:
+ resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js: 4.4.1
+ dev: false
+
+ /ansi-colors@4.1.1:
+ resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /ansi-styles@3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: true
+
+ /ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+ dependencies:
+ color-convert: 2.0.1
+
+ /anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+ dev: true
+
+ /argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ /aria-hidden@1.2.3:
+ resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ tslib: 2.6.2
+ dev: true
+
+ /array-buffer-byte-length@1.0.1:
+ resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ is-array-buffer: 3.0.4
+ dev: true
+
+ /array-flatten@1.1.1:
+ resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
+ dev: false
+
+ /array-includes@3.1.7:
+ resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ get-intrinsic: 1.2.4
+ is-string: 1.0.7
+ dev: true
+
+ /array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /array.prototype.filter@1.0.3:
+ resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ es-array-method-boxes-properly: 1.0.0
+ is-string: 1.0.7
+ dev: true
+
+ /array.prototype.findlastindex@1.2.4:
+ resolution: {integrity: sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.0.2
+ dev: true
+
+ /array.prototype.flat@1.3.2:
+ resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ es-shim-unscopables: 1.0.2
+ dev: true
+
+ /array.prototype.flatmap@1.3.2:
+ resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ es-shim-unscopables: 1.0.2
+ dev: true
+
+ /arraybuffer.prototype.slice@1.0.3:
+ resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ array-buffer-byte-length: 1.0.1
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ is-array-buffer: 3.0.4
+ is-shared-array-buffer: 1.0.2
+ dev: true
+
+ /asap@2.0.6:
+ resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
+
+ /async@3.2.5:
+ resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
+
+ /asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ /available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ possible-typed-array-names: 1.0.0
+ dev: true
+
+ /axios@1.6.8:
+ resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==}
+ dependencies:
+ follow-redirects: 1.15.6
+ form-data: 4.0.0
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+ dev: false
+
+ /bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+ dev: false
+
+ /balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ /base64id@2.0.0:
+ resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
+ engines: {node: ^4.5.0 || >= 5.9}
+ dev: false
+
+ /bath-es5@3.0.3:
+ resolution: {integrity: sha512-PdCioDToH3t84lP40kUFCKWCOCH389Dl1kbC8FGoqOwamxsmqxxnJSXdkTOsPoNHXjem4+sJ+bbNoQm5zeCqxg==}
+ dev: false
+
+ /binary-extensions@2.2.0:
+ resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /binary-search@1.3.6:
+ resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==}
+ dev: false
+
+ /body-parser@1.20.2:
+ resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.11.0
+ raw-body: 2.5.2
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ /brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+
+ /braces@3.0.2:
+ resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ engines: {node: '>=8'}
+ dependencies:
+ fill-range: 7.0.1
+ dev: true
+
+ /browser-stdout@1.3.1:
+ resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
+ dev: true
+
+ /browserslist@4.23.0:
+ resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ caniuse-lite: 1.0.30001597
+ electron-to-chromium: 1.4.703
+ node-releases: 2.0.14
+ update-browserslist-db: 1.0.13(browserslist@4.23.0)
+ dev: true
+
+ /buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ dev: false
+
+ /builtin-modules@3.3.0:
+ resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /builtins@5.0.1:
+ resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
+ dependencies:
+ semver: 7.6.0
+ dev: true
+
+ /bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /call-bind@1.0.7:
+ resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ set-function-length: 1.2.1
+
+ /callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ /camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /caniuse-lite@1.0.30001597:
+ resolution: {integrity: sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==}
+ dev: true
+
+ /ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+ dev: false
+
+ /chalk@2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: true
+
+ /chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ /character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+ dev: false
+
+ /character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+ dev: false
+
+ /chokidar@3.5.3:
+ resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+ engines: {node: '>= 8.10.0'}
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.2
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /chownr@2.0.0:
+ resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
+ engines: {node: '>=10'}
+ dev: false
+
+ /clean-css@5.3.3:
+ resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
+ engines: {node: '>= 10.0'}
+ dependencies:
+ source-map: 0.6.1
+ dev: false
+
+ /cliui@7.0.4:
+ resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+ dev: true
+
+ /color-convert@1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: true
+
+ /color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+ dependencies:
+ color-name: 1.1.4
+
+ /color-name@1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+ dev: true
+
+ /color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ /combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ delayed-stream: 1.0.0
+
+ /comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+ dev: false
+
+ /commander@2.20.3:
+ resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+ dev: false
+
+ /component-emitter@1.3.1:
+ resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
+
+ /concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ /content-disposition@0.5.4:
+ resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: false
+
+ /content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ dev: true
+
+ /cookie-parser@1.4.6:
+ resolution: {integrity: sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ cookie: 0.4.1
+ cookie-signature: 1.0.6
+ dev: false
+
+ /cookie-signature@1.0.6:
+ resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
+ dev: false
+
+ /cookie@0.4.1:
+ resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /cookie@0.4.2:
+ resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /cookie@0.5.0:
+ resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /cookiejar@2.1.4:
+ resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
+
+ /cors@2.8.5:
+ resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+ engines: {node: '>= 0.10'}
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+ dev: false
+
+ /cosmiconfig@8.3.6(typescript@5.4.2):
+ resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ typescript: 5.4.2
+ dev: true
+
+ /cross-spawn@7.0.3:
+ resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
+ engines: {node: '>= 8'}
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ /cssstyle@4.0.1:
+ resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ rrweb-cssom: 0.6.0
+ dev: false
+
+ /csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ dev: true
+
+ /data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.0.0
+ dev: false
+
+ /date-format@4.0.14:
+ resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==}
+ engines: {node: '>=4.0'}
+ dev: false
+
+ /debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.0.0
+ dev: false
+
+ /debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.3
+ dev: true
+
+ /debug@4.3.4(supports-color@8.1.1):
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+ supports-color: 8.1.1
+
+ /decamelize@4.0.0:
+ resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /decimal.js@10.4.3:
+ resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
+ dev: false
+
+ /deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+ dev: true
+
+ /define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ gopd: 1.0.1
+
+ /define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+ dev: true
+
+ /delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ /depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /dereference-json-schema@0.2.1:
+ resolution: {integrity: sha512-uzJsrg225owJyRQ8FNTPHIuBOdSzIZlHhss9u6W8mp7jJldHqGuLv9cULagP/E26QVJDnjtG8U7Dw139mM1ydA==}
+ dev: false
+
+ /destroy@1.2.0:
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dev: false
+
+ /detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+ dev: true
+
+ /devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+ dependencies:
+ dequal: 2.0.3
+ dev: false
+
+ /dezalgo@1.0.4:
+ resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}
+ dependencies:
+ asap: 2.0.6
+ wrappy: 1.0.2
+
+ /diff@5.0.0:
+ resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
+ engines: {node: '>=0.3.1'}
+ dev: true
+
+ /diff@5.2.0:
+ resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
+ engines: {node: '>=0.3.1'}
+ dev: true
+
+ /dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+ dependencies:
+ path-type: 4.0.0
+ dev: true
+
+ /doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ esutils: 2.0.3
+ dev: true
+
+ /doctrine@3.0.0:
+ resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ esutils: 2.0.3
+ dev: true
+
+ /dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+ dev: true
+
+ /ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+ dev: false
+
+ /ejs@3.1.9:
+ resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==}
+ engines: {node: '>=0.10.0'}
+ hasBin: true
+ dependencies:
+ jake: 10.8.7
+ dev: false
+
+ /electron-to-chromium@1.4.703:
+ resolution: {integrity: sha512-094ZZC4nHXPKl/OwPinSMtLN9+hoFkdfQGKnvXbY+3WEAYtVDpz9UhJIViiY6Zb8agvqxiaJzNG9M+pRZWvSZw==}
+ dev: true
+
+ /emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+ dev: true
+
+ /encodeurl@1.0.2:
+ resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /engine.io-client@6.5.3:
+ resolution: {integrity: sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==}
+ dependencies:
+ '@socket.io/component-emitter': 3.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ engine.io-parser: 5.2.2
+ ws: 8.11.0
+ xmlhttprequest-ssl: 2.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ /engine.io-parser@5.2.2:
+ resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==}
+ engines: {node: '>=10.0.0'}
+
+ /engine.io@6.5.4:
+ resolution: {integrity: sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==}
+ engines: {node: '>=10.2.0'}
+ dependencies:
+ '@types/cookie': 0.4.1
+ '@types/cors': 2.8.17
+ '@types/node': 20.11.28
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cookie: 0.4.2
+ cors: 2.8.5
+ debug: 4.3.4(supports-color@8.1.1)
+ engine.io-parser: 5.2.2
+ ws: 8.11.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: false
+
+ /enhanced-resolve@5.15.0:
+ resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.1
+ dev: true
+
+ /entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ /error-ex@1.3.2:
+ resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+ dependencies:
+ is-arrayish: 0.2.1
+ dev: true
+
+ /es-abstract@1.22.4:
+ resolution: {integrity: sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ array-buffer-byte-length: 1.0.1
+ arraybuffer.prototype.slice: 1.0.3
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.7
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.0.2
+ es-to-primitive: 1.2.1
+ function.prototype.name: 1.1.6
+ get-intrinsic: 1.2.4
+ get-symbol-description: 1.0.2
+ globalthis: 1.0.3
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.2
+ has-proto: 1.0.3
+ has-symbols: 1.0.3
+ hasown: 2.0.1
+ internal-slot: 1.0.7
+ is-array-buffer: 3.0.4
+ is-callable: 1.2.7
+ is-negative-zero: 2.0.3
+ is-regex: 1.1.4
+ is-shared-array-buffer: 1.0.2
+ is-string: 1.0.7
+ is-typed-array: 1.1.13
+ is-weakref: 1.0.2
+ object-inspect: 1.13.1
+ object-keys: 1.1.1
+ object.assign: 4.1.5
+ regexp.prototype.flags: 1.5.2
+ safe-array-concat: 1.1.0
+ safe-regex-test: 1.0.3
+ string.prototype.trim: 1.2.8
+ string.prototype.trimend: 1.0.7
+ string.prototype.trimstart: 1.0.7
+ typed-array-buffer: 1.0.2
+ typed-array-byte-length: 1.0.0
+ typed-array-byte-offset: 1.0.1
+ typed-array-length: 1.0.4
+ unbox-primitive: 1.0.2
+ which-typed-array: 1.1.14
+ dev: true
+
+ /es-array-method-boxes-properly@1.0.0:
+ resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==}
+ dev: true
+
+ /es-define-property@1.0.0:
+ resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ /es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ /es-set-tostringtag@2.0.2:
+ resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.2.4
+ has-tostringtag: 1.0.2
+ hasown: 2.0.1
+ dev: true
+
+ /es-shim-unscopables@1.0.2:
+ resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
+ dependencies:
+ hasown: 2.0.1
+ dev: true
+
+ /es-to-primitive@1.2.1:
+ resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.0.5
+ is-symbol: 1.0.4
+ dev: true
+
+ /esbuild@0.19.12:
+ resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.19.12
+ '@esbuild/android-arm': 0.19.12
+ '@esbuild/android-arm64': 0.19.12
+ '@esbuild/android-x64': 0.19.12
+ '@esbuild/darwin-arm64': 0.19.12
+ '@esbuild/darwin-x64': 0.19.12
+ '@esbuild/freebsd-arm64': 0.19.12
+ '@esbuild/freebsd-x64': 0.19.12
+ '@esbuild/linux-arm': 0.19.12
+ '@esbuild/linux-arm64': 0.19.12
+ '@esbuild/linux-ia32': 0.19.12
+ '@esbuild/linux-loong64': 0.19.12
+ '@esbuild/linux-mips64el': 0.19.12
+ '@esbuild/linux-ppc64': 0.19.12
+ '@esbuild/linux-riscv64': 0.19.12
+ '@esbuild/linux-s390x': 0.19.12
+ '@esbuild/linux-x64': 0.19.12
+ '@esbuild/netbsd-x64': 0.19.12
+ '@esbuild/openbsd-x64': 0.19.12
+ '@esbuild/sunos-x64': 0.19.12
+ '@esbuild/win32-arm64': 0.19.12
+ '@esbuild/win32-ia32': 0.19.12
+ '@esbuild/win32-x64': 0.19.12
+
+ /escalade@3.1.2:
+ resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+ dev: false
+
+ /escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+ dev: true
+
+ /escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /eslint-compat-utils@0.1.2(eslint@8.57.0):
+ resolution: {integrity: sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ eslint: '>=6.0.0'
+ dependencies:
+ eslint: 8.57.0
+ dev: true
+
+ /eslint-config-etherpad@3.0.22(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-RLB4Ry7gvdd+wkifxq16sgRGjRCLk3IofDDlbIHDQNWBhWH7avAu+tdHExj/MxJylZHIYQVhUO2uVG6WdqinVA==}
+ engines: {node: '>=12.17.0'}
+ peerDependencies:
+ eslint: ^8.46.0
+ typescript: ^4.5.5
+ dependencies:
+ '@rushstack/eslint-patch': 1.7.2
+ '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.2)
+ eslint: 8.57.0
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-plugin-cypress: 2.15.1(eslint@8.57.0)
+ eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-mocha: 10.3.0(eslint@8.57.0)
+ eslint-plugin-n: 16.6.2(eslint@8.57.0)
+ eslint-plugin-prefer-arrow: 1.2.3(eslint@8.57.0)
+ eslint-plugin-promise: 6.1.1(eslint@8.57.0)
+ eslint-plugin-you-dont-need-lodash-underscore: 6.13.0
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - eslint-import-resolver-node
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.13.1
+ resolve: 1.22.8
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
+ resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+ dependencies:
+ debug: 4.3.4(supports-color@8.1.1)
+ enhanced-resolve: 5.15.0
+ eslint: 8.57.0
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ fast-glob: 3.3.2
+ get-tsconfig: 4.7.2
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint-import-resolver-node
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.2)
+ debug: 3.2.7
+ eslint: 8.57.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-plugin-cypress@2.15.1(eslint@8.57.0):
+ resolution: {integrity: sha512-eLHLWP5Q+I4j2AWepYq0PgFEei9/s5LvjuSqWrxurkg1YZ8ltxdvMNmdSf0drnsNo57CTgYY/NIHHLRSWejR7w==}
+ peerDependencies:
+ eslint: '>= 3.2.1'
+ dependencies:
+ eslint: 8.57.0
+ globals: 13.24.0
+ dev: true
+
+ /eslint-plugin-es-x@7.5.0(eslint@8.57.0):
+ resolution: {integrity: sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '>=8'
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@eslint-community/regexpp': 4.10.0
+ eslint: 8.57.0
+ eslint-compat-utils: 0.1.2(eslint@8.57.0)
+ dev: true
+
+ /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.0):
+ resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==}
+ engines: {node: '>=6.5.0'}
+ peerDependencies:
+ eslint: '>=4.19.1'
+ dependencies:
+ escape-string-regexp: 1.0.5
+ eslint: 8.57.0
+ ignore: 5.3.1
+ dev: true
+
+ /eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+ resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.2)
+ array-includes: 3.1.7
+ array.prototype.findlastindex: 1.2.4
+ array.prototype.flat: 1.3.2
+ array.prototype.flatmap: 1.3.2
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 8.57.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ hasown: 2.0.1
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.7
+ object.groupby: 1.0.2
+ object.values: 1.1.7
+ semver: 6.3.1
+ tsconfig-paths: 3.15.0
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-plugin-mocha@10.3.0(eslint@8.57.0):
+ resolution: {integrity: sha512-IWzbg2K6B1Q7h37Ih4zMyW+nhmw1JvUlHlbCUUUu6PfOOAUGCB0gxmvv7/U+TQQ6e8yHUv+q7KMdIIum4bx+PA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ eslint: '>=7.0.0'
+ dependencies:
+ eslint: 8.57.0
+ eslint-utils: 3.0.0(eslint@8.57.0)
+ rambda: 7.5.0
+ dev: true
+
+ /eslint-plugin-n@16.6.2(eslint@8.57.0):
+ resolution: {integrity: sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==}
+ engines: {node: '>=16.0.0'}
+ peerDependencies:
+ eslint: '>=7.0.0'
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ builtins: 5.0.1
+ eslint: 8.57.0
+ eslint-plugin-es-x: 7.5.0(eslint@8.57.0)
+ get-tsconfig: 4.7.2
+ globals: 13.24.0
+ ignore: 5.3.1
+ is-builtin-module: 3.2.1
+ is-core-module: 2.13.1
+ minimatch: 3.1.2
+ resolve: 1.22.8
+ semver: 7.6.0
+ dev: true
+
+ /eslint-plugin-prefer-arrow@1.2.3(eslint@8.57.0):
+ resolution: {integrity: sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==}
+ peerDependencies:
+ eslint: '>=2.0.0'
+ dependencies:
+ eslint: 8.57.0
+ dev: true
+
+ /eslint-plugin-promise@6.1.1(eslint@8.57.0):
+ resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ dependencies:
+ eslint: 8.57.0
+ dev: true
+
+ /eslint-plugin-react-hooks@4.6.0(eslint@8.57.0):
+ resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+ dependencies:
+ eslint: 8.57.0
+ dev: true
+
+ /eslint-plugin-react-refresh@0.4.6(eslint@8.57.0):
+ resolution: {integrity: sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==}
+ peerDependencies:
+ eslint: '>=7'
+ dependencies:
+ eslint: 8.57.0
+ dev: true
+
+ /eslint-plugin-you-dont-need-lodash-underscore@6.13.0:
+ resolution: {integrity: sha512-6FkFLp/R/QlgfJl5NrxkIXMQ36jMVLczkWDZJvMd7/wr/M3K0DS7mtX7plZ3giTDcbDD7VBfNYUfUVaBCZOXKA==}
+ engines: {node: '>=4.0'}
+ dependencies:
+ kebab-case: 1.0.2
+ dev: true
+
+ /eslint-scope@5.1.1:
+ resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
+ engines: {node: '>=8.0.0'}
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 4.3.0
+ dev: true
+
+ /eslint-scope@7.2.2:
+ resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+ dev: true
+
+ /eslint-utils@3.0.0(eslint@8.57.0):
+ resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
+ engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
+ peerDependencies:
+ eslint: '>=5'
+ dependencies:
+ eslint: 8.57.0
+ eslint-visitor-keys: 2.1.0
+ dev: true
+
+ /eslint-visitor-keys@2.1.0:
+ resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dev: true
+
+ /eslint@8.57.0:
+ resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ hasBin: true
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@eslint-community/regexpp': 4.10.0
+ '@eslint/eslintrc': 2.1.4
+ '@eslint/js': 8.57.0
+ '@humanwhocodes/config-array': 0.11.14
+ '@humanwhocodes/module-importer': 1.0.1
+ '@nodelib/fs.walk': 1.2.8
+ '@ungap/structured-clone': 1.2.0
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.3
+ debug: 4.3.4(supports-color@8.1.1)
+ doctrine: 3.0.0
+ escape-string-regexp: 4.0.0
+ eslint-scope: 7.2.2
+ eslint-visitor-keys: 3.4.3
+ espree: 9.6.1
+ esquery: 1.5.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 6.0.1
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ globals: 13.24.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ js-yaml: 4.1.0
+ json-stable-stringify-without-jsonify: 1.0.1
+ levn: 0.4.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.3
+ strip-ansi: 6.0.1
+ text-table: 0.2.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /esm@3.2.25:
+ resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /espree@9.6.1:
+ resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ acorn: 8.11.3
+ acorn-jsx: 5.3.2(acorn@8.11.3)
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /esquery@1.5.0:
+ resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
+ engines: {node: '>=0.10'}
+ dependencies:
+ estraverse: 5.3.0
+ dev: true
+
+ /esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+ dependencies:
+ estraverse: 5.3.0
+ dev: true
+
+ /estraverse@4.3.0:
+ resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
+ engines: {node: '>=4.0'}
+ dev: true
+
+ /estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+ dev: true
+
+ /estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+ dev: true
+
+ /esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /etherpad-cli-client@3.0.2:
+ resolution: {integrity: sha512-3uJxz8cx1WSVAPe7haqfwQj4N4wWDrei5BXBjH+e1BLPiEpqWtnMX29Qo9jWhqO9ctyPPHO0nZ1x/ZCNxMVCqA==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+ dependencies:
+ async: 3.2.5
+ socket.io-client: 4.7.5
+ superagent: 8.1.2
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: true
+
+ /etherpad-require-kernel@1.0.16:
+ resolution: {integrity: sha512-Zym7acX8tsB0mjZmQgcHnx9W+8djpFGeSA6/LhbKEGALaoaKBxCH/QQwJmsqfbvfYQBEg+NbUfDp9gD8QVjhsg==}
+ engines: {node: '>=12.13.0'}
+ dev: false
+
+ /etherpad-yajsml@0.0.12:
+ resolution: {integrity: sha512-lVCqsZYpFsuIz417h+O83I7eadNXJ3MnQavriFa52/KTwj6xPAzEYr0PvH7KTxcqyAFtW7ItoTNVXe2h7zGxlw==}
+ engines: {node: '>=12.13.0'}
+ optionalDependencies:
+ mime: 1.6.0
+ dev: false
+
+ /express-rate-limit@7.2.0(express@4.18.3):
+ resolution: {integrity: sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ express: 4 || 5 || ^5.0.0-beta.1
+ dependencies:
+ express: 4.18.3
+ dev: false
+
+ /express@4.18.3:
+ resolution: {integrity: sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==}
+ engines: {node: '>= 0.10.0'}
+ dependencies:
+ accepts: 1.3.8
+ array-flatten: 1.1.1
+ body-parser: 1.20.2
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ cookie: 0.5.0
+ cookie-signature: 1.0.6
+ debug: 2.6.9
+ depd: 2.0.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 1.2.0
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ merge-descriptors: 1.0.1
+ methods: 1.1.2
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ path-to-regexp: 0.1.7
+ proxy-addr: 2.0.7
+ qs: 6.11.0
+ range-parser: 1.2.1
+ safe-buffer: 5.2.1
+ send: 0.18.0
+ serve-static: 1.15.0
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ type-is: 1.6.18
+ utils-merge: 1.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+ dev: false
+
+ /fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ /fast-glob@3.3.2:
+ resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.5
+ dev: true
+
+ /fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+ dev: true
+
+ /fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ dev: true
+
+ /fast-safe-stringify@2.1.1:
+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+
+ /fastq@1.17.1:
+ resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+ dependencies:
+ reusify: 1.0.4
+ dev: true
+
+ /file-entry-cache@6.0.1:
+ resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+ dependencies:
+ flat-cache: 3.2.0
+ dev: true
+
+ /filelist@1.0.4:
+ resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
+ dependencies:
+ minimatch: 5.1.6
+ dev: false
+
+ /fill-range@7.0.1:
+ resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ to-regex-range: 5.0.1
+ dev: true
+
+ /finalhandler@1.2.0:
+ resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ debug: 2.6.9
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.1
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /find-root@1.1.0:
+ resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
+ dev: false
+
+ /find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+ dev: true
+
+ /flat-cache@3.2.0:
+ resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+ dependencies:
+ flatted: 3.2.9
+ keyv: 4.5.4
+ rimraf: 3.0.2
+ dev: true
+
+ /flat@5.0.2:
+ resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
+ hasBin: true
+ dev: true
+
+ /flatted@3.2.9:
+ resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
+
+ /follow-redirects@1.15.6:
+ resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+ dev: false
+
+ /for-each@0.3.3:
+ resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
+ dependencies:
+ is-callable: 1.2.7
+ dev: true
+
+ /form-data@4.0.0:
+ resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+ engines: {node: '>= 6'}
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+
+ /formidable@2.1.2:
+ resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==}
+ dependencies:
+ dezalgo: 1.0.4
+ hexoid: 1.0.0
+ once: 1.4.0
+ qs: 6.11.2
+
+ /formidable@3.5.1:
+ resolution: {integrity: sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==}
+ dependencies:
+ dezalgo: 1.0.4
+ hexoid: 1.0.0
+ once: 1.4.0
+ dev: false
+
+ /forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /fresh@0.5.2:
+ resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /fs-extra@10.1.0:
+ resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.1.0
+ universalify: 2.0.1
+ dev: false
+
+ /fs-extra@11.2.0:
+ resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
+ engines: {node: '>=14.14'}
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.1.0
+ universalify: 2.0.1
+ dev: true
+
+ /fs-extra@8.1.0:
+ resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
+ engines: {node: '>=6 <7 || >=8'}
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+ dev: false
+
+ /fs-minipass@2.1.0:
+ resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ minipass: 3.3.6
+ dev: false
+
+ /fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ dev: true
+
+ /fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ /function.prototype.name@1.1.6:
+ resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ functions-have-names: 1.2.3
+ dev: true
+
+ /functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+ dev: true
+
+ /gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+ dev: true
+
+ /get-intrinsic@1.2.4:
+ resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ has-proto: 1.0.3
+ has-symbols: 1.0.3
+ hasown: 2.0.1
+
+ /get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /get-symbol-description@1.0.2:
+ resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ dev: true
+
+ /get-tsconfig@4.7.2:
+ resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ /glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
+ /glob@8.1.0:
+ resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 5.0.1
+ once: 1.4.0
+ dev: true
+
+ /globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /globals@13.24.0:
+ resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ type-fest: 0.20.2
+ dev: true
+
+ /globalthis@1.0.3:
+ resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-properties: 1.2.1
+ dev: true
+
+ /globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.2
+ ignore: 5.3.1
+ merge2: 1.4.1
+ slash: 3.0.0
+ dev: true
+
+ /gopd@1.0.1:
+ resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ /graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ /graphemer@1.4.0:
+ resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+ dev: true
+
+ /has-bigints@1.0.2:
+ resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+ dev: true
+
+ /has-flag@3.0.0:
+ resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ /has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+ dependencies:
+ es-define-property: 1.0.0
+
+ /has-proto@1.0.3:
+ resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+ engines: {node: '>= 0.4'}
+
+ /has-symbols@1.0.3:
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ engines: {node: '>= 0.4'}
+
+ /has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
+ /hasown@2.0.1:
+ resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ function-bind: 1.1.2
+
+ /hast-util-embedded@3.0.0:
+ resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==}
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-is-element: 3.0.0
+ dev: false
+
+ /hast-util-from-html@2.0.1:
+ resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==}
+ dependencies:
+ '@types/hast': 3.0.4
+ devlop: 1.1.0
+ hast-util-from-parse5: 8.0.1
+ parse5: 7.1.2
+ vfile: 6.0.1
+ vfile-message: 4.0.2
+ dev: false
+
+ /hast-util-from-parse5@8.0.1:
+ resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==}
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.2
+ devlop: 1.1.0
+ hastscript: 8.0.0
+ property-information: 6.4.1
+ vfile: 6.0.1
+ vfile-location: 5.0.2
+ web-namespaces: 2.0.1
+ dev: false
+
+ /hast-util-is-element@3.0.0:
+ resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
+ dependencies:
+ '@types/hast': 3.0.4
+ dev: false
+
+ /hast-util-parse-selector@4.0.0:
+ resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+ dependencies:
+ '@types/hast': 3.0.4
+ dev: false
+
+ /hast-util-raw@9.0.2:
+ resolution: {integrity: sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==}
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.2
+ '@ungap/structured-clone': 1.2.0
+ hast-util-from-parse5: 8.0.1
+ hast-util-to-parse5: 8.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.1.0
+ parse5: 7.1.2
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.1
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+ dev: false
+
+ /hast-util-to-html@9.0.0:
+ resolution: {integrity: sha512-IVGhNgg7vANuUA2XKrT6sOIIPgaYZnmLx3l/CCOAK0PtgfoHrZwX7jCSYyFxHTrGmC6S9q8aQQekjp4JPZF+cw==}
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.2
+ ccount: 2.0.1
+ comma-separated-tokens: 2.0.3
+ hast-util-raw: 9.0.2
+ hast-util-whitespace: 3.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.1.0
+ property-information: 6.4.1
+ space-separated-tokens: 2.0.2
+ stringify-entities: 4.0.3
+ zwitch: 2.0.4
+ dev: false
+
+ /hast-util-to-parse5@8.0.0:
+ resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ property-information: 6.4.1
+ space-separated-tokens: 2.0.2
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+ dev: false
+
+ /hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+ dependencies:
+ '@types/hast': 3.0.4
+ dev: false
+
+ /hastscript@8.0.0:
+ resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==}
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ hast-util-parse-selector: 4.0.0
+ property-information: 6.4.1
+ space-separated-tokens: 2.0.2
+ dev: false
+
+ /he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+ dev: true
+
+ /hexoid@1.0.0:
+ resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==}
+ engines: {node: '>=8'}
+
+ /html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ whatwg-encoding: 3.1.1
+ dev: false
+
+ /html-parse-stringify@3.0.1:
+ resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
+ dependencies:
+ void-elements: 3.1.0
+ dev: true
+
+ /html-void-elements@3.0.0:
+ resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+ dev: false
+
+ /http-errors@2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+ dev: false
+
+ /http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+ dependencies:
+ agent-base: 7.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /https-proxy-agent@7.0.4:
+ resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
+ engines: {node: '>= 14'}
+ dependencies:
+ agent-base: 7.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /i18next-browser-languagedetector@7.2.0:
+ resolution: {integrity: sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==}
+ dependencies:
+ '@babel/runtime': 7.24.0
+ dev: true
+
+ /i18next@23.10.1:
+ resolution: {integrity: sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==}
+ dependencies:
+ '@babel/runtime': 7.24.0
+ dev: true
+
+ /iconv-lite@0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ safer-buffer: 2.1.2
+ dev: false
+
+ /iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ safer-buffer: 2.1.2
+ dev: false
+
+ /ignore@5.3.1:
+ resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
+ engines: {node: '>= 4'}
+ dev: true
+
+ /import-fresh@3.3.0:
+ resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+ engines: {node: '>=6'}
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+ dev: true
+
+ /imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+ dev: true
+
+ /inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+ dev: true
+
+ /inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ /internal-slot@1.0.7:
+ resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.1
+ side-channel: 1.0.5
+ dev: true
+
+ /invariant@2.2.4:
+ resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: true
+
+ /ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+ dev: false
+
+ /is-array-buffer@3.0.4:
+ resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
+ dev: true
+
+ /is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ dev: true
+
+ /is-bigint@1.0.4:
+ resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+ dependencies:
+ has-bigints: 1.0.2
+ dev: true
+
+ /is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+ dependencies:
+ binary-extensions: 2.2.0
+ dev: true
+
+ /is-boolean-object@1.1.2:
+ resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ has-tostringtag: 1.0.2
+ dev: true
+
+ /is-builtin-module@3.2.1:
+ resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
+ engines: {node: '>=6'}
+ dependencies:
+ builtin-modules: 3.3.0
+ dev: true
+
+ /is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /is-core-module@2.13.1:
+ resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
+ dependencies:
+ hasown: 2.0.1
+
+ /is-date-object@1.0.5:
+ resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+ dev: true
+
+ /is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+ dev: true
+
+ /is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /is-number-object@1.0.7:
+ resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+ dev: true
+
+ /is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+ dev: true
+
+ /is-observable@2.1.0:
+ resolution: {integrity: sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==}
+ engines: {node: '>=8'}
+ dev: false
+
+ /is-path-inside@3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-plain-obj@2.1.0:
+ resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+ dev: false
+
+ /is-promise@1.0.1:
+ resolution: {integrity: sha512-mjWH5XxnhMA8cFnDchr6qRP9S/kLntKuEfIYku+PaN1CnS8v+OG9O/BKpRCVRJvpIkgAZm0Pf5Is3iSSOILlcg==}
+ dev: true
+
+ /is-regex@1.1.4:
+ resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ has-tostringtag: 1.0.2
+ dev: true
+
+ /is-shared-array-buffer@1.0.2:
+ resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
+ dependencies:
+ call-bind: 1.0.7
+ dev: true
+
+ /is-string@1.0.7:
+ resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+ dev: true
+
+ /is-symbol@1.0.4:
+ resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
+ /is-typed-array@1.1.13:
+ resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ which-typed-array: 1.1.14
+ dev: true
+
+ /is-unicode-supported@0.1.0:
+ resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /is-weakref@1.0.2:
+ resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+ dependencies:
+ call-bind: 1.0.7
+ dev: true
+
+ /isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+ dev: true
+
+ /isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ /jake@10.8.7:
+ resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ async: 3.2.5
+ chalk: 4.1.2
+ filelist: 1.0.4
+ minimatch: 3.1.2
+ dev: false
+
+ /js-cookie@3.0.5:
+ resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+ engines: {node: '>=14'}
+ dev: false
+
+ /js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+ dev: true
+
+ /js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+ dependencies:
+ argparse: 2.0.1
+
+ /jsdom@24.0.0:
+ resolution: {integrity: sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^2.11.2
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+ dependencies:
+ cssstyle: 4.0.1
+ data-urls: 5.0.0
+ decimal.js: 10.4.3
+ form-data: 4.0.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.4
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.7
+ parse5: 7.1.2
+ rrweb-cssom: 0.6.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 4.1.3
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.0.0
+ ws: 8.16.0
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: false
+
+ /jsesc@2.5.2:
+ resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+ dev: true
+
+ /json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+ dev: true
+
+ /json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ dev: true
+
+ /json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ dev: false
+
+ /json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+ dev: true
+
+ /json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.8
+ dev: true
+
+ /json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+ dev: true
+
+ /jsonfile@4.0.0:
+ resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+ optionalDependencies:
+ graceful-fs: 4.2.11
+ dev: false
+
+ /jsonfile@6.1.0:
+ resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ /jsonminify@0.4.2:
+ resolution: {integrity: sha512-mEtP5ECD0293D+s45JhDutqF5mFCkWY8ClrPFxjSFR2KUoantofky7noSzyKnAnD9Gd8pXHZSUd5bgzLDUBbfA==}
+ engines: {node: '>=0.8.0', npm: '>=1.1.0'}
+ dev: false
+
+ /jsonschema-draft4@1.0.0:
+ resolution: {integrity: sha512-sBV3UnQPRiyCTD6uzY/Oao2Yohv6KKgQq7zjPwjFHeR6scg/QSXnzDxdugsGaLQDmFUrUlTbMYdEE+72PizhGA==}
+ dev: true
+
+ /jsonschema@1.2.4:
+ resolution: {integrity: sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==}
+ dev: true
+
+ /just-extend@6.2.0:
+ resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==}
+ dev: true
+
+ /kebab-case@1.0.2:
+ resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==}
+ dev: true
+
+ /keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ dependencies:
+ json-buffer: 3.0.1
+ dev: true
+
+ /languages4translatewiki@0.1.3:
+ resolution: {integrity: sha512-Z7+IM3FF+VyRbWl2CPWQoRf498zGy/qnolP5wJhMny4W3v0SL9rUCIPDHPao9rrw2yg2KIKnHIzopuWvGdnooQ==}
+ dev: false
+
+ /levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ dev: true
+
+ /lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ dev: true
+
+ /live-plugin-manager-pnpm@0.18.1:
+ resolution: {integrity: sha512-B+H8lL1LrXS8VIXyyVAoX9NPQx4aVFRvjbQEfBLiaxEMEH/MqI/+Yxyetps9/TfVdes8LCLzDMTWoo/lxZpnSw==}
+ dependencies:
+ '@types/debug': 4.1.12
+ '@types/fs-extra': 9.0.13
+ '@types/lockfile': 1.0.4
+ '@types/node-fetch': 2.6.11
+ '@types/semver': 7.5.8
+ '@types/tar': 6.1.11
+ '@types/url-join': 4.0.1
+ debug: 4.3.4(supports-color@8.1.1)
+ fs-extra: 10.1.0
+ lockfile: 1.0.4
+ node-fetch: 2.7.0
+ semver: 7.6.0
+ tar: 6.2.0
+ url-join: 4.0.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+
+ /locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+ dependencies:
+ p-locate: 5.0.0
+ dev: true
+
+ /lockfile@1.0.4:
+ resolution: {integrity: sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==}
+ dependencies:
+ signal-exit: 3.0.7
+ dev: false
+
+ /lodash.clonedeep@4.5.0:
+ resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
+ dev: false
+
+ /lodash.get@4.4.2:
+ resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
+ dev: true
+
+ /lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ /lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ dev: false
+
+ /log-symbols@4.1.0:
+ resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
+ engines: {node: '>=10'}
+ dependencies:
+ chalk: 4.1.2
+ is-unicode-supported: 0.1.0
+ dev: true
+
+ /log4js@6.9.1:
+ resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ date-format: 4.0.14
+ debug: 4.3.4(supports-color@8.1.1)
+ flatted: 3.2.9
+ rfdc: 1.3.1
+ streamroller: 3.1.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+ dependencies:
+ js-tokens: 4.0.0
+ dev: true
+
+ /lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+ dependencies:
+ tslib: 2.6.2
+ dev: true
+
+ /lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ dependencies:
+ yallist: 3.1.1
+ dev: true
+
+ /lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+ dependencies:
+ yallist: 4.0.0
+
+ /lucide-react@0.356.0(react@18.2.0):
+ resolution: {integrity: sha512-MDInjLrmZToccH2UxEshntujBlFwtOofGB22FN/eg39FfGVYV1TT1eMIv2j4rdaTJBpYjUuX7fEo9pwYkNFgwA==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: true
+
+ /mdast-util-to-hast@13.1.0:
+ resolution: {integrity: sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==}
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.3
+ '@ungap/structured-clone': 1.2.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.0
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.1
+ dev: false
+
+ /measured-core@2.0.0:
+ resolution: {integrity: sha512-SIzGtX1WGDvR59FqcJaGEAqDueBvLBh6W4T/gQaHr5ufcqvQkUHGcfQhlmq77mkeF5Mo+UpD+8hm69CwUVibGw==}
+ engines: {node: '>= 5.12'}
+ dependencies:
+ binary-search: 1.3.6
+ optional-js: 2.3.0
+ dev: false
+
+ /media-typer@0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /merge-descriptors@1.0.1:
+ resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
+ dev: false
+
+ /merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /methods@1.1.2:
+ resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+ engines: {node: '>= 0.6'}
+
+ /micromark-util-character@2.1.0:
+ resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==}
+ dependencies:
+ micromark-util-symbol: 2.0.0
+ micromark-util-types: 2.0.0
+ dev: false
+
+ /micromark-util-encode@2.0.0:
+ resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==}
+ dev: false
+
+ /micromark-util-sanitize-uri@2.0.0:
+ resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==}
+ dependencies:
+ micromark-util-character: 2.1.0
+ micromark-util-encode: 2.0.0
+ micromark-util-symbol: 2.0.0
+ dev: false
+
+ /micromark-util-symbol@2.0.0:
+ resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==}
+ dev: false
+
+ /micromark-util-types@2.0.0:
+ resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==}
+ dev: false
+
+ /micromatch@4.0.5:
+ resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ /mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-db: 1.52.0
+
+ /mime@1.6.0:
+ resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: false
+
+ /mime@2.6.0:
+ resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
+ engines: {node: '>=4.0.0'}
+ hasBin: true
+
+ /minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ dependencies:
+ brace-expansion: 1.1.11
+
+ /minimatch@5.0.1:
+ resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
+ engines: {node: '>=10'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
+ /minimatch@5.1.6:
+ resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+ engines: {node: '>=10'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: false
+
+ /minimatch@9.0.3:
+ resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
+ /minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ dev: true
+
+ /minipass@3.3.6:
+ resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
+ engines: {node: '>=8'}
+ dependencies:
+ yallist: 4.0.0
+ dev: false
+
+ /minipass@4.2.8:
+ resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==}
+ engines: {node: '>=8'}
+ dev: false
+
+ /minipass@5.0.0:
+ resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
+ engines: {node: '>=8'}
+ dev: false
+
+ /minizlib@2.1.2:
+ resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ minipass: 3.3.6
+ yallist: 4.0.0
+ dev: false
+
+ /mkdirp@1.0.4:
+ resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dev: false
+
+ /mocha-froth@0.2.10:
+ resolution: {integrity: sha512-xyJqAYtm2zjrkG870hjeSVvGgS4Dc9tRokmN6R7XLgBKhdtAJ1ytU6zL045djblfHaPyTkSerQU4wqcjsv7Aew==}
+ dev: true
+
+ /mocha@10.3.0:
+ resolution: {integrity: sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==}
+ engines: {node: '>= 14.0.0'}
+ hasBin: true
+ dependencies:
+ ansi-colors: 4.1.1
+ browser-stdout: 1.3.1
+ chokidar: 3.5.3
+ debug: 4.3.4(supports-color@8.1.1)
+ diff: 5.0.0
+ escape-string-regexp: 4.0.0
+ find-up: 5.0.0
+ glob: 8.1.0
+ he: 1.2.0
+ js-yaml: 4.1.0
+ log-symbols: 4.1.0
+ minimatch: 5.0.1
+ ms: 2.1.3
+ serialize-javascript: 6.0.0
+ strip-json-comments: 3.1.1
+ supports-color: 8.1.1
+ workerpool: 6.2.1
+ yargs: 16.2.0
+ yargs-parser: 20.2.4
+ yargs-unparser: 2.0.0
+ dev: true
+
+ /mock-json-schema@1.1.1:
+ resolution: {integrity: sha512-YV23vlsLP1EEOy0EviUvZTluXjLR+rhMzeayP2rcDiezj3RW01MhOSQkbQskdtg0K2fnGas5LKbSXgNjAOSX4A==}
+ dependencies:
+ lodash: 4.17.21
+ dev: false
+
+ /ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+ dev: false
+
+ /ms@2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+
+ /ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ /nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+ dev: true
+
+ /natural-compare-lite@1.4.0:
+ resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+ dev: true
+
+ /natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ dev: true
+
+ /negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /nise@5.1.9:
+ resolution: {integrity: sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==}
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+ '@sinonjs/fake-timers': 11.2.2
+ '@sinonjs/text-encoding': 0.7.2
+ just-extend: 6.2.0
+ path-to-regexp: 6.2.1
+ dev: true
+
+ /no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.6.2
+ dev: true
+
+ /node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+ dependencies:
+ whatwg-url: 5.0.0
+ dev: false
+
+ /node-releases@2.0.14:
+ resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+ dev: true
+
+ /nodeify@1.0.1:
+ resolution: {integrity: sha512-n7C2NyEze8GCo/z73KdbjRsBiLbv6eBn1FxwYKQ23IqGo7pQY3mhQan61Sv7eEDJCiyUjTVrVkXTzJCo1dW7Aw==}
+ dependencies:
+ is-promise: 1.0.1
+ promise: 1.3.0
+ dev: true
+
+ /normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /nwsapi@2.2.7:
+ resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
+ dev: false
+
+ /object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /object-inspect@1.13.1:
+ resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
+
+ /object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /object.assign@4.1.5:
+ resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ has-symbols: 1.0.3
+ object-keys: 1.1.1
+ dev: true
+
+ /object.fromentries@2.0.7:
+ resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ dev: true
+
+ /object.groupby@1.0.2:
+ resolution: {integrity: sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==}
+ dependencies:
+ array.prototype.filter: 1.0.3
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ es-errors: 1.3.0
+ dev: true
+
+ /object.values@1.1.7:
+ resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ dev: true
+
+ /observable-fns@0.6.1:
+ resolution: {integrity: sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==}
+ dev: false
+
+ /on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ ee-first: 1.1.1
+ dev: false
+
+ /on-headers@1.0.2:
+ resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+ dependencies:
+ wrappy: 1.0.2
+
+ /openapi-backend@5.10.6:
+ resolution: {integrity: sha512-vTjBRys/O4JIHdlRHUKZ7pxS+gwIJreAAU9dvYRFrImtPzQ5qxm5a6B8BTVT9m6I8RGGsShJv35MAc3Tu2/y/A==}
+ engines: {node: '>=12.0.0'}
+ dependencies:
+ '@apidevtools/json-schema-ref-parser': 11.1.0
+ ajv: 8.12.0
+ bath-es5: 3.0.3
+ cookie: 0.5.0
+ dereference-json-schema: 0.2.1
+ lodash: 4.17.21
+ mock-json-schema: 1.1.1
+ openapi-schema-validator: 12.1.3
+ openapi-types: 12.1.3
+ qs: 6.11.2
+ dev: false
+
+ /openapi-schema-validation@0.4.2:
+ resolution: {integrity: sha512-K8LqLpkUf2S04p2Nphq9L+3bGFh/kJypxIG2NVGKX0ffzT4NQI9HirhiY6Iurfej9lCu7y4Ndm4tv+lm86Ck7w==}
+ dependencies:
+ jsonschema: 1.2.4
+ jsonschema-draft4: 1.0.0
+ swagger-schema-official: 2.0.0-bab6bed
+ dev: true
+
+ /openapi-schema-validator@12.1.3:
+ resolution: {integrity: sha512-xTHOmxU/VQGUgo7Cm0jhwbklOKobXby+/237EG967+3TQEYJztMgX9Q5UE2taZKwyKPUq0j11dngpGjUuxz1hQ==}
+ dependencies:
+ ajv: 8.12.0
+ ajv-formats: 2.1.1(ajv@8.12.0)
+ lodash.merge: 4.6.2
+ openapi-types: 12.1.3
+ dev: false
+
+ /openapi-types@12.1.3:
+ resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
+ dev: false
+
+ /optional-js@2.3.0:
+ resolution: {integrity: sha512-B0LLi+Vg+eko++0z/b8zIv57kp7HKEzaPJo7LowJXMUKYdf+3XJGu/cw03h/JhIOsLnP+cG5QnTHAuicjA5fMw==}
+ dev: false
+
+ /optionator@0.9.3:
+ resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ '@aashutoshrathi/word-wrap': 1.2.6
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ dev: true
+
+ /p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ yocto-queue: 0.1.0
+ dev: true
+
+ /p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+ dependencies:
+ p-limit: 3.1.0
+ dev: true
+
+ /parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+ dependencies:
+ callsites: 3.1.0
+ dev: true
+
+ /parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ error-ex: 1.3.2
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+ dev: true
+
+ /parse5@7.1.2:
+ resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
+ dependencies:
+ entities: 4.5.0
+
+ /parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ /path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ /path-to-regexp@0.1.7:
+ resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
+ dev: false
+
+ /path-to-regexp@6.2.1:
+ resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
+ dev: true
+
+ /path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /picocolors@1.0.0:
+ resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+ dev: true
+
+ /picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+ dev: true
+
+ /playwright-core@1.42.1:
+ resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dev: true
+
+ /playwright@1.42.1:
+ resolution: {integrity: sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dependencies:
+ playwright-core: 1.42.1
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /possible-typed-array-names@1.0.0:
+ resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /postcss@8.4.35:
+ resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+ dev: true
+
+ /prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+ dev: true
+
+ /promise@1.3.0:
+ resolution: {integrity: sha512-R9WrbTF3EPkVtWjp7B7umQGVndpsi+rsDAfrR4xAALQpFLa/+2OriecLhawxzvii2gd9+DZFwROWDuUUaqS5yA==}
+ dependencies:
+ is-promise: 1.0.1
+ dev: true
+
+ /property-information@6.4.1:
+ resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==}
+ dev: false
+
+ /proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+ dev: false
+
+ /proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ dev: false
+
+ /psl@1.9.0:
+ resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
+ dev: false
+
+ /punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ /qs@6.11.0:
+ resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.5
+ dev: false
+
+ /qs@6.11.2:
+ resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.5
+
+ /querystringify@2.2.0:
+ resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
+ dev: false
+
+ /queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ dev: true
+
+ /rambda@7.5.0:
+ resolution: {integrity: sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==}
+ dev: true
+
+ /random-bytes@1.0.0:
+ resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /randombytes@2.1.0:
+ resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: true
+
+ /range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /rate-limiter-flexible@5.0.0:
+ resolution: {integrity: sha512-ivCyLBwPtR5IRrz+aZnztVwX16ZK3iAjdlW21I/vjHq56at5Zb8eIefDzODg8R7hwPOHpBtb6Pj9Zdmn0nRb8g==}
+ dev: false
+
+ /raw-body@2.5.2:
+ resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+ dev: false
+
+ /react-dom@18.2.0(react@18.2.0):
+ resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
+ peerDependencies:
+ react: ^18.2.0
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.2.0
+ scheduler: 0.23.0
+ dev: true
+
+ /react-hook-form@7.51.0(react@18.2.0):
+ resolution: {integrity: sha512-BggOy5j58RdhdMzzRUHGOYhSz1oeylFAv6jUSG86OvCIvlAvS7KvnRY7yoAf2pfEiPN7BesnR0xx73nEk3qIiw==}
+ engines: {node: '>=12.22.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ dependencies:
+ react: 18.2.0
+ dev: true
+
+ /react-i18next@14.1.0(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ==}
+ peerDependencies:
+ i18next: '>= 23.2.3'
+ react: '>= 16.8.0'
+ react-dom: '*'
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.0
+ html-parse-stringify: 3.0.1
+ i18next: 23.10.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: true
+
+ /react-remove-scroll-bar@2.3.5(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.65
+ react: 18.2.0
+ react-style-singleton: 2.2.1(@types/react@18.2.65)(react@18.2.0)
+ tslib: 2.6.2
+ dev: true
+
+ /react-remove-scroll@2.5.5(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.65
+ react: 18.2.0
+ react-remove-scroll-bar: 2.3.5(@types/react@18.2.65)(react@18.2.0)
+ react-style-singleton: 2.2.1(@types/react@18.2.65)(react@18.2.0)
+ tslib: 2.6.2
+ use-callback-ref: 1.3.1(@types/react@18.2.65)(react@18.2.0)
+ use-sidecar: 1.1.2(@types/react@18.2.65)(react@18.2.0)
+ dev: true
+
+ /react-router-dom@6.22.3(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ react-dom: '>=16.8'
+ dependencies:
+ '@remix-run/router': 1.15.3
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-router: 6.22.3(react@18.2.0)
+ dev: true
+
+ /react-router@6.22.3(react@18.2.0):
+ resolution: {integrity: sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ dependencies:
+ '@remix-run/router': 1.15.3
+ react: 18.2.0
+ dev: true
+
+ /react-style-singleton@2.2.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.65
+ get-nonce: 1.0.1
+ invariant: 2.2.4
+ react: 18.2.0
+ tslib: 2.6.2
+ dev: true
+
+ /react@18.2.0:
+ resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: true
+
+ /readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ dependencies:
+ picomatch: 2.3.1
+ dev: true
+
+ /regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+ dev: true
+
+ /regexp.prototype.flags@1.5.2:
+ resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ set-function-name: 2.0.2
+ dev: true
+
+ /rehype-minify-whitespace@6.0.0:
+ resolution: {integrity: sha512-i9It4YHR0Sf3GsnlR5jFUKXRr9oayvEk9GKQUkwZv6hs70OH9q3OCZrq9PpLvIGKt3W+JxBOxCidNVpH/6rWdA==}
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-embedded: 3.0.0
+ hast-util-is-element: 3.0.0
+ hast-util-whitespace: 3.0.0
+ unist-util-is: 6.0.0
+ dev: false
+
+ /rehype-parse@9.0.0:
+ resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==}
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-from-html: 2.0.1
+ unified: 11.0.4
+ dev: false
+
+ /rehype-stringify@10.0.0:
+ resolution: {integrity: sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==}
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.0
+ unified: 11.0.4
+ dev: false
+
+ /rehype@13.0.1:
+ resolution: {integrity: sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg==}
+ dependencies:
+ '@types/hast': 3.0.4
+ rehype-parse: 9.0.0
+ rehype-stringify: 10.0.0
+ unified: 11.0.4
+ dev: false
+
+ /require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /requires-port@1.0.0:
+ resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+ dev: false
+
+ /resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ /resolve@1.22.8:
+ resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.13.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ /reusify@1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ dev: true
+
+ /rfdc@1.3.1:
+ resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
+ dev: false
+
+ /rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ hasBin: true
+ dependencies:
+ glob: 7.2.3
+ dev: true
+
+ /rollup@4.13.0:
+ resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+ dependencies:
+ '@types/estree': 1.0.5
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.13.0
+ '@rollup/rollup-android-arm64': 4.13.0
+ '@rollup/rollup-darwin-arm64': 4.13.0
+ '@rollup/rollup-darwin-x64': 4.13.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.13.0
+ '@rollup/rollup-linux-arm64-gnu': 4.13.0
+ '@rollup/rollup-linux-arm64-musl': 4.13.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.13.0
+ '@rollup/rollup-linux-x64-gnu': 4.13.0
+ '@rollup/rollup-linux-x64-musl': 4.13.0
+ '@rollup/rollup-win32-arm64-msvc': 4.13.0
+ '@rollup/rollup-win32-ia32-msvc': 4.13.0
+ '@rollup/rollup-win32-x64-msvc': 4.13.0
+ fsevents: 2.3.3
+ dev: true
+
+ /rrweb-cssom@0.6.0:
+ resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
+ dev: false
+
+ /run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+ dev: true
+
+ /safe-array-concat@1.1.0:
+ resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==}
+ engines: {node: '>=0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
+ has-symbols: 1.0.3
+ isarray: 2.0.5
+ dev: true
+
+ /safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ /safe-regex-test@1.0.3:
+ resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ is-regex: 1.1.4
+ dev: true
+
+ /safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ dev: false
+
+ /saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+ dependencies:
+ xmlchars: 2.2.0
+ dev: false
+
+ /scheduler@0.23.0:
+ resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: true
+
+ /security@1.0.0:
+ resolution: {integrity: sha512-5qfoAgfRWS1sUn+fUJtdbbqM1BD/LoQGa+smPTDjf9OqHyuJqi6ewtbYL0+V1S1RaU6OCOCMWGZocIfz2YK4uw==}
+ dev: false
+
+ /semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+ dev: true
+
+ /semver@7.6.0:
+ resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ lru-cache: 6.0.0
+
+ /send@0.18.0:
+ resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ mime: 1.6.0
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /serialize-javascript@6.0.0:
+ resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
+ dependencies:
+ randombytes: 2.1.0
+ dev: true
+
+ /serve-static@1.15.0:
+ resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 0.18.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /set-cookie-parser@2.6.0:
+ resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
+ dev: true
+
+ /set-function-length@1.2.1:
+ resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.2
+
+ /set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+ dev: true
+
+ /setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ dev: false
+
+ /shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+ dependencies:
+ shebang-regex: 3.0.0
+
+ /shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ /side-channel@1.0.5:
+ resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ object-inspect: 1.13.1
+
+ /signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+ dev: false
+
+ /sinon@17.0.1:
+ resolution: {integrity: sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==}
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+ '@sinonjs/fake-timers': 11.2.2
+ '@sinonjs/samsam': 8.0.0
+ diff: 5.2.0
+ nise: 5.1.9
+ supports-color: 7.2.0
+ dev: true
+
+ /slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /snake-case@3.0.4:
+ resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.2
+ dev: true
+
+ /socket.io-adapter@2.5.4:
+ resolution: {integrity: sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==}
+ dependencies:
+ debug: 4.3.4(supports-color@8.1.1)
+ ws: 8.11.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: false
+
+ /socket.io-client@4.7.4:
+ resolution: {integrity: sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ '@socket.io/component-emitter': 3.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ engine.io-client: 6.5.3
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: true
+
+ /socket.io-client@4.7.5:
+ resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ '@socket.io/component-emitter': 3.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ engine.io-client: 6.5.3
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ /socket.io-parser@4.2.4:
+ resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ '@socket.io/component-emitter': 3.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ /socket.io@4.7.5:
+ resolution: {integrity: sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==}
+ engines: {node: '>=10.2.0'}
+ dependencies:
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cors: 2.8.5
+ debug: 4.3.4(supports-color@8.1.1)
+ engine.io: 6.5.4
+ socket.io-adapter: 2.5.4
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: false
+
+ /source-map-js@1.0.2:
+ resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+ dev: false
+
+ /source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+ dev: false
+
+ /split-grid@1.0.11:
+ resolution: {integrity: sha512-ELtFtxc3r5we5GZfe6Fi0BFFxIi2M6BY1YEntBscKRDD3zx4JVHqx2VnTRSQu1BixCYSTH3MTjKd4esI2R7EgQ==}
+ dev: true
+
+ /statuses@2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /streamroller@3.1.5:
+ resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ date-format: 4.0.14
+ debug: 4.3.4(supports-color@8.1.1)
+ fs-extra: 8.1.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+ dev: true
+
+ /string.prototype.trim@1.2.8:
+ resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ dev: true
+
+ /string.prototype.trimend@1.0.7:
+ resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ dev: true
+
+ /string.prototype.trimstart@1.0.7:
+ resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.4
+ dev: true
+
+ /stringify-entities@4.0.3:
+ resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==}
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+ dev: false
+
+ /strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+ dependencies:
+ ansi-regex: 5.0.1
+ dev: true
+
+ /strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /superagent@8.1.2:
+ resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==}
+ engines: {node: '>=6.4.0 <13 || >=14'}
+ dependencies:
+ component-emitter: 1.3.1
+ cookiejar: 2.1.4
+ debug: 4.3.4(supports-color@8.1.1)
+ fast-safe-stringify: 2.1.1
+ form-data: 4.0.0
+ formidable: 2.1.2
+ methods: 1.1.2
+ mime: 2.6.0
+ qs: 6.11.2
+ semver: 7.6.0
+ transitivePeerDependencies:
+ - supports-color
+
+ /supertest@6.3.4:
+ resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==}
+ engines: {node: '>=6.4.0'}
+ dependencies:
+ methods: 1.1.2
+ superagent: 8.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /supports-color@5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+ dependencies:
+ has-flag: 3.0.0
+ dev: true
+
+ /supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+ dependencies:
+ has-flag: 4.0.0
+
+ /supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+ dependencies:
+ has-flag: 4.0.0
+
+ /supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ /svg-parser@2.0.4:
+ resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
+ dev: true
+
+ /swagger-schema-official@2.0.0-bab6bed:
+ resolution: {integrity: sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA==}
+ dev: true
+
+ /symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ dev: false
+
+ /tapable@2.2.1:
+ resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /tar@6.2.0:
+ resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ chownr: 2.0.0
+ fs-minipass: 2.1.0
+ minipass: 5.0.0
+ minizlib: 2.1.2
+ mkdirp: 1.0.4
+ yallist: 4.0.0
+ dev: false
+
+ /terser@5.29.2:
+ resolution: {integrity: sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ '@jridgewell/source-map': 0.3.5
+ acorn: 8.11.3
+ commander: 2.20.3
+ source-map-support: 0.5.21
+ dev: false
+
+ /text-table@0.2.0:
+ resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+ dev: true
+
+ /threads@1.7.0:
+ resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==}
+ dependencies:
+ callsites: 3.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ is-observable: 2.1.0
+ observable-fns: 0.6.1
+ optionalDependencies:
+ tiny-worker: 2.3.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /tiny-worker@2.3.0:
+ resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
+ requiresBuild: true
+ dependencies:
+ esm: 3.2.25
+ dev: false
+ optional: true
+
+ /tinycon@0.6.8:
+ resolution: {integrity: sha512-bF8Lxm4JUXF6Cw0XlZdugJ44GV575OinZ0Pt8vQPr8ooNqd2yyNkoFdCHzmdpHlgoqfSLfcyk4HDP1EyllT+ug==}
+ dev: false
+
+ /to-fast-properties@2.0.0:
+ resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ is-number: 7.0.0
+ dev: true
+
+ /toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+ dev: false
+
+ /tough-cookie@4.1.3:
+ resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==}
+ engines: {node: '>=6'}
+ dependencies:
+ psl: 1.9.0
+ punycode: 2.3.1
+ universalify: 0.2.0
+ url-parse: 1.5.10
+ dev: false
+
+ /tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+ dev: false
+
+ /tr46@5.0.0:
+ resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
+ engines: {node: '>=18'}
+ dependencies:
+ punycode: 2.3.1
+ dev: false
+
+ /trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+ dev: false
+
+ /trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+ dev: false
+
+ /ts-api-utils@1.3.0(typescript@5.4.2):
+ resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ typescript: '>=4.2.0'
+ dependencies:
+ typescript: 5.4.2
+ dev: true
+
+ /tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+ dev: true
+
+ /tslib@1.14.1:
+ resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+ dev: true
+
+ /tslib@2.6.2:
+ resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ dev: true
+
+ /tsutils@3.21.0(typescript@5.4.2):
+ resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+ engines: {node: '>= 6'}
+ peerDependencies:
+ typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+ dependencies:
+ tslib: 1.14.1
+ typescript: 5.4.2
+ dev: true
+
+ /tsx@4.7.1:
+ resolution: {integrity: sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+ dependencies:
+ esbuild: 0.19.12
+ get-tsconfig: 4.7.2
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: false
+
+ /type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ prelude-ls: 1.2.1
+ dev: true
+
+ /type-detect@4.0.8:
+ resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /type-fest@0.20.2:
+ resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /type-is@1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+ dev: false
+
+ /typed-array-buffer@1.0.2:
+ resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ is-typed-array: 1.1.13
+ dev: true
+
+ /typed-array-byte-length@1.0.0:
+ resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ has-proto: 1.0.3
+ is-typed-array: 1.1.13
+ dev: true
+
+ /typed-array-byte-offset@1.0.1:
+ resolution: {integrity: sha512-tcqKMrTRXjqvHN9S3553NPCaGL0VPgFI92lXszmrE8DMhiDPLBYLlvo8Uu4WZAAX/aGqp/T1sbA4ph8EWjDF9Q==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-proto: 1.0.3
+ is-typed-array: 1.1.13
+ dev: true
+
+ /typed-array-length@1.0.4:
+ resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
+ dependencies:
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ is-typed-array: 1.1.13
+ dev: true
+
+ /typescript@5.4.2:
+ resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+ dev: true
+
+ /ueberdb2@4.2.63:
+ resolution: {integrity: sha512-5zmp6vkBNcfRbhXG623pgHtdhbxNi2B4d8PqLXPa65hC8vdWJrjPQbldpl9Y49LuUF7n50k+nOuseVKAeTkgyA==}
+ engines: {node: '>=16.20.1'}
+ dev: false
+
+ /uid-safe@2.1.5:
+ resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ random-bytes: 1.0.0
+ dev: false
+
+ /unbox-primitive@1.0.2:
+ resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+ dependencies:
+ call-bind: 1.0.7
+ has-bigints: 1.0.2
+ has-symbols: 1.0.3
+ which-boxed-primitive: 1.0.2
+ dev: true
+
+ /underscore@1.13.6:
+ resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==}
+ dev: false
+
+ /undici-types@5.26.5:
+ resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+
+ /unified@11.0.4:
+ resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==}
+ dependencies:
+ '@types/unist': 3.0.2
+ bail: 2.0.2
+ devlop: 1.1.0
+ extend: 3.0.2
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 6.0.1
+ dev: false
+
+ /unist-util-is@6.0.0:
+ resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
+ /unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
+ /unist-util-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
+ /unist-util-visit-parents@6.0.1:
+ resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
+ dependencies:
+ '@types/unist': 3.0.2
+ unist-util-is: 6.0.0
+ dev: false
+
+ /unist-util-visit@5.0.0:
+ resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+ dependencies:
+ '@types/unist': 3.0.2
+ unist-util-is: 6.0.0
+ unist-util-visit-parents: 6.0.1
+ dev: false
+
+ /universalify@0.1.2:
+ resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+ engines: {node: '>= 4.0.0'}
+ dev: false
+
+ /universalify@0.2.0:
+ resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
+ engines: {node: '>= 4.0.0'}
+ dev: false
+
+ /universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ /unorm@1.6.0:
+ resolution: {integrity: sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==}
+ engines: {node: '>= 0.4.0'}
+ dev: false
+
+ /unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /update-browserslist-db@1.0.13(browserslist@4.23.0):
+ resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.23.0
+ escalade: 3.1.2
+ picocolors: 1.0.0
+ dev: true
+
+ /uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ dependencies:
+ punycode: 2.3.1
+
+ /url-join@4.0.1:
+ resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
+ dev: false
+
+ /url-parse@1.5.10:
+ resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
+ dependencies:
+ querystringify: 2.2.0
+ requires-port: 1.0.0
+ dev: false
+
+ /use-callback-ref@1.3.1(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.65
+ react: 18.2.0
+ tslib: 2.6.2
+ dev: true
+
+ /use-sidecar@1.1.2(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.65
+ detect-node-es: 1.1.0
+ react: 18.2.0
+ tslib: 2.6.2
+ dev: true
+
+ /use-sync-external-store@1.2.0(react@18.2.0):
+ resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: true
+
+ /utils-merge@1.0.1:
+ resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
+ engines: {node: '>= 0.4.0'}
+ dev: false
+
+ /vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
+ /vfile-location@5.0.2:
+ resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==}
+ dependencies:
+ '@types/unist': 3.0.2
+ vfile: 6.0.1
+ dev: false
+
+ /vfile-message@4.0.2:
+ resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
+ dependencies:
+ '@types/unist': 3.0.2
+ unist-util-stringify-position: 4.0.0
+ dev: false
+
+ /vfile@6.0.1:
+ resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
+ dependencies:
+ '@types/unist': 3.0.2
+ unist-util-stringify-position: 4.0.0
+ vfile-message: 4.0.2
+ dev: false
+
+ /vite-plugin-static-copy@1.0.1(vite@5.1.6):
+ resolution: {integrity: sha512-3eGL4mdZoPJMDBT68pv/XKIHR4MgVolStIxxv1gIBP4R8TpHn9C9EnaU0hesqlseJ4ycLGUxckFTu/jpuJXQlA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ peerDependencies:
+ vite: ^5.0.0
+ dependencies:
+ chokidar: 3.5.3
+ fast-glob: 3.3.2
+ fs-extra: 11.2.0
+ picocolors: 1.0.0
+ vite: 5.1.6
+ dev: true
+
+ /vite-plugin-svgr@4.2.0(typescript@5.4.2)(vite@5.1.6):
+ resolution: {integrity: sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==}
+ peerDependencies:
+ vite: ^2.6.0 || 3 || 4 || 5
+ dependencies:
+ '@rollup/pluginutils': 5.1.0
+ '@svgr/core': 8.1.0(typescript@5.4.2)
+ '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0)
+ vite: 5.1.6
+ transitivePeerDependencies:
+ - rollup
+ - supports-color
+ - typescript
+ dev: true
+
+ /vite@5.1.6:
+ resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ esbuild: 0.19.12
+ postcss: 8.4.35
+ rollup: 4.13.0
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /void-elements@3.1.0:
+ resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+ dependencies:
+ xml-name-validator: 5.0.0
+ dev: false
+
+ /web-namespaces@2.0.1:
+ resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+ dev: false
+
+ /webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ dev: false
+
+ /webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ iconv-lite: 0.6.3
+ dev: false
+
+ /whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+ dev: false
+
+ /whatwg-url@14.0.0:
+ resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==}
+ engines: {node: '>=18'}
+ dependencies:
+ tr46: 5.0.0
+ webidl-conversions: 7.0.0
+ dev: false
+
+ /whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+ dev: false
+
+ /which-boxed-primitive@1.0.2:
+ resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+ dependencies:
+ is-bigint: 1.0.4
+ is-boolean-object: 1.1.2
+ is-number-object: 1.0.7
+ is-string: 1.0.7
+ is-symbol: 1.0.4
+ dev: true
+
+ /which-typed-array@1.1.14:
+ resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-tostringtag: 1.0.2
+ dev: true
+
+ /which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+ dependencies:
+ isexe: 2.0.0
+
+ /workerpool@6.2.1:
+ resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
+ dev: true
+
+ /wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ dev: true
+
+ /wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ /ws@8.11.0:
+ resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ^5.0.2
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ /ws@8.16.0:
+ resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dev: false
+
+ /wtfnode@0.9.1:
+ resolution: {integrity: sha512-Ip6C2KeQPl/F3aP1EfOnPoQk14Udd9lffpoqWDNH3Xt78svxPbv53ngtmtfI0q2Te3oTq79XKTnRNXVIn/GsPA==}
+ hasBin: true
+ dev: false
+
+ /xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+ dev: false
+
+ /xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+ dev: false
+
+ /xmlhttprequest-ssl@2.0.0:
+ resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==}
+ engines: {node: '>=0.4.0'}
+
+ /y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ dev: true
+
+ /yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ /yargs-parser@20.2.4:
+ resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /yargs-unparser@2.0.0:
+ resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
+ engines: {node: '>=10'}
+ dependencies:
+ camelcase: 6.3.0
+ decamelize: 4.0.0
+ flat: 5.0.2
+ is-plain-obj: 2.1.0
+ dev: true
+
+ /yargs@16.2.0:
+ resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+ engines: {node: '>=10'}
+ dependencies:
+ cliui: 7.0.4
+ escalade: 3.1.2
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 20.2.4
+ dev: true
+
+ /yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /zustand@4.5.2(@types/react@18.2.65)(react@18.2.0):
+ resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ dependencies:
+ '@types/react': 18.2.65
+ react: 18.2.0
+ use-sync-external-store: 1.2.0(react@18.2.0)
+ dev: true
+
+ /zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+ dev: false
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 000000000..dd590d517
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,4 @@
+packages:
+ - src
+ - admin
+ - bin
diff --git a/settings.json.docker b/settings.json.docker
index 79f72f05e..7fbda1aef 100644
--- a/settings.json.docker
+++ b/settings.json.docker
@@ -527,7 +527,7 @@
/*
* Restrict socket.io transport methods
*/
- "socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
+ "socketTransportProtocols" : ["websocket", "polling"],
"socketIo": {
/*
diff --git a/settings.json.template b/settings.json.template
index 735b9d5e7..9c9150394 100644
--- a/settings.json.template
+++ b/settings.json.template
@@ -527,7 +527,7 @@
/*
* Restrict socket.io transport methods
*/
- "socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
+ "socketTransportProtocols" : ["websocket", "polling"],
"socketIo": {
/*
diff --git a/src/.eslintrc.cjs b/src/.eslintrc.cjs
index 95c9efa07..03d432ede 100644
--- a/src/.eslintrc.cjs
+++ b/src/.eslintrc.cjs
@@ -5,8 +5,6 @@ require('eslint-config-etherpad/patch/modern-module-resolution');
module.exports = {
ignorePatterns: [
- '/static/js/admin/jquery.autosize.js',
- '/static/js/admin/minify.json.js',
'/static/js/vendors/browser.js',
'/static/js/vendors/farbtastic.js',
'/static/js/vendors/gritter.js',
diff --git a/src/.npmrc b/src/.npmrc
new file mode 100644
index 000000000..f301fedf9
--- /dev/null
+++ b/src/.npmrc
@@ -0,0 +1 @@
+auto-install-peers=false
diff --git a/src/bin/createUserSession.js b/src/bin/createUserSession.js
deleted file mode 100644
index 33dcac18e..000000000
--- a/src/bin/createUserSession.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-/*
- * A tool for generating a test user session which can be used for debugging configs
- * that require sessions.
- */
-
-// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
-// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
-process.on('unhandledRejection', (err) => { throw err; });
-
-const fs = require('fs');
-const path = require('path');
-const querystring = require('querystring');
-const settings = require('../node/utils/Settings');
-const supertest = require('supertest');
-
-(async () => {
- const api = supertest(`http://${settings.ip}:${settings.port}`);
-
- const filePath = path.join(__dirname, '../../APIKEY.txt');
- const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
-
- let res;
-
- res = await api.get('/api/');
- const apiVersion = res.body.currentVersion;
- if (!apiVersion) throw new Error('No version set in API');
- const uri = (cmd, args) => `/api/${apiVersion}/${cmd}?${querystring.stringify(args)}`;
-
- res = await api.post(uri('createGroup', {apikey}));
- if (res.body.code === 1) throw new Error(`Error creating group: ${res.body}`);
- const groupID = res.body.data.groupID;
- console.log('groupID', groupID);
-
- res = await api.post(uri('createGroupPad', {apikey, groupID}));
- if (res.body.code === 1) throw new Error(`Error creating group pad: ${res.body}`);
- console.log('Test Pad ID ====> ', res.body.data.padID);
-
- res = await api.post(uri('createAuthor', {apikey}));
- if (res.body.code === 1) throw new Error(`Error creating author: ${res.body}`);
- const authorID = res.body.data.authorID;
- console.log('authorID', authorID);
-
- const validUntil = Math.floor(new Date() / 1000) + 60000;
- console.log('validUntil', validUntil);
- res = await api.post(uri('createSession', {apikey, groupID, authorID, validUntil}));
- if (res.body.code === 1) throw new Error(`Error creating session: ${res.body}`);
- console.log('Session made: ====> create a cookie named sessionID and set the value to',
- res.body.data.sessionID);
-})();
diff --git a/src/bin/doc/LICENSE b/src/bin/doc/LICENSE
deleted file mode 100644
index e3d4e695a..000000000
--- a/src/bin/doc/LICENSE
+++ /dev/null
@@ -1,18 +0,0 @@
-Copyright Joyent, Inc. and other Node contributors. All rights reserved.
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to
-deal in the Software without restriction, including without limitation the
-rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-sell copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-IN THE SOFTWARE.
diff --git a/src/bin/doc/README.md b/src/bin/doc/README.md
deleted file mode 100644
index 19a137fd7..000000000
--- a/src/bin/doc/README.md
+++ /dev/null
@@ -1,76 +0,0 @@
-Here's how the node docs work.
-
-Each type of heading has a description block.
-
-
- ## module
-
- Stability: 3 - Stable
-
- description and examples.
-
- ### module.property
-
- * Type
-
- description of the property.
-
- ### module.someFunction(x, y, [z=100])
-
- * `x` {String} the description of the string
- * `y` {Boolean} Should I stay or should I go?
- * `z` {Number} How many zebras to bring.
-
- A description of the function.
-
- ### Event: 'blerg'
-
- * Argument: SomeClass object.
-
- Modules don't usually raise events on themselves. `cluster` is the
- only exception.
-
- ## Class: SomeClass
-
- description of the class.
-
- ### Class Method: SomeClass.classMethod(anArg)
-
- * `anArg` {Object} Just an argument
- * `field` {String} anArg can have this field.
- * `field2` {Boolean} Another field. Default: `false`.
- * Return: {Boolean} `true` if it worked.
-
- Description of the method for humans.
-
- ### someClass.nextSibling()
-
- * Return: {SomeClass object | null} The next someClass in line.
-
- ### someClass.someProperty
-
- * String
-
- The indication of what someProperty is.
-
- ### Event: 'grelb'
-
- * `isBlerg` {Boolean}
-
- This event is emitted on instances of SomeClass, not on the module itself.
-
-
-* Modules have (description, Properties, Functions, Classes, Examples)
-* Properties have (type, description)
-* Functions have (list of arguments, description)
-* Classes have (description, Properties, Methods, Events)
-* Events have (list of arguments, description)
-* Methods have (list of arguments, description)
-* Properties have (type, description)
-
-# CLI usage
-
-Run the following from the etherpad-lite root directory:
-```sh
-$ node src/bin/doc/generate doc/index.md --format=html --template=doc/template.html > out.html
-```
diff --git a/src/bin/doc/generate.js b/src/bin/doc/generate.js
deleted file mode 100644
index d04468a8b..000000000
--- a/src/bin/doc/generate.js
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env node
-
-'use strict';
-
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-const fs = require('fs');
-const path = require('path');
-
-// parse the args.
-// Don't use nopt or whatever for this. It's simple enough.
-
-const args = process.argv.slice(2);
-let format = 'json';
-let template = null;
-let inputFile = null;
-
-args.forEach((arg) => {
- if (!arg.match(/^--/)) {
- inputFile = arg;
- } else if (arg.match(/^--format=/)) {
- format = arg.replace(/^--format=/, '');
- } else if (arg.match(/^--template=/)) {
- template = arg.replace(/^--template=/, '');
- }
-});
-
-
-if (!inputFile) {
- throw new Error('No input file specified');
-}
-
-
-console.error('Input file = %s', inputFile);
-fs.readFile(inputFile, 'utf8', (er, input) => {
- if (er) throw er;
- // process the input for @include lines
- processIncludes(inputFile, input, next);
-});
-
-
-const includeExpr = /^@include\s+([A-Za-z0-9-_/]+)(?:\.)?([a-zA-Z]*)$/gmi;
-const includeData = {};
-const processIncludes = (inputFile, input, cb) => {
- const includes = input.match(includeExpr);
- if (includes == null) return cb(null, input);
- let errState = null;
- console.error(includes);
- let incCount = includes.length;
- if (incCount === 0) cb(null, input);
-
- includes.forEach((include) => {
- let fname = include.replace(/^@include\s+/, '');
- if (!fname.match(/\.md$/)) fname += '.md';
-
- if (Object.prototype.hasOwnProperty.call(includeData, fname)) {
- input = input.split(include).join(includeData[fname]);
- incCount--;
- if (incCount === 0) {
- return cb(null, input);
- }
- }
-
- const fullFname = path.resolve(path.dirname(inputFile), fname);
- fs.readFile(fullFname, 'utf8', (er, inc) => {
- if (errState) return;
- if (er) return cb(errState = er);
- processIncludes(fullFname, inc, (er, inc) => {
- if (errState) return;
- if (er) return cb(errState = er);
- incCount--;
- includeData[fname] = inc;
- input = input.split(include).join(includeData[fname]);
- if (incCount === 0) {
- return cb(null, input);
- }
- });
- });
- });
-};
-
-
-const next = (er, input) => {
- if (er) throw er;
- switch (format) {
- case 'json':
- require('./json.js')(input, inputFile, (er, obj) => {
- console.log(JSON.stringify(obj, null, 2));
- if (er) throw er;
- });
- break;
-
- case 'html':
- require('./html.js')(input, inputFile, template, (er, html) => {
- if (er) throw er;
- console.log(html);
- });
- break;
-
- default:
- throw new Error(`Invalid format: ${format}`);
- }
-};
diff --git a/src/bin/doc/html.js b/src/bin/doc/html.js
deleted file mode 100644
index ff07ae7c8..000000000
--- a/src/bin/doc/html.js
+++ /dev/null
@@ -1,165 +0,0 @@
-'use strict';
-
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-const fs = require('fs');
-const marked = require('marked');
-const path = require('path');
-
-
-const toHTML = (input, filename, template, cb) => {
- const lexed = marked.lexer(input);
- fs.readFile(template, 'utf8', (er, template) => {
- if (er) return cb(er);
- render(lexed, filename, template, cb);
- });
-};
-module.exports = toHTML;
-
-const render = (lexed, filename, template, cb) => {
- // get the section
- const section = getSection(lexed);
-
- filename = path.basename(filename, '.md');
-
- lexed = parseLists(lexed);
-
- // generate the table of contents.
- // this mutates the lexed contents in-place.
- buildToc(lexed, filename, (er, toc) => {
- if (er) return cb(er);
-
- template = template.replace(/__FILENAME__/g, filename);
- template = template.replace(/__SECTION__/g, section);
- template = template.replace(/__TOC__/g, toc);
-
- // content has to be the last thing we do with
- // the lexed tokens, because it's destructive.
- const content = marked.parser(lexed);
- template = template.replace(/__CONTENT__/g, content);
-
- cb(null, template);
- });
-};
-
-
-// just update the list item text in-place.
-// lists that come right after a heading are what we're after.
-const parseLists = (input) => {
- let state = null;
- let depth = 0;
- const output = [];
- output.links = input.links;
- input.forEach((tok) => {
- if (state == null) {
- if (tok.type === 'heading') {
- state = 'AFTERHEADING';
- }
- output.push(tok);
- return;
- }
- if (state === 'AFTERHEADING') {
- if (tok.type === 'list_start') {
- state = 'LIST';
- if (depth === 0) {
- output.push({type: 'html', text: ''});
- }
- depth++;
- output.push(tok);
- return;
- }
- state = null;
- output.push(tok);
- return;
- }
- if (state === 'LIST') {
- if (tok.type === 'list_start') {
- depth++;
- output.push(tok);
- return;
- }
- if (tok.type === 'list_end') {
- depth--;
- if (depth === 0) {
- state = null;
- output.push({type: 'html', text: '
'});
- }
- output.push(tok);
- return;
- }
- if (tok.text) {
- tok.text = parseListItem(tok.text);
- }
- }
- output.push(tok);
- });
-
- return output;
-};
-
-
-const parseListItem = (text) => {
- text = text.replace(/\{([^}]+)\}/, '$1');
- // XXX maybe put more stuff here?
- return text;
-};
-
-
-// section is just the first heading
-const getSection = (lexed) => {
- for (let i = 0, l = lexed.length; i < l; i++) {
- const tok = lexed[i];
- if (tok.type === 'heading') return tok.text;
- }
- return '';
-};
-
-
-const buildToc = (lexed, filename, cb) => {
- let toc = [];
- let depth = 0;
-
- marked.setOptions({
- headerIds: true,
- headerPrefix: `${filename}_`,
- });
-
- lexed.forEach((tok) => {
- if (tok.type !== 'heading') return;
- if (tok.depth - depth > 1) {
- return cb(new Error(`Inappropriate heading level\n${JSON.stringify(tok)}`));
- }
-
- depth = tok.depth;
-
- const slugger = new marked.Slugger();
- const id = slugger.slug(`${filename}_${tok.text.trim()}`);
-
- toc.push(`${new Array((depth - 1) * 2 + 1).join(' ')}* ${tok.text}`);
-
- tok.text += `#`;
- });
-
- toc = marked.parse(toc.join('\n'));
- cb(null, toc);
-};
diff --git a/src/bin/doc/json.js b/src/bin/doc/json.js
deleted file mode 100644
index 1a5ecb1d8..000000000
--- a/src/bin/doc/json.js
+++ /dev/null
@@ -1,556 +0,0 @@
-'use strict';
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-module.exports = doJSON;
-
-// Take the lexed input, and return a JSON-encoded object
-// A module looks like this: https://gist.github.com/1777387
-
-const marked = require('marked');
-
-const doJSON = (input, filename, cb) => {
- const root = {source: filename};
- const stack = [root];
- let depth = 0;
- let current = root;
- let state = null;
- const lexed = marked.lexer(input);
- lexed.forEach((tok) => {
- const type = tok.type;
- let text = tok.text;
-
- //
- // This is for cases where the markdown semantic structure is lacking.
- if (type === 'paragraph' || type === 'html') {
- const metaExpr = /\n*/g;
- text = text.replace(metaExpr, (_0, k, v) => {
- current[k.trim()] = v.trim();
- return '';
- });
- text = text.trim();
- if (!text) return;
- }
-
- if (type === 'heading' &&
- !text.trim().match(/^example/i)) {
- if (tok.depth - depth > 1) {
- return cb(new Error(`Inappropriate heading level\n${
- JSON.stringify(tok)}`));
- }
-
- // Sometimes we have two headings with a single
- // blob of description. Treat as a clone.
- if (current &&
- state === 'AFTERHEADING' &&
- depth === tok.depth) {
- const clone = current;
- current = newSection(tok);
- current.clone = clone;
- // don't keep it around on the stack.
- stack.pop();
- } else {
- // if the level is greater than the current depth,
- // then it's a child, so we should just leave the stack
- // as it is.
- // However, if it's a sibling or higher, then it implies
- // the closure of the other sections that came before.
- // root is always considered the level=0 section,
- // and the lowest heading is 1, so this should always
- // result in having a valid parent node.
- let d = tok.depth;
- while (d <= depth) {
- finishSection(stack.pop(), stack[stack.length - 1]);
- d++;
- }
- current = newSection(tok);
- }
-
- depth = tok.depth;
- stack.push(current);
- state = 'AFTERHEADING';
- return;
- } // heading
-
- // Immediately after a heading, we can expect the following
- //
- // { type: 'code', text: 'Stability: ...' },
- //
- // a list: starting with list_start, ending with list_end,
- // maybe containing other nested lists in each item.
- //
- // If one of these isn't found, then anything that comes between
- // here and the next heading should be parsed as the desc.
- let stability;
- if (state === 'AFTERHEADING') {
- if (type === 'code' &&
- (stability = text.match(/^Stability: ([0-5])(?:\s*-\s*)?(.*)$/))) {
- current.stability = parseInt(stability[1], 10);
- current.stabilityText = stability[2].trim();
- return;
- } else if (type === 'list_start' && !tok.ordered) {
- state = 'AFTERHEADING_LIST';
- current.list = current.list || [];
- current.list.push(tok);
- current.list.level = 1;
- } else {
- current.desc = current.desc || [];
- if (!Array.isArray(current.desc)) {
- current.shortDesc = current.desc;
- current.desc = [];
- }
- current.desc.push(tok);
- state = 'DESC';
- }
- return;
- }
-
- if (state === 'AFTERHEADING_LIST') {
- current.list.push(tok);
- if (type === 'list_start') {
- current.list.level++;
- } else if (type === 'list_end') {
- current.list.level--;
- }
- if (current.list.level === 0) {
- state = 'AFTERHEADING';
- processList(current);
- }
- return;
- }
-
- current.desc = current.desc || [];
- current.desc.push(tok);
- });
-
- // finish any sections left open
- while (root !== (current = stack.pop())) {
- finishSection(current, stack[stack.length - 1]);
- }
-
- return cb(null, root);
-};
-
-
-// go from something like this:
-// [ { type: 'list_item_start' },
-// { type: 'text',
-// text: '`settings` Object, Optional' },
-// { type: 'list_start', ordered: false },
-// { type: 'list_item_start' },
-// { type: 'text',
-// text: 'exec: String, file path to worker file. Default: `__filename`' },
-// { type: 'list_item_end' },
-// { type: 'list_item_start' },
-// { type: 'text',
-// text: 'args: Array, string arguments passed to worker.' },
-// { type: 'text',
-// text: 'Default: `process.argv.slice(2)`' },
-// { type: 'list_item_end' },
-// { type: 'list_item_start' },
-// { type: 'text',
-// text: 'silent: Boolean, whether or not to send output to parent\'s stdio.' },
-// { type: 'text', text: 'Default: `false`' },
-// { type: 'space' },
-// { type: 'list_item_end' },
-// { type: 'list_end' },
-// { type: 'list_item_end' },
-// { type: 'list_end' } ]
-// to something like:
-// [ { name: 'settings',
-// type: 'object',
-// optional: true,
-// settings:
-// [ { name: 'exec',
-// type: 'string',
-// desc: 'file path to worker file',
-// default: '__filename' },
-// { name: 'args',
-// type: 'array',
-// default: 'process.argv.slice(2)',
-// desc: 'string arguments passed to worker.' },
-// { name: 'silent',
-// type: 'boolean',
-// desc: 'whether or not to send output to parent\'s stdio.',
-// default: 'false' } ] } ]
-
-const processList = (section) => {
- const list = section.list;
- const values = [];
- let current;
- const stack = [];
-
- // for now, *just* build the hierarchical list
- list.forEach((tok) => {
- const type = tok.type;
- if (type === 'space') return;
- if (type === 'list_item_start') {
- if (!current) {
- const n = {};
- values.push(n);
- current = n;
- } else {
- current.options = current.options || [];
- stack.push(current);
- const n = {};
- current.options.push(n);
- current = n;
- }
- return;
- } else if (type === 'list_item_end') {
- if (!current) {
- throw new Error(`invalid list - end without current item\n${
- JSON.stringify(tok)}\n${
- JSON.stringify(list)}`);
- }
- current = stack.pop();
- } else if (type === 'text') {
- if (!current) {
- throw new Error(`invalid list - text without current item\n${
- JSON.stringify(tok)}\n${
- JSON.stringify(list)}`);
- }
- current.textRaw = current.textRaw || '';
- current.textRaw += `${tok.text} `;
- }
- });
-
- // shove the name in there for properties, since they are always
- // just going to be the value etc.
- if (section.type === 'property' && values[0]) {
- values[0].textRaw = `\`${section.name}\` ${values[0].textRaw}`;
- }
-
- // now pull the actual values out of the text bits.
- values.forEach(parseListItem);
-
- // Now figure out what this list actually means.
- // depending on the section type, the list could be different things.
-
- switch (section.type) {
- case 'ctor':
- case 'classMethod':
- case 'method': {
- // each item is an argument, unless the name is 'return',
- // in which case it's the return value.
- section.signatures = section.signatures || [];
- const sig = {};
- section.signatures.push(sig);
- sig.params = values.filter((v) => {
- if (v.name === 'return') {
- sig.return = v;
- return false;
- }
- return true;
- });
- parseSignature(section.textRaw, sig);
- break;
- }
- case 'property': {
- // there should be only one item, which is the value.
- // copy the data up to the section.
- const value = values[0] || {};
- delete value.name;
- section.typeof = value.type;
- delete value.type;
- Object.keys(value).forEach((k) => {
- section[k] = value[k];
- });
- break;
- }
-
- case 'event': {
- // event: each item is an argument.
- section.params = values;
- break;
- }
- }
-
- delete section.list;
-};
-
-
-// textRaw = "someobject.someMethod(a, [b=100], [c])"
-const parseSignature = (text, sig) => {
- let params = text.match(paramExpr);
- if (!params) return;
- params = params[1];
- // the ] is irrelevant. [ indicates optionalness.
- params = params.replace(/\]/g, '');
- params = params.split(/,/);
- params.forEach((p, i, _) => {
- p = p.trim();
- if (!p) return;
- let param = sig.params[i];
- let optional = false;
- let def;
- // [foo] -> optional
- if (p.charAt(0) === '[') {
- optional = true;
- p = p.substr(1);
- }
- const eq = p.indexOf('=');
- if (eq !== -1) {
- def = p.substr(eq + 1);
- p = p.substr(0, eq);
- }
- if (!param) {
- param = sig.params[i] = {name: p};
- }
- // at this point, the name should match.
- if (p !== param.name) {
- console.error('Warning: invalid param "%s"', p);
- console.error(` > ${JSON.stringify(param)}`);
- console.error(` > ${text}`);
- }
- if (optional) param.optional = true;
- if (def !== undefined) param.default = def;
- });
-};
-
-
-const parseListItem = (item) => {
- if (item.options) item.options.forEach(parseListItem);
- if (!item.textRaw) return;
-
- // the goal here is to find the name, type, default, and optional.
- // anything left over is 'desc'
- let text = item.textRaw.trim();
- // text = text.replace(/^(Argument|Param)s?\s*:?\s*/i, '');
-
- text = text.replace(/^, /, '').trim();
- const retExpr = /^returns?\s*:?\s*/i;
- const ret = text.match(retExpr);
- if (ret) {
- item.name = 'return';
- text = text.replace(retExpr, '');
- } else {
- const nameExpr = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
- const name = text.match(nameExpr);
- if (name) {
- item.name = name[1];
- text = text.replace(nameExpr, '');
- }
- }
-
- text = text.trim();
- const defaultExpr = /\(default\s*[:=]?\s*['"`]?([^, '"`]*)['"`]?\)/i;
- const def = text.match(defaultExpr);
- if (def) {
- item.default = def[1];
- text = text.replace(defaultExpr, '');
- }
-
- text = text.trim();
- const typeExpr = /^\{([^}]+)\}/;
- const type = text.match(typeExpr);
- if (type) {
- item.type = type[1];
- text = text.replace(typeExpr, '');
- }
-
- text = text.trim();
- const optExpr = /^Optional\.|(?:, )?Optional$/;
- const optional = text.match(optExpr);
- if (optional) {
- item.optional = true;
- text = text.replace(optExpr, '');
- }
-
- text = text.replace(/^\s*-\s*/, '');
- text = text.trim();
- if (text) item.desc = text;
-};
-
-
-const finishSection = (section, parent) => {
- if (!section || !parent) {
- throw new Error(`Invalid finishSection call\n${
- JSON.stringify(section)}\n${
- JSON.stringify(parent)}`);
- }
-
- if (!section.type) {
- section.type = 'module';
- if (parent && (parent.type === 'misc')) {
- section.type = 'misc';
- }
- section.displayName = section.name;
- section.name = section.name.toLowerCase()
- .trim().replace(/\s+/g, '_');
- }
-
- if (section.desc && Array.isArray(section.desc)) {
- section.desc.links = section.desc.links || [];
- section.desc = marked.parser(section.desc);
- }
-
- if (!section.list) section.list = [];
- processList(section);
-
- // classes sometimes have various 'ctor' children
- // which are actually just descriptions of a constructor
- // class signature.
- // Merge them into the parent.
- if (section.type === 'class' && section.ctors) {
- section.signatures = section.signatures || [];
- const sigs = section.signatures;
- section.ctors.forEach((ctor) => {
- ctor.signatures = ctor.signatures || [{}];
- ctor.signatures.forEach((sig) => {
- sig.desc = ctor.desc;
- });
- sigs.push(...ctor.signatures);
- });
- delete section.ctors;
- }
-
- // properties are a bit special.
- // their "type" is the type of object, not "property"
- if (section.properties) {
- section.properties.forEach((p) => {
- if (p.typeof) p.type = p.typeof;
- else delete p.type;
- delete p.typeof;
- });
- }
-
- // handle clones
- if (section.clone) {
- const clone = section.clone;
- delete section.clone;
- delete clone.clone;
- deepCopy(section, clone);
- finishSection(clone, parent);
- }
-
- let plur;
- if (section.type.slice(-1) === 's') {
- plur = `${section.type}es`;
- } else if (section.type.slice(-1) === 'y') {
- plur = section.type.replace(/y$/, 'ies');
- } else {
- plur = `${section.type}s`;
- }
-
- // if the parent's type is 'misc', then it's just a random
- // collection of stuff, like the "globals" section.
- // Make the children top-level items.
- if (section.type === 'misc') {
- Object.keys(section).forEach((k) => {
- switch (k) {
- case 'textRaw':
- case 'name':
- case 'type':
- case 'desc':
- case 'miscs':
- return;
- default:
- if (parent.type === 'misc') {
- return;
- }
- if (Array.isArray(k) && parent[k]) {
- parent[k] = parent[k].concat(section[k]);
- } else if (!parent[k]) {
- parent[k] = section[k];
- } else {
- // parent already has, and it's not an array.
- return;
- }
- }
- });
- }
-
- parent[plur] = parent[plur] || [];
- parent[plur].push(section);
-};
-
-
-// Not a general purpose deep copy.
-// But sufficient for these basic things.
-const deepCopy = (src, dest) => {
- Object.keys(src).filter((k) => !Object.prototype.hasOwnProperty.call(dest, k)).forEach((k) => {
- dest[k] = deepCopy_(src[k]);
- });
-};
-
-const deepCopy_ = (src) => {
- if (!src) return src;
- if (Array.isArray(src)) {
- const c = new Array(src.length);
- src.forEach((v, i) => {
- c[i] = deepCopy_(v);
- });
- return c;
- }
- if (typeof src === 'object') {
- const c = {};
- Object.keys(src).forEach((k) => {
- c[k] = deepCopy_(src[k]);
- });
- return c;
- }
- return src;
-};
-
-
-// these parse out the contents of an H# tag
-const eventExpr = /^Event(?::|\s)+['"]?([^"']+).*$/i;
-const classExpr = /^Class:\s*([^ ]+).*?$/i;
-const propExpr = /^(?:property:?\s*)?[^.]+\.([^ .()]+)\s*?$/i;
-const braceExpr = /^(?:property:?\s*)?[^.[]+(\[[^\]]+\])\s*?$/i;
-const classMethExpr =
- /^class\s*method\s*:?[^.]+\.([^ .()]+)\([^)]*\)\s*?$/i;
-const methExpr =
- /^(?:method:?\s*)?(?:[^.]+\.)?([^ .()]+)\([^)]*\)\s*?$/i;
-const newExpr = /^new ([A-Z][a-z]+)\([^)]*\)\s*?$/;
-const paramExpr = /\((.*)\);?$/;
-
-const newSection = (tok) => {
- const section = {};
- // infer the type from the text.
- const text = section.textRaw = tok.text;
- if (text.match(eventExpr)) {
- section.type = 'event';
- section.name = text.replace(eventExpr, '$1');
- } else if (text.match(classExpr)) {
- section.type = 'class';
- section.name = text.replace(classExpr, '$1');
- } else if (text.match(braceExpr)) {
- section.type = 'property';
- section.name = text.replace(braceExpr, '$1');
- } else if (text.match(propExpr)) {
- section.type = 'property';
- section.name = text.replace(propExpr, '$1');
- } else if (text.match(classMethExpr)) {
- section.type = 'classMethod';
- section.name = text.replace(classMethExpr, '$1');
- } else if (text.match(methExpr)) {
- section.type = 'method';
- section.name = text.replace(methExpr, '$1');
- } else if (text.match(newExpr)) {
- section.type = 'ctor';
- section.name = text.replace(newExpr, '$1');
- } else {
- section.name = text;
- }
- return section;
-};
diff --git a/src/bin/doc/package-lock.json b/src/bin/doc/package-lock.json
deleted file mode 100644
index a51a03a65..000000000
--- a/src/bin/doc/package-lock.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "node-doc-generator",
- "version": "0.0.0",
- "lockfileVersion": 1,
- "requires": true,
- "dependencies": {
- "marked": {
- "version": "11.2.0",
- "resolved": "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz",
- "integrity": "sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw=="
- }
- }
-}
diff --git a/src/bin/doc/package.json b/src/bin/doc/package.json
deleted file mode 100644
index e2e77b8c1..000000000
--- a/src/bin/doc/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "author": "Isaac Z. Schlueter (http://blog.izs.me/)",
- "name": "node-doc-generator",
- "description": "Internal tool for generating Node.js API docs",
- "version": "0.0.0",
- "engines": {
- "node": ">=12.17.0"
- },
- "dependencies": {
- "marked": "^11.2.0"
- },
- "devDependencies": {},
- "optionalDependencies": {},
- "bin": "./generate.js"
-}
diff --git a/src/bin/etherpad-healthcheck b/src/bin/etherpad-healthcheck
deleted file mode 100755
index 59105d38a..000000000
--- a/src/bin/etherpad-healthcheck
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env node
-
-// Checks the health of Etherpad by visiting http://localhost:9001/health. Returns 0 on success, 1
-// on error as required by the Dockerfile HEALTHCHECK instruction.
-
-'use strict';
-
-// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
-// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
-process.on('unhandledRejection', (err) => { throw err; });
-
-const assert = require('assert').strict;
-const superagent = require('superagent');
-
-(async () => {
- const res = await superagent.get('http://localhost:9001/health')
- .accept('application/health+json')
- .buffer(true)
- .parse(superagent.parse['application/json']);
- assert(res.ok, `Unexpected HTTP status: ${res.status}`);
- assert.equal(res.type, 'application/health+json');
- const {body: {status} = {}} = res;
- assert(status != null);
- assert.equal(typeof status, 'string');
- assert(['pass', 'ok', 'up'].includes(status.toLowerCase()), `Unexpected status: ${status}`);
-})();
diff --git a/src/bin/plugins/lib/frontend-tests.yml b/src/bin/plugins/lib/frontend-tests.yml
deleted file mode 100644
index 8491ec3d1..000000000
--- a/src/bin/plugins/lib/frontend-tests.yml
+++ /dev/null
@@ -1,110 +0,0 @@
-# Publicly credit Sauce Labs because they generously support open source
-# projects.
-name: "frontend tests powered by Sauce Labs"
-
-on: [push]
-
-jobs:
- test:
- runs-on: ubuntu-latest
-
- steps:
- -
- name: Fail if Dependabot
- if: github.actor == 'dependabot[bot]'
- run: |
- cat <&2
- Frontend tests skipped because Dependabot can't access secrets.
- Manually re-run the jobs to run the frontend tests.
- For more information, see:
- https://github.blog/changelog/2021-02-19-github-actions-workflows-triggered-by-dependabot-prs-will-run-with-read-only-permissions/
- EOF
- exit 1
- -
- name: Generate Sauce Labs strings
- id: sauce_strings
- run: |
- printf %s\\n '::set-output name=name::${{github.event.repository.name}} ${{ github.workflow }} - ${{ github.job }}'
- printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}'
- -
- name: Check out Etherpad core
- uses: actions/checkout@v3
- with:
- repository: ether/etherpad-lite
- -
- uses: actions/setup-node@v3
- with:
- node-version: 12
- cache: 'npm'
- cache-dependency-path: |
- src/package-lock.json
- src/bin/doc/package-lock.json
- -
- name: Check out the plugin
- uses: actions/checkout@v3
- with:
- path: ./node_modules/__tmp
- -
- name: export GIT_HASH to env
- id: environment
- run: |
- cd ./node_modules/__tmp
- echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})"
- -
- name: Determine plugin name
- id: plugin_name
- run: |
- cd ./node_modules/__tmp
- npx -c 'printf %s\\n "::set-output name=plugin_name::${npm_package_name}"'
- -
- name: Rename plugin directory
- env:
- PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
- run: |
- mv ./node_modules/__tmp ./node_modules/"${PLUGIN_NAME}"
- -
- name: Install plugin dependencies
- env:
- PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
- run: |
- cd ./node_modules/"${PLUGIN_NAME}"
- npm ci
- # 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 Etherpad core dependencies
- run: src/bin/installDeps.sh
- -
- name: Create settings.json
- run: cp settings.json.template settings.json
- -
- name: Disable import/export rate limiting
- run: |
- sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 0/' -i settings.json
- -
- name: Remove standard frontend test files
- run: rm -rf src/tests/frontend/specs
- -
- uses: saucelabs/sauce-connect-action@v2.1.1
- 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
diff --git a/src/bin/plugins/lib/npmpublish.yml b/src/bin/plugins/lib/npmpublish.yml
deleted file mode 100644
index d9dfb3fee..000000000
--- a/src/bin/plugins/lib/npmpublish.yml
+++ /dev/null
@@ -1,122 +0,0 @@
-# This workflow will run tests using node and then publish a package to the npm registry when a release is created
-# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
-
-name: Node.js Package
-
-on:
- pull_request:
- push:
- branches:
- - main
- - master
-
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- # Clone ether/etherpad-lite to ../etherpad-lite so that ep_etherpad-lite
- # can be "installed" in this plugin's node_modules. The checkout v2 action
- # doesn't support cloning outside of $GITHUB_WORKSPACE (see
- # https://github.com/actions/checkout/issues/197), so the repo is first
- # cloned to etherpad-lite then moved to ../etherpad-lite. To avoid
- # conflicts with this plugin's clone, etherpad-lite must be cloned and
- # moved out before this plugin's repo is cloned to $GITHUB_WORKSPACE.
- -
- uses: actions/checkout@v3
- with:
- repository: ether/etherpad-lite
- path: etherpad-lite
- -
- run: mv etherpad-lite ..
- # etherpad-lite has been moved outside of $GITHUB_WORKSPACE, so it is now
- # safe to clone this plugin's repo to $GITHUB_WORKSPACE.
- -
- uses: actions/checkout@v3
- # This is necessary for actions/setup-node because '..' can't be used in
- # cache-dependency-path.
- -
- name: Create ep_etherpad-lite symlink
- run: |
- mkdir -p node_modules
- ln -s ../../etherpad-lite/src node_modules/ep_etherpad-lite
- -
- uses: actions/setup-node@v3
- with:
- node-version: 12
- cache: 'npm'
- cache-dependency-path: |
- node_modules/ep_etherpad-lite/package-lock.json
- node_modules/ep_etherpad-lite/bin/doc/package-lock.json
- package-lock.json
- # All of ep_etherpad-lite's devDependencies are installed because the
- # plugin might do `require('ep_etherpad-lite/node_modules/${devDep}')`.
- # Eventually it would be nice to create an ESLint plugin that prohibits
- # Etherpad plugins from piggybacking off of ep_etherpad-lite's
- # devDependencies. If we had that, we could change this line to only
- # install production dependencies.
- -
- run: cd ../etherpad-lite/src && npm ci
- -
- run: npm ci
- # This runs some sanity checks and creates a symlink at
- # node_modules/ep_etherpad-lite that points to ../../etherpad-lite/src.
- # This step must be done after `npm ci` installs the plugin's dependencies
- # because npm "helpfully" cleans up such symlinks. :( Installing
- # ep_etherpad-lite in the plugin's node_modules prevents lint errors and
- # unit test failures if the plugin does `require('ep_etherpad-lite/foo')`.
- -
- run: npm install --no-save ep_etherpad-lite@file:../etherpad-lite/src
- -
- run: npm test
- -
- run: npm run lint
-
- publish-npm:
- if: github.event_name == 'push'
- needs: test
- runs-on: ubuntu-latest
- steps:
- -
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
- -
- uses: actions/setup-node@v3
- with:
- node-version: 12
- registry-url: https://registry.npmjs.org/
- cache: 'npm'
- -
- name: Bump version (patch)
- run: |
- LATEST_TAG=$(git describe --tags --abbrev=0) || exit 1
- NEW_COMMITS=$(git rev-list --count "${LATEST_TAG}"..) || exit 1
- [ "${NEW_COMMITS}" -gt 0 ] || exit 0
- git config user.name 'github-actions[bot]'
- git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
- npm ci
- npm version patch
- git push --follow-tags
- # This is required if the package has a prepare script that uses something
- # in dependencies or devDependencies.
- -
- run: npm ci
- # `npm publish` must come after `git push` otherwise there is a race
- # condition: If two PRs are merged back-to-back then master/main will be
- # updated with the commits from the second PR before the first PR's
- # workflow has a chance to push the commit generated by `npm version
- # patch`. This causes the first PR's `git push` step to fail after the
- # package has already been published, which in turn will cause all future
- # workflow runs to fail because they will all attempt to use the same
- # already-used version number. By running `npm publish` after `git push`,
- # back-to-back merges will cause the first merge's workflow to fail but
- # the second's will succeed.
- -
- run: npm publish
- env:
- NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
- -
- name: Add package to etherpad organization
- run: npm access grant read-write etherpad:developers
- env:
- NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
diff --git a/src/bin/plugins/stalePlugins.js b/src/bin/plugins/stalePlugins.js
deleted file mode 100644
index 7e1817c66..000000000
--- a/src/bin/plugins/stalePlugins.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-// Returns a list of stale plugins and their authors email
-
-const superagent = require('superagent');
-const currentTime = new Date();
-
-(async () => {
- const res = await superagent.get('https://static.etherpad.org/plugins.full.json');
- const plugins = JSON.parse(res.text);
- for (const plugin of Object.keys(plugins)) {
- const name = plugins[plugin].data.name;
- const date = new Date(plugins[plugin].time);
- const diffTime = Math.abs(currentTime - date);
- const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
- if (diffDays > (365 * 2)) {
- console.log(`${name}, ${plugins[plugin].data.maintainers[0].email}`);
- }
- }
-})();
diff --git a/src/ep.json b/src/ep.json
index ec09696c5..f6d41e203 100644
--- a/src/ep.json
+++ b/src/ep.json
@@ -92,14 +92,12 @@
{
"name": "adminplugins",
"hooks": {
- "expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins",
"socketio": "ep_etherpad-lite/node/hooks/express/adminplugins"
}
},
{
"name": "adminsettings",
"hooks": {
- "expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminsettings",
"socketio": "ep_etherpad-lite/node/hooks/express/adminsettings"
}
},
diff --git a/src/locales/az.json b/src/locales/az.json
index 2bd1245e9..07083d064 100644
--- a/src/locales/az.json
+++ b/src/locales/az.json
@@ -13,6 +13,12 @@
"Wertuose"
]
},
+ "admin.page-title": "İdarəetmə Paneli - Etherpad",
+ "admin_plugins": "Plugin meneceri",
+ "admin_plugins.available": "Mövcud plaginlər",
+ "admin_plugins.available_not-found": "Heç bir plagin tapılmadı.",
+ "admin_plugins.available_fetching": "Alınır...",
+ "admin_plugins.available_install.value": "Yüklə",
"index.newPad": "Yeni lövhə",
"index.createOpenPad": "və ya lövhəni bu adla yarat/aç:",
"pad.toolbar.bold.title": "Qalın (Ctrl+B)",
diff --git a/src/locales/ms.json b/src/locales/ms.json
index efc45a82f..a5d8d1deb 100644
--- a/src/locales/ms.json
+++ b/src/locales/ms.json
@@ -2,9 +2,11 @@
"@metadata": {
"authors": [
"Anakmalaysia",
+ "Hakimi97",
"Jeluang Terluang"
]
},
+ "admin.page-title": "Papan muka Penyelia - Etherpad",
"index.newPad": "Pad baru",
"index.createOpenPad": "atau cipta/buka Pad yang bernama:",
"pad.toolbar.bold.title": "Tebal (Ctrl-B)",
diff --git a/src/locales/sl.json b/src/locales/sl.json
index 7e25a4d7e..e553905ad 100644
--- a/src/locales/sl.json
+++ b/src/locales/sl.json
@@ -66,7 +66,7 @@
"pad.colorpicker.cancel": "Prekliči",
"pad.loading": "Nalaganje ...",
"pad.noCookie": "Piškotka ni bilo mogoče najti. Prosimo, dovolite piškotke v vašem brskalniku! Vaša seja in nastavitve se med obiski ne bodo shranili. Razlog za to je morda, da je Etherpad v nekaterih brskalnikih vključen v iFrame. Zsgotovite, da je Etherpad na isti poddomeni/domeni kot nadrejeni iFrame.",
- "pad.permissionDenied": "Nimate dovoljenja za dostop do tega bkokca.",
+ "pad.permissionDenied": "Nimate dovoljenja za dostop do tega blokca.",
"pad.settings.padSettings": "Nastavitve blokca.",
"pad.settings.myView": "Moj prikaz",
"pad.settings.stickychat": "Vsebina klepeta je vedno na zaslonu",
@@ -98,7 +98,7 @@
"pad.modals.userdup": "Blokec je že odprt v drugem oknu",
"pad.modals.userdup.explanation": "Videti je, da je ta blokec na tem računalniku odprt v več kot enem oknu brskalnika.",
"pad.modals.userdup.advice": "Znova vzpostavite povezavo in uporabljajte to okno.",
- "pad.modals.unauth": "Nepooblaščen dostop",
+ "pad.modals.unauth": "Nepooblaščeni dostop",
"pad.modals.unauth.explanation": "Med ogledovanjem strani so se vaša dovoljenja za ogled spremenila. Poskusite se znova povezati.",
"pad.modals.looping.explanation": "Pri komunikaciji s sinhronizacijskim strežnikom je prišlo do težav.",
"pad.modals.looping.cause": "Morda ste se povezali skozi neustrezno nastavljen požarni zid ali prek posredniškega strežnika.",
diff --git a/src/locales/zh-hans.json b/src/locales/zh-hans.json
index 5a8858424..ae454be4b 100644
--- a/src/locales/zh-hans.json
+++ b/src/locales/zh-hans.json
@@ -151,11 +151,11 @@
"timeslider.toolbar.exportlink.title": "导出",
"timeslider.exportCurrent": "当前版本导出为:",
"timeslider.version": "版本 {{version}}",
- "timeslider.saved": "在{{月}}{{日}}{{年}}保存",
+ "timeslider.saved": "在{{year}}年{{month}}月{{day}}日保存",
"timeslider.playPause": "回放 / 暂停记事本内容",
"timeslider.backRevision": "返回此记事本的一次修订",
"timeslider.forwardRevision": "前往此记事本的下一次修订",
- "timeslider.dateformat": "{{月}}/{{日}}/{{年}} {{小时}}:{{分钟}}:{{秒}}",
+ "timeslider.dateformat": "{{year}}年{{month}}月{{day}}日{{hours}}时{{minutes}}分{{seconds}}秒",
"timeslider.month.january": "1月",
"timeslider.month.february": "2月",
"timeslider.month.march": "3月",
diff --git a/src/node/README.md b/src/node/README.md
index d0a61287a..88a53bfd1 100644
--- a/src/node/README.md
+++ b/src/node/README.md
@@ -10,4 +10,4 @@ Module file names start with a capital letter and uses camelCase
# Where does it start?
-server.js is started directly
+server.ts is started directly
diff --git a/src/node/db/API.js b/src/node/db/API.ts
similarity index 90%
rename from src/node/db/API.js
rename to src/node/db/API.ts
index b9efb5411..8b84693ec 100644
--- a/src/node/db/API.js
+++ b/src/node/db/API.ts
@@ -104,7 +104,7 @@ Example returns:
}
*/
-exports.getAttributePool = async (padID) => {
+exports.getAttributePool = async (padID: string) => {
const pad = await getPadSafe(padID, true);
return {pool: pad.pool};
};
@@ -122,7 +122,7 @@ Example returns:
}
*/
-exports.getRevisionChangeset = async (padID, rev) => {
+exports.getRevisionChangeset = async (padID: string, rev: string) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
@@ -155,7 +155,7 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome Text"}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getText = async (padID, rev) => {
+exports.getText = async (padID: string, rev: string) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
@@ -173,7 +173,7 @@ exports.getText = async (padID, rev) => {
}
// get the text of this revision
- // getInternalRevisionAText() returns an atext object but we only want the .text inside it.
+ // getInternalRevisionAText() returns an atext object, but we only want the .text inside it.
// Details at https://github.com/ether/etherpad-lite/issues/5073
const {text} = await pad.getInternalRevisionAText(rev);
return {text};
@@ -200,7 +200,7 @@ Example returns:
* @param {String} authorId the id of the author, defaulting to empty string
* @returns {Promise}
*/
-exports.setText = async (padID, text, authorId = '') => {
+exports.setText = async (padID: string, text?: string, authorId: string = ''): Promise => {
// text is required
if (typeof text !== 'string') {
throw new CustomError('text is not a string', 'apierror');
@@ -225,7 +225,7 @@ Example returns:
@param {String} text the text of the pad
@param {String} authorId the id of the author, defaulting to empty string
*/
-exports.appendText = async (padID, text, authorId = '') => {
+exports.appendText = async (padID:string, text?: string, authorId:string = '') => {
// text is required
if (typeof text !== 'string') {
throw new CustomError('text is not a string', 'apierror');
@@ -247,7 +247,7 @@ Example returns:
@param {String} rev the revision number, defaulting to the latest revision
@return {Promise<{html: string}>} the html of the pad
*/
-exports.getHTML = async (padID, rev) => {
+exports.getHTML = async (padID: string, rev: string): Promise<{ html: string; }> => {
if (rev !== undefined) {
rev = checkValidRev(rev);
}
@@ -283,7 +283,7 @@ Example returns:
@param {String} html the html of the pad
@param {String} authorId the id of the author, defaulting to empty string
*/
-exports.setHTML = async (padID, html, authorId = '') => {
+exports.setHTML = async (padID: string, html:string|object, authorId = '') => {
// html string is required
if (typeof html !== 'string') {
throw new CustomError('html is not a string', 'apierror');
@@ -324,7 +324,7 @@ Example returns:
@param {Number} start the start point of the chat-history
@param {Number} end the end point of the chat-history
*/
-exports.getChatHistory = async (padID, start, end) => {
+exports.getChatHistory = async (padID: string, start:number, end:number) => {
if (start && end) {
if (start < 0) {
throw new CustomError('start is below zero', 'apierror');
@@ -374,7 +374,7 @@ Example returns:
@param {String} authorID the id of the author
@param {Number} time the timestamp of the chat-message
*/
-exports.appendChatMessage = async (padID, text, authorID, time) => {
+exports.appendChatMessage = async (padID: string, text: string|object, authorID: string, time: number) => {
// text is required
if (typeof text !== 'string') {
throw new CustomError('text is not a string', 'apierror');
@@ -404,7 +404,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
-exports.getRevisionsCount = async (padID) => {
+exports.getRevisionsCount = async (padID: string) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {revisions: pad.getHeadRevisionNumber()};
@@ -419,7 +419,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
-exports.getSavedRevisionsCount = async (padID) => {
+exports.getSavedRevisionsCount = async (padID: string) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsNumber()};
@@ -434,7 +434,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
-exports.listSavedRevisions = async (padID) => {
+exports.listSavedRevisions = async (padID: string) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsList()};
@@ -450,7 +450,7 @@ Example returns:
@param {String} padID the id of the pad
@param {Number} rev the revision number, defaulting to the latest revision
*/
-exports.saveRevision = async (padID, rev) => {
+exports.saveRevision = async (padID: string, rev: number) => {
// check if rev is a number
if (rev !== undefined) {
rev = checkValidRev(rev);
@@ -483,7 +483,7 @@ Example returns:
@param {String} padID the id of the pad
@return {Promise<{lastEdited: number}>} the timestamp of the last revision of the pad
*/
-exports.getLastEdited = async (padID) => {
+exports.getLastEdited = async (padID: string): Promise<{ lastEdited: number; }> => {
// get the pad
const pad = await getPadSafe(padID, true);
const lastEdited = await pad.getLastEdit();
@@ -497,11 +497,11 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"pad does already exist", data: null}
- @param {String} padName the name of the new pad
+ @param {String} padID the name of the new pad
@param {String} text the initial text of the pad
@param {String} authorId the id of the author, defaulting to empty string
*/
-exports.createPad = async (padID, text, authorId = '') => {
+exports.createPad = async (padID: string, text: string, authorId = '') => {
if (padID) {
// ensure there is no $ in the padID
if (padID.indexOf('$') !== -1) {
@@ -527,7 +527,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
-exports.deletePad = async (padID) => {
+exports.deletePad = async (padID: string) => {
const pad = await getPadSafe(padID, true);
await pad.remove();
};
@@ -543,7 +543,7 @@ exports.deletePad = async (padID) => {
@param {Number} rev the revision number, defaulting to the latest revision
@param {String} authorId the id of the author, defaulting to empty string
*/
-exports.restoreRevision = async (padID, rev, authorId = '') => {
+exports.restoreRevision = async (padID: string, rev: number, authorId = '') => {
// check if rev is a number
if (rev === undefined) {
throw new CustomError('rev is not defined', 'apierror');
@@ -563,7 +563,7 @@ exports.restoreRevision = async (padID, rev, authorId = '') => {
const oldText = pad.text();
atext.text += '\n';
- const eachAttribRun = (attribs, func) => {
+ const eachAttribRun = (attribs: string[], func:Function) => {
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = atext.text.length;
@@ -580,7 +580,7 @@ exports.restoreRevision = async (padID, rev, authorId = '') => {
const builder = Changeset.builder(oldText.length);
// assemble each line into the builder
- eachAttribRun(atext.attribs, (start, end, attribs) => {
+ eachAttribRun(atext.attribs, (start: number, end: number, attribs:string[]) => {
builder.insert(atext.text.substring(start, end), attribs);
});
@@ -610,7 +610,7 @@ Example returns:
@param {String} destinationID the id of the destination pad
@param {Boolean} force whether to overwrite the destination pad if it exists
*/
-exports.copyPad = async (sourceID, destinationID, force) => {
+exports.copyPad = async (sourceID: string, destinationID: string, force: boolean) => {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
};
@@ -628,7 +628,7 @@ Example returns:
@param {Boolean} force whether to overwrite the destination pad if it exists
@param {String} authorId the id of the author, defaulting to empty string
*/
-exports.copyPadWithoutHistory = async (sourceID, destinationID, force, authorId = '') => {
+exports.copyPadWithoutHistory = async (sourceID: string, destinationID: string, force:boolean, authorId = '') => {
const pad = await getPadSafe(sourceID, true);
await pad.copyPadWithoutHistory(destinationID, force, authorId);
};
@@ -645,7 +645,7 @@ Example returns:
@param {String} destinationID the id of the destination pad
@param {Boolean} force whether to overwrite the destination pad if it exists
*/
-exports.movePad = async (sourceID, destinationID, force) => {
+exports.movePad = async (sourceID: string, destinationID: string, force:boolean) => {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
await pad.remove();
@@ -660,7 +660,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
-exports.getReadOnlyID = async (padID) => {
+exports.getReadOnlyID = async (padID: string) => {
// we don't need the pad object, but this function does all the security stuff for us
await getPadSafe(padID, true);
@@ -679,7 +679,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} roID the readonly id of the pad
*/
-exports.getPadID = async (roID) => {
+exports.getPadID = async (roID: string) => {
// get the PadId
const padID = await readOnlyManager.getPadId(roID);
if (padID == null) {
@@ -699,7 +699,7 @@ Example returns:
@param {String} padID the id of the pad
@param {Boolean} publicStatus the public status of the pad
*/
-exports.setPublicStatus = async (padID, publicStatus) => {
+exports.setPublicStatus = async (padID: string, publicStatus: boolean|string) => {
// ensure this is a group pad
checkGroupPad(padID, 'publicStatus');
@@ -723,7 +723,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
-exports.getPublicStatus = async (padID) => {
+exports.getPublicStatus = async (padID: string) => {
// ensure this is a group pad
checkGroupPad(padID, 'publicStatus');
@@ -741,7 +741,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
-exports.listAuthorsOfPad = async (padID) => {
+exports.listAuthorsOfPad = async (padID: string) => {
// get the pad
const pad = await getPadSafe(padID, true);
const authorIDs = pad.getAllAuthors();
@@ -773,7 +773,7 @@ Example returns:
@param {String} msg the message to send
*/
-exports.sendClientsMessage = async (padID, msg) => {
+exports.sendClientsMessage = async (padID: string, msg: string) => {
await getPadSafe(padID, true); // Throw if the padID is invalid or if the pad does not exist.
padMessageHandler.handleCustomMessage(padID, msg);
};
@@ -799,7 +799,7 @@ Example returns:
@param {String} padID the id of the pad
@return {Promise<{chatHead: number}>} the chatHead of the pad
*/
-exports.getChatHead = async (padID) => {
+exports.getChatHead = async (padID:string): Promise<{ chatHead: number; }> => {
// get the pad
const pad = await getPadSafe(padID, true);
return {chatHead: pad.chatHead};
@@ -825,7 +825,7 @@ Example returns:
@param {Number} startRev the start revision number
@param {Number} endRev the end revision number
*/
-exports.createDiffHTML = async (padID, startRev, endRev) => {
+exports.createDiffHTML = async (padID: string, startRev: number, endRev: number) => {
// check if startRev is a number
if (startRev !== undefined) {
startRev = checkValidRev(startRev);
@@ -846,7 +846,7 @@ exports.createDiffHTML = async (padID, startRev, endRev) => {
let padDiff;
try {
padDiff = new PadDiff(pad, startRev, endRev);
- } catch (e) {
+ } catch (e:any) {
throw {stop: e.message};
}
@@ -872,6 +872,7 @@ exports.getStats = async () => {
const sessionInfos = padMessageHandler.sessioninfos;
const sessionKeys = Object.keys(sessionInfos);
+ // @ts-ignore
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
const {padIDs} = await padManager.listAllPads();
@@ -888,7 +889,7 @@ exports.getStats = async () => {
**************************** */
// gets a pad safe
-const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
+const getPadSafe = async (padID: string|object, shouldExist: boolean, text?:string, authorId:string = '') => {
// check if padID is a string
if (typeof padID !== 'string') {
throw new CustomError('padID is not a string', 'apierror');
@@ -917,7 +918,7 @@ const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
};
// checks if a padID is part of a group
-const checkGroupPad = (padID, field) => {
+const checkGroupPad = (padID: string, field: string) => {
// ensure this is a group pad
if (padID && padID.indexOf('$') === -1) {
throw new CustomError(
diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.ts
similarity index 86%
rename from src/node/db/AuthorManager.js
rename to src/node/db/AuthorManager.ts
index 7413b5806..2f4e7d751 100644
--- a/src/node/db/AuthorManager.js
+++ b/src/node/db/AuthorManager.ts
@@ -95,7 +95,7 @@ exports.getColorPalette = () => [
* Checks if the author exists
* @param {String} authorID The id of the author
*/
-exports.doesAuthorExist = async (authorID) => {
+exports.doesAuthorExist = async (authorID: string) => {
const author = await db.get(`globalAuthor:${authorID}`);
return author != null;
@@ -114,7 +114,7 @@ exports.doesAuthorExists = exports.doesAuthorExist;
* @param {String} mapperkey The database key name for this mapper
* @param {String} mapper The mapper
*/
-const mapAuthorWithDBKey = async (mapperkey, mapper) => {
+const mapAuthorWithDBKey = async (mapperkey: string, mapper:string) => {
// try to map to an author
const author = await db.get(`${mapperkey}:${mapper}`);
@@ -142,7 +142,7 @@ const mapAuthorWithDBKey = async (mapperkey, mapper) => {
* @param {String} token The token of the author
* @return {Promise}
*/
-const getAuthor4Token = async (token) => {
+const getAuthor4Token = async (token: string) => {
const author = await mapAuthorWithDBKey('token2author', token);
// return only the sub value authorID
@@ -155,7 +155,7 @@ const getAuthor4Token = async (token) => {
* @param {Object} user
* @return {Promise<*>}
*/
-exports.getAuthorId = async (token, user) => {
+exports.getAuthorId = async (token: string, user: object) => {
const context = {dbKey: token, token, user};
let [authorId] = await hooks.aCallFirst('getAuthorId', context);
if (!authorId) authorId = await getAuthor4Token(context.dbKey);
@@ -168,7 +168,7 @@ exports.getAuthorId = async (token, user) => {
* @deprecated Use `getAuthorId` instead.
* @param {String} token The token
*/
-exports.getAuthor4Token = async (token) => {
+exports.getAuthor4Token = async (token: string) => {
warnDeprecated(
'AuthorManager.getAuthor4Token() is deprecated; use AuthorManager.getAuthorId() instead');
return await getAuthor4Token(token);
@@ -179,7 +179,7 @@ exports.getAuthor4Token = async (token) => {
* @param {String} authorMapper The mapper
* @param {String} name The name of the author (optional)
*/
-exports.createAuthorIfNotExistsFor = async (authorMapper, name) => {
+exports.createAuthorIfNotExistsFor = async (authorMapper: string, name: string) => {
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
if (name) {
@@ -195,7 +195,7 @@ exports.createAuthorIfNotExistsFor = async (authorMapper, name) => {
* Internal function that creates the database entry for an author
* @param {String} name The name of the author
*/
-exports.createAuthor = async (name) => {
+exports.createAuthor = async (name: string) => {
// create the new author name
const author = `a.${randomString(16)}`;
@@ -216,41 +216,41 @@ exports.createAuthor = async (name) => {
* Returns the Author Obj of the author
* @param {String} author The id of the author
*/
-exports.getAuthor = async (author) => await db.get(`globalAuthor:${author}`);
+exports.getAuthor = async (author: string) => await db.get(`globalAuthor:${author}`);
/**
* Returns the color Id of the author
* @param {String} author The id of the author
*/
-exports.getAuthorColorId = async (author) => await db.getSub(`globalAuthor:${author}`, ['colorId']);
+exports.getAuthorColorId = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['colorId']);
/**
* Sets the color Id of the author
* @param {String} author The id of the author
* @param {String} colorId The color id of the author
*/
-exports.setAuthorColorId = async (author, colorId) => await db.setSub(
+exports.setAuthorColorId = async (author: string, colorId: string) => await db.setSub(
`globalAuthor:${author}`, ['colorId'], colorId);
/**
* Returns the name of the author
* @param {String} author The id of the author
*/
-exports.getAuthorName = async (author) => await db.getSub(`globalAuthor:${author}`, ['name']);
+exports.getAuthorName = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['name']);
/**
* Sets the name of the author
* @param {String} author The id of the author
* @param {String} name The name of the author
*/
-exports.setAuthorName = async (author, name) => await db.setSub(
+exports.setAuthorName = async (author: string, name: string) => await db.setSub(
`globalAuthor:${author}`, ['name'], name);
/**
* Returns an array of all pads this author contributed to
* @param {String} authorID The id of the author
*/
-exports.listPadsOfAuthor = async (authorID) => {
+exports.listPadsOfAuthor = async (authorID: string) => {
/* There are two other places where this array is manipulated:
* (1) When the author is added to a pad, the author object is also updated
* (2) When a pad is deleted, each author of that pad is also updated
@@ -275,7 +275,7 @@ exports.listPadsOfAuthor = async (authorID) => {
* @param {String} authorID The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
-exports.addPad = async (authorID, padID) => {
+exports.addPad = async (authorID: string, padID: string) => {
// get the entry
const author = await db.get(`globalAuthor:${authorID}`);
@@ -302,7 +302,7 @@ exports.addPad = async (authorID, padID) => {
* @param {String} authorID The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
-exports.removePad = async (authorID, padID) => {
+exports.removePad = async (authorID: string, padID: string) => {
const author = await db.get(`globalAuthor:${authorID}`);
if (author == null) return;
diff --git a/src/node/db/DB.js b/src/node/db/DB.ts
similarity index 88%
rename from src/node/db/DB.js
rename to src/node/db/DB.ts
index 02e83f85d..542da6735 100644
--- a/src/node/db/DB.js
+++ b/src/node/db/DB.ts
@@ -21,10 +21,10 @@
* limitations under the License.
*/
-const ueberDB = require('ueberdb2');
+import ueberDB from 'ueberdb2';
const settings = require('../utils/Settings');
-const log4js = require('log4js');
-const stats = require('../stats');
+import log4js from 'log4js';
+const stats = require('../stats')
const logger = log4js.getLogger('ueberDB');
@@ -47,13 +47,13 @@ exports.init = async () => {
}
for (const fn of ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove']) {
const f = exports.db[fn];
- exports[fn] = async (...args) => await f.call(exports.db, ...args);
+ exports[fn] = async (...args:string[]) => await f.call(exports.db, ...args);
Object.setPrototypeOf(exports[fn], Object.getPrototypeOf(f));
Object.defineProperties(exports[fn], Object.getOwnPropertyDescriptors(f));
}
};
-exports.shutdown = async (hookName, context) => {
+exports.shutdown = async (hookName: string, context:any) => {
if (exports.db != null) await exports.db.close();
exports.db = null;
logger.log('Database closed');
diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.ts
similarity index 93%
rename from src/node/db/GroupManager.js
rename to src/node/db/GroupManager.ts
index 2ab20ac16..0524c4eda 100644
--- a/src/node/db/GroupManager.js
+++ b/src/node/db/GroupManager.ts
@@ -42,7 +42,7 @@ exports.listAllGroups = async () => {
* @param {String} groupID The id of the group
* @return {Promise} Resolves when the group is deleted
*/
-exports.deleteGroup = async (groupID) => {
+exports.deleteGroup = async (groupID: string): Promise => {
const group = await db.get(`group:${groupID}`);
// ensure group exists
@@ -82,7 +82,7 @@ exports.deleteGroup = async (groupID) => {
* @param {String} groupID the id of the group to delete
* @return {Promise} Resolves to true if the group exists
*/
-exports.doesGroupExist = async (groupID) => {
+exports.doesGroupExist = async (groupID: string) => {
// try to get the group entry
const group = await db.get(`group:${groupID}`);
@@ -108,7 +108,7 @@ exports.createGroup = async () => {
* @param groupMapper the mapper of the group
* @return {Promise<{groupID: string}|{groupID: *}>} a promise that resolves to the group ID
*/
-exports.createGroupIfNotExistsFor = async (groupMapper) => {
+exports.createGroupIfNotExistsFor = async (groupMapper: string|object) => {
if (typeof groupMapper !== 'string') {
throw new CustomError('groupMapper is not a string', 'apierror');
}
@@ -134,7 +134,7 @@ exports.createGroupIfNotExistsFor = async (groupMapper) => {
* @param {String} authorId The id of the author
* @return {Promise<{padID: string}>} a promise that resolves to the id of the new pad
*/
-exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
+exports.createGroupPad = async (groupID: string, padName: string, text: string, authorId: string = ''): Promise<{ padID: string; }> => {
// create the padID
const padID = `${groupID}$${padName}`;
@@ -167,7 +167,7 @@ exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
* @param {String} groupID The id of the group
* @return {Promise<{padIDs: string[]}>} a promise that resolves to the ids of all pads of the group
*/
-exports.listPads = async (groupID) => {
+exports.listPads = async (groupID: string): Promise<{ padIDs: string[]; }> => {
const exists = await exports.doesGroupExist(groupID);
// ensure the group exists
diff --git a/src/node/db/Pad.js b/src/node/db/Pad.ts
similarity index 89%
rename from src/node/db/Pad.js
rename to src/node/db/Pad.ts
index 884b420f0..fa4af994d 100644
--- a/src/node/db/Pad.js
+++ b/src/node/db/Pad.ts
@@ -1,4 +1,8 @@
'use strict';
+import {Database} from "ueberdb2";
+import {AChangeSet, APool, AText} from "../types/PadType";
+import {MapArrayType} from "../types/MapType";
+
/**
* The pad object, defined with joose
*/
@@ -28,20 +32,29 @@ const promises = require('../utils/promises');
* @param {String} txt The text to clean
* @returns {String} The cleaned text
*/
-exports.cleanText = (txt) => txt.replace(/\r\n/g, '\n')
+exports.cleanText = (txt:string): string => txt.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\xa0/g, ' ');
class Pad {
+ private db: Database;
+ private atext: AText;
+ private pool: APool;
+ private head: number;
+ private chatHead: number;
+ private publicStatus: boolean;
+ private id: string;
+ private savedRevisions: any[];
/**
+ * @param id
* @param [database] - Database object to access this pad's records (and only this pad's records;
* the shared global Etherpad database object is still used for all other pad accesses, such
* as copying the pad). Defaults to the shared global Etherpad database object. This parameter
* can be used to shard pad storage across multiple database backends, to put each pad in its
* own database table, or to validate imported pad data before it is written to the database.
*/
- constructor(id, database = db) {
+ constructor(id:string, database = db) {
this.db = database;
this.atext = Changeset.makeAText('\n');
this.pool = new AttributePool();
@@ -80,7 +93,7 @@ class Pad {
* @param {String} authorId The id of the author
* @return {Promise}
*/
- async appendRevision(aChangeset, authorId = '') {
+ async appendRevision(aChangeset:AChangeSet, authorId = '') {
const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
if (newAText.text === this.atext.text && newAText.attribs === this.atext.attribs &&
this.head !== -1) {
@@ -95,6 +108,7 @@ class Pad {
const hook = this.head === 0 ? 'padCreate' : 'padUpdate';
await Promise.all([
+ // @ts-ignore
this.db.set(`pad:${this.id}:revs:${newRev}`, {
changeset: aChangeset,
meta: {
@@ -129,32 +143,39 @@ class Pad {
}
toJSON() {
- const o = {...this, pool: this.pool.toJsonable()};
+ const o:Pad = {...this, pool: this.pool.toJsonable()};
+ // @ts-ignore
delete o.db;
+ // @ts-ignore
delete o.id;
return o;
}
// save all attributes to the database
async saveToDatabase() {
+ // @ts-ignore
await this.db.set(`pad:${this.id}`, this);
}
// get time of last edit (changeset application)
async getLastEdit() {
const revNum = this.getHeadRevisionNumber();
+ // @ts-ignore
return await this.db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
}
- async getRevisionChangeset(revNum) {
+ async getRevisionChangeset(revNum: number) {
+ // @ts-ignore
return await this.db.getSub(`pad:${this.id}:revs:${revNum}`, ['changeset']);
}
- async getRevisionAuthor(revNum) {
+ async getRevisionAuthor(revNum: number) {
+ // @ts-ignore
return await this.db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'author']);
}
- async getRevisionDate(revNum) {
+ async getRevisionDate(revNum: number) {
+ // @ts-ignore
return await this.db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
}
@@ -162,7 +183,8 @@ class Pad {
* @param {number} revNum - Must be a key revision number (see `getKeyRevisionNumber`).
* @returns The attribute text stored at `revNum`.
*/
- async _getKeyRevisionAText(revNum) {
+ async _getKeyRevisionAText(revNum: number) {
+ // @ts-ignore
return await this.db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'atext']);
}
@@ -182,7 +204,7 @@ class Pad {
return authorIds;
}
- async getInternalRevisionAText(targetRev) {
+ async getInternalRevisionAText(targetRev: number) {
const keyRev = this.getKeyRevisionNumber(targetRev);
const headRev = this.getHeadRevisionNumber();
if (targetRev > headRev) targetRev = headRev;
@@ -197,17 +219,17 @@ class Pad {
return atext;
}
- async getRevision(revNum) {
+ async getRevision(revNum: number) {
return await this.db.get(`pad:${this.id}:revs:${revNum}`);
}
async getAllAuthorColors() {
const authorIds = this.getAllAuthors();
- const returnTable = {};
+ const returnTable:MapArrayType = {};
const colorPalette = authorManager.getColorPalette();
await Promise.all(
- authorIds.map((authorId) => authorManager.getAuthorColorId(authorId).then((colorId) => {
+ authorIds.map((authorId) => authorManager.getAuthorColorId(authorId).then((colorId:string) => {
// colorId might be a hex color or an number out of the palette
returnTable[authorId] = colorPalette[colorId] || colorId;
})));
@@ -215,7 +237,7 @@ class Pad {
return returnTable;
}
- getValidRevisionRange(startRev, endRev) {
+ getValidRevisionRange(startRev: any, endRev:any) {
startRev = parseInt(startRev, 10);
const head = this.getHeadRevisionNumber();
endRev = endRev ? parseInt(endRev, 10) : head;
@@ -236,14 +258,14 @@ class Pad {
return null;
}
- getKeyRevisionNumber(revNum) {
+ getKeyRevisionNumber(revNum: number) {
return Math.floor(revNum / 100) * 100;
}
/**
* @returns {string} The pad's text.
*/
- text() {
+ text(): string {
return this.atext.text;
}
@@ -258,7 +280,7 @@ class Pad {
* @param {string} ins - New text to insert at `start` (after the `ndel` characters are deleted).
* @param {string} [authorId] - Author ID of the user making the change (if applicable).
*/
- async spliceText(start, ndel, ins, authorId = '') {
+ async spliceText(start:number, ndel:number, ins: string, authorId: string = '') {
if (start < 0) throw new RangeError(`start index must be non-negative (is ${start})`);
if (ndel < 0) throw new RangeError(`characters to delete must be non-negative (is ${ndel})`);
const orig = this.text();
@@ -283,7 +305,7 @@ class Pad {
* @param {string} [authorId] - The author ID of the user that initiated the change, if
* applicable.
*/
- async setText(newText, authorId = '') {
+ async setText(newText: string, authorId = '') {
await this.spliceText(0, this.text().length, newText, authorId);
}
@@ -294,7 +316,7 @@ class Pad {
* @param {string} [authorId] - The author ID of the user that initiated the change, if
* applicable.
*/
- async appendText(newText, authorId = '') {
+ async appendText(newText:string, authorId = '') {
await this.spliceText(this.text().length - 1, 0, newText, authorId);
}
@@ -308,7 +330,7 @@ class Pad {
* @param {?number} [time] - Message timestamp (milliseconds since epoch). Deprecated; use
* `msgOrText.time` instead.
*/
- async appendChatMessage(msgOrText, authorId = null, time = null) {
+ async appendChatMessage(msgOrText: string|typeof ChatMessage, authorId = null, time = null) {
const msg =
msgOrText instanceof ChatMessage ? msgOrText : new ChatMessage(msgOrText, authorId, time);
this.chatHead++;
@@ -325,7 +347,7 @@ class Pad {
* @param {number} entryNum - ID of the desired chat message.
* @returns {?ChatMessage}
*/
- async getChatMessage(entryNum) {
+ async getChatMessage(entryNum: number) {
const entry = await this.db.get(`pad:${this.id}:chat:${entryNum}`);
if (entry == null) return null;
const message = ChatMessage.fromObject(entry);
@@ -340,7 +362,7 @@ class Pad {
* (inclusive), in order. Note: `start` and `end` form a closed interval, not a half-open
* interval as is typical in code.
*/
- async getChatMessages(start, end) {
+ async getChatMessages(start: string, end: number) {
const entries =
await Promise.all(Stream.range(start, end + 1).map(this.getChatMessage.bind(this)));
@@ -356,7 +378,7 @@ class Pad {
});
}
- async init(text, authorId = '') {
+ async init(text:string, authorId = '') {
// try to load the pad
const value = await this.db.get(`pad:${this.id}`);
@@ -377,7 +399,7 @@ class Pad {
await hooks.aCallAll('padLoad', {pad: this});
}
- async copy(destinationID, force) {
+ async copy(destinationID: string, force: boolean) {
// Kick everyone from this pad.
// This was commented due to https://github.com/ether/etherpad-lite/issues/3183.
// Do we really need to kick everyone out?
@@ -392,15 +414,18 @@ class Pad {
// if force is true and already exists a Pad with the same id, remove that Pad
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
- const copyRecord = async (keySuffix) => {
+ const copyRecord = async (keySuffix: string) => {
const val = await this.db.get(`pad:${this.id}${keySuffix}`);
await db.set(`pad:${destinationID}${keySuffix}`, val);
};
const promises = (function* () {
yield copyRecord('');
+ // @ts-ignore
yield* Stream.range(0, this.head + 1).map((i) => copyRecord(`:revs:${i}`));
+ // @ts-ignore
yield* Stream.range(0, this.chatHead + 1).map((i) => copyRecord(`:chat:${i}`));
+ // @ts-ignore
yield this.copyAuthorInfoToDestinationPad(destinationID);
if (destGroupID) yield db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
}).call(this);
@@ -427,8 +452,8 @@ class Pad {
return {padID: destinationID};
}
- async checkIfGroupExistAndReturnIt(destinationID) {
- let destGroupID = false;
+ async checkIfGroupExistAndReturnIt(destinationID: string) {
+ let destGroupID:false|string = false;
if (destinationID.indexOf('$') >= 0) {
destGroupID = destinationID.split('$')[0];
@@ -442,7 +467,7 @@ class Pad {
return destGroupID;
}
- async removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
+ async removePadIfForceIsTrueAndAlreadyExist(destinationID: string, force: boolean|string) {
// if the pad exists, we should abort, unless forced.
const exists = await padManager.doesPadExist(destinationID);
@@ -465,13 +490,13 @@ class Pad {
}
}
- async copyAuthorInfoToDestinationPad(destinationID) {
+ async copyAuthorInfoToDestinationPad(destinationID: string) {
// add the new sourcePad to all authors who contributed to the old one
await Promise.all(this.getAllAuthors().map(
(authorID) => authorManager.addPad(authorID, destinationID)));
}
- async copyPadWithoutHistory(destinationID, force, authorId = '') {
+ async copyPadWithoutHistory(destinationID: string, force: string|boolean, authorId = '') {
// flush the source pad
this.saveToDatabase();
@@ -554,18 +579,18 @@ class Pad {
}
// remove the readonly entries
- p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => {
+ p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID: string) => {
await db.remove(`readonly2pad:${readonlyID}`);
}));
p.push(db.remove(`pad2readonly:${padID}`));
// delete all chat messages
- p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => {
+ p.push(promises.timesLimit(this.chatHead + 1, 500, async (i: string) => {
await this.db.remove(`pad:${this.id}:chat:${i}`, null);
}));
// delete all revisions
- p.push(promises.timesLimit(this.head + 1, 500, async (i) => {
+ p.push(promises.timesLimit(this.head + 1, 500, async (i: string) => {
await this.db.remove(`pad:${this.id}:revs:${i}`, null);
}));
@@ -587,12 +612,12 @@ class Pad {
}
// set in db
- async setPublicStatus(publicStatus) {
+ async setPublicStatus(publicStatus: boolean) {
this.publicStatus = publicStatus;
await this.saveToDatabase();
}
- async addSavedRevision(revNum, savedById, label) {
+ async addSavedRevision(revNum: string, savedById: string, label: string) {
// if this revision is already saved, return silently
for (const i in this.savedRevisions) {
if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) {
@@ -601,7 +626,7 @@ class Pad {
}
// build the saved revision object
- const savedRevision = {};
+ const savedRevision:MapArrayType = {};
savedRevision.revNum = revNum;
savedRevision.savedById = savedById;
savedRevision.label = label || `Revision ${revNum}`;
@@ -664,7 +689,7 @@ class Pad {
if (k === 'author' && v) authorIds.add(v);
});
const revs = Stream.range(0, head + 1)
- .map(async (r) => {
+ .map(async (r: number) => {
const isKeyRev = r === this.getKeyRevisionNumber(r);
try {
return await Promise.all([
@@ -675,7 +700,7 @@ class Pad {
isKeyRev,
isKeyRev ? this._getKeyRevisionAText(r) : null,
]);
- } catch (err) {
+ } catch (err:any) {
err.message = `(pad ${this.id} revision ${r}) ${err.message}`;
throw err;
}
@@ -708,7 +733,7 @@ class Pad {
}
atext = Changeset.applyToAText(changeset, atext, pool);
if (isKeyRev) assert.deepEqual(keyAText, atext);
- } catch (err) {
+ } catch (err:any) {
err.message = `(pad ${this.id} revision ${r}) ${err.message}`;
throw err;
}
@@ -721,12 +746,12 @@ class Pad {
assert(Number.isInteger(this.chatHead));
assert(this.chatHead >= -1);
const chats = Stream.range(0, this.chatHead + 1)
- .map(async (c) => {
+ .map(async (c: number) => {
try {
const msg = await this.getChatMessage(c);
assert(msg != null);
assert(msg instanceof ChatMessage);
- } catch (err) {
+ } catch (err:any) {
err.message = `(pad ${this.id} chat message ${c}) ${err.message}`;
throw err;
}
diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.ts
similarity index 85%
rename from src/node/db/PadManager.js
rename to src/node/db/PadManager.ts
index 7c4021541..54dbbf089 100644
--- a/src/node/db/PadManager.js
+++ b/src/node/db/PadManager.ts
@@ -19,6 +19,9 @@
* limitations under the License.
*/
+import {MapArrayType} from "../types/MapType";
+import {PadType} from "../types/PadType";
+
const CustomError = require('../utils/customError');
const Pad = require('../db/Pad');
const db = require('./DB');
@@ -35,12 +38,16 @@ const settings = require('../utils/Settings');
* If this is needed in other places, it would be wise to make this a prototype
* that's defined somewhere more sensible.
*/
-const globalPads = {
- get(name) { return this[`:${name}`]; },
- set(name, value) {
+const globalPads:MapArrayType = {
+ get(name: string)
+ {
+ return this[`:${name}`];
+ },
+ set(name: string, value: any)
+ {
this[`:${name}`] = value;
},
- remove(name) {
+ remove(name: string) {
delete this[`:${name}`];
},
};
@@ -51,6 +58,9 @@ const globalPads = {
* Updated without db access as new pads are created/old ones removed.
*/
const padList = new class {
+ private _cachedList: string[] | null;
+ private _list: Set;
+ private _loaded: Promise | null;
constructor() {
this._cachedList = null;
this._list = new Set();
@@ -74,13 +84,13 @@ const padList = new class {
return this._cachedList;
}
- addPad(name) {
+ addPad(name: string) {
if (this._list.has(name)) return;
this._list.add(name);
this._cachedList = null;
}
- removePad(name) {
+ removePad(name: string) {
if (!this._list.has(name)) return;
this._list.delete(name);
this._cachedList = null;
@@ -96,7 +106,7 @@ const padList = new class {
* @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if
* applicable).
*/
-exports.getPad = async (id, text, authorId = '') => {
+exports.getPad = async (id: string, text?: string|null, authorId:string|null = ''):Promise => {
// check if this is a valid padId
if (!exports.isValidPadId(id)) {
throw new CustomError(`${id} is not a valid padId`, 'apierror');
@@ -139,8 +149,11 @@ exports.listAllPads = async () => {
return {padIDs};
};
+
+
+
// checks if a pad exists
-exports.doesPadExist = async (padId) => {
+exports.doesPadExist = async (padId: string) => {
const value = await db.get(`pad:${padId}`);
return (value != null && value.atext);
@@ -159,7 +172,7 @@ const padIdTransforms = [
];
// returns a sanitized padId, respecting legacy pad id formats
-exports.sanitizePadId = async (padId) => {
+exports.sanitizePadId = async (padId: string) => {
for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
const exists = await exports.doesPadExist(padId);
@@ -169,6 +182,7 @@ exports.sanitizePadId = async (padId) => {
const [from, to] = padIdTransforms[i];
+ // @ts-ignore
padId = padId.replace(from, to);
}
@@ -178,12 +192,12 @@ exports.sanitizePadId = async (padId) => {
return padId;
};
-exports.isValidPadId = (padId) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
+exports.isValidPadId = (padId: string) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
/**
* Removes the pad from database and unloads it.
*/
-exports.removePad = async (padId) => {
+exports.removePad = async (padId: string) => {
const p = db.remove(`pad:${padId}`);
exports.unloadPad(padId);
padList.removePad(padId);
@@ -191,6 +205,6 @@ exports.removePad = async (padId) => {
};
// removes a pad from the cache
-exports.unloadPad = (padId) => {
+exports.unloadPad = (padId: string) => {
globalPads.remove(padId);
};
diff --git a/src/node/db/ReadOnlyManager.js b/src/node/db/ReadOnlyManager.ts
similarity index 89%
rename from src/node/db/ReadOnlyManager.js
rename to src/node/db/ReadOnlyManager.ts
index b96dd3413..23639d665 100644
--- a/src/node/db/ReadOnlyManager.js
+++ b/src/node/db/ReadOnlyManager.ts
@@ -29,14 +29,14 @@ const randomString = require('../utils/randomstring');
* @param {String} id the pad's id
* @return {Boolean} true if the id is readonly
*/
-exports.isReadOnlyId = (id) => id.startsWith('r.');
+exports.isReadOnlyId = (id:string) => id.startsWith('r.');
/**
* returns a read only id for a pad
* @param {String} padId the id of the pad
* @return {String} the read only id
*/
-exports.getReadOnlyId = async (padId) => {
+exports.getReadOnlyId = async (padId:string) => {
// check if there is a pad2readonly entry
let readOnlyId = await db.get(`pad2readonly:${padId}`);
@@ -57,14 +57,14 @@ exports.getReadOnlyId = async (padId) => {
* @param {String} readOnlyId read only id
* @return {String} the padId
*/
-exports.getPadId = async (readOnlyId) => await db.get(`readonly2pad:${readOnlyId}`);
+exports.getPadId = async (readOnlyId:string) => await db.get(`readonly2pad:${readOnlyId}`);
/**
* returns the padId and readonlyPadId in an object for any id
* @param {String} id read only id or real pad id
* @return {Object} an object with the padId and readonlyPadId
*/
-exports.getIds = async (id) => {
+exports.getIds = async (id:string) => {
const readonly = exports.isReadOnlyId(id);
// Might be null, if this is an unknown read-only id
diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.ts
similarity index 96%
rename from src/node/db/SecurityManager.js
rename to src/node/db/SecurityManager.ts
index 0e5b2c77c..326bf3659 100644
--- a/src/node/db/SecurityManager.js
+++ b/src/node/db/SecurityManager.ts
@@ -19,6 +19,8 @@
* limitations under the License.
*/
+import {UserSettingsObject} from "../types/UserSettingsObject";
+
const authorManager = require('./AuthorManager');
const hooks = require('../../static/js/pluginfw/hooks.js');
const padManager = require('./PadManager');
@@ -55,7 +57,7 @@ const DENY = Object.freeze({accessStatus: 'deny'});
* @param {Object} userSettings
* @return {DENY|{accessStatus: String, authorID: String}}
*/
-exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
+exports.checkAccess = async (padID:string, sessionCookie:string, token:string, userSettings:UserSettingsObject) => {
if (!padID) {
authLogger.debug('access denied: missing padID');
return DENY;
@@ -95,7 +97,7 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
}
// allow plugins to deny access
- const isFalse = (x) => x === false;
+ const isFalse = (x:boolean) => x === false;
if (hooks.callAll('onAccessCheck', {padID, token, sessionCookie}).some(isFalse)) {
authLogger.debug('access denied: an onAccessCheck hook function returned false');
return DENY;
diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.ts
similarity index 91%
rename from src/node/db/SessionManager.js
rename to src/node/db/SessionManager.ts
index 3d39ed747..c0e43a659 100644
--- a/src/node/db/SessionManager.js
+++ b/src/node/db/SessionManager.ts
@@ -36,7 +36,7 @@ const authorManager = require('./AuthorManager');
* sessionCookie, and is bound to a group with the given ID, then this returns the author ID
* bound to the session. Otherwise, returns undefined.
*/
-exports.findAuthorID = async (groupID, sessionCookie) => {
+exports.findAuthorID = async (groupID:string, sessionCookie: string) => {
if (!sessionCookie) return undefined;
/*
* Sometimes, RFC 6265-compliant web servers may send back a cookie whose
@@ -65,7 +65,7 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
const sessionInfoPromises = sessionIDs.map(async (id) => {
try {
return await exports.getSessionInfo(id);
- } catch (err) {
+ } catch (err:any) {
if (err.message === 'sessionID does not exist') {
console.debug(`SessionManager getAuthorID: no session exists with ID ${id}`);
} else {
@@ -75,7 +75,10 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
return undefined;
});
const now = Math.floor(Date.now() / 1000);
- const isMatch = (si) => (si != null && si.groupID === groupID && now < si.validUntil);
+ const isMatch = (si: {
+ groupID: string;
+ validUntil: number;
+ }|null) => (si != null && si.groupID === groupID && now < si.validUntil);
const sessionInfo = await promises.firstSatisfies(sessionInfoPromises, isMatch);
if (sessionInfo == null) return undefined;
return sessionInfo.authorID;
@@ -86,7 +89,7 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
* @param {String} sessionID The id of the session
* @return {Promise} Resolves to true if the session exists
*/
-exports.doesSessionExist = async (sessionID) => {
+exports.doesSessionExist = async (sessionID: string) => {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
return (session != null);
@@ -99,7 +102,7 @@ exports.doesSessionExist = async (sessionID) => {
* @param {Number} validUntil The unix timestamp when the session should expire
* @return {Promise<{sessionID: string}>} the id of the new session
*/
-exports.createSession = async (groupID, authorID, validUntil) => {
+exports.createSession = async (groupID: string, authorID: string, validUntil: number) => {
// check if the group exists
const groupExists = await groupManager.doesGroupExist(groupID);
if (!groupExists) {
@@ -160,7 +163,7 @@ exports.createSession = async (groupID, authorID, validUntil) => {
* @param {String} sessionID The id of the session
* @return {Promise