diff --git a/admin/src/components/IconButton.tsx b/admin/src/components/IconButton.tsx index 876a55779..a93c9877c 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 acc0d2e97..3190b153d 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 b5db854f5..0d4da31ff 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 3c324e6a7..a7194b5a5 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 e646323f1..a782c6d40 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', {