mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-19 14:13:34 +01:00
feat(admin): Added shoutout to admin panel (#6346)
* Added shoutout * Added shoutout function * Fixed test. * Included feedback from review. * Removed unnecessary file
This commit is contained in:
parent
d64924e9f5
commit
e12be96102
14 changed files with 467 additions and 214 deletions
|
@ -9,19 +9,12 @@
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"@radix-ui/react-switch": "^1.0.3"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@radix-ui/react-toast": "^1.1.5",
|
||||||
"i18next": "^23.11.2",
|
|
||||||
"i18next-browser-languagedetector": "^7.2.1",
|
|
||||||
"lucide-react": "^0.372.0",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-hook-form": "^7.51.3",
|
|
||||||
"react-i18next": "^14.1.0",
|
|
||||||
"react-router-dom": "^6.22.3",
|
|
||||||
"zustand": "^4.5.2",
|
|
||||||
"@types/react": "^18.2.79",
|
"@types/react": "^18.2.79",
|
||||||
"@types/react-dom": "^18.2.25",
|
"@types/react-dom": "^18.2.25",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||||
|
@ -30,10 +23,19 @@
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
|
"i18next": "^23.11.2",
|
||||||
|
"i18next-browser-languagedetector": "^7.2.1",
|
||||||
|
"lucide-react": "^0.372.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.51.3",
|
||||||
|
"react-i18next": "^14.1.0",
|
||||||
|
"react-router-dom": "^6.22.3",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.2.9",
|
"vite": "^5.2.9",
|
||||||
"vite-plugin-static-copy": "^1.0.3",
|
"vite-plugin-static-copy": "^1.0.3",
|
||||||
"vite-plugin-svgr": "^4.2.0"
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
|
"zustand": "^4.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {NavLink, Outlet, useNavigate} from "react-router-dom";
|
||||||
import {useStore} from "./store/store.ts";
|
import {useStore} from "./store/store.ts";
|
||||||
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import {Cable, Construction, Crown, NotepadText, Wrench} from "lucide-react";
|
import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall} from "lucide-react";
|
||||||
|
|
||||||
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
||||||
export const App = ()=> {
|
export const App = ()=> {
|
||||||
|
@ -97,7 +97,9 @@ export const App = ()=> {
|
||||||
<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>
|
||||||
<li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
<li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
||||||
<li><NavLink to={"/pads"}><NotepadText/><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
<li><NavLink to={"/pads"}><NotepadText/><Trans
|
||||||
|
i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
||||||
|
<li><NavLink to={"/shout"}><PhoneCall/>Communication</NavLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
13
admin/src/components/ShoutType.ts
Normal file
13
admin/src/components/ShoutType.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export type ShoutType = {
|
||||||
|
type: string,
|
||||||
|
data:{
|
||||||
|
type: string,
|
||||||
|
payload: {
|
||||||
|
message: {
|
||||||
|
message: string,
|
||||||
|
sticky: boolean
|
||||||
|
},
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -604,6 +604,25 @@ pre {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.send-message {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-message input {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-message {
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-message svg {
|
||||||
|
position: absolute;
|
||||||
|
right: 3px;
|
||||||
|
bottom: -3px;
|
||||||
|
left: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
.search-field svg {
|
.search-field svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 3px;
|
left: 3px;
|
||||||
|
@ -733,3 +752,52 @@ input, button, select, optgroup, textarea {
|
||||||
right: 10px;
|
right: 10px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.SwitchRoot {
|
||||||
|
align-self: center;
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: 9999px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 2px 10px var(--black-a7);
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.SwitchRoot:focus {
|
||||||
|
box-shadow: 0 0 0 2px black;
|
||||||
|
}
|
||||||
|
.SwitchRoot[data-state='checked'] {
|
||||||
|
background-color: var(--etherpad-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.SwitchThumb {
|
||||||
|
display: block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 9999px;
|
||||||
|
box-shadow: 0 2px 2px var(--black-a7);
|
||||||
|
transition: transform 100ms;
|
||||||
|
transform: translateX(2px);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.SwitchThumb[data-state='checked'] {
|
||||||
|
transform: translateX(25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Label {
|
||||||
|
color: white;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
margin: 10px 20px 10px 10px;
|
||||||
|
border-radius: 10px 0 10px 10px;
|
||||||
|
background-color: var(--etherpad-color);
|
||||||
|
color: white
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {I18nextProvider} from "react-i18next";
|
||||||
import i18n from "./localization/i18n.ts";
|
import i18n from "./localization/i18n.ts";
|
||||||
import {PadPage} from "./pages/PadPage.tsx";
|
import {PadPage} from "./pages/PadPage.tsx";
|
||||||
import {ToastDialog} from "./utils/Toast.tsx";
|
import {ToastDialog} from "./utils/Toast.tsx";
|
||||||
|
import {ShoutPage} from "./pages/ShoutPage.tsx";
|
||||||
|
|
||||||
const router = createBrowserRouter(createRoutesFromElements(
|
const router = createBrowserRouter(createRoutesFromElements(
|
||||||
<><Route element={<App/>}>
|
<><Route element={<App/>}>
|
||||||
|
@ -20,6 +21,7 @@ const router = createBrowserRouter(createRoutesFromElements(
|
||||||
<Route path="/settings" element={<SettingsPage/>}/>
|
<Route path="/settings" element={<SettingsPage/>}/>
|
||||||
<Route path="/help" element={<HelpPage/>}/>
|
<Route path="/help" element={<HelpPage/>}/>
|
||||||
<Route path="/pads" element={<PadPage/>}/>
|
<Route path="/pads" element={<PadPage/>}/>
|
||||||
|
<Route path="/shout" element={<ShoutPage/>}/>
|
||||||
</Route><Route path="/login">
|
</Route><Route path="/login">
|
||||||
<Route index element={<LoginScreen/>}/>
|
<Route index element={<LoginScreen/>}/>
|
||||||
</Route></>
|
</Route></>
|
||||||
|
|
76
admin/src/pages/ShoutPage.tsx
Normal file
76
admin/src/pages/ShoutPage.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {SendHorizonal} from 'lucide-react'
|
||||||
|
import {useStore} from "../store/store.ts";
|
||||||
|
import * as Switch from '@radix-ui/react-switch';
|
||||||
|
import {ShoutType} from "../components/ShoutType.ts";
|
||||||
|
|
||||||
|
export const ShoutPage = ()=>{
|
||||||
|
const [totalUsers, setTotalUsers] = useState(0);
|
||||||
|
const [message, setMessage] = useState<string>("");
|
||||||
|
const [sticky, setSticky] = useState<boolean>(false);
|
||||||
|
const socket = useStore(state => state.settingsSocket);
|
||||||
|
const [shouts, setShouts] = useState<ShoutType[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/stats')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => setTotalUsers(data.totalUsers));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(socket) {
|
||||||
|
socket.on('shout', (shout) => {
|
||||||
|
setShouts([...shouts, shout])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [socket, shouts])
|
||||||
|
|
||||||
|
const sendMessage = () => {
|
||||||
|
socket?.emit('shout', {
|
||||||
|
message,
|
||||||
|
sticky
|
||||||
|
});
|
||||||
|
setMessage('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Communication</h1>
|
||||||
|
{totalUsers > 0 && <p>There {totalUsers>1?"are":"is"} currently {totalUsers} user{totalUsers>1?"s":""} online</p>}
|
||||||
|
<div style={{height: '80vh', display: 'flex', flexDirection: 'column'}}>
|
||||||
|
<div style={{flexGrow: 1, backgroundColor: 'white', overflowY: "auto"}}>
|
||||||
|
{
|
||||||
|
shouts.map((shout) => {
|
||||||
|
return (
|
||||||
|
<div key={shout.data.payload.timestamp} className="message">
|
||||||
|
<div>{shout.data.payload.message.message}</div>
|
||||||
|
<div style={{display: 'flex'}}>
|
||||||
|
<div style={{flexGrow: 1}}></div>
|
||||||
|
<div
|
||||||
|
style={{color: "lightgray"}}>{new Date(shout.data.payload.timestamp).toLocaleTimeString()
|
||||||
|
+ " " + new Date(shout.data.payload.timestamp).toLocaleDateString()}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<form onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
sendMessage()
|
||||||
|
}} className="send-message search-field" style={{display: 'flex', gap: '10px'}}>
|
||||||
|
<Switch.Root title="Change sticky message" className="SwitchRoot" checked={sticky}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
setSticky(!sticky);
|
||||||
|
}}>
|
||||||
|
<Switch.Thumb className="SwitchThumb"/>
|
||||||
|
</Switch.Root>
|
||||||
|
<input required value={message} onChange={v=>setMessage(v.target.value)}
|
||||||
|
style={{width: '100%', paddingRight: '55px', backgroundColor: '#e0e0e0', flexGrow: 1}}/>
|
||||||
|
<SendHorizonal style={{bottom: '5px', right: '9px', color: '#0f775b'}} onClick={()=>sendMessage()}/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -28,7 +28,10 @@ export default defineConfig({
|
||||||
'/admin-auth/': {
|
'/admin-auth/': {
|
||||||
target: 'http://localhost:9001',
|
target: 'http://localhost:9001',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/admin-prox/, '/admin/')
|
},
|
||||||
|
'/stats': {
|
||||||
|
target: 'http://localhost:9001',
|
||||||
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,5 +44,6 @@
|
||||||
"url": "https://github.com/ether/etherpad-lite.git"
|
"url": "https://github.com/ether/etherpad-lite.git"
|
||||||
},
|
},
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0",
|
||||||
|
"packageManager": "pnpm@9.0.4+sha256.caa915eaae9d9aefccf50ee8aeda25a2f8684d8f9d5c6e367eaf176d97c1f89e"
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ importers:
|
||||||
version: link:ui
|
version: link:ui
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-switch':
|
||||||
|
specifier: ^1.0.3
|
||||||
|
version: 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@radix-ui/react-dialog':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
|
@ -683,7 +687,6 @@ packages:
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@babel/template@7.24.0:
|
/@babel/template@7.24.0:
|
||||||
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
|
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
|
||||||
|
@ -1339,7 +1342,6 @@ packages:
|
||||||
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
|
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.24.0
|
'@babel/runtime': 7.24.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
|
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
|
||||||
|
@ -1377,7 +1379,6 @@ packages:
|
||||||
'@babel/runtime': 7.24.0
|
'@babel/runtime': 7.24.0
|
||||||
'@types/react': 18.2.79
|
'@types/react': 18.2.79
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@radix-ui/react-context@1.0.1(@types/react@18.2.79)(react@18.2.0):
|
/@radix-ui/react-context@1.0.1(@types/react@18.2.79)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
|
resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
|
||||||
|
@ -1391,7 +1392,6 @@ packages:
|
||||||
'@babel/runtime': 7.24.0
|
'@babel/runtime': 7.24.0
|
||||||
'@types/react': 18.2.79
|
'@types/react': 18.2.79
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
/@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
|
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
|
||||||
|
@ -1566,7 +1566,6 @@ packages:
|
||||||
'@types/react-dom': 18.2.25
|
'@types/react-dom': 18.2.25
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@radix-ui/react-slot@1.0.2(@types/react@18.2.79)(react@18.2.0):
|
/@radix-ui/react-slot@1.0.2(@types/react@18.2.79)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||||
|
@ -1581,7 +1580,33 @@ packages:
|
||||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
'@types/react': 18.2.79
|
'@types/react': 18.2.79
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
/@radix-ui/react-switch@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.24.0
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.79
|
||||||
|
'@types/react-dom': 18.2.25
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-toast@1.1.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
/@radix-ui/react-toast@1.1.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==}
|
resolution: {integrity: sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==}
|
||||||
|
@ -1627,7 +1652,6 @@ packages:
|
||||||
'@babel/runtime': 7.24.0
|
'@babel/runtime': 7.24.0
|
||||||
'@types/react': 18.2.79
|
'@types/react': 18.2.79
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.79)(react@18.2.0):
|
/@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.79)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
|
resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
|
||||||
|
@ -1642,7 +1666,6 @@ packages:
|
||||||
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
'@types/react': 18.2.79
|
'@types/react': 18.2.79
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.79)(react@18.2.0):
|
/@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.79)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
|
resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
|
||||||
|
@ -1671,7 +1694,35 @@ packages:
|
||||||
'@babel/runtime': 7.24.0
|
'@babel/runtime': 7.24.0
|
||||||
'@types/react': 18.2.79
|
'@types/react': 18.2.79
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
|
||||||
|
/@radix-ui/react-use-previous@1.0.1(@types/react@18.2.79)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.24.0
|
||||||
|
'@types/react': 18.2.79
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-size@1.0.1(@types/react@18.2.79)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.24.0
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.79
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
/@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
|
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
|
||||||
|
@ -2357,7 +2408,6 @@ packages:
|
||||||
|
|
||||||
/@types/prop-types@15.7.11:
|
/@types/prop-types@15.7.11:
|
||||||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/qs@6.9.11:
|
/@types/qs@6.9.11:
|
||||||
resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
|
resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
|
||||||
|
@ -2371,14 +2421,12 @@ packages:
|
||||||
resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==}
|
resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.2.79
|
'@types/react': 18.2.79
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/react@18.2.79:
|
/@types/react@18.2.79:
|
||||||
resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==}
|
resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.11
|
'@types/prop-types': 15.7.11
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/semver@7.5.8:
|
/@types/semver@7.5.8:
|
||||||
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
||||||
|
@ -3391,7 +3439,6 @@ packages:
|
||||||
|
|
||||||
/csstype@3.1.3:
|
/csstype@3.1.3:
|
||||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/data-uri-to-buffer@6.0.2:
|
/data-uri-to-buffer@6.0.2:
|
||||||
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
|
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
|
||||||
|
@ -5179,7 +5226,6 @@ packages:
|
||||||
|
|
||||||
/js-tokens@4.0.0:
|
/js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/js-yaml@4.1.0:
|
/js-yaml@4.1.0:
|
||||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||||
|
@ -5512,7 +5558,6 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/lower-case@2.0.2:
|
/lower-case@2.0.2:
|
||||||
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
|
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
|
||||||
|
@ -6291,7 +6336,6 @@ packages:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
scheduler: 0.23.0
|
scheduler: 0.23.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/react-hook-form@7.51.3(react@18.2.0):
|
/react-hook-form@7.51.3(react@18.2.0):
|
||||||
resolution: {integrity: sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==}
|
resolution: {integrity: sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==}
|
||||||
|
@ -6402,7 +6446,6 @@ packages:
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/readdirp@3.6.0:
|
/readdirp@3.6.0:
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
|
@ -6413,7 +6456,6 @@ packages:
|
||||||
|
|
||||||
/regenerator-runtime@0.14.1:
|
/regenerator-runtime@0.14.1:
|
||||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/regexp.prototype.flags@1.5.2:
|
/regexp.prototype.flags@1.5.2:
|
||||||
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
|
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
|
||||||
|
@ -6579,7 +6621,6 @@ packages:
|
||||||
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/security@1.0.0:
|
/security@1.0.0:
|
||||||
resolution: {integrity: sha512-5qfoAgfRWS1sUn+fUJtdbbqM1BD/LoQGa+smPTDjf9OqHyuJqi6ewtbYL0+V1S1RaU6OCOCMWGZocIfz2YK4uw==}
|
resolution: {integrity: sha512-5qfoAgfRWS1sUn+fUJtdbbqM1BD/LoQGa+smPTDjf9OqHyuJqi6ewtbYL0+V1S1RaU6OCOCMWGZocIfz2YK4uw==}
|
||||||
|
|
|
@ -112,6 +112,12 @@
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"expressPreSession": "ep_etherpad-lite/node/hooks/express/openapi"
|
"expressPreSession": "ep_etherpad-lite/node/hooks/express/openapi"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ep_message_all",
|
||||||
|
"client_hooks": {
|
||||||
|
"handleClientMessage_shoutMessage": "ep_etherpad-lite/static/js/messageHandler"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
socket.on('uninstall', (pluginName:string) => {
|
socket.on('uninstall', (pluginName:string) => {
|
||||||
uninstall(pluginName, (err:ErrorCaused) => {
|
uninstall(pluginName, (err:ErrorCaused) => {
|
||||||
if (err) console.warn(err.stack || err.toString());
|
if (err) console.warn(err.stack || err.toString());
|
||||||
|
|
|
@ -45,6 +45,28 @@ exports.socketio = (hookName:string, {io}:any) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
type ShoutMessage = {
|
||||||
|
message: string,
|
||||||
|
sticky: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('shout', (message: ShoutMessage) => {
|
||||||
|
const messageToSend = {
|
||||||
|
type: "COLLABROOM",
|
||||||
|
data: {
|
||||||
|
type: "shoutMessage",
|
||||||
|
payload: {
|
||||||
|
message: message,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.of('/settings').emit('shout', messageToSend);
|
||||||
|
io.sockets.emit('shout', messageToSend);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
socket.on('help', () => {
|
socket.on('help', () => {
|
||||||
const gitCommit = settings.getGitCommit();
|
const gitCommit = settings.getGitCommit();
|
||||||
const epVersion = settings.getEpVersion();
|
const epVersion = settings.getEpVersion();
|
||||||
|
@ -130,7 +152,8 @@ exports.socketio = (hookName:string, {io}:any) => {
|
||||||
lastEdited,
|
lastEdited,
|
||||||
userCount,
|
userCount,
|
||||||
revisionNumber
|
revisionNumber
|
||||||
}}));
|
}
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
const currentWinners: PadQueryResult[] = []
|
const currentWinners: PadQueryResult[] = []
|
||||||
let queryOffsetCounter = 0
|
let queryOffsetCounter = 0
|
||||||
|
@ -204,7 +227,6 @@ exports.socketio = (hookName:string, {io}:any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const searchPad = async (query: PadSearchQuery) => {
|
const searchPad = async (query: PadSearchQuery) => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,11 +250,27 @@ const handshake = async () => {
|
||||||
socket.on('disconnect', (reason) => {
|
socket.on('disconnect', (reason) => {
|
||||||
// The socket.io client will automatically try to reconnect for all reasons other than "io
|
// The socket.io client will automatically try to reconnect for all reasons other than "io
|
||||||
// server disconnect".
|
// server disconnect".
|
||||||
|
console.log(`Socket disconnected: ${reason}`)
|
||||||
if (reason !== 'io server disconnect') return;
|
if (reason !== 'io server disconnect') return;
|
||||||
socketReconnecting();
|
socketReconnecting();
|
||||||
socket.connect();
|
socket.connect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
socket.on('shout', (obj) => {
|
||||||
|
if(obj.type === "COLLABROOM") {
|
||||||
|
let date = new Date(obj.data.payload.timestamp);
|
||||||
|
$.gritter.add({
|
||||||
|
// (string | mandatory) the heading of the notification
|
||||||
|
title: 'Admin message',
|
||||||
|
// (string | mandatory) the text inside the notification
|
||||||
|
text: '[' + date.toLocaleTimeString() + ']: ' + obj.data.payload.message.message,
|
||||||
|
// (bool | optional) if you want it to fade out on its own or just sit there
|
||||||
|
sticky: obj.data.payload.message.sticky
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
socket.on('reconnecting', socketReconnecting);
|
socket.on('reconnecting', socketReconnecting);
|
||||||
|
|
||||||
socket.on('reconnect_failed', (error) => {
|
socket.on('reconnect_failed', (error) => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ test('Shows troubleshooting page manager', async ({page}) => {
|
||||||
await page.goto('http://localhost:9001/admin/help')
|
await page.goto('http://localhost:9001/admin/help')
|
||||||
await page.waitForSelector('.menu')
|
await page.waitForSelector('.menu')
|
||||||
const menu = page.locator('.menu');
|
const menu = page.locator('.menu');
|
||||||
await expect(menu.locator('li')).toHaveCount(4);
|
await expect(menu.locator('li')).toHaveCount(5);
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Shows a version number', async function ({page}) {
|
test('Shows a version number', async function ({page}) {
|
||||||
|
|
Loading…
Reference in a new issue