mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 20:09:25 +00:00
feat: init editor + collaboration
This commit is contained in:
parent
8ca7d9fc1a
commit
85ac404ee7
10 changed files with 1437 additions and 12 deletions
|
|
@ -2,6 +2,7 @@ import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||||
import Element, { ElementWrapper } from "./element";
|
import Element, { ElementWrapper } from "./element";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
|
||||||
const ChapterWrapper = styled.div`
|
const ChapterWrapper = styled.div`
|
||||||
|
|
@ -26,7 +27,7 @@ function Chapter(props: any) {
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<ElementsList {...provided.droppableProps} ref={provided.innerRef}>
|
<ElementsList {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
{props.info.list.elements.map((element: any, index: any) => (
|
{props.info.list.elements.map((element: any, index: any) => (
|
||||||
<Element key={element.id} element={element} index={index}></Element>
|
<div key={element.id}> <Element key={element.id} element={element} index={index}></Element> <Link href={"/"}><a>Edit</a></Link></div>
|
||||||
))}
|
))}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</ElementsList>
|
</ElementsList>
|
||||||
|
|
|
||||||
33
front/components/editor/Editor.tsx
Normal file
33
front/components/editor/Editor.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { default as React, useEffect, useRef } from "react";
|
||||||
|
import * as Y from "yjs";
|
||||||
|
import { WebrtcProvider } from "y-webrtc";
|
||||||
|
import EditorWithOptions from "./EditorWithOptions";
|
||||||
|
|
||||||
|
// tools
|
||||||
|
|
||||||
|
function Editor() {
|
||||||
|
// A new Y document
|
||||||
|
const ydoc = new Y.Doc();
|
||||||
|
const [providerState, setProviderState] = React.useState<any>({});
|
||||||
|
const [ydocState, setYdocState] = React.useState<any>({});
|
||||||
|
const [isLoading, setIsLoading] = React.useState(true);
|
||||||
|
|
||||||
|
function createRTCProvider() {
|
||||||
|
const provider = new WebrtcProvider("learnhouse-1", ydoc);
|
||||||
|
setYdocState(ydoc);
|
||||||
|
setProviderState(provider);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
createRTCProvider();
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<EditorWithOptions provider={providerState} ydoc={ydocState}></EditorWithOptions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Editor;
|
||||||
139
front/components/editor/EditorWithOptions.tsx
Normal file
139
front/components/editor/EditorWithOptions.tsx
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useEditor, EditorContent } from "@tiptap/react";
|
||||||
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
|
import Collaboration from "@tiptap/extension-collaboration";
|
||||||
|
import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
|
||||||
|
|
||||||
|
function EditorWithOptions(props: any) {
|
||||||
|
const MenuBar = ({ editor }: any) => {
|
||||||
|
if (!editor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleBold().run()}
|
||||||
|
className={editor.isActive("bold") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
bold
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
||||||
|
className={editor.isActive("italic") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
italic
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleStrike().run()}
|
||||||
|
className={editor.isActive("strike") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
strike
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleCode().run()}
|
||||||
|
className={editor.isActive("code") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
code
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>clear marks</button>
|
||||||
|
<button onClick={() => editor.chain().focus().clearNodes().run()}>clear nodes</button>
|
||||||
|
<button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive("paragraph") ? "is-active" : ""}>
|
||||||
|
paragraph
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||||
|
className={editor.isActive("heading", { level: 1 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h1
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||||
|
className={editor.isActive("heading", { level: 2 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h2
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
|
||||||
|
className={editor.isActive("heading", { level: 3 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h3
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
|
||||||
|
className={editor.isActive("heading", { level: 4 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h4
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
|
||||||
|
className={editor.isActive("heading", { level: 5 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h5
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
|
||||||
|
className={editor.isActive("heading", { level: 6 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h6
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().toggleBulletList().run()} className={editor.isActive("bulletList") ? "is-active" : ""}>
|
||||||
|
bullet list
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().toggleOrderedList().run()} className={editor.isActive("orderedList") ? "is-active" : ""}>
|
||||||
|
ordered list
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().toggleCodeBlock().run()} className={editor.isActive("codeBlock") ? "is-active" : ""}>
|
||||||
|
code block
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().toggleBlockquote().run()} className={editor.isActive("blockquote") ? "is-active" : ""}>
|
||||||
|
blockquote
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>horizontal rule</button>
|
||||||
|
<button onClick={() => editor.chain().focus().setHardBreak().run()}>hard break</button>
|
||||||
|
<button onClick={() => editor.chain().focus().undo().run()} disabled={!editor.can().chain().focus().undo().run()}>
|
||||||
|
undo
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().redo().run()} disabled={!editor.can().chain().focus().redo().run()}>
|
||||||
|
redo
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit.configure({
|
||||||
|
// The Collaboration extension comes with its own history handling
|
||||||
|
history: false,
|
||||||
|
}),
|
||||||
|
// Register the document with Tiptap
|
||||||
|
Collaboration.configure({
|
||||||
|
document: props.ydoc,
|
||||||
|
}),
|
||||||
|
// Register the collaboration cursor extension
|
||||||
|
CollaborationCursor.configure({
|
||||||
|
provider: props.provider,
|
||||||
|
user: {
|
||||||
|
name: "Cyndi Lauper",
|
||||||
|
color: "#f783ac",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: "<p>Hello World!</p>",
|
||||||
|
});
|
||||||
|
console.log(props.ydoc);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MenuBar editor={editor} />
|
||||||
|
<EditorContent editor={editor} style={{ backgroundColor: "white" }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditorWithOptions;
|
||||||
1235
front/package-lock.json
generated
1235
front/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,12 +9,18 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tiptap/extension-collaboration": "^2.0.0-beta.199",
|
||||||
|
"@tiptap/extension-collaboration-cursor": "^2.0.0-beta.199",
|
||||||
|
"@tiptap/react": "^2.0.0-beta.199",
|
||||||
|
"@tiptap/starter-kit": "^2.0.0-beta.199",
|
||||||
"framer-motion": "^7.3.6",
|
"framer-motion": "^7.3.6",
|
||||||
"next": "12.3.1",
|
"next": "12.3.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"styled-components": "^5.3.5"
|
"styled-components": "^5.3.5",
|
||||||
|
"y-webrtc": "^10.2.3",
|
||||||
|
"yjs": "^13.5.42"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.7.18",
|
"@types/node": "18.7.18",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { default as React, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import Layout from "../../../../../../components/ui/layout";
|
||||||
|
import { Title } from "../../../../../../components/ui/styles/title";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const Editor = dynamic(() => import("../../../../../../components/editor/editor"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// tools
|
||||||
|
|
||||||
|
function EditElement() {
|
||||||
|
// A new Y document
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Title>Edit Page </Title>
|
||||||
|
<br />
|
||||||
|
<Editor></Editor>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditElement;
|
||||||
|
|
@ -141,6 +141,7 @@ function CourseEdit() {
|
||||||
<div key={"chapters"} {...provided.droppableProps} ref={provided.innerRef}>
|
<div key={"chapters"} {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
{getChapters().map((info: any, index: any) => (
|
{getChapters().map((info: any, index: any) => (
|
||||||
<Chapter key={index} info={info} index={index}></Chapter>
|
<Chapter key={index} info={info} index={index}></Chapter>
|
||||||
|
|
||||||
))}
|
))}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,8 @@ const CourseWrapper = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 794px;
|
width: 794px;
|
||||||
height: 224.28px;
|
height: 224.28px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: top;
|
||||||
|
|
||||||
background: url(), #d9d9d9;
|
background: url(), #d9d9d9;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.19);
|
border: 1px solid rgba(255, 255, 255, 0.19);
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ const CoursesIndexPage = () => {
|
||||||
<button style={{backgroundColor:"red" , border:"none"}} onClick={() => deleteCourses(course.course_id)}>Delete</button>
|
<button style={{backgroundColor:"red" , border:"none"}} onClick={() => deleteCourses(course.course_id)}>Delete</button>
|
||||||
<Link href={"/org/" + orgslug + "/course/" + removeCoursePrefix(course.course_id) + "/edit"}>
|
<Link href={"/org/" + orgslug + "/course/" + removeCoursePrefix(course.course_id) + "/edit"}>
|
||||||
<a>
|
<a>
|
||||||
<button >Edit</button>
|
<button >Edit Chapters</button>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue