Merge branch 'develop'

This commit is contained in:
Etherpad Release Bot 2025-08-24 20:36:27 +00:00
commit 7d077e108c
119 changed files with 2507 additions and 2520 deletions

View file

@ -24,11 +24,11 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [20, 22, 23]
node: [20, 22, 24]
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:
@ -53,7 +53,7 @@ jobs:
run: pnpm config set auto-install-peers false
-
name: Install libreoffice
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: libreoffice libreoffice-pdfimport
version: 1.0
@ -84,11 +84,11 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [20, 22, 23]
node: [20, 22, 24]
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:
@ -113,7 +113,7 @@ jobs:
run: pnpm config set auto-install-peers false
-
name: Install libreoffice
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: libreoffice libreoffice-pdfimport
version: 1.0
@ -160,7 +160,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:
@ -217,7 +217,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:

View file

@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Pages
uses: actions/configure-pages@v5
- uses: pnpm/action-setup@v4
@ -61,7 +61,7 @@ jobs:
env:
COMMIT_REF: ${{ github.sha }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
# Upload entire repository
path: './doc/.vitepress/dist'

View file

@ -25,7 +25,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.

View file

@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4

View file

@ -21,7 +21,7 @@ jobs:
steps:
-
name: Check out
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
path: etherpad
@ -123,7 +123,7 @@ jobs:
enable-url-completion: true
- name: Check out
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
path: ether-charts
repository: ether/ether-charts

View file

@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [20, 22, 23]
node: [20, 22, 24]
steps:
-
@ -28,7 +28,7 @@ jobs:
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}-node${{ matrix.node }}'
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:

View file

@ -22,7 +22,7 @@ jobs:
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}'
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:
@ -96,7 +96,7 @@ jobs:
printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}'
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}'
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: 22
@ -169,7 +169,7 @@ jobs:
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}'
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:

View file

@ -24,7 +24,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:
@ -68,7 +68,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:
@ -139,7 +139,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:

View file

@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: 20

View file

@ -24,7 +24,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:

View file

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: ether/etherpad-lite
path: etherpad
@ -37,7 +37,7 @@ jobs:
git checkout develop
git reset --hard origin/develop
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: ether/ether.github.com
path: ether.github.com

View file

@ -24,11 +24,11 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [20, 22, 23]
node: [20, 22, 24]
steps:
-
name: Check out latest release
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: develop #FIXME change to master when doing release
-
@ -43,7 +43,7 @@ jobs:
- name: Only install direct dependencies
run: pnpm config set auto-install-peers false
- name: Install libreoffice
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: libreoffice libreoffice-pdfimport
version: 1.0
@ -62,7 +62,7 @@ jobs:
run: pnpm config set auto-install-peers false
-
name: Install libreoffice
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: libreoffice libreoffice-pdfimport
version: 1.0
@ -97,7 +97,7 @@ jobs:
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: ./bin/installDeps.sh
# Because actions/checkout@v4 is called with "ref: master" and without
# Because actions/checkout@v5 is called with "ref: master" and without
# "fetch-depth: 0", the local clone does not have the ${GITHUB_SHA}
# commit. Fetch ${GITHUB_REF} to get the ${GITHUB_SHA} commit. Note that a
# plain "git fetch" only fetches "normal" references (refs/heads/* and

View file

@ -31,7 +31,7 @@ jobs:
zip
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
-
uses: actions/setup-node@v4
with:

View file

@ -1,3 +1,11 @@
# 2.5.0
### Notable enhancements and fixes
- Updated to express 5.0.0. This is a major update to express that brings a lot of improvements and fixes. Please update all your plugins to the latest version to ensure compatibility. A lot changed in the route matching, and thus old plugins will throw errors and crash Etherpad.
- Fixed an issue with the no-skin theme with cookie recentPadList feature
- Fixed layout issues with the no-skin theme
# 2.4.2
### Notable enhancements and fixes

View file

