Skip to content

Commit a5413d7

Browse files
authored
chore: added possibility to create pads via admin menu (#7100)
1 parent 061ea19 commit a5413d7

File tree

5 files changed

+116
-5
lines changed

5 files changed

+116
-5
lines changed

admin/src/components/IconButton.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import {FC, JSX, ReactElement} from "react";
22

33
export type IconButtonProps = {
4+
style?: React.CSSProperties,
45
icon: JSX.Element,
56
title: string|ReactElement,
67
onClick: ()=>void,
78
className?: string,
89
disabled?: boolean
910
}
1011

11-
export const IconButton:FC<IconButtonProps> = ({icon,className,onClick,title, disabled})=>{
12-
return <button onClick={onClick} className={"icon-button "+ className} disabled={disabled}>
12+
export const IconButton:FC<IconButtonProps> = ({icon,className,onClick,title, disabled, style})=>{
13+
return <button style={style} onClick={onClick} className={"icon-button "+ className} disabled={disabled}>
1314
{icon}
1415
<span>{title}</span>
1516
</button>

admin/src/index.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ div.menu {
4242
position: fixed;
4343
}
4444

45+
[role="dialog"] h2 {
46+
color: var(--etherpad-color);
47+
}
4548

4649
.icon-button {
4750
display: flex;
@@ -54,6 +57,26 @@ div.menu {
5457
cursor: pointer;
5558
}
5659

60+
.icon-button:hover {
61+
background-color: #13a37c;
62+
}
63+
64+
65+
.dialog-close-button {
66+
position: absolute;
67+
top: 10px;
68+
right: 10px;
69+
background: none;
70+
border: none;
71+
cursor: pointer;
72+
color: var(--etherpad-color);
73+
}
74+
75+
.icon-button:active {
76+
background-color: #13a37c;
77+
transform: scale(0.98);
78+
}
79+
5780
.icon-button svg {
5881
align-self: center;
5982
}
@@ -868,3 +891,7 @@ input, button, select, optgroup, textarea {
868891
display: flex;
869892
justify-content: center;
870893
}
894+
895+
.manage-pads-header {
896+
display: flex;
897+
}

admin/src/pages/PadPage.tsx

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ import {useDebounce} from "../utils/useDebounce.ts";
66
import {determineSorting} from "../utils/sorting.ts";
77
import * as Dialog from "@radix-ui/react-dialog";
88
import {IconButton} from "../components/IconButton.tsx";
9-
import {ChevronLeft, ChevronRight, Eye, Trash2, FileStack} from "lucide-react";
9+
import {ChevronLeft, ChevronRight, Eye, Trash2, FileStack, PlusIcon} from "lucide-react";
1010
import {SearchField} from "../components/SearchField.tsx";
11+
import {useForm} from "react-hook-form";
12+
13+
type PadCreateProps = {
14+
padName: string
15+
}
1116

1217
export const PadPage = ()=>{
1318
const settingsSocket = useStore(state=>state.settingsSocket)
@@ -25,6 +30,8 @@ export const PadPage = ()=>{
2530
const [deleteDialog, setDeleteDialog] = useState<boolean>(false)
2631
const [errorText, setErrorText] = useState<string|null>(null)
2732
const [padToDelete, setPadToDelete] = useState<string>('')
33+
const [createPadDialogOpen, setCreatePadDialogOpen] = useState<boolean>(false)
34+
const {register, handleSubmit} = useForm<PadCreateProps>()
2835
const pages = useMemo(()=>{
2936
if(!pads){
3037
return 0;
@@ -70,8 +77,33 @@ export const PadPage = ()=>{
7077
})
7178
})
7279

80+
type SettingsSocketCreateReponse = {
81+
error: string
82+
} | {
83+
success: string
84+
}
85+
86+
settingsSocket.on('results:createPad', (rep: SettingsSocketCreateReponse)=>{
87+
if ('error' in rep) {
88+
useStore.getState().setToastState({
89+
open: true,
90+
title: rep.error,
91+
success: false
92+
})
93+
} else {
94+
useStore.getState().setToastState({
95+
open: true,
96+
title: rep.success,
97+
success: true
98+
})
99+
setCreatePadDialogOpen(false)
100+
// reload pads
101+
settingsSocket.emit('padLoad', searchParams)
102+
}
103+
})
104+
73105
settingsSocket.on('results:cleanupPadRevisions', (data)=>{
74-
let newPads = useStore.getState().pads?.results ?? []
106+
const newPads = useStore.getState().pads?.results ?? []
75107

76108
if (data.error) {
77109
setErrorText(data.error)
@@ -99,6 +131,12 @@ export const PadPage = ()=>{
99131
settingsSocket?.emit('cleanupPadRevisions', padID)
100132
}
101133

134+
const onPadCreate = (data: PadCreateProps)=>{
135+
settingsSocket?.emit('createPad', {
136+
padName: data.padName
137+
})
138+
}
139+
102140

103141
return <div>
104142
<Dialog.Root open={deleteDialog}><Dialog.Portal>
@@ -139,7 +177,32 @@ export const PadPage = ()=>{
139177
</Dialog.Content>
140178
</Dialog.Portal>
141179
</Dialog.Root>
142-
<h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
180+
<Dialog.Root open={createPadDialogOpen}>
181+
<Dialog.Portal>
182+
<Dialog.Overlay className="dialog-confirm-overlay" />
183+
<Dialog.Content className="dialog-confirm-content">
184+
<Dialog.Title className="dialog-confirm-title"><Trans i18nKey="index.newPad"/></Dialog.Title>
185+
<form onSubmit={handleSubmit(onPadCreate)}>
186+
<button className="dialog-close-button" onClick={()=>{
187+
setCreatePadDialogOpen(false);
188+
}}>x</button>
189+
<div style={{display: 'grid', gap: '10px', gridTemplateColumns: 'auto auto', marginBottom: '1rem'}}>
190+
<label><Trans i18nKey="ep_admin_pads:ep_adminpads2_padname"/></label>
191+
<input {...register('padName', {
192+
required: true
193+
})}/>
194+
</div>
195+
<input type="submit" value={t('admin_settings.createPad')} className="login-button" />
196+
</form>
197+
</Dialog.Content>
198+
</Dialog.Portal>
199+
</Dialog.Root>
200+
<span className="manage-pads-header">
201+
<h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
202+
<span style={{width: '29px', marginBottom: 'auto', marginTop: 'auto', flexGrow: 1}}><IconButton style={{float: 'right'}} icon={<PlusIcon/>} title={<Trans i18nKey="index.newPad"/>} onClick={()=>{
203+
setCreatePadDialogOpen(true)
204+
}}/></span>
205+
</span>
143206
<SearchField value={searchTerm} onChange={v=>setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
144207
<table>
145208
<thead>

src/locales/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"admin_settings.current_save.value": "Einstellungen speichern",
5353
"admin_settings.page-title": "Einstellungen - Etherpad",
5454
"index.newPad": "Neues Pad",
55+
"admin_settings.createPad": "Erstellen",
5556
"index.createOpenPad": "Pad öffnen",
5657
"index.openPad": "Öffne ein vorhandenes Pad mit folgendem Namen:",
5758
"index.recentPads": "Zuletzt bearbeitete Pads",

src/node/hooks/express/adminsettings.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,25 @@ exports.socketio = (hookName: string, {io}: any) => {
251251
}
252252
})
253253

254+
type PadCreationOptions = {
255+
padName: string,
256+
}
257+
258+
socket.on('createPad', async ({padName}: PadCreationOptions)=>{
259+
const padExists = await padManager.doesPadExists(padName);
260+
if (padExists) {
261+
socket.emit('results:createPad', {
262+
error: 'Pad already exists',
263+
});
264+
return;
265+
}
266+
padManager.getPad(padName);
267+
socket.emit('results:createPad', {
268+
success: `Pad created ${padName}`,
269+
});
270+
return;
271+
})
272+
254273
socket.on('cleanupPadRevisions', async (padId: string) => {
255274
if (!settings.cleanup.enabled) {
256275
socket.emit('results:cleanupPadRevisions', {

0 commit comments

Comments
 (0)