Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Yjs wiki block editing integration #18

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"@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-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",
Expand All @@ -42,7 +44,10 @@
"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",
"color-hash": "^2.0.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
Expand All @@ -54,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"
},
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down
6 changes: 2 additions & 4 deletions src/model/text/TextSpace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ class TextSpace extends HashedObject implements SpaceEntryPoint

this.setRandomId();

const content = new MutableReference<string>();
content.typeConstraints = ['string'];

const content = new MutableReference<string>({acceptedTypes: ['string']});
this.addDerivedField('content', content);
}

Expand Down Expand Up @@ -54,7 +52,7 @@ class TextSpace extends HashedObject implements SpaceEntryPoint
}
}

if (!Types.checkTypeConstraint(this.content.typeConstraints, ['string'])) {
if (!this.content.validateAcceptedTypes(['string'])) {
return false;
}

Expand Down
1 change: 0 additions & 1 deletion src/pages/space/wiki/BlockToolbar.tsx
Original file line number Diff line number Diff line change
@@ -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';


Expand Down
20 changes: 19 additions & 1 deletion src/pages/space/wiki/WikiSpaceBlock.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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')
.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')
32 changes: 27 additions & 5 deletions src/pages/space/wiki/WikiSpaceBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ 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 CollaborationCursor from '@tiptap/extension-collaboration-cursor';

import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
Expand All @@ -18,7 +21,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';
Expand All @@ -27,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';
Expand All @@ -39,11 +45,13 @@ 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<WikiContext>();
const { spaceContext, ydoc, yjsProvider } = useOutletContext<WikiContext>();
const resources = spaceContext.resources;
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<WikiContext>();
const pageSetState = useObjectState<WikiSpace>(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.pages, debounceFreq: 250});

Expand Down Expand Up @@ -117,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,
Expand All @@ -126,20 +135,29 @@ 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: (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...' })
],
parseOptions: {
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) {
Expand All @@ -149,24 +167,28 @@ function WikiSpaceBlock(props: { block: Block, startedEditing?: any, stoppedEdit
editable,
onBlur: stoppedEditing,
onFocus: startedEditing
});
}, [editorFieldId, blockState?.value?.type]);

/*editor?.on('focus', () => {
console.log('focusing editor')
startedEditing!(editor)
});*/

useEffect(() => {
setEditorFieldId(blockContentsState?.getValue()?.hash())
const newText = blockContentsState?.getValue()?.getValue();

if (!newText) {
return
}

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' })
}
}, [blockContentsState, editor])//, editor, blockState])

}, [blockContentsState, editor, blockState])//, editor, blockState])

const handleAddBlock = (event: React.MouseEvent<HTMLButtonElement>) => {
props.showAddBlockMenu(event.currentTarget, props.idx + 1);
Expand Down
1 change: 0 additions & 1 deletion src/pages/space/wiki/WikiSpacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ function WikiSpacePage(props: {noNavigation: boolean, navigationWidth: string, c
const { pageName } = useParams();
const { wiki, nav } = useOutletContext<WikiContext>();

//const wikiState = useObjectState<WikiSpace>(wiki, {debounceFreq: 250});
const wikiTitleState = useObjectState<WikiSpace>(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.title, debounceFreq: 250});
const pageSetState = useObjectState<WikiSpace>(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.pages, debounceFreq: 250});

Expand Down
42 changes: 13 additions & 29 deletions src/pages/space/wiki/WikiSpaceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -16,13 +19,19 @@ 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 }) {


const spaceContext = useOutletContext<SpaceContext>();
console.log(spaceContext)

const wiki = props.entryPoint;

Expand Down Expand Up @@ -57,7 +66,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();
Expand Down Expand Up @@ -102,33 +113,6 @@ function WikiSpaceView(props: { entryPoint: WikiSpace, path?: string }) {
</Route>
</Routes>


{/* <Paper style={{ padding: '60px 1rem', height: '100%' }}>
<TextField
value={targetPageName}
// value={pate}
onKeyPress={onNavigationUpdate}
//inputRef={navigationRef}
onChange={onTargetPageNameChange}
InputProps={{
style:{fontSize: 25, fontWeight: 'bold'},
endAdornment:
<InputAdornment position="end">
<IconButton
onClick={navigate}
aria-label="navigate to wiki page"
><ExploreIcon></ExploreIcon></IconButton>
</InputAdornment>
}}
></TextField>
{currentPage === undefined &&
<Typography>Loading...</Typography>
}
{currentPage !== undefined &&
<WikiSpacePage page={currentPage}></WikiSpacePage>
}

</Paper>*/};
}

export type { WikiContext };
Expand Down