mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
Merge branch 'develop'
This commit is contained in:
commit
113df1f186
310 changed files with 11226 additions and 21462 deletions
43
.github/workflows/backend-tests.yml
vendored
43
.github/workflows/backend-tests.yml
vendored
|
@ -17,6 +17,10 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Install libreoffice
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:libreoffice/ppa
|
||||
|
@ -24,11 +28,11 @@ jobs:
|
|||
sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport
|
||||
|
||||
- name: Install all dependencies and symlink for ep_etherpad-lite
|
||||
run: bin/installDeps.sh
|
||||
run: src/bin/installDeps.sh
|
||||
|
||||
# configures some settings and runs npm run test
|
||||
- name: Run the backend tests
|
||||
run: tests/frontend/travis/runnerBackend.sh
|
||||
run: src/tests/frontend/travis/runnerBackend.sh
|
||||
|
||||
withplugins:
|
||||
# run on pushes to any branch
|
||||
|
@ -43,18 +47,43 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- 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 all dependencies and symlink for ep_etherpad-lite
|
||||
run: bin/installDeps.sh
|
||||
- name: Install Etherpad plugins
|
||||
run: >
|
||||
npm install
|
||||
ep_align
|
||||
ep_author_hover
|
||||
ep_cursortrace
|
||||
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
|
||||
|
||||
- name: Install etherpad plugins
|
||||
run: npm install ep_align ep_author_hover ep_cursortrace ep_font_size ep_hash_auth ep_headings2 ep_markdown ep_readonly_guest ep_spellcheck ep_subscript_and_superscript ep_table_of_contents
|
||||
# This must be run after installing the plugins, 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
|
||||
|
||||
# configures some settings and runs npm run test
|
||||
- name: Run the backend tests
|
||||
run: tests/frontend/travis/runnerBackend.sh
|
||||
run: src/tests/frontend/travis/runnerBackend.sh
|
||||
|
|
2
.github/workflows/dockerfile.yml
vendored
2
.github/workflows/dockerfile.yml
vendored
|
@ -21,6 +21,6 @@ jobs:
|
|||
run: |
|
||||
docker build -t etherpad:test .
|
||||
docker run -d -p 9001:9001 etherpad:test
|
||||
./bin/installDeps.sh
|
||||
./src/bin/installDeps.sh
|
||||
sleep 3 # delay for startup?
|
||||
cd src && npm run test-container
|
||||
|
|
58
.github/workflows/frontend-admin-tests.yml
vendored
Normal file
58
.github/workflows/frontend-admin-tests.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
name: "Frontend admin tests"
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
withplugins:
|
||||
name: with plugins
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Run sauce-connect-action
|
||||
shell: bash
|
||||
env:
|
||||
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}
|
||||
run: src/tests/frontend/travis/sauce_tunnel.sh
|
||||
|
||||
- name: Install all dependencies and symlink for ep_etherpad-lite
|
||||
run: src/bin/installDeps.sh
|
||||
|
||||
# We intentionally install a much old ep_align version to test update minor versions
|
||||
- name: Install etherpad plugins
|
||||
run: npm install ep_align@0.2.27
|
||||
|
||||
# Nuke plugin tests
|
||||
- name: Install etherpad plugins
|
||||
run: rm -Rf node_modules/ep_align/static/tests/*
|
||||
|
||||
- name: export GIT_HASH to env
|
||||
id: environment
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})"
|
||||
|
||||
- name: Write custom settings.json with loglevel WARN
|
||||
run: "sed 's/\"loglevel\": \"INFO\",/\"loglevel\": \"WARN\",/' < 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"
|
||||
|
||||
- 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
|
||||
|
||||
- name: Run the frontend admin tests
|
||||
shell: bash
|
||||
env:
|
||||
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}
|
||||
GIT_HASH: ${{ steps.environment.outputs.sha_short }}
|
||||
run: |
|
||||
src/tests/frontend/travis/adminrunner.sh
|
52
.github/workflows/frontend-tests.yml
vendored
52
.github/workflows/frontend-tests.yml
vendored
|
@ -11,16 +11,20 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Run sauce-connect-action
|
||||
shell: bash
|
||||
env:
|
||||
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}
|
||||
run: tests/frontend/travis/sauce_tunnel.sh
|
||||
run: src/tests/frontend/travis/sauce_tunnel.sh
|
||||
|
||||
- name: Install all dependencies and symlink for ep_etherpad-lite
|
||||
run: bin/installDeps.sh
|
||||
run: src/bin/installDeps.sh
|
||||
|
||||
- name: export GIT_HASH to env
|
||||
id: environment
|
||||
|
@ -37,7 +41,7 @@ jobs:
|
|||
TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}
|
||||
GIT_HASH: ${{ steps.environment.outputs.sha_short }}
|
||||
run: |
|
||||
tests/frontend/travis/runner.sh
|
||||
src/tests/frontend/travis/runner.sh
|
||||
|
||||
withplugins:
|
||||
name: with plugins
|
||||
|
@ -47,19 +51,44 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Run sauce-connect-action
|
||||
shell: bash
|
||||
env:
|
||||
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}
|
||||
run: tests/frontend/travis/sauce_tunnel.sh
|
||||
run: src/tests/frontend/travis/sauce_tunnel.sh
|
||||
|
||||
- name: Install Etherpad plugins
|
||||
run: >
|
||||
npm install
|
||||
ep_align
|
||||
ep_author_hover
|
||||
ep_cursortrace
|
||||
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
|
||||
|
||||
# This must be run after installing the plugins, 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: bin/installDeps.sh
|
||||
|
||||
- name: Install etherpad plugins
|
||||
run: npm install ep_align ep_author_hover ep_cursortrace ep_font_size ep_hash_auth ep_headings2 ep_markdown ep_readonly_guest ep_spellcheck ep_subscript_and_superscript ep_table_of_contents ep_set_title_on_pad
|
||||
run: src/bin/installDeps.sh
|
||||
|
||||
- name: export GIT_HASH to env
|
||||
id: environment
|
||||
|
@ -68,9 +97,12 @@ jobs:
|
|||
- name: Write custom settings.json with loglevel WARN
|
||||
run: "sed 's/\"loglevel\": \"INFO\",/\"loglevel\": \"WARN\",/' < 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"
|
||||
|
||||
# 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 tests/frontend/specs/*
|
||||
run: rm src/tests/frontend/specs/*
|
||||
|
||||
- name: Run the frontend tests
|
||||
shell: bash
|
||||
|
@ -80,4 +112,4 @@ jobs:
|
|||
TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}
|
||||
GIT_HASH: ${{ steps.environment.outputs.sha_short }}
|
||||
run: |
|
||||
tests/frontend/travis/runner.sh
|
||||
src/tests/frontend/travis/runner.sh
|
||||
|
|
4
.github/workflows/lint-package-lock.yml
vendored
4
.github/workflows/lint-package-lock.yml
vendored
|
@ -17,6 +17,10 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Install lockfile-lint
|
||||
run: npm install lockfile-lint
|
||||
|
||||
|
|
40
.github/workflows/load-test.yml
vendored
40
.github/workflows/load-test.yml
vendored
|
@ -17,14 +17,18 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Install all dependencies and symlink for ep_etherpad-lite
|
||||
run: bin/installDeps.sh
|
||||
run: src/bin/installDeps.sh
|
||||
|
||||
- name: Install etherpad-load-test
|
||||
run: sudo npm install -g etherpad-load-test
|
||||
|
||||
- name: Run load test
|
||||
run: tests/frontend/travis/runnerLoadTest.sh
|
||||
run: src/tests/frontend/travis/runnerLoadTest.sh
|
||||
|
||||
withplugins:
|
||||
# run on pushes to any branch
|
||||
|
@ -39,15 +43,39 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install all dependencies and symlink for ep_etherpad-lite
|
||||
run: bin/installDeps.sh
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Install etherpad-load-test
|
||||
run: sudo npm install -g etherpad-load-test
|
||||
|
||||
- name: Install etherpad plugins
|
||||
run: npm install ep_align ep_author_hover ep_cursortrace ep_font_size ep_hash_auth ep_headings2 ep_markdown ep_readonly_guest ep_spellcheck ep_subscript_and_superscript ep_table_of_contents
|
||||
run: >
|
||||
npm install
|
||||
ep_align
|
||||
ep_author_hover
|
||||
ep_cursortrace
|
||||
ep_font_size
|
||||
ep_hash_auth
|
||||
ep_headings2
|
||||
ep_markdown
|
||||
ep_readonly_guest
|
||||
ep_set_title_on_pad
|
||||
ep_spellcheck
|
||||
ep_subscript_and_superscript
|
||||
ep_table_of_contents
|
||||
|
||||
# This must be run after installing the plugins, 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
|
||||
|
||||
# configures some settings and runs npm run test
|
||||
- name: Run load test
|
||||
run: tests/frontend/travis/runnerLoadTest.sh
|
||||
run: src/tests/frontend/travis/runnerLoadTest.sh
|
||||
|
|
12
.github/workflows/rate-limit.yml
vendored
12
.github/workflows/rate-limit.yml
vendored
|
@ -16,14 +16,18 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- 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 tests/ratelimit/Dockerfile.nginx -t nginx-latest .
|
||||
docker build -f tests/ratelimit/Dockerfile.anotherip -t anotherip .
|
||||
docker build -f src/tests/ratelimit/Dockerfile.nginx -t nginx-latest .
|
||||
docker build -f src/tests/ratelimit/Dockerfile.anotherip -t anotherip .
|
||||
- name: run docker images
|
||||
run: |
|
||||
docker run --name etherpad-docker -p 9000:9001 --rm --network ep_net --ip 172.23.42.2 -e 'TRUST_PROXY=true' epl-debian-slim &
|
||||
|
@ -31,9 +35,9 @@ 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: bin/installDeps.sh
|
||||
run: src/bin/installDeps.sh
|
||||
|
||||
- name: run rate limit test
|
||||
run: |
|
||||
cd tests/ratelimit
|
||||
cd src/tests/ratelimit
|
||||
./testlimits.sh
|
||||
|
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -3,11 +3,8 @@ node_modules
|
|||
!settings.json.template
|
||||
APIKEY.txt
|
||||
SESSIONKEY.txt
|
||||
bin/abiword.exe
|
||||
bin/node.exe
|
||||
etherpad-lite-win.zip
|
||||
var/dirty.db
|
||||
bin/convertSettings.json
|
||||
*~
|
||||
*.patch
|
||||
npm-debug.log
|
||||
|
@ -15,9 +12,12 @@ npm-debug.log
|
|||
.ep_initialized
|
||||
*.crt
|
||||
*.key
|
||||
bin/etherpad-1.deb
|
||||
credentials.json
|
||||
out/
|
||||
.nyc_output
|
||||
./package-lock.json
|
||||
.idea
|
||||
/package-lock.json
|
||||
/src/bin/abiword.exe
|
||||
/src/bin/convertSettings.json
|
||||
/src/bin/etherpad-1.deb
|
||||
/src/bin/node.exe
|
||||
|
|
45
.travis.yml
45
.travis.yml
|
@ -18,6 +18,11 @@ _set_loglevel_warn: &set_loglevel_warn |
|
|||
settings.json.template >settings.json.template.new &&
|
||||
mv settings.json.template.new settings.json.template
|
||||
|
||||
_enable_admin_tests: &enable_admin_tests |
|
||||
sed -e 's/"enableAdminUITests": false/"enableAdminUITests": true,\n"users":{"admin":{"password":"changeme","is_admin":true}}/' \
|
||||
settings.json.template >settings.json.template.new &&
|
||||
mv settings.json.template.new settings.json.template
|
||||
|
||||
_install_libreoffice: &install_libreoffice >-
|
||||
sudo add-apt-repository -y ppa:libreoffice/ppa &&
|
||||
sudo apt-get update &&
|
||||
|
@ -46,19 +51,20 @@ jobs:
|
|||
name: "Test the Frontend without Plugins"
|
||||
install:
|
||||
- *set_loglevel_warn
|
||||
- "tests/frontend/travis/sauce_tunnel.sh"
|
||||
- "bin/installDeps.sh"
|
||||
- *enable_admin_tests
|
||||
- "src/tests/frontend/travis/sauce_tunnel.sh"
|
||||
- "src/bin/installDeps.sh"
|
||||
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
|
||||
script:
|
||||
- "./tests/frontend/travis/runner.sh"
|
||||
- "./src/tests/frontend/travis/runner.sh"
|
||||
- name: "Run the Backend tests without Plugins"
|
||||
install:
|
||||
- *install_libreoffice
|
||||
- *set_loglevel_warn
|
||||
- "bin/installDeps.sh"
|
||||
- "src/bin/installDeps.sh"
|
||||
- "cd src && npm install && cd -"
|
||||
script:
|
||||
- "tests/frontend/travis/runnerBackend.sh"
|
||||
- "src/tests/frontend/travis/runnerBackend.sh"
|
||||
- name: "Test the Dockerfile"
|
||||
install:
|
||||
- "cd src && npm install && cd -"
|
||||
|
@ -69,24 +75,25 @@ jobs:
|
|||
- name: "Load test Etherpad without Plugins"
|
||||
install:
|
||||
- *set_loglevel_warn
|
||||
- "bin/installDeps.sh"
|
||||
- "src/bin/installDeps.sh"
|
||||
- "cd src && npm install && cd -"
|
||||
- "npm install -g etherpad-load-test"
|
||||
script:
|
||||
- "tests/frontend/travis/runnerLoadTest.sh"
|
||||
- "src/tests/frontend/travis/runnerLoadTest.sh"
|
||||
# we can only frontend tests from the ether/ organization and not from forks.
|
||||
# To request tests to be run ask a maintainer to fork your repo to ether/
|
||||
- if: fork = false
|
||||
name: "Test the Frontend Plugins only"
|
||||
install:
|
||||
- *set_loglevel_warn
|
||||
- "tests/frontend/travis/sauce_tunnel.sh"
|
||||
- "bin/installDeps.sh"
|
||||
- "rm tests/frontend/specs/*"
|
||||
- *enable_admin_tests
|
||||
- "src/tests/frontend/travis/sauce_tunnel.sh"
|
||||
- "src/bin/installDeps.sh"
|
||||
- "rm src/tests/frontend/specs/*"
|
||||
- *install_plugins
|
||||
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
|
||||
script:
|
||||
- "./tests/frontend/travis/runner.sh"
|
||||
- "./src/tests/frontend/travis/runner.sh"
|
||||
- name: "Lint test package-lock.json"
|
||||
install:
|
||||
- "npm install lockfile-lint"
|
||||
|
@ -96,11 +103,11 @@ jobs:
|
|||
install:
|
||||
- *install_libreoffice
|
||||
- *set_loglevel_warn
|
||||
- "bin/installDeps.sh"
|
||||
- "src/bin/installDeps.sh"
|
||||
- *install_plugins
|
||||
- "cd src && npm install && cd -"
|
||||
script:
|
||||
- "tests/frontend/travis/runnerBackend.sh"
|
||||
- "src/tests/frontend/travis/runnerBackend.sh"
|
||||
- name: "Test the Dockerfile"
|
||||
install:
|
||||
- "cd src && npm install && cd -"
|
||||
|
@ -111,24 +118,24 @@ jobs:
|
|||
- name: "Load test Etherpad with Plugins"
|
||||
install:
|
||||
- *set_loglevel_warn
|
||||
- "bin/installDeps.sh"
|
||||
- "src/bin/installDeps.sh"
|
||||
- *install_plugins
|
||||
- "cd src && npm install && cd -"
|
||||
- "npm install -g etherpad-load-test"
|
||||
script:
|
||||
- "tests/frontend/travis/runnerLoadTest.sh"
|
||||
- "src/tests/frontend/travis/runnerLoadTest.sh"
|
||||
- name: "Test rate limit"
|
||||
install:
|
||||
- "docker network create --subnet=172.23.42.0/16 ep_net"
|
||||
- "docker build -f Dockerfile -t epl-debian-slim ."
|
||||
- "docker build -f tests/ratelimit/Dockerfile.nginx -t nginx-latest ."
|
||||
- "docker build -f tests/ratelimit/Dockerfile.anotherip -t anotherip ."
|
||||
- "docker build -f src/tests/ratelimit/Dockerfile.nginx -t nginx-latest ."
|
||||
- "docker build -f src/tests/ratelimit/Dockerfile.anotherip -t anotherip ."
|
||||
- "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"
|
||||
- "./bin/installDeps.sh"
|
||||
- "./src/bin/installDeps.sh"
|
||||
script:
|
||||
- "cd tests/ratelimit && bash testlimits.sh"
|
||||
- "cd src/tests/ratelimit && bash testlimits.sh"
|
||||
|
||||
notifications:
|
||||
irc:
|
||||
|
|
94
CHANGELOG.md
94
CHANGELOG.md
|
@ -1,3 +1,79 @@
|
|||
# 1.8.8
|
||||
|
||||
### Security patches
|
||||
|
||||
* EJS has been updated to 3.1.6 to mitigate an Arbitrary Code Injection
|
||||
|
||||
### Compatibility changes
|
||||
|
||||
* Node.js 10.17.0 or newer is now required.
|
||||
* The `bin/` and `tests/` directories were moved under `src/`. Symlinks were
|
||||
added at the old locations to hopefully avoid breaking user scripts and other
|
||||
tools.
|
||||
* Dependencies are now installed with the `--no-optional` flag to speed
|
||||
installation. Optional dependencies such as `sqlite3` must now be manually
|
||||
installed (e.g., `(cd src && npm i sqlite3)`).
|
||||
* Socket.IO messages are now limited to 10K bytes to make denial of service
|
||||
attacks more difficult. This may cause issues when pasting large amounts of
|
||||
text or with plugins that send large messages (e.g., `ep_image_upload`). You
|
||||
can change the limit via `settings.json`; see `socketIo.maxHttpBufferSize`.
|
||||
* The top-level `package.json` file, added in v1.8.7, has been removed due to
|
||||
problematic npm behavior. Whenever you install a plugin you will see the
|
||||
following benign warnings that can be safely ignored:
|
||||
|
||||
```
|
||||
npm WARN saveError ENOENT: no such file or directory, open '.../package.json'
|
||||
npm WARN enoent ENOENT: no such file or directory, open '.../package.json'
|
||||
npm WARN develop No description
|
||||
npm WARN develop No repository field.
|
||||
npm WARN develop No README data
|
||||
npm WARN develop No license field.
|
||||
```
|
||||
|
||||
### Notable enhancements
|
||||
|
||||
* You can now generate a link to a specific line number in a pad. Appending
|
||||
`#L10` to a pad URL will cause your browser to scroll down to line 10.
|
||||
* Database performance is significantly improved.
|
||||
* Admin UI now has test coverage in CI. (The tests are not enabled by default;
|
||||
see `settings.json`.)
|
||||
* New stats/metrics: `activePads`, `httpStartTime`, `lastDisconnected`,
|
||||
`memoryUsageHeap`.
|
||||
* Improved import UX.
|
||||
* Browser caching improvements.
|
||||
* Users can now pick absolute white (`#fff`) as their color.
|
||||
* The `settings.json` template used for Docker images has new variables for
|
||||
controlling rate limiting.
|
||||
* Admin UI now has test coverage in CI. (The tests are not enabled by default
|
||||
because the admin password is required; see `settings.json`.)
|
||||
* For plugin authors:
|
||||
* New `callAllSerial()` function that invokes hook functions like `callAll()`
|
||||
except it supports asynchronous hook functions.
|
||||
* `callFirst()` and `aCallFirst()` now support the same wide range of hook
|
||||
function behaviors that `callAll()`, `aCallAll()`, and `callAllSerial()`
|
||||
support. Also, they now warn when a hook function misbehaves.
|
||||
* The following server-side hooks now support asynchronous hook functions:
|
||||
`expressConfigure`, `expressCreateServer`, `padCopy`, `padRemove`
|
||||
* Backend tests for plugins can now use the
|
||||
[`ep_etherpad-lite/tests/backend/common`](src/tests/backend/common.js)
|
||||
module to start the server and simplify API access.
|
||||
* The `checkPlugins.js` script now automatically adds GitHub CI test coverage
|
||||
badges for backend tests and npm publish.
|
||||
|
||||
### Notable fixes
|
||||
|
||||
* Enter key now stays in focus when inserted at bottom of viewport.
|
||||
* Numbering for ordered list items now properly increments when exported to
|
||||
text.
|
||||
* Suppressed benign socket.io connection errors
|
||||
* Interface no longer loses color variants on disconnect/reconnect event.
|
||||
* General code quality is further significantly improved.
|
||||
* Restarting Etherpad via `/admin` actions is more robust.
|
||||
* Improved reliability of server shutdown and restart.
|
||||
* No longer error if no buttons are visible.
|
||||
* For plugin authors:
|
||||
* Fixed `collectContentLineText` return value handling.
|
||||
|
||||
# 1.8.7
|
||||
### Compatibility-breaking changes
|
||||
* **IMPORTANT:** It is no longer possible to protect a group pad with a
|
||||
|
@ -40,7 +116,7 @@
|
|||
content in `.etherpad` exports
|
||||
* New `expressCloseServer` hook to close Express when required
|
||||
* The `padUpdate` hook context now includes `revs` and `changeset`
|
||||
* `checkPlugins.js` has various improvements to help plugin developers
|
||||
* `checkPlugin.js` has various improvements to help plugin developers
|
||||
* The HTTP request object (and therefore the express-session state) is now
|
||||
accessible from within most `eejsBlock_*` hooks
|
||||
* Users without a `password` or `hash` property in `settings.json` are no longer
|
||||
|
@ -114,7 +190,7 @@
|
|||
* MINOR: Fix ?showChat URL param issue
|
||||
* MINOR: Issue where timeslider URI fails to be correct if padID is numeric
|
||||
* MINOR: Include prompt for clear authorship when entire document is selected
|
||||
* MINOR: Include full document aText every 100 revisions to make pad restoration on database curruption achievable
|
||||
* MINOR: Include full document aText every 100 revisions to make pad restoration on database corruption achievable
|
||||
* MINOR: Several Colibris CSS fixes
|
||||
* MINOR: Use mime library for mime types instead of hard-coded.
|
||||
* MINOR: Don't show "new pad button" if instance is read only
|
||||
|
@ -364,7 +440,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
# 1.5.3
|
||||
* NEW: Accessibility support for Screen readers, includes new fonts and keyboard shortcuts
|
||||
* NEW: API endpoint for Append Chat Message and Chat Backend Tests
|
||||
* NEW: Error messages displayed on load are included in Default Pad Text (can be supressed)
|
||||
* NEW: Error messages displayed on load are included in Default Pad Text (can be suppressed)
|
||||
* NEW: Content Collector can handle key values
|
||||
* NEW: getAttributesOnPosition Method
|
||||
* FIX: Firefox keeps attributes (bold etc) on cut/copy -> paste
|
||||
|
@ -433,7 +509,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* Fix: Timeslider UI Fix
|
||||
* Fix: Remove Dokuwiki
|
||||
* Fix: Remove long paths from windows build (stops error during extract)
|
||||
* Fix: Various globals remvoed
|
||||
* Fix: Various globals removed
|
||||
* Fix: Move all scripts into bin/
|
||||
* Fix: Various CSS bugfixes for Mobile devices
|
||||
* Fix: Overflow Toolbar
|
||||
|
@ -509,7 +585,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* FIX: HTML import (don't crash on malformed or blank HTML input; strip title out of html during import)
|
||||
* FIX: check if uploaded file only contains ascii chars when abiword disabled
|
||||
* FIX: Plugin search in /admin/plugins
|
||||
* FIX: Don't create new pad if a non-existant read-only pad is accessed
|
||||
* FIX: Don't create new pad if a non-existent read-only pad is accessed
|
||||
* FIX: Drop messages from unknown connections (would lead to a crash after a restart)
|
||||
* FIX: API: fix createGroupFor endpoint, if mapped group is deleted
|
||||
* FIX: Import form for other locales
|
||||
|
@ -526,7 +602,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* NEW: Bump log4js for improved logging
|
||||
* Fix: Remove URL schemes which don't have RFC standard
|
||||
* Fix: Fix safeRun subsequent restarts issue
|
||||
* Fix: Allow safeRun to pass arguements to run.sh
|
||||
* Fix: Allow safeRun to pass arguments to run.sh
|
||||
* Fix: Include script for more efficient import
|
||||
* Fix: Fix sysv comptibile script
|
||||
* Fix: Fix client side changeset spamming
|
||||
|
@ -565,7 +641,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* Fix: Support Node 0.10
|
||||
* Fix: Log HTTP on DEBUG log level
|
||||
* Fix: Server wont crash on import fails on 0 file import.
|
||||
* Fix: Import no longer fails consistantly
|
||||
* Fix: Import no longer fails consistently
|
||||
* Fix: Language support for non existing languages
|
||||
* Fix: Mobile support for chat notifications are now usable
|
||||
* Fix: Re-Enable Editbar buttons on reconnect
|
||||
|
@ -597,7 +673,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* NEW: Admin dashboard mobile device support and new hooks for Admin dashboard
|
||||
* NEW: Get current API version from API
|
||||
* NEW: CLI script to delete pads
|
||||
* Fix: Automatic client reconnection on disonnect
|
||||
* Fix: Automatic client reconnection on disconnect
|
||||
* Fix: Text Export indentation now supports multiple indentations
|
||||
* Fix: Bugfix getChatHistory API method
|
||||
* Fix: Stop Chrome losing caret after paste is texted
|
||||
|
@ -617,7 +693,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* Fix: Stop Opera browser inserting two new lines on enter keypress
|
||||
* Fix: Stop timeslider from showing NaN on pads with only one revision
|
||||
* Other: Allow timeslider tests to run and provide & fix various other frontend-tests
|
||||
* Other: Begin dropping referene to Lite. Etherpad Lite is now named "Etherpad"
|
||||
* Other: Begin dropping reference to Lite. Etherpad Lite is now named "Etherpad"
|
||||
* Other: Update to latest jQuery
|
||||
* Other: Change loading message asking user to please wait on first build
|
||||
* Other: Allow etherpad to use global npm installation (Safe since node 6.3)
|
||||
|
|
|
@ -35,7 +35,7 @@ WORKDIR /opt/etherpad-lite
|
|||
COPY --chown=etherpad:0 ./ ./
|
||||
|
||||
# install node dependencies for Etherpad
|
||||
RUN bin/installDeps.sh && \
|
||||
RUN src/bin/installDeps.sh && \
|
||||
rm -rf ~/.npm/_cacache
|
||||
|
||||
# Install the plugins, if ETHERPAD_PLUGINS is not empty.
|
||||
|
|
6
Makefile
6
Makefile
|
@ -9,8 +9,8 @@ UNAME := $(shell uname -s)
|
|||
ensure_marked_is_installed:
|
||||
set -eu; \
|
||||
hash npm; \
|
||||
if [ $(shell npm list --prefix bin/doc >/dev/null 2>/dev/null; echo $$?) -ne "0" ]; then \
|
||||
npm ci --prefix=bin/doc; \
|
||||
if [ $(shell npm list --prefix src/bin/doc >/dev/null 2>/dev/null; echo $$?) -ne "0" ]; then \
|
||||
npm ci --prefix=src/bin/doc; \
|
||||
fi
|
||||
|
||||
docs: ensure_marked_is_installed $(outdoc_files) $(docassets)
|
||||
|
@ -21,7 +21,7 @@ out/doc/assets/%: doc/assets/%
|
|||
|
||||
out/doc/%.html: doc/%.md
|
||||
mkdir -p $(@D)
|
||||
node bin/doc/generate.js --format=html --template=doc/template.html $< > $@
|
||||
node src/bin/doc/generate.js --format=html --template=doc/template.html $< > $@
|
||||
ifeq ($(UNAME),Darwin)
|
||||
sed -i '' 's/__VERSION__/${VERSION}/' $@
|
||||
else
|
||||
|
|
33
README.md
33
README.md
|
@ -13,7 +13,7 @@ Etherpad is a real-time collaborative editor [scalable to thousands of simultane
|
|||
# Installation
|
||||
|
||||
## Requirements
|
||||
- `nodejs` >= **10.13.0**.
|
||||
- `nodejs` >= **10.17.0**.
|
||||
|
||||
## GNU/Linux and other UNIX-like systems
|
||||
|
||||
|
@ -21,19 +21,22 @@ Etherpad is a real-time collaborative editor [scalable to thousands of simultane
|
|||
```
|
||||
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
git clone --branch master https://github.com/ether/etherpad-lite.git && cd etherpad-lite && bin/run.sh
|
||||
git clone --branch master https://github.com/ether/etherpad-lite.git &&
|
||||
cd etherpad-lite &&
|
||||
src/bin/run.sh
|
||||
```
|
||||
|
||||
### Manual install
|
||||
You'll need git and [node.js](https://nodejs.org) installed (minimum required Node version: **10.13.0**).
|
||||
You'll need git and [node.js](https://nodejs.org) installed (minimum required Node version: **10.17.0**).
|
||||
|
||||
**As any user (we recommend creating a separate user called etherpad):**
|
||||
|
||||
1. Move to a folder where you want to install Etherpad. Clone the git repository: `git clone --branch master git://github.com/ether/etherpad-lite.git`
|
||||
2. Change into the new directory containing the cloned source code: `cd etherpad-lite`
|
||||
3. run `bin/run.sh` and open <http://127.0.0.1:9001> in your browser.
|
||||
3. run `src/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 `bin/run.sh` will update the dependencies.
|
||||
To update to the latest released version, execute `git pull origin`. The next
|
||||
start with `src/bin/run.sh` will update the dependencies.
|
||||
|
||||
[Next steps](#next-steps).
|
||||
|
||||
|
@ -53,11 +56,13 @@ You'll need [node.js](https://nodejs.org) and (optionally, though recommended) g
|
|||
1. Grab the source, either
|
||||
- download <https://github.com/ether/etherpad-lite/zipball/master>
|
||||
- or `git clone --branch master https://github.com/ether/etherpad-lite.git`
|
||||
2. With a "Run as administrator" command prompt execute `bin\installOnWindows.bat`
|
||||
2. With a "Run as administrator" command prompt execute
|
||||
`src\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 `bin\installOnWindows.bat`, again.
|
||||
Update to the latest version with `git pull origin`, then run
|
||||
`src\bin\installOnWindows.bat`, again.
|
||||
|
||||
If cloning to a subdirectory within another project, you may need to do the following:
|
||||
|
||||
|
@ -73,7 +78,9 @@ Find [here](doc/docker.md) information on running Etherpad in a container.
|
|||
|
||||
## 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 `bin/run.sh` using the `-s|--settings` option: this allows you to run multiple Etherpad instances from the same installation.
|
||||
If you need to handle multiple settings files, you can pass the path to a
|
||||
settings file to `src/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 and `--sessionkey` to give a non-default SESSIONKEY.txt.
|
||||
**Each configuration parameter can also be set via an environment variable**, using the syntax `"${ENV_VAR}"` or `"${ENV_VAR:default_value}"`. For details, refer to `settings.json.template`.
|
||||
Once you have access to your `/admin` section settings can be modified through the web browser.
|
||||
|
@ -98,7 +105,7 @@ Etherpad is very customizable through plugins. Instructions for installing theme
|
|||
Run the following command in your Etherpad folder to get all of the features visible in the demo gif:
|
||||
|
||||
```
|
||||
npm install ep_headings2 ep_markdown ep_comments_page ep_align ep_page_view ep_font_color ep_webrtc ep_embedded_hyperlinks2
|
||||
npm install ep_headings2 ep_markdown ep_comments_page ep_align ep_font_color ep_webrtc ep_embedded_hyperlinks2
|
||||
```
|
||||
|
||||
## Customize the style with skin variants
|
||||
|
@ -115,9 +122,13 @@ Documentation can be found in `doc/`.
|
|||
# Development
|
||||
|
||||
## Things you should know
|
||||
You can debug Etherpad using `bin/debugRun.sh`.
|
||||
|
||||
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 `bin/installDeps.sh` after installing a new dependency or upgrading version.
|
||||
You can debug Etherpad using `src/bin/debugRun.sh`.
|
||||
|
||||
You can run Etherpad quickly launching `src/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
|
||||
dependency or upgrading version.
|
||||
|
||||
If you want to find out how Etherpad's `Easysync` works (the library that makes it really realtime), start with this [PDF](https://github.com/ether/etherpad-lite/raw/master/doc/easysync/easysync-full-description.pdf) (complex, but worth reading).
|
||||
|
||||
|
|
1
bin
Symbolic link
1
bin
Symbolic link
|
@ -0,0 +1 @@
|
|||
src/bin
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* This is a debug tool. It checks all revisions for data corruption
|
||||
*/
|
||||
|
||||
if (process.argv.length != 2) {
|
||||
console.error('Use: node bin/checkAllPads.js');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// load and initialize NPM
|
||||
const npm = require('../src/node_modules/npm');
|
||||
npm.load({}, async () => {
|
||||
try {
|
||||
// initialize the database
|
||||
const settings = require('../src/node/utils/Settings');
|
||||
const db = require('../src/node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load modules
|
||||
const Changeset = require('../src/static/js/Changeset');
|
||||
const padManager = require('../src/node/db/PadManager');
|
||||
|
||||
// get all pads
|
||||
const res = await padManager.listAllPads();
|
||||
|
||||
for (const padId of res.padIDs) {
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// check if the pad has a pool
|
||||
if (pad.pool === undefined) {
|
||||
console.error(`[${pad.id}] Missing attribute pool`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// create an array with key kevisions
|
||||
// key revisions always save the full pad atext
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
const keyRevisions = [];
|
||||
for (let rev = 0; rev < head; rev += 100) {
|
||||
keyRevisions.push(rev);
|
||||
}
|
||||
|
||||
// run through all key revisions
|
||||
for (const keyRev of keyRevisions) {
|
||||
// create an array of revisions we need till the next keyRevision or the End
|
||||
const revisionsNeeded = [];
|
||||
for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
revisionsNeeded.push(rev);
|
||||
}
|
||||
|
||||
// this array will hold all revision changesets
|
||||
const revisions = [];
|
||||
|
||||
// run through all needed revisions and get them from the database
|
||||
for (const revNum of revisionsNeeded) {
|
||||
const revision = await db.get(`pad:${pad.id}:revs:${revNum}`);
|
||||
revisions[revNum] = revision;
|
||||
}
|
||||
|
||||
// check if the revision exists
|
||||
if (revisions[keyRev] == null) {
|
||||
console.error(`[${pad.id}] Missing revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if there is a atext in the keyRevisions
|
||||
if (revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined) {
|
||||
console.error(`[${pad.id}] Missing atext in revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const apool = pad.pool;
|
||||
let atext = revisions[keyRev].meta.atext;
|
||||
|
||||
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
try {
|
||||
const cs = revisions[rev].changeset;
|
||||
atext = Changeset.applyToAText(cs, atext, apool);
|
||||
} catch (e) {
|
||||
console.error(`[${pad.id}] Bad changeset at revision ${i} - ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('finished');
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (err) {
|
||||
console.trace(err);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* This is a debug tool. It checks all revisions for data corruption
|
||||
*/
|
||||
|
||||
if (process.argv.length != 3) {
|
||||
console.error('Use: node bin/checkPad.js $PADID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
// load and initialize NPM;
|
||||
const npm = require('../src/node_modules/npm');
|
||||
npm.load({}, async () => {
|
||||
try {
|
||||
// initialize database
|
||||
const settings = require('../src/node/utils/Settings');
|
||||
const db = require('../src/node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load modules
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const padManager = require('../src/node/db/PadManager');
|
||||
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) {
|
||||
console.error('Pad does not exist');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// create an array with key revisions
|
||||
// key revisions always save the full pad atext
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
const keyRevisions = [];
|
||||
for (let rev = 0; rev < head; rev += 100) {
|
||||
keyRevisions.push(rev);
|
||||
}
|
||||
|
||||
// run through all key revisions
|
||||
for (const keyRev of keyRevisions) {
|
||||
// create an array of revisions we need till the next keyRevision or the End
|
||||
const revisionsNeeded = [];
|
||||
for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
revisionsNeeded.push(rev);
|
||||
}
|
||||
|
||||
// this array will hold all revision changesets
|
||||
const revisions = [];
|
||||
|
||||
// run through all needed revisions and get them from the database
|
||||
for (const revNum of revisionsNeeded) {
|
||||
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
|
||||
revisions[revNum] = revision;
|
||||
}
|
||||
|
||||
// check if the pad has a pool
|
||||
if (pad.pool === undefined) {
|
||||
console.error('Attribute pool is missing');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if there is an atext in the keyRevisions
|
||||
if (revisions[keyRev] === undefined || revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined) {
|
||||
console.error(`No atext in key revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const apool = pad.pool;
|
||||
let atext = revisions[keyRev].meta.atext;
|
||||
|
||||
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
try {
|
||||
// console.log("check revision " + rev);
|
||||
const cs = revisions[rev].changeset;
|
||||
atext = Changeset.applyToAText(cs, atext, apool);
|
||||
} catch (e) {
|
||||
console.error(`Bad changeset at revision ${rev} - ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
console.log('finished');
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* This is a debug tool. It checks all revisions for data corruption
|
||||
*/
|
||||
|
||||
if (process.argv.length != 3) {
|
||||
console.error('Use: node bin/checkPadDeltas.js $PADID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
// load and initialize NPM;
|
||||
const expect = require('expect.js');
|
||||
const diff = require('diff');
|
||||
var async = require('async');
|
||||
|
||||
const npm = require('../src/node_modules/npm');
|
||||
var async = require('ep_etherpad-lite/node_modules/async');
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
|
||||
npm.load({}, async () => {
|
||||
try {
|
||||
// initialize database
|
||||
const settings = require('../src/node/utils/Settings');
|
||||
const db = require('../src/node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load modules
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const padManager = require('../src/node/db/PadManager');
|
||||
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) {
|
||||
console.error('Pad does not exist');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// create an array with key revisions
|
||||
// key revisions always save the full pad atext
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
const keyRevisions = [];
|
||||
for (var i = 0; i < head; i += 100) {
|
||||
keyRevisions.push(i);
|
||||
}
|
||||
|
||||
// create an array with all revisions
|
||||
const revisions = [];
|
||||
for (var i = 0; i <= head; i++) {
|
||||
revisions.push(i);
|
||||
}
|
||||
|
||||
let atext = Changeset.makeAText('\n');
|
||||
|
||||
// run trough all revisions
|
||||
async.forEachSeries(revisions, (revNum, callback) => {
|
||||
// console.log('Fetching', revNum)
|
||||
db.db.get(`pad:${padId}:revs:${revNum}`, (err, revision) => {
|
||||
if (err) return callback(err);
|
||||
|
||||
// check if there is a atext in the keyRevisions
|
||||
if (~keyRevisions.indexOf(revNum) && (revision === undefined || revision.meta === undefined || revision.meta.atext === undefined)) {
|
||||
console.error(`No atext in key revision ${revNum}`);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// console.log("check revision ", revNum);
|
||||
const cs = revision.changeset;
|
||||
atext = Changeset.applyToAText(cs, atext, pad.pool);
|
||||
} catch (e) {
|
||||
console.error(`Bad changeset at revision ${revNum} - ${e.message}`);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (~keyRevisions.indexOf(revNum)) {
|
||||
try {
|
||||
expect(revision.meta.atext.text).to.eql(atext.text);
|
||||
expect(revision.meta.atext.attribs).to.eql(atext.attribs);
|
||||
} catch (e) {
|
||||
console.error(`Atext in key revision ${revNum} doesn't match computed one.`);
|
||||
console.log(diff.diffChars(atext.text, revision.meta.atext.text).map((op) => { if (!op.added && !op.removed) op.value = op.value.length; return op; }));
|
||||
// console.error(e)
|
||||
// console.log('KeyRev. :', revision.meta.atext)
|
||||
// console.log('Computed:', atext)
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setImmediate(callback);
|
||||
});
|
||||
}, (er) => {
|
||||
if (pad.atext.text == atext.text) { console.log('ok'); } else {
|
||||
console.error('Pad AText doesn\'t match computed one! (Computed ', atext.text.length, ', db', pad.atext.text.length, ')');
|
||||
console.log(diff.diffChars(atext.text, pad.atext.text).map((op) => { if (!op.added && !op.removed) op.value = op.value.length; return op; }));
|
||||
}
|
||||
callback(er);
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
391
bin/convert.js
391
bin/convert.js
|
@ -1,391 +0,0 @@
|
|||
const startTime = Date.now();
|
||||
const fs = require('fs');
|
||||
const ueberDB = require('../src/node_modules/ueberdb2');
|
||||
const mysql = require('../src/node_modules/ueberdb2/node_modules/mysql');
|
||||
const async = require('../src/node_modules/async');
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
|
||||
|
||||
const settingsFile = process.argv[2];
|
||||
const sqlOutputFile = process.argv[3];
|
||||
|
||||
// stop if the settings file is not set
|
||||
if (!settingsFile || !sqlOutputFile) {
|
||||
console.error('Use: node convert.js $SETTINGSFILE $SQLOUTPUT');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log('read settings file...');
|
||||
// read the settings file and parse the json
|
||||
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
||||
log('done');
|
||||
|
||||
log('open output file...');
|
||||
const sqlOutput = fs.openSync(sqlOutputFile, 'w');
|
||||
const sql = 'SET CHARACTER SET UTF8;\n' +
|
||||
'CREATE TABLE IF NOT EXISTS `store` ( \n' +
|
||||
'`key` VARCHAR( 100 ) NOT NULL , \n' +
|
||||
'`value` LONGTEXT NOT NULL , \n' +
|
||||
'PRIMARY KEY ( `key` ) \n' +
|
||||
') ENGINE = INNODB;\n' +
|
||||
'START TRANSACTION;\n\n';
|
||||
fs.writeSync(sqlOutput, sql);
|
||||
log('done');
|
||||
|
||||
const etherpadDB = mysql.createConnection({
|
||||
host: settings.etherpadDB.host,
|
||||
user: settings.etherpadDB.user,
|
||||
password: settings.etherpadDB.password,
|
||||
database: settings.etherpadDB.database,
|
||||
port: settings.etherpadDB.port,
|
||||
});
|
||||
|
||||
// get the timestamp once
|
||||
const timestamp = Date.now();
|
||||
|
||||
let padIDs;
|
||||
|
||||
async.series([
|
||||
// get all padids out of the database...
|
||||
function (callback) {
|
||||
log('get all padIds out of the database...');
|
||||
|
||||
etherpadDB.query('SELECT ID FROM PAD_META', [], (err, _padIDs) => {
|
||||
padIDs = _padIDs;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
log('done');
|
||||
|
||||
// create a queue with a concurrency 100
|
||||
const queue = async.queue((padId, callback) => {
|
||||
convertPad(padId, (err) => {
|
||||
incrementPadStats();
|
||||
callback(err);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// set the step callback as the queue callback
|
||||
queue.drain = callback;
|
||||
|
||||
// add the padids to the worker queue
|
||||
for (let i = 0, length = padIDs.length; i < length; i++) {
|
||||
queue.push(padIDs[i].ID);
|
||||
}
|
||||
},
|
||||
], (err) => {
|
||||
if (err) throw err;
|
||||
|
||||
// write the groups
|
||||
let sql = '';
|
||||
for (const proID in proID2groupID) {
|
||||
const groupID = proID2groupID[proID];
|
||||
const subdomain = proID2subdomain[proID];
|
||||
|
||||
sql += `REPLACE INTO store VALUES (${etherpadDB.escape(`group:${groupID}`)}, ${etherpadDB.escape(JSON.stringify(groups[groupID]))});\n`;
|
||||
sql += `REPLACE INTO store VALUES (${etherpadDB.escape(`mapper2group:subdomain:${subdomain}`)}, ${etherpadDB.escape(groupID)});\n`;
|
||||
}
|
||||
|
||||
// close transaction
|
||||
sql += 'COMMIT;';
|
||||
|
||||
// end the sql file
|
||||
fs.writeSync(sqlOutput, sql, undefined, 'utf-8');
|
||||
fs.closeSync(sqlOutput);
|
||||
|
||||
log('finished.');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
function log(str) {
|
||||
console.log(`${(Date.now() - startTime) / 1000}\t${str}`);
|
||||
}
|
||||
|
||||
let padsDone = 0;
|
||||
|
||||
function incrementPadStats() {
|
||||
padsDone++;
|
||||
|
||||
if (padsDone % 100 == 0) {
|
||||
const averageTime = Math.round(padsDone / ((Date.now() - startTime) / 1000));
|
||||
log(`${padsDone}/${padIDs.length}\t${averageTime} pad/s`);
|
||||
}
|
||||
}
|
||||
|
||||
var proID2groupID = {};
|
||||
var proID2subdomain = {};
|
||||
var groups = {};
|
||||
|
||||
function convertPad(padId, callback) {
|
||||
const changesets = [];
|
||||
const changesetsMeta = [];
|
||||
const chatMessages = [];
|
||||
const authors = [];
|
||||
let apool;
|
||||
let subdomain;
|
||||
let padmeta;
|
||||
|
||||
async.series([
|
||||
// get all needed db values
|
||||
function (callback) {
|
||||
async.parallel([
|
||||
// get the pad revisions
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_CHAT_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_CHAT_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(chatMessages, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the chat entries
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_REVS_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_REVS_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(changesets, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, false);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the pad revisions meta data
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_REVMETA_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_REVMETA_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(changesetsMeta, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the attribute pool of this pad
|
||||
function (callback) {
|
||||
const sql = 'SELECT `JSON` FROM `PAD_APOOL` WHERE `ID` = ?';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
apool = JSON.parse(results[0].JSON).x;
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the authors informations
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_AUTHORS_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_AUTHORS_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(authors, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the pad information
|
||||
function (callback) {
|
||||
const sql = 'SELECT JSON FROM `PAD_META` WHERE ID=?';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
padmeta = JSON.parse(results[0].JSON).x;
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the subdomain
|
||||
function (callback) {
|
||||
// skip if this is no proPad
|
||||
if (padId.indexOf('$') == -1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// get the proID out of this padID
|
||||
const proID = padId.split('$')[0];
|
||||
|
||||
const sql = 'SELECT subDomain FROM pro_domains WHERE ID = ?';
|
||||
|
||||
etherpadDB.query(sql, [proID], (err, results) => {
|
||||
if (!err) {
|
||||
subdomain = results[0].subDomain;
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
},
|
||||
function (callback) {
|
||||
// saves all values that should be written to the database
|
||||
const values = {};
|
||||
|
||||
// this is a pro pad, let's convert it to a group pad
|
||||
if (padId.indexOf('$') != -1) {
|
||||
const padIdParts = padId.split('$');
|
||||
const proID = padIdParts[0];
|
||||
const padName = padIdParts[1];
|
||||
|
||||
let groupID;
|
||||
|
||||
// this proID is not converted so far, do it
|
||||
if (proID2groupID[proID] == null) {
|
||||
groupID = `g.${randomString(16)}`;
|
||||
|
||||
// create the mappers for this new group
|
||||
proID2groupID[proID] = groupID;
|
||||
proID2subdomain[proID] = subdomain;
|
||||
groups[groupID] = {pads: {}};
|
||||
}
|
||||
|
||||
// use the generated groupID;
|
||||
groupID = proID2groupID[proID];
|
||||
|
||||
// rename the pad
|
||||
padId = `${groupID}$${padName}`;
|
||||
|
||||
// set the value for this pad in the group
|
||||
groups[groupID].pads[padId] = 1;
|
||||
}
|
||||
|
||||
try {
|
||||
const newAuthorIDs = {};
|
||||
const oldName2newName = {};
|
||||
|
||||
// replace the authors with generated authors
|
||||
// we need to do that cause where the original etherpad saves pad local authors, the new (lite) etherpad uses them global
|
||||
for (var i in apool.numToAttrib) {
|
||||
var key = apool.numToAttrib[i][0];
|
||||
const value = apool.numToAttrib[i][1];
|
||||
|
||||
// skip non authors and anonymous authors
|
||||
if (key != 'author' || value == '') continue;
|
||||
|
||||
// generate new author values
|
||||
const authorID = `a.${randomString(16)}`;
|
||||
const authorColorID = authors[i].colorId || Math.floor(Math.random() * (exports.getColorPalette().length));
|
||||
const authorName = authors[i].name || null;
|
||||
|
||||
// overwrite the authorID of the attribute pool
|
||||
apool.numToAttrib[i][1] = authorID;
|
||||
|
||||
// write the author to the database
|
||||
values[`globalAuthor:${authorID}`] = {colorId: authorColorID, name: authorName, timestamp};
|
||||
|
||||
// save in mappers
|
||||
newAuthorIDs[i] = authorID;
|
||||
oldName2newName[value] = authorID;
|
||||
}
|
||||
|
||||
// save all revisions
|
||||
for (var i = 0; i < changesets.length; i++) {
|
||||
values[`pad:${padId}:revs:${i}`] = {changeset: changesets[i],
|
||||
meta: {
|
||||
author: newAuthorIDs[changesetsMeta[i].a],
|
||||
timestamp: changesetsMeta[i].t,
|
||||
atext: changesetsMeta[i].atext || undefined,
|
||||
}};
|
||||
}
|
||||
|
||||
// save all chat messages
|
||||
for (var i = 0; i < chatMessages.length; i++) {
|
||||
values[`pad:${padId}:chat:${i}`] = {text: chatMessages[i].lineText,
|
||||
userId: oldName2newName[chatMessages[i].userId],
|
||||
time: chatMessages[i].time};
|
||||
}
|
||||
|
||||
// generate the latest atext
|
||||
const fullAPool = (new AttributePool()).fromJsonable(apool);
|
||||
const keyRev = Math.floor(padmeta.head / padmeta.keyRevInterval) * padmeta.keyRevInterval;
|
||||
let atext = changesetsMeta[keyRev].atext;
|
||||
let curRev = keyRev;
|
||||
while (curRev < padmeta.head) {
|
||||
curRev++;
|
||||
const changeset = changesets[curRev];
|
||||
atext = Changeset.applyToAText(changeset, atext, fullAPool);
|
||||
}
|
||||
|
||||
values[`pad:${padId}`] = {atext,
|
||||
pool: apool,
|
||||
head: padmeta.head,
|
||||
chatHead: padmeta.numChatMessages};
|
||||
} catch (e) {
|
||||
console.error(`Error while converting pad ${padId}, pad skipped`);
|
||||
console.error(e.stack ? e.stack : JSON.stringify(e));
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
let sql = '';
|
||||
for (var key in values) {
|
||||
sql += `REPLACE INTO store VALUES (${etherpadDB.escape(key)}, ${etherpadDB.escape(JSON.stringify(values[key]))});\n`;
|
||||
}
|
||||
|
||||
fs.writeSync(sqlOutput, sql, undefined, 'utf-8');
|
||||
callback();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* This parses a Page like Etherpad uses them in the databases
|
||||
* The offsets describes the length of a unit in the page, the data are
|
||||
* all values behind each other
|
||||
*/
|
||||
function parsePage(array, pageStart, offsets, data, json) {
|
||||
let start = 0;
|
||||
const lengths = offsets.split(',');
|
||||
|
||||
for (let i = 0; i < lengths.length; i++) {
|
||||
let unitLength = lengths[i];
|
||||
|
||||
// skip empty units
|
||||
if (unitLength == '') continue;
|
||||
|
||||
// parse the number
|
||||
unitLength = Number(unitLength);
|
||||
|
||||
// cut the unit out of data
|
||||
const unit = data.substr(start, unitLength);
|
||||
|
||||
// put it into the array
|
||||
array[pageStart + i] = json ? JSON.parse(unit) : unit;
|
||||
|
||||
// update start
|
||||
start += unitLength;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* A tool for deleting ALL GROUP sessions Etherpad user sessions from the CLI,
|
||||
* because sometimes a brick is required to fix a face.
|
||||
*/
|
||||
|
||||
const request = require('../src/node_modules/request');
|
||||
const settings = require(`${__dirname}/../tests/container/loadSettings`).loadSettings();
|
||||
const supertest = require(`${__dirname}/../src/node_modules/supertest`);
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// get the API Key
|
||||
const filePath = path.join(__dirname, '../APIKEY.txt');
|
||||
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
|
||||
|
||||
// Set apiVersion to base value, we change this later.
|
||||
let apiVersion = 1;
|
||||
let guids;
|
||||
|
||||
// Update the apiVersion
|
||||
api.get('/api/')
|
||||
.expect((res) => {
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error('No version set in API');
|
||||
return;
|
||||
})
|
||||
.then(() => {
|
||||
const guri = `/api/${apiVersion}/listAllGroups?apikey=${apikey}`;
|
||||
api.get(guri)
|
||||
.then((res) => {
|
||||
guids = res.body.data.groupIDs;
|
||||
guids.forEach((groupID) => {
|
||||
const luri = `/api/${apiVersion}/listSessionsOfGroup?apikey=${apikey}&groupID=${groupID}`;
|
||||
api.get(luri)
|
||||
.then((res) => {
|
||||
if (res.body.data) {
|
||||
Object.keys(res.body.data).forEach((sessionID) => {
|
||||
if (sessionID) {
|
||||
console.log('Deleting', sessionID);
|
||||
const duri = `/api/${apiVersion}/deleteSession?apikey=${apikey}&sessionID=${sessionID}`;
|
||||
api.post(duri); // deletes
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// no session in this group.
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* A tool for deleting pads from the CLI, because sometimes a brick is required
|
||||
* to fix a window.
|
||||
*/
|
||||
|
||||
const request = require('../src/node_modules/request');
|
||||
const settings = require(`${__dirname}/../tests/container/loadSettings`).loadSettings();
|
||||
const supertest = require(`${__dirname}/../src/node_modules/supertest`);
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
if (process.argv.length != 3) {
|
||||
console.error('Use: node deletePad.js $PADID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
// get the API Key
|
||||
const filePath = path.join(__dirname, '../APIKEY.txt');
|
||||
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
|
||||
|
||||
// Set apiVersion to base value, we change this later.
|
||||
let apiVersion = 1;
|
||||
|
||||
// Update the apiVersion
|
||||
api.get('/api/')
|
||||
.expect((res) => {
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error('No version set in API');
|
||||
return;
|
||||
})
|
||||
.end((err, res) => {
|
||||
// Now we know the latest API version, let's delete pad
|
||||
const uri = `/api/${apiVersion}/deletePad?apikey=${apikey}&padID=${padId}`;
|
||||
api.post(uri)
|
||||
.expect((res) => {
|
||||
if (res.body.code === 1) {
|
||||
console.error('Error deleting pad', res.body);
|
||||
} else {
|
||||
console.log('Deleted pad', res.body);
|
||||
}
|
||||
return;
|
||||
})
|
||||
.end(() => {});
|
||||
});
|
||||
// end
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* This is a debug tool. It helps to extract all datas of a pad and move it from
|
||||
* a productive environment and to a develop environment to reproduce bugs
|
||||
* there. It outputs a dirtydb file
|
||||
*/
|
||||
|
||||
if (process.argv.length != 3) {
|
||||
console.error('Use: node extractPadData.js $PADID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
const npm = require('../src/node_modules/npm');
|
||||
|
||||
npm.load({}, async (er) => {
|
||||
if (er) {
|
||||
console.error(`Could not load NPM: ${er}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// initialize database
|
||||
const settings = require('../src/node/utils/Settings');
|
||||
const db = require('../src/node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load extra modules
|
||||
const dirtyDB = require('../src/node_modules/dirty');
|
||||
const padManager = require('../src/node/db/PadManager');
|
||||
const util = require('util');
|
||||
|
||||
// initialize output database
|
||||
const dirty = dirtyDB(`${padId}.db`);
|
||||
|
||||
// Promise wrapped get and set function
|
||||
const wrapped = db.db.db.wrappedDB;
|
||||
const get = util.promisify(wrapped.get.bind(wrapped));
|
||||
const set = util.promisify(dirty.set.bind(dirty));
|
||||
|
||||
// array in which required key values will be accumulated
|
||||
const neededDBValues = [`pad:${padId}`];
|
||||
|
||||
// get the actual pad object
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// add all authors
|
||||
neededDBValues.push(...pad.getAllAuthors().map((author) => `globalAuthor:${author}`));
|
||||
|
||||
// add all revisions
|
||||
for (let rev = 0; rev <= pad.head; ++rev) {
|
||||
neededDBValues.push(`pad:${padId}:revs:${rev}`);
|
||||
}
|
||||
|
||||
// add all chat values
|
||||
for (let chat = 0; chat <= pad.chatHead; ++chat) {
|
||||
neededDBValues.push(`pad:${padId}:chat:${chat}`);
|
||||
}
|
||||
|
||||
for (const dbkey of neededDBValues) {
|
||||
let dbvalue = await get(dbkey);
|
||||
if (dbvalue && typeof dbvalue !== 'object') {
|
||||
dbvalue = JSON.parse(dbvalue);
|
||||
}
|
||||
await set(dbkey, dbvalue);
|
||||
}
|
||||
|
||||
console.log('finished');
|
||||
process.exit(0);
|
||||
} catch (er) {
|
||||
console.error(er);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Run Etherpad directly, assuming all the dependencies are already installed.
|
||||
#
|
||||
# Useful for developers, or users that know what they are doing. If you just
|
||||
# upgraded Etherpad version, installed a new dependency, or are simply unsure
|
||||
# of what to do, please execute bin/installDeps.sh once before running this
|
||||
# script.
|
||||
|
||||
set -eu
|
||||
|
||||
# source: https://stackoverflow.com/questions/59895/how-to-get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
|
||||
# Source constants and usefull functions
|
||||
. ${DIR}/../bin/functions.sh
|
||||
|
||||
echo "Running directly, without checking/installing dependencies"
|
||||
|
||||
# move to the base Etherpad directory. This will be necessary until Etherpad
|
||||
# learns to run from arbitrary CWDs.
|
||||
cd "${DIR}/.."
|
||||
|
||||
# run Etherpad main class
|
||||
node $(compute_node_args) "${DIR}/../node_modules/ep_etherpad-lite/node/server.js" "$@"
|
|
@ -1,103 +0,0 @@
|
|||
const startTime = Date.now();
|
||||
|
||||
require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => {
|
||||
const fs = require('fs');
|
||||
|
||||
const ueberDB = require('ep_etherpad-lite/node_modules/ueberdb2');
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
const log4js = require('ep_etherpad-lite/node_modules/log4js');
|
||||
|
||||
const dbWrapperSettings = {
|
||||
cache: 0,
|
||||
writeInterval: 100,
|
||||
json: false, // data is already json encoded
|
||||
};
|
||||
const db = new ueberDB.database(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger('ueberDB'));
|
||||
|
||||
const sqlFile = process.argv[2];
|
||||
|
||||
// stop if the settings file is not set
|
||||
if (!sqlFile) {
|
||||
console.error('Use: node importSqlFile.js $SQLFILE');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log('initializing db');
|
||||
db.init((err) => {
|
||||
// there was an error while initializing the database, output it and stop
|
||||
if (err) {
|
||||
console.error('ERROR: Problem while initializing the database');
|
||||
console.error(err.stack ? err.stack : err);
|
||||
process.exit(1);
|
||||
} else {
|
||||
log('done');
|
||||
|
||||
log('open output file...');
|
||||
const lines = fs.readFileSync(sqlFile, 'utf8').split('\n');
|
||||
|
||||
const count = lines.length;
|
||||
let keyNo = 0;
|
||||
|
||||
process.stdout.write(`Start importing ${count} keys...\n`);
|
||||
lines.forEach((l) => {
|
||||
if (l.substr(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);
|
||||
console.log(`key: ${key} val: ${value}`);
|
||||
console.log(`unval: ${unescape(value)}`);
|
||||
db.set(key, unescape(value), null);
|
||||
keyNo++;
|
||||
if (keyNo % 1000 == 0) {
|
||||
process.stdout.write(` ${keyNo}/${count}\n`);
|
||||
}
|
||||
}
|
||||
});
|
||||
process.stdout.write('\n');
|
||||
process.stdout.write('done. waiting for db to finish transaction. depended on dbms this may take some time...\n');
|
||||
|
||||
db.doShutdown(() => {
|
||||
log(`finished, imported ${keyNo} keys.`);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function log(str) {
|
||||
console.log(`${(Date.now() - startTime) / 1000}\t${str}`);
|
||||
}
|
||||
|
||||
unescape = function (val) {
|
||||
// value is a string
|
||||
if (val.substr(0, 1) == "'") {
|
||||
val = val.substr(0, val.length - 1).substr(1);
|
||||
|
||||
return val.replace(/\\[0nrbtZ\\'"]/g, (s) => {
|
||||
switch (s) {
|
||||
case '\\0': return '\0';
|
||||
case '\\n': return '\n';
|
||||
case '\\r': return '\r';
|
||||
case '\\b': return '\b';
|
||||
case '\\t': return '\t';
|
||||
case '\\Z': return '\x1a';
|
||||
default: return s.substr(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// value is a boolean or NULL
|
||||
if (val == 'NULL') {
|
||||
return null;
|
||||
}
|
||||
if (val == 'true') {
|
||||
return true;
|
||||
}
|
||||
if (val == 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// value is a number
|
||||
return val;
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => {
|
||||
process.chdir(`${npm.root}/..`);
|
||||
|
||||
// This script requires that you have modified your settings.json file
|
||||
// to work with a real database. Please make a backup of your dirty.db
|
||||
// file before using this script, just to be safe.
|
||||
|
||||
// It might be necessary to run the script using more memory:
|
||||
// `node --max-old-space-size=4096 bin/migrateDirtyDBtoRealDB.js`
|
||||
|
||||
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
let dirty = require('../src/node_modules/dirty');
|
||||
const ueberDB = require('../src/node_modules/ueberdb2');
|
||||
const log4js = require('../src/node_modules/log4js');
|
||||
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(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger('ueberDB'));
|
||||
let i = 0;
|
||||
let length = 0;
|
||||
|
||||
db.init(() => {
|
||||
console.log('Waiting for dirtyDB to parse its file.');
|
||||
dirty = dirty('var/dirty.db').on('load', () => {
|
||||
dirty.forEach(() => {
|
||||
length++;
|
||||
});
|
||||
console.log(`Found ${length} records, processing now.`);
|
||||
|
||||
dirty.forEach(async (key, value) => {
|
||||
const error = await db.set(key, value);
|
||||
console.log(`Wrote record ${i}`);
|
||||
i++;
|
||||
|
||||
if (i === length) {
|
||||
console.log('finished, just clearing up for a bit...');
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
console.log('Please wait for all records to flush to database, then kill this process.');
|
||||
});
|
||||
console.log('done?');
|
||||
});
|
||||
});
|
|
@ -1,469 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Usage -- see README.md
|
||||
*
|
||||
* Normal usage: node bin/plugins/checkPlugins.js ep_whatever
|
||||
* Auto fix the things it can: node bin/plugins/checkPlugins.js ep_whatever autofix
|
||||
* Auto commit, push and publish(to npm) * highly dangerous:
|
||||
node bin/plugins/checkPlugins.js ep_whatever autofix autocommit
|
||||
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const {exec} = require('child_process');
|
||||
|
||||
// get plugin name & path from user input
|
||||
const pluginName = process.argv[2];
|
||||
|
||||
if (!pluginName) {
|
||||
console.error('no plugin name specified');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pluginPath = `node_modules/${pluginName}`;
|
||||
|
||||
console.log(`Checking the plugin: ${pluginName}`);
|
||||
|
||||
// Should we autofix?
|
||||
if (process.argv[3] && process.argv[3] === 'autofix') var autoFix = true;
|
||||
|
||||
// Should we update files where possible?
|
||||
if (process.argv[5] && process.argv[5] === 'autoupdate') var autoUpdate = true;
|
||||
|
||||
// Should we automcommit and npm publish?!
|
||||
if (process.argv[4] && process.argv[4] === 'autocommit') var autoCommit = true;
|
||||
|
||||
|
||||
if (autoCommit) {
|
||||
console.warn('Auto commit is enabled, I hope you know what you are doing...');
|
||||
}
|
||||
|
||||
fs.readdir(pluginPath, (err, rootFiles) => {
|
||||
// handling error
|
||||
if (err) {
|
||||
return console.log(`Unable to scan directory: ${err}`);
|
||||
}
|
||||
|
||||
// rewriting files to lower case
|
||||
const files = [];
|
||||
|
||||
// some files we need to know the actual file name. Not compulsory but might help in the future.
|
||||
let readMeFileName;
|
||||
let repository;
|
||||
let hasAutoFixed = false;
|
||||
|
||||
for (let i = 0; i < rootFiles.length; i++) {
|
||||
if (rootFiles[i].toLowerCase().indexOf('readme') !== -1) readMeFileName = rootFiles[i];
|
||||
files.push(rootFiles[i].toLowerCase());
|
||||
}
|
||||
|
||||
if (files.indexOf('.git') === -1) {
|
||||
console.error('No .git folder, aborting');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// do a git pull...
|
||||
var child_process = require('child_process');
|
||||
try {
|
||||
child_process.execSync('git pull ', {cwd: `${pluginPath}/`});
|
||||
} catch (e) {
|
||||
console.error('Error git pull', e);
|
||||
}
|
||||
|
||||
try {
|
||||
const path = `${pluginPath}/.github/workflows/npmpublish.yml`;
|
||||
if (!fs.existsSync(path)) {
|
||||
console.log('no .github/workflows/npmpublish.yml, create one and set npm secret to auto publish to npm on commit');
|
||||
if (autoFix) {
|
||||
const npmpublish =
|
||||
fs.readFileSync('bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, npmpublish);
|
||||
hasAutoFixed = true;
|
||||
console.log("If you haven't already, setup autopublish for this plugin https://github.com/ether/etherpad-lite/wiki/Plugins:-Automatically-publishing-to-npm-on-commit-to-Github-Repo");
|
||||
} else {
|
||||
console.log('Setup autopublish for this plugin https://github.com/ether/etherpad-lite/wiki/Plugins:-Automatically-publishing-to-npm-on-commit-to-Github-Repo');
|
||||
}
|
||||
} else {
|
||||
// autopublish exists, we should check the version..
|
||||
// checkVersion takes two file paths and checks for a version string in them.
|
||||
const currVersionFile = fs.readFileSync(path, {encoding: 'utf8', flag: 'r'});
|
||||
const existingConfigLocation = currVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const existingValue = parseInt(currVersionFile.substr(existingConfigLocation + 17, existingConfigLocation.length));
|
||||
|
||||
const reqVersionFile = fs.readFileSync('bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
|
||||
const reqConfigLocation = reqVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const reqValue = parseInt(reqVersionFile.substr(reqConfigLocation + 17, reqConfigLocation.length));
|
||||
|
||||
if (!existingValue || (reqValue > existingValue)) {
|
||||
const npmpublish =
|
||||
fs.readFileSync('bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, npmpublish);
|
||||
hasAutoFixed = true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const path = `${pluginPath}/.github/workflows/backend-tests.yml`;
|
||||
if (!fs.existsSync(path)) {
|
||||
console.log('no .github/workflows/backend-tests.yml, create one and set npm secret to auto publish to npm on commit');
|
||||
if (autoFix) {
|
||||
const backendTests =
|
||||
fs.readFileSync('bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, backendTests);
|
||||
hasAutoFixed = true;
|
||||
}
|
||||
} else {
|
||||
// autopublish exists, we should check the version..
|
||||
// checkVersion takes two file paths and checks for a version string in them.
|
||||
const currVersionFile = fs.readFileSync(path, {encoding: 'utf8', flag: 'r'});
|
||||
const existingConfigLocation = currVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const existingValue = parseInt(currVersionFile.substr(existingConfigLocation + 17, existingConfigLocation.length));
|
||||
|
||||
const reqVersionFile = fs.readFileSync('bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
|
||||
const reqConfigLocation = reqVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const reqValue = parseInt(reqVersionFile.substr(reqConfigLocation + 17, reqConfigLocation.length));
|
||||
|
||||
if (!existingValue || (reqValue > existingValue)) {
|
||||
const backendTests =
|
||||
fs.readFileSync('bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, backendTests);
|
||||
hasAutoFixed = true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (files.indexOf('package.json') === -1) {
|
||||
console.warn('no package.json, please create');
|
||||
}
|
||||
|
||||
if (files.indexOf('package.json') !== -1) {
|
||||
const packageJSON = fs.readFileSync(`${pluginPath}/package.json`, {encoding: 'utf8', flag: 'r'});
|
||||
const parsedPackageJSON = JSON.parse(packageJSON);
|
||||
if (autoFix) {
|
||||
let updatedPackageJSON = false;
|
||||
if (!parsedPackageJSON.funding) {
|
||||
updatedPackageJSON = true;
|
||||
parsedPackageJSON.funding = {
|
||||
type: 'individual',
|
||||
url: 'https://etherpad.org/',
|
||||
};
|
||||
}
|
||||
if (updatedPackageJSON) {
|
||||
hasAutoFixed = true;
|
||||
fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (packageJSON.toLowerCase().indexOf('repository') === -1) {
|
||||
console.warn('No repository in package.json');
|
||||
if (autoFix) {
|
||||
console.warn('Repository not detected in package.json. Please add repository section manually.');
|
||||
}
|
||||
} else {
|
||||
// useful for creating README later.
|
||||
repository = parsedPackageJSON.repository.url;
|
||||
}
|
||||
|
||||
// include lint config
|
||||
if (packageJSON.toLowerCase().indexOf('devdependencies') === -1 || !parsedPackageJSON.devDependencies.eslint) {
|
||||
console.warn('Missing eslint reference in devDependencies');
|
||||
if (autoFix) {
|
||||
const devDependencies = {
|
||||
'eslint': '^7.14.0',
|
||||
'eslint-config-etherpad': '^1.0.13',
|
||||
'eslint-plugin-mocha': '^8.0.0',
|
||||
'eslint-plugin-node': '^11.1.0',
|
||||
'eslint-plugin-prefer-arrow': '^1.2.2',
|
||||
'eslint-plugin-promise': '^4.2.1',
|
||||
};
|
||||
hasAutoFixed = true;
|
||||
parsedPackageJSON.devDependencies = devDependencies;
|
||||
fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
|
||||
|
||||
const child_process = require('child_process');
|
||||
try {
|
||||
child_process.execSync('npm install', {cwd: `${pluginPath}/`});
|
||||
hasAutoFixed = true;
|
||||
} catch (e) {
|
||||
console.error('Failed to create package-lock.json');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// include peer deps config
|
||||
if (packageJSON.toLowerCase().indexOf('peerdependencies') === -1 || !parsedPackageJSON.peerDependencies) {
|
||||
console.warn('Missing peer deps reference in package.json');
|
||||
if (autoFix) {
|
||||
const peerDependencies = {
|
||||
'ep_etherpad-lite': '>=1.8.6',
|
||||
};
|
||||
hasAutoFixed = true;
|
||||
parsedPackageJSON.peerDependencies = peerDependencies;
|
||||
fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
|
||||
const child_process = require('child_process');
|
||||
try {
|
||||
child_process.execSync('npm install --no-save ep_etherpad-lite@file:../../src', {cwd: `${pluginPath}/`});
|
||||
hasAutoFixed = true;
|
||||
} catch (e) {
|
||||
console.error('Failed to create package-lock.json');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packageJSON.toLowerCase().indexOf('eslintconfig') === -1) {
|
||||
console.warn('No esLintConfig in package.json');
|
||||
if (autoFix) {
|
||||
const eslintConfig = {
|
||||
root: true,
|
||||
extends: 'etherpad/plugin',
|
||||
};
|
||||
hasAutoFixed = true;
|
||||
parsedPackageJSON.eslintConfig = eslintConfig;
|
||||
fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (packageJSON.toLowerCase().indexOf('scripts') === -1) {
|
||||
console.warn('No scripts in package.json');
|
||||
if (autoFix) {
|
||||
const scripts = {
|
||||
'lint': 'eslint .',
|
||||
'lint:fix': 'eslint --fix .',
|
||||
};
|
||||
hasAutoFixed = true;
|
||||
parsedPackageJSON.scripts = scripts;
|
||||
fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
if ((packageJSON.toLowerCase().indexOf('engines') === -1) || !parsedPackageJSON.engines.node) {
|
||||
console.warn('No engines or node engine in package.json');
|
||||
if (autoFix) {
|
||||
const engines = {
|
||||
node: '>=10.13.0',
|
||||
};
|
||||
hasAutoFixed = true;
|
||||
parsedPackageJSON.engines = engines;
|
||||
fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('package-lock.json') === -1) {
|
||||
console.warn('package-lock.json file not found. Please run npm install in the plugin folder and commit the package-lock.json file.');
|
||||
if (autoFix) {
|
||||
var child_process = require('child_process');
|
||||
try {
|
||||
child_process.execSync('npm install', {cwd: `${pluginPath}/`});
|
||||
console.log('Making package-lock.json');
|
||||
hasAutoFixed = true;
|
||||
} catch (e) {
|
||||
console.error('Failed to create package-lock.json');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('readme') === -1 && files.indexOf('readme.md') === -1) {
|
||||
console.warn('README.md file not found, please create');
|
||||
if (autoFix) {
|
||||
console.log('Autofixing missing README.md file, please edit the README.md file further to include plugin specific details.');
|
||||
let readme = fs.readFileSync('bin/plugins/lib/README.md', {encoding: 'utf8', flag: 'r'});
|
||||
readme = readme.replace(/\[plugin_name\]/g, pluginName);
|
||||
if (repository) {
|
||||
const org = repository.split('/')[3];
|
||||
const name = repository.split('/')[4];
|
||||
readme = readme.replace(/\[org_name\]/g, org);
|
||||
readme = readme.replace(/\[repo_url\]/g, name);
|
||||
fs.writeFileSync(`${pluginPath}/README.md`, readme);
|
||||
} else {
|
||||
console.warn('Unable to find repository in package.json, aborting.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('contributing') === -1 && files.indexOf('contributing.md') === -1) {
|
||||
console.warn('CONTRIBUTING.md file not found, please create');
|
||||
if (autoFix) {
|
||||
console.log('Autofixing missing CONTRIBUTING.md file, please edit the CONTRIBUTING.md file further to include plugin specific details.');
|
||||
let contributing = fs.readFileSync('bin/plugins/lib/CONTRIBUTING.md', {encoding: 'utf8', flag: 'r'});
|
||||
contributing = contributing.replace(/\[plugin_name\]/g, pluginName);
|
||||
fs.writeFileSync(`${pluginPath}/CONTRIBUTING.md`, contributing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (files.indexOf('readme') !== -1 && files.indexOf('readme.md') !== -1) {
|
||||
const readme = fs.readFileSync(`${pluginPath}/${readMeFileName}`, {encoding: 'utf8', flag: 'r'});
|
||||
if (readme.toLowerCase().indexOf('license') === -1) {
|
||||
console.warn('No license section in README');
|
||||
if (autoFix) {
|
||||
console.warn('Please add License section to README manually.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('license') === -1 && files.indexOf('license.md') === -1) {
|
||||
console.warn('LICENSE.md file not found, please create');
|
||||
if (autoFix) {
|
||||
hasAutoFixed = true;
|
||||
console.log('Autofixing missing LICENSE.md file, including Apache 2 license.');
|
||||
exec('git config user.name', (error, name, stderr) => {
|
||||
if (error) {
|
||||
console.log(`error: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
console.log(`stderr: ${stderr}`);
|
||||
return;
|
||||
}
|
||||
let license = fs.readFileSync('bin/plugins/lib/LICENSE.md', {encoding: 'utf8', flag: 'r'});
|
||||
license = license.replace('[yyyy]', new Date().getFullYear());
|
||||
license = license.replace('[name of copyright owner]', name);
|
||||
fs.writeFileSync(`${pluginPath}/LICENSE.md`, license);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let travisConfig = fs.readFileSync('bin/plugins/lib/travis.yml', {encoding: 'utf8', flag: 'r'});
|
||||
travisConfig = travisConfig.replace(/\[plugin_name\]/g, pluginName);
|
||||
|
||||
if (files.indexOf('.travis.yml') === -1) {
|
||||
console.warn('.travis.yml file not found, please create. .travis.yml is used for automatically CI testing Etherpad. It is useful to know if your plugin breaks another feature for example.');
|
||||
// TODO: Make it check version of the .travis file to see if it needs an update.
|
||||
if (autoFix) {
|
||||
hasAutoFixed = true;
|
||||
console.log('Autofixing missing .travis.yml file');
|
||||
fs.writeFileSync(`${pluginPath}/.travis.yml`, travisConfig);
|
||||
console.log('Travis file created, please sign into travis and enable this repository');
|
||||
}
|
||||
}
|
||||
if (autoFix && autoUpdate) {
|
||||
// checks the file versioning of .travis and updates it to the latest.
|
||||
const existingConfig = fs.readFileSync(`${pluginPath}/.travis.yml`, {encoding: 'utf8', flag: 'r'});
|
||||
const existingConfigLocation = existingConfig.indexOf('##ETHERPAD_TRAVIS_V=');
|
||||
const existingValue = parseInt(existingConfig.substr(existingConfigLocation + 20, existingConfig.length));
|
||||
|
||||
const newConfigLocation = travisConfig.indexOf('##ETHERPAD_TRAVIS_V=');
|
||||
const newValue = parseInt(travisConfig.substr(newConfigLocation + 20, travisConfig.length));
|
||||
if (existingConfigLocation === -1) {
|
||||
console.warn('no previous .travis.yml version found so writing new.');
|
||||
// we will write the newTravisConfig to the location.
|
||||
fs.writeFileSync(`${pluginPath}/.travis.yml`, travisConfig);
|
||||
} else if (newValue > existingValue) {
|
||||
console.log('updating .travis.yml');
|
||||
fs.writeFileSync(`${pluginPath}/.travis.yml`, travisConfig);
|
||||
hasAutoFixed = true;
|
||||
}//
|
||||
}
|
||||
|
||||
if (files.indexOf('.gitignore') === -1) {
|
||||
console.warn(".gitignore file not found, please create. .gitignore files are useful to ensure files aren't incorrectly commited to a repository.");
|
||||
if (autoFix) {
|
||||
hasAutoFixed = true;
|
||||
console.log('Autofixing missing .gitignore file');
|
||||
const gitignore = fs.readFileSync('bin/plugins/lib/gitignore', {encoding: 'utf8', flag: 'r'});
|
||||
fs.writeFileSync(`${pluginPath}/.gitignore`, gitignore);
|
||||
}
|
||||
} else {
|
||||
let gitignore =
|
||||
fs.readFileSync(`${pluginPath}/.gitignore`, {encoding: 'utf8', flag: 'r'});
|
||||
if (gitignore.indexOf('node_modules/') === -1) {
|
||||
console.warn('node_modules/ missing from .gitignore');
|
||||
if (autoFix) {
|
||||
gitignore += 'node_modules/';
|
||||
fs.writeFileSync(`${pluginPath}/.gitignore`, gitignore);
|
||||
hasAutoFixed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we include templates but don't have translations...
|
||||
if (files.indexOf('templates') !== -1 && files.indexOf('locales') === -1) {
|
||||
console.warn('Translations not found, please create. Translation files help with Etherpad accessibility.');
|
||||
}
|
||||
|
||||
|
||||
if (files.indexOf('.ep_initialized') !== -1) {
|
||||
console.warn('.ep_initialized found, please remove. .ep_initialized should never be commited to git and should only exist once the plugin has been executed one time.');
|
||||
if (autoFix) {
|
||||
hasAutoFixed = true;
|
||||
console.log('Autofixing incorrectly existing .ep_initialized file');
|
||||
fs.unlinkSync(`${pluginPath}/.ep_initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('npm-debug.log') !== -1) {
|
||||
console.warn('npm-debug.log found, please remove. npm-debug.log should never be commited to your repository.');
|
||||
if (autoFix) {
|
||||
hasAutoFixed = true;
|
||||
console.log('Autofixing incorrectly existing npm-debug.log file');
|
||||
fs.unlinkSync(`${pluginPath}/npm-debug.log`);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('static') !== -1) {
|
||||
fs.readdir(`${pluginPath}/static`, (errRead, staticFiles) => {
|
||||
if (staticFiles.indexOf('tests') === -1) {
|
||||
console.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin');
|
||||
}
|
||||
|
||||
// linting begins
|
||||
if (autoFix) {
|
||||
var lintCmd = 'npm run lint:fix';
|
||||
} else {
|
||||
var lintCmd = 'npm run lint';
|
||||
}
|
||||
|
||||
try {
|
||||
child_process.execSync(lintCmd, {cwd: `${pluginPath}/`});
|
||||
console.log('Linting...');
|
||||
if (autoFix) {
|
||||
// todo: if npm run lint doesn't do anything no need for...
|
||||
hasAutoFixed = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// it is gonna throw an error anyway
|
||||
console.log('Manual linting probably required, check with: npm run lint');
|
||||
}
|
||||
// linting ends.
|
||||
|
||||
if (hasAutoFixed) {
|
||||
console.log('Fixes applied, please check git diff then run the following command:\n\n');
|
||||
// bump npm Version
|
||||
if (autoCommit) {
|
||||
// holy shit you brave.
|
||||
console.log('Attempting autocommit and auto publish to npm');
|
||||
// github should push to npm for us :)
|
||||
exec(`cd node_modules/${pluginName} && git rm -rf node_modules --ignore-unmatch && git add -A && git commit --allow-empty -m 'autofixes from Etherpad checkPlugins.js' && git push && cd ../..`, (error, name, stderr) => {
|
||||
if (error) {
|
||||
console.log(`error: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
console.log(`stderr: ${stderr}`);
|
||||
return;
|
||||
}
|
||||
console.log("I think she's got it! By George she's got it!");
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
console.log(`cd node_modules/${pluginName} && git add -A && git commit --allow-empty -m 'autofixes from Etherpad checkPlugins.js' && npm version patch && git add package.json && git commit --allow-empty -m 'bump version' && git push && npm publish && cd ../..`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Finished');
|
||||
});
|
|
@ -1,70 +0,0 @@
|
|||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "lts/*"
|
||||
|
||||
cache: false
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
install:
|
||||
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
|
||||
|
||||
#script:
|
||||
# - "tests/frontend/travis/runner.sh"
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "WMGxFkOeTTlhWB+ChMucRtIqVmMbwzYdNHuHQjKCcj8HBEPdZLfCuK/kf4rG\nVLcLQiIsyllqzNhBGVHG1nyqWr0/LTm8JRqSCDDVIhpyzp9KpCJQQJG2Uwjk\n6/HIJJh/wbxsEdLNV2crYU/EiVO3A4Bq0YTHUlbhUqG3mSCr5Ec="
|
||||
- secure: "gejXUAHYscbR6Bodw35XexpToqWkv2ifeECsbeEmjaLkYzXmUUNWJGknKSu7\nEUsSfQV8w+hxApr1Z+jNqk9aX3K1I4btL3cwk2trnNI8XRAvu1c1Iv60eerI\nkE82Rsd5lwUaMEh+/HoL8ztFCZamVndoNgX7HWp5J/NRZZMmh4g="
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: "Lint test package-lock"
|
||||
install:
|
||||
- "npm install lockfile-lint"
|
||||
script:
|
||||
- npx lockfile-lint --path package-lock.json --validate-https --allowed-hosts npm
|
||||
- name: "Run the Backend tests"
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:libreoffice/ppa
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -y install libreoffice
|
||||
- sudo apt-get -y install libreoffice-pdfimport
|
||||
install:
|
||||
- "npm install"
|
||||
- "mkdir [plugin_name]"
|
||||
- "mv !([plugin_name]) [plugin_name]"
|
||||
- "git clone https://github.com/ether/etherpad-lite.git etherpad"
|
||||
- "cd etherpad"
|
||||
- "mkdir -p node_modules"
|
||||
- "mv ../[plugin_name] node_modules"
|
||||
- "bin/installDeps.sh"
|
||||
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
|
||||
- "cd src && npm install && cd -"
|
||||
script:
|
||||
- "tests/frontend/travis/runnerBackend.sh"
|
||||
- name: "Test the Frontend"
|
||||
before_script:
|
||||
- "tests/frontend/travis/sauce_tunnel.sh"
|
||||
install:
|
||||
- "npm install"
|
||||
- "mkdir [plugin_name]"
|
||||
- "mv !([plugin_name]) [plugin_name]"
|
||||
- "git clone https://github.com/ether/etherpad-lite.git etherpad"
|
||||
- "cd etherpad"
|
||||
- "mkdir -p node_modules"
|
||||
- "mv ../[plugin_name] node_modules"
|
||||
- "bin/installDeps.sh"
|
||||
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
|
||||
script:
|
||||
- "tests/frontend/travis/runner.sh"
|
||||
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#etherpad-lite-dev"
|
||||
|
||||
##ETHERPAD_TRAVIS_V=9
|
||||
## Travis configuration automatically created using bin/plugins/updateAllPluginsScript.sh
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
This is a repair tool. It rebuilds an old pad at a new pad location up to a
|
||||
known "good" revision.
|
||||
*/
|
||||
|
||||
if (process.argv.length != 4 && process.argv.length != 5) {
|
||||
console.error('Use: node bin/repairPad.js $PADID $REV [$NEWPADID]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const npm = require('../src/node_modules/npm');
|
||||
const async = require('../src/node_modules/async');
|
||||
const ueberDB = require('../src/node_modules/ueberdb2');
|
||||
|
||||
const padId = process.argv[2];
|
||||
const newRevHead = process.argv[3];
|
||||
const newPadId = process.argv[4] || `${padId}-rebuilt`;
|
||||
|
||||
let db, oldPad, newPad, settings;
|
||||
let AuthorManager, ChangeSet, Pad, PadManager;
|
||||
|
||||
async.series([
|
||||
function (callback) {
|
||||
npm.load({}, (err) => {
|
||||
if (err) {
|
||||
console.error(`Could not load NPM: ${err}`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
// Get a handle into the database
|
||||
db = require('../src/node/db/DB');
|
||||
db.init(callback);
|
||||
},
|
||||
function (callback) {
|
||||
PadManager = require('../src/node/db/PadManager');
|
||||
Pad = require('../src/node/db/Pad').Pad;
|
||||
// Get references to the original pad and to a newly created pad
|
||||
// HACK: This is a standalone script, so we want to write everything
|
||||
// out to the database immediately. The only problem with this is
|
||||
// that a driver (like the mysql driver) can hardcode these values.
|
||||
db.db.db.settings = {cache: 0, writeInterval: 0, json: true};
|
||||
// Validate the newPadId if specified and that a pad with that ID does
|
||||
// not already exist to avoid overwriting it.
|
||||
if (!PadManager.isValidPadId(newPadId)) {
|
||||
console.error('Cannot create a pad with that id as it is invalid');
|
||||
process.exit(1);
|
||||
}
|
||||
PadManager.doesPadExists(newPadId, (err, exists) => {
|
||||
if (exists) {
|
||||
console.error('Cannot create a pad with that id as it already exists');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
PadManager.getPad(padId, (err, pad) => {
|
||||
oldPad = pad;
|
||||
newPad = new Pad(newPadId);
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
// Clone all Chat revisions
|
||||
const chatHead = oldPad.chatHead;
|
||||
for (var i = 0, curHeadNum = 0; i <= chatHead; i++) {
|
||||
db.db.get(`pad:${padId}:chat:${i}`, (err, chat) => {
|
||||
db.db.set(`pad:${newPadId}:chat:${curHeadNum++}`, chat);
|
||||
console.log(`Created: Chat Revision: pad:${newPadId}:chat:${curHeadNum}`);
|
||||
});
|
||||
}
|
||||
callback();
|
||||
},
|
||||
function (callback) {
|
||||
// Rebuild Pad from revisions up to and including the new revision head
|
||||
AuthorManager = require('../src/node/db/AuthorManager');
|
||||
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
|
||||
newPad.pool.numToAttrib = oldPad.pool.numToAttrib;
|
||||
for (let curRevNum = 0; curRevNum <= newRevHead; curRevNum++) {
|
||||
db.db.get(`pad:${padId}:revs:${curRevNum}`, (err, rev) => {
|
||||
if (rev.meta) {
|
||||
throw 'The specified revision number could not be found.';
|
||||
}
|
||||
const newRevNum = ++newPad.head;
|
||||
const newRevId = `pad:${newPad.id}:revs:${newRevNum}`;
|
||||
db.db.set(newRevId, rev);
|
||||
AuthorManager.addPad(rev.meta.author, newPad.id);
|
||||
newPad.atext = Changeset.applyToAText(rev.changeset, newPad.atext, newPad.pool);
|
||||
console.log(`Created: Revision: pad:${newPad.id}:revs:${newRevNum}`);
|
||||
if (newRevNum == newRevHead) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
function (callback) {
|
||||
// Add saved revisions up to the new revision head
|
||||
console.log(newPad.head);
|
||||
const newSavedRevisions = [];
|
||||
for (const i in oldPad.savedRevisions) {
|
||||
savedRev = oldPad.savedRevisions[i];
|
||||
if (savedRev.revNum <= newRevHead) {
|
||||
newSavedRevisions.push(savedRev);
|
||||
console.log(`Added: Saved Revision: ${savedRev.revNum}`);
|
||||
}
|
||||
}
|
||||
newPad.savedRevisions = newSavedRevisions;
|
||||
callback();
|
||||
},
|
||||
function (callback) {
|
||||
// Save the source pad
|
||||
db.db.set(`pad:${newPadId}`, newPad, (err) => {
|
||||
console.log(`Created: Source Pad: pad:${newPadId}`);
|
||||
newPad.saveToDatabase().then(() => callback(), callback);
|
||||
});
|
||||
},
|
||||
], (err) => {
|
||||
if (err) { throw err; } else {
|
||||
console.info('finished');
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
|
@ -1,67 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const child_process = require('child_process');
|
||||
const semver = require('../src/node_modules/semver');
|
||||
|
||||
/*
|
||||
|
||||
Usage
|
||||
|
||||
node bin/release.js patch
|
||||
|
||||
*/
|
||||
const usage = 'node bin/release.js [patch/minor/major] -- example: "node bin/release.js patch"';
|
||||
|
||||
const release = process.argv[2];
|
||||
|
||||
if(!release) {
|
||||
console.log(usage);
|
||||
throw new Error('No release type included');
|
||||
}
|
||||
|
||||
const changelog = fs.readFileSync('CHANGELOG.md', {encoding: 'utf8', flag: 'r'});
|
||||
let packageJson = fs.readFileSync('./src/package.json', {encoding: 'utf8', flag: 'r'});
|
||||
packageJson = JSON.parse(packageJson);
|
||||
const currentVersion = packageJson.version;
|
||||
|
||||
const newVersion = semver.inc(currentVersion, release);
|
||||
if(!newVersion) {
|
||||
console.log(usage);
|
||||
throw new Error('Unable to generate new version from input');
|
||||
}
|
||||
|
||||
const changelogIncludesVersion = changelog.indexOf(newVersion) !== -1;
|
||||
|
||||
if(!changelogIncludesVersion) {
|
||||
throw new Error('No changelog record for ', newVersion, ' - please create changelog record');
|
||||
}
|
||||
|
||||
console.log('Okay looks good, lets create the package.json and package-lock.json');
|
||||
|
||||
packageJson.version = newVersion;
|
||||
|
||||
fs.writeFileSync('src/package.json', JSON.stringify(packageJson, null, 2));
|
||||
|
||||
// run npm version `release` where release is patch, minor or major
|
||||
child_process.execSync('npm install --package-lock-only', {cwd: `src/`});
|
||||
// run npm install --package-lock-only <-- required???
|
||||
|
||||
child_process.execSync(`git checkout -b release/${newVersion}`);
|
||||
child_process.execSync(`git add src/package.json`);
|
||||
child_process.execSync(`git add src/package-lock.json`);
|
||||
child_process.execSync(`git commit -m 'bump version'`);
|
||||
child_process.execSync(`git push origin release/${newVersion}`);
|
||||
|
||||
|
||||
child_process.execSync(`make docs`);
|
||||
child_process.execSync(`git clone git@github.com:ether/ether.github.com.git`);
|
||||
child_process.execSync(`cp -R out/doc/ ether.github.com/doc/${newVersion}`);
|
||||
|
||||
console.log('Once merged into master please run the following commands');
|
||||
console.log(`git tag -a ${newVersion} -m ${newVersion} && git push origin master`);
|
||||
console.log(`cd ether.github.com && git add . && git commit -m '${newVersion} docs'`);
|
||||
console.log(`Build the windows zip`)
|
||||
console.log(`Visit https://github.com/ether/etherpad-lite/releases/new and create a new release with 'master' as the target and the version is ${newVersion}. Include the windows zip as an assett`)
|
||||
console.log('Once the new docs are uploaded then modify the download link on etherpad.org and then pull master onto develop');
|
||||
console.log('Finally go public with an announcement via our comms channels :)');
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* This is a repair tool. It extracts all datas of a pad, removes and inserts them again.
|
||||
*/
|
||||
|
||||
console.warn('WARNING: This script must not be used while etherpad is running!');
|
||||
|
||||
if (process.argv.length != 3) {
|
||||
console.error('Use: node bin/repairPad.js $PADID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
const npm = require('../src/node_modules/npm');
|
||||
npm.load({}, async (er) => {
|
||||
if (er) {
|
||||
console.error(`Could not load NPM: ${er}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// intialize database
|
||||
const settings = require('../src/node/utils/Settings');
|
||||
const db = require('../src/node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// get the pad
|
||||
const padManager = require('../src/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:'));
|
||||
|
||||
// add all revisions
|
||||
for (let rev = 0; rev <= pad.head; ++rev) {
|
||||
neededDBValues.push(`pad:${padId}:revs:${rev}`);
|
||||
}
|
||||
|
||||
// add all chat values
|
||||
for (let chat = 0; chat <= pad.chatHead; ++chat) {
|
||||
neededDBValues.push(`pad:${padId}:chat:${chat}`);
|
||||
}
|
||||
|
||||
//
|
||||
// NB: this script doesn't actually does what's documented
|
||||
// since the `value` fields in the following `.forEach`
|
||||
// block are just the array index numbers
|
||||
//
|
||||
// the script therefore craps out now before it can do
|
||||
// any damage.
|
||||
//
|
||||
// See gitlab issue #3545
|
||||
//
|
||||
console.info('aborting [gitlab #3545]');
|
||||
process.exit(1);
|
||||
|
||||
// now fetch and reinsert every key
|
||||
neededDBValues.forEach((key, value) => {
|
||||
console.log(`Key: ${key}, value: ${value}`);
|
||||
db.remove(key);
|
||||
db.set(key, value);
|
||||
});
|
||||
|
||||
console.info('finished');
|
||||
process.exit(0);
|
||||
} catch (er) {
|
||||
if (er.name === 'apierror') {
|
||||
console.error(er);
|
||||
} else {
|
||||
console.trace(er);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -3,10 +3,10 @@ You can easily embed your etherpad-lite into any webpage by using iframes. You c
|
|||
|
||||
Example:
|
||||
|
||||
Cut and paste the following code into any webpage to embed a pad. The parameters below will hide the chat and the line numbers.
|
||||
Cut and paste the following code into any webpage to embed a pad. The parameters below will hide the chat and the line numbers and will auto-focus on Line 4.
|
||||
|
||||
```
|
||||
<iframe src='http://pad.test.de/p/PAD_NAME?showChat=false&showLineNumbers=false' width=600 height=400></iframe>
|
||||
<iframe src='http://pad.test.de/p/PAD_NAME#L4?showChat=false&showLineNumbers=false' width=600 height=400></iframe>
|
||||
```
|
||||
|
||||
## showLineNumbers
|
||||
|
@ -66,3 +66,10 @@ Example: `lang=ar` (translates the interface into Arabic)
|
|||
Default: true
|
||||
Displays pad text from right to left.
|
||||
|
||||
## #L
|
||||
* Int
|
||||
|
||||
Default: 0
|
||||
Focuses pad at specific line number and places caret at beginning of this line
|
||||
Special note: Is not a URL parameter but instead of a Hash value
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Client-side hooks
|
||||
Most of these hooks are called during or in order to set up the formatting process.
|
||||
|
||||
Most of these hooks are called during or in order to set up the formatting
|
||||
process.
|
||||
|
||||
## documentReady
|
||||
Called from: src/templates/pad.html
|
||||
|
@ -11,6 +13,7 @@ nothing
|
|||
This hook proxies the functionality of jQuery's `$(document).ready` event.
|
||||
|
||||
## aceDomLinePreProcessLineAttributes
|
||||
|
||||
Called from: src/static/js/domline.js
|
||||
|
||||
Things in context:
|
||||
|
@ -18,15 +21,21 @@ Things in context:
|
|||
1. domline - The current DOM line being processed
|
||||
2. cls - The class of the current block element (useful for styling)
|
||||
|
||||
This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. This hook is run BEFORE the numbered and ordered lists logic is applied.
|
||||
This hook is called for elements in the DOM that have the "lineMarkerAttribute"
|
||||
set. You can add elements into this category with the aceRegisterBlockElements
|
||||
hook above. This hook is run BEFORE the numbered and ordered lists logic is
|
||||
applied.
|
||||
|
||||
The return value of this hook should have the following structure:
|
||||
|
||||
`{ preHtml: String, postHtml: String, processedMarker: Boolean }`
|
||||
|
||||
The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more.
|
||||
The preHtml and postHtml values will be added to the HTML display of the
|
||||
element, and if processedMarker is true, the engine won't try to process it any
|
||||
more.
|
||||
|
||||
## aceDomLineProcessLineAttributes
|
||||
|
||||
Called from: src/static/js/domline.js
|
||||
|
||||
Things in context:
|
||||
|
@ -34,15 +43,21 @@ Things in context:
|
|||
1. domline - The current DOM line being processed
|
||||
2. cls - The class of the current block element (useful for styling)
|
||||
|
||||
This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. This hook is run AFTER the ordered and numbered lists logic is applied.
|
||||
This hook is called for elements in the DOM that have the "lineMarkerAttribute"
|
||||
set. You can add elements into this category with the aceRegisterBlockElements
|
||||
hook above. This hook is run AFTER the ordered and numbered lists logic is
|
||||
applied.
|
||||
|
||||
The return value of this hook should have the following structure:
|
||||
|
||||
`{ preHtml: String, postHtml: String, processedMarker: Boolean }`
|
||||
|
||||
The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more.
|
||||
The preHtml and postHtml values will be added to the HTML display of the
|
||||
element, and if processedMarker is true, the engine won't try to process it any
|
||||
more.
|
||||
|
||||
## aceCreateDomLine
|
||||
|
||||
Called from: src/static/js/domline.js
|
||||
|
||||
Things in context:
|
||||
|
@ -50,43 +65,55 @@ Things in context:
|
|||
1. domline - the current DOM line being processed
|
||||
2. cls - The class of the current element (useful for styling)
|
||||
|
||||
This hook is called for any line being processed by the formatting engine, unless the aceDomLineProcessLineAttributes hook from above returned true, in which case this hook is skipped.
|
||||
This hook is called for any line being processed by the formatting engine,
|
||||
unless the aceDomLineProcessLineAttributes hook from above returned true, in
|
||||
which case this hook is skipped.
|
||||
|
||||
The return value of this hook should have the following structure:
|
||||
|
||||
`{ extraOpenTags: String, extraCloseTags: String, cls: String }`
|
||||
|
||||
extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward.
|
||||
extraOpenTags and extraCloseTags will be added before and after the element in
|
||||
question, and cls will be the new class of the element going forward.
|
||||
|
||||
## acePostWriteDomLineHTML
|
||||
|
||||
Called from: src/static/js/domline.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. node - the DOM node that just got written to the page
|
||||
|
||||
This hook is for right after a node has been fully formatted and written to the page.
|
||||
This hook is for right after a node has been fully formatted and written to the
|
||||
page.
|
||||
|
||||
## aceAttribsToClasses
|
||||
|
||||
Called from: src/static/js/linestylefilter.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. linestylefilter - the JavaScript object that's currently processing the ace attributes
|
||||
1. linestylefilter - the JavaScript object that's currently processing the ace
|
||||
attributes
|
||||
2. key - the current attribute being processed
|
||||
3. value - the value of the attribute being processed
|
||||
|
||||
This hook is called during the attribute processing procedure, and should be used to translate key, value pairs into valid HTML classes that can be inserted into the DOM.
|
||||
This hook is called during the attribute processing procedure, and should be
|
||||
used to translate key, value pairs into valid HTML classes that can be inserted
|
||||
into the DOM.
|
||||
|
||||
The return value for this function should be a list of classes, which will then be parsed into a valid class string.
|
||||
The return value for this function should be a list of classes, which will then
|
||||
be parsed into a valid class string.
|
||||
|
||||
## aceAttribClasses
|
||||
|
||||
Called from: src/static/js/linestylefilter.js
|
||||
|
||||
Things in context:
|
||||
1. Attributes - Object of Attributes
|
||||
|
||||
This hook is called when attributes are investigated on a line. It is useful if you want to add another attribute type or property type to a pad.
|
||||
This hook is called when attributes are investigated on a line. It is useful if
|
||||
you want to add another attribute type or property type to a pad.
|
||||
|
||||
Example:
|
||||
```
|
||||
|
@ -97,32 +124,45 @@ exports.aceAttribClasses = function(hook_name, attr, cb){
|
|||
```
|
||||
|
||||
## aceGetFilterStack
|
||||
|
||||
Called from: src/static/js/linestylefilter.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. linestylefilter - the JavaScript object that's currently processing the ace attributes
|
||||
1. linestylefilter - the JavaScript object that's currently processing the ace
|
||||
attributes
|
||||
2. browser - an object indicating which browser is accessing the page
|
||||
|
||||
This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale `[[ ]]` syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above).
|
||||
This hook is called to apply custom regular expression filters to a set of
|
||||
styles. The one example available is the ep_linkify plugin, which adds internal
|
||||
links. They use it to find the telltale `[[ ]]` syntax that signifies internal
|
||||
links, and finding that syntax, they add in the internalHref attribute to be
|
||||
later used by the aceCreateDomLine hook (documented above).
|
||||
|
||||
## aceEditorCSS
|
||||
|
||||
Called from: src/static/js/ace.js
|
||||
|
||||
Things in context: None
|
||||
|
||||
This hook is provided to allow custom CSS files to be loaded. The return value should be an array of resource urls or paths relative to the plugins directory.
|
||||
This hook is provided to allow custom CSS files to be loaded. The return value
|
||||
should be an array of resource urls or paths relative to the plugins directory.
|
||||
|
||||
## aceInitInnerdocbodyHead
|
||||
|
||||
Called from: src/static/js/ace.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. iframeHTML - the HTML of the editor iframe up to this point, in array format
|
||||
|
||||
This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the `<head>` element of the editor HTML document.
|
||||
This hook is called during the creation of the editor HTML. The array should
|
||||
have lines of HTML added to it, giving the plugin author a chance to add in
|
||||
meta, script, link, and other tags that go into the `<head>` element of the
|
||||
editor HTML document.
|
||||
|
||||
## aceEditEvent
|
||||
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context:
|
||||
|
@ -130,16 +170,25 @@ Things in context:
|
|||
1. callstack - a bunch of information about the current action
|
||||
2. editorInfo - information about the user who is making the change
|
||||
3. rep - information about where the change is being made
|
||||
4. documentAttributeManager - information about attributes in the document (this is a mystery to me)
|
||||
4. documentAttributeManager - information about attributes in the document (this
|
||||
is a mystery to me)
|
||||
|
||||
This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event.
|
||||
This hook is made available to edit the edit events that might occur when
|
||||
changes are made. Currently you can change the editor information, some of the
|
||||
meanings of the edit, and so on. You can also make internal changes (internal to
|
||||
your plugin) that use the information provided by the edit event.
|
||||
|
||||
## aceRegisterNonScrollableEditEvents
|
||||
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context: None
|
||||
|
||||
When aceEditEvent (documented above) finishes processing the event, it scrolls the viewport to make caret visible to the user, but if you don't want that behavior to happen you can use this hook to register which edit events should not scroll viewport. The return value of this hook should be a list of event names.
|
||||
When aceEditEvent (documented above) finishes processing the event, it scrolls
|
||||
the viewport to make caret visible to the user, but if you don't want that
|
||||
behavior to happen you can use this hook to register which edit events should
|
||||
not scroll viewport. The return value of this hook should be a list of event
|
||||
names.
|
||||
|
||||
Example:
|
||||
```
|
||||
|
@ -149,24 +198,32 @@ exports.aceRegisterNonScrollableEditEvents = function(){
|
|||
```
|
||||
|
||||
## aceRegisterBlockElements
|
||||
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context: None
|
||||
|
||||
The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements.
|
||||
The return value of this hook will add elements into the "lineMarkerAttribute"
|
||||
category, making the aceDomLineProcessLineAttributes hook (documented below)
|
||||
call for those elements.
|
||||
|
||||
## aceInitialized
|
||||
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. editorInfo - information about the user who will be making changes through the interface, and a way to insert functions into the main ace object (see ep_headings)
|
||||
1. editorInfo - information about the user who will be making changes through
|
||||
the interface, and a way to insert functions into the main ace object (see
|
||||
ep_headings)
|
||||
2. rep - information about where the user's cursor is
|
||||
3. documentAttributeManager - some kind of magic
|
||||
|
||||
This hook is for inserting further information into the ace engine, for later use in formatting hooks.
|
||||
This hook is for inserting further information into the ace engine, for later
|
||||
use in formatting hooks.
|
||||
|
||||
## postAceInit
|
||||
|
||||
Called from: src/static/js/pad.js
|
||||
|
||||
Things in context:
|
||||
|
@ -175,6 +232,7 @@ Things in context:
|
|||
2. pad - the pad object of the current pad.
|
||||
|
||||
## postToolbarInit
|
||||
|
||||
Called from: src/static/js/pad_editbar.js
|
||||
|
||||
Things in context:
|
||||
|
@ -189,30 +247,37 @@ Usage examples:
|
|||
* [https://github.com/tiblu/ep_authorship_toggle]()
|
||||
|
||||
## postTimesliderInit
|
||||
|
||||
Called from: src/static/js/timeslider.js
|
||||
|
||||
There doesn't appear to be any example available of this particular hook being used, but it gets fired after the timeslider is all set up.
|
||||
There doesn't appear to be any example available of this particular hook being
|
||||
used, but it gets fired after the timeslider is all set up.
|
||||
|
||||
## goToRevisionEvent
|
||||
|
||||
Called from: src/static/js/broadcast.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. rev - The newRevision
|
||||
|
||||
This hook gets fired both on timeslider load (as timeslider shows a new revision) and when the new revision is showed to a user.
|
||||
There doesn't appear to be any example available of this particular hook being used.
|
||||
This hook gets fired both on timeslider load (as timeslider shows a new
|
||||
revision) and when the new revision is showed to a user. There doesn't appear to
|
||||
be any example available of this particular hook being used.
|
||||
|
||||
## userJoinOrUpdate
|
||||
|
||||
Called from: src/static/js/pad_userlist.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. info - the user information
|
||||
|
||||
This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list.
|
||||
This hook is called on the client side whenever a user joins or changes. This
|
||||
can be used to create notifications or an alternate user list.
|
||||
|
||||
## chatNewMessage
|
||||
|
||||
Called from: src/static/js/chat.js
|
||||
|
||||
Things in context:
|
||||
|
@ -220,14 +285,18 @@ Things in context:
|
|||
1. authorName - The user that wrote this message
|
||||
2. author - The authorID of the user that wrote the message
|
||||
3. text - the message text
|
||||
4. sticky (boolean) - if you want the gritter notification bubble to fade out on its own or just sit there
|
||||
4. sticky (boolean) - if you want the gritter notification bubble to fade out on
|
||||
its own or just sit there
|
||||
5. timestamp - the timestamp of the chat message
|
||||
6. timeStr - the timestamp as a formatted string
|
||||
7. duration - for how long in milliseconds should the gritter notification appear (0 to disable)
|
||||
7. duration - for how long in milliseconds should the gritter notification
|
||||
appear (0 to disable)
|
||||
|
||||
This hook is called on the client side whenever a chat message is received from the server. It can be used to create different notifications for chat messages.
|
||||
This hook is called on the client side whenever a chat message is received from
|
||||
the server. It can be used to create different notifications for chat messages.
|
||||
|
||||
## collectContentPre
|
||||
|
||||
Called from: src/static/js/contentcollector.js
|
||||
|
||||
Things in context:
|
||||
|
@ -238,16 +307,20 @@ Things in context:
|
|||
4. styl - the style applied to the node (probably CSS) -- Note the typo
|
||||
5. cls - the HTML class string of the node
|
||||
|
||||
This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original.
|
||||
This hook is called before the content of a node is collected by the usual
|
||||
methods. The cc object can be used to do a bunch of things that modify the
|
||||
content of the pad. See, for example, the heading1 plugin for etherpad original.
|
||||
|
||||
E.g. if you need to apply an attribute to newly inserted characters,
|
||||
call cc.doAttrib(state, "attributeName") which results in an attribute attributeName=true.
|
||||
E.g. if you need to apply an attribute to newly inserted characters, call
|
||||
cc.doAttrib(state, "attributeName") which results in an attribute
|
||||
attributeName=true.
|
||||
|
||||
If you want to specify also a value, call cc.doAttrib(state, "attributeName::value")
|
||||
which results in an attribute attributeName=value.
|
||||
If you want to specify also a value, call cc.doAttrib(state,
|
||||
"attributeName::value") which results in an attribute attributeName=value.
|
||||
|
||||
|
||||
## collectContentImage
|
||||
|
||||
Called from: src/static/js/contentcollector.js
|
||||
|
||||
Things in context:
|
||||
|
@ -259,7 +332,9 @@ Things in context:
|
|||
5. cls - the HTML class string of the node
|
||||
6. node - the node being modified
|
||||
|
||||
This hook is called before the content of an image node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad.
|
||||
This hook is called before the content of an image node is collected by the
|
||||
usual methods. The cc object can be used to do a bunch of things that modify the
|
||||
content of the pad.
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -271,6 +346,7 @@ exports.collectContentImage = function(name, context){
|
|||
```
|
||||
|
||||
## collectContentPost
|
||||
|
||||
Called from: src/static/js/contentcollector.js
|
||||
|
||||
Things in context:
|
||||
|
@ -281,20 +357,29 @@ Things in context:
|
|||
4. style - the style applied to the node (probably CSS)
|
||||
5. cls - the HTML class string of the node
|
||||
|
||||
This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original.
|
||||
This hook is called after the content of a node is collected by the usual
|
||||
methods. The cc object can be used to do a bunch of things that modify the
|
||||
content of the pad. See, for example, the heading1 plugin for etherpad original.
|
||||
|
||||
## handleClientMessage_`name`
|
||||
|
||||
Called from: `src/static/js/collab_client.js`
|
||||
|
||||
Things in context:
|
||||
|
||||
1. payload - the data that got sent with the message (use it for custom message content)
|
||||
1. payload - the data that got sent with the message (use it for custom message
|
||||
content)
|
||||
|
||||
This hook gets called every time the client receives a message of type `name`. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types.
|
||||
This hook gets called every time the client receives a message of type `name`.
|
||||
This can most notably be used with the new HTTP API call, "sendClientsMessage",
|
||||
which sends a custom message type to all clients connected to a pad. You can
|
||||
also use this to handle existing types.
|
||||
|
||||
`collab_client.js` has a pretty extensive list of message types, if you want to take a look.
|
||||
`collab_client.js` has a pretty extensive list of message types, if you want to
|
||||
take a look.
|
||||
|
||||
## aceStartLineAndCharForPoint-aceEndLineAndCharForPoint
|
||||
|
||||
##aceStartLineAndCharForPoint-aceEndLineAndCharForPoint
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context:
|
||||
|
@ -306,10 +391,11 @@ Things in context:
|
|||
5. point - the starting/ending element where the cursor highlights
|
||||
6. documentAttributeManager - information about attributes in the document
|
||||
|
||||
This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection.
|
||||
The return value should be an array of [line,char]
|
||||
This hook is provided to allow a plugin to turn DOM node selection into
|
||||
[line,char] selection. The return value should be an array of [line,char]
|
||||
|
||||
## aceKeyEvent
|
||||
|
||||
##aceKeyEvent
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context:
|
||||
|
@ -323,7 +409,8 @@ Things in context:
|
|||
This hook is provided to allow a plugin to handle key events.
|
||||
The return value should be true if you have handled the event.
|
||||
|
||||
##collectContentLineText
|
||||
## collectContentLineText
|
||||
|
||||
Called from: src/static/js/contentcollector.js
|
||||
|
||||
Things in context:
|
||||
|
@ -333,10 +420,24 @@ Things in context:
|
|||
3. tname - the tag name of this node currently being processed
|
||||
4. text - the text for that line
|
||||
|
||||
This hook allows you to validate/manipulate the text before it's sent to the server side.
|
||||
The return value should be the validated/manipulated text.
|
||||
This hook allows you to validate/manipulate the text before it's sent to the
|
||||
server side. To change the text, either:
|
||||
|
||||
* Set the `text` context property to the desired value and return `undefined`.
|
||||
* (Deprecated) Return a string. If a hook function changes the `text` context
|
||||
property, the return value is ignored. If no hook function changes `text` but
|
||||
multiple hook functions return a string, the first one wins.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
exports.collectContentLineText = (hookName, context) => {
|
||||
context.text = tweakText(context.text);
|
||||
};
|
||||
```
|
||||
|
||||
## collectContentLineBreak
|
||||
|
||||
##collectContentLineBreak
|
||||
Called from: src/static/js/contentcollector.js
|
||||
|
||||
Things in context:
|
||||
|
@ -345,24 +446,27 @@ Things in context:
|
|||
2. state - the current state of the change being made
|
||||
3. tname - the tag name of this node currently being processed
|
||||
|
||||
This hook is provided to allow whether the br tag should induce a new magic domline or not.
|
||||
The return value should be either true(break the line) or false.
|
||||
This hook is provided to allow whether the br tag should induce a new magic
|
||||
domline or not. The return value should be either true(break the line) or false.
|
||||
|
||||
## disableAuthorColorsForThisLine
|
||||
|
||||
##disableAuthorColorsForThisLine
|
||||
Called from: src/static/js/linestylefilter.js
|
||||
|
||||
Things in context:
|
||||
|
||||
1. linestylefilter - the JavaScript object that's currently processing the ace attributes
|
||||
1. linestylefilter - the JavaScript object that's currently processing the ace
|
||||
attributes
|
||||
2. text - the line text
|
||||
3. class - line class
|
||||
|
||||
This hook is provided to allow whether a given line should be deliniated with multiple authors.
|
||||
Multiple authors in one line cause the creation of magic span lines. This might not suit you and
|
||||
now you can disable it and handle your own deliniation.
|
||||
The return value should be either true(disable) or false.
|
||||
This hook is provided to allow whether a given line should be deliniated with
|
||||
multiple authors. Multiple authors in one line cause the creation of magic span
|
||||
lines. This might not suit you and now you can disable it and handle your own
|
||||
deliniation. The return value should be either true(disable) or false.
|
||||
|
||||
## aceSetAuthorStyle
|
||||
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context:
|
||||
|
@ -374,10 +478,12 @@ Things in context:
|
|||
5. author - author info
|
||||
6. authorSelector - css selector for author span in inner ace
|
||||
|
||||
This hook is provided to allow author highlight style to be modified.
|
||||
Registered hooks should return 1 if the plugin handles highlighting. If no plugin returns 1, the core will use the default background-based highlighting.
|
||||
This hook is provided to allow author highlight style to be modified. Registered
|
||||
hooks should return 1 if the plugin handles highlighting. If no plugin returns
|
||||
1, the core will use the default background-based highlighting.
|
||||
|
||||
## aceSelectionChanged
|
||||
|
||||
Called from: src/static/js/ace2_inner.js
|
||||
|
||||
Things in context:
|
||||
|
|
|
@ -263,7 +263,7 @@ deletes a session
|
|||
#### getSessionInfo(sessionID)
|
||||
* API >= 1
|
||||
|
||||
returns informations about a session
|
||||
returns information about a session
|
||||
|
||||
*Example returns:*
|
||||
* `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif", groupID: g.s8oes9dhwrvt0zif, validUntil: 1312201246}}`
|
||||
|
|
|
@ -168,6 +168,8 @@ For the editor container, you can also make it full width by adding `full-width-
|
|||
| `IMPORT_MAX_FILE_SIZE` | maximum allowed file size when importing a pad, in bytes. | `52428800` (50 MB) |
|
||||
| `IMPORT_EXPORT_MAX_REQ_PER_IP` | maximum number of import/export calls per IP. | `10` |
|
||||
| `IMPORT_EXPORT_RATE_LIMIT_WINDOW` | the call rate for import/export requests will be estimated in this time window (in milliseconds) | `90000` |
|
||||
| `COMMIT_RATE_LIMIT_DURATION` | duration of the rate limit window for commits by individual users/IPs (in seconds) | `1` |
|
||||
| `COMMIT_RATE_LIMIT_POINTS` | maximum number of changes per IP to allow during the rate limit window | `10` |
|
||||
| `SUPPRESS_ERRORS_IN_PAD_TEXT` | Should we suppress errors from being visible in the default Pad Text? | `false` |
|
||||
| `REQUIRE_SESSION` | If this option is enabled, a user must have a session to access pads. This effectively allows only group pads to be accessed. | `false` |
|
||||
| `EDIT_ONLY` | Users may edit pads but not create new ones. Pad creation is only via the API. This applies both to group pads and regular pads. | `false` |
|
||||
|
|
|
@ -11,5 +11,5 @@ heading.
|
|||
|
||||
Every `.html` file is generated based on the corresponding
|
||||
`.md` file in the `doc/api/` folder in the source tree. The
|
||||
documentation is generated using the `bin/doc/generate.js` program.
|
||||
documentation is generated using the `src/bin/doc/generate.js` program.
|
||||
The HTML template is located at `doc/template.html`.
|
||||
|
|
|
@ -95,7 +95,7 @@ For example, if you want to replace `Chat` with `Notes`, simply add...
|
|||
|
||||
## Customization for Administrators
|
||||
|
||||
As an Etherpad administrator, it is possible to overwrite core mesages as well as messages in plugins. These include error messages, labels, and user instructions. Whereas the localization in the source code is in separate files separated by locale, an administrator's custom localizations are in `settings.json` under the `customLocaleStrings` key, with each locale separated by a sub-key underneath.
|
||||
As an Etherpad administrator, it is possible to overwrite core messages as well as messages in plugins. These include error messages, labels, and user instructions. Whereas the localization in the source code is in separate files separated by locale, an administrator's custom localizations are in `settings.json` under the `customLocaleStrings` key, with each locale separated by a sub-key underneath.
|
||||
|
||||
For example, let's say you want to change the text on the "New Pad" button on Etherpad's home page. If you look in `locales/en.json` (or `locales/en-gb.json`) you'll see the key for this text is `"index.newPad"`. You could add the following to `settings.json`:
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ publish your plugin.
|
|||
"author": "USERNAME (REAL NAME) <MAIL@EXAMPLE.COM>",
|
||||
"contributors": [],
|
||||
"dependencies": {"MODULE": "0.3.20"},
|
||||
"engines": { "node": ">= 10.13.0"}
|
||||
"engines": { "node": "^10.17.0 || >=11.14.0"}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
10704
package-lock.json
generated
10704
package-lock.json
generated
File diff suppressed because it is too large
Load diff
105
package.json
105
package.json
|
@ -1,105 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"ep_etherpad-lite": "file:src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.15.0",
|
||||
"eslint-config-etherpad": "^1.0.20",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-mocha": "^8.0.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prefer-arrow": "^1.2.2",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"ignorePatterns": [
|
||||
"/src/",
|
||||
"/tests/frontend/lib/"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/.eslintrc.js"
|
||||
],
|
||||
"extends": "etherpad/node"
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"**/.eslintrc.js",
|
||||
"tests/frontend/**/*"
|
||||
],
|
||||
"extends": "etherpad/node"
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"tests/**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"**/.eslintrc.js"
|
||||
],
|
||||
"extends": "etherpad/tests",
|
||||
"rules": {
|
||||
"mocha/no-exports": "off",
|
||||
"mocha/no-top-level-hooks": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"tests/backend/**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"**/.eslintrc.js"
|
||||
],
|
||||
"extends": "etherpad/tests/backend",
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"tests/backend/**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"tests/backend/specs/**/*"
|
||||
],
|
||||
"rules": {
|
||||
"mocha/no-exports": "off",
|
||||
"mocha/no-top-level-hooks": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"tests/frontend/**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"**/.eslintrc.js"
|
||||
],
|
||||
"extends": "etherpad/tests/frontend",
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"tests/frontend/**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"tests/frontend/specs/**/*"
|
||||
],
|
||||
"rules": {
|
||||
"mocha/no-exports": "off",
|
||||
"mocha/no-top-level-hooks": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"root": true
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
}
|
|
@ -171,7 +171,7 @@
|
|||
*
|
||||
*
|
||||
* Database specific settings are dependent on dbType, and go in dbSettings.
|
||||
* Remember that since Etherpad 1.6.0 you can also store these informations in
|
||||
* Remember that since Etherpad 1.6.0 you can also store this information in
|
||||
* credentials.json.
|
||||
*
|
||||
* For a complete list of the supported drivers, please refer to:
|
||||
|
@ -445,6 +445,17 @@
|
|||
*/
|
||||
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
|
||||
|
||||
"socketIo": {
|
||||
/*
|
||||
* Maximum permitted client message size (in bytes). All messages from
|
||||
* clients that are larger than this will be rejected. Large values make it
|
||||
* possible to paste large amounts of text, and plugins may require a larger
|
||||
* value to work properly, but increasing the value increases susceptibility
|
||||
* to denial of service attacks (malicious clients can exhaust memory).
|
||||
*/
|
||||
"maxHttpBufferSize": 10000
|
||||
},
|
||||
|
||||
/*
|
||||
* Allow Load Testing tools to hit the Etherpad Instance.
|
||||
*
|
||||
|
@ -486,6 +497,22 @@
|
|||
*/
|
||||
"importMaxFileSize": "${IMPORT_MAX_FILE_SIZE:52428800}", // 50 * 1024 * 1024
|
||||
|
||||
/*
|
||||
* From Etherpad 1.8.5 onwards, when Etherpad is in production mode commits from individual users are rate limited
|
||||
*
|
||||
* The default is to allow at most 10 changes per IP in a 1 second window.
|
||||
* After that the change is rejected.
|
||||
*
|
||||
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
|
||||
*/
|
||||
"commitRateLimiting": {
|
||||
// duration of the rate limit window (seconds)
|
||||
"duration": "${COMMIT_RATE_LIMIT_DURATION:1}",
|
||||
|
||||
// maximum number of changes per IP to allow during the rate limit window
|
||||
"points": "${COMMIT_RATE_LIMIT_POINTS:10}"
|
||||
},
|
||||
|
||||
/*
|
||||
* Toolbar buttons configuration.
|
||||
*
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
*
|
||||
*
|
||||
* Database specific settings are dependent on dbType, and go in dbSettings.
|
||||
* Remember that since Etherpad 1.6.0 you can also store these informations in
|
||||
* Remember that since Etherpad 1.6.0 you can also store this information in
|
||||
* credentials.json.
|
||||
*
|
||||
* For a complete list of the supported drivers, please refer to:
|
||||
|
@ -450,6 +450,17 @@
|
|||
*/
|
||||
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
|
||||
|
||||
"socketIo": {
|
||||
/*
|
||||
* Maximum permitted client message size (in bytes). All messages from
|
||||
* clients that are larger than this will be rejected. Large values make it
|
||||
* possible to paste large amounts of text, and plugins may require a larger
|
||||
* value to work properly, but increasing the value increases susceptibility
|
||||
* to denial of service attacks (malicious clients can exhaust memory).
|
||||
*/
|
||||
"maxHttpBufferSize": 10000
|
||||
},
|
||||
|
||||
/*
|
||||
* Allow Load Testing tools to hit the Etherpad Instance.
|
||||
*
|
||||
|
@ -492,7 +503,7 @@
|
|||
"importMaxFileSize": 52428800, // 50 * 1024 * 1024
|
||||
|
||||
/*
|
||||
* From Etherpad 1.9.0 onwards, when Etherpad is in production mode commits from individual users are rate limited
|
||||
* From Etherpad 1.8.5 onwards, when Etherpad is in production mode commits from individual users are rate limited
|
||||
*
|
||||
* The default is to allow at most 10 changes per IP in a 1 second window.
|
||||
* After that the change is rejected.
|
||||
|
@ -503,7 +514,7 @@
|
|||
// duration of the rate limit window (seconds)
|
||||
"duration": 1,
|
||||
|
||||
// maximum number of chanes per IP to allow during the rate limit window
|
||||
// maximum number of changes per IP to allow during the rate limit window
|
||||
"points": 10
|
||||
},
|
||||
|
||||
|
@ -600,5 +611,8 @@
|
|||
}, // logconfig
|
||||
|
||||
/* Override any strings found in locale directories */
|
||||
"customLocaleStrings": {}
|
||||
"customLocaleStrings": {},
|
||||
|
||||
/* Disable Admin UI tests */
|
||||
"enableAdminUITests": false
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ rm -rf ${DIST}
|
|||
mkdir -p ${DIST}/
|
||||
|
||||
rm -rf ${SRC}
|
||||
rsync -a bin/deb-src/ ${SRC}/
|
||||
rsync -a src/bin/deb-src/ ${SRC}/
|
||||
mkdir -p ${SYSROOT}/opt/
|
||||
|
||||
rsync --exclude '.git' -a . ${SYSROOT}/opt/etherpad/ --delete
|
|
@ -32,7 +32,7 @@ rm -f etherpad-lite-win.zip
|
|||
export NODE_ENV=production
|
||||
|
||||
log "do a normal unix install first..."
|
||||
bin/installDeps.sh || exit 1
|
||||
src/bin/installDeps.sh || exit 1
|
||||
|
||||
log "copy the windows settings template..."
|
||||
cp settings.json.template settings.json
|
||||
|
@ -43,8 +43,7 @@ rm -rf node_modules
|
|||
mv node_modules_resolved node_modules
|
||||
|
||||
log "download windows node..."
|
||||
cd bin
|
||||
wget "https://nodejs.org/dist/latest-erbium/win-x86/node.exe" -O ../node.exe
|
||||
wget "https://nodejs.org/dist/latest-erbium/win-x86/node.exe" -O node.exe
|
||||
|
||||
log "remove git history to reduce folder size"
|
||||
rm -rf .git/objects
|
88
src/bin/checkAllPads.js
Normal file
88
src/bin/checkAllPads.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
/*
|
||||
* This is a debug tool. It checks all revisions for data corruption
|
||||
*/
|
||||
|
||||
// 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');
|
||||
|
||||
(async () => {
|
||||
// initialize the database
|
||||
require('../node/utils/Settings');
|
||||
const db = require('../node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load modules
|
||||
const Changeset = require('../static/js/Changeset');
|
||||
const padManager = require('../node/db/PadManager');
|
||||
|
||||
let revTestedCount = 0;
|
||||
|
||||
// get all pads
|
||||
const res = await padManager.listAllPads();
|
||||
for (const padId of res.padIDs) {
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// check if the pad has a pool
|
||||
if (pad.pool == null) {
|
||||
console.error(`[${pad.id}] Missing attribute pool`);
|
||||
continue;
|
||||
}
|
||||
// create an array with key kevisions
|
||||
// key revisions always save the full pad atext
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
const keyRevisions = [];
|
||||
for (let rev = 0; rev < head; rev += 100) {
|
||||
keyRevisions.push(rev);
|
||||
}
|
||||
|
||||
// run through all key revisions
|
||||
for (const keyRev of keyRevisions) {
|
||||
// create an array of revisions we need till the next keyRevision or the End
|
||||
const revisionsNeeded = [];
|
||||
for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
revisionsNeeded.push(rev);
|
||||
}
|
||||
|
||||
// this array will hold all revision changesets
|
||||
const revisions = [];
|
||||
|
||||
// run through all needed revisions and get them from the database
|
||||
for (const revNum of revisionsNeeded) {
|
||||
const revision = await db.get(`pad:${pad.id}:revs:${revNum}`);
|
||||
revisions[revNum] = revision;
|
||||
}
|
||||
|
||||
// check if the revision exists
|
||||
if (revisions[keyRev] == null) {
|
||||
console.error(`[${pad.id}] Missing revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if there is a atext in the keyRevisions
|
||||
let {meta: {atext} = {}} = revisions[keyRev];
|
||||
if (atext == null) {
|
||||
console.error(`[${pad.id}] Missing atext in revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const apool = pad.pool;
|
||||
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
try {
|
||||
const cs = revisions[rev].changeset;
|
||||
atext = Changeset.applyToAText(cs, atext, apool);
|
||||
revTestedCount++;
|
||||
} catch (e) {
|
||||
console.error(`[${pad.id}] Bad changeset at revision ${rev} - ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (revTestedCount === 0) {
|
||||
throw new Error('No revisions tested');
|
||||
}
|
||||
console.log(`Finished: Tested ${revTestedCount} revisions`);
|
||||
})();
|
82
src/bin/checkPad.js
Normal file
82
src/bin/checkPad.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
'use strict';
|
||||
/*
|
||||
* This is a debug tool. It checks all revisions for data corruption
|
||||
*/
|
||||
|
||||
// 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');
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
let checkRevisionCount = 0;
|
||||
|
||||
(async () => {
|
||||
// initialize database
|
||||
require('../node/utils/Settings');
|
||||
const db = require('../node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load modules
|
||||
const Changeset = require('../static/js/Changeset');
|
||||
const padManager = require('../node/db/PadManager');
|
||||
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) throw new Error('Pad does not exist');
|
||||
|
||||
// get the pad
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// create an array with key revisions
|
||||
// key revisions always save the full pad atext
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
const keyRevisions = [];
|
||||
for (let rev = 0; rev < head; rev += 100) {
|
||||
keyRevisions.push(rev);
|
||||
}
|
||||
|
||||
// run through all key revisions
|
||||
for (let keyRev of keyRevisions) {
|
||||
keyRev = parseInt(keyRev);
|
||||
// create an array of revisions we need till the next keyRevision or the End
|
||||
const revisionsNeeded = [];
|
||||
for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
revisionsNeeded.push(rev);
|
||||
}
|
||||
|
||||
// this array will hold all revision changesets
|
||||
const revisions = [];
|
||||
|
||||
// run through all needed revisions and get them from the database
|
||||
for (const revNum of revisionsNeeded) {
|
||||
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
|
||||
revisions[revNum] = revision;
|
||||
}
|
||||
|
||||
// check if the pad has a pool
|
||||
if (pad.pool == null) throw new Error('Attribute pool is missing');
|
||||
|
||||
// check if there is an atext in the keyRevisions
|
||||
let {meta: {atext} = {}} = revisions[keyRev] || {};
|
||||
if (atext == null) {
|
||||
console.error(`No atext in key revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const apool = pad.pool;
|
||||
|
||||
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
checkRevisionCount++;
|
||||
try {
|
||||
const cs = revisions[rev].changeset;
|
||||
atext = Changeset.applyToAText(cs, atext, apool);
|
||||
} catch (e) {
|
||||
console.error(`Bad changeset at revision ${rev} - ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
console.log(`Finished: Checked ${checkRevisionCount} revisions`);
|
||||
}
|
||||
})();
|
103
src/bin/checkPadDeltas.js
Normal file
103
src/bin/checkPadDeltas.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
'use strict';
|
||||
/*
|
||||
* This is a debug tool. It checks all revisions for data corruption
|
||||
*/
|
||||
|
||||
// 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/checkPadDeltas.js $PADID');
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
const expect = require('../tests/frontend/lib/expect');
|
||||
const diff = require('diff');
|
||||
|
||||
(async () => {
|
||||
// initialize database
|
||||
require('../node/utils/Settings');
|
||||
const db = require('../node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load modules
|
||||
const Changeset = require('../static/js/Changeset');
|
||||
const padManager = require('../node/db/PadManager');
|
||||
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) throw new Error('Pad does not exist');
|
||||
|
||||
// get the pad
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// create an array with key revisions
|
||||
// key revisions always save the full pad atext
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
const keyRevisions = [];
|
||||
for (let i = 0; i < head; i += 100) {
|
||||
keyRevisions.push(i);
|
||||
}
|
||||
|
||||
// create an array with all revisions
|
||||
const revisions = [];
|
||||
for (let i = 0; i <= head; i++) {
|
||||
revisions.push(i);
|
||||
}
|
||||
|
||||
let atext = Changeset.makeAText('\n');
|
||||
|
||||
// run through all revisions
|
||||
for (const revNum of revisions) {
|
||||
// console.log('Fetching', revNum)
|
||||
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
|
||||
// check if there is a atext in the keyRevisions
|
||||
const {meta: {atext: revAtext} = {}} = revision || {};
|
||||
if (~keyRevisions.indexOf(revNum) && revAtext == null) {
|
||||
console.error(`No atext in key revision ${revNum}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// try glue everything together
|
||||
try {
|
||||
// console.log("check revision ", revNum);
|
||||
const cs = revision.changeset;
|
||||
atext = Changeset.applyToAText(cs, atext, pad.pool);
|
||||
} catch (e) {
|
||||
console.error(`Bad changeset at revision ${revNum} - ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check things are working properly
|
||||
if (~keyRevisions.indexOf(revNum)) {
|
||||
try {
|
||||
expect(revision.meta.atext.text).to.eql(atext.text);
|
||||
expect(revision.meta.atext.attribs).to.eql(atext.attribs);
|
||||
} catch (e) {
|
||||
console.error(`Atext in key revision ${revNum} doesn't match computed one.`);
|
||||
console.log(diff.diffChars(atext.text, revision.meta.atext.text).map((op) => {
|
||||
if (!op.added && !op.removed) op.value = op.value.length;
|
||||
return op;
|
||||
}));
|
||||
// console.error(e)
|
||||
// console.log('KeyRev. :', revision.meta.atext)
|
||||
// console.log('Computed:', atext)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check final text is right...
|
||||
if (pad.atext.text === atext.text) {
|
||||
console.log('ok');
|
||||
} else {
|
||||
console.error('Pad AText doesn\'t match computed one! (Computed ',
|
||||
atext.text.length, ', db', pad.atext.text.length, ')');
|
||||
console.log(diff.diffChars(atext.text, pad.atext.text).map((op) => {
|
||||
if (!op.added && !op.removed) {
|
||||
op.value = op.value.length;
|
||||
return op;
|
||||
}
|
||||
}));
|
||||
}
|
||||
})();
|
|
@ -1,15 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
# Move to the Etherpad base directory.
|
||||
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
|
||||
cd "${MY_DIR}/../.." || exit 1
|
||||
|
||||
# Source constants and usefull functions
|
||||
. bin/functions.sh
|
||||
|
||||
#Was this script started in the bin folder? if yes move out
|
||||
if [ -d "../bin" ]; then
|
||||
cd "../"
|
||||
fi
|
||||
# Source constants and useful functions
|
||||
. src/bin/functions.sh
|
||||
|
||||
ignoreRoot=0
|
||||
for ARG in "$@"
|
||||
|
@ -35,7 +31,7 @@ fi
|
|||
rm -rf src/node_modules
|
||||
|
||||
#Prepare the environment
|
||||
bin/installDeps.sh "$@" || exit 1
|
||||
src/bin/installDeps.sh "$@" || exit 1
|
||||
|
||||
#Move to the node folder and start
|
||||
echo "Started Etherpad..."
|
|
@ -134,7 +134,7 @@ function create_builds {
|
|||
git clone $ETHER_WEB_REPO
|
||||
echo "Creating windows build..."
|
||||
cd etherpad-lite
|
||||
bin/buildForWindows.sh
|
||||
src/bin/buildForWindows.sh
|
||||
[[ $? != 0 ]] && echo "Aborting: Error creating build for windows" && exit 1
|
||||
echo "Creating docs..."
|
||||
make docs
|
|
@ -1,20 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
* A tool for generating a test user session which can be used for debugging configs
|
||||
* that require sessions.
|
||||
*/
|
||||
const m = (f) => `${__dirname}/../${f}`;
|
||||
|
||||
// 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 request = require(m('src/node_modules/request'));
|
||||
const settings = require(m('src/node/utils/Settings'));
|
||||
const supertest = require(m('src/node_modules/supertest'));
|
||||
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 filePath = path.join(__dirname, '../../APIKEY.txt');
|
||||
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
|
||||
|
||||
let res;
|
|
@ -15,7 +15,7 @@ pre-start script
|
|||
chown $EPUSER $EPLOGS ||true
|
||||
chmod 0755 $EPLOGS ||true
|
||||
chown -R $EPUSER $EPHOME/var ||true
|
||||
$EPHOME/bin/installDeps.sh >> $EPLOGS/error.log || { stop; exit 1; }
|
||||
$EPHOME/src/bin/installDeps.sh >> $EPLOGS/error.log || { stop; exit 1; }
|
||||
end script
|
||||
|
||||
script
|
|
@ -1,13 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
# Move to the Etherpad base directory.
|
||||
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
|
||||
cd "${MY_DIR}/../.." || exit 1
|
||||
|
||||
# Source constants and usefull functions
|
||||
. bin/functions.sh
|
||||
# Source constants and useful functions
|
||||
. src/bin/functions.sh
|
||||
|
||||
# Prepare the environment
|
||||
bin/installDeps.sh || exit 1
|
||||
src/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"
|
47
src/bin/deleteAllGroupSessions.js
Normal file
47
src/bin/deleteAllGroupSessions.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
'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.
|
||||
*/
|
||||
|
||||
// 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 path = require('path');
|
||||
const fs = require('fs');
|
||||
const supertest = require('supertest');
|
||||
|
||||
// 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');
|
||||
console.log('Deleting all group sessions, please be patient.');
|
||||
|
||||
(async () => {
|
||||
const settings = require('../tests/container/loadSettings').loadSettings();
|
||||
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
|
||||
const apiVersionResponse = await api.get('/api/');
|
||||
const apiVersion = apiVersionResponse.body.currentVersion; // 1.12.5
|
||||
|
||||
const groupsResponse = await api.get(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
|
||||
const groups = groupsResponse.body.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;
|
||||
|
||||
for (const sessionID of Object.keys(sessions)) {
|
||||
const deleteURI = `/api/${apiVersion}/deleteSession?apikey=${apikey}&sessionID=${sessionID}`;
|
||||
await api.post(deleteURI); // delete
|
||||
deleteCount++;
|
||||
}
|
||||
}
|
||||
console.log(`Deleted ${deleteCount} sessions`);
|
||||
})();
|
38
src/bin/deletePad.js
Normal file
38
src/bin/deletePad.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
* A tool for deleting pads from the CLI, because sometimes a brick is required
|
||||
* to fix a window.
|
||||
*/
|
||||
|
||||
// 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 settings = require('../tests/container/loadSettings').loadSettings();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const supertest = require('supertest');
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
|
||||
if (process.argv.length !== 3) throw new Error('Use: node deletePad.js $PADID');
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
// get the API Key
|
||||
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;
|
||||
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);
|
||||
})();
|
|
@ -72,5 +72,5 @@ Each type of heading has a description block.
|
|||
|
||||
Run the following from the etherpad-lite root directory:
|
||||
```sh
|
||||
$ node bin/doc/generate doc/index.md --format=html --template=doc/template.html > out.html
|
||||
```
|
||||
$ node src/bin/doc/generate doc/index.md --format=html --template=doc/template.html > out.html
|
||||
```
|
|
@ -1,4 +1,7 @@
|
|||
#!/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
|
||||
|
@ -20,7 +23,6 @@
|
|||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
const marked = require('marked');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
|
@ -33,12 +35,12 @@ let template = null;
|
|||
let inputFile = null;
|
||||
|
||||
args.forEach((arg) => {
|
||||
if (!arg.match(/^\-\-/)) {
|
||||
if (!arg.match(/^--/)) {
|
||||
inputFile = arg;
|
||||
} else if (arg.match(/^\-\-format=/)) {
|
||||
format = arg.replace(/^\-\-format=/, '');
|
||||
} else if (arg.match(/^\-\-template=/)) {
|
||||
template = arg.replace(/^\-\-template=/, '');
|
||||
} else if (arg.match(/^--format=/)) {
|
||||
format = arg.replace(/^--format=/, '');
|
||||
} else if (arg.match(/^--template=/)) {
|
||||
template = arg.replace(/^--template=/, '');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -56,11 +58,11 @@ fs.readFile(inputFile, 'utf8', (er, input) => {
|
|||
});
|
||||
|
||||
|
||||
const includeExpr = /^@include\s+([A-Za-z0-9-_\/]+)(?:\.)?([a-zA-Z]*)$/gmi;
|
||||
const includeExpr = /^@include\s+([A-Za-z0-9-_/]+)(?:\.)?([a-zA-Z]*)$/gmi;
|
||||
const includeData = {};
|
||||
function processIncludes(inputFile, input, cb) {
|
||||
const processIncludes = (inputFile, input, cb) => {
|
||||
const includes = input.match(includeExpr);
|
||||
if (includes === null) return cb(null, input);
|
||||
if (includes == null) return cb(null, input);
|
||||
let errState = null;
|
||||
console.error(includes);
|
||||
let incCount = includes.length;
|
||||
|
@ -70,7 +72,7 @@ function processIncludes(inputFile, input, cb) {
|
|||
let fname = include.replace(/^@include\s+/, '');
|
||||
if (!fname.match(/\.md$/)) fname += '.md';
|
||||
|
||||
if (includeData.hasOwnProperty(fname)) {
|
||||
if (Object.prototype.hasOwnProperty.call(includeData, fname)) {
|
||||
input = input.split(include).join(includeData[fname]);
|
||||
incCount--;
|
||||
if (incCount === 0) {
|
||||
|
@ -94,10 +96,10 @@ function processIncludes(inputFile, input, cb) {
|
|||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function next(er, input) {
|
||||
const next = (er, input) => {
|
||||
if (er) throw er;
|
||||
switch (format) {
|
||||
case 'json':
|
||||
|
@ -117,4 +119,4 @@ function next(er, input) {
|
|||
default:
|
||||
throw new Error(`Invalid format: ${format}`);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
|
@ -23,17 +25,17 @@ const fs = require('fs');
|
|||
const marked = require('marked');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = toHTML;
|
||||
|
||||
function toHTML(input, filename, template, cb) {
|
||||
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;
|
||||
|
||||
function render(lexed, filename, template, cb) {
|
||||
const render = (lexed, filename, template, cb) => {
|
||||
// get the section
|
||||
const section = getSection(lexed);
|
||||
|
||||
|
@ -52,23 +54,23 @@ function render(lexed, filename, template, cb) {
|
|||
|
||||
// content has to be the last thing we do with
|
||||
// the lexed tokens, because it's destructive.
|
||||
content = marked.parser(lexed);
|
||||
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.
|
||||
function parseLists(input) {
|
||||
const parseLists = (input) => {
|
||||
let state = null;
|
||||
let depth = 0;
|
||||
const output = [];
|
||||
output.links = input.links;
|
||||
input.forEach((tok) => {
|
||||
if (state === null) {
|
||||
if (state == null) {
|
||||
if (tok.type === 'heading') {
|
||||
state = 'AFTERHEADING';
|
||||
}
|
||||
|
@ -112,29 +114,27 @@ function parseLists(input) {
|
|||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function parseListItem(text) {
|
||||
text = text.replace(/\{([^\}]+)\}/, '<span class="type">$1</span>');
|
||||
const parseListItem = (text) => {
|
||||
text = text.replace(/\{([^}]+)\}/, '<span class="type">$1</span>');
|
||||
// XXX maybe put more stuff here?
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// section is just the first heading
|
||||
function getSection(lexed) {
|
||||
const section = '';
|
||||
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 '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function buildToc(lexed, filename, cb) {
|
||||
const indent = 0;
|
||||
const buildToc = (lexed, filename, cb) => {
|
||||
let toc = [];
|
||||
let depth = 0;
|
||||
lexed.forEach((tok) => {
|
||||
|
@ -155,18 +155,18 @@ function buildToc(lexed, filename, cb) {
|
|||
|
||||
toc = marked.parse(toc.join('\n'));
|
||||
cb(null, toc);
|
||||
}
|
||||
};
|
||||
|
||||
const idCounters = {};
|
||||
function getId(text) {
|
||||
const getId = (text) => {
|
||||
text = text.toLowerCase();
|
||||
text = text.replace(/[^a-z0-9]+/g, '_');
|
||||
text = text.replace(/^_+|_+$/, '');
|
||||
text = text.replace(/^([^a-z])/, '_$1');
|
||||
if (idCounters.hasOwnProperty(text)) {
|
||||
if (Object.prototype.hasOwnProperty.call(idCounters, text)) {
|
||||
text += `_${++idCounters[text]}`;
|
||||
} else {
|
||||
idCounters[text] = 0;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
'use strict';
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
|
@ -26,7 +27,7 @@ module.exports = doJSON;
|
|||
|
||||
const marked = require('marked');
|
||||
|
||||
function doJSON(input, filename, cb) {
|
||||
const doJSON = (input, filename, cb) => {
|
||||
const root = {source: filename};
|
||||
const stack = [root];
|
||||
let depth = 0;
|
||||
|
@ -40,7 +41,7 @@ function doJSON(input, filename, cb) {
|
|||
// <!-- type = module -->
|
||||
// This is for cases where the markdown semantic structure is lacking.
|
||||
if (type === 'paragraph' || type === 'html') {
|
||||
const metaExpr = /<!--([^=]+)=([^\-]+)-->\n*/g;
|
||||
const metaExpr = /<!--([^=]+)=([^-]+)-->\n*/g;
|
||||
text = text.replace(metaExpr, (_0, k, v) => {
|
||||
current[k.trim()] = v.trim();
|
||||
return '';
|
||||
|
@ -146,7 +147,7 @@ function doJSON(input, filename, cb) {
|
|||
}
|
||||
|
||||
return cb(null, root);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// go from something like this:
|
||||
|
@ -191,7 +192,7 @@ function doJSON(input, filename, cb) {
|
|||
// desc: 'whether or not to send output to parent\'s stdio.',
|
||||
// default: 'false' } ] } ]
|
||||
|
||||
function processList(section) {
|
||||
const processList = (section) => {
|
||||
const list = section.list;
|
||||
const values = [];
|
||||
let current;
|
||||
|
@ -203,13 +204,13 @@ function processList(section) {
|
|||
if (type === 'space') return;
|
||||
if (type === 'list_item_start') {
|
||||
if (!current) {
|
||||
var n = {};
|
||||
const n = {};
|
||||
values.push(n);
|
||||
current = n;
|
||||
} else {
|
||||
current.options = current.options || [];
|
||||
stack.push(current);
|
||||
var n = {};
|
||||
const n = {};
|
||||
current.options.push(n);
|
||||
current = n;
|
||||
}
|
||||
|
@ -247,11 +248,11 @@ function processList(section) {
|
|||
switch (section.type) {
|
||||
case 'ctor':
|
||||
case 'classMethod':
|
||||
case 'method':
|
||||
case 'method': {
|
||||
// each item is an argument, unless the name is 'return',
|
||||
// in which case it's the return value.
|
||||
section.signatures = section.signatures || [];
|
||||
var sig = {};
|
||||
const sig = {};
|
||||
section.signatures.push(sig);
|
||||
sig.params = values.filter((v) => {
|
||||
if (v.name === 'return') {
|
||||
|
@ -262,11 +263,11 @@ function processList(section) {
|
|||
});
|
||||
parseSignature(section.textRaw, sig);
|
||||
break;
|
||||
|
||||
case 'property':
|
||||
}
|
||||
case 'property': {
|
||||
// there should be only one item, which is the value.
|
||||
// copy the data up to the section.
|
||||
var value = values[0] || {};
|
||||
const value = values[0] || {};
|
||||
delete value.name;
|
||||
section.typeof = value.type;
|
||||
delete value.type;
|
||||
|
@ -274,20 +275,21 @@ function processList(section) {
|
|||
section[k] = value[k];
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'event':
|
||||
case 'event': {
|
||||
// event: each item is an argument.
|
||||
section.params = values;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// section.listParsed = values;
|
||||
delete section.list;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// textRaw = "someobject.someMethod(a, [b=100], [c])"
|
||||
function parseSignature(text, sig) {
|
||||
const parseSignature = (text, sig) => {
|
||||
let params = text.match(paramExpr);
|
||||
if (!params) return;
|
||||
params = params[1];
|
||||
|
@ -322,10 +324,10 @@ function parseSignature(text, sig) {
|
|||
if (optional) param.optional = true;
|
||||
if (def !== undefined) param.default = def;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function parseListItem(item) {
|
||||
const parseListItem = (item) => {
|
||||
if (item.options) item.options.forEach(parseListItem);
|
||||
if (!item.textRaw) return;
|
||||
|
||||
|
@ -341,7 +343,7 @@ function parseListItem(item) {
|
|||
item.name = 'return';
|
||||
text = text.replace(retExpr, '');
|
||||
} else {
|
||||
const nameExpr = /^['`"]?([^'`": \{]+)['`"]?\s*:?\s*/;
|
||||
const nameExpr = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
|
||||
const name = text.match(nameExpr);
|
||||
if (name) {
|
||||
item.name = name[1];
|
||||
|
@ -358,7 +360,7 @@ function parseListItem(item) {
|
|||
}
|
||||
|
||||
text = text.trim();
|
||||
const typeExpr = /^\{([^\}]+)\}/;
|
||||
const typeExpr = /^\{([^}]+)\}/;
|
||||
const type = text.match(typeExpr);
|
||||
if (type) {
|
||||
item.type = type[1];
|
||||
|
@ -376,10 +378,10 @@ function parseListItem(item) {
|
|||
text = text.replace(/^\s*-\s*/, '');
|
||||
text = text.trim();
|
||||
if (text) item.desc = text;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function finishSection(section, parent) {
|
||||
const finishSection = (section, parent) => {
|
||||
if (!section || !parent) {
|
||||
throw new Error(`Invalid finishSection call\n${
|
||||
JSON.stringify(section)}\n${
|
||||
|
@ -416,7 +418,7 @@ function finishSection(section, parent) {
|
|||
ctor.signatures.forEach((sig) => {
|
||||
sig.desc = ctor.desc;
|
||||
});
|
||||
sigs.push.apply(sigs, ctor.signatures);
|
||||
sigs.push(...ctor.signatures);
|
||||
});
|
||||
delete section.ctors;
|
||||
}
|
||||
|
@ -479,50 +481,50 @@ function finishSection(section, parent) {
|
|||
|
||||
parent[plur] = parent[plur] || [];
|
||||
parent[plur].push(section);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Not a general purpose deep copy.
|
||||
// But sufficient for these basic things.
|
||||
function deepCopy(src, dest) {
|
||||
Object.keys(src).filter((k) => !dest.hasOwnProperty(k)).forEach((k) => {
|
||||
const deepCopy = (src, dest) => {
|
||||
Object.keys(src).filter((k) => !Object.prototype.hasOwnProperty.call(dest, k)).forEach((k) => {
|
||||
dest[k] = deepCopy_(src[k]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function deepCopy_(src) {
|
||||
const deepCopy_ = (src) => {
|
||||
if (!src) return src;
|
||||
if (Array.isArray(src)) {
|
||||
var c = new Array(src.length);
|
||||
const c = new Array(src.length);
|
||||
src.forEach((v, i) => {
|
||||
c[i] = deepCopy_(v);
|
||||
});
|
||||
return c;
|
||||
}
|
||||
if (typeof src === 'object') {
|
||||
var c = {};
|
||||
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 propExpr = /^(?:property:?\s*)?[^.]+\.([^ .()]+)\s*?$/i;
|
||||
const braceExpr = /^(?:property:?\s*)?[^.[]+(\[[^\]]+\])\s*?$/i;
|
||||
const classMethExpr =
|
||||
/^class\s*method\s*:?[^\.]+\.([^ \.\(\)]+)\([^\)]*\)\s*?$/i;
|
||||
/^class\s*method\s*:?[^.]+\.([^ .()]+)\([^)]*\)\s*?$/i;
|
||||
const methExpr =
|
||||
/^(?:method:?\s*)?(?:[^\.]+\.)?([^ \.\(\)]+)\([^\)]*\)\s*?$/i;
|
||||
const newExpr = /^new ([A-Z][a-z]+)\([^\)]*\)\s*?$/;
|
||||
var paramExpr = /\((.*)\);?$/;
|
||||
/^(?:method:?\s*)?(?:[^.]+\.)?([^ .()]+)\([^)]*\)\s*?$/i;
|
||||
const newExpr = /^new ([A-Z][a-z]+)\([^)]*\)\s*?$/;
|
||||
const paramExpr = /\((.*)\);?$/;
|
||||
|
||||
function newSection(tok) {
|
||||
const newSection = (tok) => {
|
||||
const section = {};
|
||||
// infer the type from the text.
|
||||
const text = section.textRaw = tok.text;
|
||||
|
@ -551,4 +553,4 @@ function newSection(tok) {
|
|||
section.name = text;
|
||||
}
|
||||
return section;
|
||||
}
|
||||
};
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Internal tool for generating Node.js API docs",
|
||||
"version": "0.0.0",
|
||||
"engines": {
|
||||
"node": ">=0.6.10"
|
||||
"node": ">=10.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"marked": "0.8.2"
|
64
src/bin/extractPadData.js
Normal file
64
src/bin/extractPadData.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
* This is a debug tool. It helps to extract all datas of a pad and move it from
|
||||
* a productive environment and to a develop environment to reproduce bugs
|
||||
* there. It outputs a dirtydb file
|
||||
*/
|
||||
|
||||
// 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 extractPadData.js $PADID');
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
(async () => {
|
||||
// initialize database
|
||||
require('../node/utils/Settings');
|
||||
const db = require('../node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// load extra modules
|
||||
const dirtyDB = require('dirty');
|
||||
const padManager = require('../node/db/PadManager');
|
||||
|
||||
// initialize output database
|
||||
const dirty = dirtyDB(`${padId}.db`);
|
||||
|
||||
// Promise wrapped get and set function
|
||||
const wrapped = db.db.db.wrappedDB;
|
||||
const get = util.promisify(wrapped.get.bind(wrapped));
|
||||
const set = util.promisify(dirty.set.bind(dirty));
|
||||
|
||||
// array in which required key values will be accumulated
|
||||
const neededDBValues = [`pad:${padId}`];
|
||||
|
||||
// get the actual pad object
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// add all authors
|
||||
neededDBValues.push(...pad.getAllAuthors().map((author) => `globalAuthor:${author}`));
|
||||
|
||||
// add all revisions
|
||||
for (let rev = 0; rev <= pad.head; ++rev) {
|
||||
neededDBValues.push(`pad:${padId}:revs:${rev}`);
|
||||
}
|
||||
|
||||
// add all chat values
|
||||
for (let chat = 0; chat <= pad.chatHead; ++chat) {
|
||||
neededDBValues.push(`pad:${padId}:chat:${chat}`);
|
||||
}
|
||||
|
||||
for (const dbkey of neededDBValues) {
|
||||
let dbvalue = await get(dbkey);
|
||||
if (dbvalue && typeof dbvalue !== 'object') {
|
||||
dbvalue = JSON.parse(dbvalue);
|
||||
}
|
||||
await set(dbkey, dbvalue);
|
||||
}
|
||||
|
||||
console.log('finished');
|
||||
})();
|
22
src/bin/fastRun.sh
Executable file
22
src/bin/fastRun.sh
Executable file
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Run Etherpad directly, assuming all the dependencies are already installed.
|
||||
#
|
||||
# Useful for developers, or users that know what they are doing. If you just
|
||||
# upgraded Etherpad version, installed a new dependency, or are simply unsure
|
||||
# of what to do, please execute bin/installDeps.sh once before running this
|
||||
# script.
|
||||
|
||||
set -eu
|
||||
|
||||
# Move to the Etherpad base directory.
|
||||
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
|
||||
cd "${MY_DIR}/../.." || exit 1
|
||||
|
||||
# Source constants and useful functions
|
||||
. src/bin/functions.sh
|
||||
|
||||
echo "Running directly, without checking/installing dependencies"
|
||||
|
||||
# run Etherpad main class
|
||||
node $(compute_node_args) "node_modules/ep_etherpad-lite/node/server.js" "$@"
|
100
src/bin/importSqlFile.js
Normal file
100
src/bin/importSqlFile.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
'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 startTime = Date.now();
|
||||
|
||||
const log = (str) => {
|
||||
console.log(`${(Date.now() - startTime) / 1000}\t${str}`);
|
||||
};
|
||||
|
||||
const unescape = (val) => {
|
||||
// value is a string
|
||||
if (val.substr(0, 1) === "'") {
|
||||
val = val.substr(0, val.length - 1).substr(1);
|
||||
|
||||
return val.replace(/\\[0nrbtZ\\'"]/g, (s) => {
|
||||
switch (s) {
|
||||
case '\\0': return '\0';
|
||||
case '\\n': return '\n';
|
||||
case '\\r': return '\r';
|
||||
case '\\b': return '\b';
|
||||
case '\\t': return '\t';
|
||||
case '\\Z': return '\x1a';
|
||||
default: return s.substr(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// value is a boolean or NULL
|
||||
if (val === 'NULL') {
|
||||
return null;
|
||||
}
|
||||
if (val === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (val === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// value is a number
|
||||
return val;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const fs = require('fs');
|
||||
const log4js = require('log4js');
|
||||
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
|
||||
settings.dbType,
|
||||
settings.dbSettings,
|
||||
dbWrapperSettings,
|
||||
log4js.getLogger('ueberDB'));
|
||||
|
||||
const sqlFile = process.argv[2];
|
||||
|
||||
// stop if the settings file is not set
|
||||
if (!sqlFile) throw new Error('Use: node importSqlFile.js $SQLFILE');
|
||||
|
||||
log('initializing db');
|
||||
await util.promisify(db.init.bind(db))();
|
||||
log('done');
|
||||
|
||||
log('open output file...');
|
||||
const lines = fs.readFileSync(sqlFile, 'utf8').split('\n');
|
||||
|
||||
const count = lines.length;
|
||||
let keyNo = 0;
|
||||
|
||||
process.stdout.write(`Start importing ${count} keys...\n`);
|
||||
lines.forEach((l) => {
|
||||
if (l.substr(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);
|
||||
console.log(`key: ${key} val: ${value}`);
|
||||
console.log(`unval: ${unescape(value)}`);
|
||||
db.set(key, unescape(value), null);
|
||||
keyNo++;
|
||||
if (keyNo % 1000 === 0) {
|
||||
process.stdout.write(` ${keyNo}/${count}\n`);
|
||||
}
|
||||
}
|
||||
});
|
||||
process.stdout.write('\n');
|
||||
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))();
|
||||
log(`finished, imported ${keyNo} keys.`);
|
||||
})();
|
|
@ -1,10 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
# Move to the Etherpad base directory.
|
||||
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
|
||||
cd "${MY_DIR}/../.." || exit 1
|
||||
|
||||
# Source constants and usefull functions
|
||||
. bin/functions.sh
|
||||
# Source constants and useful functions
|
||||
. src/bin/functions.sh
|
||||
|
||||
# Is node installed?
|
||||
# Not checking io.js, default installation creates a symbolic link to node
|
||||
|
@ -39,7 +40,7 @@ log "Ensure that all dependencies are up to date... If this is the first time y
|
|||
cd node_modules
|
||||
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
|
||||
cd ep_etherpad-lite
|
||||
npm ci
|
||||
npm ci --no-optional
|
||||
) || {
|
||||
rm -rf src/node_modules
|
||||
exit 1
|
56
src/bin/migrateDirtyDBtoRealDB.js
Normal file
56
src/bin/migrateDirtyDBtoRealDB.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
'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; });
|
||||
|
||||
(async () => {
|
||||
// This script requires that you have modified your settings.json file
|
||||
// to work with a real database. Please make a backup of your dirty.db
|
||||
// file before using this script, just to be safe.
|
||||
|
||||
// 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
|
||||
settings.dbType,
|
||||
settings.dbSettings,
|
||||
dbWrapperSettings,
|
||||
log4js.getLogger('ueberDB'));
|
||||
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); });
|
||||
|
||||
console.log(`Found ${length} records, processing now.`);
|
||||
const p = [];
|
||||
let numWritten = 0;
|
||||
dirty.forEach((key, value) => {
|
||||
let bcb, wcb;
|
||||
p.push(new Promise((resolve, reject) => {
|
||||
bcb = (err) => { if (err != null) return reject(err); };
|
||||
wcb = (err) => {
|
||||
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))();
|
||||
console.log('Finished.');
|
||||
})();
|
|
@ -2,28 +2,33 @@ The files in this folder are for Plugin developers.
|
|||
|
||||
# Get suggestions to improve your Plugin
|
||||
|
||||
This code will check your plugin for known usual issues and some suggestions for improvements. No changes will be made to your project.
|
||||
This code will check your plugin for known usual issues and some suggestions for
|
||||
improvements. No changes will be made to your project.
|
||||
|
||||
```
|
||||
node bin/plugins/checkPlugin.js $PLUGIN_NAME$
|
||||
node src/bin/plugins/checkPlugin.js $PLUGIN_NAME$
|
||||
```
|
||||
|
||||
# Basic Example:
|
||||
|
||||
```
|
||||
node bin/plugins/checkPlugin.js ep_webrtc
|
||||
node src/bin/plugins/checkPlugin.js ep_webrtc
|
||||
```
|
||||
|
||||
## Autofixing - will autofix any issues it can
|
||||
|
||||
```
|
||||
node bin/plugins/checkPlugins.js ep_whatever autofix
|
||||
node src/bin/plugins/checkPlugin.js ep_whatever autofix
|
||||
```
|
||||
|
||||
## Autocommitting, push, npm minor patch and npm publish (highly dangerous)
|
||||
|
||||
```
|
||||
node bin/plugins/checkPlugins.js ep_whatever autofix autocommit
|
||||
node src/bin/plugins/checkPlugin.js ep_whatever autocommit
|
||||
```
|
||||
|
||||
# All the plugins
|
||||
|
||||
Replace johnmclear with your github username
|
||||
|
||||
```
|
||||
|
@ -33,19 +38,15 @@ GHUSER=johnmclear; curl "https://api.github.com/users/$GHUSER/repos?per_page=100
|
|||
cd ..
|
||||
|
||||
# autofixes and autocommits /pushes & npm publishes
|
||||
for dir in `ls node_modules`;
|
||||
do
|
||||
# echo $0
|
||||
if [[ $dir == *"ep_"* ]]; then
|
||||
if [[ $dir != "ep_etherpad-lite" ]]; then
|
||||
node bin/plugins/checkPlugin.js $dir autofix autocommit
|
||||
fi
|
||||
fi
|
||||
# echo $dir
|
||||
for dir in node_modules/ep_*; do
|
||||
dir=${dir#node_modules/}
|
||||
[ "$dir" != ep_etherpad-lite ] || continue
|
||||
node src/bin/plugins/checkPlugin.js "$dir" autocommit
|
||||
done
|
||||
```
|
||||
|
||||
# Automating update of ether organization plugins
|
||||
|
||||
```
|
||||
getCorePlugins.sh
|
||||
updateCorePlugins.sh
|
458
src/bin/plugins/checkPlugin.js
Executable file
458
src/bin/plugins/checkPlugin.js
Executable file
|
@ -0,0 +1,458 @@
|
|||
'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 commit, push and publish to npm (highly dangerous):
|
||||
* node src/bin/plugins/checkPlugin.js ep_whatever autocommit
|
||||
*/
|
||||
|
||||
// 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 childProcess = require('child_process');
|
||||
|
||||
// get plugin name & path from user input
|
||||
const pluginName = process.argv[2];
|
||||
|
||||
if (!pluginName) throw new Error('no plugin name specified');
|
||||
|
||||
const pluginPath = `node_modules/${pluginName}`;
|
||||
|
||||
console.log(`Checking the plugin: ${pluginName}`);
|
||||
|
||||
const optArgs = process.argv.slice(3);
|
||||
const autoCommit = optArgs.indexOf('autocommit') !== -1;
|
||||
const autoFix = autoCommit || optArgs.indexOf('autofix') !== -1;
|
||||
|
||||
const execSync = (cmd, opts = {}) => (childProcess.execSync(cmd, {
|
||||
cwd: `${pluginPath}/`,
|
||||
...opts,
|
||||
}) || '').toString().replace(/\n+$/, '');
|
||||
|
||||
const writePackageJson = (obj) => {
|
||||
let s = JSON.stringify(obj, null, 2);
|
||||
if (s.length && s.slice(s.length - 1) !== '\n') s += '\n';
|
||||
return fs.writeFileSync(`${pluginPath}/package.json`, s);
|
||||
};
|
||||
|
||||
const updateDeps = (parsedPackageJson, key, wantDeps) => {
|
||||
const {[key]: deps = {}} = parsedPackageJson;
|
||||
let changed = false;
|
||||
for (const [pkg, verInfo] of Object.entries(wantDeps)) {
|
||||
const {ver, overwrite = true} = typeof verInfo === 'string' ? {ver: verInfo} : verInfo;
|
||||
if (deps[pkg] === ver) continue;
|
||||
if (deps[pkg] == null) {
|
||||
console.warn(`Missing dependency in ${key}: '${pkg}': '${ver}'`);
|
||||
} else {
|
||||
if (!overwrite) continue;
|
||||
console.warn(`Dependency mismatch in ${key}: '${pkg}': '${ver}' (current: ${deps[pkg]})`);
|
||||
}
|
||||
if (autoFix) {
|
||||
deps[pkg] = ver;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
parsedPackageJson[key] = deps;
|
||||
writePackageJson(parsedPackageJson);
|
||||
}
|
||||
};
|
||||
|
||||
const prepareRepo = () => {
|
||||
let branch = execSync('git symbolic-ref HEAD');
|
||||
if (branch !== 'refs/heads/master' && branch !== 'refs/heads/main') {
|
||||
throw new Error('master/main must be checked out');
|
||||
}
|
||||
branch = branch.replace(/^refs\/heads\//, '');
|
||||
execSync('git rev-parse --verify -q HEAD^0 || ' +
|
||||
`{ echo "Error: no commits on ${branch}" >&2; exit 1; }`);
|
||||
execSync('git rev-parse --verify @{u}'); // Make sure there's a remote tracking branch.
|
||||
const modified = execSync('git diff-files --name-status');
|
||||
if (modified !== '') 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');
|
||||
if (indexStatus !== '') throw new Error(`uncommitted staged changes to files:\n${indexStatus}`);
|
||||
execSync('git pull --ff-only', {stdio: 'inherit'});
|
||||
if (execSync('git rev-list @{u}...') !== '') throw new Error('repo contains unpushed commits');
|
||||
if (autoCommit) {
|
||||
execSync('git config --get user.name');
|
||||
execSync('git config --get user.email');
|
||||
}
|
||||
};
|
||||
|
||||
if (autoCommit) {
|
||||
console.warn('Auto commit is enabled, I hope you know what you are doing...');
|
||||
}
|
||||
|
||||
fs.readdir(pluginPath, (err, rootFiles) => {
|
||||
// handling error
|
||||
if (err) {
|
||||
return console.log(`Unable to scan directory: ${err}`);
|
||||
}
|
||||
|
||||
// rewriting files to lower case
|
||||
const files = [];
|
||||
|
||||
// some files we need to know the actual file name. Not compulsory but might help in the future.
|
||||
let readMeFileName;
|
||||
let repository;
|
||||
|
||||
for (let i = 0; i < rootFiles.length; i++) {
|
||||
if (rootFiles[i].toLowerCase().indexOf('readme') !== -1) readMeFileName = rootFiles[i];
|
||||
files.push(rootFiles[i].toLowerCase());
|
||||
}
|
||||
|
||||
if (files.indexOf('.git') === -1) throw new Error('No .git folder, aborting');
|
||||
prepareRepo();
|
||||
|
||||
try {
|
||||
const path = `${pluginPath}/.github/workflows/npmpublish.yml`;
|
||||
if (!fs.existsSync(path)) {
|
||||
console.log('no .github/workflows/npmpublish.yml');
|
||||
console.log('create one and set npm secret to auto publish to npm on commit');
|
||||
if (autoFix) {
|
||||
const npmpublish =
|
||||
fs.readFileSync('src/bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, npmpublish);
|
||||
console.log("If you haven't already, setup autopublish for this plugin https://github.com/ether/etherpad-lite/wiki/Plugins:-Automatically-publishing-to-npm-on-commit-to-Github-Repo");
|
||||
} else {
|
||||
console.log('Setup autopublish for this plugin https://github.com/ether/etherpad-lite/wiki/Plugins:-Automatically-publishing-to-npm-on-commit-to-Github-Repo');
|
||||
}
|
||||
} else {
|
||||
// autopublish exists, we should check the version..
|
||||
// checkVersion takes two file paths and checks for a version string in them.
|
||||
const currVersionFile = fs.readFileSync(path, {encoding: 'utf8', flag: 'r'});
|
||||
const existingConfigLocation = currVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const existingValue = parseInt(
|
||||
currVersionFile.substr(existingConfigLocation + 17, existingConfigLocation.length));
|
||||
|
||||
const reqVersionFile =
|
||||
fs.readFileSync('src/bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
|
||||
const reqConfigLocation = reqVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const reqValue =
|
||||
parseInt(reqVersionFile.substr(reqConfigLocation + 17, reqConfigLocation.length));
|
||||
|
||||
if (!existingValue || (reqValue > existingValue)) {
|
||||
const npmpublish =
|
||||
fs.readFileSync('src/bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, npmpublish);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const path = `${pluginPath}/.github/workflows/backend-tests.yml`;
|
||||
if (!fs.existsSync(path)) {
|
||||
console.log('no .github/workflows/backend-tests.yml');
|
||||
console.log('create one and set npm secret to auto publish to npm on commit');
|
||||
if (autoFix) {
|
||||
const backendTests =
|
||||
fs.readFileSync('src/bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, backendTests);
|
||||
}
|
||||
} else {
|
||||
// autopublish exists, we should check the version..
|
||||
// checkVersion takes two file paths and checks for a version string in them.
|
||||
const currVersionFile = fs.readFileSync(path, {encoding: 'utf8', flag: 'r'});
|
||||
const existingConfigLocation = currVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const existingValue = parseInt(
|
||||
currVersionFile.substr(existingConfigLocation + 17, existingConfigLocation.length));
|
||||
|
||||
const reqVersionFile =
|
||||
fs.readFileSync('src/bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
|
||||
const reqConfigLocation = reqVersionFile.indexOf('##ETHERPAD_NPM_V=');
|
||||
const reqValue =
|
||||
parseInt(reqVersionFile.substr(reqConfigLocation + 17, reqConfigLocation.length));
|
||||
|
||||
if (!existingValue || (reqValue > existingValue)) {
|
||||
const backendTests =
|
||||
fs.readFileSync('src/bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
|
||||
fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
|
||||
fs.writeFileSync(path, backendTests);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (files.indexOf('package.json') === -1) {
|
||||
console.warn('no package.json, please create');
|
||||
}
|
||||
|
||||
if (files.indexOf('package.json') !== -1) {
|
||||
const packageJSON =
|
||||
fs.readFileSync(`${pluginPath}/package.json`, {encoding: 'utf8', flag: 'r'});
|
||||
const parsedPackageJSON = JSON.parse(packageJSON);
|
||||
if (autoFix) {
|
||||
let updatedPackageJSON = false;
|
||||
if (!parsedPackageJSON.funding) {
|
||||
updatedPackageJSON = true;
|
||||
parsedPackageJSON.funding = {
|
||||
type: 'individual',
|
||||
url: 'https://etherpad.org/',
|
||||
};
|
||||
}
|
||||
if (updatedPackageJSON) {
|
||||
writePackageJson(parsedPackageJSON);
|
||||
}
|
||||
}
|
||||
|
||||
if (packageJSON.toLowerCase().indexOf('repository') === -1) {
|
||||
console.warn('No repository in package.json');
|
||||
if (autoFix) {
|
||||
console.warn('Repository not detected in package.json. Add repository section.');
|
||||
}
|
||||
} else {
|
||||
// useful for creating README later.
|
||||
repository = parsedPackageJSON.repository.url;
|
||||
}
|
||||
|
||||
updateDeps(parsedPackageJSON, 'devDependencies', {
|
||||
'eslint': '^7.18.0',
|
||||
'eslint-config-etherpad': '^1.0.24',
|
||||
'eslint-plugin-eslint-comments': '^3.2.0',
|
||||
'eslint-plugin-mocha': '^8.0.0',
|
||||
'eslint-plugin-node': '^11.1.0',
|
||||
'eslint-plugin-prefer-arrow': '^1.2.3',
|
||||
'eslint-plugin-promise': '^4.2.1',
|
||||
'eslint-plugin-you-dont-need-lodash-underscore': '^6.10.0',
|
||||
});
|
||||
|
||||
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},
|
||||
});
|
||||
|
||||
if (packageJSON.toLowerCase().indexOf('eslintconfig') === -1) {
|
||||
console.warn('No esLintConfig in package.json');
|
||||
if (autoFix) {
|
||||
const eslintConfig = {
|
||||
root: true,
|
||||
extends: 'etherpad/plugin',
|
||||
};
|
||||
parsedPackageJSON.eslintConfig = eslintConfig;
|
||||
writePackageJson(parsedPackageJSON);
|
||||
}
|
||||
}
|
||||
|
||||
if (packageJSON.toLowerCase().indexOf('scripts') === -1) {
|
||||
console.warn('No scripts in package.json');
|
||||
if (autoFix) {
|
||||
const scripts = {
|
||||
'lint': 'eslint .',
|
||||
'lint:fix': 'eslint --fix .',
|
||||
};
|
||||
parsedPackageJSON.scripts = scripts;
|
||||
writePackageJson(parsedPackageJSON);
|
||||
}
|
||||
}
|
||||
|
||||
if ((packageJSON.toLowerCase().indexOf('engines') === -1) || !parsedPackageJSON.engines.node) {
|
||||
console.warn('No engines or node engine in package.json');
|
||||
if (autoFix) {
|
||||
const engines = {
|
||||
node: '^10.17.0 || >=11.14.0',
|
||||
};
|
||||
parsedPackageJSON.engines = engines;
|
||||
writePackageJson(parsedPackageJSON);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('package-lock.json') === -1) {
|
||||
console.warn('package-lock.json not found');
|
||||
if (!autoFix) {
|
||||
console.warn('Run npm install in the plugin folder and commit the package-lock.json file.');
|
||||
}
|
||||
}
|
||||
if (files.indexOf('readme') === -1 && files.indexOf('readme.md') === -1) {
|
||||
console.warn('README.md file not found, please create');
|
||||
if (autoFix) {
|
||||
console.log('Autofixing missing README.md file');
|
||||
console.log('please edit the README.md file further to include plugin specific details.');
|
||||
let readme = fs.readFileSync('src/bin/plugins/lib/README.md', {encoding: 'utf8', flag: 'r'});
|
||||
readme = readme.replace(/\[plugin_name\]/g, pluginName);
|
||||
if (repository) {
|
||||
const org = repository.split('/')[3];
|
||||
const name = repository.split('/')[4];
|
||||
readme = readme.replace(/\[org_name\]/g, org);
|
||||
readme = readme.replace(/\[repo_url\]/g, name);
|
||||
fs.writeFileSync(`${pluginPath}/README.md`, readme);
|
||||
} else {
|
||||
console.warn('Unable to find repository in package.json, aborting.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('contributing') === -1 && files.indexOf('contributing.md') === -1) {
|
||||
console.warn('CONTRIBUTING.md file not found, please create');
|
||||
if (autoFix) {
|
||||
console.log('Autofixing missing CONTRIBUTING.md file, please edit the CONTRIBUTING.md ' +
|
||||
'file further to include plugin specific details.');
|
||||
let contributing =
|
||||
fs.readFileSync('src/bin/plugins/lib/CONTRIBUTING.md', {encoding: 'utf8', flag: 'r'});
|
||||
contributing = contributing.replace(/\[plugin_name\]/g, pluginName);
|
||||
fs.writeFileSync(`${pluginPath}/CONTRIBUTING.md`, contributing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (readMeFileName) {
|
||||
let readme =
|
||||
fs.readFileSync(`${pluginPath}/${readMeFileName}`, {encoding: 'utf8', flag: 'r'});
|
||||
if (readme.toLowerCase().indexOf('license') === -1) {
|
||||
console.warn('No license section in README');
|
||||
if (autoFix) {
|
||||
console.warn('Please add License section to README manually.');
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line max-len
|
||||
const publishBadge = `![Publish Status](https://github.com/ether/${pluginName}/workflows/Node.js%20Package/badge.svg)`;
|
||||
// eslint-disable-next-line max-len
|
||||
const testBadge = `![Backend Tests Status](https://github.com/ether/${pluginName}/workflows/Backend%20tests/badge.svg)`;
|
||||
if (readme.toLowerCase().indexOf('travis') !== -1) {
|
||||
console.warn('Remove Travis badges');
|
||||
}
|
||||
if (readme.indexOf('workflows/Node.js%20Package/badge.svg') === -1) {
|
||||
console.warn('No Github workflow badge detected');
|
||||
if (autoFix) {
|
||||
readme = `${publishBadge} ${testBadge}\n\n${readme}`;
|
||||
// write readme to file system
|
||||
fs.writeFileSync(`${pluginPath}/${readMeFileName}`, readme);
|
||||
console.log('Wrote Github workflow badges to README');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('license') === -1 && files.indexOf('license.md') === -1) {
|
||||
console.warn('LICENSE.md file not found, please create');
|
||||
if (autoFix) {
|
||||
console.log('Autofixing missing LICENSE.md file, including Apache 2 license.');
|
||||
let license =
|
||||
fs.readFileSync('src/bin/plugins/lib/LICENSE.md', {encoding: 'utf8', flag: 'r'});
|
||||
license = license.replace('[yyyy]', new Date().getFullYear());
|
||||
license = license.replace('[name of copyright owner]', execSync('git config user.name'));
|
||||
fs.writeFileSync(`${pluginPath}/LICENSE.md`, license);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('.gitignore') === -1) {
|
||||
console.warn('.gitignore file not found, please create. .gitignore files are useful to ' +
|
||||
"ensure files aren't incorrectly commited to a repository.");
|
||||
if (autoFix) {
|
||||
console.log('Autofixing missing .gitignore file');
|
||||
const gitignore =
|
||||
fs.readFileSync('src/bin/plugins/lib/gitignore', {encoding: 'utf8', flag: 'r'});
|
||||
fs.writeFileSync(`${pluginPath}/.gitignore`, gitignore);
|
||||
}
|
||||
} else {
|
||||
let gitignore =
|
||||
fs.readFileSync(`${pluginPath}/.gitignore`, {encoding: 'utf8', flag: 'r'});
|
||||
if (gitignore.indexOf('node_modules/') === -1) {
|
||||
console.warn('node_modules/ missing from .gitignore');
|
||||
if (autoFix) {
|
||||
gitignore += 'node_modules/';
|
||||
fs.writeFileSync(`${pluginPath}/.gitignore`, gitignore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we include templates but don't have translations...
|
||||
if (files.indexOf('templates') !== -1 && files.indexOf('locales') === -1) {
|
||||
console.warn('Translations not found, please create. ' +
|
||||
'Translation files help with Etherpad accessibility.');
|
||||
}
|
||||
|
||||
|
||||
if (files.indexOf('.ep_initialized') !== -1) {
|
||||
console.warn(
|
||||
'.ep_initialized found, please remove. .ep_initialized should never be commited to git ' +
|
||||
'and should only exist once the plugin has been executed one time.');
|
||||
if (autoFix) {
|
||||
console.log('Autofixing incorrectly existing .ep_initialized file');
|
||||
fs.unlinkSync(`${pluginPath}/.ep_initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('npm-debug.log') !== -1) {
|
||||
console.warn('npm-debug.log found, please remove. npm-debug.log should never be commited to ' +
|
||||
'your repository.');
|
||||
if (autoFix) {
|
||||
console.log('Autofixing incorrectly existing npm-debug.log file');
|
||||
fs.unlinkSync(`${pluginPath}/npm-debug.log`);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.indexOf('static') !== -1) {
|
||||
fs.readdir(`${pluginPath}/static`, (errRead, staticFiles) => {
|
||||
if (staticFiles.indexOf('tests') === -1) {
|
||||
console.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin');
|
||||
}
|
||||
|
||||
// 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'}`;
|
||||
execSync(npmInstall, {stdio: 'inherit'});
|
||||
// The ep_etherpad-lite peer dep must be installed last otherwise `npm install` will nuke it. An
|
||||
// absolute path to etherpad-lite/src is used here so that pluginPath can be a symlink.
|
||||
execSync(
|
||||
`${npmInstall} --no-save ep_etherpad-lite@file:${__dirname}/../../`, {stdio: 'inherit'});
|
||||
// linting begins
|
||||
try {
|
||||
console.log('Linting...');
|
||||
const lintCmd = autoFix ? 'npx eslint --fix .' : 'npx eslint';
|
||||
execSync(lintCmd, {stdio: 'inherit'});
|
||||
} catch (e) {
|
||||
// it is gonna throw an error anyway
|
||||
console.log('Manual linting probably required, check with: npm run lint');
|
||||
}
|
||||
// linting ends.
|
||||
|
||||
if (autoFix) {
|
||||
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) {
|
||||
// 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 ""', {
|
||||
env: {...process.env, GIT_INDEX_FILE: '.git/checkPlugin.index'},
|
||||
stdio: 'inherit',
|
||||
});
|
||||
fs.unlinkSync(`${pluginPath}/.git/checkPlugin.index`);
|
||||
|
||||
const cmd = [
|
||||
'git add -A',
|
||||
'git commit -m "autofixes from Etherpad checkPlugin.js"',
|
||||
'git push',
|
||||
].join(' && ');
|
||||
if (autoCommit) {
|
||||
console.log('Attempting autocommit and auto publish to npm');
|
||||
execSync(cmd, {stdio: 'inherit'});
|
||||
} else {
|
||||
console.log('Fixes applied. Check the above git diff then run the following command:');
|
||||
console.log(`(cd node_modules/${pluginName} && ${cmd})`);
|
||||
}
|
||||
} else {
|
||||
console.log('No changes.');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Finished');
|
||||
});
|
|
@ -113,7 +113,9 @@ Documentation should be kept up-to-date. This means, whenever you add a new API
|
|||
You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.
|
||||
|
||||
## Testing
|
||||
Front-end tests are found in the `tests/frontend/` folder in the repository. Run them by pointing your browser to `<yourdomainhere>/tests/frontend`.
|
||||
|
||||
Front-end tests are found in the `src/tests/frontend/` folder in the repository.
|
||||
Run them by pointing your browser to `<yourdomainhere>/tests/frontend`.
|
||||
|
||||
Back-end tests can be run from the `src` directory, via `npm test`.
|
||||
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
repository: ether/etherpad-lite
|
||||
|
||||
- name: Install all dependencies and symlink for ep_etherpad-lite
|
||||
run: bin/installDeps.sh
|
||||
run: src/bin/installDeps.sh
|
||||
|
||||
# clone this repository into node_modules/ep_plugin-name
|
||||
- name: Checkout plugin repository
|
||||
|
@ -45,7 +45,7 @@ jobs:
|
|||
|
||||
# configures some settings and runs npm run test
|
||||
- name: Run the backend tests
|
||||
run: tests/frontend/travis/runnerBackend.sh
|
||||
run: src/tests/frontend/travis/runnerBackend.sh
|
||||
|
||||
##ETHERPAD_NPM_V=1
|
||||
## NPM configuration automatically created using bin/plugins/updateAllPluginsScript.sh
|
||||
## NPM configuration automatically created using src/bin/plugins/updateAllPluginsScript.sh
|
|
@ -64,10 +64,20 @@ jobs:
|
|||
- run: git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
- run: npm ci
|
||||
- run: npm version patch
|
||||
- run: git push --follow-tags
|
||||
# `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}}
|
||||
- run: git push --follow-tags
|
||||
|
||||
##ETHERPAD_NPM_V=1
|
||||
## NPM configuration automatically created using bin/plugins/updateAllPluginsScript.sh
|
||||
##ETHERPAD_NPM_V=2
|
||||
## NPM configuration automatically created using src/bin/plugins/updateAllPluginsScript.sh
|
|
@ -4,7 +4,7 @@ do
|
|||
echo $dir
|
||||
if [[ $dir == *"ep_"* ]]; then
|
||||
if [[ $dir != "ep_etherpad-lite" ]]; then
|
||||
# node bin/plugins/checkPlugin.js $dir autofix autocommit autoupdate
|
||||
# node src/bin/plugins/checkPlugin.js $dir autofix autocommit autoupdate
|
||||
cd node_modules/$dir
|
||||
git commit -m "Automatic update: bump update to re-run latest Etherpad tests" --allow-empty
|
||||
git push origin master
|
|
@ -10,7 +10,7 @@ do
|
|||
# echo $0
|
||||
if [[ $dir == *"ep_"* ]]; then
|
||||
if [[ $dir != "ep_etherpad-lite" ]]; then
|
||||
node bin/plugins/checkPlugin.js $dir autofix autocommit autoupdate
|
||||
node src/bin/plugins/checkPlugin.js $dir autofix autocommit autoupdate
|
||||
fi
|
||||
fi
|
||||
# echo $dir
|
|
@ -5,5 +5,5 @@ set -e
|
|||
for dir in node_modules/ep_*; do
|
||||
dir=${dir#node_modules/}
|
||||
[ "$dir" != ep_etherpad-lite ] || continue
|
||||
node bin/plugins/checkPlugin.js "$dir" autofix autocommit autoupdate
|
||||
node src/bin/plugins/checkPlugin.js "$dir" autofix autocommit autoupdate
|
||||
done
|
84
src/bin/rebuildPad.js
Normal file
84
src/bin/rebuildPad.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
This is a repair tool. It rebuilds an old pad at a new pad location up to a
|
||||
known "good" revision.
|
||||
*/
|
||||
|
||||
// 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 !== 4 && process.argv.length !== 5) {
|
||||
throw new Error('Use: node src/bin/repairPad.js $PADID $REV [$NEWPADID]');
|
||||
}
|
||||
|
||||
const padId = process.argv[2];
|
||||
const newRevHead = process.argv[3];
|
||||
const newPadId = process.argv[4] || `${padId}-rebuilt`;
|
||||
|
||||
(async () => {
|
||||
const db = require('../node/db/DB');
|
||||
await db.init();
|
||||
|
||||
const PadManager = require('../node/db/PadManager');
|
||||
const Pad = require('../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)) {
|
||||
throw new Error('Cannot create a pad with that id as it is invalid');
|
||||
}
|
||||
const exists = await PadManager.doesPadExist(newPadId);
|
||||
if (exists) throw new Error('Cannot create a pad with that id as it already exists');
|
||||
|
||||
const oldPad = await PadManager.getPad(padId);
|
||||
const newPad = new Pad(newPadId);
|
||||
|
||||
// Clone all Chat revisions
|
||||
const chatHead = oldPad.chatHead;
|
||||
await Promise.all([...Array(chatHead + 1).keys()].map(async (i) => {
|
||||
const chat = await db.get(`pad:${padId}:chat:${i}`);
|
||||
await db.set(`pad:${newPadId}:chat:${i}`, chat);
|
||||
console.log(`Created: Chat Revision: pad:${newPadId}:chat:${i}`);
|
||||
}));
|
||||
|
||||
// Rebuild Pad from revisions up to and including the new revision head
|
||||
const AuthorManager = require('../node/db/AuthorManager');
|
||||
const Changeset = require('../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
|
||||
newPad.pool.numToAttrib = oldPad.pool.numToAttrib;
|
||||
for (let curRevNum = 0; curRevNum <= newRevHead; curRevNum++) {
|
||||
const rev = await db.get(`pad:${padId}:revs:${curRevNum}`);
|
||||
if (!rev || !rev.meta) throw new Error('The specified revision number could not be found.');
|
||||
const newRevNum = ++newPad.head;
|
||||
const newRevId = `pad:${newPad.id}:revs:${newRevNum}`;
|
||||
await Promise.all([
|
||||
db.set(newRevId, rev),
|
||||
AuthorManager.addPad(rev.meta.author, newPad.id),
|
||||
]);
|
||||
newPad.atext = Changeset.applyToAText(rev.changeset, newPad.atext, newPad.pool);
|
||||
console.log(`Created: Revision: pad:${newPad.id}:revs:${newRevNum}`);
|
||||
}
|
||||
|
||||
// Add saved revisions up to the new revision head
|
||||
console.log(newPad.head);
|
||||
const newSavedRevisions = [];
|
||||
for (const savedRev of oldPad.savedRevisions) {
|
||||
if (savedRev.revNum <= newRevHead) {
|
||||
newSavedRevisions.push(savedRev);
|
||||
console.log(`Added: Saved Revision: ${savedRev.revNum}`);
|
||||
}
|
||||
}
|
||||
newPad.savedRevisions = newSavedRevisions;
|
||||
|
||||
// Save the source pad
|
||||
await db.set(`pad:${newPadId}`, newPad);
|
||||
|
||||
console.log(`Created: Source Pad: pad:${newPadId}`);
|
||||
await newPad.saveToDatabase();
|
||||
|
||||
await db.shutdown();
|
||||
console.info('finished');
|
||||
})();
|
75
src/bin/release.js
Normal file
75
src/bin/release.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
'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 fs = require('fs');
|
||||
const childProcess = require('child_process');
|
||||
const semver = require('semver');
|
||||
|
||||
/*
|
||||
|
||||
Usage
|
||||
|
||||
node src/bin/release.js patch
|
||||
|
||||
*/
|
||||
const usage =
|
||||
'node src/bin/release.js [patch/minor/major] -- example: "node src/bin/release.js patch"';
|
||||
|
||||
const release = process.argv[2];
|
||||
|
||||
if (!release) {
|
||||
console.log(usage);
|
||||
throw new Error('No release type included');
|
||||
}
|
||||
|
||||
const changelog = fs.readFileSync('CHANGELOG.md', {encoding: 'utf8', flag: 'r'});
|
||||
let packageJson = fs.readFileSync('./src/package.json', {encoding: 'utf8', flag: 'r'});
|
||||
packageJson = JSON.parse(packageJson);
|
||||
const currentVersion = packageJson.version;
|
||||
|
||||
const newVersion = semver.inc(currentVersion, release);
|
||||
if (!newVersion) {
|
||||
console.log(usage);
|
||||
throw new Error('Unable to generate new version from input');
|
||||
}
|
||||
|
||||
const changelogIncludesVersion = changelog.indexOf(newVersion) !== -1;
|
||||
|
||||
if (!changelogIncludesVersion) {
|
||||
throw new Error(`No changelog record for ${newVersion}, please create changelog record`);
|
||||
}
|
||||
|
||||
console.log('Okay looks good, lets create the package.json and package-lock.json');
|
||||
|
||||
packageJson.version = newVersion;
|
||||
|
||||
fs.writeFileSync('src/package.json', JSON.stringify(packageJson, null, 2));
|
||||
|
||||
// run npm version `release` where release is patch, minor or major
|
||||
childProcess.execSync('npm install --package-lock-only', {cwd: 'src/'});
|
||||
// run npm install --package-lock-only <-- required???
|
||||
|
||||
childProcess.execSync(`git checkout -b release/${newVersion}`);
|
||||
childProcess.execSync('git add src/package.json');
|
||||
childProcess.execSync('git add src/package-lock.json');
|
||||
childProcess.execSync('git commit -m "bump version"');
|
||||
childProcess.execSync(`git push origin release/${newVersion}`);
|
||||
|
||||
|
||||
childProcess.execSync('make docs');
|
||||
childProcess.execSync('git clone git@github.com:ether/ether.github.com.git');
|
||||
childProcess.execSync(`cp -R out/doc/ ether.github.com/doc/v${newVersion}`);
|
||||
|
||||
console.log('Once merged into master please run the following commands');
|
||||
console.log(`git tag -a ${newVersion} -m ${newVersion} && git push origin master`);
|
||||
console.log(`cd ether.github.com && git add . && git commit -m '${newVersion} docs'`);
|
||||
console.log('Build the windows zip');
|
||||
console.log('Visit https://github.com/ether/etherpad-lite/releases/new and create a new release ' +
|
||||
`with 'master' as the target and the version is ${newVersion}. Include the windows ` +
|
||||
'zip as an asset');
|
||||
console.log(`Once the new docs are uploaded then modify the download
|
||||
link on etherpad.org and then pull master onto develop`);
|
||||
console.log('Finally go public with an announcement via our comms channels :)');
|
56
src/bin/repairPad.js
Normal file
56
src/bin/repairPad.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
* This is a repair tool. It extracts all datas of a pad, removes and inserts them again.
|
||||
*/
|
||||
|
||||
// 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; });
|
||||
|
||||
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');
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
let valueCount = 0;
|
||||
|
||||
(async () => {
|
||||
// initialize database
|
||||
require('../node/utils/Settings');
|
||||
const db = require('../node/db/DB');
|
||||
await db.init();
|
||||
|
||||
// get the pad
|
||||
const padManager = require('../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}`));
|
||||
|
||||
// add all revisions
|
||||
for (let rev = 0; rev <= pad.head; ++rev) {
|
||||
neededDBValues.push(`pad:${padId}:revs:${rev}`);
|
||||
}
|
||||
|
||||
// add all chat values
|
||||
for (let chat = 0; chat <= pad.chatHead; ++chat) {
|
||||
neededDBValues.push(`pad:${padId}:chat:${chat}`);
|
||||
}
|
||||
// now fetch and reinsert every key
|
||||
for (const key of neededDBValues) {
|
||||
const value = await db.get(key);
|
||||
// if it isn't a globalAuthor value which we want to ignore..
|
||||
// console.log(`Key: ${key}, value: ${JSON.stringify(value)}`);
|
||||
await db.remove(key);
|
||||
await db.set(key, value);
|
||||
valueCount++;
|
||||
}
|
||||
|
||||
console.info(`Finished: Replaced ${valueCount} values in the database`);
|
||||
})();
|
|
@ -1,10 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
# Move to the Etherpad base directory.
|
||||
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
|
||||
cd "${MY_DIR}/../.." || exit 1
|
||||
|
||||
# Source constants and usefull functions
|
||||
. bin/functions.sh
|
||||
# Source constants and useful functions
|
||||
. src/bin/functions.sh
|
||||
|
||||
ignoreRoot=0
|
||||
for ARG in "$@"; do
|
||||
|
@ -26,7 +27,7 @@ EOF
|
|||
fi
|
||||
|
||||
# Prepare the environment
|
||||
bin/installDeps.sh "$@" || exit 1
|
||||
src/bin/installDeps.sh "$@" || exit 1
|
||||
|
||||
# Move to the node folder and start
|
||||
log "Starting Etherpad..."
|
|
@ -23,8 +23,9 @@ fatal() { error "$@"; exit 1; }
|
|||
|
||||
LAST_EMAIL_SEND=0
|
||||
|
||||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
# Move to the Etherpad base directory.
|
||||
MY_DIR=$(try cd "${0%/*}" && try pwd -P) || exit 1
|
||||
try cd "${MY_DIR}/../.."
|
||||
|
||||
# Check if a logfile parameter is set
|
||||
LOG="$1"
|
||||
|
@ -39,7 +40,7 @@ while true; do
|
|||
[ -w "${LOG}" ] || fatal "Logfile '${LOG}' is not writeable"
|
||||
|
||||
# Start the application
|
||||
bin/run.sh "$@" >>${LOG} 2>>${LOG}
|
||||
src/bin/run.sh "$@" >>${LOG} 2>>${LOG}
|
||||
|
||||
TIME_FMT=$(date +%Y-%m-%dT%H:%M:%S%z)
|
||||
|
|
@ -7,6 +7,13 @@
|
|||
"Wizardist"
|
||||
]
|
||||
},
|
||||
"admin.page-title": "Адміністрацыйная панэль — Etherpad",
|
||||
"admin_plugins": "Кіраўнік плагінаў",
|
||||
"admin_plugins.available": "Даступныя плагіны",
|
||||
"admin_plugins.available_not-found": "Плагіны ня знойдзеныя.",
|
||||
"admin_plugins.available_fetching": "Атрымліваем…",
|
||||
"admin_plugins.available_install.value": "Усталяваць",
|
||||
"admin_settings.page-title": "Налады — Etherpad",
|
||||
"index.newPad": "Стварыць",
|
||||
"index.createOpenPad": "ці тварыць/адкрыць дакумэнт з назвай:",
|
||||
"index.openPad": "адкрыць існы Нататнік з назваю:",
|
||||
|
@ -17,7 +24,7 @@
|
|||
"pad.toolbar.ol.title": "Упарадкаваны сьпіс (Ctrl+Shift+N)",
|
||||
"pad.toolbar.ul.title": "Неўпарадкаваны сьпіс (Ctrl+Shift+L)",
|
||||
"pad.toolbar.indent.title": "Водступ (TAB)",
|
||||
"pad.toolbar.unindent.title": "Выступ (Shift+TAB)",
|
||||
"pad.toolbar.unindent.title": "Водступ (Shift+TAB)",
|
||||
"pad.toolbar.undo.title": "Скасаваць(Ctrl-Z)",
|
||||
"pad.toolbar.redo.title": "Вярнуць (Ctrl-Y)",
|
||||
"pad.toolbar.clearAuthorship.title": "Прыбраць колер дакумэнту (Ctrl+Shift+C)",
|
||||
|
@ -42,6 +49,7 @@
|
|||
"pad.settings.fontType": "Тып шрыфту:",
|
||||
"pad.settings.fontType.normal": "Звычайны",
|
||||
"pad.settings.language": "Мова:",
|
||||
"pad.settings.about": "Пра",
|
||||
"pad.importExport.import_export": "Імпарт/Экспарт",
|
||||
"pad.importExport.import": "Загрузіжайце любыя тэкставыя файлы або дакумэнты",
|
||||
"pad.importExport.importSuccessful": "Пасьпяхова!",
|
||||
|
@ -54,7 +62,7 @@
|
|||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Вы можаце імпартаваць толькі з звычайнага тэксту або HTML. Дзеля больш пашыраных магчымасьцяў імпарту, калі ласка, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">усталюйце AbiWord альбо LibreOffice</a>.",
|
||||
"pad.modals.connected": "Падлучыліся.",
|
||||
"pad.modals.reconnecting": "Перападлучэньне да вашага дакумэнта...",
|
||||
"pad.modals.reconnecting": "Перападлучэньне да вашага дакумэнта…",
|
||||
"pad.modals.forcereconnect": "Прымусовае перападлучэньне",
|
||||
"pad.modals.reconnecttimer": "Спрабуем перападключыцца праз",
|
||||
"pad.modals.cancel": "Скасаваць",
|
||||
|
|
|
@ -10,11 +10,46 @@
|
|||
"Leanes",
|
||||
"Mormegil",
|
||||
"Peldrjan",
|
||||
"Quinn"
|
||||
"Quinn",
|
||||
"Spotter"
|
||||
]
|
||||
},
|
||||
"admin.page-title": "Ovládací panel Správce - Etherpad",
|
||||
"admin_plugins": "Správce zásuvných moodulů",
|
||||
"admin_plugins.available": "Dostupné zásuvné moduly",
|
||||
"admin_plugins.available_not-found": "Nejsou žádné zásuvné moduly",
|
||||
"admin_plugins.available_fetching": "Načítání...",
|
||||
"admin_plugins.available_install.value": "Instalovat",
|
||||
"admin_plugins.available_search.placeholder": "Vyhledat zásuvné moduly k instalaci",
|
||||
"admin_plugins.description": "Popis",
|
||||
"admin_plugins.installed": "Nainstalované zásuvné moduly",
|
||||
"admin_plugins.installed_fetching": "Načítání instalovaných zásuvných modulů...",
|
||||
"admin_plugins.installed_nothing": "Dosud jste nenainstalovali žádné zásuvné moduly.",
|
||||
"admin_plugins.installed_uninstall.value": "Odinstalovat",
|
||||
"admin_plugins.last-update": "Poslední aktualizace",
|
||||
"admin_plugins.name": "Název",
|
||||
"admin_plugins.page-title": "Správce zásuvných modulů - Etherpad",
|
||||
"admin_plugins.version": "Verze",
|
||||
"admin_plugins_info": "Informace o řešení problému",
|
||||
"admin_plugins_info.hooks": "Instalované hooks",
|
||||
"admin_plugins_info.hooks_client": "hooks na straně klienta",
|
||||
"admin_plugins_info.hooks_server": "hooks na straně serveru",
|
||||
"admin_plugins_info.parts": "Nainstalované součásti",
|
||||
"admin_plugins_info.plugins": "Nainstalované zásuvné moduly",
|
||||
"admin_plugins_info.page-title": "Informace o zásuvných modulech - Etherpad",
|
||||
"admin_plugins_info.version": "Verze Etherpad",
|
||||
"admin_plugins_info.version_latest": "Poslední dostupná verze",
|
||||
"admin_plugins_info.version_number": "Číslo verze",
|
||||
"admin_settings": "Nastavení",
|
||||
"admin_settings.current": "Aktuální konfugurace",
|
||||
"admin_settings.current_example-devel": "Příklad ukázkové vývojové šablony",
|
||||
"admin_settings.current_example-prod": "Příklad šablony nastavení výroby",
|
||||
"admin_settings.current_restart.value": "Restartovat Etherpad",
|
||||
"admin_settings.current_save.value": "Uložit nastavení",
|
||||
"admin_settings.page-title": "Nastavení - Etherpad",
|
||||
"index.newPad": "Založ nový Pad",
|
||||
"index.createOpenPad": "nebo vytvoř/otevři Pad s názvem:",
|
||||
"index.openPad": "otevřít existující Pad se jménem:",
|
||||
"pad.toolbar.bold.title": "Tučný text (Ctrl-B)",
|
||||
"pad.toolbar.italic.title": "Kurzíva (Ctrl-I)",
|
||||
"pad.toolbar.underline.title": "Podtržené písmo (Ctrl-U)",
|
||||
|
@ -35,7 +70,7 @@
|
|||
"pad.colorpicker.save": "Uložit",
|
||||
"pad.colorpicker.cancel": "Zrušit",
|
||||
"pad.loading": "Načítání...",
|
||||
"pad.noCookie": "Nelze nalézt cookie. Povolte prosím cookie ve Vašem prohlížeči.",
|
||||
"pad.noCookie": "Soubor cookie nebyl nalezen. Povolte prosím cookies ve svém prohlížeči! Vaše relace a nastavení se mezi návštěvami neuloží. Může to být způsobeno tím, že je Etherpad v některých prohlížečích zahrnut do iFrame. Zkontrolujte, zda je Etherpad ve stejné subdoméně / doméně jako nadřazený iFrame",
|
||||
"pad.permissionDenied": "Nemáte oprávnění pro přístup k tomuto Padu",
|
||||
"pad.settings.padSettings": "Nastavení Padu",
|
||||
"pad.settings.myView": "Vlastní pohled",
|
||||
|
@ -47,6 +82,8 @@
|
|||
"pad.settings.fontType": "Typ písma:",
|
||||
"pad.settings.fontType.normal": "Normální",
|
||||
"pad.settings.language": "Jazyk:",
|
||||
"pad.settings.about": "O projektu",
|
||||
"pad.settings.poweredBy": "Běží na",
|
||||
"pad.importExport.import_export": "Import/Export",
|
||||
"pad.importExport.import": "Nahrát libovolný textový soubor nebo dokument",
|
||||
"pad.importExport.importSuccessful": "Úspěšně!",
|
||||
|
@ -57,9 +94,9 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Importovat můžeš pouze prostý text nebo HTML formátování. Pro pokročilejší funkce importu, prosím, nainstaluj „<a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">AbiWord</a>“.",
|
||||
"pad.importExport.abiword.innerHTML": "Importovat lze pouze z formátů prostého textu nebo HTML. Pokročilejší funkce pro import naleznete v <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">instalaci AbiWord nebo LibreOffice</a>.",
|
||||
"pad.modals.connected": "Připojeno.",
|
||||
"pad.modals.reconnecting": "Znovupřipojování k Padu…",
|
||||
"pad.modals.reconnecting": "Opětovné připojení k Padu...",
|
||||
"pad.modals.forcereconnect": "Vynutit znovupřipojení",
|
||||
"pad.modals.reconnecttimer": "Zkouším se znovu připojit",
|
||||
"pad.modals.cancel": "Zrušit",
|
||||
|
@ -81,6 +118,10 @@
|
|||
"pad.modals.corruptPad.cause": "To může být kvůli špatné konfiguraci serveru, nebo kvůli jinému neočekávanému chování. Kontaktujte prosím správce služby.",
|
||||
"pad.modals.deleted": "Odstraněno.",
|
||||
"pad.modals.deleted.explanation": "Tento Pad byl odebrán.",
|
||||
"pad.modals.rateLimited": "Rychlost je omezená.",
|
||||
"pad.modals.rateLimited.explanation": "Na tento Pad jste poslali příliš mnoho zpráv, takže vás odpojil.",
|
||||
"pad.modals.rejected.explanation": "Server odmítl zprávu odeslanou vaším prohlížečem.",
|
||||
"pad.modals.rejected.cause": "Server mohl být aktualizován, když jste sledovali podložku, nebo možná došlo k chybě v Etherpadu. Zkuste stránku znovu načíst.",
|
||||
"pad.modals.disconnected": "Byl jste odpojen.",
|
||||
"pad.modals.disconnected.explanation": "Připojení k serveru bylo přerušeno",
|
||||
"pad.modals.disconnected.cause": "Server může být nedostupný. Upozorněte administrátora služby, pokud se to bude opakovat.",
|
||||
|
@ -93,6 +134,7 @@
|
|||
"pad.chat.loadmessages": "Načíst více zpráv",
|
||||
"pad.chat.stick.title": "Přichytit chat k obrazovce",
|
||||
"pad.chat.writeMessage.placeholder": "Zde napište zprávu",
|
||||
"timeslider.followContents": "Sledovat aktualizace obsahu Padu",
|
||||
"timeslider.pageTitle": "Časová osa {{appTitle}}",
|
||||
"timeslider.toolbar.returnbutton": "Návrat do Padu",
|
||||
"timeslider.toolbar.authors": "Autoři:",
|
||||
|
@ -131,5 +173,6 @@
|
|||
"pad.impexp.uploadFailed": "Nahrávání selhalo, zkuste to znovu",
|
||||
"pad.impexp.importfailed": "Import selhal",
|
||||
"pad.impexp.copypaste": "Vložte prosím kopii",
|
||||
"pad.impexp.exportdisabled": "Export do formátu {{type}} je zakázán. Kontaktujte svého administrátora pro zjištění detailů."
|
||||
"pad.impexp.exportdisabled": "Export do formátu {{type}} je zakázán. Kontaktujte svého administrátora pro zjištění detailů.",
|
||||
"pad.impexp.maxFileSize": "Soubor je příliš velký. Požádejte svého správce webu o zvýšení povolené velikosti souboru pro import"
|
||||
}
|
||||
|
|
|
@ -11,24 +11,42 @@
|
|||
"Sebastian Wallroth",
|
||||
"Thargon",
|
||||
"Tim.krieger",
|
||||
"Wikinaut"
|
||||
"Wikinaut",
|
||||
"Zunkelty"
|
||||
]
|
||||
},
|
||||
"admin.page-title": "Admin Dashboard - Etherpad",
|
||||
"admin_plugins": "Plugins verwalten",
|
||||
"admin_plugins.available": "Verfügbare Plugins",
|
||||
"admin_plugins.available_not-found": "Keine Plugins gefunden.",
|
||||
"admin_plugins.available_fetching": "Wird abgerufen...",
|
||||
"admin_plugins.available_install.value": "Installieren",
|
||||
"admin_plugins.available_search.placeholder": "Suche nach Plugins zum Installieren",
|
||||
"admin_plugins.description": "Beschreibung",
|
||||
"admin_plugins.installed": "Installierte Plugins",
|
||||
"admin_plugins.installed_fetching": "Rufe installierte Plugins ab...",
|
||||
"admin_plugins.installed_nothing": "Du hast bisher noch keine Plugins installiert.",
|
||||
"admin_plugins.installed_uninstall.value": "Deinstallieren",
|
||||
"admin_plugins.last-update": "Letze Aktualisierung",
|
||||
"admin_plugins.name": "Name",
|
||||
"admin_plugins.page-title": "Plugin Manager - Etherpad",
|
||||
"admin_plugins.version": "Version",
|
||||
"admin_plugins_info": "Hilfestellung",
|
||||
"admin_plugins_info.hooks": "Installierte Hooks",
|
||||
"admin_plugins_info.hooks_client": "Client-seitige Hooks",
|
||||
"admin_plugins_info.hooks_server": "Server-seitige Hooks",
|
||||
"admin_plugins_info.parts": "Installierte Teile",
|
||||
"admin_plugins_info.plugins": "Installierte Plugins",
|
||||
"admin_plugins_info.page-title": "Plugin Informationen - Etherpad",
|
||||
"admin_plugins_info.version": "Etherpad Version",
|
||||
"admin_plugins_info.version_latest": "Neueste Version",
|
||||
"admin_plugins_info.version_number": "Versionsnummer",
|
||||
"admin_settings": "Einstellungen",
|
||||
"admin_settings.current": "Derzeitige Konfiguration",
|
||||
"admin_settings.current_example-devel": "Beispielhafte Entwicklungseinstellungs-Templates",
|
||||
"admin_settings.current_restart.value": "Etherpad neustarten",
|
||||
"admin_settings.current_save.value": "Einstellungen speichern",
|
||||
"admin_settings.page-title": "Einstellungen - Etherpad",
|
||||
"index.newPad": "Neues Pad",
|
||||
"index.createOpenPad": "oder ein Pad mit folgendem Namen erstellen/öffnen:",
|
||||
"index.openPad": "Öffne ein vorhandenes Pad mit folgendem Namen:",
|
||||
|
@ -78,7 +96,7 @@
|
|||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Du kannst nur aus reinen Text- oder HTML-Formaten importieren. Für umfangreichere Importfunktionen <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">muss AbiWord oder LibreOffice auf dem Server installiert werden</a>.",
|
||||
"pad.modals.connected": "Verbunden.",
|
||||
"pad.modals.reconnecting": "Wiederherstellen der Verbindung …",
|
||||
"pad.modals.reconnecting": "Dein Pad wird neu verbunden...",
|
||||
"pad.modals.forcereconnect": "Erneutes Verbinden erzwingen",
|
||||
"pad.modals.reconnecttimer": "Versuche Neuverbindung in",
|
||||
"pad.modals.cancel": "Abbrechen",
|
||||
|
@ -102,6 +120,7 @@
|
|||
"pad.modals.deleted.explanation": "Dieses Pad wurde entfernt.",
|
||||
"pad.modals.rateLimited": "Begrenzte Rate.",
|
||||
"pad.modals.rateLimited.explanation": "Sie haben zu viele Nachrichten an dieses Pad gesendet, so dass die Verbindung unterbrochen wurde.",
|
||||
"pad.modals.rejected.explanation": "Der Server hat eine Nachricht abgelehnt, die von deinem Browser gesendet wurde.",
|
||||
"pad.modals.disconnected": "Ihre Verbindung wurde getrennt.",
|
||||
"pad.modals.disconnected.explanation": "Die Verbindung zum Server wurde unterbrochen.",
|
||||
"pad.modals.disconnected.cause": "Möglicherweise ist der Server nicht erreichbar. Bitte benachrichtige den Dienstadministrator, falls dies weiterhin passiert.",
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
]
|
||||
},
|
||||
"admin.page-title": "Panoyê İdarekari - Etherpad",
|
||||
"admin_plugins": "İdarekarê Dekerdeki",
|
||||
"admin_plugins": "Gıredayışê raverberi",
|
||||
"admin_plugins.available": "Mewcud Dekerdeki",
|
||||
"admin_plugins.available_not-found": "Dekerdek nevineya",
|
||||
"admin_plugins.available_fetching": "Aniyeno...",
|
||||
|
@ -132,15 +132,15 @@
|
|||
"pad.chat.writeMessage.placeholder": "Mesacê xo tiya bınusne",
|
||||
"timeslider.followContents": "Rocaney zerrekê padi taqib bıkerê",
|
||||
"timeslider.pageTitle": "Ğızagê zemani {{appTitle}}",
|
||||
"timeslider.toolbar.returnbutton": "Peyser şo ped",
|
||||
"timeslider.toolbar.returnbutton": "Peyser şo bloknot",
|
||||
"timeslider.toolbar.authors": "Nuştoği:",
|
||||
"timeslider.toolbar.authorsList": "Nuştoği çıniyê",
|
||||
"timeslider.toolbar.authorsList": "Nuştekari çıniyê",
|
||||
"timeslider.toolbar.exportlink.title": "Teberdayış",
|
||||
"timeslider.exportCurrent": "Versiyonê enewki teber de:",
|
||||
"timeslider.version": "Versiyonê {{version}}",
|
||||
"timeslider.saved": "{{day}} {{month}}, {{year}} de biyo qeyd",
|
||||
"timeslider.playPause": "Zerrekê bloknoti kayfi/vındarn",
|
||||
"timeslider.backRevision": "Peyser şo revizyona ena bloknoter",
|
||||
"timeslider.backRevision": "Peyser şo çımraviyarnayışê na bloknoti",
|
||||
"timeslider.forwardRevision": "Ena bloknot de şo revizyonê bini",
|
||||
"timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||
"timeslider.month.january": "Çele",
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
"admin_plugins.page-title": "Plugin-en kudeaketa - Etherpad",
|
||||
"admin_plugins.version": "Bertsioa",
|
||||
"admin_plugins_info": "Arazoak konpontzeko informazioa",
|
||||
"admin_plugins_info.hooks": "Instalatutako kakoak",
|
||||
"admin_plugins_info.hooks_client": "Bezeroaren aldeko kakoak",
|
||||
"admin_plugins_info.hooks_server": "Zerbitzari aldeko kakoak",
|
||||
"admin_plugins_info.parts": "Instalatutako atalaka",
|
||||
"admin_plugins_info.plugins": "Instalatutako plugin-ak",
|
||||
"admin_plugins_info.page-title": "Plugin-en informazioa - Etherpad",
|
||||
"admin_plugins_info.version": "Etherpad bertsioa",
|
||||
|
@ -34,6 +38,8 @@
|
|||
"admin_plugins_info.version_number": "Bertsio-zenbakia",
|
||||
"admin_settings": "Ezarpenak",
|
||||
"admin_settings.current": "Oraingo konfigurazioa",
|
||||
"admin_settings.current_example-devel": "Adibiderako garapenerako ezarpenen txantiloia",
|
||||
"admin_settings.current_example-prod": "Adibiderako lanerako ezarpenen txantiloia",
|
||||
"admin_settings.current_restart.value": "Berrabiarazi Etherpad",
|
||||
"admin_settings.current_save.value": "Gorde Ezarpenak",
|
||||
"admin_settings.page-title": "Ezarpenak - Etherpad",
|
||||
|
@ -108,8 +114,10 @@
|
|||
"pad.modals.corruptPad.cause": "Baliteke zerbitzari okerreko konfigurazioagatik edo beste ustekabeko portaera batengatik izatea. Jarri harremanetan zerbitzu-administratzailearekin.",
|
||||
"pad.modals.deleted": "Ezabatua.",
|
||||
"pad.modals.deleted.explanation": "Pad hau ezabatu da.",
|
||||
"pad.modals.rateLimited": "Baloratzea Mugatuta.",
|
||||
"pad.modals.rateLimited.explanation": "Pad honetara mezu gehiegi bidali dituzu eta ondorioz deskonektatu zaizu.",
|
||||
"pad.modals.rejected.explanation": "Zerbitzariak zure nabigatzailetik bidali den mezu bat baztertu du.",
|
||||
"pad.modals.rejected.cause": "Baliteke pad-a ikusten ari zinen bitartean zerbitzaria eguneratu izana, edo bestela Etherpad-en arazo bat egon liteke. Orria freskatzen saiatu zaitez.",
|
||||
"pad.modals.disconnected": "Deskonektatua izan zara.",
|
||||
"pad.modals.disconnected.explanation": "Zerbitzariarekiko konexioa galdu da",
|
||||
"pad.modals.disconnected.cause": "Baliteke zerbitzaria eskuragarri ez egotea. Mesedez, jakinarazi zerbitzuko administratzaileari honek gertatzen jarraitzen badu.",
|
||||
|
@ -122,6 +130,7 @@
|
|||
"pad.chat.loadmessages": "Kargatu mezu gehiago",
|
||||
"pad.chat.stick.title": "Itsatsi txata pantailan",
|
||||
"pad.chat.writeMessage.placeholder": "Idatzi hemen zure mezua",
|
||||
"timeslider.followContents": "Jarraitu pad-aren edukien eguneratzeak",
|
||||
"timeslider.pageTitle": "{{appTitle}} Denbora-lerroa",
|
||||
"timeslider.toolbar.returnbutton": "Itzuli pad-era",
|
||||
"timeslider.toolbar.authors": "Egileak:",
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
"VezonThunder"
|
||||
]
|
||||
},
|
||||
"admin_plugins.available": "Saatavilla olevat liitännäiset",
|
||||
"admin_plugins.available_install.value": "Lataa",
|
||||
"admin_plugins.available_search.placeholder": "Etsi asennettavia laajennuksia",
|
||||
"admin_plugins.description": "Kuvaus",
|
||||
"admin_plugins.installed": "Asennetut laajennukset",
|
||||
|
@ -41,6 +43,8 @@
|
|||
"admin_settings": "Asetukset",
|
||||
"admin_settings.current": "Nykyinen kokoonpano",
|
||||
"admin_settings.current_example-devel": "Esimerkki kehitysasetusten mallista",
|
||||
"admin_settings.current_save.value": "Tallenna Asetukset",
|
||||
"admin_settings.page-title": "asetukset - Etherpad",
|
||||
"index.newPad": "Uusi muistio",
|
||||
"index.createOpenPad": "tai luo tai avaa muistio nimellä:",
|
||||
"pad.toolbar.bold.title": "Lihavointi (Ctrl-B)",
|
||||
|
@ -89,7 +93,7 @@
|
|||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Tuonti on tuettu vain HTML- ja raakatekstitiedostoista. Monipuoliset tuontiominaisuudet ovat käytettävissä <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">asentamalla AbiWordin tai LibreOfficen</a>.",
|
||||
"pad.modals.connected": "Yhdistetty.",
|
||||
"pad.modals.reconnecting": "Muodostetaan yhteyttä muistioon uudelleen...",
|
||||
"pad.modals.reconnecting": "Muodostetaan yhteyttä muistioon uudelleen…",
|
||||
"pad.modals.forcereconnect": "Pakota yhdistämään uudelleen",
|
||||
"pad.modals.reconnecttimer": "Yritetään yhdistää uudelleen",
|
||||
"pad.modals.cancel": "Peruuta",
|
||||
|
|
|
@ -30,29 +30,29 @@
|
|||
]
|
||||
},
|
||||
"admin.page-title": "Tableau de bord administrateur — Etherpad",
|
||||
"admin_plugins": "Gestionnaire de compléments",
|
||||
"admin_plugins.available": "Compléments disponibles",
|
||||
"admin_plugins.available_not-found": "Aucun complément trouvé.",
|
||||
"admin_plugins.available_fetching": "Récupération…",
|
||||
"admin_plugins": "Gestionnaire de greffons",
|
||||
"admin_plugins.available": "Greffons disponibles",
|
||||
"admin_plugins.available_not-found": "Aucun greffon trouvé.",
|
||||
"admin_plugins.available_fetching": "Récupération en cours...",
|
||||
"admin_plugins.available_install.value": "Installer",
|
||||
"admin_plugins.available_search.placeholder": "Rechercher des compléments à installer",
|
||||
"admin_plugins.available_search.placeholder": "Rechercher des greffons à installer",
|
||||
"admin_plugins.description": "Description",
|
||||
"admin_plugins.installed": "Compléments installés",
|
||||
"admin_plugins.installed_fetching": "Récupération des compléments installés…",
|
||||
"admin_plugins.installed_nothing": "Vous n’avez pas encore installé de complément.",
|
||||
"admin_plugins.installed": "Greffons installés",
|
||||
"admin_plugins.installed_fetching": "Récupération des greffons installés en cours...",
|
||||
"admin_plugins.installed_nothing": "Vous n’avez encore installé aucun greffon.",
|
||||
"admin_plugins.installed_uninstall.value": "Désinstaller",
|
||||
"admin_plugins.last-update": "Dernière mise à jour",
|
||||
"admin_plugins.name": "Nom",
|
||||
"admin_plugins.page-title": "Gestionnaire de compléments — Etherpad",
|
||||
"admin_plugins.page-title": "Gestionnaire de greffons — Etherpad",
|
||||
"admin_plugins.version": "Version",
|
||||
"admin_plugins_info": "Information de résolution de problème",
|
||||
"admin_plugins_info": "Informations de résolution de problème",
|
||||
"admin_plugins_info.hooks": "Crochets installés",
|
||||
"admin_plugins_info.hooks_client": "Crochets côté client",
|
||||
"admin_plugins_info.hooks_server": "Crochets côté serveur",
|
||||
"admin_plugins_info.parts": "Parties installées",
|
||||
"admin_plugins_info.plugins": "Compléments installés",
|
||||
"admin_plugins_info.page-title": "Information de complément — Etherpad",
|
||||
"admin_plugins_info.version": "Version Etherpad",
|
||||
"admin_plugins_info.plugins": "Greffons installés",
|
||||
"admin_plugins_info.page-title": "Informations du greffon — Etherpad",
|
||||
"admin_plugins_info.version": "Version d’Etherpad",
|
||||
"admin_plugins_info.version_latest": "Dernière version disponible",
|
||||
"admin_plugins_info.version_number": "Numéro de version",
|
||||
"admin_settings": "Paramètres",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"admin_settings.page-title": "Paramètres — Etherpad",
|
||||
"index.newPad": "Nouveau bloc-notes",
|
||||
"index.createOpenPad": "ou créer/ouvrir un bloc-notes intitulé :",
|
||||
"index.openPad": "ouvrir un Pad existant avec le nom :",
|
||||
"index.openPad": "ouvrir un bloc-note existant avec le nom :",
|
||||
"pad.toolbar.bold.title": "Gras (Ctrl+B)",
|
||||
"pad.toolbar.italic.title": "Italique (Ctrl+I)",
|
||||
"pad.toolbar.underline.title": "Souligné (Ctrl+U)",
|
||||
|
@ -76,7 +76,7 @@
|
|||
"pad.toolbar.undo.title": "Annuler (Ctrl+Z)",
|
||||
"pad.toolbar.redo.title": "Rétablir (Ctrl+Y)",
|
||||
"pad.toolbar.clearAuthorship.title": "Effacer le surlignage par auteur (Ctrl+Shift+C)",
|
||||
"pad.toolbar.import_export.title": "Importer de/Exporter vers un format de fichier différent",
|
||||
"pad.toolbar.import_export.title": "Importer/Exporter des formats de fichiers différents",
|
||||
"pad.toolbar.timeslider.title": "Historique dynamique",
|
||||
"pad.toolbar.savedRevision.title": "Enregistrer la révision",
|
||||
"pad.toolbar.settings.title": "Paramètres",
|
||||
|
@ -85,7 +85,7 @@
|
|||
"pad.colorpicker.save": "Enregistrer",
|
||||
"pad.colorpicker.cancel": "Annuler",
|
||||
"pad.loading": "Chargement...",
|
||||
"pad.noCookie": "Un cookie n’a pas pu être trouvé. Veuillez autoriser les fichiers témoins (ou cookies) dans votre navigateur ! Votre session et vos paramètres ne seront pas enregistrés entre les visites. Cela peut être dû au fait qu’Etehrpad est inclus dans un iFrame dans certains navigateurs. Veuillez vous assurer que Etherpad est dans le même sous-domaine/domaine que son iFrame parent",
|
||||
"pad.noCookie": "Un fichier témoin (ou ''cookie'') n’a pas pu être trouvé. Veuillez autoriser les fichiers témoins dans votre navigateur ! Votre session et vos paramètres ne seront pas enregistrés entre les visites. Cela peut être dû au fait qu’Etherpad est inclus dans un ''iFrame'' dans certains navigateurs. Veuillez vous assurer qu’Etherpad est dans le même sous-domaine/domaine que son ''iFrame'' parent.",
|
||||
"pad.permissionDenied": "Vous n’êtes pas autorisé à accéder à ce bloc-notes",
|
||||
"pad.settings.padSettings": "Paramètres du bloc-notes",
|
||||
"pad.settings.myView": "Ma vue",
|
||||
|
@ -94,11 +94,11 @@
|
|||
"pad.settings.colorcheck": "Surlignage par auteur",
|
||||
"pad.settings.linenocheck": "Numéros de lignes",
|
||||
"pad.settings.rtlcheck": "Le contenu doit-il être lu de droite à gauche ?",
|
||||
"pad.settings.fontType": "Police :",
|
||||
"pad.settings.fontType": "Type de police :",
|
||||
"pad.settings.fontType.normal": "Normal",
|
||||
"pad.settings.language": "Langue :",
|
||||
"pad.settings.about": "À propos",
|
||||
"pad.settings.poweredBy": "Fourni par",
|
||||
"pad.settings.poweredBy": "Propulsé par",
|
||||
"pad.importExport.import_export": "Importer/Exporter",
|
||||
"pad.importExport.import": "Charger un texte ou un document",
|
||||
"pad.importExport.importSuccessful": "Réussi !",
|
||||
|
@ -111,13 +111,13 @@
|
|||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Vous ne pouvez importer que des formats texte brut ou HTML. Pour des fonctionnalités d’importation plus évoluées, veuillez <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">installer AbiWord ou LibreOffice</a>.",
|
||||
"pad.modals.connected": "Connecté.",
|
||||
"pad.modals.reconnecting": "Reconnexion à votre bloc-notes...",
|
||||
"pad.modals.reconnecting": "Reconnexion à votre bloc-notes en cours...",
|
||||
"pad.modals.forcereconnect": "Forcer la reconnexion",
|
||||
"pad.modals.reconnecttimer": "Essai de reconnexion",
|
||||
"pad.modals.cancel": "Annuler",
|
||||
"pad.modals.userdup": "Ouvert dans une autre fenêtre",
|
||||
"pad.modals.userdup.explanation": "Ce bloc-notes semble être ouvert dans plusieurs fenêtres sur cet ordinateur.",
|
||||
"pad.modals.userdup.advice": "Se reconnecter en utilisant cette fenêtre.",
|
||||
"pad.modals.userdup.advice": "Se reconnecter en utilisant plutôt cette fenêtre.",
|
||||
"pad.modals.unauth": "Non autorisé",
|
||||
"pad.modals.unauth.explanation": "Vos autorisations ont été changées lors de l’affichage de cette page. Essayez de vous reconnecter.",
|
||||
"pad.modals.looping.explanation": "Nous éprouvons des problèmes de communication au serveur de synchronisation.",
|
||||
|
@ -133,10 +133,10 @@
|
|||
"pad.modals.corruptPad.cause": "Cela peut être dû à une mauvaise configuration du serveur ou à un autre comportement inattendu. Veuillez contacter l’administrateur du service.",
|
||||
"pad.modals.deleted": "Supprimé.",
|
||||
"pad.modals.deleted.explanation": "Ce bloc-notes a été supprimé.",
|
||||
"pad.modals.rateLimited": "Taux limité.",
|
||||
"pad.modals.rateLimited.explanation": "Vous avez envoyé trop de messages à ce bloc, il vous a donc déconnecté.",
|
||||
"pad.modals.rateLimited": "Flot limité.",
|
||||
"pad.modals.rateLimited.explanation": "Vous avez envoyé trop de messages à ce bloc-notes, il vous a donc déconnecté.",
|
||||
"pad.modals.rejected.explanation": "Le serveur a rejeté un message qui a été envoyé par votre navigateur.",
|
||||
"pad.modals.rejected.cause": "Le serveur peut avoir été mis à jour pendant que vous regardiez le bloc, ou il y a peut-être un bogue dans Etherpad. Essayez de recharger la page.",
|
||||
"pad.modals.rejected.cause": "Le serveur peut avoir été mis à jour pendant que vous regardiez le bloc-notes, ou il y a peut-être une anomalie dans Etherpad. Essayez de recharger la page.",
|
||||
"pad.modals.disconnected": "Vous avez été déconnecté.",
|
||||
"pad.modals.disconnected.explanation": "La connexion au serveur a échoué.",
|
||||
"pad.modals.disconnected.cause": "Il se peut que le serveur soit indisponible. Si le problème persiste, veuillez en informer l’administrateur du service.",
|
||||
|
@ -149,7 +149,7 @@
|
|||
"pad.chat.loadmessages": "Charger davantage de messages",
|
||||
"pad.chat.stick.title": "Ancrer la discussion sur l’écran",
|
||||
"pad.chat.writeMessage.placeholder": "Entrez votre message ici",
|
||||
"timeslider.followContents": "Suivre les mises à jour de contenu du bloc",
|
||||
"timeslider.followContents": "Suivre les mises à jour de contenu du bloc-notes",
|
||||
"timeslider.pageTitle": "Historique dynamique de {{appTitle}}",
|
||||
"timeslider.toolbar.returnbutton": "Retourner au bloc-notes",
|
||||
"timeslider.toolbar.authors": "Auteurs :",
|
||||
|
@ -158,7 +158,7 @@
|
|||
"timeslider.exportCurrent": "Exporter la version actuelle sous :",
|
||||
"timeslider.version": "Version {{version}}",
|
||||
"timeslider.saved": "Enregistré le {{day}} {{month}} {{year}}",
|
||||
"timeslider.playPause": "Lecture / Pause des contenus du bloc-notes",
|
||||
"timeslider.playPause": "Lecture / Pause des contenus du bloc-notes",
|
||||
"timeslider.backRevision": "Reculer d’une révision dans ce bloc-notes",
|
||||
"timeslider.forwardRevision": "Avancer d’une révision dans ce bloc-notes",
|
||||
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||
|
@ -184,10 +184,10 @@
|
|||
"pad.impexp.importing": "Import en cours...",
|
||||
"pad.impexp.confirmimport": "Importer un fichier écrasera le contenu actuel du bloc-notes. Êtes-vous sûr de vouloir le faire ?",
|
||||
"pad.impexp.convertFailed": "Nous ne pouvons pas importer ce fichier. Veuillez utiliser un autre format de document ou faire manuellement un copier/coller du texte brut",
|
||||
"pad.impexp.padHasData": "Nous n’avons pas pu importer ce fichier parce que ce bloc-notes a déjà été modifié ; veuillez l’importer vers un nouveau bloc-notes",
|
||||
"pad.impexp.uploadFailed": "Le téléversement a échoué, veuillez réessayer",
|
||||
"pad.impexp.importfailed": "Échec de l’importation",
|
||||
"pad.impexp.padHasData": "Nous n’avons pas pu importer ce fichier parce que ce bloc-notes a déjà été modifié ; veuillez l’importer vers un nouveau bloc-notes.",
|
||||
"pad.impexp.uploadFailed": "Le téléversement a échoué, veuillez réessayer.",
|
||||
"pad.impexp.importfailed": "Échec de l’import",
|
||||
"pad.impexp.copypaste": "Veuillez copier-coller",
|
||||
"pad.impexp.exportdisabled": "L’exportation au format {{type}} est désactivée. Veuillez contacter votre administrateur système pour plus de détails.",
|
||||
"pad.impexp.maxFileSize": "Fichier trop gros. Contactez votre administrateur de site pour augmenter la taille maximale des fichiers importés"
|
||||
"pad.impexp.maxFileSize": "Fichier trop gros. Contactez votre administrateur de site pour augmenter la taille maximale des fichiers importés."
|
||||
}
|
||||
|
|
|
@ -35,8 +35,5 @@
|
|||
"timeslider.month.october": "oktober",
|
||||
"timeslider.month.november": "novimber",
|
||||
"timeslider.month.december": "desimber",
|
||||
"pad.userlist.unnamed": "sûnder namme",
|
||||
"pad.userlist.guest": "Gast",
|
||||
"pad.userlist.deny": "Wegerje",
|
||||
"pad.userlist.approve": "Goedkarre"
|
||||
"pad.userlist.unnamed": "sûnder namme"
|
||||
}
|
||||
|
|
|
@ -2,12 +2,47 @@
|
|||
"@metadata": {
|
||||
"authors": [
|
||||
"Elisardojm",
|
||||
"Ghose",
|
||||
"Toliño"
|
||||
]
|
||||
},
|
||||
"admin.page-title": "Panel de administración - Etherpad",
|
||||
"admin_plugins": "Xestor de complementos",
|
||||
"admin_plugins.available": "Complementos dispoñibles",
|
||||
"admin_plugins.available_not-found": "Non se atopan complementos.",
|
||||
"admin_plugins.available_fetching": "Obtendo...",
|
||||
"admin_plugins.available_install.value": "Instalar",
|
||||
"admin_plugins.available_search.placeholder": "Buscar complementos para instalar",
|
||||
"admin_plugins.description": "Descrición",
|
||||
"admin_plugins.installed": "Complementos instalados",
|
||||
"admin_plugins.installed_fetching": "Obtendo os complementos instalados...",
|
||||
"admin_plugins.installed_nothing": "Aínda non instalaches ningún complemento.",
|
||||
"admin_plugins.installed_uninstall.value": "Desinstalar",
|
||||
"admin_plugins.last-update": "Última actualización",
|
||||
"admin_plugins.name": "Nome",
|
||||
"admin_plugins.page-title": "Xestos de complementos - Etherpad",
|
||||
"admin_plugins.version": "Versión",
|
||||
"admin_plugins_info": "Información para resolver problemas",
|
||||
"admin_plugins_info.hooks": "Ganchos instalados",
|
||||
"admin_plugins_info.hooks_client": "Ganchos do lado do cliente",
|
||||
"admin_plugins_info.hooks_server": "Ganchos do lado do servidor",
|
||||
"admin_plugins_info.parts": "Partes instaladas",
|
||||
"admin_plugins_info.plugins": "Complementos instalados",
|
||||
"admin_plugins_info.page-title": "Información do complemento - Etherpad",
|
||||
"admin_plugins_info.version": "Versión de Etherpad",
|
||||
"admin_plugins_info.version_latest": "Última versión dispoñible",
|
||||
"admin_plugins_info.version_number": "Número da versión",
|
||||
"admin_settings": "Axustes",
|
||||
"admin_settings.current": "Configuración actual",
|
||||
"admin_settings.current_example-devel": "Modelo de exemplo dos axustes de desenvolvemento",
|
||||
"admin_settings.current_example-prod": "Modelo de exemplo dos axustes en produción",
|
||||
"admin_settings.current_restart.value": "Reiniciar Etherpad",
|
||||
"admin_settings.current_save.value": "Gardar axustes",
|
||||
"admin_settings.page-title": "Axustes - Etherpad",
|
||||
"index.newPad": "Novo documento",
|
||||
"index.createOpenPad": "ou cree/abra un documento co nome:",
|
||||
"pad.toolbar.bold.title": "Negra (Ctrl-B)",
|
||||
"index.createOpenPad": "ou crea/abre un documento co nome:",
|
||||
"index.openPad": "abrir un Pad existente co nome:",
|
||||
"pad.toolbar.bold.title": "Resaltado (Ctrl-B)",
|
||||
"pad.toolbar.italic.title": "Cursiva (Ctrl-I)",
|
||||
"pad.toolbar.underline.title": "Subliñar (Ctrl-U)",
|
||||
"pad.toolbar.strikethrough.title": "Riscar (Ctrl+5)",
|
||||
|
@ -17,28 +52,30 @@
|
|||
"pad.toolbar.unindent.title": "Sen sangría (Maiús.+TAB)",
|
||||
"pad.toolbar.undo.title": "Desfacer (Ctrl-Z)",
|
||||
"pad.toolbar.redo.title": "Refacer (Ctrl-Y)",
|
||||
"pad.toolbar.clearAuthorship.title": "Limpar as cores de identificación dos autores (Ctrl+Shift+C)",
|
||||
"pad.toolbar.clearAuthorship.title": "Eliminar as cores que identifican ás autoras (Ctrl+Shift+C)",
|
||||
"pad.toolbar.import_export.title": "Importar/Exportar desde/a diferentes formatos de ficheiro",
|
||||
"pad.toolbar.timeslider.title": "Liña do tempo",
|
||||
"pad.toolbar.savedRevision.title": "Gardar a revisión",
|
||||
"pad.toolbar.settings.title": "Configuracións",
|
||||
"pad.toolbar.settings.title": "Axustes",
|
||||
"pad.toolbar.embed.title": "Compartir e incorporar este documento",
|
||||
"pad.toolbar.showusers.title": "Mostrar os usuarios deste documento",
|
||||
"pad.toolbar.showusers.title": "Mostrar as usuarias deste documento",
|
||||
"pad.colorpicker.save": "Gardar",
|
||||
"pad.colorpicker.cancel": "Cancelar",
|
||||
"pad.loading": "Cargando...",
|
||||
"pad.noCookie": "Non se puido atopar a cookie. Por favor, habilite as cookies no seu navegador!",
|
||||
"pad.permissionDenied": "Non ten permiso para acceder a este documento",
|
||||
"pad.noCookie": "Non se puido atopar a cookie. Por favor, habilita as cookies no teu navegador! A túa sesión e axustes non se gardarán entre visitas. Esto podería deberse a que Etherpad está incluído nalgún iFrame nalgúns navegadores. Asegúrate de que Etherpad está no mesmo subdominio/dominio que o iFrame pai",
|
||||
"pad.permissionDenied": "Non tes permiso para acceder a este documento",
|
||||
"pad.settings.padSettings": "Configuracións do documento",
|
||||
"pad.settings.myView": "A miña vista",
|
||||
"pad.settings.stickychat": "Chat sempre visible",
|
||||
"pad.settings.chatandusers": "Mostrar o chat e os usuarios",
|
||||
"pad.settings.colorcheck": "Cores de identificación",
|
||||
"pad.settings.linenocheck": "Números de liña",
|
||||
"pad.settings.rtlcheck": "Quere ler o contido da dereita á esquerda?",
|
||||
"pad.settings.rtlcheck": "Queres ler o contido da dereita á esquerda?",
|
||||
"pad.settings.fontType": "Tipo de letra:",
|
||||
"pad.settings.fontType.normal": "Normal",
|
||||
"pad.settings.language": "Lingua:",
|
||||
"pad.settings.about": "Acerca de",
|
||||
"pad.settings.poweredBy": "Grazas a",
|
||||
"pad.importExport.import_export": "Importar/Exportar",
|
||||
"pad.importExport.import": "Cargar un ficheiro de texto ou documento",
|
||||
"pad.importExport.importSuccessful": "Correcto!",
|
||||
|
@ -49,9 +86,9 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Só pode importar texto simple ou formatos HTML. Para obter máis información sobre as características de importación avanzadas <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">instale AbiWord</a>.",
|
||||
"pad.importExport.abiword.innerHTML": "Só podes importar texto simple ou formatos HTML. Para obter máis información sobre as características de importación avanzadas <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">instala AbiWord</a>.",
|
||||
"pad.modals.connected": "Conectado.",
|
||||
"pad.modals.reconnecting": "Reconectando co seu documento...",
|
||||
"pad.modals.reconnecting": "Reconectando co teu documento...",
|
||||
"pad.modals.forcereconnect": "Forzar a reconexión",
|
||||
"pad.modals.reconnecttimer": "Intentarase reconectar en",
|
||||
"pad.modals.cancel": "Cancelar",
|
||||
|
@ -73,6 +110,10 @@
|
|||
"pad.modals.corruptPad.cause": "Isto pode deberse a unha cofiguración errónea do servidor ou algún outro comportamento inesperado. Póñase en contacto co administrador do servizo.",
|
||||
"pad.modals.deleted": "Borrado.",
|
||||
"pad.modals.deleted.explanation": "Este documento foi eliminado.",
|
||||
"pad.modals.rateLimited": "Taxa limitada.",
|
||||
"pad.modals.rateLimited.explanation": "Enviaches demasiadas mensaxes a este documento polo que te desconectamos.",
|
||||
"pad.modals.rejected.explanation": "O servidor rexeitou unha mensaxe que o teu navegador enviou.",
|
||||
"pad.modals.rejected.cause": "O servidor podería ter sido actualizado mentras ollabas o documento, ou pode que sexa un fallo de Etherpad. Intenta recargar a páxina.",
|
||||
"pad.modals.disconnected": "Foi desconectado.",
|
||||
"pad.modals.disconnected.explanation": "Perdeuse a conexión co servidor",
|
||||
"pad.modals.disconnected.cause": "O servidor non está dispoñible. Póñase en contacto co administrador do servizo se o problema continúa.",
|
||||
|
@ -83,6 +124,9 @@
|
|||
"pad.chat": "Chat",
|
||||
"pad.chat.title": "Abrir o chat deste documento.",
|
||||
"pad.chat.loadmessages": "Cargar máis mensaxes",
|
||||
"pad.chat.stick.title": "Pegar a conversa á pantalla",
|
||||
"pad.chat.writeMessage.placeholder": "Escribe aquí a túa mensaxe",
|
||||
"timeslider.followContents": "Segue as actualizacións do contido",
|
||||
"timeslider.pageTitle": "Liña do tempo de {{appTitle}}",
|
||||
"timeslider.toolbar.returnbutton": "Volver ao documento",
|
||||
"timeslider.toolbar.authors": "Autores:",
|
||||
|
@ -112,7 +156,7 @@
|
|||
"pad.savedrevs.timeslider": "Pode consultar as revisións gardadas visitando a liña do tempo",
|
||||
"pad.userlist.entername": "Insira o seu nome",
|
||||
"pad.userlist.unnamed": "anónimo",
|
||||
"pad.editbar.clearcolors": "Quere limpar as cores de identificación dos autores en todo o documento?",
|
||||
"pad.editbar.clearcolors": "Eliminar as cores relativas aos autores en todo o documento? Non se poderán recuperar",
|
||||
"pad.impexp.importbutton": "Importar agora",
|
||||
"pad.impexp.importing": "Importando...",
|
||||
"pad.impexp.confirmimport": "A importación dun ficheiro ha sobrescribir o texto actual do documento. Está seguro de querer continuar?",
|
||||
|
@ -121,5 +165,6 @@
|
|||
"pad.impexp.uploadFailed": "Houbo un erro ao cargar o ficheiro; inténteo de novo",
|
||||
"pad.impexp.importfailed": "Fallou a importación",
|
||||
"pad.impexp.copypaste": "Copie e pegue",
|
||||
"pad.impexp.exportdisabled": "A exportación en formato {{type}} está desactivada. Póñase en contacto co administrador do sistema se quere máis detalles."
|
||||
"pad.impexp.exportdisabled": "A exportación en formato {{type}} está desactivada. Póñase en contacto co administrador do sistema se quere máis detalles.",
|
||||
"pad.impexp.maxFileSize": "Ficheiro demasiado granda. Contacta coa administración para aumentar o tamaño permitido para importacións"
|
||||
}
|
||||
|
|
|
@ -45,9 +45,6 @@
|
|||
"timeslider.month.december": "ડિસેમ્બર",
|
||||
"pad.userlist.entername": "તમારું નામ દાખલ કરો",
|
||||
"pad.userlist.unnamed": "અનામી",
|
||||
"pad.userlist.guest": "મહેમાન",
|
||||
"pad.userlist.deny": "નકારો",
|
||||
"pad.userlist.approve": "મંજૂર",
|
||||
"pad.impexp.importbutton": "આયાત કરો",
|
||||
"pad.impexp.importing": "આયાત કરે છે..."
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue