From 08b629eda1447b82b355a6e9ea8a00161a176a0b Mon Sep 17 00:00:00 2001 From: Micah Date: Tue, 20 Sep 2022 22:30:51 -0500 Subject: [PATCH 1/6] beginnings of a Yjs wiki integration --- package.json | 5 ++- src/pages/space/wiki/BlockToolbar.tsx | 1 - src/pages/space/wiki/WikiSpaceBlock.tsx | 7 +++-- src/pages/space/wiki/WikiSpacePage.tsx | 1 - src/pages/space/wiki/WikiSpaceView.tsx | 41 ++++++++----------------- 5 files changed, 21 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 021a2ec..efd8b9c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@tiptap/extension-code-block-lowlight": "^2.0.0-beta.73", "@tiptap/extension-document": "^2.0.0-beta.196", "@tiptap/extension-heading": "^2.0.0-beta.196", + "@tiptap/extension-collaboration": "^2.0.0-beta.196", "@tiptap/extension-highlight": "^2.0.0-beta.35", "@tiptap/extension-history": "^2.0.0-beta.196", "@tiptap/extension-italic": "^2.0.0-beta.196", @@ -42,7 +43,9 @@ "react-router-dom": "^6.2.1", "react-scripts": "5.0.0", "tippy.js": "^6.3.7", - "web-vitals": "^2.1.0" + "web-vitals": "^2.1.0", + "y-webrtc": "^10.2.3", + "yjs": "^13.5.41" }, "devDependencies": { "@testing-library/jest-dom": "^5.14.1", diff --git a/src/pages/space/wiki/BlockToolbar.tsx b/src/pages/space/wiki/BlockToolbar.tsx index 09b0625..7f0f617 100644 --- a/src/pages/space/wiki/BlockToolbar.tsx +++ b/src/pages/space/wiki/BlockToolbar.tsx @@ -1,7 +1,6 @@ import type { Editor } from '@tiptap/core' import { Button, ButtonGroup } from '@mui/material' import CodeIcon from '@mui/icons-material/Code'; -import HighlightIcon from '@mui/icons-material/Highlight'; import LinkIcon from '@mui/icons-material/Link'; diff --git a/src/pages/space/wiki/WikiSpaceBlock.tsx b/src/pages/space/wiki/WikiSpaceBlock.tsx index 5165be3..295b4c8 100644 --- a/src/pages/space/wiki/WikiSpaceBlock.tsx +++ b/src/pages/space/wiki/WikiSpaceBlock.tsx @@ -4,6 +4,8 @@ import { Fragment, useEffect, useRef, useState } from 'react'; import { Add, DragIndicator, Delete } from '@mui/icons-material'; import { Block, BlockType, WikiSpace } from '@hyper-hyper-space/wiki-collab'; +import Collaboration from '@tiptap/extension-collaboration' + import Document from '@tiptap/extension-document' import Paragraph from '@tiptap/extension-paragraph' import Text from '@tiptap/extension-text' @@ -39,7 +41,7 @@ import { Box } from '@mui/system'; function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEditing?: any, idx: number, showAddBlockMenu: (newAnchorEl: HTMLElement, newBlockIdx?: number) => void, removeBlock: () => void}, ) { - const { spaceContext } = useOutletContext(); + const { spaceContext, ydoc } = useOutletContext(); const resources = spaceContext.resources; const blockState = useObjectState(props.block, {debounceFreq: 250}); const blockContentsState = useObjectState(props.block?.contents, {debounceFreq: 250}); @@ -115,7 +117,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit blockContents.setResources(resources!); blockContents.saveQueuedOps(); console.log('SAVED BLOCK') - }, 1500)) + }, 10000)) const editor = useEditor({ extensions: [ @@ -133,6 +135,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit WikiLink.configure({ definedPageNames: [...pageSetState?.getValue()?.pages?.values()!].map(page => page.name!) }), + Collaboration.configure({document: ydoc, field: blockState?.getValue()?.getId()}), CodeBlockLowlight.configure({lowlight}), Placeholder.configure({ placeholder: 'Write something...' }) ], diff --git a/src/pages/space/wiki/WikiSpacePage.tsx b/src/pages/space/wiki/WikiSpacePage.tsx index e2a32a9..864e3e5 100644 --- a/src/pages/space/wiki/WikiSpacePage.tsx +++ b/src/pages/space/wiki/WikiSpacePage.tsx @@ -21,7 +21,6 @@ function WikiSpacePage(props: {noNavigation: boolean, navigationWidth: string, c const { pageName } = useParams(); const { wiki, nav } = useOutletContext(); - //const wikiState = useObjectState(wiki, {debounceFreq: 250}); const wikiTitleState = useObjectState(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.title, debounceFreq: 250}); const pageSetState = useObjectState(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.pages, debounceFreq: 250}); diff --git a/src/pages/space/wiki/WikiSpaceView.tsx b/src/pages/space/wiki/WikiSpaceView.tsx index dc78135..aed0e1f 100644 --- a/src/pages/space/wiki/WikiSpaceView.tsx +++ b/src/pages/space/wiki/WikiSpaceView.tsx @@ -6,6 +6,9 @@ import { Outlet, Route, Routes, useNavigate, useOutletContext } from 'react-rout import NewPage from './NewPage'; import WikiSpaceNavigation from './WikiSpaceNavigation'; import { SpaceContext } from '../SpaceFrame'; +import { WebrtcProvider } from 'y-webrtc' +import { Doc } from 'yjs'; +import { memoize } from 'lodash-es'; type WikiNav = { goToPage: (pageName: string) => void, @@ -16,9 +19,14 @@ type WikiNav = { type WikiContext = { wiki : WikiSpace, nav : WikiNav, - spaceContext : SpaceContext + spaceContext : SpaceContext, + yjsProvider : WebrtcProvider, + ydoc: Doc } +const getYdoc = memoize(id => new Doc()) +const getYjsProvider = memoize(id => new WebrtcProvider(id, getYdoc(id))) + function WikiSpaceView(props: { entryPoint: WikiSpace, path?: string }) { @@ -57,7 +65,9 @@ function WikiSpaceView(props: { entryPoint: WikiSpace, path?: string }) { goToAddPage: goToAddPage, goToIndex: goToIndex }, - spaceContext: spaceContext + spaceContext: spaceContext, + yjsProvider: getYjsProvider(wiki.getId()), + ydoc: getYdoc(wiki.getId()) } const theme = useTheme(); @@ -102,33 +112,6 @@ function WikiSpaceView(props: { entryPoint: WikiSpace, path?: string }) { - - {/* - - - - }} - > - {currentPage === undefined && - Loading... - } - {currentPage !== undefined && - - } - - */}; } export type { WikiContext }; From b1e507499879dd1910a9690eece23e50b913d028 Mon Sep 17 00:00:00 2001 From: Micah Date: Wed, 21 Sep 2022 10:50:46 -0500 Subject: [PATCH 2/6] base Yjs block collaboration on block content hash --- src/pages/space/wiki/WikiSpaceBlock.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/space/wiki/WikiSpaceBlock.tsx b/src/pages/space/wiki/WikiSpaceBlock.tsx index 295b4c8..6a170e2 100644 --- a/src/pages/space/wiki/WikiSpaceBlock.tsx +++ b/src/pages/space/wiki/WikiSpaceBlock.tsx @@ -20,7 +20,7 @@ import TextAlign from '@tiptap/extension-text-align' import Underline from '@tiptap/extension-underline' import WikiLink from './WikiLink'; import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight' -import History from '@tiptap/extension-history'; +// import History from '@tiptap/extension-history'; import { lowlight } from 'lowlight/lib/all.js' import { EditorContent, useEditor } from '@tiptap/react' import { MutableReference, MutationEvent } from '@hyper-hyper-space/core'; @@ -46,6 +46,8 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit const blockState = useObjectState(props.block, {debounceFreq: 250}); const blockContentsState = useObjectState(props.block?.contents, {debounceFreq: 250}); + const [editorFieldId, setEditorFieldId] = useState(props.block?.contents?.hash()) + const { wiki } = useOutletContext(); const pageSetState = useObjectState(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.pages, debounceFreq: 250}); @@ -128,14 +130,14 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit Strike, Italic, Heading, - History, + // History, // conflicts with Collaboration plugin Highlight, TextAlign, Underline, WikiLink.configure({ definedPageNames: [...pageSetState?.getValue()?.pages?.values()!].map(page => page.name!) }), - Collaboration.configure({document: ydoc, field: blockState?.getValue()?.getId()}), + Collaboration.configure({document: ydoc, field: (editorFieldId || '')}), CodeBlockLowlight.configure({lowlight}), Placeholder.configure({ placeholder: 'Write something...' }) ], @@ -152,7 +154,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit editable, onBlur: stoppedEditing, onFocus: startedEditing - }); + }, [editorFieldId]); /*editor?.on('focus', () => { console.log('focusing editor') @@ -160,6 +162,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit });*/ useEffect(() => { + setEditorFieldId(blockContentsState?.getValue()?.hash()) const newText = blockContentsState?.getValue()?.getValue(); if (!newText) { @@ -169,6 +172,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit if (!editor?.isDestroyed && newText !== editor?.getHTML()) { editor?.commands.setContent(newText, false, { preserveWhitespace: 'full' }) } + }, [blockContentsState, editor])//, editor, blockState]) const handleAddBlock = (event: React.MouseEvent) => { From 12452f3f973438cf2ca3202fbadacd7fddf208ba Mon Sep 17 00:00:00 2001 From: Micah Date: Wed, 21 Sep 2022 12:21:54 -0500 Subject: [PATCH 3/6] disable tiptap updates for image blocks --- src/pages/space/wiki/WikiSpaceBlock.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/space/wiki/WikiSpaceBlock.tsx b/src/pages/space/wiki/WikiSpaceBlock.tsx index 6a170e2..0fb3524 100644 --- a/src/pages/space/wiki/WikiSpaceBlock.tsx +++ b/src/pages/space/wiki/WikiSpaceBlock.tsx @@ -119,7 +119,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit blockContents.setResources(resources!); blockContents.saveQueuedOps(); console.log('SAVED BLOCK') - }, 10000)) + }, 1500)) const editor = useEditor({ extensions: [ @@ -145,6 +145,10 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit preserveWhitespace: 'full' }, onUpdate: async ({ editor }) => { + if (blockState?.value?.type === BlockType.Image) { + console.log('NOT UPDATING IMAGE BLOCK') + return + } console.log('UPDATE') console.log(blockContentsState) if (blockContentsState && !editor.isDestroyed) { @@ -154,7 +158,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit editable, onBlur: stoppedEditing, onFocus: startedEditing - }, [editorFieldId]); + }, [editorFieldId, blockState?.value?.type]); /*editor?.on('focus', () => { console.log('focusing editor') @@ -173,7 +177,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit editor?.commands.setContent(newText, false, { preserveWhitespace: 'full' }) } - }, [blockContentsState, editor])//, editor, blockState]) + }, [blockContentsState, editor, blockState])//, editor, blockState]) const handleAddBlock = (event: React.MouseEvent) => { props.showAddBlockMenu(event.currentTarget, props.idx + 1); From 28e247d6dcb8fcf47c671a02f5141e5a013a51ba Mon Sep 17 00:00:00 2001 From: Micah Date: Wed, 21 Sep 2022 13:32:45 -0500 Subject: [PATCH 4/6] rudimentary collaborative cursor for wiki... not secure! --- package.json | 1 + src/pages/space/wiki/WikiSpaceBlock.scss | 20 +++++++++++++++++++- src/pages/space/wiki/WikiSpaceBlock.tsx | 11 ++++++++++- src/pages/space/wiki/WikiSpaceView.tsx | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index efd8b9c..454c199 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@tiptap/extension-document": "^2.0.0-beta.196", "@tiptap/extension-heading": "^2.0.0-beta.196", "@tiptap/extension-collaboration": "^2.0.0-beta.196", + "@tiptap/extension-collaboration-cursor": "^2.0.0-beta.196", "@tiptap/extension-highlight": "^2.0.0-beta.35", "@tiptap/extension-history": "^2.0.0-beta.196", "@tiptap/extension-italic": "^2.0.0-beta.196", diff --git a/src/pages/space/wiki/WikiSpaceBlock.scss b/src/pages/space/wiki/WikiSpaceBlock.scss index 8ad9607..cf47dde 100644 --- a/src/pages/space/wiki/WikiSpaceBlock.scss +++ b/src/pages/space/wiki/WikiSpaceBlock.scss @@ -104,4 +104,22 @@ a:not(.existing-page-link) { text-decoration: underline currentcolor dotted; } -@import url('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/github-dark.min.css') \ No newline at end of file +.collaboration-cursor__caret { + display: inline-block; + margin: 0; + padding: 0; + // position: absolute; +} + +// .collaboration-cursor__caret::after { +// content: "}{"; +// position: absolute; +// } + +.collaboration-cursor__label { + color: white; + // position: absolute; + // top: -1em; +} + +@import url('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/github-dark.min.css') diff --git a/src/pages/space/wiki/WikiSpaceBlock.tsx b/src/pages/space/wiki/WikiSpaceBlock.tsx index 0fb3524..1ca7a9e 100644 --- a/src/pages/space/wiki/WikiSpaceBlock.tsx +++ b/src/pages/space/wiki/WikiSpaceBlock.tsx @@ -5,6 +5,7 @@ import { Add, DragIndicator, Delete } from '@mui/icons-material'; import { Block, BlockType, WikiSpace } from '@hyper-hyper-space/wiki-collab'; import Collaboration from '@tiptap/extension-collaboration' +import CollaborationCursor from '@tiptap/extension-collaboration-cursor'; import Document from '@tiptap/extension-document' import Paragraph from '@tiptap/extension-paragraph' @@ -29,6 +30,9 @@ import { Icon, Tooltip } from '@mui/material'; import { useOutletContext } from 'react-router'; import { WikiContext } from './WikiSpaceView'; import { Box } from '@mui/system'; + +import ColorHash from 'color-hash'; +const colorHash = new ColorHash({lightness: 0.4}); // other extensions from the tiptap StarterKit: // import BlockQuote from '@tiptap/extension-blockquote'; // import BulletList from '@tiptap/extension-bullet-list'; @@ -41,7 +45,7 @@ import { Box } from '@mui/system'; function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEditing?: any, idx: number, showAddBlockMenu: (newAnchorEl: HTMLElement, newBlockIdx?: number) => void, removeBlock: () => void}, ) { - const { spaceContext, ydoc } = useOutletContext(); + const { spaceContext, ydoc, yjsProvider } = useOutletContext(); const resources = spaceContext.resources; const blockState = useObjectState(props.block, {debounceFreq: 250}); const blockContentsState = useObjectState(props.block?.contents, {debounceFreq: 250}); @@ -121,6 +125,7 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit console.log('SAVED BLOCK') }, 1500)) + console.log('space context author name', spaceContext.home?.getAuthor()?.info?.name) const editor = useEditor({ extensions: [ Document, @@ -138,6 +143,10 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit definedPageNames: [...pageSetState?.getValue()?.pages?.values()!].map(page => page.name!) }), Collaboration.configure({document: ydoc, field: (editorFieldId || '')}), + CollaborationCursor.configure({ provider: yjsProvider, user: { + name: spaceContext.home?.getAuthor()?.info?.name || "anonymous", + color: colorHash.hex(spaceContext.home?.getAuthor()?.info?.name || "anonymous"), + }}), CodeBlockLowlight.configure({lowlight}), Placeholder.configure({ placeholder: 'Write something...' }) ], diff --git a/src/pages/space/wiki/WikiSpaceView.tsx b/src/pages/space/wiki/WikiSpaceView.tsx index aed0e1f..fe66fa2 100644 --- a/src/pages/space/wiki/WikiSpaceView.tsx +++ b/src/pages/space/wiki/WikiSpaceView.tsx @@ -31,6 +31,7 @@ function WikiSpaceView(props: { entryPoint: WikiSpace, path?: string }) { const spaceContext = useOutletContext(); + console.log(spaceContext) const wiki = props.entryPoint; From 1eff1393199944542dc8703d343ce441abef83c9 Mon Sep 17 00:00:00 2001 From: Santi Bazerque Date: Mon, 26 Sep 2022 15:19:11 -0300 Subject: [PATCH 5/6] added deps for color-hash library --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 454c199..64031cc 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "tippy.js": "^6.3.7", "web-vitals": "^2.1.0", "y-webrtc": "^10.2.3", - "yjs": "^13.5.41" + "yjs": "^13.5.41", + "color-hash": "^2.0.1" }, "devDependencies": { "@testing-library/jest-dom": "^5.14.1", @@ -58,6 +59,7 @@ "@types/react": "^17.0.20", "@types/react-beautiful-dnd": "^13.1.0", "@types/react-dom": "^17.0.9", + "@types/color-hash": "^1.0.2", "node-sass": "^7.0.1", "typescript": "^4.4.2" }, From a66c88130a17bb2ea172b1d862a42c76d1d1b8f0 Mon Sep 17 00:00:00 2001 From: Santi Bazerque Date: Mon, 10 Oct 2022 12:30:39 -0300 Subject: [PATCH 6/6] updated to latest core API (collections refactor) --- src/index.tsx | 2 +- src/model/text/TextSpace.ts | 6 ++---- src/pages/space/wiki/WikiSpaceBlock.tsx | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 188783e..0cc2cf0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,7 +20,7 @@ HistorySynchronizer.controlLog.level = LogLevel.INFO; const main = async () => { - const configBackend = new WorkerSafeIdbBackend('hyper-browser-config-0.3'); + const configBackend = new WorkerSafeIdbBackend('hyper-browser-config-0.4'); let configBackendError: (string|undefined) = undefined; diff --git a/src/model/text/TextSpace.ts b/src/model/text/TextSpace.ts index 018f60d..1d97456 100644 --- a/src/model/text/TextSpace.ts +++ b/src/model/text/TextSpace.ts @@ -15,9 +15,7 @@ class TextSpace extends HashedObject implements SpaceEntryPoint this.setRandomId(); - const content = new MutableReference(); - content.typeConstraints = ['string']; - + const content = new MutableReference({acceptedTypes: ['string']}); this.addDerivedField('content', content); } @@ -54,7 +52,7 @@ class TextSpace extends HashedObject implements SpaceEntryPoint } } - if (!Types.checkTypeConstraint(this.content.typeConstraints, ['string'])) { + if (!this.content.validateAcceptedTypes(['string'])) { return false; } diff --git a/src/pages/space/wiki/WikiSpaceBlock.tsx b/src/pages/space/wiki/WikiSpaceBlock.tsx index 1ca7a9e..719125c 100644 --- a/src/pages/space/wiki/WikiSpaceBlock.tsx +++ b/src/pages/space/wiki/WikiSpaceBlock.tsx @@ -183,6 +183,8 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit } if (!editor?.isDestroyed && newText !== editor?.getHTML()) { + console.log('setting contents of block ' + props.block.getLastHash() + ' to:'); + console.log(newText); editor?.commands.setContent(newText, false, { preserveWhitespace: 'full' }) }