Merge branch 'develop'

This commit is contained in:
SamTV12345 2024-10-28 22:05:59 +01:00
commit 0c68ddce1e
26 changed files with 1561 additions and 1060 deletions

View file

@ -1,3 +1,10 @@
# 2.2.6
### Notable enhancements and fixes
- Added option to delete a pad by the creator. This option can be found in the settings menu. When you click on it you get a confirm dialog and after that you have the chance to completely erase the pad.
# 2.2.5 # 2.2.5
### Notable enhancements and fixes ### Notable enhancements and fixes

View file

@ -174,6 +174,31 @@ following plugins:
that each user's chosen color, display name, comment ownership, etc. is that each user's chosen color, display name, comment ownership, etc. is
strongly linked to their account. strongly linked to their account.
### Upgrade Etherpad
Run the following command in your Etherpad folder to upgrade
1. Stop any running Etherpad (manual, systemd ...)
2. Get present version
```sh
git -P tag --contains
```
3. List versions available
```sh
git -P tag --list "v*" --merged
```
4. Select the version
```sh
git checkout v2.2.5
git switch -c v2.2.5
```
5. Upgrade Etherpad
```sh
./bin/run.sh
```
6. Stop with [CTRL-C]
7. Restart your Etherpad service
## Next Steps ## Next Steps
### Tweak the settings ### Tweak the settings

View file

@ -1,7 +1,7 @@
{ {
"name": "admin", "name": "admin",
"private": true, "private": true,
"version": "2.2.5", "version": "2.2.6",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@ -11,32 +11,32 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-switch": "^1.1.0" "@radix-ui/react-switch": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toast": "^1.2.2",
"@types/react": "^18.3.8", "@types/react": "^18.3.12",
"@types/react-dom": "^18.2.25", "@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/eslint-plugin": "^8.11.0",
"@typescript-eslint/parser": "^8.6.0", "@typescript-eslint/parser": "^8.11.0",
"@vitejs/plugin-react-swc": "^3.5.0", "@vitejs/plugin-react-swc": "^3.7.1",
"eslint": "^9.10.0", "eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.12", "eslint-plugin-react-refresh": "^0.4.13",
"i18next": "^23.15.1", "i18next": "^23.16.2",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.441.0", "lucide-react": "^0.453.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.1",
"react-i18next": "^15.0.2", "react-i18next": "^15.1.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.27.0",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.8.0",
"typescript": "^5.6.2", "typescript": "^5.6.3",
"vite": "^5.4.7", "vite": "^5.4.10",
"vite-plugin-static-copy": "^1.0.6", "vite-plugin-static-copy": "^2.0.0",
"vite-plugin-svgr": "^4.2.0", "vite-plugin-svgr": "^4.2.0",
"zustand": "^4.5.5" "zustand": "^5.0.0"
} }
} }

View file

@ -95,7 +95,9 @@ export const App = () => {
<h1>Etherpad</h1> <h1>Etherpad</h1>
</span> </span>
<ul onClick={()=>{ <ul onClick={()=>{
setSidebarOpen(false) if (window.innerWidth < 768) {
setSidebarOpen(false)
}
}}> }}>
<li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li> <li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
<li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li> <li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>

View file

@ -4,7 +4,7 @@ import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts";
import {useDebounce} from "../utils/useDebounce.ts"; import {useDebounce} from "../utils/useDebounce.ts";
import {Trans, useTranslation} from "react-i18next"; import {Trans, useTranslation} from "react-i18next";
import {SearchField} from "../components/SearchField.tsx"; import {SearchField} from "../components/SearchField.tsx";
import {Download, Trash} from "lucide-react"; import {ArrowUpFromDot, Download, Trash} from "lucide-react";
import {IconButton} from "../components/IconButton.tsx"; import {IconButton} from "../components/IconButton.tsx";
import {determineSorting} from "../utils/sorting.ts"; import {determineSorting} from "../utils/sorting.ts";
@ -12,7 +12,8 @@ import {determineSorting} from "../utils/sorting.ts";
export const HomePage = () => { export const HomePage = () => {
const pluginsSocket = useStore(state=>state.pluginsSocket) const pluginsSocket = useStore(state=>state.pluginsSocket)
const [plugins,setPlugins] = useState<PluginDef[]>([]) const [plugins,setPlugins] = useState<PluginDef[]>([])
const [installedPlugins, setInstalledPlugins] = useState<InstalledPlugin[]>([]) const installedPlugins = useStore(state=>state.installedPlugins)
const setInstalledPlugins = useStore(state=>state.setInstalledPlugins)
const [searchParams, setSearchParams] = useState<SearchParams>({ const [searchParams, setSearchParams] = useState<SearchParams>({
offset: 0, offset: 0,
limit: 99999, limit: 99999,
@ -49,7 +50,7 @@ export const HomePage = () => {
}, [plugins, searchParams]) }, [plugins, searchParams])
const sortedInstalledPlugins = useMemo(()=>{ const sortedInstalledPlugins = useMemo(()=>{
return installedPlugins.sort((a, b)=>{ return useStore.getState().installedPlugins.sort((a, b)=>{
if(a.name < b.name){ if(a.name < b.name){
return -1 return -1
@ -78,17 +79,16 @@ export const HomePage = () => {
}) })
pluginsSocket.on('results:updatable', (data) => { pluginsSocket.on('results:updatable', (data) => {
data.updatable.forEach((pluginName: string) => { const newInstalledPlugins = useStore.getState().installedPlugins.map(plugin => {
setInstalledPlugins(installedPlugins.map(plugin => { if (data.updatable.includes(plugin.name)) {
if (plugin.name === pluginName) { return {
return { ...plugin,
...plugin, updatable: true
updatable: true }
} }
} return plugin
return plugin })
})) setInstalledPlugins(newInstalledPlugins)
})
}) })
pluginsSocket.on('finished:install', () => { pluginsSocket.on('finished:install', () => {
@ -159,6 +159,7 @@ export const HomePage = () => {
}) })
}, 500, [searchTerm]) }, 500, [searchTerm])
return <div> return <div>
<h1><Trans i18nKey="admin_plugins"/></h1> <h1><Trans i18nKey="admin_plugins"/></h1>
@ -180,7 +181,7 @@ export const HomePage = () => {
<td> <td>
{ {
plugin.updatable ? plugin.updatable ?
<button onClick={() => installPlugin(plugin.name)}>Update</button> <IconButton onClick={() => installPlugin(plugin.name)} icon={<ArrowUpFromDot/>} title="Update"></IconButton>
: <IconButton disabled={plugin.name == "ep_etherpad-lite"} icon={<Trash/>} title={<Trans i18nKey="admin_plugins.installed_uninstall.value"/>} onClick={() => uninstallPlugin(plugin.name)}/> : <IconButton disabled={plugin.name == "ep_etherpad-lite"} icon={<Trash/>} title={<Trans i18nKey="admin_plugins.installed_uninstall.value"/>} onClick={() => uninstallPlugin(plugin.name)}/>
} }
</td> </td>

View file

@ -1,6 +1,7 @@
import {create} from "zustand"; import {create} from "zustand";
import {Socket} from "socket.io-client"; import {Socket} from "socket.io-client";
import {PadSearchResult} from "../utils/PadSearch.ts"; import {PadSearchResult} from "../utils/PadSearch.ts";
import {InstalledPlugin} from "../pages/Plugin.ts";
type ToastState = { type ToastState = {
description?:string, description?:string,
@ -22,7 +23,9 @@ type StoreState = {
toastState: ToastState, toastState: ToastState,
setToastState: (val: ToastState)=>void, setToastState: (val: ToastState)=>void,
pads: PadSearchResult|undefined, pads: PadSearchResult|undefined,
setPads: (pads: PadSearchResult)=>void setPads: (pads: PadSearchResult)=>void,
installedPlugins: InstalledPlugin[],
setInstalledPlugins: (plugins: InstalledPlugin[])=>void
} }
@ -43,5 +46,7 @@ export const useStore = create<StoreState>()((set) => ({
success: false success: false
}, },
pads: undefined, pads: undefined,
setPads: (pads)=>set({pads}) setPads: (pads)=>set({pads}),
installedPlugins: [],
setInstalledPlugins: (plugins)=>set({installedPlugins: plugins})
})); }));

View file

@ -1,6 +1,6 @@
{ {
"name": "bin", "name": "bin",
"version": "2.2.5", "version": "2.2.6",
"description": "", "description": "",
"main": "checkAllPads.js", "main": "checkAllPads.js",
"directories": { "directories": {
@ -12,12 +12,12 @@
"log4js": "^6.9.1", "log4js": "^6.9.1",
"semver": "^7.6.3", "semver": "^7.6.3",
"tsx": "^4.19.1", "tsx": "^4.19.1",
"ueberdb2": "^5.0.2" "ueberdb2": "^5.0.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.5.5", "@types/node": "^22.7.9",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"typescript": "^5.6.2" "typescript": "^5.6.3"
}, },
"scripts": { "scripts": {
"makeDocs": "node --import tsx make_docs.ts", "makeDocs": "node --import tsx make_docs.ts",

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -16,10 +16,18 @@
] ]
}, },
"admin.page-title": "لوحة تحكم المسؤول - Etherpad", "admin.page-title": "لوحة تحكم المسؤول - Etherpad",
"admin_plugins": "مدير المكونات الإضافية",
"admin_plugins.available": "المكونات الإضافية المتاحة",
"admin_plugins.available_not-found": "لم يتم العثور على أي مكونات إضافية.",
"admin_plugins.available_fetching": "جاري الجلب…",
"admin_plugins.available_install.value": "تنصيب",
"admin_plugins.available_search.placeholder": "ابحث عن المكونات الإضافية للتثبيت",
"admin_plugins.description": "الوصف", "admin_plugins.description": "الوصف",
"admin_plugins.installed": "الإضافات المثبتة", "admin_plugins.installed": "الإضافات المثبتة",
"admin_plugins.installed_fetching": "جارٍ إحضار المكونات الإضافية المثبتة ...", "admin_plugins.installed_fetching": "جارٍ إحضار المكونات الإضافية المثبتة ...",
"admin_plugins.installed_nothing": "لم تقم بتثبيت أي مكونات إضافية حتى الآن.", "admin_plugins.installed_nothing": "لم تقم بتثبيت أي مكونات إضافية حتى الآن.",
"admin_plugins.installed_uninstall.value": "إلغاء التثبيت",
"admin_plugins.last-update": "آخر تحديث",
"admin_plugins.name": "الاسم", "admin_plugins.name": "الاسم",
"admin_plugins.page-title": "مدير البرنامج المساعد - Etherpad", "admin_plugins.page-title": "مدير البرنامج المساعد - Etherpad",
"admin_plugins.version": "الإصدار", "admin_plugins.version": "الإصدار",
@ -31,8 +39,10 @@
"admin_plugins_info.plugins": "الإضافات المثبتة", "admin_plugins_info.plugins": "الإضافات المثبتة",
"admin_plugins_info.page-title": "معلومات البرنامج المساعد - Etherpad", "admin_plugins_info.page-title": "معلومات البرنامج المساعد - Etherpad",
"admin_plugins_info.version": "إصدار Etherpad", "admin_plugins_info.version": "إصدار Etherpad",
"admin_plugins_info.version_latest": "أحدث إصدار متاح",
"admin_plugins_info.version_number": "رقم الإصدار", "admin_plugins_info.version_number": "رقم الإصدار",
"admin_settings": "إعدادات", "admin_settings": "إعدادات",
"admin_settings.current": "التكوين الحالي",
"admin_settings.current_example-devel": "مثال على قالب إعدادات التطوير", "admin_settings.current_example-devel": "مثال على قالب إعدادات التطوير",
"admin_settings.current_example-prod": "مثال على قالب إعدادات الإنتاج", "admin_settings.current_example-prod": "مثال على قالب إعدادات الإنتاج",
"admin_settings.current_restart.value": "أعد تشغيل Etherpad", "admin_settings.current_restart.value": "أعد تشغيل Etherpad",
@ -110,6 +120,8 @@
"pad.modals.deleted": "محذوف.", "pad.modals.deleted": "محذوف.",
"pad.modals.deleted.explanation": "تمت إزالة هذا الباد.", "pad.modals.deleted.explanation": "تمت إزالة هذا الباد.",
"pad.modals.rateLimited": "معدل محدود.", "pad.modals.rateLimited": "معدل محدود.",
"pad.modals.rateLimited.explanation": "لقد أرسلت الكثير من الرسائل إلى هذه اللوحة مما أدى إلى قطع الاتصال بك.",
"pad.modals.rejected.explanation": "رفض الخادم الرسالة التي أرسلها متصفحك.",
"pad.modals.rejected.cause": "ربما تم تحديث الخادم أثناء عرض اللوحة ، أو ربما كان هناك خطأ في Etherpad. حاول إعادة تحميل الصفحة.", "pad.modals.rejected.cause": "ربما تم تحديث الخادم أثناء عرض اللوحة ، أو ربما كان هناك خطأ في Etherpad. حاول إعادة تحميل الصفحة.",
"pad.modals.disconnected": "لم تعد متصلا.", "pad.modals.disconnected": "لم تعد متصلا.",
"pad.modals.disconnected.explanation": "تم فقدان الاتصال بالخادم", "pad.modals.disconnected.explanation": "تم فقدان الاتصال بالخادم",

View file

@ -82,7 +82,9 @@
"pad.settings.colorcheck": "Autorenfarben anzeigen", "pad.settings.colorcheck": "Autorenfarben anzeigen",
"pad.settings.linenocheck": "Zeilennummern", "pad.settings.linenocheck": "Zeilennummern",
"pad.settings.rtlcheck": "Inhalt von rechts nach links lesen?", "pad.settings.rtlcheck": "Inhalt von rechts nach links lesen?",
"pad.settings.fontType": "Schriftart:", "pad.settings.delete": "Pad löschen",
"pad.delete.confirm": "Möchtest du dieses Pad wirklich löschen?",
"pad.settings.fontType": "Schriftart:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.language": "Sprache:", "pad.settings.language": "Sprache:",
"pad.settings.about": "Über", "pad.settings.about": "Über",

View file

@ -72,6 +72,8 @@
"pad.settings.fontType": "Font type:", "pad.settings.fontType": "Font type:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.language": "Language:", "pad.settings.language": "Language:",
"pad.settings.deletePad": "Delete Pad",
"pad.delete.confirm": "Do you really want to delete this pad?",
"pad.settings.about": "About", "pad.settings.about": "About",
"pad.settings.poweredBy": "Powered by", "pad.settings.poweredBy": "Powered by",

View file

@ -21,6 +21,7 @@
"Peter17", "Peter17",
"Quenenni", "Quenenni",
"Rastus Vernon", "Rastus Vernon",
"Spf",
"Stephane Cottin", "Stephane Cottin",
"Thibaut120094", "Thibaut120094",
"Tux-tn", "Tux-tn",
@ -157,7 +158,7 @@
"timeslider.toolbar.exportlink.title": "Exporter", "timeslider.toolbar.exportlink.title": "Exporter",
"timeslider.exportCurrent": "Exporter la version actuelle sous:", "timeslider.exportCurrent": "Exporter la version actuelle sous:",
"timeslider.version": "Version {{version}}", "timeslider.version": "Version {{version}}",
"timeslider.saved": "Enregistré le {{day}} {{month}} {{year}}", "timeslider.saved": "Enregistrée le {{day}} {{month}} {{year}}",
"timeslider.playPause": "Lecture/Pause des contenus du bloc-notes", "timeslider.playPause": "Lecture/Pause des contenus du bloc-notes",
"timeslider.backRevision": "Reculer dune révision dans ce bloc-notes", "timeslider.backRevision": "Reculer dune révision dans ce bloc-notes",
"timeslider.forwardRevision": "Avancer dune révision dans ce bloc-notes", "timeslider.forwardRevision": "Avancer dune révision dans ce bloc-notes",

View file

@ -25,7 +25,7 @@
"pad.toolbar.import_export.title": "ਵੱਖ-ਵੱਖ ਫਾਇਲ ਫਾਰਮੈਟ ਤੋਂ/ਵਿੱਚ ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ ਕਰੋ", "pad.toolbar.import_export.title": "ਵੱਖ-ਵੱਖ ਫਾਇਲ ਫਾਰਮੈਟ ਤੋਂ/ਵਿੱਚ ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ ਕਰੋ",
"pad.toolbar.timeslider.title": "ਸਮਾਂ-ਲਕੀਰ", "pad.toolbar.timeslider.title": "ਸਮਾਂ-ਲਕੀਰ",
"pad.toolbar.savedRevision.title": "ਦੁਹਰਾਅ ਸਾਂਭੋ", "pad.toolbar.savedRevision.title": "ਦੁਹਰਾਅ ਸਾਂਭੋ",
"pad.toolbar.settings.title": "ਸੈਟਿੰਗ", "pad.toolbar.settings.title": "ਪਸੰਦਾਂ",
"pad.toolbar.embed.title": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਤੇ ਇੰਬੈੱਡ ਕਰੋ", "pad.toolbar.embed.title": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਤੇ ਇੰਬੈੱਡ ਕਰੋ",
"pad.toolbar.showusers.title": "ਇਸ ਫੱਟੀ ਉੱਤੇ ਵਰਤੋਂਕਾਰ ਵਿਖਾਓ", "pad.toolbar.showusers.title": "ਇਸ ਫੱਟੀ ਉੱਤੇ ਵਰਤੋਂਕਾਰ ਵਿਖਾਓ",
"pad.colorpicker.save": "ਸੰਭਾਲੋ", "pad.colorpicker.save": "ਸੰਭਾਲੋ",
@ -41,7 +41,7 @@
"pad.settings.linenocheck": "ਲਕੀਰ ਨੰਬਰ", "pad.settings.linenocheck": "ਲਕੀਰ ਨੰਬਰ",
"pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?", "pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?",
"pad.settings.fontType": "ਫੋਂਟ ਕਿਸਮ:", "pad.settings.fontType": "ਫੋਂਟ ਕਿਸਮ:",
"pad.settings.fontType.normal": "ਸਧਾਰਨ", "pad.settings.fontType.normal": "ਆਮ",
"pad.settings.language": "ਭਾਸ਼ਾ:", "pad.settings.language": "ਭਾਸ਼ਾ:",
"pad.importExport.import_export": "ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ", "pad.importExport.import_export": "ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ",
"pad.importExport.import": "ਕੋਈ ਵੀ ਟੈਕਸਟ ਫਾਇਲ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਅੱਪਲੋਡ ਕਰੋ", "pad.importExport.import": "ਕੋਈ ਵੀ ਟੈਕਸਟ ਫਾਇਲ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਅੱਪਲੋਡ ਕਰੋ",
@ -80,11 +80,11 @@
"pad.modals.disconnected.cause": "ਸਰਵਰ ਨਾਮੌਜੂਦ ਹੋ ਸਕਦਾ ਹੈ। ਜੇਕਰ ਇਹ ਹੁੰਦਾ ਰਹੇ ਤਾਂ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਸੇਵਾ ਪ੍ਰਬੰਧਕ ਨੂੰ ਖ਼ਬਰ ਕਰੋ।", "pad.modals.disconnected.cause": "ਸਰਵਰ ਨਾਮੌਜੂਦ ਹੋ ਸਕਦਾ ਹੈ। ਜੇਕਰ ਇਹ ਹੁੰਦਾ ਰਹੇ ਤਾਂ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਸੇਵਾ ਪ੍ਰਬੰਧਕ ਨੂੰ ਖ਼ਬਰ ਕਰੋ।",
"pad.share": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਕਰੋ", "pad.share": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਕਰੋ",
"pad.share.readonly": "ਕੇਵਲ ਪੜ੍ਹਨ ਲਈ", "pad.share.readonly": "ਕੇਵਲ ਪੜ੍ਹਨ ਲਈ",
"pad.share.link": "ਲਿੰਕ", "pad.share.link": "ੜੀ",
"pad.share.emebdcode": "ਇੰਬੈੱਡ URL", "pad.share.emebdcode": "ਇੰਬੈੱਡ URL",
"pad.chat": "ਗੱਲਬਾਤ", "pad.chat": "ਗੱਲਬਾਤ",
"pad.chat.title": "ਇਹ ਪੈਡ ਲਈ ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ।", "pad.chat.title": "ਇਹ ਪੈਡ ਲਈ ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ।",
"pad.chat.loadmessages": "ਹੋਰ ਸੁਨੇਹੇ ਲੋਡ ਕਰੋ", "pad.chat.loadmessages": "ਹੋਰ ਸੁਨੇਹੇ ਲੱਦੋ",
"timeslider.pageTitle": "{{appTitle}} ਸਮਾਂ-ਲਕੀਰ", "timeslider.pageTitle": "{{appTitle}} ਸਮਾਂ-ਲਕੀਰ",
"timeslider.toolbar.returnbutton": "ਪੈਡ ਉੱਤੇ ਵਾਪਸ", "timeslider.toolbar.returnbutton": "ਪੈਡ ਉੱਤੇ ਵਾਪਸ",
"timeslider.toolbar.authors": "ਲੇਖਕ:", "timeslider.toolbar.authors": "ਲੇਖਕ:",

View file

@ -43,7 +43,7 @@ import {RateLimiterMemory} from 'rate-limiter-flexible';
import {ChangesetRequest, PadUserInfo, SocketClientRequest} from "../types/SocketClientRequest"; import {ChangesetRequest, PadUserInfo, SocketClientRequest} from "../types/SocketClientRequest";
import {APool, AText, PadAuthor, PadType} from "../types/PadType"; import {APool, AText, PadAuthor, PadType} from "../types/PadType";
import {ChangeSet} from "../types/ChangeSet"; import {ChangeSet} from "../types/ChangeSet";
import {ChatMessageMessage, ClientReadyMessage, ClientSaveRevisionMessage, ClientSuggestUserName, ClientUserChangesMessage, ClientVarMessage, CustomMessage, UserNewInfoMessage} from "../../static/js/types/SocketIOMessage"; import {ChatMessageMessage, ClientReadyMessage, ClientSaveRevisionMessage, ClientSuggestUserName, ClientUserChangesMessage, ClientVarMessage, CustomMessage, PadDeleteMessage, UserNewInfoMessage} from "../../static/js/types/SocketIOMessage";
import {Builder} from "../../static/js/Builder"; import {Builder} from "../../static/js/Builder";
const webaccess = require('../hooks/express/webaccess'); const webaccess = require('../hooks/express/webaccess');
const { checkValidRev } = require('../utils/checkValidRev'); const { checkValidRev } = require('../utils/checkValidRev');
@ -211,6 +211,45 @@ exports.handleDisconnect = async (socket:any) => {
}); });
}; };
const handlePadDelete = async (socket: any, padDeleteMessage: PadDeleteMessage) => {
const session = sessioninfos[socket.id];
if (!session || !session.author || !session.padId) throw new Error('session not ready');
if (await padManager.doesPadExist(padDeleteMessage.data.padId)) {
const retrievedPad = await padManager.getPad(padDeleteMessage.data.padId)
// Only the one doing the first revision can delete the pad, otherwise people could troll a lot
const firstContributor = await retrievedPad.getRevisionAuthor(0)
if (session.author === firstContributor) {
retrievedPad.remove()
} else {
type ShoutMessage = {
message: string,
sticky: boolean,
}
const messageToShout: ShoutMessage = {
message: 'You are not the creator of this pad, so you cannot delete it',
sticky: false
}
const messageToSend = {
type: "COLLABROOM",
data: {
type: "shoutMessage",
payload: {
message: messageToShout,
timestamp: Date.now()
}
}
}
socket.emit('shout',
messageToSend
)
}
}
}
/** /**
* Handles a message from a user * Handles a message from a user
* @param socket the socket.io Socket object for the client * @param socket the socket.io Socket object for the client
@ -350,6 +389,7 @@ exports.handleMessage = async (socket:any, message: ClientVarMessage) => {
stats.counter('pendingEdits').inc(); stats.counter('pendingEdits').inc();
await padChannels.enqueue(thisSession.padId, {socket, message}); await padChannels.enqueue(thisSession.padId, {socket, message});
break; break;
case 'PAD_DELETE': await handlePadDelete(socket, message.data as unknown as PadDeleteMessage); break;
case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message as unknown as UserNewInfoMessage); break; case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message as unknown as UserNewInfoMessage); break;
case 'CHAT_MESSAGE': await handleChatMessage(socket, message as unknown as ChatMessageMessage); break; case 'CHAT_MESSAGE': await handleChatMessage(socket, message as unknown as ChatMessageMessage); break;
case 'GET_CHAT_MESSAGES': await handleGetChatMessages(socket, message); break; case 'GET_CHAT_MESSAGES': await handleGetChatMessages(socket, message); break;

View file

@ -6,10 +6,10 @@ import {QueryType} from "../../types/QueryType";
import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer"; import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer";
import {PackageData} from "../../types/PackageInfo"; import {PackageData} from "../../types/PackageInfo";
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
import semver from 'semver'; import semver from 'semver';
import log4js from 'log4js'; import log4js from 'log4js';
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
const logger = log4js.getLogger('adminPlugins'); const logger = log4js.getLogger('adminPlugins');
@ -20,10 +20,28 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request; const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
if (!isAdmin) return; if (!isAdmin) return;
socket.on('getInstalled', (query:string) => { const checkPluginForUpdates = async () => {
const results = await getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
return Object.keys(pluginDefs.plugins).filter((plugin) => {
if (!results[plugin]) return false;
const latestVersion = results[plugin].version;
const currentVersion = pluginDefs.plugins[plugin].package.version;
return semver.gt(latestVersion, currentVersion);
})
}
socket.on('getInstalled', async (query: string) => {
// send currently installed plugins // send currently installed plugins
const installed = const installed =
Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package); Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
const updatable = await checkPluginForUpdates();
installed.forEach((plugin) => {
plugin.updatable = updatable.includes(plugin.name);
})
socket.emit('results:installed', {installed}); socket.emit('results:installed', {installed});
}); });
@ -31,16 +49,7 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
socket.on('checkUpdates', async () => { socket.on('checkUpdates', async () => {
// Check plugins for updates // Check plugins for updates
try { try {
const results = await getAvailablePlugins(/* maxCacheAge:*/ 60 * 10); const updatable = checkPluginForUpdates();
const updatable = Object.keys(pluginDefs.plugins).filter((plugin) => {
if (!results[plugin]) return false;
const latestVersion = results[plugin].version;
const currentVersion = pluginDefs.plugins[plugin].package.version;
return semver.gt(latestVersion, currentVersion);
});
socket.emit('results:updatable', {updatable}); socket.emit('results:updatable', {updatable});
} catch (err) { } catch (err) {

View file

@ -112,7 +112,7 @@ const convertTypescript = (content: string) => {
} }
} }
const handleLiveReload = async (args: any, padString: string, timeSliderString: string, indexString: any) => { const handleLiveReload = async (args: ArgsExpressType, padString: string, timeSliderString: string, indexString: any) => {
const chokidar = await import('chokidar') const chokidar = await import('chokidar')
const watcher = chokidar.watch(path.join(settings.root, 'src', 'static', 'js'), {}); const watcher = chokidar.watch(path.join(settings.root, 'src', 'static', 'js'), {});
let routeHandlers: { [key: string]: Function } = {}; let routeHandlers: { [key: string]: Function } = {};

View file

@ -33,20 +33,20 @@
"@etherpad/express-session": "^1.18.4", "@etherpad/express-session": "^1.18.4",
"async": "^3.2.6", "async": "^3.2.6",
"axios": "^1.7.7", "axios": "^1.7.7",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.7",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.3",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"esbuild": "^0.23.1", "esbuild": "^0.24.0",
"express": "4.21.0", "express": "4.21.1",
"express-rate-limit": "^7.4.0", "express-rate-limit": "^7.4.1",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"find-root": "1.1.0", "find-root": "1.1.0",
"formidable": "^3.5.1", "formidable": "^3.5.2",
"http-errors": "^2.0.0", "http-errors": "^2.0.0",
"jose": "^5.9.2", "jose": "^5.9.6",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsdom": "^25.0.0", "jsdom": "^25.0.1",
"jsonminify": "0.4.2", "jsonminify": "0.4.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"languages4translatewiki": "0.1.3", "languages4translatewiki": "0.1.3",
@ -56,23 +56,23 @@
"lru-cache": "^11.0.1", "lru-cache": "^11.0.1",
"measured-core": "^2.0.0", "measured-core": "^2.0.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"oidc-provider": "^8.5.1", "oidc-provider": "^8.5.2",
"openapi-backend": "^5.11.0", "openapi-backend": "^5.11.0",
"proxy-addr": "^2.0.7", "proxy-addr": "^2.0.7",
"rate-limiter-flexible": "^5.0.3", "rate-limiter-flexible": "^5.0.4",
"rehype": "^13.0.1", "rehype": "^13.0.2",
"rehype-minify-whitespace": "^6.0.1", "rehype-minify-whitespace": "^6.0.2",
"resolve": "1.22.8", "resolve": "1.22.8",
"rusty-store-kv": "^1.3.1", "rusty-store-kv": "^1.3.1",
"security": "1.0.0", "security": "1.0.0",
"semver": "^7.6.3", "semver": "^7.6.3",
"socket.io": "^4.7.5", "socket.io": "^4.8.0",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.8.0",
"superagent": "10.1.0", "superagent": "10.1.1",
"swagger-ui-express": "^5.0.1", "swagger-ui-express": "^5.0.1",
"tinycon": "0.6.8", "tinycon": "0.6.8",
"tsx": "4.19.1", "tsx": "4.19.1",
"ueberdb2": "^5.0.2", "ueberdb2": "^5.0.6",
"underscore": "1.13.7", "underscore": "1.13.7",
"unorm": "1.6.0", "unorm": "1.6.0",
"wtfnode": "^0.9.3" "wtfnode": "^0.9.3"
@ -82,39 +82,39 @@
"etherpad-lite": "node/server.ts" "etherpad-lite": "node/server.ts"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.47.1", "@playwright/test": "^1.48.1",
"@types/async": "^3.2.24", "@types/async": "^3.2.24",
"@types/express": "^4.17.21", "@types/express": "4.17.21",
"@types/formidable": "^3.4.5", "@types/formidable": "^3.4.5",
"@types/http-errors": "^2.0.4", "@types/http-errors": "^2.0.4",
"@types/jquery": "^3.5.30", "@types/jquery": "^3.5.32",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/jsdom": "^21.1.7", "@types/jsdom": "^21.1.7",
"@types/jsonwebtoken": "^9.0.7", "@types/jsonwebtoken": "^9.0.7",
"@types/mime-types": "^2.1.4", "@types/mime-types": "^2.1.4",
"@types/mocha": "^10.0.8", "@types/mocha": "^10.0.9",
"@types/node": "^22.5.5", "@types/node": "^22.7.9",
"@types/oidc-provider": "^8.5.2", "@types/oidc-provider": "^8.5.2",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"@types/sinon": "^17.0.3", "@types/sinon": "^17.0.3",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.6", "@types/swagger-ui-express": "^4.1.6",
"@types/underscore": "^1.11.15", "@types/underscore": "^1.13.0",
"@types/whatwg-mimetype": "^3.0.2", "@types/whatwg-mimetype": "^3.0.2",
"chokidar": "^4.0.0", "chokidar": "^4.0.1",
"eslint": "^9.10.0", "eslint": "^9.13.0",
"eslint-config-etherpad": "^4.0.4", "eslint-config-etherpad": "^4.0.4",
"etherpad-cli-client": "^3.0.2", "etherpad-cli-client": "^3.0.2",
"mocha": "^10.7.3", "mocha": "^10.7.3",
"mocha-froth": "^0.2.10", "mocha-froth": "^0.2.10",
"nodeify": "^1.0.1", "nodeify": "^1.0.1",
"openapi-schema-validation": "^0.4.2", "openapi-schema-validation": "^0.4.2",
"set-cookie-parser": "^2.7.0", "set-cookie-parser": "^2.7.1",
"sinon": "^19.0.2", "sinon": "^19.0.2",
"split-grid": "^1.0.11", "split-grid": "^1.0.11",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"typescript": "^5.6.2", "typescript": "^5.6.3",
"vitest": "^2.1.1" "vitest": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=18.18.2", "node": ">=18.18.2",
@ -141,6 +141,6 @@
"debug:socketio": "cross-env DEBUG=socket.io* node --require tsx/cjs node/server.ts", "debug:socketio": "cross-env DEBUG=socket.io* node --require tsx/cjs node/server.ts",
"test:vitest": "vitest" "test:vitest": "vitest"
}, },
"version": "2.2.5", "version": "2.2.6",
"license": "Apache-2.0" "license": "Apache-2.0"
} }

View file

@ -75,11 +75,20 @@ const padeditor = (() => {
padutils.setCheckbox($('#options-rtlcheck'), ('rtl' === html10n.getDirection())); padutils.setCheckbox($('#options-rtlcheck'), ('rtl' === html10n.getDirection()));
}); });
// font family change // font family change
$('#viewfontmenu').on('change', () => { $('#viewfontmenu').on('change', () => {
pad.changeViewOption('padFontFamily', $('#viewfontmenu').val()); pad.changeViewOption('padFontFamily', $('#viewfontmenu').val());
}); });
// delete pad
$('#delete-pad').on('click', () => {
if (window.confirm(html10n.get('pad.delete.confirm'))) {
pad.collabClient.sendMessage({type: 'PAD_DELETE', data:{padId: pad.getPadId()}});
}
})
// Language // Language
html10n.bind('localized', () => { html10n.bind('localized', () => {
$('#languagemenu').val(html10n.getLanguage()); $('#languagemenu').val(html10n.getLanguage());

View file

@ -192,6 +192,14 @@ export type ClientSaveRevisionMessage = {
type: 'SAVE_REVISION' type: 'SAVE_REVISION'
} }
export type PadDeleteMessage = {
type: 'PAD_DELETE'
data: {
padId: string
}
}
export type GetChatMessageMessage = { export type GetChatMessageMessage = {
type: 'GET_CHAT_MESSAGES', type: 'GET_CHAT_MESSAGES',
start: number, start: number,
@ -283,7 +291,7 @@ export type ChangesetRequestMessage = {
export type CollabroomMessage = { export type CollabroomMessage = {
type: 'COLLABROOM' type: 'COLLABROOM'
data: ClientSendUserInfoUpdate | ClientUserChangesMessage | ChatMessageMessage | GetChatMessageMessage | ClientSaveRevisionMessage | ClientMessageMessage data: ClientSendUserInfoUpdate | ClientUserChangesMessage | ChatMessageMessage | GetChatMessageMessage | ClientSaveRevisionMessage | ClientMessageMessage | PadDeleteMessage
} }
export type ClientVarMessage = | ClientVarData | ClientDisconnectedMessage | ClientReadyMessage| ChangesetRequestMessage | CollabroomMessage | CustomMessage export type ClientVarMessage = | ClientVarData | ClientDisconnectedMessage | ClientReadyMessage| ChangesetRequestMessage | CollabroomMessage | CustomMessage

View file

@ -81,3 +81,7 @@
.skin-variant-container { .skin-variant-container {
text-transform: capitalize; text-transform: capitalize;
} }
#delete-pad {
background-color: #ff7b72;
}

View file

@ -164,10 +164,10 @@
</p> </p>
<% e.end_block(); %> <% e.end_block(); %>
</div> </div>
<button data-l10n-id="pad.settings.delete" id="delete-pad">Delete pad</button>
<h2 data-l10n-id="pad.settings.about">About</h2> <h2 data-l10n-id="pad.settings.about">About</h2>
<span data-l10n-id="pad.settings.poweredBy">Powered by</span> <span data-l10n-id="pad.settings.poweredBy">Powered by</span>
<a href="https://etherpad.org">Etherpad</a> <a href="https://etherpad.org" target="_blank" referrerpolicy="no-referrer" rel="noopener">Etherpad</a>
<% if (settings.exposeVersion) { %>(commit <%=settings.getGitCommit()%>)<% } %> <% if (settings.exposeVersion) { %>(commit <%=settings.getGitCommit()%>)<% } %>
</div></div> </div></div>

View file

@ -250,6 +250,19 @@ export const sendUserChanges = async (socket:any, data:any) => await sendMessage
}, },
}); });
/*
* Convenience function to send a delete pad request.
*/
export const sendPadDelete = async (socket:any, data:any) => await sendMessage(socket, {
type: 'PAD_DELETE',
component: 'pad',
data: {
padId: data.padId
},
});
/** /**
* Convenience function that waits for an ACCEPT_COMMIT message. Asserts that the new revision * Convenience function that waits for an ACCEPT_COMMIT message. Asserts that the new revision
* matches the expected revision. * matches the expected revision.

View file

@ -11,7 +11,7 @@
}, },
"devDependencies": { "devDependencies": {
"ep_etherpad-lite": "workspace:../src", "ep_etherpad-lite": "workspace:../src",
"typescript": "^5.6.2", "typescript": "^5.6.3",
"vite": "^5.4.7" "vite": "^5.4.10"
} }
} }

View file

@ -385,7 +385,7 @@
</p> </p>
</div> </div>
<button data-l10n-id="pad.settings.delete">Delete pad</button>
<h2 data-l10n-id="pad.settings.about">About</h2> <h2 data-l10n-id="pad.settings.about">About</h2>
<span data-l10n-id="pad.settings.poweredBy">Powered by</span> <span data-l10n-id="pad.settings.poweredBy">Powered by</span>
<a href="https://etherpad.org">Etherpad</a> <a href="https://etherpad.org">Etherpad</a>