@ -107,7 +107,8 @@ RUN \
curl \
git \
${INSTALL_ABIWORD:+abiword abiword-plugin-command} \
${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common}
${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common} && \
rm -rf /var/cache/apk/*
USER etherpad
@ -172,7 +173,8 @@ 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
fi && \
pnpm store prune
# Copy the configuration file.
COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json

View file

@ -1,7 +1,7 @@
{
"name": "admin",
"private": true,
"version": "2.4.2",
"version": "2.5.0",
"type": "module",
"scripts": {
"dev": "vite",
@ -11,32 +11,32 @@
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-switch": "^1.2.5"
"@radix-ui/react-switch": "^1.2.6"
},
"devDependencies": {
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-toast": "^1.2.14",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.37.0",
"@vitejs/plugin-react-swc": "^3.10.2",
"eslint": "^9.31.0",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-toast": "^1.2.15",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@typescript-eslint/eslint-plugin": "^8.40.0",
"@typescript-eslint/parser": "^8.40.0",
"@vitejs/plugin-react-swc": "^4.0.1",
"eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"i18next": "^25.3.2",
"i18next": "^25.4.0",
"i18next-browser-languagedetector": "^8.2.0",
"lucide-react": "^0.525.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.60.0",
"react-i18next": "^15.6.0",
"react-router-dom": "^7.6.3",
"lucide-react": "^0.541.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hook-form": "^7.62.0",
"react-i18next": "^15.7.1",
"react-router-dom": "^7.8.1",
"socket.io-client": "^4.8.1",
"typescript": "^5.8.2",
"vite": "^7.0.4",
"vite-plugin-static-copy": "^3.1.1",
"typescript": "^5.9.2",
"vite": "^7.1.3",
"vite-plugin-static-copy": "^3.1.2",
"vite-plugin-svgr": "^4.3.0",
"zustand": "^5.0.6"
"zustand": "^5.0.8"
}
}

View file

@ -18,7 +18,7 @@ import process from "node:process";
process.on('unhandledRejection', (err) => { throw err; });
const settings = require('ep_etherpad-lite/node/utils/Settings');
import settings from 'ep_etherpad-lite/node/utils/Settings';
(async () => {
axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
const api = axios;

View file

@ -19,4 +19,4 @@ cd "${MY_DIR}/.." || exit 1
echo "Running directly, without checking/installing dependencies"
# run Etherpad main class
exec node --import tsx src/node/server.ts "$@"
exec pnpm run prod "$@"

View file

@ -3,13 +3,13 @@
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
import util from "node:util";
const fs = require('fs');
import fs from 'node:fs';
import log4js from 'log4js';
import readline from 'readline';
import {Database} from "ueberdb2";
import {Database, DatabaseType} from "ueberdb2";
import process from "node:process";
const settings = require('ep_etherpad-lite/node/utils/Settings');
import settings from 'ep_etherpad-lite/node/utils/Settings';
process.on('unhandledRejection', (err) => { throw err; });
const startTime = Date.now();
@ -58,7 +58,7 @@ const unescape = (val: string) => {
json: false, // data is already json encoded
};
const db = new Database( // eslint-disable-line new-cap
settings.dbType,
settings.dbType as DatabaseType,
settings.dbSettings,
dbWrapperSettings,
log4js.getLogger('ueberDB'));

View file

@ -2,15 +2,15 @@
import {readFileSync} from 'node:fs'
import {Database, DatabaseType} from "ueberdb2";
import path from "node:path";
const settings = require('ep_etherpad-lite/node/utils/Settings');
import settings from 'ep_etherpad-lite/node/utils/Settings';
// file1 = source, file2 = target
// pnpm run migrateDB --file1 <db1.json> --file2 <db2.json>
// pnpm run --filter bin 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')
console.error('Wrong number of arguments!. Call with pnpm run --filter bin migrateDB --file1 source.json target.json')
process.exit(1)
}

View file

@ -1,10 +1,9 @@
'use strict';
import process from 'node:process';
import {Database} from "ueberdb2";
import {Database, DatabaseType} from "ueberdb2";
import log4js from 'log4js';
import util from 'util';
const settings = require('ep_etherpad-lite/node/utils/Settings');
import settings from 'ep_etherpad-lite/node/utils/Settings';
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
@ -24,7 +23,7 @@ process.on('unhandledRejection', (err) => { throw err; });
writeInterval: 0, // Write directly to the database, don't buffer
};
const db = new Database( // eslint-disable-line new-cap
settings.dbType,
settings.dbType as DatabaseType,
settings.dbSettings,
dbWrapperSettings,
log4js.getLogger('ueberDB'));

View file

@ -1,6 +1,6 @@
{
"name": "bin",
"version": "2.4.2",
"version": "2.5.0",
"description": "",
"main": "checkAllPads.js",
"directories": {
@ -11,13 +11,13 @@
"ep_etherpad-lite": "workspace:../src",
"log4js": "^6.9.1",
"semver": "^7.7.2",
"tsx": "^4.20.3",
"ueberdb2": "^5.0.14"
"tsx": "^4.20.5",
"ueberdb2": "^5.0.15"
},
"devDependencies": {
"@types/node": "^24.0.14",
"@types/node": "^24.3.0",
"@types/semver": "^7.7.0",
"typescript": "^5.8.2"
"typescript": "^5.9.2"
},
"scripts": {
"makeDocs": "node --import tsx make_docs.ts",

View file

@ -1,5 +1,3 @@
'use strict';
/*
This is a repair tool. It rebuilds an old pad at a new pad location up to a
known "good" revision.

View file

@ -26,4 +26,4 @@ In this example we migrate from the old dirty db to the new rustydb engine. So w
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.
Therefore, we call `pnpm run --filter bin 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.

View file

@ -1,6 +1,6 @@
{
"devDependencies": {
"vitepress": "^2.0.0-alpha.9"
"vitepress": "^2.0.0-alpha.12"
},
"scripts": {
"docs:dev": "vitepress dev",

View file

@ -50,6 +50,6 @@
"type": "git",
"url": "https://github.com/ether/etherpad-lite.git"
},
"version": "2.4.2",
"version": "2.5.0",
"license": "Apache-2.0"
}

2828
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -96,12 +96,6 @@
"socketio": "ep_etherpad-lite/node/handler/PadMessageHandler"
}
},
{
"name": "tests",
"hooks": {
"expressPreSession": "ep_etherpad-lite/node/hooks/express/tests"
}
},
{
"name": "admin",
"hooks": {

View file

@ -49,8 +49,15 @@
"admin_settings.current_save.value": "حفظ الإعدادات",
"admin_settings.page-title": "الإعدادات - Etherpad",
"index.newPad": "باد جديد",
"index.createOpenPad": "أو صنع/فتح باد بوضع اسمه:",
"index.createOpenPad": "افتح الوسادة حسب الاسم",
"index.openPad": "افتح باد موجودة بالاسم:",
"index.recentPads": "الوسادات الأخيرة",
"index.recentPadsEmpty": "لم يتم العثور على أي وسادات حديثة.",
"index.generateNewPad": "إنشاء اسم لوحة عشوائي",
"index.labelPad": "اسم الوسادة (اختياري)",
"index.placeholderPadEnter": "الرجاء إدخال اسم الوسادة...",
"index.createAndShareDocuments": "إنشاء المستندات ومشاركتها في الوقت الفعلي",
"index.createAndShareDocumentsDescription": "يتيح لك Etherpad تحرير المستندات بشكل تعاوني في الوقت الفعلي، تمامًا مثل محرر متعدد اللاعبين مباشر يعمل في متصفحك.",
"pad.toolbar.bold.title": "سميك (Ctrl+B)",
"pad.toolbar.italic.title": "مائل (Ctrl+I)",
"pad.toolbar.underline.title": "تسطير (Ctrl+U)",
@ -67,6 +74,7 @@
"pad.toolbar.savedRevision.title": "حفظ المراجعة",
"pad.toolbar.settings.title": "الإعدادات",
"pad.toolbar.embed.title": "تبادل و تضمين هذا الباد",
"pad.toolbar.home.title": "العودة إلى المنزل",
"pad.toolbar.showusers.title": "عرض المستخدمين على هذا الباد",
"pad.colorpicker.save": "حفظ",
"pad.colorpicker.cancel": "إلغاء",

View file

@ -41,8 +41,13 @@
"admin_settings.current_save.value": "Захаваць налады",
"admin_settings.page-title": "Налады — Etherpad",
"index.newPad": "Стварыць",
"index.createOpenPad": "ці тварыць/адкрыць дакумэнт з назвай:",
"index.createOpenPad": "Адкрыць дакумэнт паводле назвы",
"index.openPad": "адкрыць існы Нататнік з назваю:",
"index.recentPads": "Нядаўнія дакумэнты",
"index.recentPadsEmpty": "Нядаўнія дакумэнты ня знойдзеныя.",
"index.generateNewPad": "Стварыць выпадковую назву дакумэнта",
"index.labelPad": "Назва дакумэнта (неабавязкова)",
"index.placeholderPadEnter": "Калі ласка, увядзіце назву дакумэнта…",
"pad.toolbar.bold.title": "Тоўсты (Ctrl-B)",
"pad.toolbar.italic.title": "Курсіў (Ctrl-I)",
"pad.toolbar.underline.title": "Падкрэсьліваньне (Ctrl-U)",

View file

@ -54,14 +54,14 @@
"index.newPad": "Neues Pad",
"index.createOpenPad": "Pad öffnen",
"index.openPad": "Öffne ein vorhandenes Pad mit folgendem Namen:",
"index.recentPads": "Zuletzt bearbeitete Pads",
"index.recentPadsEmpty": "Keine kürzlich bearbeiteten Pads gefunden.",
"index.generateNewPad": "Neues Pad generieren",
"index.labelPad": "Padname (optional)",
"index.placeholderPadEnter": "Gib den Namen des Pads ein...",
"index.createAndShareDocuments": "Erstelle und teile Dokumente in Echtzeit",
"index.createAndShareDocumentsDescription": "Etherpad ermöglicht die gemeinsame Bearbeitung von Dokumenten in Echtzeit, ähnlich wie ein Live-Multiplayer-Editor, der in Ihrem Browser läuft.",
"pad.toolbar.bold.title": "Fett (Strg-B)",
"index.recentPads": "Zuletzt bearbeitete Pads",
"index.recentPadsEmpty": "Keine kürzlich bearbeiteten Pads gefunden.",
"index.generateNewPad": "Neues Pad generieren",
"index.labelPad": "Padname (optional)",
"index.placeholderPadEnter": "Gib den Namen des Pads ein...",
"index.createAndShareDocuments": "Erstelle und teile Dokumente in Echtzeit",
"index.createAndShareDocumentsDescription": "Etherpad ermöglicht die gemeinsame Bearbeitung von Dokumenten in Echtzeit, ähnlich wie ein Live-Multiplayer-Editor, der in Ihrem Browser läuft.",
"pad.toolbar.bold.title": "Fett (Strg-B)",
"pad.toolbar.italic.title": "Kursiv (Strg-I)",
"pad.toolbar.underline.title": "Unterstrichen (Strg-U)",
"pad.toolbar.strikethrough.title": "Durchgestrichen (Strg+5)",

View file

@ -4,6 +4,7 @@
"Evropi",
"Geraki",
"Glavkos",
"Jimkats",
"Monopatis",
"Norhorn",
"Papspyr",
@ -61,6 +62,7 @@
"pad.toolbar.savedRevision.title": "Αποθήκευση Αναθεώρησης",
"pad.toolbar.settings.title": "Ρυθμίσεις",
"pad.toolbar.embed.title": "Διαμοίραση και Ενσωμάτωση αυτού του κοινόχρηστου πίνακα",
"pad.toolbar.home.title": "Επιστροφή στην αρχή",
"pad.toolbar.showusers.title": "Εμφάνιση των χρηστών αυτού του κοινόχρηστου πίνακα",
"pad.colorpicker.save": "Αποθήκευση",
"pad.colorpicker.cancel": "Ακύρωση",

View file

@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"Alexgabi",
"An13sa",
"HairyFotr",
"Izendegi",
@ -46,6 +47,12 @@
"index.newPad": "Pad berria",
"index.createOpenPad": "edo sortu/ireki Pad bat honako izenarekin:",
"index.openPad": "ireki existitzen den eta hurrengo izena duen Pad-a:",
"index.recentPadsEmpty": "Ez da aurkitu duela gutxiko pad-ik",
"index.generateNewPad": "Sortu pad izen aleatorioa",
"index.labelPad": "Pad izena (aukerakoa)",
"index.placeholderPadEnter": "Mesedez sartu pad izen bat...",
"index.createAndShareDocuments": "Sortu eta partekatu dokumentuak denbora errealean",
"index.createAndShareDocumentsDescription": "Etherpadek aukera ematen dizu dokumentuak elkarlanean editatzeko zure nabigatzailean exekutatzen den idazle anitzeko editore bat bezala.",
"pad.toolbar.bold.title": "Lodia (Ctrl+B)",
"pad.toolbar.italic.title": "Etzana (Ctrl+I)",
"pad.toolbar.underline.title": "Azpimarratua (Ctrl+U)",
@ -62,6 +69,7 @@
"pad.toolbar.savedRevision.title": "Gorde berrikuspena",
"pad.toolbar.settings.title": "Ezarpenak",
"pad.toolbar.embed.title": "Partekatu eta Txertatu pad hau",
"pad.toolbar.home.title": "Atzera hasierara",
"pad.toolbar.showusers.title": "Erakutsi pad honetako erabiltzaileak",
"pad.colorpicker.save": "Gorde",
"pad.colorpicker.cancel": "Utzi",
@ -78,6 +86,8 @@
"pad.settings.fontType": "Letra-mota:",
"pad.settings.fontType.normal": "Arrunta",
"pad.settings.language": "Hizkuntza:",
"pad.settings.deletePad": "Ezabatu pad-a",
"pad.delete.confirm": "Benetan pad hau ezabatu nahi duzu?",
"pad.settings.about": "Honi buruz",
"pad.settings.poweredBy": "Honek garatua:",
"pad.importExport.import_export": "Inportatu/Esportatu",

View file

@ -84,8 +84,10 @@
"pad.settings.fontType": "نوع قلم:",
"pad.settings.fontType.normal": "ساده",
"pad.settings.language": "زبان:",
"pad.settings.deletePad": "حذف پد",
"pad.delete.confirm": "آیا واقعاً می‌خواهید این پد را حذف کنید؟",
"pad.settings.about": "درباره",
"pad.settings.poweredBy": "قدرست‌گرفته از",
"pad.settings.poweredBy": "قدرت‌گرفته از",
"pad.importExport.import_export": "درون‌ریزی/برون‌بری",
"pad.importExport.import": "بارگذاری پروندهٔ متنی یا سند",
"pad.importExport.importSuccessful": "موفقیت آمیز بود!",
@ -121,6 +123,9 @@
"pad.modals.deleted": "پاک شد.",
"pad.modals.deleted.explanation": "این دفترچه یادداشت پاک شده است.",
"pad.modals.rateLimited": "نرخ محدود شده است.",
"pad.modals.rateLimited.explanation": "پیام‌های زیادی به این پد فرستادید، بنابراین اتصال شما قطع شد.",
"pad.modals.rejected.explanation": "سرور پیامی را که مرورگرتان فرستاده بود، رد کرد.",
"pad.modals.rejected.cause": "ممکن است سرور هنگام مشاهدهٔ پد به‌روزرسانی شده باشد یا شاید باگی در اترپد وجود داشته باشد. صفحه را دوباره بارگذاری کنید.",
"pad.modals.disconnected": "اتصال شما قطع شده است.",
"pad.modals.disconnected.explanation": "اتصال به سرور قطع شده است.",
"pad.modals.disconnected.cause": "ممکن است سرور در دسترس نباشد. اگر این مشکل باز هم رخ داد مدیر حدمت را آگاه کنید.",
@ -133,6 +138,7 @@
"pad.chat.loadmessages": "بارگیری پیام‌های بیشتر",
"pad.chat.stick.title": "چسباندن چت به صفحه",
"pad.chat.writeMessage.placeholder": "پیام خود را این‌جا بنویسید",
"timeslider.followContents": "پیگیری به‌روزرسانی‌های محتوای پد",
"timeslider.pageTitle": "لغزندهٔ زمان {{appTitle}}",
"timeslider.toolbar.returnbutton": "بازگشت به دفترچه یادداشت",
"timeslider.toolbar.authors": "نویسندگان:",
@ -171,5 +177,6 @@
"pad.impexp.uploadFailed": "آپلود انجام نشد، دوباره تلاش کنید",
"pad.impexp.importfailed": "درون‌ریزی انجام نشد",
"pad.impexp.copypaste": "کپی پیست کنید",
"pad.impexp.exportdisabled": "برون‌ریزی با قالب {{type}} از کار افتاده است. برای جزئیات بیشتر با مدیر سامانه خودتان تماس بگیرید."
"pad.impexp.exportdisabled": "برون‌ریزی با قالب {{type}} از کار افتاده است. برای جزئیات بیشتر با مدیر سامانه خودتان تماس بگیرید.",
"pad.impexp.maxFileSize": "پرونده خیلی بزرگ است. با مدیر سایت تماس بگیرید تا اندازهٔ مجاز برای وارد کردن پرونده را افزایش دهد."
}

View file

@ -55,7 +55,7 @@
"admin_settings.current_save.value": "Tallenna asetukset",
"admin_settings.page-title": "asetukset - Etherpad",
"index.newPad": "Uusi muistio",
"index.createOpenPad": "tai luo tai avaa muistio nimellä:",
"index.createOpenPad": "Avaa muistio nimellä",
"index.openPad": "avaa olemassa oleva muistio nimellä:",
"pad.toolbar.bold.title": "Lihavointi (Ctrl-B)",
"pad.toolbar.italic.title": "Kursivointi (Ctrl-I)",

View file

@ -18,6 +18,7 @@
"Mahabarata",
"Maxim21",
"McDutchie",
"Meaz",
"Metroitendo",
"Od1n",
"Peter17",
@ -66,8 +67,15 @@
"admin_settings.current_save.value": "Enregistrer les paramètres",
"admin_settings.page-title": "Paramètres — Etherpad",
"index.newPad": "Nouveau bloc-notes",
"index.createOpenPad": "ou créer/ouvrir un bloc-notes intitulé:",
"index.createOpenPad": "Ouvrir le bloc-notes par son nom",
"index.openPad": "ouvrir un bloc-note existant avec le nom:",
"index.recentPads": "Bloc-notes récents",
"index.recentPadsEmpty": "Aucun bloc-notes récents trouvés.",
"index.generateNewPad": "Générer un nom de bloc-notes aléatoire",
"index.labelPad": "Nom du bloc-notes (facultatif)",
"index.placeholderPadEnter": "Veuillez saisir un nom de bloc-notes...",
"index.createAndShareDocuments": "Créez et partagez des documents en temps réel",
"index.createAndShareDocumentsDescription": "Etherpad vous permet d'éditer des documents de manière collaborative en temps réel, un peu comme un éditeur multijoueur en direct qui s'exécute dans votre navigateur.",
"pad.toolbar.bold.title": "Gras (Ctrl+B)",
"pad.toolbar.italic.title": "Italique (Ctrl+I)",
"pad.toolbar.underline.title": "Souligné (Ctrl+U)",
@ -106,7 +114,7 @@
"pad.settings.about": "À propos",
"pad.settings.poweredBy": "Propulsé par",
"pad.importExport.import_export": "Importer/Exporter",
"pad.importExport.import": "Charger un texte ou un document",
"pad.importExport.import": "Téléverser un texte ou un document",
"pad.importExport.importSuccessful": "Réussi!",
"pad.importExport.export": "Exporter le bloc-notes actuel en:",
"pad.importExport.exportetherpad": "Etherpad",
@ -125,7 +133,7 @@
"pad.modals.userdup.explanation": "Ce bloc-notes semble être ouvert dans plusieurs fenêtres sur cet ordinateur.",
"pad.modals.userdup.advice": "Se reconnecter en utilisant plutôt cette fenêtre.",
"pad.modals.unauth": "Non autorisé",
"pad.modals.unauth.explanation": "Vos autorisations ont été changées lors de laffichage de cette page. Essayez de vous reconnecter.",
"pad.modals.unauth.explanation": "Vos autorisations ont changées lors de laffichage de cette page. Essayez de vous reconnecter.",
"pad.modals.looping.explanation": "Nous éprouvons des problèmes de communication au serveur de synchronisation.",
"pad.modals.looping.cause": "Il est possible que vous soyez connecté avec un pare-feu ou un mandataire incompatible.",
"pad.modals.initsocketfail": "Le serveur est introuvable.",
@ -139,12 +147,12 @@
"pad.modals.corruptPad.cause": "Cela peut être dû à une mauvaise configuration du serveur ou à un autre comportement inattendu. Veuillez contacter ladministrateur du service.",
"pad.modals.deleted": "Supprimé.",
"pad.modals.deleted.explanation": "Ce bloc-notes a été supprimé.",
"pad.modals.rateLimited": "Flot limité.",
"pad.modals.rateLimited": "Débit limité.",
"pad.modals.rateLimited.explanation": "Vous avez envoyé trop de messages à ce bloc-notes, il vous a donc déconnecté.",
"pad.modals.rejected.explanation": "Le serveur a rejeté un message qui a été envoyé par votre navigateur.",
"pad.modals.rejected.cause": "Le serveur peut avoir été mis à jour pendant que vous regardiez le bloc-notes, ou il y a peut-être une anomalie dans Etherpad. Essayez de recharger la page.",
"pad.modals.disconnected": "Vous avez été déconnecté.",
"pad.modals.disconnected.explanation": "La connexion au serveur a échoué.",
"pad.modals.disconnected.explanation": "La connexion au serveur a échoué",
"pad.modals.disconnected.cause": "Il se peut que le serveur soit indisponible. Si le problème persiste, veuillez en informer ladministrateur du service.",
"pad.share": "Partager ce bloc-notes",
"pad.share.readonly": "Lecture seule",
@ -185,13 +193,13 @@
"pad.savedrevs.timeslider": "Vous pouvez voir les révisions enregistrées en ouvrant lhistorique",
"pad.userlist.entername": "Saisissez votre nom",
"pad.userlist.unnamed": "anonyme",
"pad.editbar.clearcolors": "Effacer le surlignage par auteur dans tout le document? Cette action ne peut être annulée.",
"pad.editbar.clearcolors": "Effacer le surlignage par auteur dans tout le document? Cette action n'est pas réversible.",
"pad.impexp.importbutton": "Importer maintenant",
"pad.impexp.importing": "Importation en cours...",
"pad.impexp.confirmimport": "Importer un fichier écrasera le contenu actuel du bloc-notes. Êtes-vous sûr de vouloir le faire?",
"pad.impexp.convertFailed": "Nous ne pouvons pas importer ce fichier. Veuillez utiliser un autre format de document ou faire manuellement un copier/coller du texte brut",
"pad.impexp.padHasData": "Nous navons pas pu importer ce fichier parce que ce bloc-notes a déjà été modifié; veuillez limporter vers un nouveau bloc-notes.",
"pad.impexp.uploadFailed": "Le téléversement a échoué, veuillez réessayer.",
"pad.impexp.convertFailed": "Nous ne pouvons pas importer ce fichier. Veuillez utiliser un autre format de document ou faire manuellement",
"pad.impexp.padHasData": "Nous navons pas pu importer ce fichier parce que ce bloc-notes a déjà été modifié; veuillez limporter vers un nouveau bloc-notes",
"pad.impexp.uploadFailed": "Le téléversement a échoué, veuillez réessayer",
"pad.impexp.importfailed": "Échec de limport",
"pad.impexp.copypaste": "Veuillez copier-coller",
"pad.impexp.exportdisabled": "Lexportation au format {{type}} est désactivée. Veuillez contacter votre administrateur système pour plus de détails.",

View file

@ -40,8 +40,15 @@
"admin_settings.current_save.value": "Gardar axustes",
"admin_settings.page-title": "Axustes - Etherpad",
"index.newPad": "Novo documento",
"index.createOpenPad": "ou crea/abre un documento co nome:",
"index.openPad": "abrir un Pad existente co nome:",
"index.createOpenPad": "Abrir un documento por nome",
"index.openPad": "abrir un documento existente co nome:",
"index.recentPads": "Documentos recentes",
"index.recentPadsEmpty": "Non se atoparon documentos recentes.",
"index.generateNewPad": "Xerar un nome de documento ao chou",
"index.labelPad": "Nome do documento (opcional)",
"index.placeholderPadEnter": "Escribe un nome para o documento...",
"index.createAndShareDocuments": "Crea e comparte documentos en tempo real",
"index.createAndShareDocumentsDescription": "Etherpad permite editar documentos de forma colaborativa en tempo real, de xeito semellante a un editor multixogador en directo que se executa no navegador.",
"pad.toolbar.bold.title": "Grosa (Ctrl+B)",
"pad.toolbar.italic.title": "Cursiva (Ctrl+I)",
"pad.toolbar.underline.title": "Subliñar (Ctrl+U)",

View file

@ -42,12 +42,19 @@
"admin_settings.current_save.value": "שמירת הגדרות",
"admin_settings.page-title": "הגדרות - Etherpad",
"index.newPad": "פנקס חדש",
"index.createOpenPad": "ליצור או לפתוח פנקס בשם:",
"index.createOpenPad": "פתיחת פנקס לפי שם",
"index.openPad": "פתיחת פנקס קיים עם השם:",
"pad.toolbar.bold.title": "בולט (Ctrl-B)",
"pad.toolbar.italic.title": "נטוי (Ctrl-I)",
"pad.toolbar.underline.title": "קו תחתי (Ctrl-U)",
"pad.toolbar.strikethrough.title": "קו מוחק (Ctrl+5)",
"index.recentPads": "פנקסים אחרונים",
"index.recentPadsEmpty": "לא נמצאו פנקסים אחרונים.",
"index.generateNewPad": "יצירת שם אקראי לפנקס",
"index.labelPad": "שם הפנקס (רשות)",
"index.placeholderPadEnter": "נא למלא שם לפנקס...",
"index.createAndShareDocuments": "יצירה ושיתוף של מסמכים בזמן אמת",
"index.createAndShareDocumentsDescription": "Etherpad מאפשר לך לערוך מסמכים באופן שיתופי בזמן אמת, כמו עורך רב־שחקנים בזמן אחר שרץ בדפדפן שלך.",
"pad.toolbar.bold.title": "מודגש (Ctrl+B)",
"pad.toolbar.italic.title": "נטוי (Ctrl+I)",
"pad.toolbar.underline.title": "קו תחתי (Ctrl+U)",
"pad.toolbar.strikethrough.title": "קו חוצה (Ctrl+5)",
"pad.toolbar.ol.title": "רשימה ממוספרת (Ctrl+Shift+N)",
"pad.toolbar.ul.title": "רשימת תבליטים (Ctrl+Shift+L)",
"pad.toolbar.indent.title": "הזחה (טאב)",
@ -60,6 +67,7 @@
"pad.toolbar.savedRevision.title": "שמירת גרסה",
"pad.toolbar.settings.title": "הגדרות",
"pad.toolbar.embed.title": "שיתוף והטמעה של הפנקס הזה",
"pad.toolbar.home.title": "חזרה לדף הבית",
"pad.toolbar.showusers.title": "הצגת המשתמשים בפנקס הזה",
"pad.colorpicker.save": "שמירה",
"pad.colorpicker.cancel": "ביטול",
@ -69,7 +77,7 @@
"pad.settings.padSettings": "הגדרות פנקס",
"pad.settings.myView": "התצוגה שלי",
"pad.settings.stickychat": "השיחה תמיד על המסך",
"pad.settings.chatandusers": "הצגת צ'אט ומשתמשים",
"pad.settings.chatandusers": "הצגת צ׳אט ומשתמשים",
"pad.settings.colorcheck": "צביעה לפי מחבר",
"pad.settings.linenocheck": "מספרי שורות",
"pad.settings.rtlcheck": "לקרוא את התוכן מימין לשמאל?",

View file

@ -37,33 +37,40 @@
"admin_settings.current_restart.value": "Reinitiar Etherpad",
"admin_settings.current_save.value": "Salveguardar parametros",
"admin_settings.page-title": "Parametros  Etherpad",
"index.newPad": "Nove pad",
"index.createOpenPad": "o crear/aperir un pad con le nomine:",
"index.openPad": "aperir un Pad existente con le nomine:",
"pad.toolbar.bold.title": "Grasse (Ctrl-B)",
"pad.toolbar.italic.title": "Italic (Ctrl-I)",
"pad.toolbar.underline.title": "Sublinear (Ctrl-U)",
"index.newPad": "Nove nota",
"index.createOpenPad": "Aperir le nota con le nomine",
"index.openPad": "aperir un nota existente con le nomine:",
"index.recentPads": "Notas recente",
"index.recentPadsEmpty": "Necun nota recente trovate.",
"index.generateNewPad": "Generar un nomine de nota aleatori",
"index.labelPad": "Nomine del nota (optional)",
"index.placeholderPadEnter": "Per favor insere un nomine de nota…",
"index.createAndShareDocuments": "Crear e condivider documentos in tempore real",
"index.createAndShareDocumentsDescription": "Etherpad permitte modificar documentos de maniera collaborative e in tempore real. Es un editor multi-usator in directo que se executa in tu navigator.",
"pad.toolbar.bold.title": "Grasse (Ctrl+B)",
"pad.toolbar.italic.title": "Italic (Ctrl+I)",
"pad.toolbar.underline.title": "Sublineate (Ctrl+U)",
"pad.toolbar.strikethrough.title": "Barrate (Ctrl+5)",
"pad.toolbar.ol.title": "Lista ordinate (Ctrl+Shift+N)",
"pad.toolbar.ul.title": "Lista non ordinate (Ctrl+Shift+L)",
"pad.toolbar.indent.title": "Indentar (TAB)",
"pad.toolbar.unindent.title": "Disindentar (Shift+TAB)",
"pad.toolbar.undo.title": "Disfacer (Ctrl-Z)",
"pad.toolbar.redo.title": "Refacer (Ctrl-Y)",
"pad.toolbar.undo.title": "Disfacer (Ctrl+Z)",
"pad.toolbar.redo.title": "Refacer (Ctrl+Y)",
"pad.toolbar.clearAuthorship.title": "Rader colores de autor (Ctrl+Shift+C)",
"pad.toolbar.import_export.title": "Importar/exportar in differente formatos de file",
"pad.toolbar.import_export.title": "Importar/exportar inter differente formatos de file",
"pad.toolbar.timeslider.title": "Glissa-tempore",
"pad.toolbar.savedRevision.title": "Version salveguardate",
"pad.toolbar.settings.title": "Configuration",
"pad.toolbar.embed.title": "Condivider e incorporar iste pad",
"pad.toolbar.savedRevision.title": "Salveguardar version",
"pad.toolbar.settings.title": "Parametros",
"pad.toolbar.embed.title": "Condivider e incorporar iste nota",
"pad.toolbar.home.title": "Retro al initio",
"pad.toolbar.showusers.title": "Monstrar le usatores de iste pad",
"pad.toolbar.showusers.title": "Monstrar le usatores de iste nota",
"pad.colorpicker.save": "Salveguardar",
"pad.colorpicker.cancel": "Cancellar",
"pad.loading": "Cargamento…",
"pad.noCookie": "Le cookie non pote esser trovate. Per favor permitte le cookies in tu navigator! Tu session e parametros non essera salveguardate inter visitas. Isto pote esser debite al facto que Etherpad ha essite includite in un iFrame in alcun navigatores. Assecura te que Etherpad es sure le mesme subdominio/dominio que su iFrame parente.",
"pad.permissionDenied": "Tu non ha le permission de acceder a iste pad",
"pad.settings.padSettings": "Configuration del pad",
"pad.noCookie": "Le cookie non pote esser trovate. Per favor permitte le cookies in tu navigator! Tu session e parametros non essera salveguardate inter visitas. In alcun navigatores, isto pote esser debite al facto que Etherpad ha essite includite in un iFrame. Assecura te que Etherpad es sur le mesme subdominio/dominio que su iFrame genitor.",
"pad.permissionDenied": "Tu non ha le permission de acceder a iste nota",
"pad.settings.padSettings": "Parametros del nota",
"pad.settings.myView": "Mi vista",
"pad.settings.stickychat": "Chat sempre visibile",
"pad.settings.chatandusers": "Monstrar chat e usatores",
@ -73,28 +80,28 @@
"pad.settings.fontType": "Typo de litteras:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.language": "Lingua:",
"pad.settings.deletePad": "Deler pad",
"pad.delete.confirm": "Es tu secur de voler deler iste pad?",
"pad.settings.deletePad": "Deler nota",
"pad.delete.confirm": "Es tu secur de voler deler iste nota?",
"pad.settings.about": "A proposito",
"pad.settings.poweredBy": "Actionate per",
"pad.importExport.import_export": "Importar/Exportar",
"pad.importExport.import": "Incargar qualcunque file de texto o documento",
"pad.importExport.importSuccessful": "Succedite!",
"pad.importExport.export": "Exportar le pad actual como:",
"pad.importExport.export": "Exportar le nota actual como:",
"pad.importExport.exportetherpad": "Etherpad",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "Texto simple",
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.abiword.innerHTML": "Tu pote solmente importar files in formato de texto simple o HTML. Pro functionalitate de importation plus extense, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">installa AbiWord o LibreOffice</a>.",
"pad.importExport.abiword.innerHTML": "Es possibile importar solmente le files in formato de texto simple o HTML. Pro functionalitate de importation plus extense, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">installa AbiWord o LibreOffice</a>.",
"pad.modals.connected": "Connectite.",
"pad.modals.reconnecting": "Reconnexion a tu pad…",
"pad.modals.reconnecting": "Reconnexion a tu nota…",
"pad.modals.forcereconnect": "Fortiar reconnexion",
"pad.modals.reconnecttimer": "Tentativa de reconnexion in",
"pad.modals.cancel": "Cancellar",
"pad.modals.userdup": "Aperte in un altere fenestra",
"pad.modals.userdup.explanation": "Iste pad pare esser aperte in plus de un fenestra de navigator in iste computator.",
"pad.modals.userdup.explanation": "Iste nota pare esser aperte in plus de un fenestra de navigator in iste computator.",
"pad.modals.userdup.advice": "Reconnecte pro usar iste fenestra.",
"pad.modals.unauth": "Non autorisate",
"pad.modals.unauth.explanation": "Tu permissiones ha cambiate durante que tu legeva iste pagina. Tenta reconnecter.",
@ -107,38 +114,38 @@
"pad.modals.slowcommit.cause": "Isto pote esser causate per problemas con le connexion al rete.",
"pad.modals.badChangeset.explanation": "Un modification que tu ha facite ha essite classificate como incorrecte per le servitor de synchronisation.",
"pad.modals.badChangeset.cause": "Isto pote esser causate per un configuration incorrecte del servitor o per alcun altere comportamento impreviste. Per favor contacta le administrator del servicio si tu pensa que se tracta de un error. Tenta reconnecter te pro continuar a modificar.",
"pad.modals.corruptPad.explanation": "Le pad al qual tu tenta acceder es corrumpite.",
"pad.modals.corruptPad.explanation": "Le nota al qual tu tenta acceder es corrumpite.",
"pad.modals.corruptPad.cause": "Isto pote esser debite a un configuration incorrecte del servitor o a alcun altere comportamento impreviste. Per favor contacta le administrator del servicio.",
"pad.modals.deleted": "Delite.",
"pad.modals.deleted.explanation": "Iste pad ha essite removite.",
"pad.modals.deleted.explanation": "Iste nota ha essite removite.",
"pad.modals.rateLimited": "Frequentia limitate.",
"pad.modals.rateLimited.explanation": "Tu ha inviate troppo de messages a iste pad, dunque illo te ha disconnectite.",
"pad.modals.rateLimited.explanation": "Tu ha inviate troppo de messages a iste nota, dunque illo te ha disconnectite.",
"pad.modals.rejected.explanation": "Le servitor ha rejectate un message que tu navigator ha inviate.",
"pad.modals.rejected.cause": "Es possibile que le servitor ha essite actualisate durante que tu legeva le pad, o que il ha un falta in Etherpad. Tenta recargar le pagina.",
"pad.modals.rejected.cause": "Es possibile que le servitor ha essite actualisate durante que tu legeva le nota, o que il ha un falta in Etherpad. Tenta recargar le pagina.",
"pad.modals.disconnected": "Tu ha essite disconnectite.",
"pad.modals.disconnected.explanation": "Le connexion al servitor ha essite perdite.",
"pad.modals.disconnected.cause": "Le servitor pote esser indisponibile. Per favor notifica le administrator del servicio si isto continua a producer se.",
"pad.share": "Diffunder iste pad",
"pad.share": "Condivider iste nota",
"pad.share.readonly": "Lectura solmente",
"pad.share.link": "Ligamine",
"pad.share.emebdcode": "Codice de incorporation",
"pad.chat": "Chat",
"pad.chat.title": "Aperir le chat pro iste pad.",
"pad.chat.title": "Aperir le conversation pro iste nota.",
"pad.chat.loadmessages": "Cargar plus messages",
"pad.chat.stick.title": "Ancorar le chat sur le schermo",
"pad.chat.writeMessage.placeholder": "Scribe tu message hic",
"timeslider.followContents": "Sequer le actualisationes de contento del pad",
"timeslider.followContents": "Sequer le nove contento del nota",
"timeslider.pageTitle": "Glissa-tempore pro {{appTitle}}",
"timeslider.toolbar.returnbutton": "Retornar al pad",
"timeslider.toolbar.returnbutton": "Retornar al nota",
"timeslider.toolbar.authors": "Autores:",
"timeslider.toolbar.authorsList": "Nulle autor",
"timeslider.toolbar.exportlink.title": "Exportar",
"timeslider.exportCurrent": "Exportar le version actual como:",
"timeslider.version": "Version {{version}}",
"timeslider.saved": "Salveguardate le {{day}} de {{month}} {{year}}",
"timeslider.playPause": "Reproducer/pausar le contento del pad",
"timeslider.backRevision": "Recular un version in iste pad",
"timeslider.forwardRevision": "Avantiar un version in iste pad",
"timeslider.playPause": "Reproducer/pausar le contento del nota",
"timeslider.backRevision": "Recular un version in iste nota",
"timeslider.forwardRevision": "Avantiar un version in iste nota",
"timeslider.dateformat": "{{year}}-{{month}}-{{day}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "januario",
"timeslider.month.february": "februario",
@ -160,9 +167,9 @@
"pad.editbar.clearcolors": "Rader le colores de autor in tote le documento? Isto non pote esser disfacite",
"pad.impexp.importbutton": "Importar ora",
"pad.impexp.importing": "Importation in curso…",
"pad.impexp.confirmimport": "Le importation de un file superscribera le texto actual del pad. Es tu secur de voler continuar?",
"pad.impexp.convertFailed": "Nos non ha potite importar iste file. Per favor usa un altere formato de documento o copia e colla le texto manualmente.",
"pad.impexp.padHasData": "Nos non ha potite importar iste file perque iste Pad ha jam habite cambiamentos. Per favor importa lo a un nove pad.",
"pad.impexp.confirmimport": "Le importation de un file superscribera le texto actual del nota. Es tu secur de voler continuar?",
"pad.impexp.convertFailed": "Non esseva possibile importar iste file. Per favor usa un altere formato de documento o copia e colla le texto manualmente.",
"pad.impexp.padHasData": "Non es possibile importar iste file perque iste nota ha ja essite modificate. Per favor importa lo in un nove nota.",
"pad.impexp.uploadFailed": "Le incargamento ha fallite. Per favor reproba.",
"pad.impexp.importfailed": "Importation fallite",
"pad.impexp.copypaste": "Per favor copia e colla",

View file

@ -2,6 +2,7 @@
"@metadata": {
"authors": [
"Ajeje Brazorf",
"Albano",
"Beta16",
"Gianfranco",
"Jack",
@ -16,16 +17,44 @@
"admin_plugins": "Gestione plugin",
"admin_plugins.available": "Plugin disponibili",
"admin_plugins.available_not-found": "Nessun plugin trovato.",
"admin_plugins.available_fetching": "Recupero in corso…",
"admin_plugins.available_install.value": "Installa",
"admin_plugins.available_search.placeholder": "Cerca i plugin da installare",
"admin_plugins.description": "Descrizione",
"admin_plugins.installed": "Plugin installati",
"admin_plugins.installed_fetching": "Recupero dei plugin installati…",
"admin_plugins.installed_nothing": "Non hai ancora installato alcun plugin.",
"admin_plugins.installed_uninstall.value": "Disinstalla",
"admin_plugins.last-update": "Ultimo aggiornamento",
"admin_plugins.name": "Nome",
"admin_plugins.page-title": "Gestore dei plugin - Etherpad",
"admin_plugins.version": "Versione",
"admin_plugins_info": "Informazioni sulla risoluzione dei problemi",
"admin_plugins_info.hooks_client": "Hook lato client",
"admin_plugins_info.hooks_server": "Hook lato server",
"admin_plugins_info.parts": "Parti installate",
"admin_plugins_info.plugins": "Plugin installati",
"admin_plugins_info.page-title": "Informazioni sul plugin - Etherpad",
"admin_plugins_info.version": "Versione di Etherpad",
"admin_plugins_info.version_latest": "Ultima versione disponibile",
"admin_plugins_info.version_number": "Numero di versione",
"admin_settings": "Impostazioni",
"admin_settings.current": "Configurazione attuale",
"admin_settings.current_example-devel": "Esempio di modello di impostazioni di sviluppo",
"admin_settings.current_example-prod": "Esempio di modello di impostazioni di produzione",
"admin_settings.current_restart.value": "Riavvia Etherpad",
"admin_settings.current_save.value": "Salva impostazioni",
"admin_settings.page-title": "Impostazioni - Etherpad",
"index.newPad": "Nuovo pad",
"index.createOpenPad": "o crea/apre un pad con il nome:",
"index.createOpenPad": "Apri pad per nome",
"index.openPad": "apri un Pad esistente col nome:",
"index.recentPads": "Pad recenti",
"index.recentPadsEmpty": "Nessun Pad recente trovato.",
"index.generateNewPad": "Genera un nome casuale per il pad",
"index.labelPad": "Nome pad (facoltativo)",
"index.placeholderPadEnter": "Inserisci un nome per il pad...",
"index.createAndShareDocuments": "Crea e condividi documenti in tempo reale",
"index.createAndShareDocumentsDescription": "Etherpad consente di modificare documenti in modo collaborativo e in tempo reale, proprio come un editor multi-player in tempo reale eseguito nel browser.",
"pad.toolbar.bold.title": "Grassetto (Ctrl+B)",
"pad.toolbar.italic.title": "Corsivo (Ctrl+I)",
"pad.toolbar.underline.title": "Sottolineato (Ctrl+U)",
@ -59,6 +88,8 @@
"pad.settings.fontType": "Tipo di carattere:",
"pad.settings.fontType.normal": "Normale",
"pad.settings.language": "Lingua:",
"pad.settings.deletePad": "Elimina Pad",
"pad.delete.confirm": "Vuoi veramente cancellare questo pad?",
"pad.settings.about": "Informazioni",
"pad.settings.poweredBy": "Realizzato con",
"pad.importExport.import_export": "Importazione/esportazione",
@ -95,6 +126,10 @@
"pad.modals.corruptPad.cause": "Ciò potrebbe essere causato da una errata configurazione del server o qualche altro comportamento imprevisto. Si prega di contattare l'amministratore del servizio.",
"pad.modals.deleted": "Cancellato.",
"pad.modals.deleted.explanation": "Questo Pad è stato rimosso.",
"pad.modals.rateLimited": "Limite di richieste.",
"pad.modals.rateLimited.explanation": "Hai inviato troppi messaggi a questo pad e la connessione è stata interrotta.",
"pad.modals.rejected.explanation": "Il server ha rifiutato un messaggio inviato dal tuo browser.",
"pad.modals.rejected.cause": "Il server potrebbe essere stato aggiornato mentre stavi visualizzando il pad, oppure potrebbe esserci un bug in Etherpad. Prova a ricaricare la pagina.",
"pad.modals.disconnected": "Sei stato disconnesso.",
"pad.modals.disconnected.explanation": "La connessione al server è stata persa",
"pad.modals.disconnected.cause": "Il server potrebbe essere non disponibile. Informa l'amministrazione del servizio se il problema persiste.",
@ -107,6 +142,7 @@
"pad.chat.loadmessages": "Carica altri messaggi",
"pad.chat.stick.title": "Ancora chat nello schermo",
"pad.chat.writeMessage.placeholder": "Scrivi il tuo messaggio qui",
"timeslider.followContents": "Segui gli aggiornamenti sui contenuti del pad",
"timeslider.pageTitle": "Cronologia {{appTitle}}",
"timeslider.toolbar.returnbutton": "Ritorna al Pad",
"timeslider.toolbar.authors": "Autori:",

View file

@ -9,6 +9,7 @@
"Revi",
"SeoJeongHo",
"Suleiman the Magnificent Television",
"YeBoy371",
"Ykhwong",
"그냥기여자",
"아라"
@ -42,8 +43,8 @@
"admin_plugins_info.version_number": "버전 번호",
"admin_settings": "설정",
"admin_settings.current": "현재 구성",
"admin_settings.current_example-devel": "예시 개발용 설정 템플릿",
"admin_settings.current_example-prod": "예시 운영용 설정 템플릿",
"admin_settings.current_example-devel": "예시 개발용 설정 ",
"admin_settings.current_example-prod": "예시 운영용 설정 ",
"admin_settings.current_restart.value": "이더패드 다시 시작",
"admin_settings.current_save.value": "설정 저장",
"admin_settings.page-title": "설정 - 이더패드",
@ -66,11 +67,12 @@
"pad.toolbar.savedRevision.title": "판 저장",
"pad.toolbar.settings.title": "설정",
"pad.toolbar.embed.title": "이 패드를 공유하고 포함하기",
"pad.toolbar.home.title": "홈으로 돌아가기",
"pad.toolbar.showusers.title": "이 패드의 사용자 보기",
"pad.colorpicker.save": "저장",
"pad.colorpicker.cancel": "취소",
"pad.loading": "불러오는 중...",
"pad.noCookie": "쿠키를 찾지 못했습니다. 브라우저에서 쿠키를 허용해 주십시오! 세션과 설정은 방문 간 저장되지 않습니다. 일부 브라우저의 iFrame에 이더패드가 포함된 것이 그 이유일 수 있습니다. 이더패드가 부모 iFrame과 동일한 서브도메인/도메인에 위치하는지 확인해 주십시오",
"pad.noCookie": "쿠키를 찾지 못했습니다. 브라우저에서 쿠키를 허용해 주십시오! 세션과 설정은 방문 간 저장되지 않습니다. 일부 브라우저의 iFrame에 이더패드가 포함된 것이 그 이유일 수 있습니다. 이더패드가 상위 iFrame과 동일한 서브도메인/도메인에 위치하는지 확인해 주십시오",
"pad.permissionDenied": "이 패드에 접근할 권한이 없습니다",
"pad.settings.padSettings": "패드 설정",
"pad.settings.myView": "내 보기",
@ -83,8 +85,8 @@
"pad.settings.fontType.normal": "보통",
"pad.settings.language": "언어:",
"pad.settings.deletePad": "패드 삭제",
"pad.delete.confirm": "이 패드를 삭제하겠습니까?",
"pad.settings.about": "소개",
"pad.delete.confirm": "정말로 이 패드를 삭제하겠습니까?",
"pad.settings.about": "정보",
"pad.settings.poweredBy": "제공:",
"pad.importExport.import_export": "가져오기/내보내기",
"pad.importExport.import": "텍스트 파일이나 문서 올리기",
@ -93,11 +95,11 @@
"pad.importExport.exportetherpad": "Etherpad",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "일반 텍스트",
"pad.importExport.exportword": "마이크로소프트 워드",
"pad.importExport.exportword": "Microsoft 워드",
"pad.importExport.exportpdf": "PDF",
"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를 설치</a>하세요.",
"pad.modals.connected": "연결했습니다.",
"pad.modals.connected": "연결.",
"pad.modals.reconnecting": "내 패드에 다시 연결하는 중...",
"pad.modals.forcereconnect": "강제로 다시 연결",
"pad.modals.reconnecttimer": "다시 접속 시도 중",
@ -106,7 +108,7 @@
"pad.modals.userdup.explanation": "이 패드는 이 컴퓨터에 하나 이상의 브라우저 창에서 열린 것 같습니다.",
"pad.modals.userdup.advice": "대신 이 창을 사용해 다시 연결합니다.",
"pad.modals.unauth": "권한이 없음",
"pad.modals.unauth.explanation": "이 페이지를 보는 동안 권한이 바뀌었습니다. 연결을 다시 시도하세요.",
"pad.modals.unauth.explanation": "이 문서를 보는 동안 권한이 바뀌었습니다. 연결을 다시 시도하세요.",
"pad.modals.looping.explanation": "동기화 서버와 통신 문제가 있습니다.",
"pad.modals.looping.cause": "아마 호환되지 않는 방화벽이나 프록시를 통해 연결되어 있습니다.",
"pad.modals.initsocketfail": "서버에 연결할 수 없습니다.",

View file

@ -32,6 +32,7 @@
"pad.toolbar.redo.title": "Widderhuelen (Ctrl-Y)",
"pad.toolbar.savedRevision.title": "Versioun späicheren",
"pad.toolbar.settings.title": "Astellungen",
"pad.toolbar.home.title": "Zréck op d'Haaptsäit",
"pad.toolbar.showusers.title": "Aktuell Benotzer vun dësem Pad uweisen",
"pad.colorpicker.save": "Späicheren",
"pad.colorpicker.cancel": "Ofbriechen",

View file

@ -40,8 +40,15 @@
"admin_settings.current_save.value": "Зачувај нагодувања",
"admin_settings.page-title": "Нагодувања — Etherpad",
"index.newPad": "Нова тетратка",
"index.createOpenPad": "или направете/отворете тетратка со името:",
"index.createOpenPad": "Отвори тетратка по име",
"index.openPad": "отвори постоечка тетратка наречена:",
"index.recentPads": "Скорешни тетратки",
"index.recentPadsEmpty": "Не најдов скорешни тетратки.",
"index.generateNewPad": "Создај случајно име на тетратка",
"index.labelPad": "Име на тетратка (незадолжително)",
"index.placeholderPadEnter": "Внесете го името на тетратката...",
"index.createAndShareDocuments": "Создавајте и споделувајте документи во живо",
"index.createAndShareDocumentsDescription": "Etherpad ви овозможува соработно уредување на документи во живо, слично како уредувачот за повеќе играчи во живо што работи во вашиот пречистувач.",
"pad.toolbar.bold.title": "Задебелено (Ctrl-B)",
"pad.toolbar.italic.title": "Косо (Ctrl-I)",
"pad.toolbar.underline.title": "Подвлечено (Ctrl-U)",

View file

@ -45,7 +45,7 @@
"timeslider.month.april": "ဨပြဳ",
"timeslider.month.may": "မေ",
"timeslider.month.june": "ဂျောန်",
"timeslider.month.july": "ဂျူလာ်",
"timeslider.month.july": "ဂျူလာ်",
"timeslider.month.august": "အဝ်ဂေတ်",
"timeslider.month.september": "\nသေပ်တေမ်ပါ",
"timeslider.month.october": "\nအံက်တ်ပါ",

View file

@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"ABPMAB",
"Dutchy45",
"Klaas van Buiten",
"KlaasZ4usV",
@ -17,65 +18,73 @@
"woeterman94"
]
},
"admin.page-title": "Admin Dashboard - Etherpad",
"admin_plugins": "Plugin Beheer",
"admin_plugins.available": "Beschikbare plugins",
"admin_plugins.available_not-found": "Geen plugins gevonden.",
"admin.page-title": "Beheerdashboard Etherpad",
"admin_plugins": "Beheer plug-ins",
"admin_plugins.available": "Beschikbare plug-ins",
"admin_plugins.available_not-found": "Geen plug-ins gevonden.",
"admin_plugins.available_fetching": "Ophalen…",
"admin_plugins.available_install.value": "Installeren",
"admin_plugins.available_search.placeholder": "Zoek achter plugins om te installeren",
"admin_plugins.available_search.placeholder": "Zoeken naar plug-ins om te installeren",
"admin_plugins.description": "Beschrijving",
"admin_plugins.installed": "Geïnstalleerd plugins",
"admin_plugins.installed_fetching": "Geïnstalleerd plugins worden opgehaald…",
"admin_plugins.installed_nothing": "Je hebt nog geen plugins geïnstalleerd.",
"admin_plugins.installed": "Geïnstalleerde plug-ins",
"admin_plugins.installed_fetching": "Geïnstalleerd plug-ins worden opgehaald…",
"admin_plugins.installed_nothing": "U hebt nog geen plug-ins geïnstalleerd.",
"admin_plugins.installed_uninstall.value": "Verwijderen",
"admin_plugins.last-update": "Laatste update",
"admin_plugins.last-update": "Laatst bijgewerkt",
"admin_plugins.name": "Naam",
"admin_plugins.page-title": "Plugin beheer - Etherpad",
"admin_plugins.page-title": "Beheer plug-ins Etherpad",
"admin_plugins.version": "Versie",
"admin_plugins_info": "Probleemoplossingsinformatie",
"admin_plugins_info.hooks": "Geïnstalleerde hooks",
"admin_plugins_info.hooks_client": "Client-side hooks",
"admin_plugins_info.hooks_server": "Server-side hooks",
"admin_plugins_info.parts": "Geïnstalleerde onderdelen",
"admin_plugins_info.plugins": "Geïnstalleerde plugins",
"admin_plugins_info.page-title": "Plugin info - Etherpad",
"admin_plugins_info.version": "Etherpad versie",
"admin_plugins_info.plugins": "Geïnstalleerde plug-ins",
"admin_plugins_info.page-title": "Plug-in-informatie Etherpad",
"admin_plugins_info.version": "Versie van Etherpad",
"admin_plugins_info.version_latest": "Meest recente versie",
"admin_plugins_info.version_number": "Versie nummer",
"admin_plugins_info.version_number": "Versienummer",
"admin_settings": "Instellingen",
"admin_settings.current": "Huidige configuratie",
"admin_settings.current_example-devel": "Voorbeeldsjabloon voor ontwikkelingsinstellingen",
"admin_settings.current_example-prod": "Productie instellingen template",
"admin_settings.current_example-prod": "Voorbeeldsjabloon voor productie-instellingen",
"admin_settings.current_restart.value": "Herstart Etherpad",
"admin_settings.current_save.value": "Bewaar instellingen",
"admin_settings.page-title": "Instellingen - Etherpad",
"index.newPad": "Nieuw pad",
"index.createOpenPad": "of maak/open een pad met de naam:",
"index.openPad": "open een bestaande Pad met de naam:",
"pad.toolbar.bold.title": "Vet (Ctrl-B)",
"pad.toolbar.italic.title": "Cursief (Ctrl-I)",
"pad.toolbar.underline.title": "Onderstrepen (Ctrl-U)",
"index.newPad": "Nieuwe notitie",
"index.createOpenPad": "Open een notitie met de naam",
"index.openPad": "open een bestaande notitie met de naam:",
"index.recentPads": "Recente notities",
"index.recentPadsEmpty": "Geen recente notities gevonden.",
"index.generateNewPad": "Genereer willekeurige notitienaam",
"index.labelPad": "Notitienaam (optioneel)",
"index.placeholderPadEnter": "Voer de naam van een notitie in…",
"index.createAndShareDocuments": "Maak en deel documenten in realtime",
"index.createAndShareDocumentsDescription": "Met Etherpad kunt u in samenwerking met anderen tegelijkertijd hetzelfde document bewerken. Het is zoals een live multiplayer-editor die in uw browser draait.",
"pad.toolbar.bold.title": "Vet (Ctrl+B)",
"pad.toolbar.italic.title": "Cursief (Ctrl+I)",
"pad.toolbar.underline.title": "Onderstrepen (Ctrl+U)",
"pad.toolbar.strikethrough.title": "Doorhalen (Ctrl+5)",
"pad.toolbar.ol.title": "Geordende lijst (Ctrl+Shift+N)",
"pad.toolbar.ul.title": "Ongeordende lijst (Ctrl+Shift+L)",
"pad.toolbar.indent.title": "Inspringen (Tab)",
"pad.toolbar.unindent.title": "Uitspringen (Shift+Tab)",
"pad.toolbar.undo.title": "Ongedaan maken (Ctrl-Z)",
"pad.toolbar.redo.title": "Opnieuw uitvoeren (Ctrl-Y)",
"pad.toolbar.indent.title": "Inspringen (TAB)",
"pad.toolbar.unindent.title": "Uitspringen (Shift+TAB)",
"pad.toolbar.undo.title": "Ongedaan maken (Ctrl+Z)",
"pad.toolbar.redo.title": "Opnieuw uitvoeren (Ctrl+Y)",
"pad.toolbar.clearAuthorship.title": "Kleuren auteurs wissen (Ctrl+Shift+C)",
"pad.toolbar.import_export.title": "Naar/van andere opmaak exporteren/importeren",
"pad.toolbar.timeslider.title": "Tijdlijn",
"pad.toolbar.savedRevision.title": "Versie opslaan",
"pad.toolbar.settings.title": "Instellingen",
"pad.toolbar.embed.title": "Pad delen en insluiten",
"pad.toolbar.showusers.title": "Gebruikers van dit pad weergeven",
"pad.toolbar.embed.title": "Deze notitie delen en insluiten",
"pad.toolbar.home.title": "Terug naar de startpagina",
"pad.toolbar.showusers.title": "Gebruikers van deze notitie weergeven",
"pad.colorpicker.save": "Opslaan",
"pad.colorpicker.cancel": "Annuleren",
"pad.loading": "Bezig met laden…",
"pad.noCookie": "Er kon geen cookie gevonden worden. Zorg ervoor dat uw browser cookies accepteert. Uw sessie en instellingen worden tussen bezoeken niet opgeslagen. Dit kan te wijten zijn aan het feit dat Etherpad in sommige browsers wordt opgenomen in een iFrame. Zorg ervoor dat Etherpad zich op hetzelfde subdomein/domein bevindt als het bovenliggende iFrame.",
"pad.permissionDenied": "U hebt geen rechten om deze pad te bekijken",
"pad.settings.padSettings": "Padinstellingen",
"pad.permissionDenied": "U hebt geen toestemming om deze notitie te openen",
"pad.settings.padSettings": "Notitie-instellingen",
"pad.settings.myView": "Mijn overzicht",
"pad.settings.stickychat": "Chat altijd zichtbaar",
"pad.settings.chatandusers": "Chat en gebruikers weergeven",
@ -85,72 +94,72 @@
"pad.settings.fontType": "Lettertype:",
"pad.settings.fontType.normal": "Normaal",
"pad.settings.language": "Taal:",
"pad.settings.deletePad": "Pad verwijderen",
"pad.delete.confirm": "Wilt u dit pad echt verwijderen?",
"pad.settings.deletePad": "Notitie verwijderen",
"pad.delete.confirm": "Wilt u deze notitie echt verwijderen?",
"pad.settings.about": "Over",
"pad.settings.poweredBy": "Aangedreven door",
"pad.settings.poweredBy": "Mogelijk gemaakt door",
"pad.importExport.import_export": "Importeren/exporteren",
"pad.importExport.import": "Tekstbestand of document uploaden",
"pad.importExport.importSuccessful": "Afgerond",
"pad.importExport.export": "Huidige pad exporteren als",
"pad.importExport.importSuccessful": "Gelukt!",
"pad.importExport.export": "Huidige notitie exporteren als:",
"pad.importExport.exportetherpad": "Etherpad",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "Tekst zonder opmaak",
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "Pdf",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.abiword.innerHTML": "U kunt alleen importeren vanuit tekst zonder opmaak of met HTML-opmaak. <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">Installeer AbiWord of LibreOffice</a> om meer geavanceerde importmogelijkheden te krijgen.",
"pad.modals.connected": "Verbonden.",
"pad.modals.reconnecting": "Opnieuw verbinding maken met uw pad…",
"pad.modals.reconnecting": "De verbinding met uw notitie wordt hersteld…",
"pad.modals.forcereconnect": "Opnieuw verbinden",
"pad.modals.reconnecttimer": "Proberen te verbinden over",
"pad.modals.reconnecttimer": "Nieuwe verbindingspoging over",
"pad.modals.cancel": "Annuleren",
"pad.modals.userdup": "In een ander venster geopend",
"pad.modals.userdup.explanation": "Dit pad is meer dan één keer geopend in een browservenster op deze computer.",
"pad.modals.userdup.explanation": "Deze notitie is blijkbaar in meer dan één browservenster op deze computer geopend.",
"pad.modals.userdup.advice": "Maak opnieuw verbinding als u dit venster wilt gebruiken.",
"pad.modals.unauth": "Niet toegestaan",
"pad.modals.unauth.explanation": "Uw rechten zijn gewijzigd terwijl u de pagina aan het bekijken was. Probeer opnieuw te verbinden.",
"pad.modals.looping.explanation": "Er is een probleem opgetreden tijdens de communicatie met de synchronisatieserver.",
"pad.modals.looping.cause": "Mogelijk gebruikt de server een niet compatibele firewall of proxy server.",
"pad.modals.looping.cause": "Mogelijk hebt u verbinding gemaakt via een niet-compatibele firewall of proxy.",
"pad.modals.initsocketfail": "De server is niet bereikbaar.",
"pad.modals.initsocketfail.explanation": "Het was niet mogelijk te verbinden met de synchronisatieserver.",
"pad.modals.initsocketfail.cause": "Mogelijk komt dit door uw browser of internetverbinding.",
"pad.modals.initsocketfail.explanation": "Er kon geen verbinding worden gemaakt met de synchronisatieserver.",
"pad.modals.initsocketfail.cause": "Dit komt waarschijnlijk door een probleem met uw browser of uw internetverbinding.",
"pad.modals.slowcommit.explanation": "De server reageert niet.",
"pad.modals.slowcommit.cause": "Dit komt mogelijk door netwerkproblemen.",
"pad.modals.badChangeset.explanation": "Een door u gemaakte bewerking is door de synchronisatieserver als incorrect aangemerkt.",
"pad.modals.badChangeset.cause": "Dit kan komen door een onjuiste serverinstelling of door ander onverwacht gedrag. Neem contact op met de servicebeheerder als u denkt dat er een onverwachte uitkomst is. Probeer opnieuw te verbinden om door te gaan met bewerken.",
"pad.modals.corruptPad.explanation": "Het pad dat u wilt openen is beschadigd.",
"pad.modals.badChangeset.cause": "Dit kan komen door een onjuiste serverinstelling of door ander onverwacht gedrag. Neem contact op met de servicebeheerder als u denkt dat dit een fout is. Probeer opnieuw te verbinden om door te gaan met bewerken.",
"pad.modals.corruptPad.explanation": "De notitie die u wilt openen is beschadigd.",
"pad.modals.corruptPad.cause": "Dit kan komen door een onjuiste serverinstelling of door ander onverwacht gedrag. Neem contact op met de servicebeheerder.",
"pad.modals.deleted": "Verwijderd.",
"pad.modals.deleted.explanation": "Dit pad is verwijderd.",
"pad.modals.deleted.explanation": "Deze notitie is verwijderd.",
"pad.modals.rateLimited": "Snelheid begrensd.",
"pad.modals.rateLimited.explanation": "Je hebt te veel berichten naar deze pad gestuurd. Daarom is je verbinding verbroken.",
"pad.modals.rejected.explanation": "De server heeft een bericht afgewezen dat door je browser is verzonden.",
"pad.modals.rejected.cause": "Mogelijks is de server bijgewerkt terwijl je de pad aan het bekijken was. Of misschien is er een bug in Etherpad. Probeer de pagina opnieuw te laden.",
"pad.modals.rateLimited.explanation": "U hebt te veel berichten naar deze notitie gestuurd. Daarom is uw verbinding verbroken.",
"pad.modals.rejected.explanation": "De server heeft een bericht verworpen dat door uw browser is verzonden.",
"pad.modals.rejected.cause": "Mogelijk is de server bijgewerkt terwijl u de notitie aan het bekijken was. Of misschien is er een bug in Etherpad. Probeer de pagina opnieuw te laden.",
"pad.modals.disconnected": "Uw verbinding is verbroken.",
"pad.modals.disconnected.explanation": "De verbinding met de server is verbroken",
"pad.modals.disconnected.cause": "De server is mogelijk niet beschikbaar. Stel de servicebeheerder op de hoogte.",
"pad.share": "Pad delen",
"pad.share.readonly": "Alleen-lezen",
"pad.modals.disconnected.cause": "De server is mogelijk niet beschikbaar. Stel de servicebeheerder op de hoogte als dit probleem aanhoudt.",
"pad.share": "Deze notitie delen",
"pad.share.readonly": "Alleen lezen",
"pad.share.link": "Koppeling",
"pad.share.emebdcode": "URL insluiten",
"pad.share.emebdcode": "URL voor insluiten",
"pad.chat": "Chatten",
"pad.chat.title": "Chat voor dit pad opnenen",
"pad.chat.title": "De chat voor deze notitie openen.",
"pad.chat.loadmessages": "Meer berichten laden",
"pad.chat.stick.title": "Chat op scherm vastzetten",
"pad.chat.writeMessage.placeholder": "Schrijf je bericht hier",
"timeslider.followContents": "Volg de inhoudelijke updates van de pad",
"pad.chat.writeMessage.placeholder": "Schrijf uw bericht hier",
"timeslider.followContents": "Volg het bijwerken van de inhoud van deze notitie",
"timeslider.pageTitle": "Tijdlijn voor {{appTitle}}",
"timeslider.toolbar.returnbutton": "Terug naar pad",
"timeslider.toolbar.returnbutton": "Terug naar notitie",
"timeslider.toolbar.authors": "Auteurs:",
"timeslider.toolbar.authorsList": "Geen auteurs",
"timeslider.toolbar.exportlink.title": "Exporteren",
"timeslider.exportCurrent": "Huidige versie exporteren als:",
"timeslider.version": "Versie {{version}}",
"timeslider.saved": "Opgeslagen op {{day}} {{month}} {{year}}",
"timeslider.playPause": "Padinhoud afspelen of pauzeren",
"timeslider.backRevision": "Een versie teruggaan voor deze pad",
"timeslider.forwardRevision": "Een versie vooruit gaan voor deze pad",
"timeslider.playPause": "Notitie-inhoud afspelen of pauzeren",
"timeslider.backRevision": "Een versie teruggaan in deze notitie",
"timeslider.forwardRevision": "Een versie vooruit gaan in deze notitie",
"timeslider.dateformat": "{{year}}-{{month}}-{{day}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "januari",
"timeslider.month.february": "februari",
@ -166,18 +175,18 @@
"timeslider.month.december": "december",
"timeslider.unnamedauthors": "{{num}} onbekende {[plural(num) one: auteur, other: auteurs ]}",
"pad.savedrevs.marked": "Deze versie is nu gemarkeerd als opgeslagen versie",
"pad.savedrevs.timeslider": "U kunt opgeslagen versies bekijken via de tijdschuiver.",
"pad.savedrevs.timeslider": "U kunt opgeslagen versies bekijken via de tijdlijn",
"pad.userlist.entername": "Geef uw naam op",
"pad.userlist.unnamed": "zonder naam",
"pad.editbar.clearcolors": "Auteurskleuren voor het hele document wissen? Dit kan je niet ongedaan maken",
"pad.editbar.clearcolors": "Auteurskleuren voor het hele document wissen? Dit kan niet ongedaan worden gemaakt.",
"pad.impexp.importbutton": "Nu importeren",
"pad.impexp.importing": "Bezig met importeren…",
"pad.impexp.confirmimport": "Door een bestand te importeren overschrijft u de huidige tekst van de pad. Wilt u echt doorgaan?",
"pad.impexp.confirmimport": "Door een bestand te importeren overschrijft u de huidige tekst van de notitie. Wilt u echt doorgaan?",
"pad.impexp.convertFailed": "Het was niet mogelijk dit bestand te importeren. Gebruik een andere documentopmaak of kopieer en plak de inhoud handmatig",
"pad.impexp.padHasData": "Het was niet mogelijk dit bestand te importeren omdat er al wijzigingen aan de etherpad zijn gemaakt. Importeer naar een nieuwe etherpad.",
"pad.impexp.padHasData": "Het was niet mogelijk dit bestand te importeren omdat er al wijzigingen aan de notitie zijn aangebracht. Importeer in een nieuwe notitie.",
"pad.impexp.uploadFailed": "Het uploaden is mislukt. Probeer het opnieuw",
"pad.impexp.importfailed": "Importeren is mislukt",
"pad.impexp.copypaste": "Gebruik kopiëren en plakken",
"pad.impexp.exportdisabled": "Exporteren als {{type}} is uitgeschakeld. Neem contact op met de systeembeheerder voor details.",
"pad.impexp.maxFileSize": "Het bestand is te groot. Neem contact op met je sitebeheerder om de toegestane bestandsgrootte voor importeren te vergroten."
"pad.impexp.exportdisabled": "Het exporteren in de indeling {{type}} is uitgeschakeld. Neem contact op met de systeembeheerder voor details.",
"pad.impexp.maxFileSize": "Het bestand is te groot. Neem contact op met uw sitebeheerder om de toegestane bestandsgrootte voor importeren te vergroten."
}

View file

@ -38,8 +38,15 @@
"admin_settings.current_save.value": "Argistré ij paràmeter",
"admin_settings.page-title": "Paràmeter - Etherpad",
"index.newPad": "Feuj neuv",
"index.createOpenPad": "o creé/duverté un feuj antitolà:",
"index.createOpenPad": "Duverté ël blochèt con sò nòm",
"index.openPad": "duverté un Pad esistent con ël nòm:",
"index.recentPads": "Blochèt recent",
"index.recentPadsEmpty": "Gnun blochèt recent trovà.",
"index.generateNewPad": "Generé un nòm ëd blochèt a l'ancàpit",
"index.labelPad": "Nòm dël blochèt (facoltativ)",
"index.placeholderPadEnter": "Për piasì, ch'a anserissa un nòm ëd blochèt...",
"index.createAndShareDocuments": "Creé e partagé dij document an temp real",
"index.createAndShareDocumentsDescription": "Etherpad a-j përmet ëd modifiché dij document an manera colaborativa an temp real, un pò coma n'editor multi-giugador che a marcia an sò navigador.",
"pad.toolbar.bold.title": "Grassèt (Ctrl+B)",
"pad.toolbar.italic.title": "Corsiv (Ctrl+I)",
"pad.toolbar.underline.title": "Sotlignà (Ctrl+U)",

View file

@ -6,7 +6,7 @@
]
},
"index.newPad": "نوې ليکچه",
"index.createOpenPad": "يا په همدې نوم يوه نوې ليکچه جوړول/پرانېستل:",
"index.createOpenPad": "ليکچه د نوم له مخې پرانېستل",
"pad.toolbar.bold.title": "زغرد (Ctrl-B)",
"pad.toolbar.italic.title": "رېوند (Ctrl-I)",
"pad.toolbar.underline.title": "لرکرښن (Ctrl+U)",

View file

@ -53,8 +53,15 @@
"admin_settings.current_save.value": "Сохранить настройки",
"admin_settings.page-title": "Настройки — Etherpad",
"index.newPad": "Создать",
"index.createOpenPad": "или создать/открыть документ с именем:",
"index.createOpenPad": "Открыть документ по имени",
"index.openPad": "откройте существующий документ с именем:",
"index.recentPads": "Последние документы",
"index.recentPadsEmpty": "Свежих документов не найдено.",
"index.generateNewPad": "Создать случайное имя документа",
"index.labelPad": "Название документа (необязательно)",
"index.placeholderPadEnter": "Введите название документа…",
"index.createAndShareDocuments": "Создать и поделиться документами в режиме реального времени",
"index.createAndShareDocumentsDescription": "Etherpad позволяет вам совместно редактировать документы в режиме реального времени, подобно многопользовательскому редактору, работающему в вашем браузере.",
"pad.toolbar.bold.title": "Полужирный (Ctrl-B)",
"pad.toolbar.italic.title": "Курсив (Ctrl-I)",
"pad.toolbar.underline.title": "подчёркивание (Ctrl-U)",

View file

@ -4,7 +4,8 @@
"Besnik b",
"Eraldkerciku",
"Kosovastar",
"Liridon"
"Liridon",
"Xhulianoo"
]
},
"admin.page-title": "Pult Përgjegjësi - Etherpad",
@ -78,7 +79,7 @@
"pad.settings.about": "Mbi",
"pad.settings.poweredBy": "Bazuar në",
"pad.importExport.import_export": "Import/Eksport",
"pad.importExport.import": "Ngarkoni cilëndo kartelë tekst ose dokument",
"pad.importExport.import": "Ngarko çdo skedar teksti ose dokument",
"pad.importExport.importSuccessful": "Me sukses!",
"pad.importExport.export": "Eksportojeni bllokun e tanishëm si:",
"pad.importExport.exportetherpad": "Etherpad",

View file

@ -3,7 +3,8 @@
"authors": [
"Andibecker",
"Edwingudfriend",
"Muddyb"
"Muddyb",
"Samuel Kiongo"
]
},
"admin.page-title": "Dashibodi ya Usimamizi - Etherpad",
@ -42,6 +43,7 @@
"index.newPad": "Pad Mpya",
"index.createOpenPad": "au tunga/fungua Pad yenye jina:",
"index.openPad": "fungua Pad iliyopo na jina:",
"index.placeholderPadEnter": "Tafadhali weka jina la mtumiaji.",
"pad.toolbar.bold.title": "Koozesha (Ctrl+B)",
"pad.toolbar.italic.title": "Mlalo (Ctrl+I)",
"pad.toolbar.underline.title": "Pigia mstari (Ctrl+U)",

View file

@ -59,8 +59,15 @@
"admin_settings.current_save.value": "保存设置",
"admin_settings.page-title": "设置 - Etherpad",
"index.newPad": "新记事本",
"index.createOpenPad": "或创建/打开以下名称的记事本:",
"index.createOpenPad": "按名称打开记事本",
"index.openPad": "打开一个现有的记事本,名称为:",
"index.recentPads": "最近的记事本",
"index.recentPadsEmpty": "未找到最近的记事本。",
"index.generateNewPad": "生成随机记事本名称",
"index.labelPad": "记事本名称(可选)",
"index.placeholderPadEnter": "输入记事本名称…",
"index.createAndShareDocuments": "实时创建和共享文档",
"index.createAndShareDocumentsDescription": "Etherpad允许您实时协作编辑文档就像在浏览器中运行的实时多人编辑器一样。",
"pad.toolbar.bold.title": "粗体Ctrl-B",
"pad.toolbar.italic.title": "斜体Ctrl-I",
"pad.toolbar.underline.title": "下划线Ctrl-U",
@ -77,6 +84,7 @@
"pad.toolbar.savedRevision.title": "保存修订",
"pad.toolbar.settings.title": "设置",
"pad.toolbar.embed.title": "共享并嵌入此记事本",
"pad.toolbar.home.title": "返回首页",
"pad.toolbar.showusers.title": "显示此记事本上的用户",
"pad.colorpicker.save": "保存",
"pad.colorpicker.cancel": "取消",

View file

@ -48,8 +48,15 @@
"admin_settings.current_save.value": "儲存設定",
"admin_settings.page-title": "設定 - Etherpad",
"index.newPad": "新記事本",
"index.createOpenPad": "或建立/開啟以下名稱的記事本:",
"index.createOpenPad": "依照名稱開啟記事本",
"index.openPad": "開啟一個現有的記事本,名稱為:",
"index.recentPads": "近期記事本",
"index.recentPadsEmpty": "找不到近期的記事本。",
"index.generateNewPad": "產生隨機記事本名稱",
"index.labelPad": "記事本名稱(可選)",
"index.placeholderPadEnter": "輸入記事本名稱…",
"index.createAndShareDocuments": "即時建立和共享文件",
"index.createAndShareDocumentsDescription": "Etherpad 允許您即時協作編輯文件,就像在瀏覽器中運作的即時多人編輯器一樣。",
"pad.toolbar.bold.title": "粗體Ctrl+B",
"pad.toolbar.italic.title": "斜體Ctrl+I",
"pad.toolbar.underline.title": "底線Ctrl+U",

View file

@ -26,7 +26,7 @@ import {Attribute} from "../../static/js/types/Attribute";
const CustomError = require('../utils/customError');
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const readOnlyManager = require('./ReadOnlyManager');
import readOnlyManager from './ReadOnlyManager';
const groupManager = require('./GroupManager');
const authorManager = require('./AuthorManager');
const sessionManager = require('./SessionManager');

View file

@ -21,8 +21,8 @@
* limitations under the License.
*/
import {Database} from 'ueberdb2';
const settings = require('../utils/Settings');
import {Database, DatabaseType} from 'ueberdb2';
import settings from '../utils/Settings';
import log4js from 'log4js';
const stats = require('../stats')
@ -37,7 +37,7 @@ exports.db = null;
* Initializes the database with the settings provided by the settings module
*/
exports.init = async () => {
exports.db = new Database(settings.dbType, settings.dbSettings, null, logger);
exports.db = new Database(settings.dbType as DatabaseType, settings.dbSettings, null, logger);
await exports.db.init();
if (exports.db.metrics != null) {
for (const [metric, value] of Object.entries(exports.db.metrics)) {

View file

@ -14,18 +14,17 @@ import AttributePool from '../../static/js/AttributePool';
const Stream = require('../utils/Stream');
const assert = require('assert').strict;
const db = require('./DB');
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
const authorManager = require('./AuthorManager');
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const groupManager = require('./GroupManager');
const CustomError = require('../utils/customError');
const readOnlyManager = require('./ReadOnlyManager');
const randomString = require('../utils/randomstring');
import readOnlyManager from './ReadOnlyManager';
import randomString from '../utils/randomstring';
const hooks = require('../../static/js/pluginfw/hooks');
import pad_utils from "../../static/js/pad_utils";
import {SmartOpAssembler} from "../../static/js/SmartOpAssembler";
import {} from '../utils/promises';
import {timesLimit} from "async";
/**

View file

@ -25,7 +25,7 @@ import {PadType} from "../types/PadType";
const CustomError = require('../utils/customError');
const Pad = require('../db/Pad');
const db = require('./DB');
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
/**
* A cache of all loaded Pads.

View file

@ -21,7 +21,7 @@
const db = require('./DB');
const randomString = require('../utils/randomstring');
import randomString from '../utils/randomstring';
/**
@ -29,14 +29,14 @@ const randomString = require('../utils/randomstring');
* @param {String} id the pad's id
* @return {Boolean} true if the id is readonly
*/
exports.isReadOnlyId = (id:string) => id.startsWith('r.');
const isReadOnlyId = (id:string) => id.startsWith('r.');
/**
* returns a read only id for a pad
* @param {String} padId the id of the pad
* @return {String} the read only id
*/
exports.getReadOnlyId = async (padId:string) => {
const getReadOnlyId = async (padId:string) => {
// check if there is a pad2readonly entry
let readOnlyId = await db.get(`pad2readonly:${padId}`);
@ -57,19 +57,29 @@ exports.getReadOnlyId = async (padId:string) => {
* @param {String} readOnlyId read only id
* @return {String} the padId
*/
exports.getPadId = async (readOnlyId:string) => await db.get(`readonly2pad:${readOnlyId}`);
const getPadId = async (readOnlyId:string) => await db.get(`readonly2pad:${readOnlyId}`);
/**
* returns the padId and readonlyPadId in an object for any id
* @param {String} id read only id or real pad id
* @return {Object} an object with the padId and readonlyPadId
*/
exports.getIds = async (id:string) => {
const readonly = exports.isReadOnlyId(id);
const getIds = async (id:string) => {
const readonly = isReadOnlyId(id);
// Might be null, if this is an unknown read-only id
const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
const padId = readonly ? await exports.getPadId(id) : id;
const readOnlyPadId = readonly ? id : await getReadOnlyId(id);
const padId = readonly ? await getPadId(id) : id;
return {readOnlyPadId, padId, readonly};
};
export default {
isReadOnlyId,
getReadOnlyId,
getPadId,
getIds,
// Export for testing purposes
__getReadOnlyId: getReadOnlyId, // eslint-disable-line no-underscore-dangle
__getPadId: getPadId, // eslint-disable-line no-underscore-dangle
}

View file

@ -24,9 +24,9 @@ import {UserSettingsObject} from "../types/UserSettingsObject";
const authorManager = require('./AuthorManager');
const hooks = require('../../static/js/pluginfw/hooks');
const padManager = require('./PadManager');
const readOnlyManager = require('./ReadOnlyManager');
import readOnlyManager from './ReadOnlyManager';
const sessionManager = require('./SessionManager');
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
const webaccess = require('../hooks/express/webaccess');
const log4js = require('log4js');
const authLogger = log4js.getLogger('auth');

View file

@ -22,7 +22,7 @@
const CustomError = require('../utils/customError');
import {firstSatisfies} from '../utils/promises';
const randomString = require('../utils/randomstring');
import randomString from '../utils/randomstring';
const db = require('./DB');
const groupManager = require('./GroupManager');
const authorManager = require('./AuthorManager');

View file

@ -20,12 +20,13 @@
* require("./index").require("./path/to/template.ejs")
*/
const ejs = require('ejs');
const fs = require('fs');
import ejs from 'ejs';
import fs from 'fs';
const hooks = require('../../static/js/pluginfw/hooks');
const path = require('path');
const resolve = require('resolve');
const settings = require('../utils/Settings');
import path from 'node:path';
// @ts-ignore
import resolve from 'resolve';
import settings from '../utils/Settings';
import {pluginInstallPath} from '../../static/js/pluginfw/installer'
const templateCache = new Map();

View file

@ -23,7 +23,7 @@ import {MapArrayType} from "../types/MapType";
import { jwtDecode } from "jwt-decode";
const api = require('../db/API');
const padManager = require('../db/PadManager');
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
import createHTTPError from 'http-errors';
import {Http2ServerRequest} from "node:http2";
import {publicKeyExported} from "../security/OAuth2Provider";
@ -183,7 +183,7 @@ exports.handle = async function (apiVersion: string, functionName: string, field
throw new createHTTPError.Unauthorized('no or wrong API Key');
}
try {
const clientIds: string[] = settings.sso.clients?.map((client: {client_id: string}) => client.client_id);
const clientIds: string[] = settings.sso.clients?.map((client: {client_id: string}) => client.client_id) ?? [];
const jwtToCheck = req.headers.authorization.replace("Bearer ", "")
const payload = jwtDecode(jwtToCheck)
// client_credentials

View file

@ -1,9 +1,9 @@
const absolutePaths = require('../utils/AbsolutePaths');
import * as absolutePaths from '../utils/AbsolutePaths';
import fs from 'fs';
import log4js from 'log4js';
const randomString = require('../utils/randomstring');
const argv = require('../utils/Cli').argv;
const settings = require('../utils/Settings');
import randomString from '../utils/randomstring';
import {argv} from '../utils/Cli'
import settings from '../utils/Settings';
const apiHandlerLogger = log4js.getLogger('APIHandler');

View file

@ -24,7 +24,7 @@ const exporthtml = require('../utils/ExportHtml');
const exporttxt = require('../utils/ExportTxt');
const exportEtherpad = require('../utils/ExportEtherpad');
import fs from 'fs';
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
import os from 'os';
const hooks = require('../../static/js/pluginfw/hooks');
import util from 'util';

View file

@ -25,7 +25,7 @@ const padManager = require('../db/PadManager');
const padMessageHandler = require('./PadMessageHandler');
import {promises as fs} from 'fs';
import path from 'path';
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
const {Formidable} = require('formidable');
import os from 'os';
const importHtml = require('../utils/ImportHtml');

View file

@ -29,8 +29,12 @@ import AttributePool from '../../static/js/AttributePool';
const AttributeManager = require('../../static/js/AttributeManager');
const authorManager = require('../db/AuthorManager');
import padutils from '../../static/js/pad_utils';
const readOnlyManager = require('../db/ReadOnlyManager');
const settings = require('../utils/Settings');
import readOnlyManager from '../db/ReadOnlyManager';
import settings, {
exportAvailable,
abiwordAvailable,
sofficeAvailable
} from '../utils/Settings';
const securityManager = require('../db/SecurityManager');
const plugins = require('../../static/js/pluginfw/plugin_defs');
import log4js from 'log4js';
@ -1021,9 +1025,9 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
serverTimestamp: Date.now(),
sessionRefreshInterval: settings.cookie.sessionRefreshInterval,
userId: sessionInfo.author,
abiwordAvailable: settings.abiwordAvailable(),
sofficeAvailable: settings.sofficeAvailable(),
exportAvailable: settings.exportAvailable(),
abiwordAvailable: abiwordAvailable(),
sofficeAvailable: sofficeAvailable(),
exportAvailable: exportAvailable(),
plugins: {
plugins: plugins.plugins,
parts: plugins.parts,

View file

@ -8,7 +8,7 @@ const apiHandler = require('./APIHandler')
import {serve, setup} from 'swagger-ui-express'
import express from "express";
const settings = require('../utils/Settings')
import settings from '../utils/Settings';
type RestAPIMapping = {

View file

@ -22,8 +22,8 @@
import {MapArrayType} from "../types/MapType";
import {SocketModule} from "../types/SocketModule";
const log4js = require('log4js');
const settings = require('../utils/Settings');
import log4js from 'log4js';
import settings from '../utils/Settings';
const stats = require('../../node/stats')
const logger = log4js.getLogger('socket.io');

View file

@ -12,7 +12,7 @@ import fs from 'fs';
const hooks = require('../../static/js/pluginfw/hooks');
import log4js from 'log4js';
const SessionStore = require('../db/SessionStore');
const settings = require('../utils/Settings');
import settings, {getEpVersion, getGitCommit} from '../utils/Settings';
const stats = require('../stats')
import util from 'util';
const webaccess = require('./express/webaccess');
@ -67,9 +67,9 @@ const closeServer = async () => {
exports.createServer = async () => {
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
serverName = `Etherpad ${getGitCommit()} (https://etherpad.org)`;
console.log(`Your Etherpad version is ${settings.getEpVersion()} (${settings.getGitCommit()})`);
console.log(`Your Etherpad version is ${getEpVersion()} (${getGitCommit()})`);
await exports.restartServer();
@ -176,7 +176,7 @@ exports.restartServer = async () => {
// starts listening to requests as reported in issue #158. Not installing the log4js connect
// logger when the log level has a higher severity than INFO since it would not log at that level
// anyway.
if (!(settings.loglevel === 'WARN' && settings.loglevel === 'ERROR')) {
if (!(settings.loglevel === 'WARN' || settings.loglevel === 'ERROR')) {
app.use(log4js.connectLogger(logger, {
level: log4js.levels.DEBUG.levelStr,
format: ':status, :method :url',
@ -189,7 +189,12 @@ exports.restartServer = async () => {
secretRotator = new SecretRotator(
'expressSessionSecrets', keyRotationInterval, sessionLifetime, settings.sessionKey);
await secretRotator.start();
secret = secretRotator.secrets;
const secrets = secretRotator.secrets;
if (Array.isArray(secrets)) {
secret = secrets[0];
} else {
secret = secretRotator.secrets as unknown as string;
}
}
if (!secret) throw new Error('missing cookie signing secret');
@ -206,7 +211,7 @@ exports.restartServer = async () => {
// cleaner :)
name: 'express_sid',
cookie: {
maxAge: sessionLifetime || null, // Convert 0 to null.
maxAge: sessionLifetime || undefined, // Convert 0 to null.
sameSite: settings.cookie.sameSite,
// The automatic express-session mechanism for determining if the application is being served

View file

@ -2,10 +2,9 @@
import {ArgsExpressType} from "../../types/ArgsExpressType";
import path from "path";
import fs from "fs";
import * as url from "node:url";
import {MapArrayType} from "../../types/MapType";
const settings = require('ep_etherpad-lite/node/utils/Settings');
import settings from 'ep_etherpad-lite/node/utils/Settings';
const ADMIN_PATH = path.join(settings.root, 'src', 'templates');
const PROXY_HEADER = "x-proxy-path"
@ -22,7 +21,7 @@ exports.expressCreateServer = (hookName: string, args: ArgsExpressType, cb: Func
console.error('admin template not found, skipping admin interface. You need to rebuild it in /admin with pnpm run build-copy')
return cb();
}
args.app.get('/admin/*', (req: any, res: any) => {
args.app.get('/admin/{*filename}', (req: any, res: any) => {
// extract URL path
let pathname = path.join(ADMIN_PATH, req.url);
pathname = path.normalize(pathname)

View file

@ -2,18 +2,16 @@
import {PadQueryResult, PadSearchQuery} from "../../types/PadSearchQuery";
import {PadType} from "../../types/PadType";
import log4js from 'log4js';
const eejs = require('../../eejs');
const fsp = require('fs').promises;
const hooks = require('../../../static/js/pluginfw/hooks');
const plugins = require('../../../static/js/pluginfw/plugins');
const settings = require('../../utils/Settings');
const UpdateCheck = require('../../utils/UpdateCheck');
import settings, {getEpVersion, getGitCommit, reloadSettings} from '../../utils/Settings';
import {getLatestVersion} from '../../utils/UpdateCheck';
const padManager = require('../../db/PadManager');
const api = require('../../db/API');
const cleanup = require('../../utils/Cleanup');
import {deleteRevisions} from '../../utils/Cleanup';
const queryPadLimit = 12;
@ -75,8 +73,8 @@ exports.socketio = (hookName: string, {io}: any) => {
socket.on('help', () => {
const gitCommit = settings.getGitCommit();
const epVersion = settings.getEpVersion();
const gitCommit = getGitCommit();
const epVersion = getEpVersion();
const hooks: Map<string, Map<string, string>> = plugins.getHooks('hooks', false);
const clientHooks: Map<string, Map<string, string>> = plugins.getHooks('client_hooks', false);
@ -100,7 +98,7 @@ exports.socketio = (hookName: string, {io}: any) => {
installedParts: plugins.getParts(),
installedServerHooks: mapToObject(hooks),
installedClientHooks: mapToObject(clientHooks),
latestVersion: UpdateCheck.getLatestVersion(),
latestVersion: getLatestVersion(),
})
});
@ -265,7 +263,7 @@ exports.socketio = (hookName: string, {io}: any) => {
if (padExists) {
logger.info(`Cleanup pad revisions: ${padId}`);
try {
const result = await cleanup.deleteRevisions(padId, settings.cleanup.keepRevisions)
const result = await deleteRevisions(padId, settings.cleanup.keepRevisions)
if (result) {
socket.emit('results:cleanupPadRevisions', {
padId: padId,
@ -289,7 +287,7 @@ exports.socketio = (hookName: string, {io}: any) => {
socket.on('restartServer', async () => {
logger.info('Admin request to restart server through a socket on /admin/settings');
settings.reloadSettings();
reloadSettings();
await plugins.update();
await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('restartServer');

View file

@ -3,11 +3,11 @@
import {ArgsExpressType} from "../../types/ArgsExpressType";
const hasPadAccess = require('../../padaccess');
const settings = require('../../utils/Settings');
import settings, {exportAvailable} from '../../utils/Settings';
const exportHandler = require('../../handler/ExportHandler');
const importHandler = require('../../handler/ImportHandler');
const padManager = require('../../db/PadManager');
const readOnlyManager = require('../../db/ReadOnlyManager');
import readOnlyManager from '../../db/ReadOnlyManager';
const rateLimit = require('express-rate-limit');
const securityManager = require('../../db/SecurityManager');
const webaccess = require('./webaccess');
@ -25,8 +25,8 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio
});
// handle export requests
args.app.use('/p/:pad/:rev?/export/:type', limiter);
args.app.get('/p/:pad/:rev?/export/:type', (req:any, res:any, next:Function) => {
args.app.use('/p/:pad{/:rev}/export/:type', limiter);
args.app.get('/p/:pad{/:rev}/export/:type', (req:any, res:any, next:Function) => {
(async () => {
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
// send a 404 if we don't support this filetype
@ -35,7 +35,7 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio
}
// if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.exportAvailable() === 'no' &&
if (exportAvailable() === 'no' &&
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
' There is no converter configured');

View file

@ -24,9 +24,9 @@ const cloneDeep = require('lodash.clonedeep');
const createHTTPError = require('http-errors');
const apiHandler = require('../../handler/APIHandler');
const settings = require('../../utils/Settings');
import settings from '../../utils/Settings';
const log4js = require('log4js');
import log4js from 'log4js';
const logger = log4js.getLogger('API');
// https://github.com/OAI/OpenAPI-Specification/tree/master/schemas/v3.0

View file

@ -1,5 +1,5 @@
import {ArgsExpressType} from "../../types/ArgsExpressType";
const settings = require('../../utils/Settings');
import settings from '../../utils/Settings';
const pwa = {
name: settings.title || "Etherpad",

View file

@ -6,7 +6,7 @@ import events from 'events';
const express = require('../express');
import log4js from 'log4js';
const proxyaddr = require('proxy-addr');
const settings = require('../../utils/Settings');
import settings from '../../utils/Settings';
import {Server, Socket} from 'socket.io'
const socketIORouter = require('../../handler/SocketIORouter');
const hooks = require('../../../static/js/pluginfw/hooks');

View file

@ -6,7 +6,7 @@ import fs from 'node:fs';
const fsp = fs.promises;
const toolbar = require('../../utils/toolbar');
const hooks = require('../../../static/js/pluginfw/hooks');
const settings = require('../../utils/Settings');
import settings, {getEpVersion} from '../../utils/Settings';
import util from 'node:util';
const webaccess = require('./webaccess');
const plugins = require('../../../static/js/pluginfw/plugin_defs');
@ -20,14 +20,14 @@ exports.socketio = (hookName: string, {io}: any) => {
}
exports.expressPreSession = async (hookName:string, {app, settings}:ArgsExpressType) => {
exports.expressPreSession = async (hookName:string, {app}:ArgsExpressType) => {
// This endpoint is intended to conform to:
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
app.get('/health', (req:any, res:any) => {
res.set('Content-Type', 'application/health+json');
res.json({
status: 'pass',
releaseId: settings.getEpVersion(),
releaseId: getEpVersion(),
});
});
@ -43,6 +43,10 @@ exports.expressPreSession = async (hookName:string, {app, settings}:ArgsExpressT
});
app.get('/robots.txt', (req:any, res:any) => {
if (!settings.skinName) {
// if no skin is set, send the default robots.txt
return res.sendFile(path.join(settings.root, 'src', 'static', 'robots.txt'));
}
let filePath =
path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
res.sendFile(filePath, (err:any) => {
@ -68,9 +72,9 @@ exports.expressPreSession = async (hookName:string, {app, settings}:ArgsExpressT
const fns = [
...(settings.favicon ? [path.resolve(settings.root, settings.favicon)] : []),
path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'),
settings.skinName && path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'),
path.join(settings.root, 'src', 'static', 'favicon.ico'),
];
].filter(f=>f != null);
for (const fn of fns) {
try {
await fsp.access(fn, fs.constants.R_OK);
@ -178,7 +182,8 @@ const handleLiveReload = async (args: ArgsExpressType, padString: string, timeSl
req,
toolbar,
isReadOnly,
entrypoint: '/watch/pad?hash=' + hash
entrypoint: '/watch/pad?hash=' + hash,
settings: settings.getPublicSettings()
})
res.send(content);
})
@ -207,7 +212,8 @@ const handleLiveReload = async (args: ArgsExpressType, padString: string, timeSl
req,
toolbar,
isReadOnly,
entrypoint: '/watch/timeslider?hash=' + hash
entrypoint: '/watch/timeslider?hash=' + hash,
settings: settings.getPublicSettings()
})
res.send(content);
})
@ -334,7 +340,8 @@ exports.expressCreateServer = async (_hookName: string, args: ArgsExpressType, c
req,
toolbar,
isReadOnly,
entrypoint: "../"+fileNamePad
entrypoint: "../"+fileNamePad,
settings: settings.getPublicSettings()
})
res.send(content);
});
@ -348,7 +355,8 @@ exports.expressCreateServer = async (_hookName: string, args: ArgsExpressType, c
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
req,
toolbar,
entrypoint: "../../"+fileNameTimeSlider
entrypoint: "../../"+fileNameTimeSlider,
settings: settings.getPublicSettings()
}));
});
} else {

View file

@ -6,8 +6,9 @@ import {PartType} from "../../types/PartType";
const fs = require('fs').promises;
import {minify} from '../../utils/Minify';
import path from 'node:path';
import {ArgsExpressType} from "../../types/ArgsExpressType";
const plugins = require('../../../static/js/pluginfw/plugin_defs');
const settings = require('../../utils/Settings');
import settings from '../../utils/Settings';
// Rewrite tar to include modules with no extensions and proper rooted paths.
const getTar = async () => {
@ -30,16 +31,16 @@ const getTar = async () => {
return tar;
};
exports.expressPreSession = async (hookName:string, {app}:any) => {
exports.expressPreSession = async (hookName:string, {app}:ArgsExpressType) => {
// Minify will serve static files compressed (minify enabled). It also has
// file-specific hacks for ace/require-kernel/etc.
app.all('/static/:filename(*)', minify);
app.all('/static/*filename', minify);
// serve plugin definitions
// not very static, but served here so that client can do
// require("pluginfw/static/js/plugin-definitions.js");
app.get('/pluginfw/plugin-definitions.json', (req: any, res:any, next:Function) => {
app.get('/pluginfw/plugin-definitions.json', (_req, res) => {
const clientParts = plugins.parts.filter((part: PartType) => part.client_hooks != null);
const clientPlugins:MapArrayType<string> = {};
for (const name of new Set(clientParts.map((part: PartType) => part.plugin))) {

View file

@ -1,83 +0,0 @@
'use strict';
import {Dirent} from "node:fs";
import {PluginDef} from "../../types/PartType";
const path = require('path');
const fsp = require('fs').promises;
const plugins = require('../../../static/js/pluginfw/plugin_defs');
const sanitizePathname = require('../../utils/sanitizePathname');
const settings = require('../../utils/Settings');
// Returns all *.js files under specDir (recursively) as relative paths to specDir, using '/'
// instead of path.sep to separate pathname components.
const findSpecs = async (specDir: string) => {
let dirents: Dirent[];
try {
dirents = await fsp.readdir(specDir, {withFileTypes: true});
} catch (err:any) {
if (['ENOENT', 'ENOTDIR'].includes(err.code)) return [];
throw err;
}
const specs: string[] = [];
await Promise.all(dirents.map(async (dirent) => {
if (dirent.isDirectory()) {
const subdirSpecs = await findSpecs(path.join(specDir, dirent.name));
specs.push(...subdirSpecs.map((spec) => `${dirent.name}/${spec}`));
return;
}
if (!dirent.name.endsWith('.js')) return;
specs.push(dirent.name);
}));
return specs;
};
exports.expressPreSession = async (hookName:string, {app}:any) => {
app.get('/tests/frontend/frontendTestSpecs.json', (req:any, res:any, next:Function) => {
(async () => {
const modules:string[] = [];
await Promise.all(Object.entries(plugins.plugins).map(async ([plugin, def]) => {
let {package: {path: pluginPath}} = def as PluginDef;
if (!pluginPath.endsWith(path.sep)) pluginPath += path.sep;
const specDir = `${plugin === 'ep_etherpad-lite' ? '' : 'static/'}tests/frontend/specs`;
for (const spec of await findSpecs(path.join(pluginPath, specDir))) {
if (plugin === 'ep_etherpad-lite' && !settings.enableAdminUITests &&
spec.startsWith('admin')) continue;
modules.push(`${plugin}/${specDir}/${spec.replace(/\.js$/, '')}`);
}
}));
// Sort plugin tests before core tests.
modules.sort((a, b) => {
a = String(a);
b = String(b);
const aCore = a.startsWith('ep_etherpad-lite/');
const bCore = b.startsWith('ep_etherpad-lite/');
if (aCore === bCore) return a.localeCompare(b);
return aCore ? 1 : -1;
});
console.debug('Sent browser the following test spec modules:', modules);
res.json(modules);
})().catch((err) => next(err || new Error(err)));
});
const rootTestFolder = path.join(settings.root, 'src/tests/frontend/');
app.get('/tests/frontend/index.html', (req:any, res:any) => {
res.redirect(['./', ...req.url.split('?').slice(1)].join('?'));
});
// The regexp /[\d\D]{0,}/ is equivalent to the regexp /.*/. The Express route path used here
// uses the more verbose /[\d\D]{0,}/ pattern instead of /.*/ because path-to-regexp v0.1.7 (the
// version used with Express v4.x) interprets '.' and '*' differently than regexp.
app.get('/tests/frontend/:file([\\d\\D]{0,})', (req:any, res:any, next:Function) => {
(async () => {
let file = sanitizePathname(req.params.file);
if (['', '.', './'].includes(file)) file = 'index.html';
res.sendFile(path.join(rootTestFolder, file));
})().catch((err) => next(err || new Error(err)));
});
app.get('/tests/frontend', (req:any, res:any) => {
res.redirect(['./frontend/', ...req.url.split('?').slice(1)].join('?'));
});
};

View file

@ -6,9 +6,9 @@ import {SocketClientRequest} from "../../types/SocketClientRequest";
import {WebAccessTypes} from "../../types/WebAccessTypes";
import {SettingsUser} from "../../types/SettingsUser";
const httpLogger = log4js.getLogger('http');
const settings = require('../../utils/Settings');
import settings from '../../utils/Settings';
const hooks = require('../../../static/js/pluginfw/hooks');
const readOnlyManager = require('../../db/ReadOnlyManager');
import readOnlyManager from '../../db/ReadOnlyManager';
hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';

View file

@ -4,12 +4,12 @@ import type {MapArrayType} from "../types/MapType";
import {I18nPluginDefs} from "../types/I18nPluginDefs";
const languages = require('languages4translatewiki');
const fs = require('fs');
const path = require('path');
const _ = require('underscore');
import fs from 'fs';
import path from 'path';
import _ from 'underscore';
const pluginDefs = require('../../static/js/pluginfw/plugin_defs');
import existsSync from '../utils/path_exists';
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
// returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...}
@ -73,7 +73,7 @@ const getAllLocales = () => {
'for Customization for Administrators, under Localization.');
if (settings.customLocaleStrings) {
if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
_.each(settings.customLocaleStrings, (overrides:MapArrayType<string> , langcode:string) => {
_.each(settings.customLocaleStrings, (overrides , langcode) => {
if (typeof overrides !== 'object') throw wrongFormatErr;
_.each(overrides, (localeString:string|object, key:string) => {
if (typeof localeString !== 'string') throw wrongFormatErr;

View file

@ -1,14 +1,13 @@
import {ArgsExpressType} from "../types/ArgsExpressType";
import Provider, {Account, Configuration} from 'oidc-provider';
import {generateKeyPair, exportJWK, KeyLike} from 'jose'
import {generateKeyPair, exportJWK, CryptoKey} from 'jose'
import MemoryAdapter from "./OIDCAdapter";
import path from "path";
const settings = require('../utils/Settings');
import settings from '../utils/Settings';
import {IncomingForm} from 'formidable'
import express, {Request, Response} from 'express';
import express from 'express';
import {format} from 'url'
import {ParsedUrlQuery} from "node:querystring";
import {Http2ServerRequest, Http2ServerResponse} from "node:http2";
import {MapArrayType} from "../types/MapType";
const configuration: Configuration = {
@ -64,14 +63,16 @@ const configuration: Configuration = {
};
export let publicKeyExported: KeyLike|null
export let privateKeyExported: KeyLike|null
export let publicKeyExported: CryptoKey|null
export let privateKeyExported: CryptoKey|null
/*
This function is used to initialize the OAuth2 provider
*/
export const expressCreateServer = async (hookName: string, args: ArgsExpressType, cb: Function) => {
const {privateKey, publicKey} = await generateKeyPair('RS256');
const {privateKey, publicKey} = await generateKeyPair('RS256', {
extractable: true
});
const privateKeyJWK = await exportJWK(privateKey);
publicKeyExported = publicKey
privateKeyExported = privateKey
@ -137,7 +138,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
} else if (token.kind === "ClientCredentials") {
let extraParams: MapArrayType<string> = {}
settings.sso.clients
settings.sso.clients && settings.sso.clients
.filter((client:any) => client.client_id === token.clientId)
.forEach((client:any) => {
if(client.extraParams !== undefined) {

View file

@ -29,7 +29,7 @@ import pkg from '../package.json';
import {checkForMigration} from "../static/js/pluginfw/installer";
import axios from "axios";
const settings = require('./utils/Settings');
import settings from './utils/Settings';
let wtfnode: any;
if (settings.dumpOnUncleanExit) {
@ -68,11 +68,11 @@ if (process.env['https_proxy']) {
* early check for version compatibility before calling
* any modules that require newer versions of NodeJS
*/
const NodeVersion = require('./utils/NodeVersion');
NodeVersion.enforceMinNodeVersion(pkg.engines.node.replace(">=", ""));
NodeVersion.checkDeprecationStatus(pkg.engines.node.replace(">=", ""), '2.1.0');
import {enforceMinNodeVersion, checkDeprecationStatus} from './utils/NodeVersion';
enforceMinNodeVersion(pkg.engines.node.replace(">=", ""));
checkDeprecationStatus(pkg.engines.node.replace(">=", ""), '2.1.0');
const UpdateCheck = require('./utils/UpdateCheck');
import {check} from './utils/UpdateCheck';
const db = require('./db/DB');
const express = require('./hooks/express');
const hooks = require('../static/js/pluginfw/hooks');
@ -128,8 +128,8 @@ exports.start = async () => {
startDoneGate = new Gate();
state = State.STARTING;
try {
// Check if Etherpad version is up-to-date
UpdateCheck.check();
// Check if the Etherpad version is up to date
check();
// @ts-ignore
stats.gauge('memoryUsage', () => process.memoryUsage().rss);

View file

@ -1,9 +1,10 @@
import {Express} from "express";
import {MapArrayType} from "./MapType";
import {SettingsType} from "../utils/Settings";
export type ArgsExpressType = {
app:Express,
io: any,
server:any
settings: MapArrayType<any>
settings: SettingsType
}

View file

@ -24,7 +24,7 @@ import {AsyncQueueTask} from "../types/AsyncQueueTask";
const spawn = require('child_process').spawn;
const async = require('async');
const settings = require('./Settings');
import settings from './Settings';
const os = require('os');
// on windows we have to spawn a process for each convertion,

View file

@ -18,9 +18,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const log4js = require('log4js');
const path = require('path');
const _ = require('underscore');
import log4js from 'log4js';
import path from 'path';
import _ from 'underscore';
const absPathLogger = log4js.getLogger('AbsolutePaths');
@ -74,7 +74,7 @@ const popIfEndsWith = (stringArray: string[], lastDesiredElements: string[]): st
* @return {string} The identified absolute base path. If such path cannot be
* identified, prints a log and exits the application.
*/
exports.findEtherpadRoot = () => {
export const findEtherpadRoot = () => {
if (etherpadRoot != null) {
return etherpadRoot;
}
@ -130,12 +130,12 @@ exports.findEtherpadRoot = () => {
* it is returned unchanged. Otherwise it is interpreted
* relative to exports.root.
*/
exports.makeAbsolute = (somePath: string) => {
export const makeAbsolute = (somePath: string) => {
if (path.isAbsolute(somePath)) {
return somePath;
}
const rewrittenPath = path.join(exports.findEtherpadRoot(), somePath);
const rewrittenPath = path.join(findEtherpadRoot(), somePath);
absPathLogger.debug(`Relative path "${somePath}" can be rewritten to "${rewrittenPath}"`);
return rewrittenPath;
@ -149,7 +149,7 @@ exports.makeAbsolute = (somePath: string) => {
* a subdirectory of the base one
* @return {boolean}
*/
exports.isSubdir = (parent: string, arbitraryDir: string): boolean => {
export const isSubdir = (parent: string, arbitraryDir: string): boolean => {
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
const relative = path.relative(parent, arbitraryDir);
return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);

View file

@ -3,15 +3,16 @@
import {AChangeSet} from "../types/PadType";
import {Revision} from "../types/Revision";
const promises = require('./promises');
import {timesLimit, firstSatisfies} from './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');
import log4js from 'log4js';
const logger = log4js.getLogger('cleanup');
exports.deleteAllRevisions = async (padID: string): Promise<void> => {
export const deleteAllRevisions = async (padID: string): Promise<void> => {
const randomPadId = padID + 'aertdfdf' + Math.random().toString(10)
@ -39,7 +40,7 @@ const createRevision = async (aChangeset: AChangeSet, timestamp: number, isKeyRe
};
}
exports.deleteRevisions = async (padId: string, keepRevisions: number): Promise<boolean> => {
export const deleteRevisions = async (padId: string, keepRevisions: number): Promise<boolean> => {
logger.debug('Start cleanup revisions', padId)
@ -61,14 +62,14 @@ exports.deleteRevisions = async (padId: string, keepRevisions: number): Promise<
const revisions: Revision[] = [];
await promises.timesLimit(keepRevisions + 1, 500, async (i: number) => {
await 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 timesLimit(pad.head + 1, 500, async (i: string) => {
await db.remove(`pad:${padId}:revs:${i}`, null);
});
@ -105,7 +106,7 @@ exports.deleteRevisions = async (padId: string, keepRevisions: number): Promise<
p.push(db.set(`pad:${padId}:revs:0`, revision))
p.push(promises.timesLimit(keepRevisions, 500, async (i: number) => {
p.push(timesLimit(keepRevisions, 500, async (i: number) => {
const rev = i + cleanupUntilRevision + 1
const newRev = rev - cleanupUntilRevision;
@ -135,7 +136,7 @@ exports.deleteRevisions = async (padId: string, keepRevisions: number): Promise<
return true
}
exports.checkTodos = async () => {
export const checkTodos = async () => {
await new Promise(resolve => setTimeout(resolve, 5000));
// TODO: Move to settings
@ -156,7 +157,7 @@ exports.checkTodos = async () => {
}
try {
const result = await exports.deleteRevisions(padId, settings.keepRevisions)
const result = await deleteRevisions(padId, settings.keepRevisions)
if (result) {
logger.info('successful cleaned up pad: ', padId)
}

View file

@ -21,32 +21,33 @@
*/
// An object containing the parsed command-line options
exports.argv = {};
const argv = process.argv.slice(2);
export const argv: Record<string, string> = {};
const argvInternal = process.argv.slice(2);
let arg, prevArg;
// Loop through args
for (let i = 0; i < argv.length; i++) {
arg = argv[i];
for (let i = 0; i < argvInternal.length; i++) {
arg = argvInternal[i];
// Override location of settings.json file
if (prevArg === '--settings' || prevArg === '-s') {
exports.argv.settings = arg;
if (prevArg && prevArg === '--settings' || prevArg === '-s') {
argv.settings = arg;
}
// Override location of credentials.json file
if (prevArg === '--credentials') {
if (prevArg && prevArg === '--credentials') {
exports.argv.credentials = arg;
}
// Override location of settings.json file
if (prevArg === '--sessionkey') {
if (prevArg && prevArg === '--sessionkey') {
exports.argv.sessionkey = arg;
}
// Override location of APIKEY.txt file
if (prevArg === '--apikey') {
if (prevArg && prevArg === '--apikey') {
exports.argv.apikey = arg;
}

View file

@ -23,7 +23,7 @@ const log4js = require('log4js');
const os = require('os');
const path = require('path');
const runCmd = require('./run_cmd');
const settings = require('./Settings');
import settings from './Settings';
const logger = log4js.getLogger('LibreOffice');

View file

@ -26,7 +26,7 @@ import mime from 'mime-types';
import log4js from 'log4js';
import {compressCSS, compressJS} from './MinifyWorker'
const settings = require('./Settings');
import settings from './Settings';
import {promises as fs} from 'fs';
import path from 'node:path';
const plugins = require('../../static/js/pluginfw/plugin_defs');
@ -146,7 +146,7 @@ const compatPaths = {
* @param res the Express response
*/
const _minify = async (req:any, res:any) => {
let filename = req.params.filename;
let filename = req.params.filename.join('/');
try {
filename = sanitizePathname(filename);
} catch (err) {

View file

@ -26,7 +26,7 @@ const semver = require('semver');
*
* @param {String} minNodeVersion Minimum required Node version
*/
exports.enforceMinNodeVersion = (minNodeVersion: string) => {
export const enforceMinNodeVersion = (minNodeVersion: string) => {
const currentNodeVersion = process.version;
// we cannot use template literals, since we still do not know if we are
@ -49,7 +49,7 @@ exports.enforceMinNodeVersion = (minNodeVersion: string) => {
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated
* Node releases
*/
exports.checkDeprecationStatus = (lowestNonDeprecatedNodeVersion: string, epRemovalVersion:Function) => {
export const checkDeprecationStatus = (lowestNonDeprecatedNodeVersion: string, epRemovalVersion: string) => {
const currentNodeVersion = process.version;
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
'use strict';
const semver = require('semver');
const settings = require('./Settings');
import semver from 'semver';
import settings, {getEpVersion} from './Settings';
import axios from 'axios';
const headers = {
'User-Agent': 'Etherpad/' + settings.getEpVersion(),
'User-Agent': 'Etherpad/' + getEpVersion(),
}
type Infos = {
@ -37,15 +37,15 @@ const loadEtherpadInformations = () => {
}
exports.getLatestVersion = () => {
exports.needsUpdate().catch();
export const getLatestVersion = () => {
needsUpdate().catch();
return infos?.latestVersion;
};
exports.needsUpdate = async (cb?: Function) => {
const needsUpdate = async (cb?: Function) => {
try {
const info = await loadEtherpadInformations()
if (semver.gt(info!.latestVersion, settings.getEpVersion())) {
if (semver.gt(info!.latestVersion, getEpVersion())) {
if (cb) return cb(true);
}
} catch (err) {
@ -54,10 +54,10 @@ exports.needsUpdate = async (cb?: Function) => {
}
};
exports.check = () => {
exports.needsUpdate((needsUpdate: boolean) => {
export const check = () => {
needsUpdate((needsUpdate: boolean) => {
if (needsUpdate) {
console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
}
});
}).then(()=>{});
};

View file

@ -1,10 +1,9 @@
'use strict';
/**
* Generates a random String with the given length. Is needed to generate the
* Author, Group, readonly, session Ids
*/
const cryptoMod = require('crypto');
import cryptoMod from 'crypto';
const randomString = (len: number) => cryptoMod.randomBytes(len).toString('hex');
module.exports = randomString;
export default randomString;

View file

@ -5,10 +5,10 @@ import {ChildProcess} from "node:child_process";
import {PromiseWithStd} from "../types/PromiseWithStd";
import {Readable} from "node:stream";
const spawn = require('cross-spawn');
const log4js = require('log4js');
const path = require('path');
const settings = require('./Settings');
import spawn from 'cross-spawn';
import log4js from 'log4js';
import path from 'path';
import settings from './Settings';
const logger = log4js.getLogger('runCmd');
@ -123,7 +123,7 @@ module.exports = exports = (args: string[], opts:RunCMDOptions = {}) => {
// process's `exit` handler so that we get a useful stack trace.
const procFailedErr: Error & ErrorExtended = new Error();
const proc: ChildProcess = spawn(args[0], args.slice(1), opts);
const proc: ChildProcess = spawn(args[0], args.slice(1), opts as any);
const streams:[undefined, Readable|null, Readable|null] = [undefined, proc.stdout, proc.stderr];
let px: { reject: any; resolve: any; };

View file

@ -31,20 +31,19 @@
],
"dependencies": {
"async": "^3.2.6",
"axios": "^1.10.0",
"axios": "^1.11.0",
"cookie-parser": "^1.4.7",
"cross-env": "^7.0.3",
"cross-env": "^10.0.0",
"cross-spawn": "^7.0.6",
"ejs": "^3.1.10",
"esbuild": "^0.25.8",
"express": "4.21.2",
"express-rate-limit": "^8.0.0",
"esbuild": "^0.25.9",
"express": "^5.1.0",
"express-rate-limit": "^8.0.1",
"express-session": "^1.18.2",
"fast-deep-equal": "^3.1.3",
"find-root": "1.1.0",
"formidable": "^3.5.4",
"http-errors": "^2.0.0",
"jose": "^5.10.0",
"jose": "^6.0.13",
"js-cookie": "^3.0.5",
"jsdom": "^26.1.0",
"jsonminify": "0.4.2",
@ -57,10 +56,10 @@
"lru-cache": "^11.1.0",
"measured-core": "^2.0.0",
"mime-types": "^3.0.1",
"oidc-provider": "^9.3.0",
"openapi-backend": "^5.13.0",
"oidc-provider": "^9.4.2",
"openapi-backend": "^5.15.0",
"proxy-addr": "^2.0.7",
"rate-limiter-flexible": "^7.1.1",
"rate-limiter-flexible": "^7.2.0",
"rehype": "^13.0.2",
"rehype-minify-whitespace": "^6.0.2",
"resolve": "1.22.10",
@ -69,11 +68,11 @@
"semver": "^7.7.2",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
"superagent": "10.2.2",
"superagent": "10.2.3",
"swagger-ui-express": "^5.0.1",
"tinycon": "0.6.8",
"tsx": "4.20.3",
"ueberdb2": "^5.0.14",
"tsx": "4.20.5",
"ueberdb2": "^5.0.15",
"underscore": "1.13.7",
"unorm": "1.6.0",
"wtfnode": "^0.10.0"
@ -83,21 +82,24 @@
"etherpad-lite": "node/server.ts"
},
"devDependencies": {
"@playwright/test": "^1.54.1",
"@types/async": "^3.2.24",
"@playwright/test": "^1.55.0",
"@types/async": "^3.2.25",
"@types/cookie-parser": "^1.4.9",
"@types/express": "^4.17.21",
"@types/cross-spawn": "^6.0.6",
"@types/ejs": "^3.1.5",
"@types/express": "^5.0.0",
"@types/express-session": "^1.18.2",
"@types/formidable": "^3.4.5",
"@types/http-errors": "^2.0.5",
"@types/jquery": "^3.5.32",
"@types/jquery": "^3.5.33",
"@types/js-cookie": "^3.0.6",
"@types/jsdom": "^21.1.7",
"@types/jsonminify": "^0.4.3",
"@types/jsonwebtoken": "^9.0.10",
"@types/mime-types": "^3.0.1",
"@types/mocha": "^10.0.9",
"@types/node": "^24.0.14",
"@types/oidc-provider": "^9.1.1",
"@types/node": "^24.3.0",
"@types/oidc-provider": "^9.1.2",
"@types/semver": "^7.7.0",
"@types/sinon": "^17.0.3",
"@types/supertest": "^6.0.2",
@ -105,7 +107,7 @@
"@types/underscore": "^1.13.0",
"@types/whatwg-mimetype": "^3.0.2",
"chokidar": "^4.0.3",
"eslint": "^9.31.0",
"eslint": "^9.33.0",
"eslint-config-etherpad": "^4.0.4",
"etherpad-cli-client": "^3.0.4",
"mocha": "^11.7.1",
@ -116,7 +118,7 @@
"sinon": "^21.0.0",
"split-grid": "^1.0.11",
"supertest": "^7.1.3",
"typescript": "^5.8.2",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
},
"engines": {
@ -144,6 +146,6 @@
"debug:socketio": "cross-env DEBUG=socket.io* node --require tsx/cjs node/server.ts",
"test:vitest": "vitest"
},
"version": "2.4.2",
"version": "2.5.0",
"license": "Apache-2.0"
}

View file

@ -328,7 +328,7 @@ export const checkRep = (cs: string) => {
* - `op2` is the current operation from `in2`, to apply to `op1`. Has the same consumption
* and advancement semantics as `op1`.
* - `opOut` is the result of applying `op2` (before consumption) to `op1` (before
* consumption). If there is no result (perhaps `op1` and `op2` cancelled each other out),
* consumption). If there is no result (perhaps `op1` and `op2` canceled each other out),
* either `opOut` must be nullish or `opOut.opcode` must be the empty string.
* @returns {string} the integrated changeset
*/

View file

@ -86,6 +86,8 @@ const padeditor = (() => {
$('#delete-pad').on('click', () => {
if (window.confirm(html10n.get('pad.delete.confirm'))) {
pad.collabClient.sendMessage({type: 'PAD_DELETE', data:{padId: pad.getPadId()}});
// redirect to home page after deletion
window.location.href = '/';
}
})

View file

@ -489,15 +489,17 @@ const paduserlist = (() => {
online++;
}
}
const recentPadsList = JSON.parse(localStorage.getItem('recentPads'));
const pathSegments = window.location.pathname.split('/');
const padName = pathSegments[pathSegments.length - 1];
const existingPad = recentPadsList.find((pad) => pad.name === padName);
if (existingPad) {
existingPad.members = online;
}
localStorage.setItem('recentPads', JSON.stringify(recentPadsList));
if (localStorage.getItem('recentPads') != null) {
const recentPadsList = JSON.parse(localStorage.getItem('recentPads'));
const pathSegments = window.location.pathname.split('/');
const padName = pathSegments[pathSegments.length - 1];
const existingPad = recentPadsList.find((pad) => pad.name === padName);
if (existingPad) {
existingPad.members = online;
}
localStorage.setItem('recentPads', JSON.stringify(recentPadsList));
}
$('#online_count').text(online);

View file

@ -4,7 +4,7 @@ import {node_modules, pluginInstallPath} from "./installer";
import {accessSync, constants, rmSync, symlinkSync, unlinkSync} from "node:fs";
import {dependencies, name} from '../../../package.json'
import {pathToFileURL} from 'node:url';
const settings = require('../../../node/utils/Settings');
import settings from '../../../node/utils/Settings';
import {readFileSync} from "fs";
export class LinkInstaller {

View file

@ -13,10 +13,13 @@ import {promises as fs} from "fs";
const plugins = require('./plugins');
const hooks = require('./hooks');
const runCmd = require('../../../node/utils/run_cmd');
const settings = require('../../../node/utils/Settings');
import settings, {
getEpVersion,
reloadSettings
} from '../../../node/utils/Settings';
import {LinkInstaller} from "./LinkInstaller";
const {findEtherpadRoot} = require('../../../node/utils/AbsolutePaths');
import {findEtherpadRoot} from '../../../node/utils/AbsolutePaths';
const logger = log4js.getLogger('plugins');
export const pluginInstallPath = path.join(settings.root, 'src','plugin_packages');
@ -27,13 +30,13 @@ export const installedPluginsPath = path.join(settings.root, 'var/installed_plug
const onAllTasksFinished = async () => {
await plugins.update();
await persistInstalledPlugins();
settings.reloadSettings();
reloadSettings();
await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('restartServer');
};
const headers = {
'User-Agent': `Etherpad/${settings.getEpVersion()}`,
'User-Agent': `Etherpad/${getEpVersion()}`,
};
let tasks = 0;

View file

@ -9,7 +9,9 @@ const runCmd = require('../../../node/utils/run_cmd');
const tsort = require('./tsort');
const pluginUtils = require('./shared');
const defs = require('./plugin_defs');
const settings = require('../../../node/utils/Settings');
import settings, {
getEpVersion,
} from '../../../node/utils/Settings';
const logger = log4js.getLogger('plugins');
@ -136,7 +138,7 @@ exports.getPackages = async () => {
newDependencies['ep_etherpad-lite'] = {
name: 'ep_etherpad-lite',
version: settings.getEpVersion(),
version: getEpVersion(),
path: path.join(settings.root, 'node_modules/ep_etherpad-lite'),
realPath: path.join(settings.root, 'src'),
};

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