mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-06 07:07:12 -04:00
Compare commits
No commits in common. "develop" and "v2.2.2" have entirely different histories.
218 changed files with 27474 additions and 11417 deletions
26
.github/workflows/backend-tests.yml
vendored
26
.github/workflows/backend-tests.yml
vendored
|
@ -24,7 +24,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [20, 22, 23]
|
node: [18, 20, 22]
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout repository
|
name: Checkout repository
|
||||||
|
@ -53,7 +53,7 @@ jobs:
|
||||||
run: pnpm config set auto-install-peers false
|
run: pnpm config set auto-install-peers false
|
||||||
-
|
-
|
||||||
name: Install libreoffice
|
name: Install libreoffice
|
||||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.0
|
uses: awalsh128/cache-apt-pkgs-action@v1.4.2
|
||||||
with:
|
with:
|
||||||
packages: libreoffice libreoffice-pdfimport
|
packages: libreoffice libreoffice-pdfimport
|
||||||
version: 1.0
|
version: 1.0
|
||||||
|
@ -69,9 +69,6 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Run the backend tests
|
name: Run the backend tests
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
- name: Run the new vitest tests
|
|
||||||
working-directory: src
|
|
||||||
run: pnpm run test:vitest
|
|
||||||
|
|
||||||
withpluginsLinux:
|
withpluginsLinux:
|
||||||
# run on pushes to any branch
|
# run on pushes to any branch
|
||||||
|
@ -84,7 +81,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [20, 22, 23]
|
node: [18, 20, 22]
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout repository
|
name: Checkout repository
|
||||||
|
@ -113,7 +110,7 @@ jobs:
|
||||||
run: pnpm config set auto-install-peers false
|
run: pnpm config set auto-install-peers false
|
||||||
-
|
-
|
||||||
name: Install libreoffice
|
name: Install libreoffice
|
||||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.0
|
uses: awalsh128/cache-apt-pkgs-action@v1.4.2
|
||||||
with:
|
with:
|
||||||
packages: libreoffice libreoffice-pdfimport
|
packages: libreoffice libreoffice-pdfimport
|
||||||
version: 1.0
|
version: 1.0
|
||||||
|
@ -145,9 +142,6 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Run the backend tests
|
name: Run the backend tests
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
- name: Run the new vitest tests
|
|
||||||
working-directory: src
|
|
||||||
run: pnpm run test:vitest
|
|
||||||
|
|
||||||
withoutpluginsWindows:
|
withoutpluginsWindows:
|
||||||
# run on pushes to any branch
|
# run on pushes to any branch
|
||||||
|
@ -199,11 +193,7 @@ jobs:
|
||||||
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
|
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
|
||||||
-
|
-
|
||||||
name: Run the backend tests
|
name: Run the backend tests
|
||||||
working-directory: src
|
run: cd src && pnpm test
|
||||||
run: pnpm test
|
|
||||||
- name: Run the new vitest tests
|
|
||||||
working-directory: src
|
|
||||||
run: pnpm run test:vitest
|
|
||||||
|
|
||||||
withpluginsWindows:
|
withpluginsWindows:
|
||||||
# run on pushes to any branch
|
# run on pushes to any branch
|
||||||
|
@ -283,8 +273,4 @@ jobs:
|
||||||
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
|
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
|
||||||
-
|
-
|
||||||
name: Run the backend tests
|
name: Run the backend tests
|
||||||
working-directory: src
|
run: cd src && pnpm test
|
||||||
run: pnpm test
|
|
||||||
- name: Run the new vitest tests
|
|
||||||
working-directory: src
|
|
||||||
run: pnpm run test:vitest
|
|
||||||
|
|
31
.github/workflows/docker.yml
vendored
31
.github/workflows/docker.yml
vendored
|
@ -22,9 +22,6 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Check out
|
name: Check out
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
path: etherpad
|
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
@ -36,7 +33,7 @@ jobs:
|
||||||
name: Build and export to Docker
|
name: Build and export to Docker
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: ./etherpad
|
context: .
|
||||||
target: production
|
target: production
|
||||||
load: true
|
load: true
|
||||||
tags: ${{ env.TEST_TAG }}
|
tags: ${{ env.TEST_TAG }}
|
||||||
|
@ -65,7 +62,6 @@ jobs:
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
working-directory: etherpad
|
|
||||||
run: |
|
run: |
|
||||||
docker run --rm -d -p 9001:9001 --name test ${{ env.TEST_TAG }}
|
docker run --rm -d -p 9001:9001 --name test ${{ env.TEST_TAG }}
|
||||||
./bin/installDeps.sh
|
./bin/installDeps.sh
|
||||||
|
@ -102,11 +98,10 @@ jobs:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build and push
|
name: Build and push
|
||||||
id: build-docker
|
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: ./etherpad
|
context: .
|
||||||
target: production
|
target: production
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
|
@ -116,29 +111,7 @@ jobs:
|
||||||
uses: peter-evans/dockerhub-description@v4
|
uses: peter-evans/dockerhub-description@v4
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
with:
|
with:
|
||||||
readme-filepath: ./etherpad/README.md
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
repository: etherpad/etherpad
|
repository: etherpad/etherpad
|
||||||
enable-url-completion: true
|
enable-url-completion: true
|
||||||
- name: Check out
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
path: ether-charts
|
|
||||||
repository: ether/ether-charts
|
|
||||||
token: ${{ secrets.ETHER_CHART_TOKEN }}
|
|
||||||
- name: Update tag in values-dev.yaml
|
|
||||||
if: success() && github.ref == 'refs/heads/develop'
|
|
||||||
working-directory: ether-charts
|
|
||||||
run: |
|
|
||||||
sed -i 's/tag: ".*"/tag: "${{ steps.build-docker.outputs.digest }}"/' values-dev.yaml
|
|
||||||
- name: Commit and push changes
|
|
||||||
working-directory: ether-charts
|
|
||||||
if: success() && github.ref == 'refs/heads/develop'
|
|
||||||
run: |
|
|
||||||
git config --global user.name 'github-actions[bot]'
|
|
||||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
|
||||||
git add values-dev.yaml
|
|
||||||
git commit -m 'Update develop image tag'
|
|
||||||
git push
|
|
||||||
|
|
4
.github/workflows/frontend-admin-tests.yml
vendored
4
.github/workflows/frontend-admin-tests.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [20, 22, 23]
|
node: [20, 22]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
|
@ -50,7 +50,7 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v3
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
|
6
.github/workflows/frontend-tests.yml
vendored
6
.github/workflows/frontend-tests.yml
vendored
|
@ -57,7 +57,7 @@ jobs:
|
||||||
name: Create settings.json
|
name: Create settings.json
|
||||||
run: cp ./src/tests/settings.json settings.json
|
run: cp ./src/tests/settings.json settings.json
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v3
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
@ -127,7 +127,7 @@ jobs:
|
||||||
- name: Create settings.json
|
- name: Create settings.json
|
||||||
run: cp ./src/tests/settings.json settings.json
|
run: cp ./src/tests/settings.json settings.json
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v3
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
@ -175,7 +175,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v3
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [20, 22, 23]
|
node: [18, 20, 22]
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Check out latest release
|
name: Check out latest release
|
||||||
|
@ -43,7 +43,7 @@ jobs:
|
||||||
- name: Only install direct dependencies
|
- name: Only install direct dependencies
|
||||||
run: pnpm config set auto-install-peers false
|
run: pnpm config set auto-install-peers false
|
||||||
- name: Install libreoffice
|
- name: Install libreoffice
|
||||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.0
|
uses: awalsh128/cache-apt-pkgs-action@v1.4.2
|
||||||
with:
|
with:
|
||||||
packages: libreoffice libreoffice-pdfimport
|
packages: libreoffice libreoffice-pdfimport
|
||||||
version: 1.0
|
version: 1.0
|
||||||
|
@ -62,7 +62,7 @@ jobs:
|
||||||
run: pnpm config set auto-install-peers false
|
run: pnpm config set auto-install-peers false
|
||||||
-
|
-
|
||||||
name: Install libreoffice
|
name: Install libreoffice
|
||||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.0
|
uses: awalsh128/cache-apt-pkgs-action@v1.4.2
|
||||||
with:
|
with:
|
||||||
packages: libreoffice libreoffice-pdfimport
|
packages: libreoffice libreoffice-pdfimport
|
||||||
version: 1.0
|
version: 1.0
|
||||||
|
|
62
CHANGELOG.md
62
CHANGELOG.md
|
@ -1,65 +1,3 @@
|
||||||
# 2.3.0
|
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
|
||||||
|
|
||||||
- Added possibility to cluster Etherpads behind reverse proxy. There is now a new reverse proxy designed for Etherpads that handles multiple Etherpads and the created pads in them. It will assign the pad assignement to an Etherpad at random but once the choice was made it will always reverse proxy the same backend. This allows to host multiple concurrent Etherpads and benefit from multi core systems even though one Etherpad is singlethreaded.
|
|
||||||
- Added reverse proxy configuration for replacing Nginx. In the past there were some issues with nginx and its configuration. This reverse proxy allows you to handle your configuration with ease.
|
|
||||||
|
|
||||||
If you want to find out more about the reverse proxy method check out the repository https://github.com/ether/etherpad-proxy . It also contains a sample docker-compose file with three Etherpads and one etherpad-proxy. Of course you need to adapt the settings.json.template to your liking and map it into the reverse proxy image before you are ready :).
|
|
||||||
|
|
||||||
|
|
||||||
- Added client authorization to work with Etherpad. Before it would get blocked because it doesn't have the required claim. As this is now fixed etherpad-proxy can also work with your new OAuth2 configuration and retrieve a token via client credentials flow.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 2.2.7
|
|
||||||
|
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
|
||||||
|
|
||||||
- We migrated all important pages to React 19 and React Router v7
|
|
||||||
|
|
||||||
Besides that only dependency updates.
|
|
||||||
|
|
||||||
|
|
||||||
-> Have a merry Christmas and a happy new year. 🎄 🎁
|
|
||||||
|
|
||||||
|
|
||||||
# 2.2.6
|
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
|
||||||
|
|
||||||
- Added option to delete a pad by the creator. This option can be found in the settings menu. When you click on it you get a confirm dialog and after that you have the chance to completely erase the pad.
|
|
||||||
|
|
||||||
|
|
||||||
# 2.2.5
|
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
|
||||||
|
|
||||||
- Fixed timeslider not scrolling when the revision count is a multiple of 100
|
|
||||||
- Added new Restful API for version 2 of Etherpad. It is available at /api-docs
|
|
||||||
|
|
||||||
|
|
||||||
# 2.2.4
|
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
|
||||||
|
|
||||||
- Switched to new SQLite backend
|
|
||||||
- Fixed rusty-store-kv module not found
|
|
||||||
|
|
||||||
|
|
||||||
# 2.2.3
|
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
|
||||||
|
|
||||||
- Introduced a new in process database `rustydb` that represents a fast key value store written in Rust.
|
|
||||||
- Readded window._ as a shortcut for getting text
|
|
||||||
- Added support for migrating any ueberdb database to another. You can now switch as you please. See here: https://docs.etherpad.org/cli.html
|
|
||||||
- Further Typescript movements
|
|
||||||
- A lot of security issues fixed and reviewed in this release. Please update.
|
|
||||||
|
|
||||||
|
|
||||||
# 2.2.2
|
# 2.2.2
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
### Notable enhancements and fixes
|
||||||
|
|
63
Dockerfile
63
Dockerfile
|
@ -3,10 +3,9 @@
|
||||||
# https://github.com/ether/etherpad-lite
|
# https://github.com/ether/etherpad-lite
|
||||||
#
|
#
|
||||||
# Author: muxator
|
# Author: muxator
|
||||||
ARG BUILD_ENV=git
|
|
||||||
|
|
||||||
FROM node:alpine AS adminbuild
|
FROM node:alpine AS adminbuild
|
||||||
RUN npm install -g pnpm@latest
|
RUN npm install -g pnpm@9.0.4
|
||||||
WORKDIR /opt/etherpad-lite
|
WORKDIR /opt/etherpad-lite
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pnpm install
|
RUN pnpm install
|
||||||
|
@ -50,14 +49,6 @@ ARG ETHERPAD_PLUGINS=
|
||||||
# ETHERPAD_LOCAL_PLUGINS="../ep_my_plugin ../ep_another_plugin"
|
# ETHERPAD_LOCAL_PLUGINS="../ep_my_plugin ../ep_another_plugin"
|
||||||
ARG ETHERPAD_LOCAL_PLUGINS=
|
ARG ETHERPAD_LOCAL_PLUGINS=
|
||||||
|
|
||||||
# github plugins to install while building the container. By default no plugins are
|
|
||||||
# installed.
|
|
||||||
# If given a value, it has to be a space-separated, quoted list of plugin names.
|
|
||||||
#
|
|
||||||
# EXAMPLE:
|
|
||||||
# ETHERPAD_GITHUB_PLUGINS="ether/ep_plugin"
|
|
||||||
ARG ETHERPAD_GITHUB_PLUGINS=
|
|
||||||
|
|
||||||
# Control whether abiword will be installed, enabling exports to DOC/PDF/ODT formats.
|
# Control whether abiword will be installed, enabling exports to DOC/PDF/ODT formats.
|
||||||
# By default, it is not installed.
|
# By default, it is not installed.
|
||||||
# If given any value, abiword will be installed.
|
# If given any value, abiword will be installed.
|
||||||
|
@ -100,7 +91,7 @@ RUN mkdir -p "${EP_DIR}" && chown etherpad:etherpad "${EP_DIR}"
|
||||||
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
|
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
|
||||||
RUN \
|
RUN \
|
||||||
mkdir -p /usr/share/man/man1 && \
|
mkdir -p /usr/share/man/man1 && \
|
||||||
npm install pnpm@latest -g && \
|
npm install pnpm@9.0.4 -g && \
|
||||||
apk update && apk upgrade && \
|
apk update && apk upgrade && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
@ -114,49 +105,26 @@ USER etherpad
|
||||||
WORKDIR "${EP_DIR}"
|
WORKDIR "${EP_DIR}"
|
||||||
|
|
||||||
# etherpads version feature requires this. Only copy what is really needed
|
# etherpads version feature requires this. Only copy what is really needed
|
||||||
|
COPY --chown=etherpad:etherpad ./.git/HEA[D] ./.git/HEAD
|
||||||
|
COPY --chown=etherpad:etherpad ./.git/ref[s] ./.git/refs
|
||||||
COPY --chown=etherpad:etherpad ${SETTINGS} ./settings.json
|
COPY --chown=etherpad:etherpad ${SETTINGS} ./settings.json
|
||||||
COPY --chown=etherpad:etherpad ./var ./var
|
COPY --chown=etherpad:etherpad ./var ./var
|
||||||
COPY --chown=etherpad:etherpad ./bin ./bin
|
COPY --chown=etherpad:etherpad ./bin ./bin
|
||||||
COPY --chown=etherpad:etherpad ./pnpm-workspace.yaml ./package.json ./
|
COPY --chown=etherpad:etherpad ./pnpm-workspace.yaml ./package.json ./
|
||||||
|
|
||||||
|
FROM build AS development
|
||||||
|
|
||||||
|
COPY --chown=etherpad:etherpad ./src/package.json .npmrc ./src/
|
||||||
FROM build AS build_git
|
|
||||||
ONBUILD COPY --chown=etherpad:etherpad ./.git/HEA[D] ./.git/HEAD
|
|
||||||
ONBUILD COPY --chown=etherpad:etherpad ./.git/ref[s] ./.git/refs
|
|
||||||
|
|
||||||
FROM build AS build_copy
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FROM build_${BUILD_ENV} AS development
|
|
||||||
|
|
||||||
ARG ETHERPAD_PLUGINS=
|
|
||||||
ARG ETHERPAD_LOCAL_PLUGINS=
|
|
||||||
ARG ETHERPAD_LOCAL_PLUGINS_ENV=
|
|
||||||
ARG ETHERPAD_GITHUB_PLUGINS=
|
|
||||||
|
|
||||||
COPY --chown=etherpad:etherpad ./src/ ./src/
|
|
||||||
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/ templates/admin./src/templates/admin
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/ templates/admin./src/templates/admin
|
||||||
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/static/oidc ./src/static/oidc
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/static/oidc ./src/static/oidc
|
||||||
|
|
||||||
COPY --chown=etherpad:etherpad ./local_plugin[s] ./local_plugins/
|
|
||||||
|
|
||||||
RUN bash -c ./bin/installLocalPlugins.sh
|
|
||||||
|
|
||||||
RUN bin/installDeps.sh && \
|
RUN bin/installDeps.sh && \
|
||||||
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_GITHUB_PLUGINS}" ]; then \
|
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_LOCAL_PLUGINS}" ]; then \
|
||||||
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_GITHUB_PLUGINS:+--github ${ETHERPAD_GITHUB_PLUGINS}}; \
|
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_LOCAL_PLUGINS:+--path ${ETHERPAD_LOCAL_PLUGINS}}; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
FROM build_${BUILD_ENV} AS production
|
FROM build AS production
|
||||||
|
|
||||||
ARG ETHERPAD_PLUGINS=
|
|
||||||
ARG ETHERPAD_LOCAL_PLUGINS=
|
|
||||||
ARG ETHERPAD_LOCAL_PLUGINS_ENV=
|
|
||||||
ARG ETHERPAD_GITHUB_PLUGINS=
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV ETHERPAD_PRODUCTION=true
|
ENV ETHERPAD_PRODUCTION=true
|
||||||
|
@ -165,14 +133,11 @@ COPY --chown=etherpad:etherpad ./src ./src
|
||||||
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/templates/admin ./src/templates/admin
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/templates/admin ./src/templates/admin
|
||||||
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/static/oidc ./src/static/oidc
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/static/oidc ./src/static/oidc
|
||||||
|
|
||||||
COPY --chown=etherpad:etherpad ./local_plugin[s] ./local_plugins/
|
RUN bin/installDeps.sh && rm -rf ~/.npm && rm -rf ~/.local && rm -rf ~/.cache && \
|
||||||
|
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_LOCAL_PLUGINS}" ]; then \
|
||||||
|
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_LOCAL_PLUGINS:+--path ${ETHERPAD_LOCAL_PLUGINS}}; \
|
||||||
|
fi
|
||||||
|
|
||||||
RUN bash -c ./bin/installLocalPlugins.sh
|
|
||||||
|
|
||||||
RUN bin/installDeps.sh && \
|
|
||||||
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_GITHUB_PLUGINS}" ]; then \
|
|
||||||
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_GITHUB_PLUGINS:+--github ${ETHERPAD_GITHUB_PLUGINS}}; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy the configuration file.
|
# Copy the configuration file.
|
||||||
COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json
|
COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json
|
||||||
|
|
27
README.md
27
README.md
|
@ -90,7 +90,7 @@ services:
|
||||||
# ports:
|
# ports:
|
||||||
# - "5432:5432"
|
# - "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data/pgdata
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
@ -174,31 +174,6 @@ following plugins:
|
||||||
that each user's chosen color, display name, comment ownership, etc. is
|
that each user's chosen color, display name, comment ownership, etc. is
|
||||||
strongly linked to their account.
|
strongly linked to their account.
|
||||||
|
|
||||||
### Upgrade Etherpad
|
|
||||||
|
|
||||||
Run the following command in your Etherpad folder to upgrade
|
|
||||||
|
|
||||||
1. Stop any running Etherpad (manual, systemd ...)
|
|
||||||
2. Get present version
|
|
||||||
```sh
|
|
||||||
git -P tag --contains
|
|
||||||
```
|
|
||||||
3. List versions available
|
|
||||||
```sh
|
|
||||||
git -P tag --list "v*" --merged
|
|
||||||
```
|
|
||||||
4. Select the version
|
|
||||||
```sh
|
|
||||||
git checkout v2.2.5
|
|
||||||
git switch -c v2.2.5
|
|
||||||
```
|
|
||||||
5. Upgrade Etherpad
|
|
||||||
```sh
|
|
||||||
./bin/run.sh
|
|
||||||
```
|
|
||||||
6. Stop with [CTRL-C]
|
|
||||||
7. Restart your Etherpad service
|
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
### Tweak the settings
|
### Tweak the settings
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "admin",
|
"name": "admin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.3.0",
|
"version": "2.2.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -11,32 +11,32 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-switch": "^1.2.2"
|
"@radix-ui/react-switch": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.11",
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-toast": "^1.2.11",
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^18.3.2",
|
||||||
"@types/react-dom": "^19.1.3",
|
"@types/react-dom": "^18.2.25",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
"@typescript-eslint/eslint-plugin": "^8.0.1",
|
||||||
"@typescript-eslint/parser": "^8.31.1",
|
"@typescript-eslint/parser": "^8.0.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.9.0",
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.8.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.9",
|
||||||
"i18next": "^25.0.2",
|
"i18next": "^23.12.2",
|
||||||
"i18next-browser-languagedetector": "^8.1.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"lucide-react": "^0.507.0",
|
"lucide-react": "^0.426.0",
|
||||||
"react": "^19.1.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.56.2",
|
"react-hook-form": "^7.52.2",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-router-dom": "^7.5.3",
|
"react-router-dom": "^6.26.0",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.7.5",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^6.3.5",
|
"vite": "^5.4.0",
|
||||||
"vite-plugin-static-copy": "^2.3.1",
|
"vite-plugin-static-copy": "^1.0.6",
|
||||||
"vite-plugin-svgr": "^4.3.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"zustand": "^5.0.4"
|
"zustand": "^4.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
"ep_adminpads2_autoupdate.title": "Aktiviert oder deaktiviert automatische Aktualisierungen für die aktuelle Abfrage.",
|
"ep_adminpads2_autoupdate.title": "Aktiviert oder deaktiviert automatische Aktualisierungen für die aktuelle Abfrage.",
|
||||||
"ep_adminpads2_confirm": "Willst du das Pad {{padID}} wirklich löschen?",
|
"ep_adminpads2_confirm": "Willst du das Pad {{padID}} wirklich löschen?",
|
||||||
"ep_adminpads2_delete.value": "Löschen",
|
"ep_adminpads2_delete.value": "Löschen",
|
||||||
"ep_adminpads2_cleanup": "Historie aufräumen",
|
|
||||||
"ep_adminpads2_last-edited": "Zuletzt bearbeitet",
|
"ep_adminpads2_last-edited": "Zuletzt bearbeitet",
|
||||||
"ep_adminpads2_loading": "Lädt...",
|
"ep_adminpads2_loading": "Lädt...",
|
||||||
"ep_adminpads2_manage-pads": "Pads verwalten",
|
"ep_adminpads2_manage-pads": "Pads verwalten",
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
"ep_adminpads2_autoupdate.title": "Enables or disables automatic updates for the current query.",
|
"ep_adminpads2_autoupdate.title": "Enables or disables automatic updates for the current query.",
|
||||||
"ep_adminpads2_confirm": "Do you really want to delete the pad {{padID}}?",
|
"ep_adminpads2_confirm": "Do you really want to delete the pad {{padID}}?",
|
||||||
"ep_adminpads2_delete.value": "Delete",
|
"ep_adminpads2_delete.value": "Delete",
|
||||||
"ep_adminpads2_cleanup": "Cleanup revisions",
|
|
||||||
"ep_adminpads2_last-edited": "Last edited",
|
"ep_adminpads2_last-edited": "Last edited",
|
||||||
"ep_adminpads2_loading": "Loading…",
|
"ep_adminpads2_loading": "Loading…",
|
||||||
"ep_adminpads2_manage-pads": "Manage pads",
|
"ep_adminpads2_manage-pads": "Manage pads",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {useEffect, useState} from 'react'
|
import {useEffect} from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import {connect} from 'socket.io-client'
|
import {connect} from 'socket.io-client'
|
||||||
import {isJSONClean} from './utils/utils.ts'
|
import {isJSONClean} from './utils/utils.ts'
|
||||||
|
@ -6,115 +6,107 @@ import {NavLink, Outlet, useNavigate} from "react-router-dom";
|
||||||
import {useStore} from "./store/store.ts";
|
import {useStore} from "./store/store.ts";
|
||||||
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall, LucideMenu} from "lucide-react";
|
import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall} from "lucide-react";
|
||||||
|
|
||||||
const WS_URL = import.meta.env.DEV ? 'http://localhost:9001' : ''
|
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
||||||
export const App = () => {
|
export const App = ()=> {
|
||||||
const setSettings = useStore(state => state.setSettings);
|
const setSettings = useStore(state => state.setSettings);
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [sidebarOpen, setSidebarOpen] = useState<boolean>(true)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/admin-auth/', {
|
fetch('/admin-auth/', {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((value) => {
|
}).then((value)=>{
|
||||||
if (!value.ok) {
|
if(!value.ok){
|
||||||
navigate('/login')
|
navigate('/login')
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(()=>{
|
||||||
navigate('/login')
|
navigate('/login')
|
||||||
})
|
})
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t('admin.page-title')
|
document.title = t('admin.page-title')
|
||||||
|
|
||||||
useStore.getState().setShowLoading(true);
|
useStore.getState().setShowLoading(true);
|
||||||
const settingSocket = connect(`${WS_URL}/settings`, {
|
const settingSocket = connect(`${WS_URL}/settings`, {
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
|
const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
})
|
})
|
||||||
|
|
||||||
pluginsSocket.on('connect', () => {
|
pluginsSocket.on('connect', () => {
|
||||||
useStore.getState().setPluginsSocket(pluginsSocket);
|
useStore.getState().setPluginsSocket(pluginsSocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
settingSocket.on('connect', () => {
|
settingSocket.on('connect', () => {
|
||||||
useStore.getState().setSettingsSocket(settingSocket);
|
useStore.getState().setSettingsSocket(settingSocket);
|
||||||
useStore.getState().setShowLoading(false)
|
useStore.getState().setShowLoading(false)
|
||||||
settingSocket.emit('load');
|
settingSocket.emit('load');
|
||||||
console.log('connected');
|
console.log('connected');
|
||||||
});
|
});
|
||||||
|
|
||||||
settingSocket.on('disconnect', (reason) => {
|
settingSocket.on('disconnect', (reason) => {
|
||||||
// The settingSocket.io client will automatically try to reconnect for all reasons other than "io
|
// The settingSocket.io client will automatically try to reconnect for all reasons other than "io
|
||||||
// server disconnect".
|
// server disconnect".
|
||||||
useStore.getState().setShowLoading(true)
|
useStore.getState().setShowLoading(true)
|
||||||
if (reason === 'io server disconnect') {
|
if (reason === 'io server disconnect') {
|
||||||
settingSocket.connect();
|
settingSocket.connect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
settingSocket.on('settings', (settings) => {
|
settingSocket.on('settings', (settings) => {
|
||||||
/* Check whether the settings.json is authorized to be viewed */
|
/* Check whether the settings.json is authorized to be viewed */
|
||||||
if (settings.results === 'NOT_ALLOWED') {
|
if (settings.results === 'NOT_ALLOWED') {
|
||||||
console.log('Not allowed to view settings.json')
|
console.log('Not allowed to view settings.json')
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check to make sure the JSON is clean before proceeding */
|
/* Check to make sure the JSON is clean before proceeding */
|
||||||
if (isJSONClean(settings.results)) {
|
if (isJSONClean(settings.results)) {
|
||||||
setSettings(settings.results);
|
setSettings(settings.results);
|
||||||
} else {
|
} else {
|
||||||
alert('Invalid JSON');
|
alert('Invalid JSON');
|
||||||
}
|
}
|
||||||
useStore.getState().setShowLoading(false);
|
useStore.getState().setShowLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
settingSocket.on('saveprogress', (status) => {
|
settingSocket.on('saveprogress', (status)=>{
|
||||||
console.log(status)
|
console.log(status)
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
settingSocket.disconnect();
|
settingSocket.disconnect();
|
||||||
pluginsSocket.disconnect()
|
pluginsSocket.disconnect()
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <div id="wrapper" className={`${sidebarOpen ? '': 'closed' }`}>
|
return <div id="wrapper">
|
||||||
<LoadingScreen/>
|
<LoadingScreen/>
|
||||||
<div className="menu">
|
<div className="menu">
|
||||||
<div className="inner-menu">
|
<div className="inner-menu">
|
||||||
<span>
|
<span>
|
||||||
<Crown width={40} height={40}/>
|
<Crown width={40} height={40}/>
|
||||||
<h1>Etherpad</h1>
|
<h1>Etherpad</h1>
|
||||||
</span>
|
</span>
|
||||||
<ul onClick={()=>{
|
<ul>
|
||||||
if (window.innerWidth < 768) {
|
<li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
|
||||||
setSidebarOpen(false)
|
<li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
|
||||||
}
|
<li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
||||||
}}>
|
<li><NavLink to={"/pads"}><NotepadText/><Trans
|
||||||
<li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
|
i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
||||||
<li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
|
<li><NavLink to={"/shout"}><PhoneCall/>Communication</NavLink></li>
|
||||||
<li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
</ul>
|
||||||
<li><NavLink to={"/pads"}><NotepadText/><Trans
|
</div>
|
||||||
i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
</div>
|
||||||
<li><NavLink to={"/shout"}><PhoneCall/>Communication</NavLink></li>
|
<div className="innerwrapper">
|
||||||
</ul>
|
<Outlet/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="icon-button" onClick={() => {
|
|
||||||
setSidebarOpen(!sidebarOpen)
|
|
||||||
}}><LucideMenu/></button>
|
|
||||||
<div className="innerwrapper">
|
|
||||||
<Outlet/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {FC, JSX, ReactElement} from "react";
|
import {FC, ReactElement} from "react";
|
||||||
|
|
||||||
export type IconButtonProps = {
|
export type IconButtonProps = {
|
||||||
icon: JSX.Element,
|
icon: JSX.Element,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,54 +4,16 @@ import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts";
|
||||||
import {useDebounce} from "../utils/useDebounce.ts";
|
import {useDebounce} from "../utils/useDebounce.ts";
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import {SearchField} from "../components/SearchField.tsx";
|
import {SearchField} from "../components/SearchField.tsx";
|
||||||
import {ArrowUpFromDot, Download, Trash} from "lucide-react";
|
import {Download, Trash} from "lucide-react";
|
||||||
import {IconButton} from "../components/IconButton.tsx";
|
import {IconButton} from "../components/IconButton.tsx";
|
||||||
import {determineSorting} from "../utils/sorting.ts";
|
|
||||||
|
|
||||||
|
|
||||||
export const HomePage = () => {
|
export const HomePage = () => {
|
||||||
const pluginsSocket = useStore(state=>state.pluginsSocket)
|
const pluginsSocket = useStore(state=>state.pluginsSocket)
|
||||||
const [plugins,setPlugins] = useState<PluginDef[]>([])
|
const [plugins,setPlugins] = useState<PluginDef[]>([])
|
||||||
const installedPlugins = useStore(state=>state.installedPlugins)
|
const [installedPlugins, setInstalledPlugins] = useState<InstalledPlugin[]>([])
|
||||||
const setInstalledPlugins = useStore(state=>state.setInstalledPlugins)
|
|
||||||
const [searchParams, setSearchParams] = useState<SearchParams>({
|
|
||||||
offset: 0,
|
|
||||||
limit: 99999,
|
|
||||||
sortBy: 'name',
|
|
||||||
sortDir: 'asc',
|
|
||||||
searchTerm: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredInstallablePlugins = useMemo(()=>{
|
|
||||||
return plugins.sort((a, b)=>{
|
|
||||||
if(searchParams.sortBy === "version"){
|
|
||||||
if(searchParams.sortDir === "asc"){
|
|
||||||
return a.version.localeCompare(b.version)
|
|
||||||
}
|
|
||||||
return b.version.localeCompare(a.version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(searchParams.sortBy === "last-updated"){
|
|
||||||
if(searchParams.sortDir === "asc"){
|
|
||||||
return a.time.localeCompare(b.time)
|
|
||||||
}
|
|
||||||
return b.time.localeCompare(a.time)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (searchParams.sortBy === "name") {
|
|
||||||
if(searchParams.sortDir === "asc"){
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
}
|
|
||||||
return b.name.localeCompare(a.name)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
}, [plugins, searchParams])
|
|
||||||
|
|
||||||
const sortedInstalledPlugins = useMemo(()=>{
|
const sortedInstalledPlugins = useMemo(()=>{
|
||||||
return useStore.getState().installedPlugins.sort((a, b)=>{
|
return installedPlugins.sort((a, b)=>{
|
||||||
|
|
||||||
if(a.name < b.name){
|
if(a.name < b.name){
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
@ -61,8 +23,14 @@ export const HomePage = () => {
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
} ,[installedPlugins, searchParams])
|
} ,[installedPlugins])
|
||||||
|
const [searchParams, setSearchParams] = useState<SearchParams>({
|
||||||
|
offset: 0,
|
||||||
|
limit: 99999,
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc',
|
||||||
|
searchTerm: ''
|
||||||
|
})
|
||||||
const [searchTerm, setSearchTerm] = useState<string>('')
|
const [searchTerm, setSearchTerm] = useState<string>('')
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
|
|
||||||
|
@ -79,16 +47,17 @@ export const HomePage = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
pluginsSocket.on('results:updatable', (data) => {
|
pluginsSocket.on('results:updatable', (data) => {
|
||||||
const newInstalledPlugins = useStore.getState().installedPlugins.map(plugin => {
|
data.updatable.forEach((pluginName: string) => {
|
||||||
if (data.updatable.includes(plugin.name)) {
|
setInstalledPlugins(installedPlugins.map(plugin => {
|
||||||
return {
|
if (plugin.name === pluginName) {
|
||||||
...plugin,
|
return {
|
||||||
updatable: true
|
...plugin,
|
||||||
}
|
updatable: true
|
||||||
}
|
}
|
||||||
return plugin
|
}
|
||||||
})
|
return plugin
|
||||||
setInstalledPlugins(newInstalledPlugins)
|
}))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
pluginsSocket.on('finished:install', () => {
|
pluginsSocket.on('finished:install', () => {
|
||||||
|
@ -159,7 +128,6 @@ export const HomePage = () => {
|
||||||
})
|
})
|
||||||
}, 500, [searchTerm])
|
}, 500, [searchTerm])
|
||||||
|
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<h1><Trans i18nKey="admin_plugins"/></h1>
|
<h1><Trans i18nKey="admin_plugins"/></h1>
|
||||||
|
|
||||||
|
@ -181,7 +149,7 @@ export const HomePage = () => {
|
||||||
<td>
|
<td>
|
||||||
{
|
{
|
||||||
plugin.updatable ?
|
plugin.updatable ?
|
||||||
<IconButton onClick={() => installPlugin(plugin.name)} icon={<ArrowUpFromDot/>} title="Update"></IconButton>
|
<button onClick={() => installPlugin(plugin.name)}>Update</button>
|
||||||
: <IconButton disabled={plugin.name == "ep_etherpad-lite"} icon={<Trash/>} title={<Trans i18nKey="admin_plugins.installed_uninstall.value"/>} onClick={() => uninstallPlugin(plugin.name)}/>
|
: <IconButton disabled={plugin.name == "ep_etherpad-lite"} icon={<Trash/>} title={<Trans i18nKey="admin_plugins.installed_uninstall.value"/>} onClick={() => uninstallPlugin(plugin.name)}/>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
@ -194,39 +162,19 @@ export const HomePage = () => {
|
||||||
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
||||||
<SearchField onChange={v=>{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
|
<SearchField onChange={v=>{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
|
||||||
|
|
||||||
<div className="table-container">
|
|
||||||
<table id="available-plugins">
|
<table id="available-plugins">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className={determineSorting(searchParams.sortBy, searchParams.sortDir == "asc", 'name')} onClick={()=>{
|
<th><Trans i18nKey="admin_plugins.name"/></th>
|
||||||
setSearchParams({
|
|
||||||
...searchParams,
|
|
||||||
sortBy: 'name',
|
|
||||||
sortDir: searchParams.sortDir === "asc"? "desc": "asc"
|
|
||||||
})
|
|
||||||
}}>
|
|
||||||
<Trans i18nKey="admin_plugins.name" /></th>
|
|
||||||
<th style={{width: '30%'}}><Trans i18nKey="admin_plugins.description"/></th>
|
<th style={{width: '30%'}}><Trans i18nKey="admin_plugins.description"/></th>
|
||||||
<th className={determineSorting(searchParams.sortBy, searchParams.sortDir == "asc", 'version')} onClick={()=>{
|
<th><Trans i18nKey="admin_plugins.version"/></th>
|
||||||
setSearchParams({
|
<th><Trans i18nKey="admin_plugins.last-update"/></th>
|
||||||
...searchParams,
|
|
||||||
sortBy: 'version',
|
|
||||||
sortDir: searchParams.sortDir === "asc"? "desc": "asc"
|
|
||||||
})
|
|
||||||
}}><Trans i18nKey="admin_plugins.version"/></th>
|
|
||||||
<th className={determineSorting(searchParams.sortBy, searchParams.sortDir == "asc", 'last-updated')} onClick={()=>{
|
|
||||||
setSearchParams({
|
|
||||||
...searchParams,
|
|
||||||
sortBy: 'last-updated',
|
|
||||||
sortDir: searchParams.sortDir === "asc"? "desc": "asc"
|
|
||||||
})
|
|
||||||
}}><Trans i18nKey="admin_plugins.last-update"/></th>
|
|
||||||
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody style={{overflow: 'auto'}}>
|
<tbody style={{overflow: 'auto'}}>
|
||||||
{(filteredInstallablePlugins.length > 0) ?
|
{(plugins.length > 0) ?
|
||||||
filteredInstallablePlugins.map((plugin) => {
|
plugins.map((plugin) => {
|
||||||
return <tr key={plugin.name}>
|
return <tr key={plugin.name}>
|
||||||
<td><a rel="noopener noreferrer" href={`https://npmjs.com/${plugin.name}`} target="_blank">{plugin.name}</a></td>
|
<td><a rel="noopener noreferrer" href={`https://npmjs.com/${plugin.name}`} target="_blank">{plugin.name}</a></td>
|
||||||
<td>{plugin.description}</td>
|
<td>{plugin.description}</td>
|
||||||
|
@ -242,6 +190,5 @@ export const HomePage = () => {
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {useDebounce} from "../utils/useDebounce.ts";
|
||||||
import {determineSorting} from "../utils/sorting.ts";
|
import {determineSorting} from "../utils/sorting.ts";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
import {IconButton} from "../components/IconButton.tsx";
|
import {IconButton} from "../components/IconButton.tsx";
|
||||||
import {ChevronLeft, ChevronRight, Eye, Trash2, FileStack} from "lucide-react";
|
import {ChevronLeft, ChevronRight, Eye, Trash2} from "lucide-react";
|
||||||
import {SearchField} from "../components/SearchField.tsx";
|
import {SearchField} from "../components/SearchField.tsx";
|
||||||
|
|
||||||
export const PadPage = ()=>{
|
export const PadPage = ()=>{
|
||||||
|
@ -23,7 +23,6 @@ export const PadPage = ()=>{
|
||||||
const pads = useStore(state=>state.pads)
|
const pads = useStore(state=>state.pads)
|
||||||
const [currentPage, setCurrentPage] = useState<number>(0)
|
const [currentPage, setCurrentPage] = useState<number>(0)
|
||||||
const [deleteDialog, setDeleteDialog] = useState<boolean>(false)
|
const [deleteDialog, setDeleteDialog] = useState<boolean>(false)
|
||||||
const [errorText, setErrorText] = useState<string|null>(null)
|
|
||||||
const [padToDelete, setPadToDelete] = useState<string>('')
|
const [padToDelete, setPadToDelete] = useState<string>('')
|
||||||
const pages = useMemo(()=>{
|
const pages = useMemo(()=>{
|
||||||
if(!pads){
|
if(!pads){
|
||||||
|
@ -69,35 +68,12 @@ export const PadPage = ()=>{
|
||||||
results: newPads
|
results: newPads
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
settingsSocket.on('results:cleanupPadRevisions', (data)=>{
|
|
||||||
let newPads = useStore.getState().pads?.results ?? []
|
|
||||||
|
|
||||||
if (data.error) {
|
|
||||||
setErrorText(data.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
newPads.forEach((pad)=>{
|
|
||||||
if (pad.padName === data.padId) {
|
|
||||||
pad.revisionNumber = data.keepRevisions
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
useStore.getState().setPads({
|
|
||||||
results: newPads,
|
|
||||||
total: useStore.getState().pads!.total
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}, [settingsSocket, pads]);
|
}, [settingsSocket, pads]);
|
||||||
|
|
||||||
const deletePad = (padID: string)=>{
|
const deletePad = (padID: string)=>{
|
||||||
settingsSocket?.emit('deletePad', padID)
|
settingsSocket?.emit('deletePad', padID)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupPad = (padID: string)=>{
|
|
||||||
settingsSocket?.emit('cleanupPadRevisions', padID)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -124,26 +100,11 @@ export const PadPage = ()=>{
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
<Dialog.Root open={errorText !== null}>
|
|
||||||
<Dialog.Portal>
|
|
||||||
<Dialog.Overlay className="dialog-confirm-overlay"/>
|
|
||||||
<Dialog.Content className="dialog-confirm-content">
|
|
||||||
<div>
|
|
||||||
<div>Error occured: {errorText}</div>
|
|
||||||
<div className="settings-button-bar">
|
|
||||||
<button onClick={() => {
|
|
||||||
setErrorText(null)
|
|
||||||
}}>OK</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog.Root>
|
|
||||||
<h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
|
<h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
|
||||||
<SearchField value={searchTerm} onChange={v=>setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
|
<SearchField value={searchTerm} onChange={v=>setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="search-pads">
|
<tr>
|
||||||
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'padName')} onClick={()=>{
|
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'padName')} onClick={()=>{
|
||||||
setSearchParams({
|
setSearchParams({
|
||||||
...searchParams,
|
...searchParams,
|
||||||
|
@ -175,7 +136,7 @@ export const PadPage = ()=>{
|
||||||
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="search-pads-body">
|
<tbody>
|
||||||
{
|
{
|
||||||
pads?.results?.map((pad)=>{
|
pads?.results?.map((pad)=>{
|
||||||
return <tr key={pad.padName}>
|
return <tr key={pad.padName}>
|
||||||
|
@ -189,9 +150,6 @@ export const PadPage = ()=>{
|
||||||
setPadToDelete(pad.padName)
|
setPadToDelete(pad.padName)
|
||||||
setDeleteDialog(true)
|
setDeleteDialog(true)
|
||||||
}}/>
|
}}/>
|
||||||
<IconButton icon={<FileStack/>} title={<Trans i18nKey="ep_admin_pads:ep_adminpads2_cleanup"/>} onClick={()=>{
|
|
||||||
cleanupPad(pad.padName)
|
|
||||||
}}/>
|
|
||||||
<IconButton icon={<Eye/>} title="view" onClick={()=>window.open(`/p/${pad.padName}`, '_blank')}/>
|
<IconButton icon={<Eye/>} title="view" onClick={()=>window.open(`/p/${pad.padName}`, '_blank')}/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -20,7 +20,7 @@ export type SearchParams = {
|
||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
offset: number,
|
offset: number,
|
||||||
limit: number,
|
limit: number,
|
||||||
sortBy: 'name'|'version'|'last-updated',
|
sortBy: 'name'|'version',
|
||||||
sortDir: 'asc'|'desc'
|
sortDir: 'asc'|'desc'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {create} from "zustand";
|
import {create} from "zustand";
|
||||||
import {Socket} from "socket.io-client";
|
import {Socket} from "socket.io-client";
|
||||||
import {PadSearchResult} from "../utils/PadSearch.ts";
|
import {PadSearchResult} from "../utils/PadSearch.ts";
|
||||||
import {InstalledPlugin} from "../pages/Plugin.ts";
|
|
||||||
|
|
||||||
type ToastState = {
|
type ToastState = {
|
||||||
description?:string,
|
description?:string,
|
||||||
|
@ -23,9 +22,7 @@ type StoreState = {
|
||||||
toastState: ToastState,
|
toastState: ToastState,
|
||||||
setToastState: (val: ToastState)=>void,
|
setToastState: (val: ToastState)=>void,
|
||||||
pads: PadSearchResult|undefined,
|
pads: PadSearchResult|undefined,
|
||||||
setPads: (pads: PadSearchResult)=>void,
|
setPads: (pads: PadSearchResult)=>void
|
||||||
installedPlugins: InstalledPlugin[],
|
|
||||||
setInstalledPlugins: (plugins: InstalledPlugin[])=>void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +43,5 @@ export const useStore = create<StoreState>()((set) => ({
|
||||||
success: false
|
success: false
|
||||||
},
|
},
|
||||||
pads: undefined,
|
pads: undefined,
|
||||||
setPads: (pads)=>set({pads}),
|
setPads: (pads)=>set({pads})
|
||||||
installedPlugins: [],
|
|
||||||
setInstalledPlugins: (plugins)=>set({installedPlugins: plugins})
|
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
IFS=$'\n\t'
|
|
||||||
|
|
||||||
trim() {
|
|
||||||
local var="$*"
|
|
||||||
# remove leading whitespace characters
|
|
||||||
var="${var#"${var%%[![:space:]]*}"}"
|
|
||||||
# remove trailing whitespace characters
|
|
||||||
var="${var%"${var##*[![:space:]]}"}"
|
|
||||||
printf '%s' "$var"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Move to the Etherpad base directory.
|
|
||||||
MY_DIR=$(cd "${0%/*}" && pwd -P) || exit 1
|
|
||||||
cd "${MY_DIR}/.." || exit 1
|
|
||||||
|
|
||||||
# Source constants and useful functions
|
|
||||||
. bin/functions.sh
|
|
||||||
|
|
||||||
PNPM_OPTIONS=
|
|
||||||
if [ ! -z "${NODE_ENV-}" ]; then
|
|
||||||
if [ "$NODE_ENV" == 'production' ]; then
|
|
||||||
PNPM_OPTIONS='--prod'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -z "${ETHERPAD_LOCAL_PLUGINS_ENV-}" ]; then
|
|
||||||
if [ "$ETHERPAD_LOCAL_PLUGINS_ENV" == 'production' ]; then
|
|
||||||
PNPM_OPTIONS='--prod'
|
|
||||||
elif [ "$ETHERPAD_LOCAL_PLUGINS_ENV" == 'development' ]; then
|
|
||||||
PNPM_OPTIONS='-D'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -z "${ETHERPAD_LOCAL_PLUGINS}" ]; then
|
|
||||||
readarray -d ' ' plugins <<< "${ETHERPAD_LOCAL_PLUGINS}"
|
|
||||||
for plugin in "${plugins[@]}"; do
|
|
||||||
plugin=$(trim "$plugin")
|
|
||||||
if [ -d "local_plugins/${plugin}" ]; then
|
|
||||||
echo "Installing plugin: '${plugin}'"
|
|
||||||
pnpm install -w ${PNPM_OPTIONS:-} "local_plugins/${plugin}/"
|
|
||||||
else
|
|
||||||
( echo "Error. Directory 'local_plugins/${plugin}' for local plugin " \
|
|
||||||
"'${plugin}' missing" >&2 )
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo 'No local plugings to install.'
|
|
||||||
fi
|
|
|
@ -1,63 +0,0 @@
|
||||||
import {exec} from 'child_process'
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
import pjson from '../src/package.json'
|
|
||||||
|
|
||||||
const VERSION=pjson.version
|
|
||||||
console.log(`Building docs for version ${VERSION}`)
|
|
||||||
|
|
||||||
const createDirIfNotExists = (dir: fs.PathLike) => {
|
|
||||||
if (!fs.existsSync(dir)){
|
|
||||||
fs.mkdirSync(dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function copyFolderSync(from: fs.PathLike, to: fs.PathLike) {
|
|
||||||
if(fs.existsSync(to)){
|
|
||||||
const stat = fs.lstatSync(to)
|
|
||||||
if (stat.isDirectory()){
|
|
||||||
fs.rmSync(to, { recursive: true })
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
fs.rmSync(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.mkdirSync(to);
|
|
||||||
fs.readdirSync(from).forEach(element => {
|
|
||||||
if (fs.lstatSync(path.join(<string>from, element)).isFile()) {
|
|
||||||
if (typeof from === "string") {
|
|
||||||
if (typeof to === "string") {
|
|
||||||
fs.copyFileSync(path.join(from, element), path.join(to, element))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (typeof from === "string") {
|
|
||||||
if (typeof to === "string") {
|
|
||||||
copyFolderSync(path.join(from, element), path.join(to, element))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
exec('asciidoctor -v', (err,stdout)=>{
|
|
||||||
if (err){
|
|
||||||
console.log('Please install asciidoctor')
|
|
||||||
console.log('https://asciidoctor.org/docs/install-toolchain/')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
createDirIfNotExists('../out')
|
|
||||||
createDirIfNotExists('../out/doc')
|
|
||||||
createDirIfNotExists('../out/doc/api')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exec(`asciidoctor -D ../out/doc ../doc/index.adoc ../*/**.adoc -a VERSION=${VERSION}`)
|
|
||||||
exec(`asciidoctor -D ../out/doc/api ../doc/api/*.adoc -a VERSION=${VERSION}`)
|
|
||||||
|
|
||||||
copyFolderSync('../doc/public/', '../out/doc/')
|
|
|
@ -1,83 +0,0 @@
|
||||||
// DB migration
|
|
||||||
import {readFileSync} from 'node:fs'
|
|
||||||
import {Database, DatabaseType} from "ueberdb2";
|
|
||||||
import path from "node:path";
|
|
||||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
|
||||||
|
|
||||||
|
|
||||||
// file1 = source, file2 = target
|
|
||||||
// pnpm run migrateDB --file1 <db1.json> --file2 <db2.json>
|
|
||||||
const arg = process.argv.slice(2);
|
|
||||||
|
|
||||||
if (arg.length != 4) {
|
|
||||||
console.error('Wrong number of arguments!. Call with pnpm run migrateDB --file1 source.json target.json')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SettingsConfig = {
|
|
||||||
dbType: string,
|
|
||||||
dbSettings: any
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"dbType": "<your-db-type>",
|
|
||||||
"dbSettings": {
|
|
||||||
<your-db-settings>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
let firstDBSettingsFile: string
|
|
||||||
let secondDBSettingsFile: string
|
|
||||||
|
|
||||||
|
|
||||||
if (arg[0] == "--file1") {
|
|
||||||
firstDBSettingsFile = arg[1]
|
|
||||||
} else if (arg[0] === "--file2") {
|
|
||||||
secondDBSettingsFile = arg[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg[2] == "--file1") {
|
|
||||||
firstDBSettingsFile = arg[3]
|
|
||||||
} else if (arg[2] === "--file2") {
|
|
||||||
secondDBSettingsFile = arg[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const settingsfile = JSON.parse(readFileSync(path.join(settings.root,firstDBSettingsFile!)).toString()) as SettingsConfig
|
|
||||||
const settingsfile2 = JSON.parse(readFileSync(path.join(settings.root,secondDBSettingsFile!)).toString()) as SettingsConfig
|
|
||||||
|
|
||||||
console.log(settingsfile2)
|
|
||||||
if ("filename" in settingsfile.dbSettings) {
|
|
||||||
settingsfile.dbSettings.filename = path.join(settings.root, settingsfile.dbSettings.filename)
|
|
||||||
console.log(settingsfile.dbType + " location is "+ settingsfile.dbSettings.filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("filename" in settingsfile2.dbSettings) {
|
|
||||||
settingsfile2.dbSettings.filename = path.join(settings.root, settingsfile2.dbSettings.filename)
|
|
||||||
console.log(settingsfile2.dbType + " location is "+ settingsfile2.dbSettings.filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ueberdb1 = new Database(settingsfile.dbType as DatabaseType, settingsfile.dbSettings)
|
|
||||||
const ueberdb2 = new Database(settingsfile2.dbType as DatabaseType, settingsfile2.dbSettings)
|
|
||||||
|
|
||||||
const handleSync = async ()=>{
|
|
||||||
await ueberdb1.init()
|
|
||||||
await ueberdb2.init()
|
|
||||||
|
|
||||||
const allKeys = await ueberdb1.findKeys('*','')
|
|
||||||
for (const key of allKeys) {
|
|
||||||
const foundVal = await ueberdb1.get(key)!
|
|
||||||
await ueberdb2.set(key, foundVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSync().then(()=>{
|
|
||||||
console.log("Done syncing dbs")
|
|
||||||
}).catch(e=>{
|
|
||||||
console.log(`Error syncing db ${e}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
{
|
{
|
||||||
"name": "bin",
|
"name": "bin",
|
||||||
"version": "2.3.0",
|
"version": "2.2.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "checkAllPads.js",
|
"main": "checkAllPads.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
"doc": "doc"
|
"doc": "doc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.7.3",
|
||||||
"ep_etherpad-lite": "workspace:../src",
|
"ep_etherpad-lite": "workspace:../src",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.6.3",
|
||||||
"tsx": "^4.19.4",
|
"tsx": "^4.17.0",
|
||||||
"ueberdb2": "^5.0.6"
|
"ueberdb2": "^4.2.92"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.15.3",
|
"@types/node": "^22.1.0",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.5.8",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.5.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"makeDocs": "node --import tsx make_docs.ts",
|
|
||||||
"checkPad": "node --import tsx checkPad.ts",
|
"checkPad": "node --import tsx checkPad.ts",
|
||||||
"checkAllPads": "node --import tsx checkAllPads.ts",
|
"checkAllPads": "node --import tsx checkAllPads.ts",
|
||||||
"createUserSession": "node --import tsx createUserSession.ts",
|
"createUserSession": "node --import tsx createUserSession.ts",
|
||||||
|
@ -34,8 +33,7 @@
|
||||||
"stalePlugins": "node --import tsx ./plugins/stalePlugins.ts",
|
"stalePlugins": "node --import tsx ./plugins/stalePlugins.ts",
|
||||||
"checkPlugin": "node --import tsx ./plugins/checkPlugin.ts",
|
"checkPlugin": "node --import tsx ./plugins/checkPlugin.ts",
|
||||||
"plugins": "node --import tsx ./plugins.ts",
|
"plugins": "node --import tsx ./plugins.ts",
|
||||||
"generateChangelog": "node --import tsx generateReleaseNotes.ts",
|
"generateChangelog": "node --import tsx generateReleaseNotes.ts"
|
||||||
"migrateDB": "node --import tsx migrateDB.ts"
|
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
|
|
|
@ -23,13 +23,17 @@ const possibleActions = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const install = ()=> {
|
const install = ()=> {
|
||||||
const argsAsString: string = args.join(" ");
|
|
||||||
const regexRegistryPlugins = /(?<=i\s)(.*?)(?=--github|--path|$)/;
|
let registryPlugins: string[] = [];
|
||||||
const regexLocalPlugins = /(?<=--path\s)(.*?)(?=--github|$)/;
|
let localPlugins: string[] = [];
|
||||||
const regexGithubPlugins = /(?<=--github\s)(.*?)(?=--path|$)/;
|
|
||||||
const registryPlugins = argsAsString.match(regexRegistryPlugins)?.[0]?.split(" ")?.filter(s => s) || [];
|
if (args.indexOf('--path') !== -1) {
|
||||||
const localPlugins = argsAsString.match(regexLocalPlugins)?.[0]?.split(" ")?.filter(s => s) || [];
|
const indexToSplit = args.indexOf('--path');
|
||||||
const githubPlugins = argsAsString.match(regexGithubPlugins)?.[0]?.split(" ")?.filter(s => s) || [];
|
registryPlugins = args.slice(1, indexToSplit);
|
||||||
|
localPlugins = args.slice(indexToSplit + 1);
|
||||||
|
} else {
|
||||||
|
registryPlugins = args;
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
for (const plugin of registryPlugins) {
|
for (const plugin of registryPlugins) {
|
||||||
|
@ -49,11 +53,6 @@ const install = ()=> {
|
||||||
console.log(`Installing plugin from path: ${plugin}`);
|
console.log(`Installing plugin from path: ${plugin}`);
|
||||||
await linkInstaller.installFromPath(plugin);
|
await linkInstaller.installFromPath(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const plugin of githubPlugins) {
|
|
||||||
console.log(`Installing plugin from github: ${plugin}`);
|
|
||||||
await linkInstaller.installFromGitHub(plugin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
|
@ -197,7 +197,7 @@ try {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Building documentation...');
|
console.log('Building documentation...');
|
||||||
run('pnpm run makeDocs');
|
run('node ./make_docs.js');
|
||||||
console.log('Updating ether.github.com master branch...');
|
console.log('Updating ether.github.com master branch...');
|
||||||
run('git pull --ff-only', {cwd: '../ether.github.com/'});
|
run('git pull --ff-only', {cwd: '../ether.github.com/'});
|
||||||
console.log('Committing documentation...');
|
console.log('Committing documentation...');
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ export default defineConfig({
|
||||||
{ text: 'Stats', link: '/stats.md' },
|
{ text: 'Stats', link: '/stats.md' },
|
||||||
{text: 'Skins', link: '/skins.md' },
|
{text: 'Skins', link: '/skins.md' },
|
||||||
{text: 'Demo', link: '/demo.md' },
|
{text: 'Demo', link: '/demo.md' },
|
||||||
{text: 'CLI', link: '/cli.md'},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
29
doc/cli.md
29
doc/cli.md
|
@ -1,29 +0,0 @@
|
||||||
# CLI
|
|
||||||
|
|
||||||
You can find different tools for migrating things, checking your Etherpad health in the bin directory.
|
|
||||||
One of these is the migrateDB command. It takes two settings.json files and copies data from one source to another one.
|
|
||||||
In this example we migrate from the old dirty db to the new rustydb engine. So we copy these files to the root of the etherpad-directory.
|
|
||||||
|
|
||||||
````json
|
|
||||||
{
|
|
||||||
"dbType": "dirty",
|
|
||||||
"dbSettings": {
|
|
||||||
"filename": "./var/rusty.db"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
````
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
````json
|
|
||||||
{
|
|
||||||
"dbType": "rustydb",
|
|
||||||
"dbSettings": {
|
|
||||||
"filename": "./var/rusty2.db"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
````
|
|
||||||
|
|
||||||
|
|
||||||
After that we need to move the data from dirty to rustydb.
|
|
||||||
Therefore, we call `pnpm run migrateDB --file1 test1.json --file2 test2.json` with these two files in our root directories. After some time the data should be copied over to the new database.
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vitepress": "^1.6.3"
|
"vitepress": "^1.3.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docs:dev": "vitepress dev",
|
"docs:dev": "vitepress dev",
|
||||||
|
|
|
@ -9,7 +9,6 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
# Attention: installed plugins in the node_modules folder get overwritten during volume mount in dev
|
|
||||||
ETHERPAD_PLUGINS:
|
ETHERPAD_PLUGINS:
|
||||||
# change from development to production if needed
|
# change from development to production if needed
|
||||||
target: development
|
target: development
|
||||||
|
@ -58,7 +57,7 @@ services:
|
||||||
# ports:
|
# ports:
|
||||||
# - "5432:5432"
|
# - "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data/pgdata
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
|
@ -42,7 +42,7 @@ services:
|
||||||
# ports:
|
# ports:
|
||||||
# - "5432:5432"
|
# - "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data/pgdata
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|
3
local_plugins/.gitignore
vendored
3
local_plugins/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
# ignore everything
|
|
||||||
*
|
|
||||||
!.gitignore
|
|
55
make_docs.js
Normal file
55
make_docs.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import {exec} from 'child_process'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import pjson from './src/package.json' assert {type: "json"}
|
||||||
|
|
||||||
|
const VERSION=pjson.version
|
||||||
|
console.log(`Building docs for version ${VERSION}`)
|
||||||
|
|
||||||
|
const createDirIfNotExists = (dir) => {
|
||||||
|
if (!fs.existsSync(dir)){
|
||||||
|
fs.mkdirSync(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function copyFolderSync(from, to) {
|
||||||
|
if(fs.existsSync(to)){
|
||||||
|
const stat = fs.lstatSync(to)
|
||||||
|
if (stat.isDirectory()){
|
||||||
|
fs.rmSync(to, { recursive: true })
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
fs.rmSync(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.mkdirSync(to);
|
||||||
|
fs.readdirSync(from).forEach(element => {
|
||||||
|
if (fs.lstatSync(path.join(from, element)).isFile()) {
|
||||||
|
fs.copyFileSync(path.join(from, element), path.join(to, element))
|
||||||
|
} else {
|
||||||
|
copyFolderSync(path.join(from, element), path.join(to, element))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exec('asciidoctor -v', (err,stdout)=>{
|
||||||
|
if (err){
|
||||||
|
console.log('Please install asciidoctor')
|
||||||
|
console.log('https://asciidoctor.org/docs/install-toolchain/')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
createDirIfNotExists('./out')
|
||||||
|
createDirIfNotExists('./out/doc')
|
||||||
|
createDirIfNotExists('./out/doc/api')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exec(`asciidoctor -D out/doc doc/index.adoc */**.adoc -a VERSION=${VERSION}`)
|
||||||
|
exec(`asciidoctor -D out/doc/api ./doc/api/*.adoc -a VERSION=${VERSION}`)
|
||||||
|
|
||||||
|
copyFolderSync('./doc/public/', './out/doc/')
|
|
@ -30,8 +30,7 @@
|
||||||
"remove-plugins": "pnpm --filter bin run remove-plugins",
|
"remove-plugins": "pnpm --filter bin run remove-plugins",
|
||||||
"list-plugins": "pnpm --filter bin run list-plugins",
|
"list-plugins": "pnpm --filter bin run list-plugins",
|
||||||
"build:etherpad": "pnpm --filter admin run build-copy && pnpm --filter ui run build-copy",
|
"build:etherpad": "pnpm --filter admin run build-copy && pnpm --filter ui run build-copy",
|
||||||
"build:ui": "pnpm --filter ui run build-copy && pnpm --filter admin run build-copy",
|
"build:ui": "pnpm --filter ui run build-copy && pnpm --filter admin run build-copy"
|
||||||
"makeDocs": "pnpm --filter bin run makeDocs"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ep_etherpad-lite": "workspace:./src"
|
"ep_etherpad-lite": "workspace:./src"
|
||||||
|
@ -50,6 +49,6 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/ether/etherpad-lite.git"
|
"url": "https://github.com/ether/etherpad-lite.git"
|
||||||
},
|
},
|
||||||
"version": "2.3.0",
|
"version": "2.2.2",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
}
|
}
|
||||||
|
|
7488
pnpm-lock.yaml
generated
7488
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -4,7 +4,3 @@ packages:
|
||||||
- bin
|
- bin
|
||||||
- doc
|
- doc
|
||||||
- ui
|
- ui
|
||||||
onlyBuiltDependencies:
|
|
||||||
- '@scarf/scarf'
|
|
||||||
- '@swc/core'
|
|
||||||
- esbuild
|
|
||||||
|
|
|
@ -96,7 +96,7 @@
|
||||||
* 3) if you want to use newlines in the default value of a string parameter,
|
* 3) if you want to use newlines in the default value of a string parameter,
|
||||||
* use "\n" as usual.
|
* use "\n" as usual.
|
||||||
*
|
*
|
||||||
* "defaultPadText" : "${DEFAULT_PAD_TEXT:Line 1\nLine 2}"
|
* "defaultPadText" : "${DEFAULT_PAD_TEXT}Line 1\nLine 2"
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -171,14 +171,6 @@
|
||||||
*/
|
*/
|
||||||
"showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:true}",
|
"showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:true}",
|
||||||
|
|
||||||
/*
|
|
||||||
* Settings for cleanup of pads
|
|
||||||
*/
|
|
||||||
"cleanup": {
|
|
||||||
"enabled": false,
|
|
||||||
"keepRevisions": 5
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The authentication method used by the server.
|
The authentication method used by the server.
|
||||||
The default value is sso
|
The default value is sso
|
||||||
|
@ -202,15 +194,6 @@
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enables the use of a different server. We have a different one that syncs changes from the original server.
|
|
||||||
* It is hosted on GitHub and should not be blocked by many firewalls.
|
|
||||||
* https://etherpad.org/ep_infos
|
|
||||||
*/
|
|
||||||
|
|
||||||
"updateServer": "https://etherpad.org/ep_infos",
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The type of the database.
|
* The type of the database.
|
||||||
*
|
*
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
* 3) if you want to use newlines in the default value of a string parameter,
|
* 3) if you want to use newlines in the default value of a string parameter,
|
||||||
* use "\n" as usual.
|
* use "\n" as usual.
|
||||||
*
|
*
|
||||||
* "defaultPadText" : "${DEFAULT_PAD_TEXT:Line 1\nLine 2}"
|
* "defaultPadText" : "${DEFAULT_PAD_TEXT}Line 1\nLine 2"
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -162,14 +162,6 @@
|
||||||
*/
|
*/
|
||||||
"showSettingsInAdminPage": true,
|
"showSettingsInAdminPage": true,
|
||||||
|
|
||||||
/*
|
|
||||||
* Settings for cleanup of pads
|
|
||||||
*/
|
|
||||||
"cleanup": {
|
|
||||||
"enabled": false,
|
|
||||||
"keepRevisions": 5
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Node native SSL support
|
* Node native SSL support
|
||||||
*
|
*
|
||||||
|
@ -279,14 +271,6 @@
|
||||||
"pageDown": true
|
"pageDown": true
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
|
||||||
* Enables the use of a different server. We have a different one that syncs changes from the original server.
|
|
||||||
* It is hosted on GitHub and should not be blocked by many firewalls.
|
|
||||||
* https://etherpad.org/ep_infos
|
|
||||||
*/
|
|
||||||
|
|
||||||
"updateServer": "https://etherpad.org/ep_infos",
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Should we suppress errors from being visible in the default Pad Text?
|
* Should we suppress errors from being visible in the default Pad Text?
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -82,12 +82,6 @@
|
||||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling"
|
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "restApi",
|
|
||||||
"hooks": {
|
|
||||||
"expressCreateServer": "ep_etherpad-lite/node/handler/RestAPI"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "socketio",
|
"name": "socketio",
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
|
|
@ -16,18 +16,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"admin.page-title": "لوحة تحكم المسؤول - Etherpad",
|
"admin.page-title": "لوحة تحكم المسؤول - Etherpad",
|
||||||
"admin_plugins": "مدير المكونات الإضافية",
|
|
||||||
"admin_plugins.available": "المكونات الإضافية المتاحة",
|
|
||||||
"admin_plugins.available_not-found": "لم يتم العثور على أي مكونات إضافية.",
|
|
||||||
"admin_plugins.available_fetching": "جاري الجلب…",
|
|
||||||
"admin_plugins.available_install.value": "تنصيب",
|
|
||||||
"admin_plugins.available_search.placeholder": "ابحث عن المكونات الإضافية للتثبيت",
|
|
||||||
"admin_plugins.description": "الوصف",
|
"admin_plugins.description": "الوصف",
|
||||||
"admin_plugins.installed": "الإضافات المثبتة",
|
"admin_plugins.installed": "الإضافات المثبتة",
|
||||||
"admin_plugins.installed_fetching": "جارٍ إحضار المكونات الإضافية المثبتة ...",
|
"admin_plugins.installed_fetching": "جارٍ إحضار المكونات الإضافية المثبتة ...",
|
||||||
"admin_plugins.installed_nothing": "لم تقم بتثبيت أي مكونات إضافية حتى الآن.",
|
"admin_plugins.installed_nothing": "لم تقم بتثبيت أي مكونات إضافية حتى الآن.",
|
||||||
"admin_plugins.installed_uninstall.value": "إلغاء التثبيت",
|
|
||||||
"admin_plugins.last-update": "آخر تحديث",
|
|
||||||
"admin_plugins.name": "الاسم",
|
"admin_plugins.name": "الاسم",
|
||||||
"admin_plugins.page-title": "مدير البرنامج المساعد - Etherpad",
|
"admin_plugins.page-title": "مدير البرنامج المساعد - Etherpad",
|
||||||
"admin_plugins.version": "الإصدار",
|
"admin_plugins.version": "الإصدار",
|
||||||
|
@ -39,10 +31,8 @@
|
||||||
"admin_plugins_info.plugins": "الإضافات المثبتة",
|
"admin_plugins_info.plugins": "الإضافات المثبتة",
|
||||||
"admin_plugins_info.page-title": "معلومات البرنامج المساعد - Etherpad",
|
"admin_plugins_info.page-title": "معلومات البرنامج المساعد - Etherpad",
|
||||||
"admin_plugins_info.version": "إصدار Etherpad",
|
"admin_plugins_info.version": "إصدار Etherpad",
|
||||||
"admin_plugins_info.version_latest": "أحدث إصدار متاح",
|
|
||||||
"admin_plugins_info.version_number": "رقم الإصدار",
|
"admin_plugins_info.version_number": "رقم الإصدار",
|
||||||
"admin_settings": "إعدادات",
|
"admin_settings": "إعدادات",
|
||||||
"admin_settings.current": "التكوين الحالي",
|
|
||||||
"admin_settings.current_example-devel": "مثال على قالب إعدادات التطوير",
|
"admin_settings.current_example-devel": "مثال على قالب إعدادات التطوير",
|
||||||
"admin_settings.current_example-prod": "مثال على قالب إعدادات الإنتاج",
|
"admin_settings.current_example-prod": "مثال على قالب إعدادات الإنتاج",
|
||||||
"admin_settings.current_restart.value": "أعد تشغيل Etherpad",
|
"admin_settings.current_restart.value": "أعد تشغيل Etherpad",
|
||||||
|
@ -83,8 +73,6 @@
|
||||||
"pad.settings.fontType": "نوع الخط:",
|
"pad.settings.fontType": "نوع الخط:",
|
||||||
"pad.settings.fontType.normal": "عادي",
|
"pad.settings.fontType.normal": "عادي",
|
||||||
"pad.settings.language": "اللغة:",
|
"pad.settings.language": "اللغة:",
|
||||||
"pad.settings.deletePad": "حذف الوسادة",
|
|
||||||
"pad.delete.confirm": "هل تريد حقا حذف هذه الوسادة؟",
|
|
||||||
"pad.settings.about": "حول",
|
"pad.settings.about": "حول",
|
||||||
"pad.settings.poweredBy": "مدعوم من",
|
"pad.settings.poweredBy": "مدعوم من",
|
||||||
"pad.importExport.import_export": "استيراد/تصدير",
|
"pad.importExport.import_export": "استيراد/تصدير",
|
||||||
|
@ -122,8 +110,6 @@
|
||||||
"pad.modals.deleted": "محذوف.",
|
"pad.modals.deleted": "محذوف.",
|
||||||
"pad.modals.deleted.explanation": "تمت إزالة هذا الباد.",
|
"pad.modals.deleted.explanation": "تمت إزالة هذا الباد.",
|
||||||
"pad.modals.rateLimited": "معدل محدود.",
|
"pad.modals.rateLimited": "معدل محدود.",
|
||||||
"pad.modals.rateLimited.explanation": "لقد أرسلت الكثير من الرسائل إلى هذه اللوحة مما أدى إلى قطع الاتصال بك.",
|
|
||||||
"pad.modals.rejected.explanation": "رفض الخادم الرسالة التي أرسلها متصفحك.",
|
|
||||||
"pad.modals.rejected.cause": "ربما تم تحديث الخادم أثناء عرض اللوحة ، أو ربما كان هناك خطأ في Etherpad. حاول إعادة تحميل الصفحة.",
|
"pad.modals.rejected.cause": "ربما تم تحديث الخادم أثناء عرض اللوحة ، أو ربما كان هناك خطأ في Etherpad. حاول إعادة تحميل الصفحة.",
|
||||||
"pad.modals.disconnected": "لم تعد متصلا.",
|
"pad.modals.disconnected": "لم تعد متصلا.",
|
||||||
"pad.modals.disconnected.explanation": "تم فقدان الاتصال بالخادم",
|
"pad.modals.disconnected.explanation": "تم فقدان الاتصال بالخادم",
|
||||||
|
|
|
@ -35,10 +35,6 @@
|
||||||
"admin_plugins_info.version_number": "Нумар вэрсіі",
|
"admin_plugins_info.version_number": "Нумар вэрсіі",
|
||||||
"admin_settings": "Налады",
|
"admin_settings": "Налады",
|
||||||
"admin_settings.current": "Цяперашняя канфігурацыя",
|
"admin_settings.current": "Цяперашняя канфігурацыя",
|
||||||
"admin_settings.current_example-devel": "Прыклад шаблёну наладаў распрацоўкі",
|
|
||||||
"admin_settings.current_example-prod": "Прыклад шаблёну наладаў вытворчасьці",
|
|
||||||
"admin_settings.current_restart.value": "Перазапуск Etherpad",
|
|
||||||
"admin_settings.current_save.value": "Захаваць налады",
|
|
||||||
"admin_settings.page-title": "Налады — Etherpad",
|
"admin_settings.page-title": "Налады — Etherpad",
|
||||||
"index.newPad": "Стварыць",
|
"index.newPad": "Стварыць",
|
||||||
"index.createOpenPad": "ці тварыць/адкрыць дакумэнт з назвай:",
|
"index.createOpenPad": "ці тварыць/адкрыць дакумэнт з назвай:",
|
||||||
|
@ -76,7 +72,6 @@
|
||||||
"pad.settings.fontType.normal": "Звычайны",
|
"pad.settings.fontType.normal": "Звычайны",
|
||||||
"pad.settings.language": "Мова:",
|
"pad.settings.language": "Мова:",
|
||||||
"pad.settings.about": "Пра",
|
"pad.settings.about": "Пра",
|
||||||
"pad.settings.poweredBy": "Працуе на",
|
|
||||||
"pad.importExport.import_export": "Імпарт/Экспарт",
|
"pad.importExport.import_export": "Імпарт/Экспарт",
|
||||||
"pad.importExport.import": "Загрузіжайце любыя тэкставыя файлы або дакумэнты",
|
"pad.importExport.import": "Загрузіжайце любыя тэкставыя файлы або дакумэнты",
|
||||||
"pad.importExport.importSuccessful": "Пасьпяхова!",
|
"pad.importExport.importSuccessful": "Пасьпяхова!",
|
||||||
|
@ -111,9 +106,6 @@
|
||||||
"pad.modals.corruptPad.cause": "Гэта можа быць выклікана няправільнай канфігурацыяй сэрвэру або іншымі нечаканымі дзеяньнямі. Калі ласка, скантактуйцеся з адміністратарам службы.",
|
"pad.modals.corruptPad.cause": "Гэта можа быць выклікана няправільнай канфігурацыяй сэрвэру або іншымі нечаканымі дзеяньнямі. Калі ласка, скантактуйцеся з адміністратарам службы.",
|
||||||
"pad.modals.deleted": "Выдалены.",
|
"pad.modals.deleted": "Выдалены.",
|
||||||
"pad.modals.deleted.explanation": "Гэты дакумэнт быў выдалены.",
|
"pad.modals.deleted.explanation": "Гэты дакумэнт быў выдалены.",
|
||||||
"pad.modals.rateLimited": "Хуткасьць абмежаваная.",
|
|
||||||
"pad.modals.rateLimited.explanation": "Вы адаслалі так шмат паведамленьняў, што гэты дакумэнт вас адключыў.",
|
|
||||||
"pad.modals.rejected.explanation": "Сэрвэр адхіліў паведамленьне, адасланае вашым броўзэрам.",
|
|
||||||
"pad.modals.disconnected": "Вы былі адключаныя.",
|
"pad.modals.disconnected": "Вы былі адключаныя.",
|
||||||
"pad.modals.disconnected.explanation": "Злучэньне з сэрвэрам было страчанае",
|
"pad.modals.disconnected.explanation": "Злучэньне з сэрвэрам было страчанае",
|
||||||
"pad.modals.disconnected.cause": "Магчыма, сэрвэр недаступны. Калі ласка, паведаміце адміністратару службы, калі праблема будзе паўтарацца.",
|
"pad.modals.disconnected.cause": "Магчыма, сэрвэр недаступны. Калі ласка, паведаміце адміністратару службы, калі праблема будзе паўтарацца.",
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Умар"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"admin.page-title": "Администраторан панель — Etherpad",
|
|
||||||
"admin_plugins": "Плагинийн менеджер",
|
|
||||||
"admin_plugins.available": "ТӀекхочуш йолу плагинаш",
|
|
||||||
"admin_plugins.available_not-found": "Плагинаш ца карийна.",
|
|
||||||
"admin_plugins.available_fetching": "Схьаоьцуш...",
|
|
||||||
"admin_plugins.available_install.value": "ДӀахӀоттайе",
|
|
||||||
"admin_plugins.installed_uninstall.value": "ДӀайаккха",
|
|
||||||
"admin_plugins.last-update": "ТӀаьххьара карлайаккхар",
|
|
||||||
"admin_plugins.name": "ЦӀе",
|
|
||||||
"admin_plugins.page-title": "Плагинийн менеджер — Etherpad",
|
|
||||||
"admin_plugins.version": "Верси",
|
|
||||||
"admin_plugins_info.version_number": "Версин лоьмар",
|
|
||||||
"admin_settings": "Нисдаран гӀирс",
|
|
||||||
"admin_settings.current": "Карара конфигураци",
|
|
||||||
"pad.colorpicker.save": "Ӏалашйан",
|
|
||||||
"pad.colorpicker.cancel": "Йухайаккхар",
|
|
||||||
"pad.loading": "Чуйолуш…",
|
|
||||||
"pad.permissionDenied": "Хьан бакъонаш йац тӀекхача",
|
|
||||||
"pad.settings.padSettings": "Документан нисдаран гӀирс",
|
|
||||||
"pad.settings.myView": "Сан васт",
|
|
||||||
"pad.settings.stickychat": "Гуттара а гайта чат",
|
|
||||||
"pad.settings.language": "Мотт:",
|
|
||||||
"pad.settings.about": "Проектах лаьцна",
|
|
||||||
"pad.importExport.importSuccessful": "Кхиамца!",
|
|
||||||
"pad.modals.cancel": "Йухайаккхар",
|
|
||||||
"pad.share.link": "Хьажорг",
|
|
||||||
"pad.chat": "Чат",
|
|
||||||
"timeslider.toolbar.authors": "Авторш:",
|
|
||||||
"timeslider.toolbar.exportlink.title": "Экспорт",
|
|
||||||
"timeslider.month.january": "январь",
|
|
||||||
"timeslider.month.february": "февраль",
|
|
||||||
"timeslider.month.march": "март",
|
|
||||||
"timeslider.month.april": "апрель",
|
|
||||||
"timeslider.month.may": "май",
|
|
||||||
"timeslider.month.june": "июнь",
|
|
||||||
"timeslider.month.july": "июль",
|
|
||||||
"timeslider.month.august": "август",
|
|
||||||
"timeslider.month.september": "сентябрь",
|
|
||||||
"timeslider.month.october": "октябрь",
|
|
||||||
"timeslider.month.november": "ноябрь",
|
|
||||||
"timeslider.month.december": "декабрь",
|
|
||||||
"pad.impexp.importing": "Импорт йар..."
|
|
||||||
}
|
|
|
@ -11,7 +11,6 @@
|
||||||
"Predatorix",
|
"Predatorix",
|
||||||
"SamTV",
|
"SamTV",
|
||||||
"Sebastian Wallroth",
|
"Sebastian Wallroth",
|
||||||
"Ssgl",
|
|
||||||
"Thargon",
|
"Thargon",
|
||||||
"Tim.krieger",
|
"Tim.krieger",
|
||||||
"Wikinaut",
|
"Wikinaut",
|
||||||
|
@ -86,8 +85,6 @@
|
||||||
"pad.settings.fontType": "Schriftart:",
|
"pad.settings.fontType": "Schriftart:",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Sprache:",
|
"pad.settings.language": "Sprache:",
|
||||||
"pad.settings.deletePad": "Pad löschen",
|
|
||||||
"pad.delete.confirm": "Möchtest du dieses Pad wirklich löschen?",
|
|
||||||
"pad.settings.about": "Über",
|
"pad.settings.about": "Über",
|
||||||
"pad.settings.poweredBy": "Betrieben von",
|
"pad.settings.poweredBy": "Betrieben von",
|
||||||
"pad.importExport.import_export": "Import/Export",
|
"pad.importExport.import_export": "Import/Export",
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
"authors": [
|
"authors": [
|
||||||
"1917 Ekim Devrimi",
|
"1917 Ekim Devrimi",
|
||||||
"Erdemaslancan",
|
"Erdemaslancan",
|
||||||
"GolyatGeri",
|
|
||||||
"Gorizon",
|
"Gorizon",
|
||||||
"Gırd",
|
"Gırd",
|
||||||
"Kumkumuk",
|
"Kumkumuk",
|
||||||
|
@ -79,8 +78,6 @@
|
||||||
"pad.settings.fontType": "Babeta nuşti:",
|
"pad.settings.fontType": "Babeta nuşti:",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Zıwan:",
|
"pad.settings.language": "Zıwan:",
|
||||||
"pad.settings.deletePad": "Defteri bıesterê",
|
|
||||||
"pad.delete.confirm": "Şıma raşti wazenê ke nê defteri bıesterên?",
|
|
||||||
"pad.settings.about": "Heqa",
|
"pad.settings.about": "Heqa",
|
||||||
"pad.settings.poweredBy": "Pheştidayoğ",
|
"pad.settings.poweredBy": "Pheştidayoğ",
|
||||||
"pad.importExport.import_export": "Zerredayış/Teberdayış",
|
"pad.importExport.import_export": "Zerredayış/Teberdayış",
|
||||||
|
|
|
@ -72,8 +72,6 @@
|
||||||
"pad.settings.fontType": "Pismowa družyna:",
|
"pad.settings.fontType": "Pismowa družyna:",
|
||||||
"pad.settings.fontType.normal": "Normalny",
|
"pad.settings.fontType.normal": "Normalny",
|
||||||
"pad.settings.language": "Rěc:",
|
"pad.settings.language": "Rěc:",
|
||||||
"pad.settings.deletePad": "Zapisnik lašowaś",
|
|
||||||
"pad.delete.confirm": "Cośo napšawdu toś ten zapisnik lašowaś?",
|
|
||||||
"pad.settings.about": "Wó",
|
"pad.settings.about": "Wó",
|
||||||
"pad.settings.poweredBy": "Pódpěrany wót",
|
"pad.settings.poweredBy": "Pódpěrany wót",
|
||||||
"pad.importExport.import_export": "Import/Eksport",
|
"pad.importExport.import_export": "Import/Eksport",
|
||||||
|
|
|
@ -14,12 +14,10 @@
|
||||||
"admin_plugins": "Διαχειριστής πρόσθετων",
|
"admin_plugins": "Διαχειριστής πρόσθετων",
|
||||||
"admin_plugins.available": "Διαθέσιμα πρόσθετα",
|
"admin_plugins.available": "Διαθέσιμα πρόσθετα",
|
||||||
"admin_plugins.available_not-found": "Δεν βρέθηκαν πρόσθετα.",
|
"admin_plugins.available_not-found": "Δεν βρέθηκαν πρόσθετα.",
|
||||||
"admin_plugins.available_fetching": "Ανακτάται...",
|
|
||||||
"admin_plugins.available_install.value": "Εγκατάσταση",
|
"admin_plugins.available_install.value": "Εγκατάσταση",
|
||||||
"admin_plugins.available_search.placeholder": "Αναζητήστε πρόσθετα για εγκατάσταση",
|
"admin_plugins.available_search.placeholder": "Αναζητήστε πρόσθετα για εγκατάσταση",
|
||||||
"admin_plugins.description": "Περιγραφή",
|
"admin_plugins.description": "Περιγραφή",
|
||||||
"admin_plugins.installed": "Εγκατεστημένα πρόσθετα",
|
"admin_plugins.installed": "Εγκατεστημένα πρόσθετα",
|
||||||
"admin_plugins.installed_fetching": "Ανάκτηση εγκατεστημένων προσθηκών…",
|
|
||||||
"admin_plugins.installed_nothing": "Δεν έχετε εγκαταστήσει πρόσθετα ακόμη.",
|
"admin_plugins.installed_nothing": "Δεν έχετε εγκαταστήσει πρόσθετα ακόμη.",
|
||||||
"admin_plugins.installed_uninstall.value": "Απεγκατάσταση",
|
"admin_plugins.installed_uninstall.value": "Απεγκατάσταση",
|
||||||
"admin_plugins.last-update": "Τελευταία ενημέρωση",
|
"admin_plugins.last-update": "Τελευταία ενημέρωση",
|
||||||
|
@ -77,8 +75,6 @@
|
||||||
"pad.settings.fontType": "Τύπος γραμματοσειράς:",
|
"pad.settings.fontType": "Τύπος γραμματοσειράς:",
|
||||||
"pad.settings.fontType.normal": "Κανονική",
|
"pad.settings.fontType.normal": "Κανονική",
|
||||||
"pad.settings.language": "Γλώσσα:",
|
"pad.settings.language": "Γλώσσα:",
|
||||||
"pad.settings.deletePad": "Διαγραφή Pad",
|
|
||||||
"pad.delete.confirm": "Θέλετε πραγματικά να διαγράψετε αυτό το pad;",
|
|
||||||
"pad.settings.about": "Σχετικά",
|
"pad.settings.about": "Σχετικά",
|
||||||
"pad.settings.poweredBy": "Υποστηρίζεται από",
|
"pad.settings.poweredBy": "Υποστηρίζεται από",
|
||||||
"pad.importExport.import_export": "Εισαγωγή/Εξαγωγή",
|
"pad.importExport.import_export": "Εισαγωγή/Εξαγωγή",
|
||||||
|
@ -93,7 +89,7 @@
|
||||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
"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.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.connected": "Συνδεμένοι.",
|
||||||
"pad.modals.reconnecting": "Επανασύνδεση στο pad σας…",
|
"pad.modals.reconnecting": "Επανασύνδεση στο pad σας...",
|
||||||
"pad.modals.forcereconnect": "Επιβολή επανασύνδεσης",
|
"pad.modals.forcereconnect": "Επιβολή επανασύνδεσης",
|
||||||
"pad.modals.reconnecttimer": "Προσπάθεια επανασύνδεσης σε",
|
"pad.modals.reconnecttimer": "Προσπάθεια επανασύνδεσης σε",
|
||||||
"pad.modals.cancel": "Ακύρωση",
|
"pad.modals.cancel": "Ακύρωση",
|
||||||
|
@ -115,9 +111,7 @@
|
||||||
"pad.modals.corruptPad.cause": "Αυτό μπορεί να οφείλεται σε ένα λάθος στη ρύθμιση του διακομιστή ή κάποια άλλη απρόβλεπτη συμπεριφορά. Παρακαλώ επικοινωνήστε με τον διαχειριστή της υπηρεσίας.",
|
"pad.modals.corruptPad.cause": "Αυτό μπορεί να οφείλεται σε ένα λάθος στη ρύθμιση του διακομιστή ή κάποια άλλη απρόβλεπτη συμπεριφορά. Παρακαλώ επικοινωνήστε με τον διαχειριστή της υπηρεσίας.",
|
||||||
"pad.modals.deleted": "Διεγράφη.",
|
"pad.modals.deleted": "Διεγράφη.",
|
||||||
"pad.modals.deleted.explanation": "Αυτό το pad έχει καταργηθεί.",
|
"pad.modals.deleted.explanation": "Αυτό το pad έχει καταργηθεί.",
|
||||||
"pad.modals.rateLimited.explanation": "Στείλατε πάρα πολλά μηνύματα σε αυτό το pad, επομένως σας αποσύνδεσε.",
|
|
||||||
"pad.modals.rejected.explanation": "Ο διακομιστής απέρριψε ένα μήνυμα που στάλθηκε από το πρόγραμμα περιήγησής σας.",
|
"pad.modals.rejected.explanation": "Ο διακομιστής απέρριψε ένα μήνυμα που στάλθηκε από το πρόγραμμα περιήγησής σας.",
|
||||||
"pad.modals.rejected.cause": "Ο διακομιστής μπορεί να έχει ενημερωθεί ενώ προβάλλατε το pad ή ίσως υπάρχει σφάλμα στο Etherpad. Δοκιμάστε να φορτώσετε ξανά τη σελίδα.",
|
|
||||||
"pad.modals.disconnected": "Είστε αποσυνδεδεμένοι.",
|
"pad.modals.disconnected": "Είστε αποσυνδεδεμένοι.",
|
||||||
"pad.modals.disconnected.explanation": "Χάθηκε η σύνδεση με τον διακομιστή",
|
"pad.modals.disconnected.explanation": "Χάθηκε η σύνδεση με τον διακομιστή",
|
||||||
"pad.modals.disconnected.cause": "Ο διακομιστής μπορεί να μην είναι διαθέσιμος. Παρακαλούμε ειδοποιήστε τον διαχειριστή της υπηρεσίας εάν εξακολουθεί να συμβαίνει αυτό.",
|
"pad.modals.disconnected.cause": "Ο διακομιστής μπορεί να μην είναι διαθέσιμος. Παρακαλούμε ειδοποιήστε τον διαχειριστή της υπηρεσίας εάν εξακολουθεί να συμβαίνει αυτό.",
|
||||||
|
@ -130,7 +124,6 @@
|
||||||
"pad.chat.loadmessages": "Φόρτωση περισσότερων μηνυμάτων",
|
"pad.chat.loadmessages": "Φόρτωση περισσότερων μηνυμάτων",
|
||||||
"pad.chat.stick.title": "Κρατήστε τη συνομιλία στην οθόνη",
|
"pad.chat.stick.title": "Κρατήστε τη συνομιλία στην οθόνη",
|
||||||
"pad.chat.writeMessage.placeholder": "Γράψτε το μήνυμα σας εδώ",
|
"pad.chat.writeMessage.placeholder": "Γράψτε το μήνυμα σας εδώ",
|
||||||
"timeslider.followContents": "Ακολουθήστε τις ενημερώσεις περιεχομένου του pad",
|
|
||||||
"timeslider.pageTitle": "{{appTitle}} Χρονοδιάγραμμα",
|
"timeslider.pageTitle": "{{appTitle}} Χρονοδιάγραμμα",
|
||||||
"timeslider.toolbar.returnbutton": "Επιστροφή στο pad",
|
"timeslider.toolbar.returnbutton": "Επιστροφή στο pad",
|
||||||
"timeslider.toolbar.authors": "Συντάκτες:",
|
"timeslider.toolbar.authors": "Συντάκτες:",
|
||||||
|
|
|
@ -72,8 +72,6 @@
|
||||||
"pad.settings.fontType": "Font type:",
|
"pad.settings.fontType": "Font type:",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Language:",
|
"pad.settings.language": "Language:",
|
||||||
"pad.settings.deletePad": "Delete Pad",
|
|
||||||
"pad.delete.confirm": "Do you really want to delete this pad?",
|
|
||||||
"pad.settings.about": "About",
|
"pad.settings.about": "About",
|
||||||
"pad.settings.poweredBy": "Powered by",
|
"pad.settings.poweredBy": "Powered by",
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,6 @@
|
||||||
"pad.settings.fontType": "Fonttityyppi:",
|
"pad.settings.fontType": "Fonttityyppi:",
|
||||||
"pad.settings.fontType.normal": "normaali",
|
"pad.settings.fontType.normal": "normaali",
|
||||||
"pad.settings.language": "Kieli:",
|
"pad.settings.language": "Kieli:",
|
||||||
"pad.settings.deletePad": "Poista muistio",
|
|
||||||
"pad.delete.confirm": "Haluatko todella poistaa tämän muistion?",
|
|
||||||
"pad.settings.about": "Tietoja",
|
"pad.settings.about": "Tietoja",
|
||||||
"pad.settings.poweredBy": "Palvelun mahdollistaa",
|
"pad.settings.poweredBy": "Palvelun mahdollistaa",
|
||||||
"pad.importExport.import_export": "Tuonti/vienti",
|
"pad.importExport.import_export": "Tuonti/vienti",
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
"Jean-Frédéric",
|
"Jean-Frédéric",
|
||||||
"Leviathan",
|
"Leviathan",
|
||||||
"Macofe",
|
"Macofe",
|
||||||
"Mahabarata",
|
|
||||||
"Maxim21",
|
"Maxim21",
|
||||||
"McDutchie",
|
"McDutchie",
|
||||||
"Metroitendo",
|
"Metroitendo",
|
||||||
|
@ -22,7 +21,6 @@
|
||||||
"Peter17",
|
"Peter17",
|
||||||
"Quenenni",
|
"Quenenni",
|
||||||
"Rastus Vernon",
|
"Rastus Vernon",
|
||||||
"Spf",
|
|
||||||
"Stephane Cottin",
|
"Stephane Cottin",
|
||||||
"Thibaut120094",
|
"Thibaut120094",
|
||||||
"Tux-tn",
|
"Tux-tn",
|
||||||
|
@ -99,8 +97,6 @@
|
||||||
"pad.settings.fontType": "Type de police :",
|
"pad.settings.fontType": "Type de police :",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Langue :",
|
"pad.settings.language": "Langue :",
|
||||||
"pad.settings.deletePad": "Supprimer le bloc-notes",
|
|
||||||
"pad.delete.confirm": "Voulez-vous vraiment supprimer ce bloc-notes ?",
|
|
||||||
"pad.settings.about": "À propos",
|
"pad.settings.about": "À propos",
|
||||||
"pad.settings.poweredBy": "Propulsé par",
|
"pad.settings.poweredBy": "Propulsé par",
|
||||||
"pad.importExport.import_export": "Importer/Exporter",
|
"pad.importExport.import_export": "Importer/Exporter",
|
||||||
|
@ -161,7 +157,7 @@
|
||||||
"timeslider.toolbar.exportlink.title": "Exporter",
|
"timeslider.toolbar.exportlink.title": "Exporter",
|
||||||
"timeslider.exportCurrent": "Exporter la version actuelle sous :",
|
"timeslider.exportCurrent": "Exporter la version actuelle sous :",
|
||||||
"timeslider.version": "Version {{version}}",
|
"timeslider.version": "Version {{version}}",
|
||||||
"timeslider.saved": "Enregistrée le {{day}} {{month}} {{year}}",
|
"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.backRevision": "Reculer d’une révision dans ce bloc-notes",
|
||||||
"timeslider.forwardRevision": "Avancer d’une révision dans ce bloc-notes",
|
"timeslider.forwardRevision": "Avancer d’une révision dans ce bloc-notes",
|
||||||
|
|
|
@ -42,9 +42,9 @@
|
||||||
"index.newPad": "Novo documento",
|
"index.newPad": "Novo documento",
|
||||||
"index.createOpenPad": "ou crea/abre un documento co nome:",
|
"index.createOpenPad": "ou crea/abre un documento co nome:",
|
||||||
"index.openPad": "abrir un Pad existente co nome:",
|
"index.openPad": "abrir un Pad existente co nome:",
|
||||||
"pad.toolbar.bold.title": "Grosa (Ctrl+B)",
|
"pad.toolbar.bold.title": "Resaltado (Ctrl-B)",
|
||||||
"pad.toolbar.italic.title": "Cursiva (Ctrl+I)",
|
"pad.toolbar.italic.title": "Cursiva (Ctrl-I)",
|
||||||
"pad.toolbar.underline.title": "Subliñar (Ctrl+U)",
|
"pad.toolbar.underline.title": "Subliñar (Ctrl-U)",
|
||||||
"pad.toolbar.strikethrough.title": "Riscar (Ctrl+5)",
|
"pad.toolbar.strikethrough.title": "Riscar (Ctrl+5)",
|
||||||
"pad.toolbar.ol.title": "Lista ordenada (Ctrl+Shift+N)",
|
"pad.toolbar.ol.title": "Lista ordenada (Ctrl+Shift+N)",
|
||||||
"pad.toolbar.ul.title": "Lista sen ordenar (Ctrl+Shift+L)",
|
"pad.toolbar.ul.title": "Lista sen ordenar (Ctrl+Shift+L)",
|
||||||
|
@ -74,8 +74,6 @@
|
||||||
"pad.settings.fontType": "Tipo de letra:",
|
"pad.settings.fontType": "Tipo de letra:",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Lingua:",
|
"pad.settings.language": "Lingua:",
|
||||||
"pad.settings.deletePad": "Borrar o documento",
|
|
||||||
"pad.delete.confirm": "Queres borrar este documento?",
|
|
||||||
"pad.settings.about": "Acerca de",
|
"pad.settings.about": "Acerca de",
|
||||||
"pad.settings.poweredBy": "Grazas a",
|
"pad.settings.poweredBy": "Grazas a",
|
||||||
"pad.importExport.import_export": "Importar/Exportar",
|
"pad.importExport.import_export": "Importar/Exportar",
|
||||||
|
@ -90,7 +88,7 @@
|
||||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||||
"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.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.connected": "Conectado.",
|
||||||
"pad.modals.reconnecting": "Reconectando co teu documento...",
|
"pad.modals.reconnecting": "Reconectando co seu documento...",
|
||||||
"pad.modals.forcereconnect": "Forzar a reconexión",
|
"pad.modals.forcereconnect": "Forzar a reconexión",
|
||||||
"pad.modals.reconnecttimer": "Intentarase reconectar en",
|
"pad.modals.reconnecttimer": "Intentarase reconectar en",
|
||||||
"pad.modals.cancel": "Cancelar",
|
"pad.modals.cancel": "Cancelar",
|
||||||
|
|
|
@ -76,8 +76,6 @@
|
||||||
"pad.settings.fontType": "סוג גופן:",
|
"pad.settings.fontType": "סוג גופן:",
|
||||||
"pad.settings.fontType.normal": "רגיל",
|
"pad.settings.fontType.normal": "רגיל",
|
||||||
"pad.settings.language": "שפה:",
|
"pad.settings.language": "שפה:",
|
||||||
"pad.settings.deletePad": "מחיקת פנקס",
|
|
||||||
"pad.delete.confirm": "למחוק את הפנקס הזה?",
|
|
||||||
"pad.settings.about": "על אודות",
|
"pad.settings.about": "על אודות",
|
||||||
"pad.settings.poweredBy": "מופעל על גבי",
|
"pad.settings.poweredBy": "מופעל על גבי",
|
||||||
"pad.importExport.import_export": "יבוא/יצוא",
|
"pad.importExport.import_export": "יבוא/יצוא",
|
||||||
|
|
|
@ -72,8 +72,6 @@
|
||||||
"pad.settings.fontType": "Pismowa družina:",
|
"pad.settings.fontType": "Pismowa družina:",
|
||||||
"pad.settings.fontType.normal": "Normalny",
|
"pad.settings.fontType.normal": "Normalny",
|
||||||
"pad.settings.language": "Rěč:",
|
"pad.settings.language": "Rěč:",
|
||||||
"pad.settings.deletePad": "Zapisnik zhašeć",
|
|
||||||
"pad.delete.confirm": "Chceće woprawdźe tutón zapisnik zhašeć?",
|
|
||||||
"pad.settings.about": "Wo",
|
"pad.settings.about": "Wo",
|
||||||
"pad.settings.poweredBy": "Spěchowany wot",
|
"pad.settings.poweredBy": "Spěchowany wot",
|
||||||
"pad.importExport.import_export": "Import/Eksport",
|
"pad.importExport.import_export": "Import/Eksport",
|
||||||
|
|
|
@ -72,8 +72,6 @@
|
||||||
"pad.settings.fontType": "Typo de litteras:",
|
"pad.settings.fontType": "Typo de litteras:",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Lingua:",
|
"pad.settings.language": "Lingua:",
|
||||||
"pad.settings.deletePad": "Deler pad",
|
|
||||||
"pad.delete.confirm": "Es tu secur de voler deler iste pad?",
|
|
||||||
"pad.settings.about": "A proposito",
|
"pad.settings.about": "A proposito",
|
||||||
"pad.settings.poweredBy": "Actionate per",
|
"pad.settings.poweredBy": "Actionate per",
|
||||||
"pad.importExport.import_export": "Importar/Exportar",
|
"pad.importExport.import_export": "Importar/Exportar",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Akmaie Ajam",
|
|
||||||
"Atriwidada",
|
"Atriwidada",
|
||||||
"Bennylin",
|
"Bennylin",
|
||||||
"IvanLanin",
|
"IvanLanin",
|
||||||
|
@ -20,7 +19,6 @@
|
||||||
"admin_plugins.installed": "Plugin terpasang",
|
"admin_plugins.installed": "Plugin terpasang",
|
||||||
"admin_plugins.installed_fetching": "Mengambil plugin yang terpasang…",
|
"admin_plugins.installed_fetching": "Mengambil plugin yang terpasang…",
|
||||||
"admin_plugins.installed_nothing": "Anda belum memasang plugin apa pun.",
|
"admin_plugins.installed_nothing": "Anda belum memasang plugin apa pun.",
|
||||||
"admin_plugins.installed_uninstall.value": "Uninstal",
|
|
||||||
"admin_plugins.last-update": "Pembaruan terakhir",
|
"admin_plugins.last-update": "Pembaruan terakhir",
|
||||||
"admin_plugins.name": "Nama",
|
"admin_plugins.name": "Nama",
|
||||||
"admin_plugins.page-title": "Manajer plugin - Etherpad",
|
"admin_plugins.page-title": "Manajer plugin - Etherpad",
|
||||||
|
@ -76,8 +74,6 @@
|
||||||
"pad.settings.rtlcheck": "Membaca dari kanan ke kiri?",
|
"pad.settings.rtlcheck": "Membaca dari kanan ke kiri?",
|
||||||
"pad.settings.fontType": "Jenis fonta:",
|
"pad.settings.fontType": "Jenis fonta:",
|
||||||
"pad.settings.language": "Bahasa:",
|
"pad.settings.language": "Bahasa:",
|
||||||
"pad.settings.deletePad": "Hapus Pad",
|
|
||||||
"pad.delete.confirm": "Apakah Anda benar-benar ingin menghapus pad ini?",
|
|
||||||
"pad.settings.about": "Tentang",
|
"pad.settings.about": "Tentang",
|
||||||
"pad.settings.poweredBy": "Ditenagai oleh",
|
"pad.settings.poweredBy": "Ditenagai oleh",
|
||||||
"pad.importExport.import_export": "Impor/Ekspor",
|
"pad.importExport.import_export": "Impor/Ekspor",
|
||||||
|
|
|
@ -82,8 +82,6 @@
|
||||||
"pad.settings.fontType": "글꼴 종류:",
|
"pad.settings.fontType": "글꼴 종류:",
|
||||||
"pad.settings.fontType.normal": "보통",
|
"pad.settings.fontType.normal": "보통",
|
||||||
"pad.settings.language": "언어:",
|
"pad.settings.language": "언어:",
|
||||||
"pad.settings.deletePad": "패드 삭제",
|
|
||||||
"pad.delete.confirm": "이 패드를 삭제하시겠습니까?",
|
|
||||||
"pad.settings.about": "소개",
|
"pad.settings.about": "소개",
|
||||||
"pad.settings.poweredBy": "제공:",
|
"pad.settings.poweredBy": "제공:",
|
||||||
"pad.importExport.import_export": "가져오기/내보내기",
|
"pad.importExport.import_export": "가져오기/내보내기",
|
||||||
|
|
|
@ -82,7 +82,7 @@
|
||||||
"timeslider.toolbar.exportlink.title": "Exportéieren",
|
"timeslider.toolbar.exportlink.title": "Exportéieren",
|
||||||
"timeslider.exportCurrent": "Exportéiert déi aktuell Versioun als:",
|
"timeslider.exportCurrent": "Exportéiert déi aktuell Versioun als:",
|
||||||
"timeslider.version": "Versioun {{version}}",
|
"timeslider.version": "Versioun {{version}}",
|
||||||
"timeslider.saved": "Gespäichert de(n) {{day}}. {{month}} {{year}}",
|
"timeslider.saved": "Gespäichert de(n) {{day}} {{month}} {{year}}",
|
||||||
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||||
"timeslider.month.january": "Januar",
|
"timeslider.month.january": "Januar",
|
||||||
"timeslider.month.february": "Februar",
|
"timeslider.month.february": "Februar",
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"BOKOBA VEROLY"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"admin.page-title": "Admin Dashboard - Etherpad",
|
|
||||||
"admin_plugins": "Mokambi ya plug-in",
|
|
||||||
"admin_plugins.available": "Ba plugins oyo ezali",
|
|
||||||
"admin_plugins.available_not-found": "Ba plugins ezwamaki te.",
|
|
||||||
"admin_plugins.available_fetching": "Kozwa...",
|
|
||||||
"admin_plugins.available_install.value": "Kotya",
|
|
||||||
"admin_plugins.available_search.placeholder": "Bolukiluki ya ba plugins mpo na kotya",
|
|
||||||
"admin_plugins.description": "Ndimbola",
|
|
||||||
"admin_plugins.installed": "Ba plugins oyo etyamaki",
|
|
||||||
"admin_plugins.installed_fetching": "Kozwa ba plugins oyo etyamaki...",
|
|
||||||
"admin_plugins.installed_nothing": "Otikaki naino ba plugins te.",
|
|
||||||
"admin_plugins.installed_uninstall.value": "Kofungola esika",
|
|
||||||
"admin_plugins.last-update": "Makambo ya sika ya suka",
|
|
||||||
"admin_plugins.name": "Nkombo na yango",
|
|
||||||
"admin_plugins.page-title": "Gestionnaire de greffons — Etherpad",
|
|
||||||
"admin_plugins.version": "Libongoli",
|
|
||||||
"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": "Biteni oyo batye",
|
|
||||||
"admin_plugins_info.plugins": "Ba plugins oyo etyamaki",
|
|
||||||
"admin_plugins_info.page-title": "Makambo etali ordinatɛrɛ - Etherpad",
|
|
||||||
"admin_plugins_info.version": "Libongoli ya Etherpad",
|
|
||||||
"admin_plugins_info.version_latest": "Libongoli ya sika",
|
|
||||||
"admin_plugins_info.version_number": "Numero ya version",
|
|
||||||
"admin_settings": "Ndenge ya kobongisa yango",
|
|
||||||
"admin_settings.current": "Configuration ya lelo",
|
|
||||||
"admin_settings.current_example-devel": "Ndakisa modèle ya paramètres ya développement",
|
|
||||||
"admin_settings.current_example-prod": "Ndakisa modèle ya paramètres ya production",
|
|
||||||
"admin_settings.current_restart.value": "Bobandi lisusu Etherpad",
|
|
||||||
"admin_settings.current_save.value": "Bomba ba Paramètres",
|
|
||||||
"admin_settings.page-title": "Paramètres - Etherpad ya kosala",
|
|
||||||
"index.newPad": "Pad ya sika",
|
|
||||||
"index.createOpenPad": "to kosala/kofungola Pad na nkombo:",
|
|
||||||
"index.openPad": "kofungola Pad oyo ezali na nkombo:",
|
|
||||||
"pad.toolbar.bold.title": "Makomi ya moindo makasi (Ctrl+B)",
|
|
||||||
"pad.toolbar.underline.title": "Mokanda ya nse (Ctrl+U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "Strikethrough (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "Liste oyo esɛngami (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "Liste oyo etyami na molongo te (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "Indent (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "Mikuwa ya libándá (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "Undo (Ctrl+Z)",
|
|
||||||
"pad.toolbar.redo.title": "Redo (Ctrl+Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "Langi ya polele ya mokomi (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "Kokotisa/kobimisa na/na ba formats ya ba fichiers ndenge na ndenge",
|
|
||||||
"pad.toolbar.timeslider.title": "Mokambi ya ntango",
|
|
||||||
"pad.toolbar.savedRevision.title": "Kobomba lisusu",
|
|
||||||
"pad.toolbar.settings.title": "Ndenge ya kobongisa yango",
|
|
||||||
"pad.toolbar.embed.title": "Kopesa mpe kobakisa yango",
|
|
||||||
"pad.toolbar.showusers.title": "Tyá bato oyo basalelaka yango",
|
|
||||||
"pad.colorpicker.save": "Kobikisa",
|
|
||||||
"pad.colorpicker.cancel": "Kolongola"
|
|
||||||
}
|
|
|
@ -63,7 +63,7 @@
|
||||||
"pad.colorpicker.save": "Išsaugoti",
|
"pad.colorpicker.save": "Išsaugoti",
|
||||||
"pad.colorpicker.cancel": "Atšaukti",
|
"pad.colorpicker.cancel": "Atšaukti",
|
||||||
"pad.loading": "Įkraunama...",
|
"pad.loading": "Įkraunama...",
|
||||||
"pad.noCookie": "Slapuko rasti nepavyko. Prašome leisti slapukus savo naršyklėje! Jūsų sesija ir nustatymai nebus išsaugoti tarp apsilankymų. Taip gali būti dėl to, kad kai kuriose naršyklėse Etherpad yra įtrauktas į iFrame. Įsitikinkite, kad Etherpad yra tame pačiame padomenyje / domene kaip ir pirminis iFrame.",
|
"pad.noCookie": "Slapuko nepavyko rasti. Prašome leisti slapukus interneto naršyklėje!",
|
||||||
"pad.permissionDenied": "Jūs neturite leidimo patekti į šį bloknotą",
|
"pad.permissionDenied": "Jūs neturite leidimo patekti į šį bloknotą",
|
||||||
"pad.settings.padSettings": "Bloknoto nustatymai",
|
"pad.settings.padSettings": "Bloknoto nustatymai",
|
||||||
"pad.settings.myView": "Mano Vaizdas",
|
"pad.settings.myView": "Mano Vaizdas",
|
||||||
|
@ -75,8 +75,6 @@
|
||||||
"pad.settings.fontType": "Šrifto tipas:",
|
"pad.settings.fontType": "Šrifto tipas:",
|
||||||
"pad.settings.fontType.normal": "Normalus",
|
"pad.settings.fontType.normal": "Normalus",
|
||||||
"pad.settings.language": "Kalba:",
|
"pad.settings.language": "Kalba:",
|
||||||
"pad.settings.deletePad": "Ištrinti bloką",
|
|
||||||
"pad.delete.confirm": "Ar tikrai norite ištrinti šį bloką?",
|
|
||||||
"pad.settings.about": "Apie",
|
"pad.settings.about": "Apie",
|
||||||
"pad.settings.poweredBy": "Palaiko",
|
"pad.settings.poweredBy": "Palaiko",
|
||||||
"pad.importExport.import_export": "Importuoti/Eksportuoti",
|
"pad.importExport.import_export": "Importuoti/Eksportuoti",
|
||||||
|
|
|
@ -74,8 +74,6 @@
|
||||||
"pad.settings.fontType": "Тип на фонт:",
|
"pad.settings.fontType": "Тип на фонт:",
|
||||||
"pad.settings.fontType.normal": "Нормален",
|
"pad.settings.fontType.normal": "Нормален",
|
||||||
"pad.settings.language": "Јазик:",
|
"pad.settings.language": "Јазик:",
|
||||||
"pad.settings.deletePad": "Избриши тетратка",
|
|
||||||
"pad.delete.confirm": "Дали навистина сакате да ја избришете тетраткава?",
|
|
||||||
"pad.settings.about": "За додатоков",
|
"pad.settings.about": "За додатоков",
|
||||||
"pad.settings.poweredBy": "Овозможено од",
|
"pad.settings.poweredBy": "Овозможено од",
|
||||||
"pad.importExport.import_export": "Увоз/Извоз",
|
"pad.importExport.import_export": "Увоз/Извоз",
|
||||||
|
|
|
@ -85,8 +85,6 @@
|
||||||
"pad.settings.fontType": "Lettertype:",
|
"pad.settings.fontType": "Lettertype:",
|
||||||
"pad.settings.fontType.normal": "Normaal",
|
"pad.settings.fontType.normal": "Normaal",
|
||||||
"pad.settings.language": "Taal:",
|
"pad.settings.language": "Taal:",
|
||||||
"pad.settings.deletePad": "Pad verwijderen",
|
|
||||||
"pad.delete.confirm": "Wilt u dit pad echt verwijderen?",
|
|
||||||
"pad.settings.about": "Over",
|
"pad.settings.about": "Over",
|
||||||
"pad.settings.poweredBy": "Aangedreven door",
|
"pad.settings.poweredBy": "Aangedreven door",
|
||||||
"pad.importExport.import_export": "Importeren/exporteren",
|
"pad.importExport.import_export": "Importeren/exporteren",
|
||||||
|
|
|
@ -9,16 +9,6 @@
|
||||||
"ਪ੍ਰਚਾਰਕ"
|
"ਪ੍ਰਚਾਰਕ"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"admin_plugins.available_fetching": "ਲਿਆਉਣਾ ਪਿਆਂ...",
|
|
||||||
"admin_plugins.available_install.value": "ਜੜੋ",
|
|
||||||
"admin_plugins.description": "ਵੇਰਵਾ",
|
|
||||||
"admin_plugins.last-update": "ਆਖਰੀ ਵਾਰ ਨਵਿਆਈਆ ਗਿਆ",
|
|
||||||
"admin_plugins.name": "ਨਾਂ",
|
|
||||||
"admin_plugins_info": "ਸਮੱਸਿਆ ਨਿਵਾਰਣ ਜਾਣਕਾਰੀ",
|
|
||||||
"admin_settings": "ਤਰਜੀਹਾਂ",
|
|
||||||
"admin_settings.current": "ਮੌਜੂਦਾ ਬਣਤਰ",
|
|
||||||
"admin_settings.current_save.value": "ਤਰਜੀਹਾਂ ਸੰਭਾਲੋ",
|
|
||||||
"admin_settings.page-title": "ਤਰਜੀਹਾਂ - ਈਥਰਪੈਡ",
|
|
||||||
"index.newPad": "ਨਵਾਂ ਪੈਡ",
|
"index.newPad": "ਨਵਾਂ ਪੈਡ",
|
||||||
"index.createOpenPad": "ਜਾਂ ਨਾਂ ਨਾਲ ਨਵਾਂ ਪੈਡ ਬਣਾਓ/ਖੋਲ੍ਹੋ:",
|
"index.createOpenPad": "ਜਾਂ ਨਾਂ ਨਾਲ ਨਵਾਂ ਪੈਡ ਬਣਾਓ/ਖੋਲ੍ਹੋ:",
|
||||||
"pad.toolbar.bold.title": "ਗੂੜ੍ਹਾ (Ctrl-B)",
|
"pad.toolbar.bold.title": "ਗੂੜ੍ਹਾ (Ctrl-B)",
|
||||||
|
@ -35,7 +25,7 @@
|
||||||
"pad.toolbar.import_export.title": "ਵੱਖ-ਵੱਖ ਫਾਇਲ ਫਾਰਮੈਟ ਤੋਂ/ਵਿੱਚ ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ ਕਰੋ",
|
"pad.toolbar.import_export.title": "ਵੱਖ-ਵੱਖ ਫਾਇਲ ਫਾਰਮੈਟ ਤੋਂ/ਵਿੱਚ ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ ਕਰੋ",
|
||||||
"pad.toolbar.timeslider.title": "ਸਮਾਂ-ਲਕੀਰ",
|
"pad.toolbar.timeslider.title": "ਸਮਾਂ-ਲਕੀਰ",
|
||||||
"pad.toolbar.savedRevision.title": "ਦੁਹਰਾਅ ਸਾਂਭੋ",
|
"pad.toolbar.savedRevision.title": "ਦੁਹਰਾਅ ਸਾਂਭੋ",
|
||||||
"pad.toolbar.settings.title": "ਪਸੰਦਾਂ",
|
"pad.toolbar.settings.title": "ਸੈਟਿੰਗ",
|
||||||
"pad.toolbar.embed.title": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਤੇ ਇੰਬੈੱਡ ਕਰੋ",
|
"pad.toolbar.embed.title": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਤੇ ਇੰਬੈੱਡ ਕਰੋ",
|
||||||
"pad.toolbar.showusers.title": "ਇਸ ਫੱਟੀ ਉੱਤੇ ਵਰਤੋਂਕਾਰ ਵਿਖਾਓ",
|
"pad.toolbar.showusers.title": "ਇਸ ਫੱਟੀ ਉੱਤੇ ਵਰਤੋਂਕਾਰ ਵਿਖਾਓ",
|
||||||
"pad.colorpicker.save": "ਸੰਭਾਲੋ",
|
"pad.colorpicker.save": "ਸੰਭਾਲੋ",
|
||||||
|
@ -50,11 +40,10 @@
|
||||||
"pad.settings.colorcheck": "ਲੇਖਕੀ ਰੰਗ",
|
"pad.settings.colorcheck": "ਲੇਖਕੀ ਰੰਗ",
|
||||||
"pad.settings.linenocheck": "ਲਕੀਰ ਨੰਬਰ",
|
"pad.settings.linenocheck": "ਲਕੀਰ ਨੰਬਰ",
|
||||||
"pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?",
|
"pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?",
|
||||||
"pad.settings.fontType": "ਅੱਖਰ ਦੀ ਕਿਸਮ:",
|
"pad.settings.fontType": "ਫੋਂਟ ਕਿਸਮ:",
|
||||||
"pad.settings.fontType.normal": "ਆਮ",
|
"pad.settings.fontType.normal": "ਸਧਾਰਨ",
|
||||||
"pad.settings.language": "ਭਾਸ਼ਾ:",
|
"pad.settings.language": "ਭਾਸ਼ਾ:",
|
||||||
"pad.settings.about": "ਬਾਬਤ",
|
"pad.importExport.import_export": "ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ",
|
||||||
"pad.importExport.import_export": "ਦਰਾਮਦ/ਬਰਾਮਦ",
|
|
||||||
"pad.importExport.import": "ਕੋਈ ਵੀ ਟੈਕਸਟ ਫਾਇਲ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਅੱਪਲੋਡ ਕਰੋ",
|
"pad.importExport.import": "ਕੋਈ ਵੀ ਟੈਕਸਟ ਫਾਇਲ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਅੱਪਲੋਡ ਕਰੋ",
|
||||||
"pad.importExport.importSuccessful": "ਸਫ਼ਲ!",
|
"pad.importExport.importSuccessful": "ਸਫ਼ਲ!",
|
||||||
"pad.importExport.export": "ਮੌਜੂਦਾ ਪੈਡ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰੋ:",
|
"pad.importExport.export": "ਮੌਜੂਦਾ ਪੈਡ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰੋ:",
|
||||||
|
@ -68,7 +57,6 @@
|
||||||
"pad.modals.connected": "ਕੁਨੈਕਟ ਹੈ।",
|
"pad.modals.connected": "ਕੁਨੈਕਟ ਹੈ।",
|
||||||
"pad.modals.reconnecting": "..ਤੁਹਾਡੇ ਪੈਡ ਨਾਲ ਮੁੜ-ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
|
"pad.modals.reconnecting": "..ਤੁਹਾਡੇ ਪੈਡ ਨਾਲ ਮੁੜ-ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
|
||||||
"pad.modals.forcereconnect": "ਧੱਕੇ ਨਾਲ ਮੁੜ-ਕੁਨੈਕਟ ਕਰੋ",
|
"pad.modals.forcereconnect": "ਧੱਕੇ ਨਾਲ ਮੁੜ-ਕੁਨੈਕਟ ਕਰੋ",
|
||||||
"pad.modals.cancel": "ਰੱਦ ਕਰੋ",
|
|
||||||
"pad.modals.userdup": "ਹੋਰ ਵਿੰਡੋ ਵਿੱਚ ਖੁੱਲ੍ਹਿਆ ਹੈ",
|
"pad.modals.userdup": "ਹੋਰ ਵਿੰਡੋ ਵਿੱਚ ਖੁੱਲ੍ਹਿਆ ਹੈ",
|
||||||
"pad.modals.userdup.explanation": "ਇਹ ਪੈਡ ਇਸ ਕੰਪਿਊਟਰ 'ਤੇ ਇੱਕ ਤੋਂ ਵੱਧ ਫਰੋਲੂ ਬਾਰੀ ਵਿੱਚ ਖੁੱਲ੍ਹਿਆ ਜਾਪਦਾ ਹੈ।",
|
"pad.modals.userdup.explanation": "ਇਹ ਪੈਡ ਇਸ ਕੰਪਿਊਟਰ 'ਤੇ ਇੱਕ ਤੋਂ ਵੱਧ ਫਰੋਲੂ ਬਾਰੀ ਵਿੱਚ ਖੁੱਲ੍ਹਿਆ ਜਾਪਦਾ ਹੈ।",
|
||||||
"pad.modals.userdup.advice": "ਸਗੋਂ ਇਹ ਬਾਰੀ ਵਰਤਣ ਵਾਸਤੇ ਮੁੜ ਜੁੜੋ।",
|
"pad.modals.userdup.advice": "ਸਗੋਂ ਇਹ ਬਾਰੀ ਵਰਤਣ ਵਾਸਤੇ ਮੁੜ ਜੁੜੋ।",
|
||||||
|
@ -92,12 +80,11 @@
|
||||||
"pad.modals.disconnected.cause": "ਸਰਵਰ ਨਾਮੌਜੂਦ ਹੋ ਸਕਦਾ ਹੈ। ਜੇਕਰ ਇਹ ਹੁੰਦਾ ਰਹੇ ਤਾਂ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਸੇਵਾ ਪ੍ਰਬੰਧਕ ਨੂੰ ਖ਼ਬਰ ਕਰੋ।",
|
"pad.modals.disconnected.cause": "ਸਰਵਰ ਨਾਮੌਜੂਦ ਹੋ ਸਕਦਾ ਹੈ। ਜੇਕਰ ਇਹ ਹੁੰਦਾ ਰਹੇ ਤਾਂ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਸੇਵਾ ਪ੍ਰਬੰਧਕ ਨੂੰ ਖ਼ਬਰ ਕਰੋ।",
|
||||||
"pad.share": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਕਰੋ",
|
"pad.share": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਕਰੋ",
|
||||||
"pad.share.readonly": "ਕੇਵਲ ਪੜ੍ਹਨ ਲਈ",
|
"pad.share.readonly": "ਕੇਵਲ ਪੜ੍ਹਨ ਲਈ",
|
||||||
"pad.share.link": "ਕੜੀ",
|
"pad.share.link": "ਲਿੰਕ",
|
||||||
"pad.share.emebdcode": "ਇੰਬੈੱਡ URL",
|
"pad.share.emebdcode": "ਇੰਬੈੱਡ URL",
|
||||||
"pad.chat": "ਗੱਲਬਾਤ",
|
"pad.chat": "ਗੱਲਬਾਤ",
|
||||||
"pad.chat.title": "ਇਹ ਪੈਡ ਲਈ ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ।",
|
"pad.chat.title": "ਇਹ ਪੈਡ ਲਈ ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ।",
|
||||||
"pad.chat.loadmessages": "ਹੋਰ ਸੁਨੇਹੇ ਲੱਦੋ",
|
"pad.chat.loadmessages": "ਹੋਰ ਸੁਨੇਹੇ ਲੋਡ ਕਰੋ",
|
||||||
"pad.chat.writeMessage.placeholder": "ਆਪਣਾ ਸੁਨੇਹਾ ਇੱਥੇ ਲਿਖੋ",
|
|
||||||
"timeslider.pageTitle": "{{appTitle}} ਸਮਾਂ-ਲਕੀਰ",
|
"timeslider.pageTitle": "{{appTitle}} ਸਮਾਂ-ਲਕੀਰ",
|
||||||
"timeslider.toolbar.returnbutton": "ਪੈਡ ਉੱਤੇ ਵਾਪਸ",
|
"timeslider.toolbar.returnbutton": "ਪੈਡ ਉੱਤੇ ਵਾਪਸ",
|
||||||
"timeslider.toolbar.authors": "ਲੇਖਕ:",
|
"timeslider.toolbar.authors": "ਲੇਖਕ:",
|
||||||
|
@ -123,18 +110,18 @@
|
||||||
"timeslider.month.november": "ਨਵੰਬਰ",
|
"timeslider.month.november": "ਨਵੰਬਰ",
|
||||||
"timeslider.month.december": "ਦਸੰਬਰ",
|
"timeslider.month.december": "ਦਸੰਬਰ",
|
||||||
"timeslider.unnamedauthors": "{{num}} ਬੇਨਾਮ {[plural(num) one: ਲੇਖਕ, other: ਲੇਖਕ ]}",
|
"timeslider.unnamedauthors": "{{num}} ਬੇਨਾਮ {[plural(num) one: ਲੇਖਕ, other: ਲੇਖਕ ]}",
|
||||||
"pad.savedrevs.marked": "ਇਹ ਦੁਹਰਾਅ ਨੂੰ ਹੁਣ ਸੰਭਾਲੇ ਹੋਏ ਦੁਹਰਾਅ ਵਜੋਂ ਮੰਨਿਆ ਗਿਆ ਹੈ",
|
"pad.savedrevs.marked": "ਇਹ ਰੀਵਿਜ਼ਨ ਨੂੰ ਹੁਣ ਸੰਭਾਲੇ ਹੋਏ ਰੀਵਿਜ਼ਨ ਵਜੋਂ ਮੰਨਿਆ ਗਿਆ ਹੈ",
|
||||||
"pad.savedrevs.timeslider": "ਤੁਸੀੰ ਸਾੰਭੀਆੰ ਹੋਈਆੰ ਵਰਜਨਾੰ ਸਮਾੰਸਲਾਈਡਰ ਤੇ ਜਾ ਕੇ ਵੇਖ ਸਕਦੇ ਹੋ",
|
"pad.savedrevs.timeslider": "ਤੁਸੀੰ ਸਾੰਭੀਆੰ ਹੋਈਆੰ ਵਰਜਨਾੰ ਸਮਾੰਸਲਾਈਡਰ ਤੇ ਜਾ ਕੇ ਵੇਖ ਸਕਦੇ ਹੋ",
|
||||||
"pad.userlist.entername": "ਆਪਣਾ ਨਾਂ ਦਿਉ",
|
"pad.userlist.entername": "ਆਪਣਾ ਨਾਂ ਦਿਉ",
|
||||||
"pad.userlist.unnamed": "ਬੇਨਾਮ",
|
"pad.userlist.unnamed": "ਬੇਨਾਮ",
|
||||||
"pad.editbar.clearcolors": "ਪੂਰੇ ਦਸਾਤਵੇਜ਼ ਉੱਤੇ ਪਰਮਾਣਕਿਤਾ ਰੰਗ ਸਾਫ਼ ਕਰਨੇ ਹਨ?",
|
"pad.editbar.clearcolors": "ਪੂਰੇ ਦਸਾਤਵੇਜ਼ ਉੱਤੇ ਪਰਮਾਣਕਿਤਾ ਰੰਗ ਸਾਫ਼ ਕਰਨੇ ਹਨ?",
|
||||||
"pad.impexp.importbutton": "ਹੁਣੇ ਦਰਾਮਦ ਕਰੋ",
|
"pad.impexp.importbutton": "ਹੁਣੇ ਇੰਪੋਰਟ ਕਰੋ",
|
||||||
"pad.impexp.importing": "...ਇੰਪੋਰਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
|
"pad.impexp.importing": "...ਇੰਪੋਰਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
|
||||||
"pad.impexp.confirmimport": "ਕੋਈ ਫ਼ਾਈਲ ਦਰਾਮਦ ਕਾਰਨ ਨਾਲ਼ ਪੈਡ ਦੀ ਮੌਜੂਦਾ ਲਿਖਤ ਉੱਤੇ ਲਿਖਿਆ ਜਾਵੇਗਾ। ਕੀ ਤੁਸੀਂ ਸੱਚੀਂ ਇਹ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
"pad.impexp.confirmimport": "ਕੋਈ ਫ਼ਾਈਲ ਦਰਾਮਦ ਕਾਰਨ ਨਾਲ਼ ਪੈਡ ਦੀ ਮੌਜੂਦਾ ਲਿਖਤ ਉੱਤੇ ਲਿਖਿਆ ਜਾਵੇਗਾ। ਕੀ ਤੁਸੀਂ ਸੱਚੀਂ ਇਹ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||||
"pad.impexp.convertFailed": "ਅਸੀਂ ਇਸ ਫ਼ਾਈਲ ਦੀ ਦਰਾਮਦ ਨਹੀਂ ਕਰ ਸਕੇ। ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਕੋਈ ਵੱਖਰੀ ਦਸਤਾਵੇਜ਼ੀ ਰੂਪ-ਰੇਖਾ ਵਰਤੋ ਜਾਂ ਹੱਥੀਂ ਨਕਲ-ਚੇਪੀ ਕਰੋ।",
|
"pad.impexp.convertFailed": "ਅਸੀਂ ਇਸ ਫ਼ਾਈਲ ਦੀ ਦਰਾਮਦ ਨਹੀਂ ਕਰ ਸਕੇ। ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਕੋਈ ਵੱਖਰੀ ਦਸਤਾਵੇਜ਼ੀ ਰੂਪ-ਰੇਖਾ ਵਰਤੋ ਜਾਂ ਹੱਥੀਂ ਨਕਲ-ਚੇਪੀ ਕਰੋ।",
|
||||||
"pad.impexp.padHasData": "ਅਸੀ ਇਸ ਫਾਈਲ ਨੂੰ ਦਰਾਮਦ ਨਹੀੰ ਕਰ ਸਕੇ ਕਿਉੰਕਿ ਇਸ ਕਾਗਜ਼ ਉੱਤੇ ਪਹਿਲਾਂ ਹੀ ਤਬਦੀਲੀਆਂ ਕੀਤੀਆਂ ਜਾ ਚੁਕੀਆਂ ਹਨ, ਕਿਰਪਾ ਕਰਕੇ ਨਵੇਂ ਕਾਗਜ਼ ਵਿਚ ਦਰਾਮਦ ਕਰੋ",
|
"pad.impexp.padHasData": "ਅਸੀ ਇਸ ਫਾਈਲ ਨੂੰ ਆਯਾਤ ਨਹੀੰ ਕਰ ਸਕੇ ਕਿਉੰਕਿ ਇਸ ਪੈਡ ਉੱਤੇ ਪਹਿਲਾੰ ਹੀ ਤਬਦੀਲੀਆੰ ਕੀਤੀਆੰ ਜਾ ਚੁਕੀਆੰ ਹਨ, ਕਿਰਪਾ ਕਰਕੇ ਨਵੇੰ ਪੈਡ ਵਿਚ ਆਯਾਤ ਕਰੋ",
|
||||||
"pad.impexp.uploadFailed": "ਅੱਪਲੋਡ ਲਈ ਫੇਲ੍ਹ ਹੈ, ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।",
|
"pad.impexp.uploadFailed": "ਅੱਪਲੋਡ ਲਈ ਫੇਲ੍ਹ ਹੈ, ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।",
|
||||||
"pad.impexp.importfailed": "ਦਰਾਮਦ ਨਾਕਾਮ",
|
"pad.impexp.importfailed": "ਇੰਪੋਰਟ ਫੇਲ੍ਹ ਹੈ",
|
||||||
"pad.impexp.copypaste": "ਕਾਪੀ ਕਰੋ ਚੇਪੋ ਜੀ",
|
"pad.impexp.copypaste": "ਕਾਪੀ ਕਰੋ ਚੇਪੋ ਜੀ",
|
||||||
"pad.impexp.exportdisabled": "{{type}} ਫਾਰਮੈਟ ਵਜੋਂ ਬਰਾਮਦ ਕਰਨਾ ਬੰਦ ਹੈ। ਵੇਰਵੇ ਵਾਸਤੇ ਆਪਣੇ ਸਿਸਟਮ ਦੇ ਪਰਬੰਧਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"
|
"pad.impexp.exportdisabled": "{{type}} ਫਾਰਮੈਟ ਵਜੋਂ ਬਰਾਮਦ ਕਰਨਾ ਬੰਦ ਹੈ। ਵੇਰਵੇ ਵਾਸਤੇ ਆਪਣੇ ਸਿਸਟਮ ਦੇ ਪਰਬੰਧਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,6 @@
|
||||||
"pad.settings.rtlcheck": "Ël contnù, dev-lo esse lesù da drita a snistra?",
|
"pad.settings.rtlcheck": "Ël contnù, dev-lo esse lesù da drita a snistra?",
|
||||||
"pad.settings.fontType": "Sòrt ëd caràter:",
|
"pad.settings.fontType": "Sòrt ëd caràter:",
|
||||||
"pad.settings.language": "Lenga:",
|
"pad.settings.language": "Lenga:",
|
||||||
"pad.settings.deletePad": "Eliminé ël blochet",
|
|
||||||
"pad.delete.confirm": "Veul-lo për da bon eliminé cost blochet?",
|
|
||||||
"pad.settings.about": "A propòsit",
|
"pad.settings.about": "A propòsit",
|
||||||
"pad.settings.poweredBy": "Potensià da",
|
"pad.settings.poweredBy": "Potensià da",
|
||||||
"pad.importExport.import_export": "Amporté/Esporté",
|
"pad.importExport.import_export": "Amporté/Esporté",
|
||||||
|
|
|
@ -23,22 +23,22 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"admin.page-title": "Painel administrativo - Etherpad",
|
"admin.page-title": "Painel administrativo - Etherpad",
|
||||||
"admin_plugins": "Gerenciador de complementos",
|
"admin_plugins": "Gerente de complementos",
|
||||||
"admin_plugins.available": "Plugins disponíveis",
|
"admin_plugins.available": "Plugins disponíveis",
|
||||||
"admin_plugins.available_not-found": "Nenhum plugin encontrado.",
|
"admin_plugins.available_not-found": "Nenhuma complementos encontrado.",
|
||||||
"admin_plugins.available_fetching": "Buscando…",
|
"admin_plugins.available_fetching": "Buscando…",
|
||||||
"admin_plugins.available_install.value": "Instalar",
|
"admin_plugins.available_install.value": "Instalar",
|
||||||
"admin_plugins.available_search.placeholder": "Procure plugins para instalar",
|
"admin_plugins.available_search.placeholder": "Procure por plug-ins para instalar",
|
||||||
"admin_plugins.description": "Descrição",
|
"admin_plugins.description": "Descrição",
|
||||||
"admin_plugins.installed": "Plugins instalados",
|
"admin_plugins.installed": "Complementos instalados",
|
||||||
"admin_plugins.installed_fetching": "Buscando plugins instalados…",
|
"admin_plugins.installed_fetching": "Buscando plug-ins instalados…",
|
||||||
"admin_plugins.installed_nothing": "Você ainda não instalou nenhum plugin.",
|
"admin_plugins.installed_nothing": "Você ainda não instalou nenhum plug-in.",
|
||||||
"admin_plugins.installed_uninstall.value": "Desinstalar",
|
"admin_plugins.installed_uninstall.value": "Desinstalar",
|
||||||
"admin_plugins.last-update": "Última atualização",
|
"admin_plugins.last-update": "Última atualização",
|
||||||
"admin_plugins.name": "Nome",
|
"admin_plugins.name": "Nome",
|
||||||
"admin_plugins.page-title": "Gerenciador de plugins - Etherpad",
|
"admin_plugins.page-title": "Gerenciador de plug-ins - Etherpad",
|
||||||
"admin_plugins.version": "Versão",
|
"admin_plugins.version": "Versão",
|
||||||
"admin_plugins_info": "Resolução de problemas",
|
"admin_plugins_info": "Informações sobre solução",
|
||||||
"admin_plugins_info.hooks": "Ganchos instalados",
|
"admin_plugins_info.hooks": "Ganchos instalados",
|
||||||
"admin_plugins_info.hooks_client": "Ganchos do lado do cliente",
|
"admin_plugins_info.hooks_client": "Ganchos do lado do cliente",
|
||||||
"admin_plugins_info.hooks_server": "Ganchos do lado do servidor",
|
"admin_plugins_info.hooks_server": "Ganchos do lado do servidor",
|
||||||
|
@ -90,8 +90,6 @@
|
||||||
"pad.settings.fontType": "Tipo de fonte:",
|
"pad.settings.fontType": "Tipo de fonte:",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Idioma:",
|
"pad.settings.language": "Idioma:",
|
||||||
"pad.settings.deletePad": "Apagar Pad",
|
|
||||||
"pad.delete.confirm": "Tem certeza de que quer deletar este pad?",
|
|
||||||
"pad.settings.about": "Sobre",
|
"pad.settings.about": "Sobre",
|
||||||
"pad.settings.poweredBy": "Fornecido por",
|
"pad.settings.poweredBy": "Fornecido por",
|
||||||
"pad.importExport.import_export": "Importar/Exportar",
|
"pad.importExport.import_export": "Importar/Exportar",
|
||||||
|
@ -129,7 +127,7 @@
|
||||||
"pad.modals.deleted": "Excluído.",
|
"pad.modals.deleted": "Excluído.",
|
||||||
"pad.modals.deleted.explanation": "Esta nota foi removida.",
|
"pad.modals.deleted.explanation": "Esta nota foi removida.",
|
||||||
"pad.modals.rateLimited": "Limitado.",
|
"pad.modals.rateLimited": "Limitado.",
|
||||||
"pad.modals.rateLimited.explanation": "Você enviou muitas mensagens para esta nota por isso será desconectado.",
|
"pad.modals.rateLimited.explanation": "Você enviou muitas mensagens para este pad por isso será desconectado.",
|
||||||
"pad.modals.rejected.explanation": "O servidor rejeitou uma mensagem que foi enviada pelo seu navegador.",
|
"pad.modals.rejected.explanation": "O servidor rejeitou uma mensagem que foi enviada pelo seu navegador.",
|
||||||
"pad.modals.rejected.cause": "O server pode ter sido atualizado enquanto visualizava esta nota, ou talvez seja apenas um bug do Etherpad. Tenta recarregar a página.",
|
"pad.modals.rejected.cause": "O server pode ter sido atualizado enquanto visualizava esta nota, ou talvez seja apenas um bug do Etherpad. Tenta recarregar a página.",
|
||||||
"pad.modals.disconnected": "Você foi desconectado.",
|
"pad.modals.disconnected": "Você foi desconectado.",
|
||||||
|
@ -144,7 +142,7 @@
|
||||||
"pad.chat.loadmessages": "Carregar mais mensagens",
|
"pad.chat.loadmessages": "Carregar mais mensagens",
|
||||||
"pad.chat.stick.title": "Cole o bate-papo na tela",
|
"pad.chat.stick.title": "Cole o bate-papo na tela",
|
||||||
"pad.chat.writeMessage.placeholder": "Escreva sua mensagem aqui",
|
"pad.chat.writeMessage.placeholder": "Escreva sua mensagem aqui",
|
||||||
"timeslider.followContents": "Siga as atualizações de conteúdo da nota",
|
"timeslider.followContents": "Siga as atualizações de conteúdo do pad",
|
||||||
"timeslider.pageTitle": "Linha do tempo de {{appTitle}}",
|
"timeslider.pageTitle": "Linha do tempo de {{appTitle}}",
|
||||||
"timeslider.toolbar.returnbutton": "Retornar para a nota",
|
"timeslider.toolbar.returnbutton": "Retornar para a nota",
|
||||||
"timeslider.toolbar.authors": "Autores:",
|
"timeslider.toolbar.authors": "Autores:",
|
||||||
|
@ -153,9 +151,9 @@
|
||||||
"timeslider.exportCurrent": "Exportar a versão atual em formato:",
|
"timeslider.exportCurrent": "Exportar a versão atual em formato:",
|
||||||
"timeslider.version": "Versão {{version}}",
|
"timeslider.version": "Versão {{version}}",
|
||||||
"timeslider.saved": "Salvo em {{day}} de {{month}} de {{year}}",
|
"timeslider.saved": "Salvo em {{day}} de {{month}} de {{year}}",
|
||||||
"timeslider.playPause": "Reproduzir / Pausar conteúdos da Nota",
|
"timeslider.playPause": "Reproduzir / Pausar conteúdo no Pad",
|
||||||
"timeslider.backRevision": "Voltar a uma revisão anterior nesta Nota",
|
"timeslider.backRevision": "Voltar a uma revisão anterior neste Pad",
|
||||||
"timeslider.forwardRevision": "Ir a uma revisão posterior nesta Nota",
|
"timeslider.forwardRevision": "Ir a uma revisão posterior neste Pad",
|
||||||
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||||
"timeslider.month.january": "Janeiro",
|
"timeslider.month.january": "Janeiro",
|
||||||
"timeslider.month.february": "Fevereiro",
|
"timeslider.month.february": "Fevereiro",
|
||||||
|
|
|
@ -87,8 +87,6 @@
|
||||||
"pad.settings.fontType": "Тип шрифта:",
|
"pad.settings.fontType": "Тип шрифта:",
|
||||||
"pad.settings.fontType.normal": "Обычный",
|
"pad.settings.fontType.normal": "Обычный",
|
||||||
"pad.settings.language": "Язык:",
|
"pad.settings.language": "Язык:",
|
||||||
"pad.settings.deletePad": "Удалить документ",
|
|
||||||
"pad.delete.confirm": "Вы действительно хотите удалить этот документ?",
|
|
||||||
"pad.settings.about": "О проекте",
|
"pad.settings.about": "О проекте",
|
||||||
"pad.settings.poweredBy": "Проект основан на",
|
"pad.settings.poweredBy": "Проект основан на",
|
||||||
"pad.importExport.import_export": "Импорт/экспорт",
|
"pad.importExport.import_export": "Импорт/экспорт",
|
||||||
|
|
|
@ -77,8 +77,6 @@
|
||||||
"pad.settings.fontType": "Vrsta pisave:",
|
"pad.settings.fontType": "Vrsta pisave:",
|
||||||
"pad.settings.fontType.normal": "Normalno",
|
"pad.settings.fontType.normal": "Normalno",
|
||||||
"pad.settings.language": "Jezik:",
|
"pad.settings.language": "Jezik:",
|
||||||
"pad.settings.deletePad": "Izbriši ploščico",
|
|
||||||
"pad.delete.confirm": "Res želite izbrisati to ploščico?",
|
|
||||||
"pad.settings.about": "Kolofon",
|
"pad.settings.about": "Kolofon",
|
||||||
"pad.settings.poweredBy": "Omogoča",
|
"pad.settings.poweredBy": "Omogoča",
|
||||||
"pad.importExport.import_export": "Uvoz/Izvoz",
|
"pad.importExport.import_export": "Uvoz/Izvoz",
|
||||||
|
|
|
@ -76,8 +76,6 @@
|
||||||
"pad.settings.fontType": "Typsnitt:",
|
"pad.settings.fontType": "Typsnitt:",
|
||||||
"pad.settings.fontType.normal": "Normal",
|
"pad.settings.fontType.normal": "Normal",
|
||||||
"pad.settings.language": "Språk:",
|
"pad.settings.language": "Språk:",
|
||||||
"pad.settings.deletePad": "Radera block",
|
|
||||||
"pad.delete.confirm": "Vill du verkligen radera detta block?",
|
|
||||||
"pad.settings.about": "Om",
|
"pad.settings.about": "Om",
|
||||||
"pad.settings.poweredBy": "Drivs av",
|
"pad.settings.poweredBy": "Drivs av",
|
||||||
"pad.importExport.import_export": "Importera/Exportera",
|
"pad.importExport.import_export": "Importera/Exportera",
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
"authors": [
|
"authors": [
|
||||||
"Aefgh39622",
|
"Aefgh39622",
|
||||||
"Andibecker",
|
"Andibecker",
|
||||||
"Ekminarin",
|
|
||||||
"Patsagorn Y.",
|
"Patsagorn Y.",
|
||||||
"Trisorn Triboon"
|
"Trisorn Triboon"
|
||||||
]
|
]
|
||||||
|
@ -122,7 +121,7 @@
|
||||||
"pad.share.readonly": "อ่านเท่านั้น",
|
"pad.share.readonly": "อ่านเท่านั้น",
|
||||||
"pad.share.link": "ลิงก์",
|
"pad.share.link": "ลิงก์",
|
||||||
"pad.share.emebdcode": "URL แบบฝังตัว",
|
"pad.share.emebdcode": "URL แบบฝังตัว",
|
||||||
"pad.chat": "แชต",
|
"pad.chat": "แชท",
|
||||||
"pad.chat.title": "เปิดการแชทสำหรับแผ่นจดบันทึกนี้",
|
"pad.chat.title": "เปิดการแชทสำหรับแผ่นจดบันทึกนี้",
|
||||||
"pad.chat.loadmessages": "โหลดข้อความเพิ่มเติม",
|
"pad.chat.loadmessages": "โหลดข้อความเพิ่มเติม",
|
||||||
"pad.chat.stick.title": "ปักการสนทนาไว้บนหน้าจอ",
|
"pad.chat.stick.title": "ปักการสนทนาไว้บนหน้าจอ",
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
"Shangkuanlc",
|
"Shangkuanlc",
|
||||||
"Shizhao",
|
"Shizhao",
|
||||||
"Stang",
|
"Stang",
|
||||||
"TFX202X",
|
|
||||||
"VulpesVulpes825",
|
"VulpesVulpes825",
|
||||||
"Yfdyh000",
|
"Yfdyh000",
|
||||||
"乌拉跨氪",
|
"乌拉跨氪",
|
||||||
|
@ -93,8 +92,6 @@
|
||||||
"pad.settings.fontType": "字体类型:",
|
"pad.settings.fontType": "字体类型:",
|
||||||
"pad.settings.fontType.normal": "正常",
|
"pad.settings.fontType.normal": "正常",
|
||||||
"pad.settings.language": "语言:",
|
"pad.settings.language": "语言:",
|
||||||
"pad.settings.deletePad": "删除记事本",
|
|
||||||
"pad.delete.confirm": "您确定要删除此记事本吗?",
|
|
||||||
"pad.settings.about": "关于",
|
"pad.settings.about": "关于",
|
||||||
"pad.settings.poweredBy": "技术支持来自",
|
"pad.settings.poweredBy": "技术支持来自",
|
||||||
"pad.importExport.import_export": "导入/导出",
|
"pad.importExport.import_export": "导入/导出",
|
||||||
|
|
|
@ -82,8 +82,6 @@
|
||||||
"pad.settings.fontType": "字型類型:",
|
"pad.settings.fontType": "字型類型:",
|
||||||
"pad.settings.fontType.normal": "正常",
|
"pad.settings.fontType.normal": "正常",
|
||||||
"pad.settings.language": "語言:",
|
"pad.settings.language": "語言:",
|
||||||
"pad.settings.deletePad": "刪除記事本",
|
|
||||||
"pad.delete.confirm": "您確定要刪除此記事本?",
|
|
||||||
"pad.settings.about": "關於",
|
"pad.settings.about": "關於",
|
||||||
"pad.settings.poweredBy": "技術支援來自",
|
"pad.settings.poweredBy": "技術支援來自",
|
||||||
"pad.importExport.import_export": "導入/匯出",
|
"pad.importExport.import_export": "導入/匯出",
|
||||||
|
|
|
@ -19,10 +19,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {deserializeOps} from '../../static/js/Changeset';
|
const Changeset = require('../../static/js/Changeset');
|
||||||
import ChatMessage from '../../static/js/ChatMessage';
|
const ChatMessage = require('../../static/js/ChatMessage');
|
||||||
import {Builder} from "../../static/js/Builder";
|
|
||||||
import {Attribute} from "../../static/js/types/Attribute";
|
|
||||||
const CustomError = require('../utils/customError');
|
const CustomError = require('../utils/customError');
|
||||||
const padManager = require('./PadManager');
|
const padManager = require('./PadManager');
|
||||||
const padMessageHandler = require('../handler/PadMessageHandler');
|
const padMessageHandler = require('../handler/PadMessageHandler');
|
||||||
|
@ -565,11 +563,11 @@ exports.restoreRevision = async (padID: string, rev: number, authorId = '') => {
|
||||||
const oldText = pad.text();
|
const oldText = pad.text();
|
||||||
atext.text += '\n';
|
atext.text += '\n';
|
||||||
|
|
||||||
const eachAttribRun = (attribs: string, func:Function) => {
|
const eachAttribRun = (attribs: string[], func:Function) => {
|
||||||
let textIndex = 0;
|
let textIndex = 0;
|
||||||
const newTextStart = 0;
|
const newTextStart = 0;
|
||||||
const newTextEnd = atext.text.length;
|
const newTextEnd = atext.text.length;
|
||||||
for (const op of deserializeOps(attribs)) {
|
for (const op of Changeset.deserializeOps(attribs)) {
|
||||||
const nextIndex = textIndex + op.chars;
|
const nextIndex = textIndex + op.chars;
|
||||||
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
|
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
|
||||||
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
|
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
|
||||||
|
@ -579,10 +577,10 @@ exports.restoreRevision = async (padID: string, rev: number, authorId = '') => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// create a new changeset with a helper builder object
|
// create a new changeset with a helper builder object
|
||||||
const builder = new Builder(oldText.length);
|
const builder = Changeset.builder(oldText.length);
|
||||||
|
|
||||||
// assemble each line into the builder
|
// assemble each line into the builder
|
||||||
eachAttribRun(atext.attribs, (start: number, end: number, attribs:Attribute[]) => {
|
eachAttribRun(atext.attribs, (start: number, end: number, attribs:string[]) => {
|
||||||
builder.insert(atext.text.substring(start, end), attribs);
|
builder.insert(atext.text.substring(start, end), attribs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
|
|
||||||
const db = require('./DB');
|
const db = require('./DB');
|
||||||
const CustomError = require('../utils/customError');
|
const CustomError = require('../utils/customError');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks.js');
|
||||||
import padutils, {randomString} from "../../static/js/pad_utils";
|
const {randomString, padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
|
||||||
|
|
||||||
exports.getColorPalette = () => [
|
exports.getColorPalette = () => [
|
||||||
'#ffc7c7',
|
'#ffc7c7',
|
||||||
|
@ -169,7 +169,7 @@ exports.getAuthorId = async (token: string, user: object) => {
|
||||||
* @param {String} token The token
|
* @param {String} token The token
|
||||||
*/
|
*/
|
||||||
exports.getAuthor4Token = async (token: string) => {
|
exports.getAuthor4Token = async (token: string) => {
|
||||||
padutils.warnDeprecated(
|
warnDeprecated(
|
||||||
'AuthorManager.getAuthor4Token() is deprecated; use AuthorManager.getAuthorId() instead');
|
'AuthorManager.getAuthor4Token() is deprecated; use AuthorManager.getAuthorId() instead');
|
||||||
return await getAuthor4Token(token);
|
return await getAuthor4Token(token);
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CustomError = require('../utils/customError');
|
const CustomError = require('../utils/customError');
|
||||||
import {randomString} from "../../static/js/pad_utils";
|
const randomString = require('../../static/js/pad_utils').randomString;
|
||||||
const db = require('./DB');
|
const db = require('./DB');
|
||||||
const padManager = require('./PadManager');
|
const padManager = require('./PadManager');
|
||||||
const sessionManager = require('./SessionManager');
|
const sessionManager = require('./SessionManager');
|
||||||
|
|
|
@ -7,10 +7,10 @@ import {MapArrayType} from "../types/MapType";
|
||||||
* The pad object, defined with joose
|
* The pad object, defined with joose
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import AttributeMap from '../../static/js/AttributeMap';
|
const AttributeMap = require('../../static/js/AttributeMap');
|
||||||
import {applyToAText, checkRep, copyAText, deserializeOps, makeAText, makeSplice, opsFromAText, pack, unpack} from '../../static/js/Changeset';
|
const Changeset = require('../../static/js/Changeset');
|
||||||
import ChatMessage from '../../static/js/ChatMessage';
|
const ChatMessage = require('../../static/js/ChatMessage');
|
||||||
import AttributePool from '../../static/js/AttributePool';
|
const AttributePool = require('../../static/js/AttributePool');
|
||||||
const Stream = require('../utils/Stream');
|
const Stream = require('../utils/Stream');
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
const db = require('./DB');
|
const db = require('./DB');
|
||||||
|
@ -23,10 +23,8 @@ const CustomError = require('../utils/customError');
|
||||||
const readOnlyManager = require('./ReadOnlyManager');
|
const readOnlyManager = require('./ReadOnlyManager');
|
||||||
const randomString = require('../utils/randomstring');
|
const randomString = require('../utils/randomstring');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks');
|
||||||
import pad_utils from "../../static/js/pad_utils";
|
const {padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
|
||||||
import {SmartOpAssembler} from "../../static/js/SmartOpAssembler";
|
const promises = require('../utils/promises');
|
||||||
import {} from '../utils/promises';
|
|
||||||
import {timesLimit} from "async";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied from the Etherpad source code. It converts Windows line breaks to Unix
|
* Copied from the Etherpad source code. It converts Windows line breaks to Unix
|
||||||
|
@ -42,7 +40,7 @@ exports.cleanText = (txt:string): string => txt.replace(/\r\n/g, '\n')
|
||||||
class Pad {
|
class Pad {
|
||||||
private db: Database;
|
private db: Database;
|
||||||
private atext: AText;
|
private atext: AText;
|
||||||
private pool: AttributePool;
|
private pool: APool;
|
||||||
private head: number;
|
private head: number;
|
||||||
private chatHead: number;
|
private chatHead: number;
|
||||||
private publicStatus: boolean;
|
private publicStatus: boolean;
|
||||||
|
@ -58,7 +56,7 @@ class Pad {
|
||||||
*/
|
*/
|
||||||
constructor(id:string, database = db) {
|
constructor(id:string, database = db) {
|
||||||
this.db = database;
|
this.db = database;
|
||||||
this.atext = makeAText('\n');
|
this.atext = Changeset.makeAText('\n');
|
||||||
this.pool = new AttributePool();
|
this.pool = new AttributePool();
|
||||||
this.head = -1;
|
this.head = -1;
|
||||||
this.chatHead = -1;
|
this.chatHead = -1;
|
||||||
|
@ -95,13 +93,13 @@ class Pad {
|
||||||
* @param {String} authorId The id of the author
|
* @param {String} authorId The id of the author
|
||||||
* @return {Promise<number|string>}
|
* @return {Promise<number|string>}
|
||||||
*/
|
*/
|
||||||
async appendRevision(aChangeset:string, authorId = '') {
|
async appendRevision(aChangeset:AChangeSet, authorId = '') {
|
||||||
const newAText = applyToAText(aChangeset, this.atext, this.pool);
|
const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
|
||||||
if (newAText.text === this.atext.text && newAText.attribs === this.atext.attribs &&
|
if (newAText.text === this.atext.text && newAText.attribs === this.atext.attribs &&
|
||||||
this.head !== -1) {
|
this.head !== -1) {
|
||||||
return this.head;
|
return this.head;
|
||||||
}
|
}
|
||||||
copyAText(newAText, this.atext);
|
Changeset.copyAText(newAText, this.atext);
|
||||||
|
|
||||||
const newRev = ++this.head;
|
const newRev = ++this.head;
|
||||||
|
|
||||||
|
@ -128,11 +126,11 @@ class Pad {
|
||||||
pad: this,
|
pad: this,
|
||||||
authorId,
|
authorId,
|
||||||
get author() {
|
get author() {
|
||||||
pad_utils.warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
|
warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
|
||||||
return this.authorId;
|
return this.authorId;
|
||||||
},
|
},
|
||||||
set author(authorId) {
|
set author(authorId) {
|
||||||
pad_utils.warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
|
warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
|
||||||
this.authorId = authorId;
|
this.authorId = authorId;
|
||||||
},
|
},
|
||||||
...this.head === 0 ? {} : {
|
...this.head === 0 ? {} : {
|
||||||
|
@ -217,7 +215,7 @@ class Pad {
|
||||||
]);
|
]);
|
||||||
const apool = this.apool();
|
const apool = this.apool();
|
||||||
let atext = keyAText;
|
let atext = keyAText;
|
||||||
for (const cs of changesets) atext = applyToAText(cs, atext, apool);
|
for (const cs of changesets) atext = Changeset.applyToAText(cs, atext, apool);
|
||||||
return atext;
|
return atext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +293,7 @@ class Pad {
|
||||||
(!ins && start > 0 && orig[start - 1] === '\n');
|
(!ins && start > 0 && orig[start - 1] === '\n');
|
||||||
if (!willEndWithNewline) ins += '\n';
|
if (!willEndWithNewline) ins += '\n';
|
||||||
if (ndel === 0 && ins.length === 0) return;
|
if (ndel === 0 && ins.length === 0) return;
|
||||||
const changeset = makeSplice(orig, start, ndel, ins);
|
const changeset = Changeset.makeSplice(orig, start, ndel, ins);
|
||||||
await this.appendRevision(changeset, authorId);
|
await this.appendRevision(changeset, authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +330,7 @@ class Pad {
|
||||||
* @param {?number} [time] - Message timestamp (milliseconds since epoch). Deprecated; use
|
* @param {?number} [time] - Message timestamp (milliseconds since epoch). Deprecated; use
|
||||||
* `msgOrText.time` instead.
|
* `msgOrText.time` instead.
|
||||||
*/
|
*/
|
||||||
async appendChatMessage(msgOrText: string| ChatMessage, authorId = null, time = null) {
|
async appendChatMessage(msgOrText: string|typeof ChatMessage, authorId = null, time = null) {
|
||||||
const msg =
|
const msg =
|
||||||
msgOrText instanceof ChatMessage ? msgOrText : new ChatMessage(msgOrText, authorId, time);
|
msgOrText instanceof ChatMessage ? msgOrText : new ChatMessage(msgOrText, authorId, time);
|
||||||
this.chatHead++;
|
this.chatHead++;
|
||||||
|
@ -395,7 +393,7 @@ class Pad {
|
||||||
if (context.type !== 'text') throw new Error(`unsupported content type: ${context.type}`);
|
if (context.type !== 'text') throw new Error(`unsupported content type: ${context.type}`);
|
||||||
text = exports.cleanText(context.content);
|
text = exports.cleanText(context.content);
|
||||||
}
|
}
|
||||||
const firstChangeset = makeSplice('\n', 0, 0, text);
|
const firstChangeset = Changeset.makeSplice('\n', 0, 0, text);
|
||||||
await this.appendRevision(firstChangeset, authorId);
|
await this.appendRevision(firstChangeset, authorId);
|
||||||
}
|
}
|
||||||
await hooks.aCallAll('padLoad', {pad: this});
|
await hooks.aCallAll('padLoad', {pad: this});
|
||||||
|
@ -439,11 +437,11 @@ class Pad {
|
||||||
// let the plugins know the pad was copied
|
// let the plugins know the pad was copied
|
||||||
await hooks.aCallAll('padCopy', {
|
await hooks.aCallAll('padCopy', {
|
||||||
get originalPad() {
|
get originalPad() {
|
||||||
pad_utils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
|
warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
|
||||||
return this.srcPad;
|
return this.srcPad;
|
||||||
},
|
},
|
||||||
get destinationID() {
|
get destinationID() {
|
||||||
pad_utils.warnDeprecated(
|
warnDeprecated(
|
||||||
'padCopy destinationID context property is deprecated; use dstPad.id instead');
|
'padCopy destinationID context property is deprecated; use dstPad.id instead');
|
||||||
return this.dstPad.id;
|
return this.dstPad.id;
|
||||||
},
|
},
|
||||||
|
@ -522,8 +520,8 @@ class Pad {
|
||||||
const oldAText = this.atext;
|
const oldAText = this.atext;
|
||||||
|
|
||||||
// based on Changeset.makeSplice
|
// based on Changeset.makeSplice
|
||||||
const assem = new SmartOpAssembler();
|
const assem = Changeset.smartOpAssembler();
|
||||||
for (const op of opsFromAText(oldAText)) assem.append(op);
|
for (const op of Changeset.opsFromAText(oldAText)) assem.append(op);
|
||||||
assem.endDocument();
|
assem.endDocument();
|
||||||
|
|
||||||
// although we have instantiated the dstPad with '\n', an additional '\n' is
|
// although we have instantiated the dstPad with '\n', an additional '\n' is
|
||||||
|
@ -535,16 +533,16 @@ class Pad {
|
||||||
|
|
||||||
// create a changeset that removes the previous text and add the newText with
|
// create a changeset that removes the previous text and add the newText with
|
||||||
// all atributes present on the source pad
|
// all atributes present on the source pad
|
||||||
const changeset = pack(oldLength, newLength, assem.toString(), newText);
|
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||||
dstPad.appendRevision(changeset, authorId);
|
dstPad.appendRevision(changeset, authorId);
|
||||||
|
|
||||||
await hooks.aCallAll('padCopy', {
|
await hooks.aCallAll('padCopy', {
|
||||||
get originalPad() {
|
get originalPad() {
|
||||||
pad_utils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
|
warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
|
||||||
return this.srcPad;
|
return this.srcPad;
|
||||||
},
|
},
|
||||||
get destinationID() {
|
get destinationID() {
|
||||||
pad_utils.warnDeprecated(
|
warnDeprecated(
|
||||||
'padCopy destinationID context property is deprecated; use dstPad.id instead');
|
'padCopy destinationID context property is deprecated; use dstPad.id instead');
|
||||||
return this.dstPad.id;
|
return this.dstPad.id;
|
||||||
},
|
},
|
||||||
|
@ -587,14 +585,12 @@ class Pad {
|
||||||
p.push(db.remove(`pad2readonly:${padID}`));
|
p.push(db.remove(`pad2readonly:${padID}`));
|
||||||
|
|
||||||
// delete all chat messages
|
// delete all chat messages
|
||||||
// @ts-ignore
|
p.push(promises.timesLimit(this.chatHead + 1, 500, async (i: string) => {
|
||||||
p.push(timesLimit(this.chatHead + 1, 500, async (i: string) => {
|
|
||||||
await this.db.remove(`pad:${this.id}:chat:${i}`, null);
|
await this.db.remove(`pad:${this.id}:chat:${i}`, null);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// delete all revisions
|
// delete all revisions
|
||||||
// @ts-ignore
|
p.push(promises.timesLimit(this.head + 1, 500, async (i: string) => {
|
||||||
p.push(timesLimit(this.head + 1, 500, async (i: string) => {
|
|
||||||
await this.db.remove(`pad:${this.id}:revs:${i}`, null);
|
await this.db.remove(`pad:${this.id}:revs:${i}`, null);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -607,7 +603,7 @@ class Pad {
|
||||||
p.push(padManager.removePad(padID));
|
p.push(padManager.removePad(padID));
|
||||||
p.push(hooks.aCallAll('padRemove', {
|
p.push(hooks.aCallAll('padRemove', {
|
||||||
get padID() {
|
get padID() {
|
||||||
pad_utils.warnDeprecated('padRemove padID context property is deprecated; use pad.id instead');
|
warnDeprecated('padRemove padID context property is deprecated; use pad.id instead');
|
||||||
return this.pad.id;
|
return this.pad.id;
|
||||||
},
|
},
|
||||||
pad: this,
|
pad: this,
|
||||||
|
@ -710,7 +706,7 @@ class Pad {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.batch(100).buffer(99);
|
.batch(100).buffer(99);
|
||||||
let atext = makeAText('\n');
|
let atext = Changeset.makeAText('\n');
|
||||||
for await (const [r, changeset, authorId, timestamp, isKeyRev, keyAText] of revs) {
|
for await (const [r, changeset, authorId, timestamp, isKeyRev, keyAText] of revs) {
|
||||||
try {
|
try {
|
||||||
assert(authorId != null);
|
assert(authorId != null);
|
||||||
|
@ -721,10 +717,10 @@ class Pad {
|
||||||
assert(timestamp > 0);
|
assert(timestamp > 0);
|
||||||
assert(changeset != null);
|
assert(changeset != null);
|
||||||
assert.equal(typeof changeset, 'string');
|
assert.equal(typeof changeset, 'string');
|
||||||
checkRep(changeset);
|
Changeset.checkRep(changeset);
|
||||||
const unpacked = unpack(changeset);
|
const unpacked = Changeset.unpack(changeset);
|
||||||
let text = atext.text;
|
let text = atext.text;
|
||||||
for (const op of deserializeOps(unpacked.ops)) {
|
for (const op of Changeset.deserializeOps(unpacked.ops)) {
|
||||||
if (['=', '-'].includes(op.opcode)) {
|
if (['=', '-'].includes(op.opcode)) {
|
||||||
assert(text.length >= op.chars);
|
assert(text.length >= op.chars);
|
||||||
const consumed = text.slice(0, op.chars);
|
const consumed = text.slice(0, op.chars);
|
||||||
|
@ -735,7 +731,7 @@ class Pad {
|
||||||
}
|
}
|
||||||
assert.equal(op.attribs, AttributeMap.fromString(op.attribs, pool).toString());
|
assert.equal(op.attribs, AttributeMap.fromString(op.attribs, pool).toString());
|
||||||
}
|
}
|
||||||
atext = applyToAText(changeset, atext, pool);
|
atext = Changeset.applyToAText(changeset, atext, pool);
|
||||||
if (isKeyRev) assert.deepEqual(keyAText, atext);
|
if (isKeyRev) assert.deepEqual(keyAText, atext);
|
||||||
} catch (err:any) {
|
} catch (err:any) {
|
||||||
err.message = `(pad ${this.id} revision ${r}) ${err.message}`;
|
err.message = `(pad ${this.id} revision ${r}) ${err.message}`;
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
import {UserSettingsObject} from "../types/UserSettingsObject";
|
import {UserSettingsObject} from "../types/UserSettingsObject";
|
||||||
|
|
||||||
const authorManager = require('./AuthorManager');
|
const authorManager = require('./AuthorManager');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks.js');
|
||||||
const padManager = require('./PadManager');
|
const padManager = require('./PadManager');
|
||||||
const readOnlyManager = require('./ReadOnlyManager');
|
const readOnlyManager = require('./ReadOnlyManager');
|
||||||
const sessionManager = require('./SessionManager');
|
const sessionManager = require('./SessionManager');
|
||||||
|
@ -30,7 +30,7 @@ const settings = require('../utils/Settings');
|
||||||
const webaccess = require('../hooks/express/webaccess');
|
const webaccess = require('../hooks/express/webaccess');
|
||||||
const log4js = require('log4js');
|
const log4js = require('log4js');
|
||||||
const authLogger = log4js.getLogger('auth');
|
const authLogger = log4js.getLogger('auth');
|
||||||
import padutils from '../../static/js/pad_utils'
|
const {padutils} = require('../../static/js/pad_utils');
|
||||||
|
|
||||||
const DENY = Object.freeze({accessStatus: 'deny'});
|
const DENY = Object.freeze({accessStatus: 'deny'});
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CustomError = require('../utils/customError');
|
const CustomError = require('../utils/customError');
|
||||||
import {firstSatisfies} from '../utils/promises';
|
const promises = require('../utils/promises');
|
||||||
const randomString = require('../utils/randomstring');
|
const randomString = require('../utils/randomstring');
|
||||||
const db = require('./DB');
|
const db = require('./DB');
|
||||||
const groupManager = require('./GroupManager');
|
const groupManager = require('./GroupManager');
|
||||||
|
@ -79,7 +79,7 @@ exports.findAuthorID = async (groupID:string, sessionCookie: string) => {
|
||||||
groupID: string;
|
groupID: string;
|
||||||
validUntil: number;
|
validUntil: number;
|
||||||
}|null) => (si != null && si.groupID === groupID && now < si.validUntil);
|
}|null) => (si != null && si.groupID === groupID && now < si.validUntil);
|
||||||
const sessionInfo = await firstSatisfies(sessionInfoPromises, isMatch) as any;
|
const sessionInfo = await promises.firstSatisfies(sessionInfoPromises, isMatch);
|
||||||
if (sessionInfo == null) return undefined;
|
if (sessionInfo == null) return undefined;
|
||||||
return sessionInfo.authorID;
|
return sessionInfo.authorID;
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
const ejs = require('ejs');
|
const ejs = require('ejs');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks.js');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const resolve = require('resolve');
|
const resolve = require('resolve');
|
||||||
const settings = require('../utils/Settings');
|
const settings = require('../utils/Settings');
|
||||||
|
|
|
@ -20,15 +20,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MapArrayType} from "../types/MapType";
|
import {MapArrayType} from "../types/MapType";
|
||||||
import { jwtDecode } from "jwt-decode";
|
|
||||||
const api = require('../db/API');
|
const api = require('../db/API');
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
const settings = require('../utils/Settings');
|
|
||||||
import createHTTPError from 'http-errors';
|
import createHTTPError from 'http-errors';
|
||||||
import {Http2ServerRequest} from "node:http2";
|
import {Http2ServerRequest, Http2ServerResponse} from "node:http2";
|
||||||
import {publicKeyExported} from "../security/OAuth2Provider";
|
import {publicKeyExported} from "../security/OAuth2Provider";
|
||||||
import {jwtVerify} from "jose";
|
import {jwtVerify} from "jose";
|
||||||
import {APIFields, apikey} from './APIKeyHandler'
|
import {apikey} from './APIKeyHandler'
|
||||||
// a list of all functions
|
// a list of all functions
|
||||||
const version:MapArrayType<any> = {};
|
const version:MapArrayType<any> = {};
|
||||||
|
|
||||||
|
@ -142,7 +141,6 @@ version['1.3.0'] = {
|
||||||
setText: ['padID', 'text', 'authorId'],
|
setText: ['padID', 'text', 'authorId'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// set the latest available API version here
|
// set the latest available API version here
|
||||||
exports.latestApiVersion = '1.3.0';
|
exports.latestApiVersion = '1.3.0';
|
||||||
|
|
||||||
|
@ -150,6 +148,13 @@ exports.latestApiVersion = '1.3.0';
|
||||||
exports.version = version;
|
exports.version = version;
|
||||||
|
|
||||||
|
|
||||||
|
type APIFields = {
|
||||||
|
apikey: string;
|
||||||
|
api_key: string;
|
||||||
|
padID: string;
|
||||||
|
padName: string;
|
||||||
|
authorization: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an HTTP API call
|
* Handles an HTTP API call
|
||||||
|
@ -183,17 +188,8 @@ exports.handle = async function (apiVersion: string, functionName: string, field
|
||||||
throw new createHTTPError.Unauthorized('no or wrong API Key');
|
throw new createHTTPError.Unauthorized('no or wrong API Key');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const clientIds: string[] = settings.sso.clients?.map((client: {client_id: string}) => client.client_id);
|
await jwtVerify(req.headers.authorization!.replace("Bearer ", ""), publicKeyExported!, {algorithms: ['RS256'],
|
||||||
const jwtToCheck = req.headers.authorization.replace("Bearer ", "")
|
requiredClaims: ["admin"]})
|
||||||
const payload = jwtDecode(jwtToCheck)
|
|
||||||
// client_credentials
|
|
||||||
if (clientIds.includes(<string>payload.sub)) {
|
|
||||||
await jwtVerify(jwtToCheck, publicKeyExported!, {algorithms: ['RS256']})
|
|
||||||
} else {
|
|
||||||
// authorization_code
|
|
||||||
await jwtVerify(jwtToCheck, publicKeyExported!, {algorithms: ['RS256'],
|
|
||||||
requiredClaims: ["admin"]})
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new createHTTPError.Unauthorized('no or wrong OAuth token');
|
throw new createHTTPError.Unauthorized('no or wrong OAuth token');
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,6 @@ const settings = require('../utils/Settings');
|
||||||
|
|
||||||
const apiHandlerLogger = log4js.getLogger('APIHandler');
|
const apiHandlerLogger = log4js.getLogger('APIHandler');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type APIFields = {
|
|
||||||
apikey: string;
|
|
||||||
api_key: string;
|
|
||||||
padID: string;
|
|
||||||
padName: string;
|
|
||||||
authorization: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure we have an apikey
|
// ensure we have an apikey
|
||||||
export let apikey:string|null = null;
|
export let apikey:string|null = null;
|
||||||
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
|
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
|
||||||
|
|
|
@ -31,7 +31,7 @@ import os from 'os';
|
||||||
const importHtml = require('../utils/ImportHtml');
|
const importHtml = require('../utils/ImportHtml');
|
||||||
const importEtherpad = require('../utils/ImportEtherpad');
|
const importEtherpad = require('../utils/ImportEtherpad');
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks.js');
|
||||||
|
|
||||||
const logger = log4js.getLogger('ImportHandler');
|
const logger = log4js.getLogger('ImportHandler');
|
||||||
|
|
||||||
|
|
|
@ -21,30 +21,28 @@
|
||||||
|
|
||||||
import {MapArrayType} from "../types/MapType";
|
import {MapArrayType} from "../types/MapType";
|
||||||
|
|
||||||
import AttributeMap from '../../static/js/AttributeMap';
|
const AttributeMap = require('../../static/js/AttributeMap');
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
import {checkRep, cloneAText, compose, deserializeOps, follow, identity, inverse, makeAText, makeSplice, moveOpsToNewPool, mutateAttributionLines, mutateTextLines, oldLen, prepareForWire, splitAttributionLines, splitTextLines, unpack} from '../../static/js/Changeset';
|
const Changeset = require('../../static/js/Changeset');
|
||||||
import ChatMessage from '../../static/js/ChatMessage';
|
const ChatMessage = require('../../static/js/ChatMessage');
|
||||||
import AttributePool from '../../static/js/AttributePool';
|
const AttributePool = require('../../static/js/AttributePool');
|
||||||
const AttributeManager = require('../../static/js/AttributeManager');
|
const AttributeManager = require('../../static/js/AttributeManager');
|
||||||
const authorManager = require('../db/AuthorManager');
|
const authorManager = require('../db/AuthorManager');
|
||||||
import padutils from '../../static/js/pad_utils';
|
const {padutils} = require('../../static/js/pad_utils');
|
||||||
const readOnlyManager = require('../db/ReadOnlyManager');
|
const readOnlyManager = require('../db/ReadOnlyManager');
|
||||||
const settings = require('../utils/Settings');
|
const settings = require('../utils/Settings');
|
||||||
const securityManager = require('../db/SecurityManager');
|
const securityManager = require('../db/SecurityManager');
|
||||||
const plugins = require('../../static/js/pluginfw/plugin_defs');
|
const plugins = require('../../static/js/pluginfw/plugin_defs.js');
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
const messageLogger = log4js.getLogger('message');
|
const messageLogger = log4js.getLogger('message');
|
||||||
const accessLogger = log4js.getLogger('access');
|
const accessLogger = log4js.getLogger('access');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks.js');
|
||||||
const stats = require('../stats')
|
const stats = require('../stats')
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
import {RateLimiterMemory} from 'rate-limiter-flexible';
|
import {RateLimiterMemory} from 'rate-limiter-flexible';
|
||||||
import {ChangesetRequest, PadUserInfo, SocketClientRequest} from "../types/SocketClientRequest";
|
import {ChangesetRequest, PadUserInfo, SocketClientRequest} from "../types/SocketClientRequest";
|
||||||
import {APool, AText, PadAuthor, PadType} from "../types/PadType";
|
import {APool, AText, PadAuthor, PadType} from "../types/PadType";
|
||||||
import {ChangeSet} from "../types/ChangeSet";
|
import {ChangeSet} from "../types/ChangeSet";
|
||||||
import {ChatMessageMessage, ClientReadyMessage, ClientSaveRevisionMessage, ClientSuggestUserName, ClientUserChangesMessage, ClientVarMessage, CustomMessage, PadDeleteMessage, UserNewInfoMessage} from "../../static/js/types/SocketIOMessage";
|
|
||||||
import {Builder} from "../../static/js/Builder";
|
|
||||||
const webaccess = require('../hooks/express/webaccess');
|
const webaccess = require('../hooks/express/webaccess');
|
||||||
const { checkValidRev } = require('../utils/checkValidRev');
|
const { checkValidRev } = require('../utils/checkValidRev');
|
||||||
|
|
||||||
|
@ -211,51 +209,12 @@ exports.handleDisconnect = async (socket:any) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handlePadDelete = async (socket: any, padDeleteMessage: PadDeleteMessage) => {
|
|
||||||
const session = sessioninfos[socket.id];
|
|
||||||
if (!session || !session.author || !session.padId) throw new Error('session not ready');
|
|
||||||
if (await padManager.doesPadExist(padDeleteMessage.data.padId)) {
|
|
||||||
const retrievedPad = await padManager.getPad(padDeleteMessage.data.padId)
|
|
||||||
// Only the one doing the first revision can delete the pad, otherwise people could troll a lot
|
|
||||||
const firstContributor = await retrievedPad.getRevisionAuthor(0)
|
|
||||||
if (session.author === firstContributor) {
|
|
||||||
retrievedPad.remove()
|
|
||||||
} else {
|
|
||||||
|
|
||||||
type ShoutMessage = {
|
|
||||||
message: string,
|
|
||||||
sticky: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageToShout: ShoutMessage = {
|
|
||||||
message: 'You are not the creator of this pad, so you cannot delete it',
|
|
||||||
sticky: false
|
|
||||||
}
|
|
||||||
const messageToSend = {
|
|
||||||
type: "COLLABROOM",
|
|
||||||
data: {
|
|
||||||
type: "shoutMessage",
|
|
||||||
payload: {
|
|
||||||
message: messageToShout,
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
socket.emit('shout',
|
|
||||||
messageToSend
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a message from a user
|
* Handles a message from a user
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
exports.handleMessage = async (socket:any, message: ClientVarMessage) => {
|
exports.handleMessage = async (socket:any, message:typeof ChatMessage) => {
|
||||||
const env = process.env.NODE_ENV || 'development';
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
if (env === 'production') {
|
if (env === 'production') {
|
||||||
|
@ -389,16 +348,15 @@ exports.handleMessage = async (socket:any, message: ClientVarMessage) => {
|
||||||
stats.counter('pendingEdits').inc();
|
stats.counter('pendingEdits').inc();
|
||||||
await padChannels.enqueue(thisSession.padId, {socket, message});
|
await padChannels.enqueue(thisSession.padId, {socket, message});
|
||||||
break;
|
break;
|
||||||
case 'PAD_DELETE': await handlePadDelete(socket, message.data as unknown as PadDeleteMessage); break;
|
case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message); break;
|
||||||
case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message as unknown as UserNewInfoMessage); break;
|
case 'CHAT_MESSAGE': await handleChatMessage(socket, message); break;
|
||||||
case 'CHAT_MESSAGE': await handleChatMessage(socket, message as unknown as ChatMessageMessage); break;
|
|
||||||
case 'GET_CHAT_MESSAGES': await handleGetChatMessages(socket, message); break;
|
case 'GET_CHAT_MESSAGES': await handleGetChatMessages(socket, message); break;
|
||||||
case 'SAVE_REVISION': await handleSaveRevisionMessage(socket, message as unknown as ClientSaveRevisionMessage); break;
|
case 'SAVE_REVISION': await handleSaveRevisionMessage(socket, message); break;
|
||||||
case 'CLIENT_MESSAGE': {
|
case 'CLIENT_MESSAGE': {
|
||||||
const {type} = message.data.payload;
|
const {type} = message.data.payload;
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'suggestUserName': handleSuggestUserName(socket, message as unknown as ClientSuggestUserName); break;
|
case 'suggestUserName': handleSuggestUserName(socket, message); break;
|
||||||
default: throw new Error('unknown message type');
|
default: throw new Error('unknown message type');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -426,7 +384,7 @@ exports.handleMessage = async (socket:any, message: ClientVarMessage) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleSaveRevisionMessage = async (socket:any, message: ClientSaveRevisionMessage) => {
|
const handleSaveRevisionMessage = async (socket:any, message: string) => {
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
const pad = await padManager.getPad(padId, null, authorId);
|
const pad = await padManager.getPad(padId, null, authorId);
|
||||||
await pad.addSavedRevision(pad.head, authorId);
|
await pad.addSavedRevision(pad.head, authorId);
|
||||||
|
@ -439,7 +397,7 @@ const handleSaveRevisionMessage = async (socket:any, message: ClientSaveRevision
|
||||||
* @param msg {Object} the message we're sending
|
* @param msg {Object} the message we're sending
|
||||||
* @param sessionID {string} the socketIO session to which we're sending this message
|
* @param sessionID {string} the socketIO session to which we're sending this message
|
||||||
*/
|
*/
|
||||||
exports.handleCustomObjectMessage = (msg: CustomMessage, sessionID: string) => {
|
exports.handleCustomObjectMessage = (msg: typeof ChatMessage, sessionID: string) => {
|
||||||
if (msg.data.type === 'CUSTOM') {
|
if (msg.data.type === 'CUSTOM') {
|
||||||
if (sessionID) {
|
if (sessionID) {
|
||||||
// a sessionID is targeted: directly to this sessionID
|
// a sessionID is targeted: directly to this sessionID
|
||||||
|
@ -474,7 +432,7 @@ exports.handleCustomMessage = (padID: string, msgString:string) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleChatMessage = async (socket:any, message: ChatMessageMessage) => {
|
const handleChatMessage = async (socket:any, message: typeof ChatMessage) => {
|
||||||
const chatMessage = ChatMessage.fromObject(message.data.message);
|
const chatMessage = ChatMessage.fromObject(message.data.message);
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
// Don't trust the user-supplied values.
|
// Don't trust the user-supplied values.
|
||||||
|
@ -494,7 +452,7 @@ const handleChatMessage = async (socket:any, message: ChatMessageMessage) => {
|
||||||
* @param {string} [padId] - The destination pad ID. Deprecated; pass a chat message
|
* @param {string} [padId] - The destination pad ID. Deprecated; pass a chat message
|
||||||
* object as the first argument and the destination pad ID as the second argument instead.
|
* object as the first argument and the destination pad ID as the second argument instead.
|
||||||
*/
|
*/
|
||||||
exports.sendChatMessageToPadClients = async (mt: ChatMessage|number, puId: string, text:string|null = null, padId:string|null = null) => {
|
exports.sendChatMessageToPadClients = async (mt: typeof ChatMessage|number, puId: string, text:string|null = null, padId:string|null = null) => {
|
||||||
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
|
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
|
||||||
padId = mt instanceof ChatMessage ? puId : padId;
|
padId = mt instanceof ChatMessage ? puId : padId;
|
||||||
const pad = await padManager.getPad(padId, null, message.authorId);
|
const pad = await padManager.getPad(padId, null, message.authorId);
|
||||||
|
@ -541,7 +499,7 @@ const handleGetChatMessages = async (socket:any, {data: {start, end}}:any) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleSuggestUserName = (socket:any, message: ClientSuggestUserName) => {
|
const handleSuggestUserName = (socket:any, message: typeof ChatMessage) => {
|
||||||
const {newName, unnamedId} = message.data.payload;
|
const {newName, unnamedId} = message.data.payload;
|
||||||
if (newName == null) throw new Error('missing newName');
|
if (newName == null) throw new Error('missing newName');
|
||||||
if (unnamedId == null) throw new Error('missing unnamedId');
|
if (unnamedId == null) throw new Error('missing unnamedId');
|
||||||
|
@ -561,7 +519,7 @@ const handleSuggestUserName = (socket:any, message: ClientSuggestUserName) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleUserInfoUpdate = async (socket:any, {data: {userInfo: {name, colorId}}}: UserNewInfoMessage) => {
|
const handleUserInfoUpdate = async (socket:any, {data: {userInfo: {name, colorId}}}: PadUserInfo) => {
|
||||||
if (colorId == null) throw new Error('missing colorId');
|
if (colorId == null) throw new Error('missing colorId');
|
||||||
if (!name) name = null;
|
if (!name) name = null;
|
||||||
const session = sessioninfos[socket.id];
|
const session = sessioninfos[socket.id];
|
||||||
|
@ -609,9 +567,7 @@ const handleUserInfoUpdate = async (socket:any, {data: {userInfo: {name, colorId
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleUserChanges = async (socket:any, message: {
|
const handleUserChanges = async (socket:any, message: typeof ChatMessage) => {
|
||||||
data: ClientUserChangesMessage
|
|
||||||
}) => {
|
|
||||||
// This one's no longer pending, as we're gonna process it now
|
// This one's no longer pending, as we're gonna process it now
|
||||||
stats.counter('pendingEdits').dec();
|
stats.counter('pendingEdits').dec();
|
||||||
|
|
||||||
|
@ -635,10 +591,10 @@ const handleUserChanges = async (socket:any, message: {
|
||||||
const pad = await padManager.getPad(thisSession.padId, null, thisSession.author);
|
const pad = await padManager.getPad(thisSession.padId, null, thisSession.author);
|
||||||
|
|
||||||
// Verify that the changeset has valid syntax and is in canonical form
|
// Verify that the changeset has valid syntax and is in canonical form
|
||||||
checkRep(changeset);
|
Changeset.checkRep(changeset);
|
||||||
|
|
||||||
// Validate all added 'author' attribs to be the same value as the current user
|
// Validate all added 'author' attribs to be the same value as the current user
|
||||||
for (const op of deserializeOps(unpack(changeset).ops)) {
|
for (const op of Changeset.deserializeOps(Changeset.unpack(changeset).ops)) {
|
||||||
// + can add text with attribs
|
// + can add text with attribs
|
||||||
// = can change or add attribs
|
// = can change or add attribs
|
||||||
// - can have attribs, but they are discarded and don't show up in the attribs -
|
// - can have attribs, but they are discarded and don't show up in the attribs -
|
||||||
|
@ -657,7 +613,7 @@ const handleUserChanges = async (socket:any, message: {
|
||||||
// ex. adoptChangesetAttribs
|
// ex. adoptChangesetAttribs
|
||||||
|
|
||||||
// Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
|
// Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
|
||||||
let rebasedChangeset = moveOpsToNewPool(changeset, wireApool, pad.pool);
|
let rebasedChangeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool);
|
||||||
|
|
||||||
// ex. applyUserChanges
|
// ex. applyUserChanges
|
||||||
let r = baseRev;
|
let r = baseRev;
|
||||||
|
@ -670,21 +626,21 @@ const handleUserChanges = async (socket:any, message: {
|
||||||
const {changeset: c, meta: {author: authorId}} = await pad.getRevision(r);
|
const {changeset: c, meta: {author: authorId}} = await pad.getRevision(r);
|
||||||
if (changeset === c && thisSession.author === authorId) {
|
if (changeset === c && thisSession.author === authorId) {
|
||||||
// Assume this is a retransmission of an already applied changeset.
|
// Assume this is a retransmission of an already applied changeset.
|
||||||
rebasedChangeset = identity(unpack(changeset).oldLen);
|
rebasedChangeset = Changeset.identity(Changeset.unpack(changeset).oldLen);
|
||||||
}
|
}
|
||||||
// At this point, both "c" (from the pad) and "changeset" (from the
|
// At this point, both "c" (from the pad) and "changeset" (from the
|
||||||
// client) are relative to revision r - 1. The follow function
|
// client) are relative to revision r - 1. The follow function
|
||||||
// rebases "changeset" so that it is relative to revision r
|
// rebases "changeset" so that it is relative to revision r
|
||||||
// and can be applied after "c".
|
// and can be applied after "c".
|
||||||
rebasedChangeset = follow(c, rebasedChangeset, false, pad.pool);
|
rebasedChangeset = Changeset.follow(c, rebasedChangeset, false, pad.pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevText = pad.text();
|
const prevText = pad.text();
|
||||||
|
|
||||||
if (oldLen(rebasedChangeset) !== prevText.length) {
|
if (Changeset.oldLen(rebasedChangeset) !== prevText.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Can't apply changeset ${rebasedChangeset} with oldLen ` +
|
`Can't apply changeset ${rebasedChangeset} with oldLen ` +
|
||||||
`${oldLen(rebasedChangeset)} to document of length ${prevText.length}`);
|
`${Changeset.oldLen(rebasedChangeset)} to document of length ${prevText.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newRev = await pad.appendRevision(rebasedChangeset, thisSession.author);
|
const newRev = await pad.appendRevision(rebasedChangeset, thisSession.author);
|
||||||
|
@ -699,7 +655,7 @@ const handleUserChanges = async (socket:any, message: {
|
||||||
|
|
||||||
// Make sure the pad always ends with an empty line.
|
// Make sure the pad always ends with an empty line.
|
||||||
if (pad.text().lastIndexOf('\n') !== pad.text().length - 1) {
|
if (pad.text().lastIndexOf('\n') !== pad.text().length - 1) {
|
||||||
const nlChangeset = makeSplice(pad.text(), pad.text().length - 1, 0, '\n');
|
const nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length - 1, 0, '\n');
|
||||||
await pad.appendRevision(nlChangeset, thisSession.author);
|
await pad.appendRevision(nlChangeset, thisSession.author);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,7 +710,7 @@ exports.updatePadClients = async (pad: PadType) => {
|
||||||
const revChangeset = revision.changeset;
|
const revChangeset = revision.changeset;
|
||||||
const currentTime = revision.meta.timestamp;
|
const currentTime = revision.meta.timestamp;
|
||||||
|
|
||||||
const forWire = prepareForWire(revChangeset, pad.pool);
|
const forWire = Changeset.prepareForWire(revChangeset, pad.pool);
|
||||||
const msg = {
|
const msg = {
|
||||||
type: 'COLLABROOM',
|
type: 'COLLABROOM',
|
||||||
data: {
|
data: {
|
||||||
|
@ -782,14 +738,14 @@ exports.updatePadClients = async (pad: PadType) => {
|
||||||
/**
|
/**
|
||||||
* Copied from the Etherpad Source Code. Don't know what this method does excatly...
|
* Copied from the Etherpad Source Code. Don't know what this method does excatly...
|
||||||
*/
|
*/
|
||||||
const _correctMarkersInPad = (atext: AText, apool: AttributePool) => {
|
const _correctMarkersInPad = (atext: AText, apool: APool) => {
|
||||||
const text = atext.text;
|
const text = atext.text;
|
||||||
|
|
||||||
// collect char positions of line markers (e.g. bullets) in new atext
|
// collect char positions of line markers (e.g. bullets) in new atext
|
||||||
// that aren't at the start of a line
|
// that aren't at the start of a line
|
||||||
const badMarkers = [];
|
const badMarkers = [];
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const op of deserializeOps(atext.attribs)) {
|
for (const op of Changeset.deserializeOps(atext.attribs)) {
|
||||||
const attribs = AttributeMap.fromString(op.attribs, apool);
|
const attribs = AttributeMap.fromString(op.attribs, apool);
|
||||||
const hasMarker = AttributeManager.lineAttributes.some((a: string) => attribs.has(a));
|
const hasMarker = AttributeManager.lineAttributes.some((a: string) => attribs.has(a));
|
||||||
if (hasMarker) {
|
if (hasMarker) {
|
||||||
|
@ -811,7 +767,7 @@ const _correctMarkersInPad = (atext: AText, apool: AttributePool) => {
|
||||||
// create changeset that removes these bad markers
|
// create changeset that removes these bad markers
|
||||||
offset = 0;
|
offset = 0;
|
||||||
|
|
||||||
const builder = new Builder(text.length);
|
const builder = Changeset.builder(text.length);
|
||||||
|
|
||||||
badMarkers.forEach((pos) => {
|
badMarkers.forEach((pos) => {
|
||||||
builder.keepText(text.substring(offset, pos));
|
builder.keepText(text.substring(offset, pos));
|
||||||
|
@ -829,7 +785,7 @@ const _correctMarkersInPad = (atext: AText, apool: AttributePool) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
const handleClientReady = async (socket:any, message: typeof ChatMessage) => {
|
||||||
const sessionInfo = sessioninfos[socket.id];
|
const sessionInfo = sessioninfos[socket.id];
|
||||||
if (sessionInfo == null) throw new Error('client disconnected');
|
if (sessionInfo == null) throw new Error('client disconnected');
|
||||||
assert(sessionInfo.author);
|
assert(sessionInfo.author);
|
||||||
|
@ -837,9 +793,8 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
||||||
await hooks.aCallAll('clientReady', message); // Deprecated due to awkward context.
|
await hooks.aCallAll('clientReady', message); // Deprecated due to awkward context.
|
||||||
|
|
||||||
let {colorId: authorColorId, name: authorName} = message.userInfo || {};
|
let {colorId: authorColorId, name: authorName} = message.userInfo || {};
|
||||||
if (authorColorId && !/^#(?:[0-9A-F]{3}){1,2}$/i.test(authorColorId as string)) {
|
if (authorColorId && !/^#(?:[0-9A-F]{3}){1,2}$/i.test(authorColorId)) {
|
||||||
messageLogger.warn(`Ignoring invalid colorId in CLIENT_READY message: ${authorColorId}`);
|
messageLogger.warn(`Ignoring invalid colorId in CLIENT_READY message: ${authorColorId}`);
|
||||||
// @ts-ignore
|
|
||||||
authorColorId = null;
|
authorColorId = null;
|
||||||
}
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -917,7 +872,7 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
||||||
const revisionsNeeded = [];
|
const revisionsNeeded = [];
|
||||||
const changesets:MapArrayType<any> = {};
|
const changesets:MapArrayType<any> = {};
|
||||||
|
|
||||||
let startNum = message.client_rev! + 1;
|
let startNum = message.client_rev + 1;
|
||||||
let endNum = pad.getHeadRevisionNumber() + 1;
|
let endNum = pad.getHeadRevisionNumber() + 1;
|
||||||
|
|
||||||
const headNum = pad.getHeadRevisionNumber();
|
const headNum = pad.getHeadRevisionNumber();
|
||||||
|
@ -946,7 +901,7 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
||||||
|
|
||||||
// return pending changesets
|
// return pending changesets
|
||||||
for (const r of revisionsNeeded) {
|
for (const r of revisionsNeeded) {
|
||||||
const forWire = prepareForWire(changesets[r].changeset, pad.pool);
|
const forWire = Changeset.prepareForWire(changesets[r].changeset, pad.pool);
|
||||||
const wireMsg = {type: 'COLLABROOM',
|
const wireMsg = {type: 'COLLABROOM',
|
||||||
data: {type: 'CLIENT_RECONNECT',
|
data: {type: 'CLIENT_RECONNECT',
|
||||||
headRev: pad.getHeadRevisionNumber(),
|
headRev: pad.getHeadRevisionNumber(),
|
||||||
|
@ -971,8 +926,8 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
||||||
let apool;
|
let apool;
|
||||||
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
|
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
|
||||||
try {
|
try {
|
||||||
atext = cloneAText(pad.atext);
|
atext = Changeset.cloneAText(pad.atext);
|
||||||
const attribsForWire = prepareForWire(atext.attribs, pad.pool);
|
const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||||
apool = attribsForWire.pool.toJsonable();
|
apool = attribsForWire.pool.toJsonable();
|
||||||
atext.attribs = attribsForWire.translated;
|
atext.attribs = attribsForWire.translated;
|
||||||
} catch (e:any) {
|
} catch (e:any) {
|
||||||
|
@ -1187,13 +1142,13 @@ const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, g
|
||||||
getPadLines(pad, startNum - 1),
|
getPadLines(pad, startNum - 1),
|
||||||
// Get all needed composite Changesets.
|
// Get all needed composite Changesets.
|
||||||
...compositesChangesetNeeded.map(async (item) => {
|
...compositesChangesetNeeded.map(async (item) => {
|
||||||
const changeset = await exports.composePadChangesets(pad, item.start, item.end);
|
const changeset = await composePadChangesets(pad, item.start, item.end);
|
||||||
composedChangesets[`${item.start}/${item.end}`] = changeset;
|
composedChangesets[`${item.start}/${item.end}`] = changeset;
|
||||||
}),
|
}),
|
||||||
// Get all needed revision Dates.
|
// Get all needed revision Dates.
|
||||||
...revTimesNeeded.map(async (revNum) => {
|
...revTimesNeeded.map(async (revNum) => {
|
||||||
const revDate = await pad.getRevisionDate(revNum);
|
const revDate = await pad.getRevisionDate(revNum);
|
||||||
revisionDate[revNum] = revDate;
|
revisionDate[revNum] = Math.floor(revDate / 1000);
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -1208,13 +1163,13 @@ const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, g
|
||||||
if (compositeEnd > endNum || compositeEnd > headRevision + 1) break;
|
if (compositeEnd > endNum || compositeEnd > headRevision + 1) break;
|
||||||
|
|
||||||
const forwards = composedChangesets[`${compositeStart}/${compositeEnd}`];
|
const forwards = composedChangesets[`${compositeStart}/${compositeEnd}`];
|
||||||
const backwards = inverse(forwards, lines.textlines, lines.alines, pad.apool());
|
const backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
|
||||||
|
|
||||||
mutateAttributionLines(forwards, lines.alines, pad.apool());
|
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
|
||||||
mutateTextLines(forwards, lines.textlines);
|
Changeset.mutateTextLines(forwards, lines.textlines);
|
||||||
|
|
||||||
const forwards2 = moveOpsToNewPool(forwards, pad.apool(), apool);
|
const forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
|
||||||
const backwards2 = moveOpsToNewPool(backwards, pad.apool(), apool);
|
const backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
|
||||||
|
|
||||||
const t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1];
|
const t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1];
|
||||||
const t2 = revisionDate[compositeEnd - 1];
|
const t2 = revisionDate[compositeEnd - 1];
|
||||||
|
@ -1240,12 +1195,12 @@ const getPadLines = async (pad: PadType, revNum: number) => {
|
||||||
if (revNum >= 0) {
|
if (revNum >= 0) {
|
||||||
atext = await pad.getInternalRevisionAText(revNum);
|
atext = await pad.getInternalRevisionAText(revNum);
|
||||||
} else {
|
} else {
|
||||||
atext = makeAText('\n');
|
atext = Changeset.makeAText('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
textlines: splitTextLines(atext.text),
|
textlines: Changeset.splitTextLines(atext.text),
|
||||||
alines: splitAttributionLines(atext.attribs, atext.text),
|
alines: Changeset.splitAttributionLines(atext.attribs, atext.text),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1253,7 +1208,7 @@ const getPadLines = async (pad: PadType, revNum: number) => {
|
||||||
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
||||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
||||||
*/
|
*/
|
||||||
exports.composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
|
const composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
|
||||||
// fetch all changesets we need
|
// fetch all changesets we need
|
||||||
const headNum = pad.getHeadRevisionNumber();
|
const headNum = pad.getHeadRevisionNumber();
|
||||||
endNum = Math.min(endNum, headNum + 1);
|
endNum = Math.min(endNum, headNum + 1);
|
||||||
|
@ -1280,7 +1235,7 @@ exports.composePadChangesets = async (pad: PadType, startNum: number, endNum: nu
|
||||||
|
|
||||||
for (r = startNum + 1; r < endNum; r++) {
|
for (r = startNum + 1; r < endNum; r++) {
|
||||||
const cs = changesets[r];
|
const cs = changesets[r];
|
||||||
changeset = compose(changeset as string, cs as string, pool);
|
changeset = Changeset.compose(changeset, cs, pool);
|
||||||
}
|
}
|
||||||
return changeset;
|
return changeset;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,12 +5,11 @@ import {ErrorCaused} from "../../types/ErrorCaused";
|
||||||
import {QueryType} from "../../types/QueryType";
|
import {QueryType} from "../../types/QueryType";
|
||||||
|
|
||||||
import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer";
|
import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer";
|
||||||
import {PackageData, PackageInfo} from "../../types/PackageInfo";
|
import {PackageData} from "../../types/PackageInfo";
|
||||||
import semver from 'semver';
|
|
||||||
import log4js from 'log4js';
|
|
||||||
import {MapArrayType} from "../../types/MapType";
|
|
||||||
|
|
||||||
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
|
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
|
||||||
|
import semver from 'semver';
|
||||||
|
import log4js from 'log4js';
|
||||||
const logger = log4js.getLogger('adminPlugins');
|
const logger = log4js.getLogger('adminPlugins');
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,34 +20,10 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||||
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
|
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
|
||||||
if (!isAdmin) return;
|
if (!isAdmin) return;
|
||||||
|
|
||||||
const checkPluginForUpdates = async () => {
|
socket.on('getInstalled', (query:string) => {
|
||||||
let results: MapArrayType<PackageInfo>
|
|
||||||
try {
|
|
||||||
results = await getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking for plugin updates:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.keys(pluginDefs.plugins).filter((plugin) => {
|
|
||||||
if (!results[plugin]) return false;
|
|
||||||
|
|
||||||
const latestVersion = results[plugin].version;
|
|
||||||
const currentVersion = pluginDefs.plugins[plugin].package.version;
|
|
||||||
|
|
||||||
return semver.gt(latestVersion, currentVersion);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('getInstalled', async (query: string) => {
|
|
||||||
// send currently installed plugins
|
// send currently installed plugins
|
||||||
const installed =
|
const installed =
|
||||||
Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
|
Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
|
||||||
|
|
||||||
const updatable = await checkPluginForUpdates();
|
|
||||||
|
|
||||||
installed.forEach((plugin) => {
|
|
||||||
plugin.updatable = updatable.includes(plugin.name);
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.emit('results:installed', {installed});
|
socket.emit('results:installed', {installed});
|
||||||
});
|
});
|
||||||
|
@ -56,7 +31,16 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||||
socket.on('checkUpdates', async () => {
|
socket.on('checkUpdates', async () => {
|
||||||
// Check plugins for updates
|
// Check plugins for updates
|
||||||
try {
|
try {
|
||||||
const updatable = checkPluginForUpdates();
|
const results = await getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
|
||||||
|
|
||||||
|
const updatable = Object.keys(pluginDefs.plugins).filter((plugin) => {
|
||||||
|
if (!results[plugin]) return false;
|
||||||
|
|
||||||
|
const latestVersion = results[plugin].version;
|
||||||
|
const currentVersion = pluginDefs.plugins[plugin].package.version;
|
||||||
|
|
||||||
|
return semver.gt(latestVersion, currentVersion);
|
||||||
|
});
|
||||||
|
|
||||||
socket.emit('results:updatable', {updatable});
|
socket.emit('results:updatable', {updatable});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -13,7 +13,6 @@ const settings = require('../../utils/Settings');
|
||||||
const UpdateCheck = require('../../utils/UpdateCheck');
|
const UpdateCheck = require('../../utils/UpdateCheck');
|
||||||
const padManager = require('../../db/PadManager');
|
const padManager = require('../../db/PadManager');
|
||||||
const api = require('../../db/API');
|
const api = require('../../db/API');
|
||||||
const cleanup = require('../../utils/Cleanup');
|
|
||||||
|
|
||||||
|
|
||||||
const queryPadLimit = 12;
|
const queryPadLimit = 12;
|
||||||
|
@ -253,40 +252,6 @@ exports.socketio = (hookName: string, {io}: any) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('cleanupPadRevisions', async (padId: string) => {
|
|
||||||
if (!settings.cleanup.enabled) {
|
|
||||||
socket.emit('results:cleanupPadRevisions', {
|
|
||||||
error: 'Cleanup disabled. Enable cleanup in settings.json: cleanup.enabled => true',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const padExists = await padManager.doesPadExists(padId);
|
|
||||||
if (padExists) {
|
|
||||||
logger.info(`Cleanup pad revisions: ${padId}`);
|
|
||||||
try {
|
|
||||||
const result = await cleanup.deleteRevisions(padId, settings.cleanup.keepRevisions)
|
|
||||||
if (result) {
|
|
||||||
socket.emit('results:cleanupPadRevisions', {
|
|
||||||
padId: padId,
|
|
||||||
keepRevisions: settings.cleanup.keepRevisions,
|
|
||||||
});
|
|
||||||
logger.info('successful cleaned up pad: ', padId)
|
|
||||||
} else {
|
|
||||||
socket.emit('results:cleanupPadRevisions', {
|
|
||||||
error: 'Error cleaning up pad',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
logger.error(`Error in pad ${padId}: ${err.stack || err}`);
|
|
||||||
socket.emit('results:cleanupPadRevisions', {
|
|
||||||
error: err.toString(),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('restartServer', async () => {
|
socket.on('restartServer', async () => {
|
||||||
logger.info('Admin request to restart server through a socket on /admin/settings');
|
logger.info('Admin request to restart server through a socket on /admin/settings');
|
||||||
settings.reloadSettings();
|
settings.reloadSettings();
|
||||||
|
|
|
@ -12,7 +12,6 @@ const webaccess = require('./webaccess');
|
||||||
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
||||||
|
|
||||||
import {build, buildSync} from 'esbuild'
|
import {build, buildSync} from 'esbuild'
|
||||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
|
||||||
let ioI: { sockets: { sockets: any[]; }; } | null = null
|
let ioI: { sockets: { sockets: any[]; }; } | null = null
|
||||||
|
|
||||||
exports.socketio = (hookName: string, {io}: any) => {
|
exports.socketio = (hookName: string, {io}: any) => {
|
||||||
|
@ -20,7 +19,7 @@ exports.socketio = (hookName: string, {io}: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
exports.expressPreSession = async (hookName:string, {app}:ArgsExpressType) => {
|
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||||
// This endpoint is intended to conform to:
|
// This endpoint is intended to conform to:
|
||||||
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
|
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
|
||||||
app.get('/health', (req:any, res:any) => {
|
app.get('/health', (req:any, res:any) => {
|
||||||
|
@ -41,7 +40,7 @@ exports.expressPreSession = async (hookName:string, {app}:ArgsExpressType) => {
|
||||||
|
|
||||||
app.get('/robots.txt', (req:any, res:any) => {
|
app.get('/robots.txt', (req:any, res:any) => {
|
||||||
let filePath =
|
let filePath =
|
||||||
path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
|
path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
|
||||||
res.sendFile(filePath, (err:any) => {
|
res.sendFile(filePath, (err:any) => {
|
||||||
// there is no custom robots.txt, send the default robots.txt which dissallows all
|
// there is no custom robots.txt, send the default robots.txt which dissallows all
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -112,9 +111,9 @@ const convertTypescript = (content: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLiveReload = async (args: ArgsExpressType, padString: string, timeSliderString: string, indexString: any) => {
|
const handleLiveReload = async (args: any, padString: string, timeSliderString: string, indexString: any) => {
|
||||||
const chokidar = await import('chokidar')
|
const chokidar = await import('chokidar')
|
||||||
const watcher = chokidar.watch(path.join(settings.root, 'src', 'static', 'js'), {});
|
const watcher = chokidar.watch(path.join(settings.root, 'src', 'static', 'js'));
|
||||||
let routeHandlers: { [key: string]: Function } = {};
|
let routeHandlers: { [key: string]: Function } = {};
|
||||||
|
|
||||||
const setRouteHandler = (path: string, newHandler: Function) => {
|
const setRouteHandler = (path: string, newHandler: Function) => {
|
||||||
|
@ -244,37 +243,37 @@ const convertTypescriptWatched = (content: string, cb: (output:string, hash: str
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.expressCreateServer = async (_hookName: string, args: ArgsExpressType, cb: Function) => {
|
exports.expressCreateServer = async (hookName: string, args: any, cb: Function) => {
|
||||||
const padString = eejs.require('ep_etherpad-lite/templates/padBootstrap.js', {
|
const padString = eejs.require('ep_etherpad-lite/templates/padBootstrap.js', {
|
||||||
pluginModules: (() => {
|
pluginModules: (() => {
|
||||||
const pluginModules = new Set();
|
const pluginModules = new Set();
|
||||||
for (const part of plugins.parts) {
|
for (const part of plugins.parts) {
|
||||||
for (const [, hookFnName] of Object.entries(part.client_hooks || {})) {
|
for (const [, hookFnName] of Object.entries(part.client_hooks || {})) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
pluginModules.add(hookFnName.split(':')[0]);
|
pluginModules.add(hookFnName.split(':')[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return [...pluginModules];
|
||||||
return [...pluginModules];
|
})(),
|
||||||
})(),
|
settings,
|
||||||
settings,
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const indexString = eejs.require('ep_etherpad-lite/templates/indexBootstrap.js', {
|
const indexString = eejs.require('ep_etherpad-lite/templates/indexBootstrap.js', {
|
||||||
})
|
})
|
||||||
|
|
||||||
const timeSliderString = eejs.require('ep_etherpad-lite/templates/timeSliderBootstrap.js', {
|
const timeSliderString = eejs.require('ep_etherpad-lite/templates/timeSliderBootstrap.js', {
|
||||||
pluginModules: (() => {
|
pluginModules: (() => {
|
||||||
const pluginModules = new Set();
|
const pluginModules = new Set();
|
||||||
for (const part of plugins.parts) {
|
for (const part of plugins.parts) {
|
||||||
for (const [, hookFnName] of Object.entries(part.client_hooks || {})) {
|
for (const [, hookFnName] of Object.entries(part.client_hooks || {})) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
pluginModules.add(hookFnName.split(':')[0]);
|
pluginModules.add(hookFnName.split(':')[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return [...pluginModules];
|
||||||
return [...pluginModules];
|
})(),
|
||||||
})(),
|
settings,
|
||||||
settings,
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -295,25 +294,37 @@ exports.expressCreateServer = async (_hookName: string, args: ArgsExpressType, c
|
||||||
fileNamePad = `padbootstrap-${padSliderWrite.hash}.min.js`
|
fileNamePad = `padbootstrap-${padSliderWrite.hash}.min.js`
|
||||||
fileNameTimeSlider = `timeSliderBootstrap-${timeSliderWrite.hash}.min.js`
|
fileNameTimeSlider = `timeSliderBootstrap-${timeSliderWrite.hash}.min.js`
|
||||||
fileNameIndex = `indexBootstrap-${indexWrite.hash}.min.js`
|
fileNameIndex = `indexBootstrap-${indexWrite.hash}.min.js`
|
||||||
|
const pathNamePad = path.join(outdir, fileNamePad)
|
||||||
|
const pathNameTimeSlider = path.join(outdir, fileNameTimeSlider)
|
||||||
|
const pathNameIndex = path.join(outdir, 'index.js')
|
||||||
|
|
||||||
args.app.get("/"+fileNamePad, (_req, res) => {
|
if (!fs.existsSync(pathNamePad)) {
|
||||||
res.header('Content-Type', 'application/javascript');
|
fs.writeFileSync(pathNamePad, padSliderWrite.output);
|
||||||
res.send(padSliderWrite.output)
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(pathNameIndex)) {
|
||||||
|
fs.writeFileSync(pathNameIndex, indexWrite.output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(pathNameTimeSlider)) {
|
||||||
|
fs.writeFileSync(pathNameTimeSlider,timeSliderWrite.output)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.app.get("/"+fileNamePad, (req: any, res: any) => {
|
||||||
|
res.sendFile(pathNamePad)
|
||||||
})
|
})
|
||||||
|
|
||||||
args.app.get("/"+fileNameIndex, (_req, res) => {
|
args.app.get("/"+fileNameIndex, (req: any, res: any) => {
|
||||||
res.header('Content-Type', 'application/javascript');
|
res.sendFile(pathNameIndex)
|
||||||
res.send(indexWrite.output)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
args.app.get("/"+fileNameTimeSlider, (_req, res) => {
|
args.app.get("/"+fileNameTimeSlider, (req: any, res: any) => {
|
||||||
res.header('Content-Type', 'application/javascript');
|
res.sendFile(pathNameTimeSlider)
|
||||||
res.send(timeSliderWrite.output)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// serve index.html under /
|
// serve index.html under /
|
||||||
args.app.get('/', (req: any, res: any) => {
|
args.app.get('/', (req: any, res: any) => {
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req, settings, entrypoint: "./"+fileNameIndex}));
|
res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req, settings, entrypoint: "/"+fileNameIndex}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -331,7 +342,7 @@ exports.expressCreateServer = async (_hookName: string, args: ArgsExpressType, c
|
||||||
req,
|
req,
|
||||||
toolbar,
|
toolbar,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
entrypoint: "../"+fileNamePad
|
entrypoint: "/"+fileNamePad
|
||||||
})
|
})
|
||||||
res.send(content);
|
res.send(content);
|
||||||
});
|
});
|
||||||
|
@ -345,7 +356,7 @@ exports.expressCreateServer = async (_hookName: string, args: ArgsExpressType, c
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
|
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
|
||||||
req,
|
req,
|
||||||
toolbar,
|
toolbar,
|
||||||
entrypoint: "../../"+fileNameTimeSlider
|
entrypoint: "/"+fileNameTimeSlider
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,10 +4,11 @@ import {MapArrayType} from "../../types/MapType";
|
||||||
import {PartType} from "../../types/PartType";
|
import {PartType} from "../../types/PartType";
|
||||||
|
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
import {minify} from '../../utils/Minify';
|
const minify = require('../../utils/Minify');
|
||||||
import path from 'node:path';
|
const path = require('path');
|
||||||
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
||||||
const settings = require('../../utils/Settings');
|
const settings = require('../../utils/Settings');
|
||||||
|
import CachingMiddleware from '../../utils/caching_middleware';
|
||||||
|
|
||||||
// Rewrite tar to include modules with no extensions and proper rooted paths.
|
// Rewrite tar to include modules with no extensions and proper rooted paths.
|
||||||
const getTar = async () => {
|
const getTar = async () => {
|
||||||
|
@ -31,10 +32,15 @@ const getTar = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||||
|
// Cache both minified and static.
|
||||||
|
const assetCache = new CachingMiddleware();
|
||||||
|
// Cache static assets
|
||||||
|
app.all(/\/js\/(.*)/, assetCache.handle.bind(assetCache));
|
||||||
|
app.all(/\/css\/(.*)/, assetCache.handle.bind(assetCache));
|
||||||
|
|
||||||
// Minify will serve static files compressed (minify enabled). It also has
|
// Minify will serve static files compressed (minify enabled). It also has
|
||||||
// file-specific hacks for ace/require-kernel/etc.
|
// file-specific hacks for ace/require-kernel/etc.
|
||||||
app.all('/static/:filename(*)', minify);
|
app.all('/static/:filename(*)', minify.minify);
|
||||||
|
|
||||||
// serve plugin definitions
|
// serve plugin definitions
|
||||||
// not very static, but served here so that client can do
|
// not very static, but served here so that client can do
|
||||||
|
|
|
@ -49,21 +49,8 @@ exports.userCanModify = (padId: string, req: SocketClientRequest) => {
|
||||||
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
|
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
|
||||||
exports.authnFailureDelayMs = 1000;
|
exports.authnFailureDelayMs = 1000;
|
||||||
|
|
||||||
const staticResources = [
|
|
||||||
/^\/padbootstrap-[a-zA-Z0-9]+\.min\.js$/,
|
|
||||||
/^\/timeSliderBootstrap-[a-zA-Z0-9]+\.min\.js$/,
|
|
||||||
/^\/manifest.json$/
|
|
||||||
]
|
|
||||||
|
|
||||||
const checkAccess = async (req:any, res:any, next: Function) => {
|
const checkAccess = async (req:any, res:any, next: Function) => {
|
||||||
const requireAdmin = req.path.toLowerCase().startsWith('/admin-auth');
|
const requireAdmin = req.path.toLowerCase().startsWith('/admin-auth');
|
||||||
for (const staticResource of staticResources) {
|
|
||||||
if (req.path.match(staticResource)) {
|
|
||||||
console.log(`Loading [${staticResource}] ${req.path}`);
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Step 1: Check the preAuthorize hook for early permit/deny (permit is only allowed for non-admin
|
// Step 1: Check the preAuthorize hook for early permit/deny (permit is only allowed for non-admin
|
||||||
|
@ -190,10 +177,6 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
||||||
res.status(401).send('Authentication Required');
|
res.status(401).send('Authentication Required');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ctx.username === '__proto__' || ctx.username === 'constructor' || ctx.username === 'prototype') {
|
|
||||||
res.end(403);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settings.users[ctx.username].username = ctx.username;
|
settings.users[ctx.username].username = ctx.username;
|
||||||
// Make a shallow copy so that the password property can be deleted (to prevent it from
|
// Make a shallow copy so that the password property can be deleted (to prevent it from
|
||||||
// appearing in logs or in the database) without breaking future authentication attempts.
|
// appearing in logs or in the database) without breaking future authentication attempts.
|
||||||
|
|
|
@ -7,8 +7,8 @@ const languages = require('languages4translatewiki');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
const pluginDefs = require('../../static/js/pluginfw/plugin_defs');
|
const pluginDefs = require('../../static/js/pluginfw/plugin_defs.js');
|
||||||
import existsSync from '../utils/path_exists';
|
const existsSync = require('../utils/path_exists');
|
||||||
const settings = require('../utils/Settings');
|
const settings = require('../utils/Settings');
|
||||||
|
|
||||||
// returns all existing messages merged together and grouped by langcode
|
// returns all existing messages merged together and grouped by langcode
|
||||||
|
|
|
@ -30,7 +30,7 @@ const configuration: Configuration = {
|
||||||
if(account === undefined) {
|
if(account === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
if (account.is_admin ) {
|
if (account.is_admin) {
|
||||||
return {
|
return {
|
||||||
accountId: id,
|
accountId: id,
|
||||||
claims: () => ({
|
claims: () => ({
|
||||||
|
@ -153,7 +153,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
args.app.post('/interaction/:uid', async (req, res, next) => {
|
args.app.post('/interaction/:uid', async (req: Http2ServerRequest, res: Http2ServerResponse, next:Function) => {
|
||||||
const formid = new IncomingForm();
|
const formid = new IncomingForm();
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -226,7 +226,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
args.app.get('/interaction/:uid', async (req, res, next) => {
|
args.app.get('/interaction/:uid', async (req: Request, res: Response, next: Function) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
uid, prompt, params, session,
|
uid, prompt, params, session,
|
||||||
|
|
|
@ -42,10 +42,6 @@ if (settings.dumpOnUncleanExit) {
|
||||||
const addProxyToAxios = (url: URL) => {
|
const addProxyToAxios = (url: URL) => {
|
||||||
axios.defaults.proxy = {
|
axios.defaults.proxy = {
|
||||||
host: url.hostname,
|
host: url.hostname,
|
||||||
auth: {
|
|
||||||
username: url.username,
|
|
||||||
password: url.password,
|
|
||||||
},
|
|
||||||
port: Number(url.port),
|
port: Number(url.port),
|
||||||
protocol: url.protocol,
|
protocol: url.protocol,
|
||||||
}
|
}
|
||||||
|
@ -78,7 +74,7 @@ const express = require('./hooks/express');
|
||||||
const hooks = require('../static/js/pluginfw/hooks');
|
const hooks = require('../static/js/pluginfw/hooks');
|
||||||
const pluginDefs = require('../static/js/pluginfw/plugin_defs');
|
const pluginDefs = require('../static/js/pluginfw/plugin_defs');
|
||||||
const plugins = require('../static/js/pluginfw/plugins');
|
const plugins = require('../static/js/pluginfw/plugins');
|
||||||
import {Gate} from './utils/promises';
|
const {Gate} = require('./utils/promises');
|
||||||
const stats = require('./stats')
|
const stats = require('./stats')
|
||||||
|
|
||||||
const logger = log4js.getLogger('server');
|
const logger = log4js.getLogger('server');
|
||||||
|
@ -104,7 +100,7 @@ const removeSignalListener = (signal: NodeJS.Signals, listener: NodeJS.SignalsLi
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let startDoneGate: Gate<unknown>
|
let startDoneGate: { resolve: () => void; }
|
||||||
exports.start = async () => {
|
exports.start = async () => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case State.INITIAL:
|
case State.INITIAL:
|
||||||
|
@ -185,14 +181,12 @@ exports.start = async () => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Error occurred while starting Etherpad');
|
logger.error('Error occurred while starting Etherpad');
|
||||||
state = State.STATE_TRANSITION_FAILED;
|
state = State.STATE_TRANSITION_FAILED;
|
||||||
// @ts-ignore
|
|
||||||
startDoneGate.resolve();
|
startDoneGate.resolve();
|
||||||
return await exports.exit(err);
|
return await exports.exit(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Etherpad is running');
|
logger.info('Etherpad is running');
|
||||||
state = State.RUNNING;
|
state = State.RUNNING;
|
||||||
// @ts-ignore
|
|
||||||
startDoneGate.resolve();
|
startDoneGate.resolve();
|
||||||
|
|
||||||
// Return the HTTP server to make it easier to write tests.
|
// Return the HTTP server to make it easier to write tests.
|
||||||
|
@ -234,13 +228,11 @@ exports.stop = async () => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Error occurred while stopping Etherpad');
|
logger.error('Error occurred while stopping Etherpad');
|
||||||
state = State.STATE_TRANSITION_FAILED;
|
state = State.STATE_TRANSITION_FAILED;
|
||||||
// @ts-ignore
|
|
||||||
stopDoneGate.resolve();
|
stopDoneGate.resolve();
|
||||||
return await exports.exit(err);
|
return await exports.exit(err);
|
||||||
}
|
}
|
||||||
logger.info('Etherpad stopped');
|
logger.info('Etherpad stopped');
|
||||||
state = State.STOPPED;
|
state = State.STOPPED;
|
||||||
// @ts-ignore
|
|
||||||
stopDoneGate.resolve();
|
stopDoneGate.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import {Express} from "express";
|
|
||||||
|
|
||||||
export type ArgsExpressType = {
|
export type ArgsExpressType = {
|
||||||
app:Express,
|
app:any,
|
||||||
io: any,
|
io: any,
|
||||||
server:any
|
server:any
|
||||||
}
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
import {MapArrayType} from "./MapType";
|
import {MapArrayType} from "./MapType";
|
||||||
import AttributePool from "../../static/js/AttributePool";
|
|
||||||
|
|
||||||
export type PadType = {
|
export type PadType = {
|
||||||
id: string,
|
id: string,
|
||||||
apool: ()=>AttributePool,
|
apool: ()=>APool,
|
||||||
atext: AText,
|
atext: AText,
|
||||||
pool: AttributePool,
|
pool: APool,
|
||||||
getInternalRevisionAText: (text:number|string)=>Promise<AText>,
|
getInternalRevisionAText: (text:number|string)=>Promise<AText>,
|
||||||
getValidRevisionRange: (fromRev: string, toRev: string)=>PadRange,
|
getValidRevisionRange: (fromRev: string, toRev: string)=>PadRange,
|
||||||
getRevisionAuthor: (rev: number)=>Promise<string>,
|
getRevisionAuthor: (rev: number)=>Promise<string>,
|
||||||
|
@ -36,7 +35,6 @@ export type APool = {
|
||||||
clone: ()=>APool,
|
clone: ()=>APool,
|
||||||
check: ()=>Promise<void>,
|
check: ()=>Promise<void>,
|
||||||
eachAttrib: (callback: (key: string, value: any)=>void)=>void,
|
eachAttrib: (callback: (key: string, value: any)=>void)=>void,
|
||||||
getAttrib: (key: number)=>any,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import {AChangeSet} from "./PadType";
|
|
||||||
|
|
||||||
export type Revision = {
|
|
||||||
changeset: AChangeSet,
|
|
||||||
meta: {
|
|
||||||
author: string,
|
|
||||||
timestamp: number,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
import {AChangeSet} from "../types/PadType";
|
|
||||||
import {Revision} from "../types/Revision";
|
|
||||||
|
|
||||||
const promises = require('./promises');
|
|
||||||
const padManager = require('ep_etherpad-lite/node/db/PadManager');
|
|
||||||
const db = require('ep_etherpad-lite/node/db/DB');
|
|
||||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
|
||||||
const padMessageHandler = require('ep_etherpad-lite/node/handler/PadMessageHandler');
|
|
||||||
const log4js = require('log4js');
|
|
||||||
const logger = log4js.getLogger('cleanup');
|
|
||||||
|
|
||||||
exports.deleteAllRevisions = async (padID: string): Promise<void> => {
|
|
||||||
|
|
||||||
const randomPadId = padID + 'aertdfdf' + Math.random().toString(10)
|
|
||||||
|
|
||||||
let pad = await padManager.getPad(padID);
|
|
||||||
await pad.copyPadWithoutHistory(randomPadId, false);
|
|
||||||
pad = await padManager.getPad(randomPadId);
|
|
||||||
await pad.copyPadWithoutHistory(padID, true);
|
|
||||||
await pad.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const createRevision = async (aChangeset: AChangeSet, timestamp: number, isKeyRev: boolean, authorId: string, atext: any, pool: any) => {
|
|
||||||
|
|
||||||
if (authorId !== '') pool.putAttrib(['author', authorId]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
changeset: aChangeset,
|
|
||||||
meta: {
|
|
||||||
author: authorId,
|
|
||||||
timestamp: timestamp,
|
|
||||||
...isKeyRev ? {
|
|
||||||
pool: pool,
|
|
||||||
atext: atext,
|
|
||||||
} : {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.deleteRevisions = async (padId: string, keepRevisions: number): Promise<boolean> => {
|
|
||||||
|
|
||||||
logger.debug('Start cleanup revisions', padId)
|
|
||||||
|
|
||||||
let pad = await padManager.getPad(padId);
|
|
||||||
await pad.check()
|
|
||||||
|
|
||||||
logger.debug('Initial pad is valid')
|
|
||||||
|
|
||||||
if (pad.head <= keepRevisions) {
|
|
||||||
logger.debug('Pad has not enough revisions')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
padMessageHandler.kickSessionsFromPad(padId)
|
|
||||||
|
|
||||||
const cleanupUntilRevision = pad.head - keepRevisions
|
|
||||||
logger.debug('Composing changesets: ', cleanupUntilRevision)
|
|
||||||
const changeset = await padMessageHandler.composePadChangesets(pad, 0, cleanupUntilRevision + 1)
|
|
||||||
|
|
||||||
const revisions: Revision[] = [];
|
|
||||||
|
|
||||||
await promises.timesLimit(keepRevisions + 1, 500, async (i: number) => {
|
|
||||||
const rev = i + cleanupUntilRevision
|
|
||||||
revisions[rev] = await pad.getRevision(rev)
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.debug('Loaded revisions: ', revisions.length)
|
|
||||||
|
|
||||||
await promises.timesLimit(pad.head + 1, 500, async (i: string) => {
|
|
||||||
await db.remove(`pad:${padId}:revs:${i}`, null);
|
|
||||||
});
|
|
||||||
|
|
||||||
let padContent = await db.get(`pad:${padId}`)
|
|
||||||
padContent.head = keepRevisions
|
|
||||||
if (padContent.savedRevisions) {
|
|
||||||
let newSavedRevisions = []
|
|
||||||
|
|
||||||
for (let i = 0; i < padContent.savedRevisions.length; i++) {
|
|
||||||
if (padContent.savedRevisions[i].revNum > cleanupUntilRevision) {
|
|
||||||
padContent.savedRevisions[i].revNum = padContent.savedRevisions[i].revNum - cleanupUntilRevision
|
|
||||||
newSavedRevisions.push(padContent.savedRevisions[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
padContent.savedRevisions = newSavedRevisions
|
|
||||||
}
|
|
||||||
await db.set(`pad:${padId}`, padContent);
|
|
||||||
|
|
||||||
let newAText = Changeset.makeAText('\n');
|
|
||||||
let pool = pad.apool()
|
|
||||||
|
|
||||||
newAText = Changeset.applyToAText(changeset, newAText, pool);
|
|
||||||
|
|
||||||
const revision = await createRevision(
|
|
||||||
changeset,
|
|
||||||
revisions[cleanupUntilRevision].meta.timestamp,
|
|
||||||
0 === pad.getKeyRevisionNumber(0),
|
|
||||||
'',
|
|
||||||
newAText,
|
|
||||||
pool
|
|
||||||
);
|
|
||||||
|
|
||||||
const p: Promise<void>[] = [];
|
|
||||||
|
|
||||||
p.push(db.set(`pad:${padId}:revs:0`, revision))
|
|
||||||
|
|
||||||
p.push(promises.timesLimit(keepRevisions, 500, async (i: number) => {
|
|
||||||
const rev = i + cleanupUntilRevision + 1
|
|
||||||
const newRev = rev - cleanupUntilRevision;
|
|
||||||
|
|
||||||
newAText = Changeset.applyToAText(revisions[rev].changeset, newAText, pool);
|
|
||||||
|
|
||||||
const revision = await createRevision(
|
|
||||||
revisions[rev].changeset,
|
|
||||||
revisions[rev].meta.timestamp,
|
|
||||||
newRev === pad.getKeyRevisionNumber(newRev),
|
|
||||||
revisions[rev].meta.author,
|
|
||||||
newAText,
|
|
||||||
pool
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.set(`pad:${padId}:revs:${newRev}`, revision);
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Promise.all(p)
|
|
||||||
|
|
||||||
logger.debug('Finished migration. Checking pad now')
|
|
||||||
|
|
||||||
padManager.unloadPad(padId);
|
|
||||||
|
|
||||||
let newPad = await padManager.getPad(padId);
|
|
||||||
await newPad.check();
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.checkTodos = async () => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
// TODO: Move to settings
|
|
||||||
const settings = {
|
|
||||||
minHead: 100,
|
|
||||||
keepRevisions: 100,
|
|
||||||
minAge: 1,//1000 * 60 * 60 * 24,
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId: string) => {
|
|
||||||
// TODO: Handle concurrency
|
|
||||||
const pad = await padManager.getPad(padId);
|
|
||||||
|
|
||||||
const revisionDate = await pad.getRevisionDate(pad.getHeadRevisionNumber())
|
|
||||||
|
|
||||||
if (pad.head < settings.minHead || padMessageHandler.padUsersCount(padId) > 0 || Date.now() < revisionDate + settings.minAge) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await exports.deleteRevisions(padId, settings.keepRevisions)
|
|
||||||
if (result) {
|
|
||||||
logger.info('successful cleaned up pad: ', padId)
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
logger.error(`Error in pad ${padId}: ${err.stack || err}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
|
@ -19,9 +19,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import AttributeMap from '../../static/js/AttributeMap';
|
const AttributeMap = require('../../static/js/AttributeMap');
|
||||||
import AttributePool from "../../static/js/AttributePool";
|
const Changeset = require('../../static/js/Changeset');
|
||||||
import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
|
|
||||||
const { checkValidRev } = require('./checkValidRev');
|
const { checkValidRev } = require('./checkValidRev');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -31,7 +30,7 @@ exports.getPadPlainText = (pad: { getInternalRevisionAText: (arg0: any) => any;
|
||||||
const _analyzeLine = exports._analyzeLine;
|
const _analyzeLine = exports._analyzeLine;
|
||||||
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(checkValidRev(revNum)) : pad.atext);
|
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(checkValidRev(revNum)) : pad.atext);
|
||||||
const textLines = atext.text.slice(0, -1).split('\n');
|
const textLines = atext.text.slice(0, -1).split('\n');
|
||||||
const attribLines = splitAttributionLines(atext.attribs, atext.text);
|
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||||
const apool = pad.pool;
|
const apool = pad.pool;
|
||||||
|
|
||||||
const pieces = [];
|
const pieces = [];
|
||||||
|
@ -52,14 +51,14 @@ type LineModel = {
|
||||||
[id:string]:string|number|LineModel
|
[id:string]:string|number|LineModel
|
||||||
}
|
}
|
||||||
|
|
||||||
exports._analyzeLine = (text:string, aline: string, apool: AttributePool) => {
|
exports._analyzeLine = (text:string, aline: LineModel, apool: Function) => {
|
||||||
const line: LineModel = {};
|
const line: LineModel = {};
|
||||||
|
|
||||||
// identify list
|
// identify list
|
||||||
let lineMarker = 0;
|
let lineMarker = 0;
|
||||||
line.listLevel = 0;
|
line.listLevel = 0;
|
||||||
if (aline) {
|
if (aline) {
|
||||||
const [op] = deserializeOps(aline);
|
const [op] = Changeset.deserializeOps(aline);
|
||||||
if (op != null) {
|
if (op != null) {
|
||||||
const attribs = AttributeMap.fromString(op.attribs, apool);
|
const attribs = AttributeMap.fromString(op.attribs, apool);
|
||||||
let listType = attribs.get('list');
|
let listType = attribs.get('list');
|
||||||
|
@ -79,7 +78,7 @@ exports._analyzeLine = (text:string, aline: string, apool: AttributePool) => {
|
||||||
}
|
}
|
||||||
if (lineMarker) {
|
if (lineMarker) {
|
||||||
line.text = text.substring(1);
|
line.text = text.substring(1);
|
||||||
line.aline = subattribution(aline, 1);
|
line.aline = Changeset.subattribution(aline, 1);
|
||||||
} else {
|
} else {
|
||||||
line.text = text;
|
line.text = text;
|
||||||
line.aline = aline;
|
line.aline = aline;
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {MapArrayType} from "../types/MapType";
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
|
const Changeset = require('../../static/js/Changeset');
|
||||||
const attributes = require('../../static/js/attributes');
|
const attributes = require('../../static/js/attributes');
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
|
@ -27,9 +27,7 @@ const hooks = require('../../static/js/pluginfw/hooks');
|
||||||
const eejs = require('../eejs');
|
const eejs = require('../eejs');
|
||||||
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||||
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
|
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
|
||||||
import padutils from "../../static/js/pad_utils";
|
const padutils = require('../../static/js/pad_utils').padutils;
|
||||||
import {StringIterator} from "../../static/js/StringIterator";
|
|
||||||
import {StringAssembler} from "../../static/js/StringAssembler";
|
|
||||||
|
|
||||||
const getPadHTML = async (pad: PadType, revNum: string) => {
|
const getPadHTML = async (pad: PadType, revNum: string) => {
|
||||||
let atext = pad.atext;
|
let atext = pad.atext;
|
||||||
|
@ -46,7 +44,7 @@ const getPadHTML = async (pad: PadType, revNum: string) => {
|
||||||
const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string[]) => {
|
const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string[]) => {
|
||||||
const apool = pad.apool();
|
const apool = pad.apool();
|
||||||
const textLines = atext.text.slice(0, -1).split('\n');
|
const textLines = atext.text.slice(0, -1).split('\n');
|
||||||
const attribLines = splitAttributionLines(atext.attribs, atext.text);
|
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||||
|
|
||||||
const tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
|
const tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
|
||||||
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||||
|
@ -82,7 +80,6 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
||||||
css += '<style>\n';
|
css += '<style>\n';
|
||||||
|
|
||||||
for (const a of Object.keys(apool.numToAttrib)) {
|
for (const a of Object.keys(apool.numToAttrib)) {
|
||||||
// @ts-ignore
|
|
||||||
const attr = apool.numToAttrib[a];
|
const attr = apool.numToAttrib[a];
|
||||||
|
|
||||||
// skip non author attributes
|
// skip non author attributes
|
||||||
|
@ -118,7 +115,6 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
||||||
// see hook exportHtmlAdditionalTagsWithData
|
// see hook exportHtmlAdditionalTagsWithData
|
||||||
attrib = propName;
|
attrib = propName;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
|
||||||
const propTrueNum = apool.putAttrib(attrib, true);
|
const propTrueNum = apool.putAttrib(attrib, true);
|
||||||
if (propTrueNum >= 0) {
|
if (propTrueNum >= 0) {
|
||||||
anumMap[propTrueNum] = i;
|
anumMap[propTrueNum] = i;
|
||||||
|
@ -131,8 +127,8 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
||||||
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
||||||
// becomes
|
// becomes
|
||||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||||
const taker = new StringIterator(text);
|
const taker = Changeset.stringIterator(text);
|
||||||
const assem = new StringAssembler();
|
const assem = Changeset.stringAssembler();
|
||||||
const openTags:string[] = [];
|
const openTags:string[] = [];
|
||||||
|
|
||||||
const getSpanClassFor = (i: string) => {
|
const getSpanClassFor = (i: string) => {
|
||||||
|
@ -208,8 +204,7 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
const ops = Changeset.deserializeOps(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||||
const ops = deserializeOps(subattribution(attribs, idx, idx + numChars));
|
|
||||||
idx += numChars;
|
idx += numChars;
|
||||||
|
|
||||||
// this iterates over every op string and decides which tags to open or to close
|
// this iterates over every op string and decides which tags to open or to close
|
||||||
|
|
|
@ -22,9 +22,7 @@
|
||||||
import {AText, PadType} from "../types/PadType";
|
import {AText, PadType} from "../types/PadType";
|
||||||
import {MapType} from "../types/MapType";
|
import {MapType} from "../types/MapType";
|
||||||
|
|
||||||
import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
|
const Changeset = require('../../static/js/Changeset');
|
||||||
import {StringIterator} from "../../static/js/StringIterator";
|
|
||||||
import {StringAssembler} from "../../static/js/StringAssembler";
|
|
||||||
const attributes = require('../../static/js/attributes');
|
const attributes = require('../../static/js/attributes');
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||||
|
@ -47,14 +45,13 @@ const getPadTXT = async (pad: PadType, revNum: string) => {
|
||||||
const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
|
const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
|
||||||
const apool = pad.apool();
|
const apool = pad.apool();
|
||||||
const textLines = atext.text.slice(0, -1).split('\n');
|
const textLines = atext.text.slice(0, -1).split('\n');
|
||||||
const attribLines = splitAttributionLines(atext.attribs, atext.text);
|
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||||
|
|
||||||
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||||
const anumMap: MapType = {};
|
const anumMap: MapType = {};
|
||||||
const css = '';
|
const css = '';
|
||||||
|
|
||||||
props.forEach((propName, i) => {
|
props.forEach((propName, i) => {
|
||||||
// @ts-ignore
|
|
||||||
const propTrueNum = apool.putAttrib([propName, true], true);
|
const propTrueNum = apool.putAttrib([propName, true], true);
|
||||||
if (propTrueNum >= 0) {
|
if (propTrueNum >= 0) {
|
||||||
anumMap[propTrueNum] = i;
|
anumMap[propTrueNum] = i;
|
||||||
|
@ -72,8 +69,8 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
|
||||||
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
||||||
// becomes
|
// becomes
|
||||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||||
const taker = new StringIterator(text);
|
const taker = Changeset.stringIterator(text);
|
||||||
const assem = new StringAssembler();
|
const assem = Changeset.stringAssembler();
|
||||||
|
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
|
|
||||||
|
@ -82,7 +79,7 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = deserializeOps(subattribution(attribs, idx, idx + numChars));
|
const ops = Changeset.deserializeOps(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||||
idx += numChars;
|
idx += numChars;
|
||||||
|
|
||||||
for (const o of ops) {
|
for (const o of ops) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {APool} from "../types/PadType";
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import AttributePool from '../../static/js/AttributePool';
|
const AttributePool = require('../../static/js/AttributePool');
|
||||||
const {Pad} = require('../db/Pad');
|
const {Pad} = require('../db/Pad');
|
||||||
const Stream = require('./Stream');
|
const Stream = require('./Stream');
|
||||||
const authorManager = require('../db/AuthorManager');
|
const authorManager = require('../db/AuthorManager');
|
||||||
|
@ -61,7 +61,7 @@ exports.setPadRaw = async (padId: string, r: string, authorId = '') => {
|
||||||
try {
|
try {
|
||||||
const processRecord = async (key:string, value: null|{
|
const processRecord = async (key:string, value: null|{
|
||||||
padIDs: string|Record<string, unknown>,
|
padIDs: string|Record<string, unknown>,
|
||||||
pool: AttributePool
|
pool: APool
|
||||||
}) => {
|
}) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
const keyParts = key.split(':');
|
const keyParts = key.split(':');
|
||||||
|
|
|
@ -16,11 +16,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import {deserializeOps} from '../../static/js/Changeset';
|
const Changeset = require('../../static/js/Changeset');
|
||||||
const contentcollector = require('../../static/js/contentcollector');
|
const contentcollector = require('../../static/js/contentcollector');
|
||||||
import jsdom from 'jsdom';
|
import jsdom from 'jsdom';
|
||||||
import {PadType} from "../types/PadType";
|
import {PadType} from "../types/PadType";
|
||||||
import {Builder} from "../../static/js/Builder";
|
|
||||||
|
|
||||||
const apiLogger = log4js.getLogger('ImportHtml');
|
const apiLogger = log4js.getLogger('ImportHtml');
|
||||||
let processor:any;
|
let processor:any;
|
||||||
|
@ -70,13 +69,13 @@ exports.setPadHTML = async (pad: PadType, html:string, authorId = '') => {
|
||||||
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
|
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
|
||||||
|
|
||||||
// create a new changeset with a helper builder object
|
// create a new changeset with a helper builder object
|
||||||
const builder = new Builder(1);
|
const builder = Changeset.builder(1);
|
||||||
|
|
||||||
// assemble each line into the builder
|
// assemble each line into the builder
|
||||||
let textIndex = 0;
|
let textIndex = 0;
|
||||||
const newTextStart = 0;
|
const newTextStart = 0;
|
||||||
const newTextEnd = newText.length;
|
const newTextEnd = newText.length;
|
||||||
for (const op of deserializeOps(newAttribs)) {
|
for (const op of Changeset.deserializeOps(newAttribs)) {
|
||||||
const nextIndex = textIndex + op.chars;
|
const nextIndex = textIndex + op.chars;
|
||||||
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
|
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
|
||||||
const start = Math.max(newTextStart, textIndex);
|
const start = Math.max(newTextStart, textIndex);
|
||||||
|
|
|
@ -21,20 +21,20 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {TransformResult} from "esbuild";
|
|
||||||
import mime from 'mime-types';
|
|
||||||
import log4js from 'log4js';
|
|
||||||
import {compressCSS, compressJS} from './MinifyWorker'
|
|
||||||
|
|
||||||
const settings = require('./Settings');
|
const settings = require('./Settings');
|
||||||
import {promises as fs} from 'fs';
|
const fs = require('fs').promises;
|
||||||
import path from 'node:path';
|
const path = require('path');
|
||||||
const plugins = require('../../static/js/pluginfw/plugin_defs');
|
const plugins = require('../../static/js/pluginfw/plugin_defs');
|
||||||
import sanitizePathname from './sanitizePathname';
|
const mime = require('mime-types');
|
||||||
|
const Threads = require('threads');
|
||||||
|
const log4js = require('log4js');
|
||||||
|
const sanitizePathname = require('./sanitizePathname');
|
||||||
|
|
||||||
const logger = log4js.getLogger('Minify');
|
const logger = log4js.getLogger('Minify');
|
||||||
|
|
||||||
const ROOT_DIR = path.join(settings.root, 'src/static/');
|
const ROOT_DIR = path.join(settings.root, 'src/static/');
|
||||||
|
|
||||||
|
const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
|
||||||
|
|
||||||
const LIBRARY_WHITELIST = [
|
const LIBRARY_WHITELIST = [
|
||||||
'async',
|
'async',
|
||||||
|
@ -48,10 +48,10 @@ const LIBRARY_WHITELIST = [
|
||||||
|
|
||||||
// What follows is a terrible hack to avoid loop-back within the server.
|
// What follows is a terrible hack to avoid loop-back within the server.
|
||||||
// TODO: Serve files from another service, or directly from the file system.
|
// TODO: Serve files from another service, or directly from the file system.
|
||||||
const requestURI = async (url: string | URL, method: any, headers: { [x: string]: any; }) => {
|
const requestURI = async (url, method, headers) => {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
let status = 500;
|
let status = 500;
|
||||||
const content: any[] = [];
|
const content = [];
|
||||||
const mockRequest = {
|
const mockRequest = {
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
|
@ -61,7 +61,7 @@ const requestURI = async (url: string | URL, method: any, headers: { [x: string]
|
||||||
let mockResponse;
|
let mockResponse;
|
||||||
const p = new Promise((resolve) => {
|
const p = new Promise((resolve) => {
|
||||||
mockResponse = {
|
mockResponse = {
|
||||||
writeHead: (_status: number, _headers: { [x: string]: any; }) => {
|
writeHead: (_status, _headers) => {
|
||||||
status = _status;
|
status = _status;
|
||||||
for (const header in _headers) {
|
for (const header in _headers) {
|
||||||
if (Object.prototype.hasOwnProperty.call(_headers, header)) {
|
if (Object.prototype.hasOwnProperty.call(_headers, header)) {
|
||||||
|
@ -69,63 +69,37 @@ const requestURI = async (url: string | URL, method: any, headers: { [x: string]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setHeader: (header: string, value: { toString: () => any; }) => {
|
setHeader: (header, value) => {
|
||||||
headers[header.toLowerCase()] = value.toString();
|
headers[header.toLowerCase()] = value.toString();
|
||||||
},
|
},
|
||||||
header: (header: string, value: { toString: () => any; }) => {
|
header: (header, value) => {
|
||||||
headers[header.toLowerCase()] = value.toString();
|
headers[header.toLowerCase()] = value.toString();
|
||||||
},
|
},
|
||||||
write: (_content: any) => {
|
write: (_content) => {
|
||||||
_content && content.push(_content);
|
_content && content.push(_content);
|
||||||
},
|
},
|
||||||
end: (_content: any) => {
|
end: (_content) => {
|
||||||
_content && content.push(_content);
|
_content && content.push(_content);
|
||||||
resolve([status, headers, content.join('')]);
|
resolve([status, headers, content.join('')]);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
await _minify(mockRequest, mockResponse);
|
await minify(mockRequest, mockResponse);
|
||||||
return await p;
|
return await p;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _requestURIs = (locations: any[], method: any, headers: {
|
const requestURIs = (locations, method, headers, callback) => {
|
||||||
[x: string]:
|
|
||||||
/**
|
|
||||||
* This Module manages all /minified/* requests. It controls the
|
|
||||||
* minification && compression of Javascript and CSS.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
any;
|
|
||||||
}, callback: (arg0: any[], arg1: any[], arg2: any[]) => void) => {
|
|
||||||
Promise.all(locations.map(async (loc) => {
|
Promise.all(locations.map(async (loc) => {
|
||||||
try {
|
try {
|
||||||
return await requestURI(loc, method, headers);
|
return await requestURI(loc, method, headers);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.debug(`requestURI(${JSON.stringify(loc)}, ${JSON.stringify(method)}, ` +
|
logger.debug(`requestURI(${JSON.stringify(loc)}, ${JSON.stringify(method)}, ` +
|
||||||
// @ts-ignore
|
`${JSON.stringify(headers)}) failed: ${err.stack || err}`);
|
||||||
`${JSON.stringify(headers)}) failed: ${err.stack || err}`);
|
|
||||||
return [500, headers, ''];
|
return [500, headers, ''];
|
||||||
}
|
}
|
||||||
})).then((responses) => {
|
})).then((responses) => {
|
||||||
// @ts-ignore
|
|
||||||
const statuss = responses.map((x) => x[0]);
|
const statuss = responses.map((x) => x[0]);
|
||||||
// @ts-ignore
|
|
||||||
const headerss = responses.map((x) => x[1]);
|
const headerss = responses.map((x) => x[1]);
|
||||||
// @ts-ignore
|
|
||||||
const contentss = responses.map((x) => x[2]);
|
const contentss = responses.map((x) => x[2]);
|
||||||
callback(statuss, headerss, contentss);
|
callback(statuss, headerss, contentss);
|
||||||
});
|
});
|
||||||
|
@ -145,12 +119,11 @@ const compatPaths = {
|
||||||
* @param req the Express request
|
* @param req the Express request
|
||||||
* @param res the Express response
|
* @param res the Express response
|
||||||
*/
|
*/
|
||||||
const _minify = async (req:any, res:any) => {
|
const minify = async (req, res) => {
|
||||||
let filename = req.params.filename;
|
let filename = req.params.filename;
|
||||||
try {
|
try {
|
||||||
filename = sanitizePathname(filename);
|
filename = sanitizePathname(filename);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// @ts-ignore
|
|
||||||
logger.error(`sanitization of pathname "${filename}" failed: ${err.stack || err}`);
|
logger.error(`sanitization of pathname "${filename}" failed: ${err.stack || err}`);
|
||||||
res.writeHead(404, {});
|
res.writeHead(404, {});
|
||||||
res.end();
|
res.end();
|
||||||
|
@ -158,7 +131,6 @@ const _minify = async (req:any, res:any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backward compatibility for plugins that require() files from old paths.
|
// Backward compatibility for plugins that require() files from old paths.
|
||||||
// @ts-ignore
|
|
||||||
const newLocation = compatPaths[filename.replace(/^plugins\/ep_etherpad-lite\/static\//, '')];
|
const newLocation = compatPaths[filename.replace(/^plugins\/ep_etherpad-lite\/static\//, '')];
|
||||||
if (newLocation != null) {
|
if (newLocation != null) {
|
||||||
logger.warn(`request for deprecated path "${filename}", replacing with "${newLocation}"`);
|
logger.warn(`request for deprecated path "${filename}", replacing with "${newLocation}"`);
|
||||||
|
@ -221,7 +193,7 @@ const _minify = async (req:any, res:any) => {
|
||||||
res.writeHead(200, {});
|
res.writeHead(200, {});
|
||||||
res.end();
|
res.end();
|
||||||
} else if (req.method === 'GET') {
|
} else if (req.method === 'GET') {
|
||||||
const content = await getFileCompressed(filename, contentType as string);
|
const content = await getFileCompressed(filename, contentType);
|
||||||
res.header('Content-Type', contentType);
|
res.header('Content-Type', contentType);
|
||||||
res.writeHead(200, {});
|
res.writeHead(200, {});
|
||||||
res.write(content);
|
res.write(content);
|
||||||
|
@ -233,7 +205,7 @@ const _minify = async (req:any, res:any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for the existance of the file and get the last modification date.
|
// Check for the existance of the file and get the last modification date.
|
||||||
const statFile = async (filename: string, dirStatLimit: number):Promise<(any | boolean)[]> => {
|
const statFile = async (filename, dirStatLimit) => {
|
||||||
/*
|
/*
|
||||||
* The only external call to this function provides an explicit value for
|
* The only external call to this function provides an explicit value for
|
||||||
* dirStatLimit: this check could be removed.
|
* dirStatLimit: this check could be removed.
|
||||||
|
@ -249,7 +221,6 @@ const statFile = async (filename: string, dirStatLimit: number):Promise<(any | b
|
||||||
try {
|
try {
|
||||||
stats = await fs.stat(path.resolve(ROOT_DIR, filename));
|
stats = await fs.stat(path.resolve(ROOT_DIR, filename));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// @ts-ignore
|
|
||||||
if (['ENOENT', 'ENOTDIR'].includes(err.code)) {
|
if (['ENOENT', 'ENOTDIR'].includes(err.code)) {
|
||||||
// Stat the directory instead.
|
// Stat the directory instead.
|
||||||
const [date] = await statFile(path.dirname(filename), dirStatLimit - 1);
|
const [date] = await statFile(path.dirname(filename), dirStatLimit - 1);
|
||||||
|
@ -263,64 +234,69 @@ const statFile = async (filename: string, dirStatLimit: number):Promise<(any | b
|
||||||
|
|
||||||
let contentCache = new Map();
|
let contentCache = new Map();
|
||||||
|
|
||||||
const getFileCompressed = async (filename: any, contentType: string) => {
|
const getFileCompressed = async (filename, contentType) => {
|
||||||
if (contentCache.has(filename)) {
|
if (contentCache.has(filename)) {
|
||||||
return contentCache.get(filename);
|
return contentCache.get(filename);
|
||||||
}
|
}
|
||||||
let content: Buffer|string = await getFile(filename);
|
let content = await getFile(filename);
|
||||||
if (!content || !settings.minify) {
|
if (!content || !settings.minify) {
|
||||||
return content;
|
return content;
|
||||||
} else if (contentType === 'application/javascript') {
|
} else if (contentType === 'application/javascript') {
|
||||||
return await new Promise(async (resolve) => {
|
return await new Promise((resolve) => {
|
||||||
try {
|
threadsPool.queue(async ({compressJS}) => {
|
||||||
logger.info('Compress JS file %s.', filename);
|
|
||||||
|
|
||||||
content = content.toString();
|
|
||||||
try {
|
try {
|
||||||
let compressResult: TransformResult<{ minify: boolean }>
|
logger.info('Compress JS file %s.', filename);
|
||||||
compressResult = await compressJS(content);
|
|
||||||
content = compressResult.code.toString(); // Convert content obj code to string
|
content = content.toString();
|
||||||
|
const compressResult = await compressJS(content);
|
||||||
|
|
||||||
|
if (compressResult.error) {
|
||||||
|
console.error(`Error compressing JS (${filename}) using terser`, compressResult.error);
|
||||||
|
} else {
|
||||||
|
content = compressResult.code.toString(); // Convert content obj code to string
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error compressing JS (${filename}) using esbuild`, error);
|
console.error('getFile() returned an error in ' +
|
||||||
|
`getFileCompressed(${filename}, ${contentType}): ${error}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
contentCache.set(filename, content);
|
||||||
console.error('getFile() returned an error in ' +
|
resolve(content);
|
||||||
`getFileCompressed(${filename}, ${contentType}): ${error}`);
|
});
|
||||||
}
|
|
||||||
contentCache.set(filename, content);
|
|
||||||
resolve(content);
|
|
||||||
});
|
});
|
||||||
} else if (contentType === 'text/css') {
|
} else if (contentType === 'text/css') {
|
||||||
return await new Promise(async (resolve) => {
|
return await new Promise((resolve) => {
|
||||||
try {
|
threadsPool.queue(async ({compressCSS}) => {
|
||||||
logger.info('Compress CSS file %s.', filename);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
content = await compressCSS(path.resolve(ROOT_DIR, filename));
|
logger.info('Compress CSS file %s.', filename);
|
||||||
|
|
||||||
|
const compressResult = await compressCSS(path.resolve(ROOT_DIR,filename));
|
||||||
|
|
||||||
|
if (compressResult.error) {
|
||||||
|
console.error(`Error compressing CSS (${filename}) using terser`, compressResult.error);
|
||||||
|
} else {
|
||||||
|
content = compressResult
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`CleanCSS.minify() returned an error on ${filename}: ${error}`);
|
console.error(`CleanCSS.minify() returned an error on ${filename}: ${error}`);
|
||||||
}
|
}
|
||||||
contentCache.set(filename, content);
|
contentCache.set(filename, content);
|
||||||
resolve(content);
|
resolve(content);
|
||||||
} catch (e) {
|
});
|
||||||
console.error('getFile() returned an error in ' +
|
});
|
||||||
`getFileCompressed(${filename}, ${contentType}): ${e}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
contentCache.set(filename, content);
|
contentCache.set(filename, content);
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFile = async (filename: any) => {
|
const getFile = async (filename) => {
|
||||||
return await fs.readFile(path.resolve(ROOT_DIR, filename));
|
return await fs.readFile(path.resolve(ROOT_DIR, filename));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const minify = (req:any, res:any, next:Function) => _minify(req, res).catch((err) => next(err || new Error(err)));
|
exports.minify = (req, res, next) => minify(req, res).catch((err) => next(err || new Error(err)));
|
||||||
|
|
||||||
export const requestURIs = _requestURIs;
|
exports.requestURIs = requestURIs;
|
||||||
|
|
||||||
export const shutdown = async (hookName: string, context:any) => {
|
exports.shutdown = async (hookName, context) => {
|
||||||
contentCache = new Map();
|
await threadsPool.terminate();
|
||||||
};
|
};
|
|
@ -3,13 +3,14 @@
|
||||||
* Worker thread to minify JS & CSS files out of the main NodeJS thread
|
* Worker thread to minify JS & CSS files out of the main NodeJS thread
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {expose} from 'threads'
|
||||||
import {build, transform} from 'esbuild';
|
import {build, transform} from 'esbuild';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Minify JS content
|
* Minify JS content
|
||||||
* @param {string} content - JS content to minify
|
* @param {string} content - JS content to minify
|
||||||
*/
|
*/
|
||||||
export const compressJS = async (content: string) => {
|
const compressJS = async (content) => {
|
||||||
return await transform(content, {minify: true});
|
return await transform(content, {minify: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ export const compressJS = async (content: string) => {
|
||||||
* @param {string} filename - name of the file
|
* @param {string} filename - name of the file
|
||||||
* @param {string} ROOT_DIR - the root dir of Etherpad
|
* @param {string} ROOT_DIR - the root dir of Etherpad
|
||||||
*/
|
*/
|
||||||
export const compressCSS = async (content: string) => {
|
const compressCSS = async (content) => {
|
||||||
const transformedCSS = await build(
|
const transformedCSS = await build(
|
||||||
{
|
{
|
||||||
entryPoints: [content],
|
entryPoints: [content],
|
||||||
|
@ -40,3 +41,8 @@ export const compressCSS = async (content: string) => {
|
||||||
)
|
)
|
||||||
return transformedCSS.outputFiles[0].text
|
return transformedCSS.outputFiles[0].text
|
||||||
};
|
};
|
||||||
|
|
||||||
|
expose({
|
||||||
|
compressJS: compressJS,
|
||||||
|
compressCSS,
|
||||||
|
});
|
|
@ -107,7 +107,6 @@ exports.ttl = {
|
||||||
RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
|
RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updateServer = "https://static.etherpad.org"
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -170,11 +169,11 @@ exports.authenticationMethod = 'sso'
|
||||||
/*
|
/*
|
||||||
* The Type of the database
|
* The Type of the database
|
||||||
*/
|
*/
|
||||||
exports.dbType = 'rustydb';
|
exports.dbType = 'dirty';
|
||||||
/**
|
/**
|
||||||
* This setting is passed with dbType to ueberDB to set up the database
|
* This setting is passed with dbType to ueberDB to set up the database
|
||||||
*/
|
*/
|
||||||
exports.dbSettings = {filename: path.join(exports.root, 'var/rusty.db')};
|
exports.dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default Text of a new pad
|
* The default Text of a new pad
|
||||||
|
@ -380,14 +379,6 @@ exports.sso = {
|
||||||
*/
|
*/
|
||||||
exports.showSettingsInAdminPage = true;
|
exports.showSettingsInAdminPage = true;
|
||||||
|
|
||||||
/*
|
|
||||||
* Settings for cleanup of pads
|
|
||||||
*/
|
|
||||||
exports.cleanup = {
|
|
||||||
enabled: false,
|
|
||||||
keepRevisions: 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* By default, when caret is moved out of viewport, it scrolls the minimum
|
* By default, when caret is moved out of viewport, it scrolls the minimum
|
||||||
* height needed to make this line visible.
|
* height needed to make this line visible.
|
||||||
|
@ -846,7 +837,7 @@ exports.reloadSettings = () => {
|
||||||
exports.skinName = 'colibris';
|
exports.skinName = 'colibris';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exports.socketTransportProtocols.includes("websocket") || !exports.socketTransportProtocols.includes("polling")) {
|
if (!exports.socketTransportProtocols.includes("websocket") || exports.socketTransportProtocols.includes("polling")) {
|
||||||
logger.warn("Invalid socketTransportProtocols setting. Please check out settings.json.template and update your settings.json. Falling back to the default ['websocket', 'polling'].");
|
logger.warn("Invalid socketTransportProtocols setting. Please check out settings.json.template and update your settings.json. Falling back to the default ['websocket', 'polling'].");
|
||||||
exports.socketTransportProtocols = ['websocket', 'polling'];
|
exports.socketTransportProtocols = ['websocket', 'polling'];
|
||||||
}
|
}
|
||||||
|
@ -950,12 +941,6 @@ exports.reloadSettings = () => {
|
||||||
logger.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`);
|
logger.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exports.dbType === 'rustydb' || exports.dbType === "sqlite") {
|
|
||||||
exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename);
|
|
||||||
logger.warn(`File location: ${exports.dbSettings.filename}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (exports.ip === '') {
|
if (exports.ip === '') {
|
||||||
// using Unix socket for connectivity
|
// using Unix socket for connectivity
|
||||||
logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' +
|
logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' +
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue