Merge branch 'develop'

This commit is contained in:
SamTV12345 2024-06-02 12:59:38 +02:00
commit c45b7a361e
50 changed files with 2035 additions and 1850 deletions

View file

@ -33,7 +33,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -90,7 +90,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -133,7 +133,6 @@ jobs:
ep_font_size
ep_hash_auth
ep_headings2
ep_image_upload
ep_markdown
ep_readonly_guest
ep_set_title_on_pad
@ -160,7 +159,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -213,7 +212,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 21
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -249,7 +248,6 @@ jobs:
ep_font_size
ep_hash_auth
ep_headings2
ep_image_upload
ep_markdown
ep_readonly_guest
ep_set_title_on_pad

View file

@ -35,7 +35,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4

View file

@ -44,7 +44,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4

View file

@ -34,7 +34,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -85,7 +85,7 @@ jobs:
run: "sed -i 's/\"enableAdminUITests\": false/\"enableAdminUITests\": true,\\n\"users\":{\"admin\":{\"password\":\"changeme1\",\"is_admin\":true}}/' settings.json"
-
name: increase maxHttpBufferSize
run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 10000000/' settings.json"
run: "sed -i 's/\"maxHttpBufferSize\": 50000/\"maxHttpBufferSize\": 10000000/' settings.json"
-
name: Disable import/export rate limiting
run: |

View file

@ -27,7 +27,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 21
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -93,7 +93,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 21
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -160,7 +160,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 21
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4

View file

@ -29,7 +29,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -73,7 +73,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -144,7 +144,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4

View file

@ -26,7 +26,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4

View file

@ -29,7 +29,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4

View file

@ -35,7 +35,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -85,7 +85,6 @@ jobs:
ep_font_size
ep_hash_auth
ep_headings2
ep_image_upload
ep_markdown
ep_readonly_guest
ep_set_title_on_pad

View file

@ -35,7 +35,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 21
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4
@ -129,7 +129,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.0.4

View file

@ -1,3 +1,16 @@
# 2.1.0
### Notable enhancements and fixes
- Added PWA support. You can now add your Etherpad instance to your home screen on your mobile device or desktop.
- Fixed live plugin manager versions clashing. Thanks to @yacchin1205
- Fixed a bug in the pad panel where pagination was not working correctly when sorting by pad name
### Compatibility changes
- Reintroduced APIKey.txt support. You can now switch between APIKey and OAuth2.0 authentication. This can be toggled with the setting authenticationMethod. The default is OAuth2. If you want to use the APIKey method you can set that to `apikey`.
# 2.0.3
### Notable enhancements and fixes
@ -47,8 +60,8 @@
- Bin folder: The bin folder has been moved from the src folder to the root folder. This change was necessary as the contained scripts do not represent core functionality of the user.
- Starting Etherpad: Etherpad can now be started with a single command: `pnpm run prod` in the root directory.
- Installing Etherpad: Etherpad no longer symlinks itself in the root directory. This is now also taken care by pnpm, and it just creates a node_modules folder with the src directory`s ep_etherpad-lite folder
- Plugins can now be installed simply via the command: `pnpm run install-plugins first-plugin second-plugin` or if you want to install from path you can do:
`pnpm run install-plugins --path ../path-to-plugin`
- Plugins can now be installed simply via the command: `pnpm run plugins i first-plugin second-plugin` or if you want to install from path you can do:
`pnpm run plugins i --path ../path-to-plugin`
# 1.9.7

View file

@ -120,7 +120,7 @@ COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/ui/dist ./sr
RUN bin/installDeps.sh && \
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_LOCAL_PLUGINS}" ]; then \
pnpm run install-plugins ${ETHERPAD_PLUGINS} ${ETHERPAD_LOCAL_PLUGINS:+--path ${ETHERPAD_LOCAL_PLUGINS}}; \
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_LOCAL_PLUGINS:+--path ${ETHERPAD_LOCAL_PLUGINS}}; \
fi
@ -135,7 +135,7 @@ COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/ui/dist ./sr
RUN bin/installDeps.sh && rm -rf ~/.npm && rm -rf ~/.local && rm -rf ~/.cache && \
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_LOCAL_PLUGINS}" ]; then \
pnpm run install-plugins ${ETHERPAD_PLUGINS} ${ETHERPAD_LOCAL_PLUGINS:+--path ${ETHERPAD_LOCAL_PLUGINS}}; \
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_LOCAL_PLUGINS:+--path ${ETHERPAD_LOCAL_PLUGINS}}; \
fi

View file

@ -195,7 +195,7 @@ Alternatively, you can install plugins from the command line:
```sh
cd /path/to/etherpad-lite
pnpm run install-plugins ep_${plugin_name}
pnpm run plugins i ep_${plugin_name}
```
Also see [the plugin wiki
@ -207,7 +207,7 @@ Run the following command in your Etherpad folder to get all of the features
visible in the above demo gif:
```sh
pnpm run install-plugins \
pnpm run plugins i \
ep_align \
ep_comments_page \
ep_embedded_hyperlinks2 \

View file

@ -1,12 +1,13 @@
{
"name": "admin",
"private": true,
"version": "2.0.3",
"version": "2.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"build-copy": "tsc && vite build --outDir ../src/templates/admin --emptyOutDir",
"preview": "vite preview"
},
"dependencies": {
@ -15,25 +16,25 @@
"devDependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-toast": "^1.1.5",
"@types/react": "^18.2.79",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.2.25",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.11.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^9.0.0",
"eslint": "^9.2.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.1",
"lucide-react": "^0.372.0",
"eslint-plugin-react-refresh": "^0.4.7",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.381.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.3",
"react-hook-form": "^7.51.5",
"react-i18next": "^14.1.0",
"react-router-dom": "^6.22.3",
"react-router-dom": "^6.23.1",
"socket.io-client": "^4.7.5",
"typescript": "^5.4.5",
"vite": "^5.2.9",
"vite": "^5.2.12",
"vite-plugin-static-copy": "^1.0.3",
"vite-plugin-svgr": "^4.2.0",
"zustand": "^4.5.2"

View file

@ -167,10 +167,11 @@ export const PadPage = ()=>{
}}><ChevronLeft/><span>Previous Page</span></button>
<span>{currentPage+1} out of {pages}</span>
<button disabled={pages == currentPage+1} onClick={()=>{
setCurrentPage(currentPage+1)
const newCurrentPage = currentPage+1
setCurrentPage(newCurrentPage)
setSearchParams({
...searchParams,
offset: (Number(currentPage)-1)*searchParams.limit
offset: (Number(newCurrentPage))*searchParams.limit
})
}}><span>Next Page</span><ChevronRight/></button>
</div>

17
bin/commonPlugins.ts Normal file
View file

@ -0,0 +1,17 @@
import {PackageData} from "ep_etherpad-lite/node/types/PackageInfo";
import {writeFileSync} from "fs";
import {installedPluginsPath} from "ep_etherpad-lite/static/js/pluginfw/installer";
const pluginsModule = require('ep_etherpad-lite/static/js/pluginfw/plugins');
export const persistInstalledPlugins = async () => {
const plugins:PackageData[] = []
const installedPlugins = {plugins: plugins};
for (const pkg of Object.values(await pluginsModule.getPackages()) as PackageData[]) {
installedPlugins.plugins.push({
name: pkg.name,
version: pkg.version,
});
}
installedPlugins.plugins = [...new Set(installedPlugins.plugins)];
writeFileSync(installedPluginsPath, JSON.stringify(installedPlugins));
};

View file

@ -1,59 +0,0 @@
'use strict';
import {writeFileSync} from 'fs'
import {linkInstaller, installedPluginsPath} from "ep_etherpad-lite/static/js/pluginfw/installer";
import {PackageData} from "ep_etherpad-lite/node/types/PackageInfo";
const pluginsModule = require('ep_etherpad-lite/static/js/pluginfw/plugins');
if (process.argv.length === 2) {
console.error('Expected at least one argument!');
process.exit(1);
}
let args = process.argv.slice(2)
let registryPlugins: string[] = [];
let localPlugins: string[] = [];
if (args.indexOf('--path') !== -1) {
const indexToSplit = args.indexOf('--path');
registryPlugins = args.slice(0, indexToSplit);
localPlugins = args.slice(indexToSplit + 1);
} else {
registryPlugins = args;
}
const persistInstalledPlugins = async () => {
const plugins:PackageData[] = []
const installedPlugins = {plugins: plugins};
for (const pkg of Object.values(await pluginsModule.getPackages()) as PackageData[]) {
installedPlugins.plugins.push({
name: pkg.name,
version: pkg.version,
});
}
installedPlugins.plugins = [...new Set(installedPlugins.plugins)];
writeFileSync(installedPluginsPath, JSON.stringify(installedPlugins));
};
async function run() {
for (const plugin of registryPlugins) {
console.log(`Installing plugin from registry: ${plugin}`)
if (plugin.includes('@')) {
const [name, version] = plugin.split('@');
await linkInstaller.installPlugin(name, version);
continue;
}
await linkInstaller.installPlugin(plugin);
}
for (const plugin of localPlugins) {
console.log(`Installing plugin from path: ${plugin}`);
await linkInstaller.installFromPath(plugin);
}
}
(async () => {
await run();
await persistInstalledPlugins();
})();

View file

@ -1,21 +1,21 @@
{
"name": "bin",
"version": "2.0.3",
"version": "2.1.0",
"description": "",
"main": "checkAllPads.js",
"directories": {
"doc": "doc"
},
"dependencies": {
"axios": "^1.6.8",
"axios": "^1.7.2",
"ep_etherpad-lite": "workspace:../src",
"log4js": "^6.9.1",
"semver": "^7.6.0",
"tsx": "^4.7.2",
"semver": "^7.6.2",
"tsx": "^4.10.5",
"ueberdb2": "^4.2.63"
},
"devDependencies": {
"@types/node": "^20.12.7",
"@types/node": "^20.12.13",
"@types/semver": "^7.5.8",
"typescript": "^5.4.5"
},
@ -32,7 +32,7 @@
"rebuildPad": "node --import tsx rebuildPad.ts",
"stalePlugins": "node --import tsx ./plugins/stalePlugins.ts",
"checkPlugin": "node --import tsx ./plugins/checkPlugin.ts",
"install-plugins": "node --import tsx ./installPlugins.ts"
"plugins": "node --import tsx ./plugins.ts"
},
"author": "",
"license": "ISC"

116
bin/plugins.ts Normal file
View file

@ -0,0 +1,116 @@
'use strict';
import {linkInstaller, checkForMigration} from "ep_etherpad-lite/static/js/pluginfw/installer";
import {persistInstalledPlugins} from "./commonPlugins";
import fs from "node:fs";
const settings = require('ep_etherpad-lite/node/utils/Settings');
if (process.argv.length === 2) {
console.error('Expected at least one argument!');
process.exit(1);
}
let args = process.argv.slice(2)
const possibleActions = [
"i",
"install",
"rm",
"remove",
"ls",
"list"
]
const install = ()=> {
let registryPlugins: string[] = [];
let localPlugins: string[] = [];
if (args.indexOf('--path') !== -1) {
const indexToSplit = args.indexOf('--path');
registryPlugins = args.slice(1, indexToSplit);
localPlugins = args.slice(indexToSplit + 1);
} else {
registryPlugins = args;
}
async function run() {
for (const plugin of registryPlugins) {
if (possibleActions.includes(plugin)){
continue
}
console.log(`Installing plugin from registry: ${plugin}`)
if (plugin.includes('@')) {
const [name, version] = plugin.split('@');
await linkInstaller.installPlugin(name, version);
continue;
}
await linkInstaller.installPlugin(plugin);
}
for (const plugin of localPlugins) {
console.log(`Installing plugin from path: ${plugin}`);
await linkInstaller.installFromPath(plugin);
}
}
(async () => {
await checkForMigration();
await run();
await persistInstalledPlugins();
})();
}
const list = ()=>{
const walk = async () => {
const plugins = fs.readFileSync(settings.root+"/var/installed_plugins.json", "utf-8")
const pluginNames = JSON.parse(plugins).plugins.map((plugin: any) => plugin.name).join(", ")
console.log("Installed plugins are:", pluginNames)
}
(async () => {
await walk();
})();
}
const remove = (plugins: string[])=>{
const walk = async () => {
for (const plugin of plugins) {
console.log(`Uninstalling plugin: ${plugin}`)
await linkInstaller.uninstallPlugin(plugin);
}
await persistInstalledPlugins();
}
(async () => {
await checkForMigration();
await walk();
})();
}
let action = args[0];
switch (action) {
case "install":
install();
break;
case "i":
install();
break;
case "ls":
list();
break;
case "list":
list();
break;
case "rm":
remove(args.slice(1));
break;
default:
console.error('Expected at least one argument!');
process.exit(1);
}

View file

@ -69,7 +69,7 @@ jobs:
working-directory: ./etherpad-lite
run: |
pnpm link --global $PLUGIN_NAME
pnpm run install-plugins --path ../../plugin
pnpm run plugins i --path ../../plugin
env:
PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
- name: Link ep_etherpad-lite

View file

@ -510,7 +510,7 @@ For the editor container, you can also make it full width by adding `full-width-
| `SOCKETIO_MAX_HTTP_BUFFER_SIZE`
| The maximum size (in bytes) of a single message accepted via Socket.IO. If a client sends a larger message, its connection gets closed to prevent DoS (memory exhaustion) attacks.
| `10000`
| `50000`
| `LOAD_TEST`
| Allow Load Testing tools to hit the Etherpad Instance. WARNING: this will disable security on the instance.

View file

@ -213,7 +213,7 @@ For the editor container, you can also make it full width by adding `full-width-
| `FOCUS_LINE_PERCENTAGE_ARROW_UP` | Percentage of viewport height to be additionally scrolled when user presses arrow up in the line of the top of the viewport. Set to 0 to let the scroll to be handled as default by Etherpad | `0` |
| `FOCUS_LINE_DURATION` | Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation | `0` |
| `FOCUS_LINE_CARET_SCROLL` | Flag to control if it should scroll when user places the caret in the last line of the viewport | `false` |
| `SOCKETIO_MAX_HTTP_BUFFER_SIZE` | The maximum size (in bytes) of a single message accepted via Socket.IO. If a client sends a larger message, its connection gets closed to prevent DoS (memory exhaustion) attacks. | `10000` |
| `SOCKETIO_MAX_HTTP_BUFFER_SIZE` | The maximum size (in bytes) of a single message accepted via Socket.IO. If a client sends a larger message, its connection gets closed to prevent DoS (memory exhaustion) attacks. | `50000` |
| `LOAD_TEST` | Allow Load Testing tools to hit the Etherpad Instance. WARNING: this will disable security on the instance. | `false` |
| `DUMP_ON_UNCLEAN_EXIT` | Enable dumping objects preventing a clean exit of Node.js. WARNING: this has a significant performance impact. | `false` |
| `EXPOSE_VERSION` | Expose Etherpad version in the web interface and in the Server http header. Do not enable on production machines. | `false` |

View file

@ -1,6 +1,6 @@
{
"devDependencies": {
"vitepress": "^1.1.3"
"vitepress": "^1.2.2"
},
"scripts": {
"docs:dev": "vitepress dev",

View file

@ -24,7 +24,10 @@
"test-ui:ui": "pnpm --filter ep_etherpad-lite run test-ui:ui",
"test-admin": "pnpm --filter ep_etherpad-lite run test-admin",
"test-admin:ui": "pnpm --filter ep_etherpad-lite run test-admin:ui",
"install-plugins": "pnpm --filter bin run install-plugins"
"plugins": "pnpm --filter bin run plugins",
"install-plugins": "pnpm --filter bin run plugins i",
"remove-plugins": "pnpm --filter bin run remove-plugins",
"list-plugins": "pnpm --filter bin run list-plugins"
},
"dependencies": {
"ep_etherpad-lite": "workspace:./src"
@ -43,6 +46,6 @@
"type": "git",
"url": "https://github.com/ether/etherpad-lite.git"
},
"version": "2.0.3",
"version": "2.1.0",
"license": "Apache-2.0"
}

File diff suppressed because it is too large Load diff

View file

@ -171,6 +171,13 @@
*/
"showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:true}",
/*
The authentication method used by the server.
The default value is sso
If you want to use the old authentication system, change this to apikey
*/
"authenticationMethod": "${AUTHENTICATION_METHOD:sso}",
/*
* Node native SSL support
*
@ -537,7 +544,7 @@
* value to work properly, but increasing the value increases susceptibility
* to denial of service attacks (malicious clients can exhaust memory).
*/
"maxHttpBufferSize": "${SOCKETIO_MAX_HTTP_BUFFER_SIZE:10000}"
"maxHttpBufferSize": "${SOCKETIO_MAX_HTTP_BUFFER_SIZE:50000}"
},
/*

View file

@ -537,7 +537,7 @@
* value to work properly, but increasing the value increases susceptibility
* to denial of service attacks (malicious clients can exhaust memory).
*/
"maxHttpBufferSize": 10000
"maxHttpBufferSize": 50000
},
/*
@ -586,6 +586,13 @@
*/
"importMaxFileSize": 52428800, // 50 * 1024 * 1024
/*
The authentication method used by the server.
The default value is sso
If you want to use the old authentication system, change this to apikey
*/
"authenticationMethod": "${AUTHENTICATION_METHOD:sso}",
/*
* From Etherpad 1.8.5 onwards, when Etherpad is in production mode commits from individual users are rate limited
*

View file

@ -57,6 +57,12 @@
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize"
}
},
{
"name": "pwa",
"hooks": {
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/pwa"
}
},
{
"name": "apicalls",
"hooks": {
@ -112,12 +118,6 @@
"hooks": {
"expressPreSession": "ep_etherpad-lite/node/hooks/express/openapi"
}
},
{
"name": "ep_message_all",
"client_hooks": {
"handleClientMessage_shoutMessage": "ep_etherpad-lite/static/js/messageHandler"
}
}
]
}

View file

@ -8,6 +8,7 @@
"Bellayet",
"Greatder",
"Nasir8891",
"RiazACU",
"Sankarshan",
"Sibabrata Banerjee",
"আজিজ",

View file

@ -36,7 +36,7 @@
"admin_settings.current_example-devel": "Юлгю хазырлау джарашдырыуланы шаблону",
"admin_settings.current_example-prod": "Юлгю чыгъарыу джарашдырыуланы шаблону",
"admin_settings.current_restart.value": "Etherpad-ны джангыдан башлат",
"admin_settings.current_save.value": "Джарашдырыуланы Сакъла",
"admin_settings.current_save.value": "Джарашдырыуланы Сакъландыр",
"admin_settings.page-title": "Джарашдырыула — Etherpad",
"index.newPad": "Джангы Блокнот",
"index.createOpenPad": "неда бу ат бла Блокнот болдур/ач:",

View file

@ -3,6 +3,7 @@
"authors": [
"Aalam",
"Babanwalia",
"Cabal",
"Tow",
"ਗੁਰਪ੍ਰੀਤ ਹੁੰਦਲ",
"ਪ੍ਰਚਾਰਕ"
@ -37,7 +38,7 @@
"pad.settings.stickychat": "ਹਮੇਸ਼ਾ ਸਕਰੀਨ ਉੱਤੇ ਗੱਲ ਕਰੋ",
"pad.settings.chatandusers": "ਗੱਲ-ਬਾਤ ਅਤੇ ਵਰਤੋਂਕਾਰ ਦਿਖਾਵੋ",
"pad.settings.colorcheck": "ਲੇਖਕੀ ਰੰਗ",
"pad.settings.linenocheck": "ਲਾਈਨ ਨੰਬਰ",
"pad.settings.linenocheck": "ਲਕੀਰ ਨੰਬਰ",
"pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?",
"pad.settings.fontType": "ਫੋਂਟ ਕਿਸਮ:",
"pad.settings.fontType.normal": "ਸਧਾਰਨ",

View file

@ -10,6 +10,7 @@
"Hzy980512",
"JuneAugust",
"Lakejason0",
"LittlePaw365",
"Liuxinyu970226",
"Qiyue2001",
"Shangkuanlc",
@ -57,7 +58,7 @@
"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",

View file

@ -27,7 +27,7 @@ import createHTTPError from 'http-errors';
import {Http2ServerRequest, Http2ServerResponse} from "node:http2";
import {publicKeyExported} from "../security/OAuth2Provider";
import {jwtVerify} from "jose";
import {apikey} from './APIKeyHandler'
// a list of all functions
const version:MapArrayType<any> = {};
@ -149,9 +149,11 @@ exports.version = version;
type APIFields = {
apikey: string;
api_key: string;
padID: string;
padName: string;
authorization: string;
}
/**
@ -160,9 +162,9 @@ type APIFields = {
* @param {String} functionName the name of the called function
* @param fields the params of the called function
* @param req express request object
* @param res express response object
*/
exports.handle = async function (apiVersion: string, functionName: string, fields: APIFields, req: Http2ServerRequest, res: Http2ServerResponse) {
exports.handle = async function (apiVersion: string, functionName: string, fields: APIFields,
req: Http2ServerRequest) {
// say goodbye if this is an unknown API version
if (!(apiVersion in version)) {
throw new createHTTPError.NotFound('no such api version');
@ -173,19 +175,25 @@ exports.handle = async function (apiVersion: string, functionName: string, field
throw new createHTTPError.NotFound('no such function');
}
if (apikey !== null && apikey.trim().length > 0) {
fields.apikey = fields.apikey || fields.api_key || fields.authorization;
// API key is configured, check if it is valid
if (fields.apikey !== apikey!.trim()) {
throw new createHTTPError.Unauthorized('no or wrong API Key');
}
} else {
if(!req.headers.authorization) {
throw new createHTTPError.Unauthorized('no or wrong API Key');
}
try {
await jwtVerify(req.headers.authorization!.replace("Bearer ", ""), publicKeyExported!, {algorithms: ['RS256'],
requiredClaims: ["admin"]})
} catch (e) {
throw new createHTTPError.Unauthorized('no or wrong API Key');
throw new createHTTPError.Unauthorized('no or wrong OAuth token');
}
}
// sanitize any padIDs before continuing
if (fields.padID) {

View file

@ -0,0 +1,25 @@
const absolutePaths = require('../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');
const apiHandlerLogger = log4js.getLogger('APIHandler');
// ensure we have an apikey
export let apikey:string|null = null;
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
if(settings.authenticationMethod === 'apikey') {
try {
apikey = fs.readFileSync(apikeyFilename, 'utf8');
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
} catch (e) {
apiHandlerLogger.info(
`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apikey = randomString(32);
fs.writeFileSync(apikeyFilename, apikey!, 'utf8');
}
}

View file

@ -2,11 +2,13 @@
import {ArgsExpressType} from "../../types/ArgsExpressType";
import path from "path";
import fs from "fs";
import express from "express";
import * as url from "node:url";
import {MapArrayType} from "../../types/MapType";
const settings = require('ep_etherpad-lite/node/utils/Settings');
const ADMIN_PATH = path.join(settings.root, 'src', 'templates', 'admin');
const ADMIN_PATH = path.join(settings.root, 'src', 'templates');
const PROXY_HEADER = "x-proxy-path"
/**
* Add the admin navigation link
* @param hookName {String} the name of the hook
@ -14,12 +16,71 @@ const ADMIN_PATH = path.join(settings.root, 'src', 'templates', 'admin');
* @param {Function} cb the callback function
* @return {*}
*/
exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function): any => {
args.app.use('/admin/', express.static(path.join(__dirname, '../../../templates/admin'), {maxAge: 1000 * 60 * 60 * 24}));
args.app.get('/admin/*', (_request:any, response:any)=>{
response.sendFile(path.resolve(__dirname,'../../../templates/admin', 'index.html'));
} )
args.app.get('/admin', (req:any, res:any, next:Function) => {
exports.expressCreateServer = (hookName: string, args: ArgsExpressType, cb: Function): any => {
if (!fs.existsSync(ADMIN_PATH)) {
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) => {
// parse URL
const parsedUrl = url.parse(req.url);
// extract URL path
let pathname = ADMIN_PATH + `${parsedUrl.pathname}`;
// based on the URL path, extract the file extension. e.g. .js, .doc, ...
let ext = path.parse(pathname).ext;
// maps file extension to MIME typere
const map: MapArrayType<string> = {
'.ico': 'image/x-icon',
'.html': 'text/html',
'.js': 'text/javascript',
'.json': 'application/json',
'.css': 'text/css',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.wav': 'audio/wav',
'.mp3': 'audio/mpeg',
'.svg': 'image/svg+xml',
'.pdf': 'application/pdf',
'.doc': 'application/msword'
};
fs.exists(pathname, function (exist) {
if (!exist) {
// if the file is not found, return 404
res.statusCode = 200;
pathname = ADMIN_PATH + "/admin/index.html"
ext = path.parse(pathname).ext;
}
// if is a directory search for index file matching the extension
if (fs.statSync(pathname).isDirectory()) {
pathname = pathname + '/index.html';
ext = path.parse(pathname).ext;
}
// read file from file system
fs.readFile(pathname, function (err, data) {
if (err) {
res.statusCode = 500;
res.end(`Error getting the file: ${err}.`);
} else {
let dataToSend:Buffer|string = data
// if the file is found, set Content-type and send data
res.setHeader('Content-type', map[ext] || 'text/plain');
if (ext === ".html" || ext === ".js" || ext === ".css") {
if (req.header(PROXY_HEADER)) {
let string = data.toString()
dataToSend = string.replaceAll("/admin", req.header(PROXY_HEADER) + "/admin")
dataToSend = dataToSend.replaceAll("/socket.io", req.header(PROXY_HEADER) + "/socket.io")
}
}
res.end(dataToSend);
}
});
})
});
args.app.get('/admin', (req: any, res: any, next: Function) => {
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
})
return cb();

View file

@ -3,6 +3,7 @@
import {PadQueryResult, PadSearchQuery} from "../../types/PadSearchQuery";
import {PadType} from "../../types/PadType";
import log4js from 'log4js';
const eejs = require('../../eejs');
const fsp = require('fs').promises;
@ -15,6 +16,7 @@ const api = require('../../db/API');
const queryPadLimit = 12;
const logger = log4js.getLogger('adminSettings');
exports.socketio = (hookName: string, {io}: any) => {
@ -28,7 +30,7 @@ exports.socketio = (hookName: string, {io}: any) => {
try {
data = await fsp.readFile(settings.settingsFilename, 'utf8');
} catch (err) {
return console.log(err);
return logger.error(`Error loading settings: ${err}`);
}
// if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
if (settings.showSettingsInAdminPage === false) {
@ -39,8 +41,12 @@ exports.socketio = (hookName: string, {io}: any) => {
});
socket.on('saveSettings', async (newSettings: string) => {
console.log('Admin request to save settings through a socket on /admin/settings');
logger.info('Admin request to save settings through a socket on /admin/settings');
try {
await fsp.writeFile(settings.settingsFilename, newSettings);
} catch (err) {
logger.error(`Error saving settings: ${err}`);
}
socket.emit('saveprogress', 'saved');
});
@ -210,6 +216,7 @@ exports.socketio = (hookName: string, {io}: any) => {
socket.on('deletePad', async (padId: string) => {
const padExists = await padManager.doesPadExists(padId);
if (padExists) {
logger.info(`Deleting pad: ${padId}`);
const pad = await padManager.getPad(padId);
await pad.remove();
socket.emit('results:deletePad', padId);
@ -217,7 +224,7 @@ exports.socketio = (hookName: string, {io}: any) => {
})
socket.on('restartServer', async () => {
console.log('Admin request to restart server through a socket on /admin/settings');
logger.info('Admin request to restart server through a socket on /admin/settings');
settings.reloadSettings();
await plugins.update();
await hooks.aCallAll('loadSettings', {settings});
@ -230,4 +237,3 @@ exports.socketio = (hookName: string, {io}: any) => {
const searchPad = async (query: PadSearchQuery) => {
}

View file

@ -608,7 +608,7 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
for (const funcName of Object.keys(apiHandler.version[version])) {
const handler = async (c: any, req:any, res:any) => {
// parse fields from request
const {header, params, query} = c.request;
const {headers, params, query} = c.request;
// read form data if method was POST
let formData:MapArrayType<any> = {};
@ -622,8 +622,7 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
}
}
const fields = Object.assign({}, header, params, query, formData);
const fields = Object.assign({}, headers, params, query, formData);
if (logger.isDebugEnabled()) {
logger.debug(`REQUEST, v${version}:${funcName}, ${JSON.stringify(fields)}`);
}

View file

@ -0,0 +1,32 @@
import {ArgsExpressType} from "../../types/ArgsExpressType";
const settings = require('../../utils/Settings');
const pwa = {
name: settings.title || "Etherpad",
short_name: settings.title,
description: "A collaborative online editor",
icons: [
{
"src": "/static/skins/colibris/images/fond.jpg",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
type: "image/png"
}
],
start_url: "/",
display: "fullscreen",
theme_color: "#0f775b",
background_color: "#0f775b"
}
exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => {
args.app.get('/manifest.json', (req:any, res:any) => {
res.json(pwa);
});
return cb();
}

View file

@ -45,5 +45,10 @@ for (let i = 0; i < argv.length; i++) {
exports.argv.sessionkey = arg;
}
// Override location of APIKEY.txt file
if (prevArg === '--apikey') {
exports.argv.apikey = arg;
}
prevArg = arg;
}

View file

@ -153,9 +153,18 @@ exports.socketIo = {
* properly, but increasing the value increases susceptibility to denial of service attacks
* (malicious clients can exhaust memory).
*/
maxHttpBufferSize: 10000,
maxHttpBufferSize: 50000,
};
/*
The authentication method used by the server.
The default value is sso
If you want to use the old authentication system, change this to apikey
*/
exports.authenticationMethod = 'sso'
/*
* The Type of the database
*/
@ -519,6 +528,8 @@ exports.getGitCommit = () => {
// Return etherpad version from package.json
exports.getEpVersion = () => require('../../package.json').version;
/**
* Receives a settingsObj and, if the property name is a valid configuration
* item, stores it in the module's exported properties via a side effect.
@ -745,7 +756,6 @@ const lookupEnvironmentVariables = (obj: MapArrayType<any>) => {
//console.log(root.collectFromLeafsUpwards())
const rooting = root.collectFromLeafsUpwards()
console.log("Rooting is", rooting.ADMIN)
obj = Object.assign(obj, rooting)
return obj;
};

View file

@ -31,7 +31,7 @@
],
"dependencies": {
"async": "^3.2.5",
"axios": "^1.6.8",
"axios": "^1.7.2",
"clean-css": "^5.3.3",
"cookie-parser": "^1.4.6",
"cross-spawn": "^7.0.3",
@ -45,13 +45,13 @@
"find-root": "1.1.0",
"formidable": "^3.5.1",
"http-errors": "^2.0.0",
"jose": "^5.2.4",
"jose": "^5.3.0",
"js-cookie": "^3.0.5",
"jsdom": "^24.0.0",
"jsdom": "^24.1.0",
"jsonminify": "0.4.2",
"jsonwebtoken": "^9.0.2",
"languages4translatewiki": "0.1.3",
"live-plugin-manager": "^0.20.0",
"live-plugin-manager": "^1.0.0",
"lodash.clonedeep": "4.5.0",
"log4js": "^6.9.1",
"measured-core": "^2.0.0",
@ -64,14 +64,14 @@
"rehype-minify-whitespace": "^6.0.0",
"resolve": "1.22.8",
"security": "1.0.0",
"semver": "^7.6.0",
"semver": "^7.6.2",
"socket.io": "^4.7.5",
"socket.io-client": "^4.7.5",
"superagent": "^8.1.2",
"superagent": "^9.0.2",
"terser": "^5.30.3",
"threads": "^1.7.0",
"tinycon": "0.6.8",
"tsx": "^4.7.2",
"tsx": "^4.10.5",
"ueberdb2": "^4.2.63",
"underscore": "1.13.6",
"unorm": "1.6.0",
@ -83,21 +83,21 @@
"etherpad-lite": "node/server.ts"
},
"devDependencies": {
"@playwright/test": "^1.43.1",
"@playwright/test": "^1.44.0",
"@types/async": "^3.2.24",
"@types/express": "^4.17.21",
"@types/formidable": "^3.4.5",
"@types/http-errors": "^2.0.4",
"@types/jsdom": "^21.1.6",
"@types/jsdom": "^21.1.7",
"@types/jsonwebtoken": "^9.0.6",
"@types/mocha": "^10.0.6",
"@types/node": "^20.12.7",
"@types/node": "^20.12.13",
"@types/oidc-provider": "^8.4.4",
"@types/semver": "^7.5.8",
"@types/sinon": "^17.0.3",
"@types/supertest": "^6.0.2",
"@types/underscore": "^1.11.15",
"eslint": "^9.0.0",
"eslint": "^9.2.0",
"eslint-config-etherpad": "^4.0.4",
"etherpad-cli-client": "^3.0.2",
"mocha": "^10.4.0",
@ -105,9 +105,9 @@
"nodeify": "^1.0.1",
"openapi-schema-validation": "^0.4.2",
"set-cookie-parser": "^2.6.0",
"sinon": "^17.0.1",
"sinon": "^18.0.0",
"split-grid": "^1.0.11",
"supertest": "^6.3.4",
"supertest": "^7.0.0",
"typescript": "^5.4.5"
},
"engines": {
@ -123,8 +123,8 @@
"lint": "eslint .",
"test": "mocha --import=tsx --timeout 120000 --recursive tests/backend/specs/**.ts ../node_modules/ep_*/static/tests/backend/specs/**",
"test-container": "mocha --import=tsx --timeout 5000 tests/container/specs/api",
"dev": "node --import tsx node/server.ts",
"prod": "node --import tsx node/server.ts",
"dev": "node --require tsx/cjs node/server.ts",
"prod": "node --require tsx/cjs node/server.ts",
"ts-check": "tsc --noEmit",
"ts-check:watch": "tsc --noEmit --watch",
"test-ui": "npx playwright test tests/frontend-new/specs",
@ -132,6 +132,6 @@
"test-admin": "npx playwright test tests/frontend-new/admin-spec --workers 1",
"test-admin:ui": "npx playwright test tests/frontend-new/admin-spec --ui --workers 1"
},
"version": "2.0.3",
"version": "2.1.0",
"license": "Apache-2.0"
}

View file

@ -100,6 +100,10 @@ export const checkForMigration = async () => {
for (let file of files){
const moduleName = path.basename(file);
if (moduleName === '.versions') {
// Skip the directory using live-plugin-manager
continue;
}
try {
await fs.access(path.join(node_modules, moduleName), fs.constants.F_OK);
logger.debug(`plugin ${moduleName} already exists in node_modules`);

View file

@ -129,8 +129,7 @@ exports.getPackages = async () => {
if (!plugin.name.startsWith(exports.prefix)) {
continue;
}
plugin.realPath = await fs.realpath(plugin.location);
plugin.path = plugin.realPath;
plugin.path = plugin.realPath = plugin.location;
newDependencies[plugin.name] = plugin;
}

View file

@ -70,7 +70,6 @@ const tsortTest = () => {
];
let sorted = tsort(edges);
console.log(sorted);
// example 2: failure ( A > B > C > A )
edges = [

View file

@ -2,7 +2,8 @@
<html lang="en">
<head>
<title><%- padId %></title>
<meta name="generator" content="Etherpad"/>
<link rel="manifest" href="/manifest.json" />
<meta name="generator" content="Etherpad"/>
<meta name="author" content="Etherpad"/>
<meta name="changedby" content="Etherpad"/>
<meta charset="utf-8"/>

View file

@ -6,6 +6,7 @@
<title><%=settings.title%></title>
<meta charset="utf-8">
<link rel="manifest" href="/manifest.json" />
<meta name="referrer" content="no-referrer">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<link rel="shortcut icon" href="favicon.ico">

View file

@ -2,6 +2,7 @@
<html>
<head>
<title>JavaScript license information</title>
<link rel="manifest" href="/manifest.json" />
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">

View file

@ -10,6 +10,7 @@
<% e.begin_block("htmlHead"); %>
<% e.end_block(); %>
<title><%=settings.title%></title>
<link rel="manifest" href="/manifest.json" />
<script>
/*
|@licstart The following is the entire license notice for the

View file

@ -31,6 +31,7 @@
</script>
<script src="../../static/js/basic_error_handler.js?v=<%=settings.randomVersionString%>"></script>
<meta charset="utf-8">
<link rel="manifest" href="/manifest.json" />
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="no-referrer">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">

View file

@ -10,6 +10,6 @@
},
"devDependencies": {
"typescript": "^5.4.5",
"vite": "^5.2.9"
"vite": "^5.2.12"
}
}