feat: collaborative editing

This commit is contained in:
Artemy 2023-04-04 20:10:41 +03:00
parent 112db5c6ac
commit 536c847375
6 changed files with 106 additions and 2 deletions

View file

@ -17,6 +17,31 @@ if (!fs.existsSync("./notes")) {
fs.mkdirSync("./notes"); fs.mkdirSync("./notes");
} }
io.on("connection", (socket) => {
socket.on("nameChanged", ({ name, room }) => {
socket.to(room).emit("nameChanged", {
name,
});
});
socket.on("textChanged", ({ text, room }) => {
socket.to(room).emit("textChanged", {
text,
});
});
socket.on("joinRoom", (room) => {
let rooms = Array.from(io.sockets.adapter.sids.get(socket.id));
for (let room of rooms) {
if (socket.id != room) {
socket.leave(room);
}
}
socket.join(room);
});
});
app.use(bodyParser.json()); app.use(bodyParser.json());
app.post("/publish", function (req, res) { app.post("/publish", function (req, res) {

View file

@ -12,7 +12,6 @@ import PubNoteSafe from "./pages/pubNoteSafe";
import RenderMarkdown from "./components/markdown"; import RenderMarkdown from "./components/markdown";
import socket from "./components/socket"; import socket from "./components/socket";
import Settings from "./pages/settings"; import Settings from "./pages/settings";
import Chat from "./pages/chat";
import Locales from "./localisation/main"; import Locales from "./localisation/main";
import { useState } from "react"; import { useState } from "react";
@ -55,7 +54,6 @@ function App() {
<Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} /> <Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} />
<Route path="/pubError" element={<PubError />} /> <Route path="/pubError" element={<PubError />} />
<Route path="/settings" element={<Settings />} /> <Route path="/settings" element={<Settings />} />
<Route path="/chat" element={<Chat />} />
<Route <Route
path="/about" path="/about"
element={ element={

View file

@ -43,6 +43,9 @@ let en = {
NoNotesYet: "No notes yet", NoNotesYet: "No notes yet",
AIComplete: "Continue Note (AI)", AIComplete: "Continue Note (AI)",
AdditionalFeatures: "Additional features", AdditionalFeatures: "Additional features",
CollabEdit: "Collaborative editing",
Password: "Password",
CollabEditPassword: "Password for collaborative editing",
}; };
export default en; export default en;

View file

@ -44,6 +44,9 @@ let ru = {
NoNotesYet: "Заметок пока нет", NoNotesYet: "Заметок пока нет",
AIComplete: "Продолжить заметку (ИИ)", AIComplete: "Продолжить заметку (ИИ)",
AdditionalFeatures: "Дополнительные функции", AdditionalFeatures: "Дополнительные функции",
CollabEdit: "Совместное редактрование",
Password: "Пароль",
CollabEditPassword: "Пароль для совместного редактирования",
}; };
export default ru; export default ru;

View file

@ -22,6 +22,26 @@ import {
import { inputStyle } from "../components/styles"; import { inputStyle } from "../components/styles";
import { Complete } from "../components/openai"; import { Complete } from "../components/openai";
function nameUpdate(e) {
if (Date.now() - window.lastSocketUpdate > window.socketTimeout) {
socket.emit("nameChanged", {
name: e.target.value,
room: settings.CollabEditPassword,
});
window.lastSocketUpdate = Date.now();
}
}
function textUpdate(e) {
if (Date.now() - window.lastSocketUpdate > window.socketTimeout) {
socket.emit("textChanged", {
text: e.target.value,
room: settings.CollabEditPassword,
});
window.lastSocketUpdate = Date.now();
}
}
function CreateNote() { function CreateNote() {
const [preview, setPreview] = useState(false); const [preview, setPreview] = useState(false);
const [publicState, setPublicState] = useState(settings.publicNote); const [publicState, setPublicState] = useState(settings.publicNote);
@ -42,6 +62,32 @@ function CreateNote() {
md = md.value.trim(); md = md.value.trim();
localStorage.setItem("NoteText", md); localStorage.setItem("NoteText", md);
if (settings.CollabEdit === true) {
socket.emit("textChanged", {
text: md,
room: settings.CollabEditPassword,
});
}
}
if (settings.CollabEdit === true) {
if (!window.alreadyConnected) {
socket.emit("joinRoom", settings.CollabEditPassword);
window.alreadyConnected = true;
window.lastSocketUpdate = Date.now();
window.socketTimeout = 100;
}
socket.on("textChanged", (data) => {
setText(data.text);
localStorage.setItem("NoteText", data.text);
});
socket.on("nameChanged", (data) => {
setName(data.name);
localStorage.setItem("NoteName", data.name);
});
} }
return ( return (
@ -73,6 +119,13 @@ function CreateNote() {
onChange={(e) => { onChange={(e) => {
setName(e.target.value); setName(e.target.value);
localStorage.setItem("NoteName", e.target.value); localStorage.setItem("NoteName", e.target.value);
if (settings.CollabEdit === true) {
nameUpdate(e);
setTimeout(() => {
nameUpdate(e);
}, window.socketTimeout);
}
}} }}
/> />
<textarea <textarea
@ -87,6 +140,13 @@ function CreateNote() {
onChange={(e) => { onChange={(e) => {
setText(e.target.value); setText(e.target.value);
localStorage.setItem("NoteText", e.target.value); localStorage.setItem("NoteText", e.target.value);
if (settings.CollabEdit === true) {
textUpdate(e);
setTimeout(() => {
textUpdate(e);
}, window.socketTimeout);
}
}} }}
value={localStorage.getItem("NoteText") || ""} value={localStorage.getItem("NoteText") || ""}
></textarea> ></textarea>
@ -148,6 +208,10 @@ function CreateNote() {
}} }}
/> />
)} )}
<SettingsCheckBox
label={locals.CollabEdit}
settingName="CollabEdit"
/>
</SettingsSection> </SettingsSection>
</div> </div>
)} )}

View file

@ -51,6 +51,17 @@ function Settings() {
label={locals.AdditionalFeatures} label={locals.AdditionalFeatures}
settingName="additionalFeatures" settingName="additionalFeatures"
/> />
<SettingsCheckBox label={locals.CollabEdit} settingName="CollabEdit" />
<SettingsTextInput
placeholder={locals.Password}
label={locals.CollabEditPassword}
settingName="CollabEditPassword"
onChange={(e) => {
window.alreadyConnected = false;
}}
/>
</SettingsSection> </SettingsSection>
<SettingsSection name={locals.Interface}> <SettingsSection name={locals.Interface}>