From 197abbf1dc8d11a22baf638fdb5dbd21b4b0f5bc Mon Sep 17 00:00:00 2001 From: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:43:24 +0200 Subject: [PATCH] chore: added possibility to create pads via admin menu --- admin/src/components/IconButton.tsx | 5 +- admin/src/index.css | 27 ++++++++++ admin/src/pages/PadPage.tsx | 69 +++++++++++++++++++++++-- src/locales/de.json | 1 + src/node/hooks/express/adminsettings.ts | 19 +++++++ 5 files changed, 116 insertions(+), 5 deletions(-) diff --git a/admin/src/components/IconButton.tsx b/admin/src/components/IconButton.tsx index 876a557795f..a93c9877c8f 100644 --- a/admin/src/components/IconButton.tsx +++ b/admin/src/components/IconButton.tsx @@ -1,6 +1,7 @@ import {FC, JSX, ReactElement} from "react"; export type IconButtonProps = { + style?: React.CSSProperties, icon: JSX.Element, title: string|ReactElement, onClick: ()=>void, @@ -8,8 +9,8 @@ export type IconButtonProps = { disabled?: boolean } -export const IconButton:FC = ({icon,className,onClick,title, disabled})=>{ - return diff --git a/admin/src/index.css b/admin/src/index.css index acc0d2e970a..3190b153da6 100644 --- a/admin/src/index.css +++ b/admin/src/index.css @@ -42,6 +42,9 @@ div.menu { position: fixed; } +[role="dialog"] h2 { + color: var(--etherpad-color); +} .icon-button { display: flex; @@ -54,6 +57,26 @@ div.menu { cursor: pointer; } +.icon-button:hover { + background-color: #13a37c; +} + + +.dialog-close-button { + position: absolute; + top: 10px; + right: 10px; + background: none; + border: none; + cursor: pointer; + color: var(--etherpad-color); +} + +.icon-button:active { + background-color: #13a37c; + transform: scale(0.98); +} + .icon-button svg { align-self: center; } @@ -868,3 +891,7 @@ input, button, select, optgroup, textarea { display: flex; justify-content: center; } + +.manage-pads-header { + display: flex; +} diff --git a/admin/src/pages/PadPage.tsx b/admin/src/pages/PadPage.tsx index b5db854f567..0d4da31ff80 100644 --- a/admin/src/pages/PadPage.tsx +++ b/admin/src/pages/PadPage.tsx @@ -6,8 +6,13 @@ import {useDebounce} from "../utils/useDebounce.ts"; import {determineSorting} from "../utils/sorting.ts"; import * as Dialog from "@radix-ui/react-dialog"; import {IconButton} from "../components/IconButton.tsx"; -import {ChevronLeft, ChevronRight, Eye, Trash2, FileStack} from "lucide-react"; +import {ChevronLeft, ChevronRight, Eye, Trash2, FileStack, PlusIcon} from "lucide-react"; import {SearchField} from "../components/SearchField.tsx"; +import {useForm} from "react-hook-form"; + +type PadCreateProps = { + padName: string +} export const PadPage = ()=>{ const settingsSocket = useStore(state=>state.settingsSocket) @@ -25,6 +30,8 @@ export const PadPage = ()=>{ const [deleteDialog, setDeleteDialog] = useState(false) const [errorText, setErrorText] = useState(null) const [padToDelete, setPadToDelete] = useState('') + const [createPadDialogOpen, setCreatePadDialogOpen] = useState(false) + const {register, handleSubmit} = useForm() const pages = useMemo(()=>{ if(!pads){ return 0; @@ -70,8 +77,33 @@ export const PadPage = ()=>{ }) }) + type SettingsSocketCreateReponse = { + error: string + } | { + success: string + } + + settingsSocket.on('results:createPad', (rep: SettingsSocketCreateReponse)=>{ + if ('error' in rep) { + useStore.getState().setToastState({ + open: true, + title: rep.error, + success: false + }) + } else { + useStore.getState().setToastState({ + open: true, + title: rep.success, + success: true + }) + setCreatePadDialogOpen(false) + // reload pads + settingsSocket.emit('padLoad', searchParams) + } + }) + settingsSocket.on('results:cleanupPadRevisions', (data)=>{ - let newPads = useStore.getState().pads?.results ?? [] + const newPads = useStore.getState().pads?.results ?? [] if (data.error) { setErrorText(data.error) @@ -99,6 +131,12 @@ export const PadPage = ()=>{ settingsSocket?.emit('cleanupPadRevisions', padID) } + const onPadCreate = (data: PadCreateProps)=>{ + settingsSocket?.emit('createPad', { + padName: data.padName + }) + } + return
@@ -139,7 +177,32 @@ export const PadPage = ()=>{ -

+ + + + + +
+ +
+ + +
+ +
+
+
+
+ +

+ } title={} onClick={()=>{ + setCreatePadDialogOpen(true) + }}/> +
setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/> diff --git a/src/locales/de.json b/src/locales/de.json index 3c324e6a7a7..a7194b5a58a 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -52,6 +52,7 @@ "admin_settings.current_save.value": "Einstellungen speichern", "admin_settings.page-title": "Einstellungen - Etherpad", "index.newPad": "Neues Pad", + "admin_settings.createPad": "Erstellen", "index.createOpenPad": "Pad öffnen", "index.openPad": "Öffne ein vorhandenes Pad mit folgendem Namen:", "index.recentPads": "Zuletzt bearbeitete Pads", diff --git a/src/node/hooks/express/adminsettings.ts b/src/node/hooks/express/adminsettings.ts index e646323f107..a782c6d40b7 100644 --- a/src/node/hooks/express/adminsettings.ts +++ b/src/node/hooks/express/adminsettings.ts @@ -251,6 +251,25 @@ exports.socketio = (hookName: string, {io}: any) => { } }) + type PadCreationOptions = { + padName: string, + } + + socket.on('createPad', async ({padName}: PadCreationOptions)=>{ + const padExists = await padManager.doesPadExists(padName); + if (padExists) { + socket.emit('results:createPad', { + error: 'Pad already exists', + }); + return; + } + padManager.getPad(padName); + socket.emit('results:createPad', { + success: `Pad created ${padName}`, + }); + return; + }) + socket.on('cleanupPadRevisions', async (padId: string) => { if (!settings.cleanup.enabled) { socket.emit('results:cleanupPadRevisions', {