mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-19 14:13:34 +01:00
Merge branch 'develop'
This commit is contained in:
commit
1c459b3e41
37 changed files with 3316 additions and 1349 deletions
|
@ -1,3 +1,11 @@
|
|||
# 2.2.5
|
||||
|
||||
### Notable enhancements and fixes
|
||||
|
||||
- Fixed timeslider not scrolling when the revision count is a multiple of 100
|
||||
- Added new Restful API for version 2 of Etherpad. It is available at /api-docs
|
||||
|
||||
|
||||
# 2.2.4
|
||||
|
||||
### Notable enhancements and fixes
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "admin",
|
||||
"private": true,
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
@ -16,25 +16,25 @@
|
|||
"devDependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react": "^18.3.8",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@typescript-eslint/eslint-plugin": "^8.4.0",
|
||||
"@typescript-eslint/parser": "^8.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.6.0",
|
||||
"@typescript-eslint/parser": "^8.6.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint": "^9.10.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.11",
|
||||
"i18next": "^23.14.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.12",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"lucide-react": "^0.439.0",
|
||||
"lucide-react": "^0.441.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.53.0",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"react-i18next": "^15.0.2",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.3",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.7",
|
||||
"vite-plugin-static-copy": "^1.0.6",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"zustand": "^4.5.5"
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"ep_adminpads2_autoupdate.title": "Aktiviert oder deaktiviert automatische Aktualisierungen für die aktuelle Abfrage.",
|
||||
"ep_adminpads2_confirm": "Willst du das Pad {{padID}} wirklich löschen?",
|
||||
"ep_adminpads2_delete.value": "Löschen",
|
||||
"ep_adminpads2_cleanup": "Historie aufräumen",
|
||||
"ep_adminpads2_last-edited": "Zuletzt bearbeitet",
|
||||
"ep_adminpads2_loading": "Lädt...",
|
||||
"ep_adminpads2_manage-pads": "Pads verwalten",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"ep_adminpads2_autoupdate.title": "Enables or disables automatic updates for the current query.",
|
||||
"ep_adminpads2_confirm": "Do you really want to delete the pad {{padID}}?",
|
||||
"ep_adminpads2_delete.value": "Delete",
|
||||
"ep_adminpads2_cleanup": "Cleanup revisions",
|
||||
"ep_adminpads2_last-edited": "Last edited",
|
||||
"ep_adminpads2_loading": "Loading…",
|
||||
"ep_adminpads2_manage-pads": "Manage pads",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {useEffect} from 'react'
|
||||
import {useEffect, useState} from 'react'
|
||||
import './App.css'
|
||||
import {connect} from 'socket.io-client'
|
||||
import {isJSONClean} from './utils/utils.ts'
|
||||
|
@ -6,107 +6,113 @@ import {NavLink, Outlet, useNavigate} from "react-router-dom";
|
|||
import {useStore} from "./store/store.ts";
|
||||
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall} from "lucide-react";
|
||||
import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall, LucideMenu} from "lucide-react";
|
||||
|
||||
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
||||
export const App = ()=> {
|
||||
const setSettings = useStore(state => state.setSettings);
|
||||
const {t} = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const WS_URL = import.meta.env.DEV ? 'http://localhost:9001' : ''
|
||||
export const App = () => {
|
||||
const setSettings = useStore(state => state.setSettings);
|
||||
const {t} = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const [sidebarOpen, setSidebarOpen] = useState<boolean>(true)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/admin-auth/', {
|
||||
method: 'POST'
|
||||
}).then((value)=>{
|
||||
if(!value.ok){
|
||||
navigate('/login')
|
||||
}
|
||||
}).catch(()=>{
|
||||
navigate('/login')
|
||||
})
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
fetch('/admin-auth/', {
|
||||
method: 'POST'
|
||||
}).then((value) => {
|
||||
if (!value.ok) {
|
||||
navigate('/login')
|
||||
}
|
||||
}).catch(() => {
|
||||
navigate('/login')
|
||||
})
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t('admin.page-title')
|
||||
useEffect(() => {
|
||||
document.title = t('admin.page-title')
|
||||
|
||||
useStore.getState().setShowLoading(true);
|
||||
const settingSocket = connect(`${WS_URL}/settings`, {
|
||||
transports: ['websocket'],
|
||||
});
|
||||
useStore.getState().setShowLoading(true);
|
||||
const settingSocket = connect(`${WS_URL}/settings`, {
|
||||
transports: ['websocket'],
|
||||
});
|
||||
|
||||
const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
|
||||
transports: ['websocket'],
|
||||
})
|
||||
const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
|
||||
transports: ['websocket'],
|
||||
})
|
||||
|
||||
pluginsSocket.on('connect', () => {
|
||||
useStore.getState().setPluginsSocket(pluginsSocket);
|
||||
});
|
||||
pluginsSocket.on('connect', () => {
|
||||
useStore.getState().setPluginsSocket(pluginsSocket);
|
||||
});
|
||||
|
||||
|
||||
settingSocket.on('connect', () => {
|
||||
useStore.getState().setSettingsSocket(settingSocket);
|
||||
useStore.getState().setShowLoading(false)
|
||||
settingSocket.emit('load');
|
||||
console.log('connected');
|
||||
});
|
||||
settingSocket.on('connect', () => {
|
||||
useStore.getState().setSettingsSocket(settingSocket);
|
||||
useStore.getState().setShowLoading(false)
|
||||
settingSocket.emit('load');
|
||||
console.log('connected');
|
||||
});
|
||||
|
||||
settingSocket.on('disconnect', (reason) => {
|
||||
// The settingSocket.io client will automatically try to reconnect for all reasons other than "io
|
||||
// server disconnect".
|
||||
useStore.getState().setShowLoading(true)
|
||||
if (reason === 'io server disconnect') {
|
||||
settingSocket.connect();
|
||||
}
|
||||
});
|
||||
settingSocket.on('disconnect', (reason) => {
|
||||
// The settingSocket.io client will automatically try to reconnect for all reasons other than "io
|
||||
// server disconnect".
|
||||
useStore.getState().setShowLoading(true)
|
||||
if (reason === 'io server disconnect') {
|
||||
settingSocket.connect();
|
||||
}
|
||||
});
|
||||
|
||||
settingSocket.on('settings', (settings) => {
|
||||
/* Check whether the settings.json is authorized to be viewed */
|
||||
if (settings.results === 'NOT_ALLOWED') {
|
||||
console.log('Not allowed to view settings.json')
|
||||
return;
|
||||
}
|
||||
settingSocket.on('settings', (settings) => {
|
||||
/* Check whether the settings.json is authorized to be viewed */
|
||||
if (settings.results === 'NOT_ALLOWED') {
|
||||
console.log('Not allowed to view settings.json')
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check to make sure the JSON is clean before proceeding */
|
||||
if (isJSONClean(settings.results)) {
|
||||
setSettings(settings.results);
|
||||
} else {
|
||||
alert('Invalid JSON');
|
||||
}
|
||||
useStore.getState().setShowLoading(false);
|
||||
});
|
||||
/* Check to make sure the JSON is clean before proceeding */
|
||||
if (isJSONClean(settings.results)) {
|
||||
setSettings(settings.results);
|
||||
} else {
|
||||
alert('Invalid JSON');
|
||||
}
|
||||
useStore.getState().setShowLoading(false);
|
||||
});
|
||||
|
||||
settingSocket.on('saveprogress', (status)=>{
|
||||
console.log(status)
|
||||
})
|
||||
settingSocket.on('saveprogress', (status) => {
|
||||
console.log(status)
|
||||
})
|
||||
|
||||
return () => {
|
||||
settingSocket.disconnect();
|
||||
pluginsSocket.disconnect()
|
||||
}
|
||||
}, []);
|
||||
return () => {
|
||||
settingSocket.disconnect();
|
||||
pluginsSocket.disconnect()
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <div id="wrapper">
|
||||
<LoadingScreen/>
|
||||
<div className="menu">
|
||||
<div className="inner-menu">
|
||||
<span>
|
||||
return <div id="wrapper" className={`${sidebarOpen ? '': 'closed' }`}>
|
||||
<LoadingScreen/>
|
||||
<div className="menu">
|
||||
<div className="inner-menu">
|
||||
<span>
|
||||
<Crown width={40} height={40}/>
|
||||
<h1>Etherpad</h1>
|
||||
</span>
|
||||
<ul>
|
||||
<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={"/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={"/shout"}><PhoneCall/>Communication</NavLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="innerwrapper">
|
||||
<Outlet/>
|
||||
</div>
|
||||
<ul onClick={()=>{
|
||||
setSidebarOpen(false)
|
||||
}}>
|
||||
<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={"/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={"/shout"}><PhoneCall/>Communication</NavLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<button id="icon-button" onClick={() => {
|
||||
setSidebarOpen(!sidebarOpen)
|
||||
}}><LucideMenu/></button>
|
||||
<div className="innerwrapper">
|
||||
<Outlet/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -193,6 +193,7 @@ export const HomePage = () => {
|
|||
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
||||
<SearchField onChange={v=>{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
|
||||
|
||||
<div className="table-container">
|
||||
<table id="available-plugins">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -240,5 +241,6 @@ export const HomePage = () => {
|
|||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ 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} from "lucide-react";
|
||||
import {ChevronLeft, ChevronRight, Eye, Trash2, FileStack} from "lucide-react";
|
||||
import {SearchField} from "../components/SearchField.tsx";
|
||||
|
||||
export const PadPage = ()=>{
|
||||
|
@ -23,6 +23,7 @@ export const PadPage = ()=>{
|
|||
const pads = useStore(state=>state.pads)
|
||||
const [currentPage, setCurrentPage] = useState<number>(0)
|
||||
const [deleteDialog, setDeleteDialog] = useState<boolean>(false)
|
||||
const [errorText, setErrorText] = useState<string|null>(null)
|
||||
const [padToDelete, setPadToDelete] = useState<string>('')
|
||||
const pages = useMemo(()=>{
|
||||
if(!pads){
|
||||
|
@ -68,12 +69,35 @@ export const PadPage = ()=>{
|
|||
results: newPads
|
||||
})
|
||||
})
|
||||
|
||||
settingsSocket.on('results:cleanupPadRevisions', (data)=>{
|
||||
let newPads = useStore.getState().pads?.results ?? []
|
||||
|
||||
if (data.error) {
|
||||
setErrorText(data.error)
|
||||
return
|
||||
}
|
||||
|
||||
newPads.forEach((pad)=>{
|
||||
if (pad.padName === data.padId) {
|
||||
pad.revisionNumber = data.keepRevisions
|
||||
}
|
||||
})
|
||||
|
||||
useStore.getState().setPads({
|
||||
results: newPads,
|
||||
total: useStore.getState().pads!.total
|
||||
})
|
||||
})
|
||||
}, [settingsSocket, pads]);
|
||||
|
||||
const deletePad = (padID: string)=>{
|
||||
settingsSocket?.emit('deletePad', padID)
|
||||
}
|
||||
|
||||
const cleanupPad = (padID: string)=>{
|
||||
settingsSocket?.emit('cleanupPadRevisions', padID)
|
||||
}
|
||||
|
||||
|
||||
return <div>
|
||||
|
@ -100,6 +124,21 @@ export const PadPage = ()=>{
|
|||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
<Dialog.Root open={errorText !== null}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="dialog-confirm-overlay"/>
|
||||
<Dialog.Content className="dialog-confirm-content">
|
||||
<div>
|
||||
<div>Error occured: {errorText}</div>
|
||||
<div className="settings-button-bar">
|
||||
<button onClick={() => {
|
||||
setErrorText(null)
|
||||
}}>OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
<h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
|
||||
<SearchField value={searchTerm} onChange={v=>setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
|
||||
<table>
|
||||
|
@ -150,6 +189,9 @@ export const PadPage = ()=>{
|
|||
setPadToDelete(pad.padName)
|
||||
setDeleteDialog(true)
|
||||
}}/>
|
||||
<IconButton icon={<FileStack/>} title={<Trans i18nKey="ep_admin_pads:ep_adminpads2_cleanup"/>} onClick={()=>{
|
||||
cleanupPad(pad.padName)
|
||||
}}/>
|
||||
<IconButton icon={<Eye/>} title="view" onClick={()=>window.open(`/p/${pad.padName}`, '_blank')}/>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -57,7 +57,7 @@ createDirIfNotExists('../out/doc/api')
|
|||
|
||||
|
||||
|
||||
exec(`asciidoctor -D ../out/doc ../doc/index.adoc */**.adoc -a VERSION=${VERSION}`)
|
||||
exec(`asciidoctor -D ../out/doc ../doc/index.adoc ../*/**.adoc -a VERSION=${VERSION}`)
|
||||
exec(`asciidoctor -D ../out/doc/api ../doc/api/*.adoc -a VERSION=${VERSION}`)
|
||||
|
||||
copyFolderSync('../doc/public/', '../out/doc/')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "bin",
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"description": "",
|
||||
"main": "checkAllPads.js",
|
||||
"directories": {
|
||||
|
@ -11,13 +11,13 @@
|
|||
"ep_etherpad-lite": "workspace:../src",
|
||||
"log4js": "^6.9.1",
|
||||
"semver": "^7.6.3",
|
||||
"tsx": "^4.19.0",
|
||||
"ueberdb2": "^4.2.103"
|
||||
"tsx": "^4.19.1",
|
||||
"ueberdb2": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/node": "^22.5.5",
|
||||
"@types/semver": "^7.5.8",
|
||||
"typescript": "^5.5.4"
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"scripts": {
|
||||
"makeDocs": "node --import tsx make_docs.ts",
|
||||
|
|
|
@ -32,6 +32,7 @@ export default defineConfig({
|
|||
{ text: 'Stats', link: '/stats.md' },
|
||||
{text: 'Skins', link: '/skins.md' },
|
||||
{text: 'Demo', link: '/demo.md' },
|
||||
{text: 'CLI', link: '/cli.md'},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -58,7 +58,7 @@ services:
|
|||
# ports:
|
||||
# - "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/pgdata
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
postgres_data:
|
||||
|
|
|
@ -42,7 +42,7 @@ services:
|
|||
# ports:
|
||||
# - "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/pgdata
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
|
|
@ -50,6 +50,6 @@
|
|||
"type": "git",
|
||||
"url": "https://github.com/ether/etherpad-lite.git"
|
||||
},
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
|
1549
pnpm-lock.yaml
1549
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -171,6 +171,14 @@
|
|||
*/
|
||||
"showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:true}",
|
||||
|
||||
/*
|
||||
* Settings for cleanup of pads
|
||||
*/
|
||||
"cleanup": {
|
||||
"enabled": false,
|
||||
"keepRevisions": 5
|
||||
},
|
||||
|
||||
/*
|
||||
The authentication method used by the server.
|
||||
The default value is sso
|
||||
|
@ -194,6 +202,15 @@
|
|||
},
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Enables the use of a different server. We have a different one that syncs changes from the original server.
|
||||
* It is hosted on GitHub and should not be blocked by many firewalls.
|
||||
* https://etherpad.org/ep_infos
|
||||
*/
|
||||
|
||||
"updateServer": "https://etherpad.org/ep_infos",
|
||||
|
||||
/*
|
||||
* The type of the database.
|
||||
*
|
||||
|
|
|
@ -162,6 +162,14 @@
|
|||
*/
|
||||
"showSettingsInAdminPage": true,
|
||||
|
||||
/*
|
||||
* Settings for cleanup of pads
|
||||
*/
|
||||
"cleanup": {
|
||||
"enabled": false,
|
||||
"keepRevisions": 5
|
||||
},
|
||||
|
||||
/*
|
||||
* Node native SSL support
|
||||
*
|
||||
|
@ -271,6 +279,14 @@
|
|||
"pageDown": true
|
||||
},
|
||||
|
||||
/*
|
||||
* Enables the use of a different server. We have a different one that syncs changes from the original server.
|
||||
* It is hosted on GitHub and should not be blocked by many firewalls.
|
||||
* https://etherpad.org/ep_infos
|
||||
*/
|
||||
|
||||
"updateServer": "https://etherpad.org/ep_infos",
|
||||
|
||||
/*
|
||||
* Should we suppress errors from being visible in the default Pad Text?
|
||||
*/
|
||||
|
|
|
@ -82,6 +82,12 @@
|
|||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "restApi",
|
||||
"hooks": {
|
||||
"expressCreateServer": "ep_etherpad-lite/node/handler/RestAPI"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "socketio",
|
||||
"hooks": {
|
||||
|
|
|
@ -24,10 +24,10 @@ import {MapArrayType} from "../types/MapType";
|
|||
const api = require('../db/API');
|
||||
const padManager = require('../db/PadManager');
|
||||
import createHTTPError from 'http-errors';
|
||||
import {Http2ServerRequest, Http2ServerResponse} from "node:http2";
|
||||
import {Http2ServerRequest} from "node:http2";
|
||||
import {publicKeyExported} from "../security/OAuth2Provider";
|
||||
import {jwtVerify} from "jose";
|
||||
import {apikey} from './APIKeyHandler'
|
||||
import {APIFields, apikey} from './APIKeyHandler'
|
||||
// a list of all functions
|
||||
const version:MapArrayType<any> = {};
|
||||
|
||||
|
@ -141,6 +141,7 @@ version['1.3.0'] = {
|
|||
setText: ['padID', 'text', 'authorId'],
|
||||
};
|
||||
|
||||
|
||||
// set the latest available API version here
|
||||
exports.latestApiVersion = '1.3.0';
|
||||
|
||||
|
@ -148,13 +149,6 @@ exports.latestApiVersion = '1.3.0';
|
|||
exports.version = version;
|
||||
|
||||
|
||||
type APIFields = {
|
||||
apikey: string;
|
||||
api_key: string;
|
||||
padID: string;
|
||||
padName: string;
|
||||
authorization: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an HTTP API call
|
||||
|
|
|
@ -7,6 +7,16 @@ const settings = require('../utils/Settings');
|
|||
|
||||
const apiHandlerLogger = log4js.getLogger('APIHandler');
|
||||
|
||||
|
||||
|
||||
export type APIFields = {
|
||||
apikey: string;
|
||||
api_key: string;
|
||||
padID: string;
|
||||
padName: string;
|
||||
authorization: string;
|
||||
}
|
||||
|
||||
// ensure we have an apikey
|
||||
export let apikey:string|null = null;
|
||||
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
|
||||
|
|
|
@ -1147,13 +1147,13 @@ const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, g
|
|||
getPadLines(pad, startNum - 1),
|
||||
// Get all needed composite Changesets.
|
||||
...compositesChangesetNeeded.map(async (item) => {
|
||||
const changeset = await composePadChangesets(pad, item.start, item.end);
|
||||
const changeset = await exports.composePadChangesets(pad, item.start, item.end);
|
||||
composedChangesets[`${item.start}/${item.end}`] = changeset;
|
||||
}),
|
||||
// Get all needed revision Dates.
|
||||
...revTimesNeeded.map(async (revNum) => {
|
||||
const revDate = await pad.getRevisionDate(revNum);
|
||||
revisionDate[revNum] = Math.floor(revDate / 1000);
|
||||
revisionDate[revNum] = revDate;
|
||||
}),
|
||||
]);
|
||||
|
||||
|
@ -1213,7 +1213,7 @@ const getPadLines = async (pad: PadType, revNum: number) => {
|
|||
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
||||
*/
|
||||
const composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
|
||||
exports.composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
|
||||
// fetch all changesets we need
|
||||
const headNum = pad.getHeadRevisionNumber();
|
||||
endNum = Math.min(endNum, headNum + 1);
|
||||
|
|
1527
src/node/handler/RestAPI.ts
Normal file
1527
src/node/handler/RestAPI.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -13,6 +13,7 @@ const settings = require('../../utils/Settings');
|
|||
const UpdateCheck = require('../../utils/UpdateCheck');
|
||||
const padManager = require('../../db/PadManager');
|
||||
const api = require('../../db/API');
|
||||
const cleanup = require('../../utils/Cleanup');
|
||||
|
||||
|
||||
const queryPadLimit = 12;
|
||||
|
@ -252,6 +253,40 @@ exports.socketio = (hookName: string, {io}: any) => {
|
|||
}
|
||||
})
|
||||
|
||||
socket.on('cleanupPadRevisions', async (padId: string) => {
|
||||
if (!settings.cleanup.enabled) {
|
||||
socket.emit('results:cleanupPadRevisions', {
|
||||
error: 'Cleanup disabled. Enable cleanup in settings.json: cleanup.enabled => true',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const padExists = await padManager.doesPadExists(padId);
|
||||
if (padExists) {
|
||||
logger.info(`Cleanup pad revisions: ${padId}`);
|
||||
try {
|
||||
const result = await cleanup.deleteRevisions(padId, settings.cleanup.keepRevisions)
|
||||
if (result) {
|
||||
socket.emit('results:cleanupPadRevisions', {
|
||||
padId: padId,
|
||||
keepRevisions: settings.cleanup.keepRevisions,
|
||||
});
|
||||
logger.info('successful cleaned up pad: ', padId)
|
||||
} else {
|
||||
socket.emit('results:cleanupPadRevisions', {
|
||||
error: 'Error cleaning up pad',
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(`Error in pad ${padId}: ${err.stack || err}`);
|
||||
socket.emit('results:cleanupPadRevisions', {
|
||||
error: err.toString(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('restartServer', async () => {
|
||||
logger.info('Admin request to restart server through a socket on /admin/settings');
|
||||
settings.reloadSettings();
|
||||
|
|
|
@ -12,6 +12,7 @@ const webaccess = require('./webaccess');
|
|||
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
||||
|
||||
import {build, buildSync} from 'esbuild'
|
||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
let ioI: { sockets: { sockets: any[]; }; } | null = null
|
||||
|
||||
exports.socketio = (hookName: string, {io}: any) => {
|
||||
|
@ -19,7 +20,7 @@ exports.socketio = (hookName: string, {io}: any) => {
|
|||
}
|
||||
|
||||
|
||||
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||
exports.expressPreSession = async (hookName:string, {app}:ArgsExpressType) => {
|
||||
// This endpoint is intended to conform to:
|
||||
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
|
||||
app.get('/health', (req:any, res:any) => {
|
||||
|
@ -113,7 +114,7 @@ const convertTypescript = (content: string) => {
|
|||
|
||||
const handleLiveReload = async (args: any, padString: string, timeSliderString: string, indexString: any) => {
|
||||
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 } = {};
|
||||
|
||||
const setRouteHandler = (path: string, newHandler: Function) => {
|
||||
|
@ -243,7 +244,7 @@ const convertTypescriptWatched = (content: string, cb: (output:string, hash: str
|
|||
})
|
||||
}
|
||||
|
||||
exports.expressCreateServer = async (hookName: string, args: any, cb: Function) => {
|
||||
exports.expressCreateServer = async (hookName: string, args: ArgsExpressType, cb: Function) => {
|
||||
const padString = eejs.require('ep_etherpad-lite/templates/padBootstrap.js', {
|
||||
pluginModules: (() => {
|
||||
const pluginModules = new Set();
|
||||
|
|
|
@ -153,7 +153,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
|||
});
|
||||
|
||||
|
||||
args.app.post('/interaction/:uid', async (req: Http2ServerRequest, res: Http2ServerResponse, next:Function) => {
|
||||
args.app.post('/interaction/:uid', async (req, res, next) => {
|
||||
const formid = new IncomingForm();
|
||||
try {
|
||||
// @ts-ignore
|
||||
|
@ -226,7 +226,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
|
|||
})
|
||||
|
||||
|
||||
args.app.get('/interaction/:uid', async (req: Request, res: Response, next: Function) => {
|
||||
args.app.get('/interaction/:uid', async (req, res, next) => {
|
||||
try {
|
||||
const {
|
||||
uid, prompt, params, session,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {Express} from "express";
|
||||
|
||||
export type ArgsExpressType = {
|
||||
app:any,
|
||||
app:Express,
|
||||
io: any,
|
||||
server:any
|
||||
}
|
||||
}
|
||||
|
|
9
src/node/types/Revision.ts
Normal file
9
src/node/types/Revision.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import {AChangeSet} from "./PadType";
|
||||
|
||||
export type Revision = {
|
||||
changeset: AChangeSet,
|
||||
meta: {
|
||||
author: string,
|
||||
timestamp: number,
|
||||
}
|
||||
}
|
168
src/node/utils/Cleanup.ts
Normal file
168
src/node/utils/Cleanup.ts
Normal file
|
@ -0,0 +1,168 @@
|
|||
'use strict'
|
||||
|
||||
import {AChangeSet} from "../types/PadType";
|
||||
import {Revision} from "../types/Revision";
|
||||
|
||||
const promises = require('./promises');
|
||||
const padManager = require('ep_etherpad-lite/node/db/PadManager');
|
||||
const db = require('ep_etherpad-lite/node/db/DB');
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const padMessageHandler = require('ep_etherpad-lite/node/handler/PadMessageHandler');
|
||||
const log4js = require('log4js');
|
||||
const logger = log4js.getLogger('cleanup');
|
||||
|
||||
exports.deleteAllRevisions = async (padID: string): Promise<void> => {
|
||||
|
||||
const randomPadId = padID + 'aertdfdf' + Math.random().toString(10)
|
||||
|
||||
let pad = await padManager.getPad(padID);
|
||||
await pad.copyPadWithoutHistory(randomPadId, false);
|
||||
pad = await padManager.getPad(randomPadId);
|
||||
await pad.copyPadWithoutHistory(padID, true);
|
||||
await pad.remove();
|
||||
}
|
||||
|
||||
const createRevision = async (aChangeset: AChangeSet, timestamp: number, isKeyRev: boolean, authorId: string, atext: any, pool: any) => {
|
||||
|
||||
if (authorId !== '') pool.putAttrib(['author', authorId]);
|
||||
|
||||
return {
|
||||
changeset: aChangeset,
|
||||
meta: {
|
||||
author: authorId,
|
||||
timestamp: timestamp,
|
||||
...isKeyRev ? {
|
||||
pool: pool,
|
||||
atext: atext,
|
||||
} : {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
exports.deleteRevisions = async (padId: string, keepRevisions: number): Promise<boolean> => {
|
||||
|
||||
logger.debug('Start cleanup revisions', padId)
|
||||
|
||||
let pad = await padManager.getPad(padId);
|
||||
await pad.check()
|
||||
|
||||
logger.debug('Initial pad is valid')
|
||||
|
||||
if (pad.head <= keepRevisions) {
|
||||
logger.debug('Pad has not enough revisions')
|
||||
return false
|
||||
}
|
||||
|
||||
padMessageHandler.kickSessionsFromPad(padId)
|
||||
|
||||
const cleanupUntilRevision = pad.head - keepRevisions
|
||||
logger.debug('Composing changesets: ', cleanupUntilRevision)
|
||||
const changeset = await padMessageHandler.composePadChangesets(pad, 0, cleanupUntilRevision + 1)
|
||||
|
||||
const revisions: Revision[] = [];
|
||||
|
||||
await promises.timesLimit(keepRevisions + 1, 500, async (i: number) => {
|
||||
const rev = i + cleanupUntilRevision
|
||||
revisions[rev] = await pad.getRevision(rev)
|
||||
});
|
||||
|
||||
logger.debug('Loaded revisions: ', revisions.length)
|
||||
|
||||
await promises.timesLimit(pad.head + 1, 500, async (i: string) => {
|
||||
await db.remove(`pad:${padId}:revs:${i}`, null);
|
||||
});
|
||||
|
||||
let padContent = await db.get(`pad:${padId}`)
|
||||
padContent.head = keepRevisions
|
||||
if (padContent.savedRevisions) {
|
||||
let newSavedRevisions = []
|
||||
|
||||
for (let i = 0; i < padContent.savedRevisions.length; i++) {
|
||||
if (padContent.savedRevisions[i].revNum > cleanupUntilRevision) {
|
||||
padContent.savedRevisions[i].revNum = padContent.savedRevisions[i].revNum - cleanupUntilRevision
|
||||
newSavedRevisions.push(padContent.savedRevisions[i])
|
||||
}
|
||||
}
|
||||
padContent.savedRevisions = newSavedRevisions
|
||||
}
|
||||
await db.set(`pad:${padId}`, padContent);
|
||||
|
||||
let newAText = Changeset.makeAText('\n');
|
||||
let pool = pad.apool()
|
||||
|
||||
newAText = Changeset.applyToAText(changeset, newAText, pool);
|
||||
|
||||
const revision = await createRevision(
|
||||
changeset,
|
||||
revisions[cleanupUntilRevision].meta.timestamp,
|
||||
0 === pad.getKeyRevisionNumber(0),
|
||||
'',
|
||||
newAText,
|
||||
pool
|
||||
);
|
||||
|
||||
const p: Promise<void>[] = [];
|
||||
|
||||
p.push(db.set(`pad:${padId}:revs:0`, revision))
|
||||
|
||||
p.push(promises.timesLimit(keepRevisions, 500, async (i: number) => {
|
||||
const rev = i + cleanupUntilRevision + 1
|
||||
const newRev = rev - cleanupUntilRevision;
|
||||
|
||||
newAText = Changeset.applyToAText(revisions[rev].changeset, newAText, pool);
|
||||
|
||||
const revision = await createRevision(
|
||||
revisions[rev].changeset,
|
||||
revisions[rev].meta.timestamp,
|
||||
newRev === pad.getKeyRevisionNumber(newRev),
|
||||
revisions[rev].meta.author,
|
||||
newAText,
|
||||
pool
|
||||
);
|
||||
|
||||
await db.set(`pad:${padId}:revs:${newRev}`, revision);
|
||||
}));
|
||||
|
||||
await Promise.all(p)
|
||||
|
||||
logger.debug('Finished migration. Checking pad now')
|
||||
|
||||
padManager.unloadPad(padId);
|
||||
|
||||
let newPad = await padManager.getPad(padId);
|
||||
await newPad.check();
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
exports.checkTodos = async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// TODO: Move to settings
|
||||
const settings = {
|
||||
minHead: 100,
|
||||
keepRevisions: 100,
|
||||
minAge: 1,//1000 * 60 * 60 * 24,
|
||||
}
|
||||
|
||||
await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId: string) => {
|
||||
// TODO: Handle concurrency
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
const revisionDate = await pad.getRevisionDate(pad.getHeadRevisionNumber())
|
||||
|
||||
if (pad.head < settings.minHead || padMessageHandler.padUsersCount(padId) > 0 || Date.now() < revisionDate + settings.minAge) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await exports.deleteRevisions(padId, settings.keepRevisions)
|
||||
if (result) {
|
||||
logger.info('successful cleaned up pad: ', padId)
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(`Error in pad ${padId}: ${err.stack || err}`);
|
||||
return;
|
||||
}
|
||||
}));
|
||||
}
|
|
@ -107,6 +107,7 @@ exports.ttl = {
|
|||
RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
|
||||
}
|
||||
|
||||
exports.updateServer = "https://static.etherpad.org"
|
||||
|
||||
|
||||
/*
|
||||
|
@ -379,6 +380,14 @@ exports.sso = {
|
|||
*/
|
||||
exports.showSettingsInAdminPage = true;
|
||||
|
||||
/*
|
||||
* Settings for cleanup of pads
|
||||
*/
|
||||
exports.cleanup = {
|
||||
enabled: false,
|
||||
keepRevisions: 100,
|
||||
}
|
||||
|
||||
/*
|
||||
* By default, when caret is moved out of viewport, it scrolls the minimum
|
||||
* height needed to make this line visible.
|
||||
|
|
|
@ -20,7 +20,7 @@ const loadEtherpadInformations = () => {
|
|||
return infos;
|
||||
}
|
||||
|
||||
return axios.get('https://static.etherpad.org/info.json', {headers: headers})
|
||||
return axios.get(`${settings.updateServer}/info.json`, {headers: headers})
|
||||
.then(async (resp: any) => {
|
||||
infos = await resp.data;
|
||||
if (infos === undefined || infos === null) {
|
||||
|
|
|
@ -38,13 +38,13 @@
|
|||
"cross-spawn": "^7.0.3",
|
||||
"ejs": "^3.1.10",
|
||||
"esbuild": "^0.23.1",
|
||||
"express": "4.19.2",
|
||||
"express": "4.21.0",
|
||||
"express-rate-limit": "^7.4.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"find-root": "1.1.0",
|
||||
"formidable": "^3.5.1",
|
||||
"http-errors": "^2.0.0",
|
||||
"jose": "^5.8.0",
|
||||
"jose": "^5.9.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsdom": "^25.0.0",
|
||||
"jsonminify": "0.4.2",
|
||||
|
@ -57,31 +57,32 @@
|
|||
"measured-core": "^2.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"oidc-provider": "^8.5.1",
|
||||
"openapi-backend": "^5.10.6",
|
||||
"openapi-backend": "^5.11.0",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"rate-limiter-flexible": "^5.0.3",
|
||||
"rehype": "^13.0.1",
|
||||
"rehype-minify-whitespace": "^6.0.0",
|
||||
"rehype-minify-whitespace": "^6.0.1",
|
||||
"resolve": "1.22.8",
|
||||
"rusty-store-kv": "^1.3.1",
|
||||
"security": "1.0.0",
|
||||
"semver": "^7.6.3",
|
||||
"socket.io": "^4.7.5",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"superagent": "10.1.0",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"tinycon": "0.6.8",
|
||||
"tsx": "4.19.0",
|
||||
"ueberdb2": "^4.2.103",
|
||||
"tsx": "4.19.1",
|
||||
"ueberdb2": "^5.0.2",
|
||||
"underscore": "1.13.7",
|
||||
"unorm": "1.6.0",
|
||||
"wtfnode": "^0.9.3",
|
||||
"rusty-store-kv": "^1.2.0"
|
||||
"wtfnode": "^0.9.3"
|
||||
},
|
||||
"bin": {
|
||||
"etherpad-healthcheck": "../bin/etherpad-healthcheck",
|
||||
"etherpad-lite": "node/server.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.47.0",
|
||||
"@playwright/test": "^1.47.1",
|
||||
"@types/async": "^3.2.24",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/formidable": "^3.4.5",
|
||||
|
@ -89,17 +90,19 @@
|
|||
"@types/jquery": "^3.5.30",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/mocha": "^10.0.7",
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/mocha": "^10.0.8",
|
||||
"@types/node": "^22.5.5",
|
||||
"@types/oidc-provider": "^8.5.2",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@types/swagger-ui-express": "^4.1.6",
|
||||
"@types/underscore": "^1.11.15",
|
||||
"chokidar": "^3.6.0",
|
||||
"eslint": "^9.9.1",
|
||||
"@types/whatwg-mimetype": "^3.0.2",
|
||||
"chokidar": "^4.0.0",
|
||||
"eslint": "^9.10.0",
|
||||
"eslint-config-etherpad": "^4.0.4",
|
||||
"etherpad-cli-client": "^3.0.2",
|
||||
"mocha": "^10.7.3",
|
||||
|
@ -107,11 +110,11 @@
|
|||
"nodeify": "^1.0.1",
|
||||
"openapi-schema-validation": "^0.4.2",
|
||||
"set-cookie-parser": "^2.7.0",
|
||||
"sinon": "^18.0.0",
|
||||
"sinon": "^19.0.2",
|
||||
"split-grid": "^1.0.11",
|
||||
"supertest": "^7.0.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^2.0.5"
|
||||
"typescript": "^5.6.2",
|
||||
"vitest": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.2",
|
||||
|
@ -138,6 +141,6 @@
|
|||
"debug:socketio": "cross-env DEBUG=socket.io* node --require tsx/cjs node/server.ts",
|
||||
"test:vitest": "vitest"
|
||||
},
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
for (const name of names) console[name] = noop;
|
||||
}
|
||||
|
||||
const scheduler = parent; // hack for opera required
|
||||
const scheduler = window; // hack for opera required
|
||||
|
||||
const performDocumentReplaceRange = (start, end, newText) => {
|
||||
if (start === undefined) start = rep.selStart;
|
||||
|
@ -240,7 +240,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
bgcolor = fadeColor(bgcolor, info.fade);
|
||||
}
|
||||
const textColor =
|
||||
colorutils.textColorFromBackgroundColor(bgcolor, parent.parent.clientVars.skinName);
|
||||
colorutils.textColorFromBackgroundColor(bgcolor, window.clientVars.skinName);
|
||||
const styles = [
|
||||
cssManagers.inner.selectorStyle(authorSelector),
|
||||
cssManagers.parent.selectorStyle(authorSelector),
|
||||
|
@ -1270,7 +1270,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
const prevLine = rep.lines.prev(thisLine);
|
||||
const prevLineText = prevLine.text;
|
||||
let theIndent = /^ *(?:)/.exec(prevLineText)[0];
|
||||
const shouldIndent = parent.parent.clientVars.indentationOnNewLine;
|
||||
const shouldIndent = window.clientVars.indentationOnNewLine;
|
||||
if (shouldIndent && /[[(:{]\s*$/.exec(prevLineText)) {
|
||||
theIndent += THE_TAB;
|
||||
}
|
||||
|
@ -2023,7 +2023,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
const isPadLoading = (t) => t === 'setup' || t === 'setBaseText' || t === 'importText';
|
||||
|
||||
const updateStyleButtonState = (attribName, hasStyleOnRepSelection) => {
|
||||
const $formattingButton = parent.parent.$(`[data-key="${attribName}"]`).find('a');
|
||||
const $formattingButton = window.$(`[data-key="${attribName}"]`).find('a');
|
||||
$formattingButton.toggleClass(SELECT_BUTTON_CLASS, hasStyleOnRepSelection);
|
||||
};
|
||||
|
||||
|
@ -2277,7 +2277,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
};
|
||||
|
||||
const hideEditBarDropdowns = () => {
|
||||
window.parent.parent.padeditbar.toggleDropDown('none');
|
||||
window.padeditbar.toggleDropDown('none');
|
||||
};
|
||||
|
||||
const renumberList = (lineNum) => {
|
||||
|
@ -2582,7 +2582,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
specialHandled = specialHandledInHook.indexOf(true) !== -1;
|
||||
}
|
||||
|
||||
const padShortcutEnabled = parent.parent.clientVars.padShortcutEnabled;
|
||||
const padShortcutEnabled = window.clientVars.padShortcutEnabled;
|
||||
if (!specialHandled && isTypeForSpecialKey &&
|
||||
altKey && keyCode === 120 &&
|
||||
padShortcutEnabled.altF9) {
|
||||
|
@ -2591,7 +2591,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
// As ubuntu cannot use Alt F10....
|
||||
// Focus on the editbar.
|
||||
// -- TODO: Move Focus back to previous state (we know it so we can use it)
|
||||
const firstEditbarElement = parent.parent.$('#editbar')
|
||||
const firstEditbarElement = window.$('#editbar')
|
||||
.children('ul').first().children().first()
|
||||
.children().first().children().first();
|
||||
$(this).trigger('blur');
|
||||
|
@ -2603,8 +2603,8 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
padShortcutEnabled.altC) {
|
||||
// Alt c focuses on the Chat window
|
||||
$(this).trigger('blur');
|
||||
parent.parent.chat.show();
|
||||
parent.parent.$('#chatinput').trigger('focus');
|
||||
window.chat.show();
|
||||
window.$('#chatinput').trigger('focus');
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (!specialHandled && type === 'keydown' &&
|
||||
|
@ -2626,12 +2626,12 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
if (authorId) authorIds.add(authorId);
|
||||
}
|
||||
}
|
||||
const idToName = new Map(parent.parent.pad.userList().map((a) => [a.userId, a.name]));
|
||||
const myId = parent.parent.clientVars.userId;
|
||||
const idToName = new Map(window.pad.userList().map((a) => [a.userId, a.name]));
|
||||
const myId = window.clientVars.userId;
|
||||
const authors =
|
||||
[...authorIds].map((id) => id === myId ? 'me' : idToName.get(id) || 'unknown');
|
||||
|
||||
parent.parent.$.gritter.add({
|
||||
window.$.gritter.add({
|
||||
title: 'Line Authors',
|
||||
text:
|
||||
authors.length === 0 ? 'No author information is available'
|
||||
|
@ -2680,7 +2680,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
specialHandled = true;
|
||||
|
||||
// close all gritters when the user hits escape key
|
||||
parent.parent.$.gritter.removeAll();
|
||||
window.$.gritter.removeAll();
|
||||
}
|
||||
if (!specialHandled && isTypeForCmdKey &&
|
||||
/* Do a saved revision on ctrl S */
|
||||
|
@ -2688,13 +2688,13 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
!evt.altKey &&
|
||||
padShortcutEnabled.cmdS) {
|
||||
evt.preventDefault();
|
||||
const originalBackground = parent.parent.$('#revisionlink').css('background');
|
||||
parent.parent.$('#revisionlink').css({background: 'lightyellow'});
|
||||
const originalBackground = window.$('#revisionlink').css('background');
|
||||
window.$('#revisionlink').css({background: 'lightyellow'});
|
||||
scheduler.setTimeout(() => {
|
||||
parent.parent.$('#revisionlink').css({background: originalBackground});
|
||||
window.$('#revisionlink').css({background: originalBackground});
|
||||
}, 1000);
|
||||
/* The parent.parent part of this is BAD and I feel bad.. It may break something */
|
||||
parent.parent.pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
|
||||
|
||||
window.pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
|
||||
specialHandled = true;
|
||||
}
|
||||
if (!specialHandled && isTypeForSpecialKey &&
|
||||
|
|
|
@ -186,7 +186,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
|
||||
mutateTextLines(changeset, padContents);
|
||||
padContents.currentRevision = revision;
|
||||
padContents.currentTime += timeDelta * 1000;
|
||||
padContents.currentTime += timeDelta;
|
||||
|
||||
updateTimer();
|
||||
|
||||
|
@ -299,7 +299,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
// Loading changeset history for new revision
|
||||
loadChangesetsForRevision(newRevision, update);
|
||||
// Loading changeset history for old revision (to make diff between old and new revision)
|
||||
loadChangesetsForRevision(padContents.currentRevision - 1);
|
||||
loadChangesetsForRevision(padContents.currentRevision);
|
||||
}
|
||||
|
||||
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
|
||||
|
|
|
@ -140,7 +140,7 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
|
|||
toSubmit = compose(submittedChangeset, userChangeset, apool);
|
||||
} else {
|
||||
// Get my authorID
|
||||
const authorId = parent.parent.pad.myUserInfo.userId;
|
||||
const authorId = window.pad.myUserInfo.userId;
|
||||
|
||||
// Sanitize authorship: Replace all author attributes with this user's author ID in case the
|
||||
// text was copied from another author.
|
||||
|
|
|
@ -171,7 +171,7 @@ export const getAvailablePlugins = (maxCacheAge: number|false) => {
|
|||
return resolve(availablePlugins);
|
||||
}
|
||||
|
||||
await axios.get('https://static.etherpad.org/plugins.json', {headers})
|
||||
await axios.get(`${settings.updateServer}/plugins.json`, {headers})
|
||||
.then((pluginsLoaded:AxiosResponse<MapArrayType<PackageInfo>>) => {
|
||||
availablePlugins = pluginsLoaded.data;
|
||||
cacheTimestamp = nowTimestamp;
|
||||
|
|
|
@ -15,7 +15,8 @@ class Scroll {
|
|||
// DOM reference
|
||||
this.outerWin = outerWin;
|
||||
this.doc = this.outerWin.contentDocument!;
|
||||
this.rootDocument = parent.parent.document;
|
||||
this.rootDocument = document;
|
||||
console.log(this.rootDocument)
|
||||
}
|
||||
|
||||
scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep: RepModel, isScrollableEvent: boolean, innerHeight: number) {
|
||||
|
@ -112,7 +113,7 @@ class Scroll {
|
|||
};
|
||||
|
||||
_getEditorPositionTop() {
|
||||
const editor = parent.document.getElementsByTagName('iframe');
|
||||
const editor = document.getElementsByTagName('iframe');
|
||||
const editorPositionTop = editor[0].offsetTop;
|
||||
return editorPositionTop;
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"ep_etherpad-lite": "workspace:../src",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.3"
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.7"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue