bitphinix / slate-yjs Goto Github PK
View Code? Open in Web Editor NEWYjs binding for Slate
Home Page: https://docs.slate-yjs.dev
License: MIT License
Yjs binding for Slate
Home Page: https://docs.slate-yjs.dev
License: MIT License
When changes are being applied from a remote user, those updates will sometimes block the local user from making text selections using their mouse. During a multiplayer collaborative editing session with 4 people, we saw this happen fairly frequently. The user selection either wouldn't apply at all, or apply in a weird, unexpected location.
Here is a video showing an exaggerated version of the problem: I have a "remote" user on the right automatically inserting text at a quick interval. On the left, I am the "local" user trying to make selections but am frequently missing them. This happens in response to both clicks, click and drags to highlight, and arrow keys to move selection. One interesting thing is that text insertion on the left still seems to work ok. FYI the video has audio narration:
Any ideas on how to tackle this one?
Hi, thanks for the excellent library!
Usage of this library on client side is pretty simple, but the server side setup is pretty scary: https://github.com/BitPhinix/slate-yjs-example/blob/master/server/server.ts
It seems this is a modification of version of y-websocket code? Hope we can just import or extend y-websocket instead of modify it's source code
Hi @BitPhinix is it possible to still keep around the toSlateOps and applySlateOps functions in 2.0.0? I use this library but have a different plugin system and need to convert / apply slate ops myself. Does the new design still allow you to extract out these functions?
Strange behavior when editing text online and offline at the same time
I ran this demo https://github.com/BitPhinix/slate-yjs-example
y-protocols has added the conditional exports pattern in 1.0.5. We need this as we use slate-yjs on both node and on the frontend.
Just a little nitpick - it is Yjs not YJs.
I think - and I never asked Kevin _ that the Y is a fork showing the merge, the js then is just a subscript for the implemented language.
This issue can be closed, really just wanted to say that this is great work!
slate-yjs should have a yjs backed history plugin to allow filtering the undo stack based on change origin without potentially causing editor crashes
Hey,
Thank you for your contribution. We also chose slate + yjs + slate-yjs as the technical solution for collaborative editing, and our development work has been basically completed.
But our front-end framework is Angular, so we developed an Angular-based view layer slate-angular based on slate. Fortunately, the logic of slate-yjs is also adapted to slate-angular, but it relies on react, so we have to recreate the repo , Copy most of the code snippets...
As mentioned earlier, our real-time collaborative editing has been developed and we have tried to solve some problems (Undos management). Of course, there are some problems that have not been solved (such as Relative position). We will create related Issues and PRs. Hope it is helpful to slate-yjs.
If the dependencies of react can be removed, then we can directly rely on slate-yjs instead of maintaining two repositories.
Thanks and good wishes
I'm just starting implementing Yjs into my editor, and I'm using SlatePlugin modeling, so I just started looking at your sample and did a first draft of a SlatePlugin (gonna update it if needed).
I just have one feedback related to useCursor
could it be simplified by using only (sharedType, provider)
as parameters instead of the full editor? I mean, I checked out the code and nothing is related to the editor itself and in the case of SlatePlugin it is complicated to have the editor declared at this right moment (look at the code below).
import { useEffect, useMemo, useState } from 'react'
import { SlatePlugin } from '@udecode/slate-plugins'
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { SyncElement, toSharedType, useCursors, withCursor, withYjs } from 'slate-yjs'
interface SlateYjsPluginProps {
onlineMode?: {
webSocketEndpoint: string
documentID: string
userName: string
color: string // rgba
}
}
// eslint-disable-next-line id-length,@typescript-eslint/no-empty-function
const fakeAwareness = { on: () => {} }
const useSlateYjsPlugin = ({ onlineMode }: SlateYjsPluginProps): [SlatePlugin, boolean] => {
// Extract onlineMode props to avoid the requirement to memoize this object in the parent
const { webSocketEndpoint, documentID, userName, color } = onlineMode || {}
const [isOnline, setIsOnline] = useState(false)
const doc = useMemo(() => new Y.Doc(), [])
const sharedType = useMemo(() => doc.getArray<SyncElement>('doc'), [doc])
const provider = useMemo(() => webSocketEndpoint && documentID && new WebsocketProvider(
webSocketEndpoint,
documentID,
doc,
{ connect: false }
), [webSocketEndpoint, documentID, doc])
useEffect(() => {
if (provider && userName && color) {
provider.on('status', ({ status }: { status: string }) => {
setIsOnline(status === 'connected')
})
provider.awareness.setLocalState({
alphaColor: `${color.replace(/, ?[\d.]+\)$/, '')},0.2)`,
color,
name: userName
})
// Super hacky way to provide a initial value from the client, if
// you plan to use y-websocket in prod you probably should provide the
// initial state from the server.
provider.on('sync', (isSynced: boolean) => {
if (isSynced && sharedType.length === 0) {
toSharedType(sharedType, [{ type: 'p', children: [{ text: '' }] }])
}
})
provider.connect()
return () => provider.disconnect()
}
return undefined
}, [provider, color, userName, sharedType])
// Use a fake editor to just pass needed values
// With SlatePlugin the editor is dynamically passed in callbacks but a hooks have to be called in the same order
const { decorate } = useCursors({ sharedType, awareness: (provider && provider.awareness) || fakeAwareness } as any)
return [
useMemo<SlatePlugin>(() => ({
withOverrides: editor => {
let wrapped = withYjs(editor, sharedType)
if (provider) { wrapped = withCursor(wrapped, provider.awareness) }
return wrapped
},
decorate: () => decorate
}), [decorate, provider, sharedType]),
isOnline
]
}
export default useSlateYjsPlugin
Is there a way to use slate-yjs with Plate ? or we must have a "plate-yjs" binding?
I'm recently getting this error and the whole app crashes. Any idea what would be the root cause?
master
branch failed. 🚨I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.
You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.
Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.
Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master
branch. You can also manually restart the failed CI job that runs semantic-release.
If you are not sure how to resolve this, here is some links that can help you:
If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.
The npm token configured in the NPM_TOKEN
environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/
.
If you are using Two-Factor Authentication, make configure the auth-only
level is supported. semantic-release cannot publish with the default auth-and-writes
level.
Please make sure to set the NPM_TOKEN
environment variable in your CI with the exact value of the npm token.
Good luck with your project ✨
Your semantic-release bot 📦🚀
Getting a strange error when installing @slate-yjs/react. Looks like the workspace URL in its package.json does not work in NPM.
▶ npm -v
8.7.0
▶ node -v
v16.14.2
▶ npm i @slate-yjs/react@latest
npm ERR! code EUNSUPPORTEDPROTOCOL
npm ERR! Unsupported URL Type "workspace:": workspace:^
In previous week I started evaluating Y.js and its integration with slate.js, because of facing some OOM and performance issues with slate-collaborative.
So first thing I wanted to is to benchmark Y.js for my use case.
Libraries I’m using are:
I tried to create some large data by hand, so that the JSON created for slate.js object is around 800KB.
I was able to collaborate just fine, but when typing faster than normal CPU hits 100%, which also resulted in some lag in reflecting typed words in editor.
Profiling dump (Generated from Chrome)
Is it expected behaviour OR I’m doing something wrong?
Hi,
I have a slate-yjs editor which gets fed content from the server side. At the server side, I insert the content to document like this doc.getArray('content').insert(0, JSON.parse(content));
. On the front end, the yjs part is syncing the content, I can also see it in the sharedType array, but the slate editor value doesn't change. I cannot call toSharedType on the frontend as this would duplicate the content.
Please help.
Thanks and regards,
Arjun Kashyap
I'm wondering if there is a plan for implementing the visualisation of the delta via Slate decorator?
I look over the code and the doc, I probably found a way to convert Yjs position to Slate position as a beginning of something.
I try to find how to get the delta on my shareType, but I don't know yet what is the best way, either I could use sharedType.observeDeep
or try to build a snapshot system like discussed in this topic https://discuss.yjs.dev/t/live-tracking-track-changes/293/11
Let me know if something is already plan or if you have any idea of the best implementation with Slate ;)
On my page I am using two slate editors for different parts of the same document.
Currently, once a user clicks into the editor, their cursor position is shared with all other collaborators and renders correctly (thanks for the hint to use cursorStateField
to differentiate the cursor position data for each), but the cursor position persistes even once the user blurs the editor.
If a user clicks into each editor, other users see a cursor for the user in each of the editors, rather than the editor where their cursor currently is. Is it possible to clear the cursor position data on blur of the slate editor?
The cursor of user Adrain Frami was incorrectly converted, and it should be positioned at the last position of the second paragraph。
We analyzed the reason for this problem. Due to the operation of slate to yjs, the original yjs structure data was deleted (the data structure corresponding to the user's Adrain Frami focus), so an exception occurred when the absolutePositions was converted through relativePosition, and the original corresponding relationship be broken.
First of all, I don’t have a solution to this problem. I understand that it may be related to split_node operation.
I am not sure if the next branch or v4 is trying to solve this problem
Assuming you have the text "Hello World"
, where "Hello"
is bold and "World"
is bold and italic:
{
"object": "text",
"leaves": [
{
"object": "leaf",
"text": "Hello",
"marks": [italic]
}, {
"object": "leaf",
"text": "World",
"marks": [bold, italic]
}
]
}
You can represent this text object using a Y.Text and formatting attributes (this is basically equivalent to the idea of marks
):
const ytext = new Y.Text()
ytext.insert(0, 'Hello World', { italic: true }) // insert "Hello World" as italic text
ytext.format(6, 5, { bold: true }) // assign bold formatting attributes to the word "World"
Or using the delta notation:
ytext.applyDelta([
{ insert: 'Hello ', attributes: { italic, true } },
{ insert: 'World', attributes: { italic: true, bold: true } }
])
A formatting attribute can be any JSON-encodable key-value pair. You can granularly remove or update attributes without replacing the underlying text. These are meta-properties that you can assign to ranges of content in the Yjs document.
ytext.toDelta() // => [{ insert: 'Hello ', attributes: { italic, true } }, { insert: 'World', attributes: { italic: true, bold: true } }]
In order to solve the split-node scenario that Andrew described, you just need to map Y.Text with formatting attributes to a Slate text node. I recommend working with the Y.Text delta events that should map nicely to Slate's operations, and vice versa.
Originally posted by @dmonad in ianstormtaylor/slate#259 (comment)
Hi, I am trying to follow the documentation in order to migrate a legacy codebase up to the latest @slate-yjs. However, I can't seem to get even a simple demo working as I try to follow along with the docs.
See this Codesandbox, where the editor does not display the value loaded into the sharedType. Any feedback?
https://codesandbox.io/s/frosty-rubin-zi72yj?file=/src/App.js
Hi,
I'm trying to wrap my head around sync-ing CRDTs and the integration with non-CRDT data models like Slate. If I understand correctly, it seems that you translate between Slate ops and Yjs events (yjsEditor.ts).
But wouldn't this cause a problem since CRDT events/ops are not regular ordered deltas, but operations that can be applied "out of order" with complicated conflict resolution based on logical clocks and LWW registers etc? For example, a property update might come in that when applied should not result in any change because there's a newer value of that property already applied.
I.e., wouldn't the only authoritative snapshot state be the one you have to ask Yjs for, not one you can compute yourself based on applying Yjs events (as you'd have to implement all the CRDT conflict resolution machinery)?
Apologies if I am misunderstanding something.
Slate-Yjs should expose simple utils for translating slate locations into relative positions and back to allow e.g. the creation of inline comment plugins out of the box
Hi,
I just realized that I broke your editor binding in my latest Yjs release.
Y.Text
operates on Quill Deltas. event.delta
is a well-formatted Quill Delta. I realized that event.changes.delta
is an Array Delta (an adaptation of the Quill Delta that operates on Arrays instead of Strings).
I fixed this issue and made event.changes.delta
a proper Quill delta. So you no longer need to do delta.insert.join()
. Since delta.insert
is a string now, you will actually get an error message as a user in https://gitter.im/Yjs/community reported.
You can fix this in a backward-compatible way by listening to event.delta
which is (and has always been) a Quill delta. So new and old versions of Yjs will work.
Hi,
I am using the awareness option with a Y.js document that has multiple sub documents for multiple Slate instances.
Awareness is registered on all instances. As you can see, the cursor from one sub doc of the main doc object is also applied on other instances, which is wrong!
the document structure is like:
{
slate1: [],
slate2: [],
...
}
As title said, Please export slateYjsSymbol this is required for Y.UndoManager as trackedOrigins to record the user action
Is it possible to declare a peerDependency on React 16 instead of 17? Don't think you actually use anything 17 specific in this repo and we haven't upgraded to React 17 yet but want to be able to use this.
When a character is typed into the editor, there are checks in place to determine whether that insertion was done by the local client or a remote one. This is so the sharedType observeDeep listener does not apply a change to a local client that the same local client just made. However, in version 2 or greater, I am seeing this exact issue happening. After a little investigation, I've verified that if-statements in the sharedType.observeDeep
callback and e.onChange
callback within the yjsEditor.ts file are both evaluating to true. This leads me to believe there is a bug with the new WEAK_MAP
implementation of this solution.
I will investigate further to try and find a fix, but will post this here in case someone finds it faster than me.
Data duplication while server restarting.
Steps to reproduce:
async onLoadDocument(data) {
if (data.document.isEmpty('content')) {
const insertDelta = slateNodesToInsertDelta([
{ type: 'p', children: [{ text: 'testing' }] },
] as any);
const sharedRoot = data.document.get('content', Y.XmlText) as Y.XmlText;
sharedRoot.applyDelta(insertDelta);
}
return data.document;
},
I'm running into an issue where loading the initial value from y-websocket
is adding an operation into undos from slate-history
that I don't want. Having that initial value operation in editor.history
allows the user to click undo and wipe out the document, which is not desirable.
I'm creating my editor like slate-yjs-example
const yDoc = new Y.Doc()
const sharedType = yDoc.getArray<SyncElement>('content')
const editor = withYjs(withHistory(withReact(createEditor())), sharedType)
I added a hack to pop off the initial value operation from the history for now:
const onLoad = () => {
editor.history.undos.pop()
sharedType.unobserveDeep(onLoad)
}
sharedType.observeDeep(onLoad)
I'm not sure if a fix belongs in slate-yjs
or somewhere else, but does anyone have a better approach to using slate-yjs
in combinations with y-websocket
& slate-history
for the initial value?
Is it possible to use the firebase realtime database as the backend ?
Hello,
I'm trying to set up a simple project following the demo but this line of code is failing with Error: Unexpected content type in insert operation
syncDoc.insert(0, doc.map(toSyncElement));
Here's my default value
const defaultValue = [
{
type: 'paragraph',
children: [
{
text: '',
},
],
},
];
Let me know if you need anything else.
Using latest slate-js and latest yjs
Does this plugin support using custom origins when applying slate ops?
It looks like the underlying function supports it: https://github.com/BitPhinix/slate-yjs/blob/master/src/applyToYjs/index.ts#L39
However, in the plugin it looks like the origin is hardcoded to the slateYjsSymbol https://github.com/BitPhinix/slate-yjs/blob/master/src/plugin/yjsEditor.ts#L77-L81
Is there a way to override this on an editor by editor basis?
slate has fixed IME bug in 0.65.3. We need this as we use slate-yjs.
Hi, I need to use slate editor for korean.
The biggest difference between Korean and English input is the existence of Processing.
When we press the keyboard key 'a', slate operation happen 'insert_text, a'.
But in order to type '가', we have to press 'ㄱ' and 'ㅏ'. In this level, slate doesn't know the input end.
User can input 'ㄱ' for '각', 'ㄴ' for '간'.
So, slate doesn't issue operation 'insert_text, 가' until they know the input perfactly end.
Slate detect the input end by 'pressing space bar or enter' or 'move cursor to another place'
The issue happen when 'move cursor to another place'.
When the input letter doesn't end, move cursor to another place doesn't work.
insert_text and set_selection happen same time but set_selection is ignored.
The solution that I found is using delay at updating cursor.
e.onChange = () => {
console.log(e.operations);
// setTimeout(() => CursorEditor.updateCursor(e), 200); // solution
setTimeout(() => CursorEditor.updateCursor(e), 0); // current code
if (onChange) {
onChange();
}
};
But this solution is not cool because the cursor movement is not good.
How can I make this good?
Hello, I'm including in my project both slate-yjs and yjs as dependencies:
"dependencies": {
...
"slate-yjs": "^3.2.0",
"yjs": "13.5.27",
...
everything worked smoothly until I bumped the yjs version, then I started getting typescript compilation errors:
Error: node_modules/slate-yjs/dist/main/applyToSlate/index.d.ts:8:66 - error TS2314: Generic type 'YEvent<T>' requires 1 type argument(s).
8 export declare function translateYjsEvent(editor: Editor, event: Y.YEvent): Operation[];
~~~~~~~~
Error: node_modules/slate-yjs/dist/main/applyToSlate/index.d.ts:12:64 - error TS2314: Generic type 'YEvent<T>' requires 1 type argument(s).
12 export declare function applyYjsEvents(editor: Editor, events: Y.YEvent[]): void;
It looks like they changed the YEvent definition in version 13.5.28, thus breaking the compilation.
I guess the applyToSlate definitions need to be changed accordingly.
I integrated slate-yjs with @udecode/slate-plugins and the problem I've encountered is that when one user presses backspace or replaces some selected text the cursor of another user is moved to the position of the first user's cursor. What could be the problem?
As stated in the title, there are some weird behaviours happening when differentiating remote and local changes. If a remote change is made inside a block that has formatting, and a local user undoes edits around the remote change inside the block, the formatting is removed from the remote change. Similarly if you create a new block locally and there is a remote edit on the new block, the local user can undo the new block and with it the remote changes.
This is an edge case for sure but I thought it is worth documenting.
Originally posted by bjarkih August 18, 2021
Hi all, and thanks for great work on Slate Yjs!
I have a question around the performance of the cursor decorator provided by the sample useCursors
implementation. When editing a document that another user is editing at the same time, we're updating the decorate
function as the remote cursor positions change, which leads to frequent re-renderings of the Slate editor. For me, this leads to a visibly sluggish editor experience as soon as multiple users are editing the same part of the document tree.
Has anyone else encountered the same problem and, hopefully, found some solutions or workarounds? Any input appreciated!
Bjarki
I described the problem here: BitPhinix/slate-yjs-example#88 but it is actually an slate-yjs
issue.
I'm trying to substitute websocket
with y-dat
but I'm having some troubles... :
This is what I've done so far, but getting errors:
In https://github.com/BitPhinix/slate-yjs-example/blob/master/src/Client.tsx :
import styled from "@emotion/styled";
import React, { useEffect, useMemo, useState } from "react";
import { createEditor, Node } from "slate";
import { withHistory } from "slate-history";
import { withReact } from "slate-react";
import {
SyncElement,
toSharedType,
useCursors,
withCursor,
withYjs,
} from "slate-yjs";
import { WebsocketProvider } from "y-websocket";
import * as Y from "yjs";
import { DatProvider } from 'y-dat'
//import * as awarenessProtocol from 'y-protocols/awareness.js'
import { Button, H4, Instance, Title } from "./Components";
import EditorFrame from "./EditorFrame";
import { withLinks } from "./plugins/link";
import randomColor from "randomcolor";
const WEBSOCKET_ENDPOINT =
process.env.NODE_ENV === "production"
? "wss://demos.yjs.dev/slate-demo"
: "ws://localhost:1234";
interface ClientProps {
name: string;
id: string;
slug: string;
removeUser: (id: any) => void;
}
const Client: React.FC<ClientProps> = ({ id, name, slug, removeUser }) => {
const [value, setValue] = useState<Node[]>([]);
const [isOnline, setOnlineState] = useState<boolean>(false);
const color = useMemo(
() =>
randomColor({
luminosity: "dark",
format: "rgba",
alpha: 1,
}),
[]
);
const doc = new Y.Doc();
const givenDatKey = Buffer.alloc(32)
//let awareness = new awarenessProtocol.Awareness(doc)
const [sharedType, provider] = useMemo(() => {
const sharedType = doc.getArray<SyncElement>("content");
//const provider = new WebsocketProvider(WEBSOCKET_ENDPOINT, slug, doc, {
//connect: false,
//});
const provider = new DatProvider(givenDatKey, doc, {
//awareness: awareness,
})
return [sharedType, provider];
}, [id]);
const editor = useMemo(() => {
const editor = withCursor(
withYjs(withLinks(withReact(withHistory(createEditor()))), sharedType),
//provider.awareness
//awareness
);
return editor;
}, [sharedType, provider]);
useEffect(() => {
provider.on("status", ({ status }: { status: string }) => {
setOnlineState(status === "connected");
});
//provider.awareness.setLocalState({
//alphaColor: color.slice(0, -2) + "0.2)",
//color,
//name,
//});
// Super hacky way to provide a initial value from the client, if
// you plan to use y-websocket in prod you probably should provide the
// initial state from the server.
provider.on("sync", (isSynced: boolean) => {
if (isSynced && sharedType.length === 0) {
toSharedType(sharedType, [
{ type: "paragraph", children: [{ text: "Hello world!" }] },
]);
}
});
//provider.connect();
return () => {
//provider.disconnect();
};
}, [provider]);
const { decorate } = useCursors(editor);
const toggleOnline = () => {
//isOnline ? provider.disconnect() : provider.connect();
};
return (
<Instance online={isOnline}>
<Title>
<Head>Editor: {name}</Head>
<div style={{ display: "flex", marginTop: 10, marginBottom: 10 }}>
<Button type="button" onClick={toggleOnline}>
Go {isOnline ? "offline" : "online"}
</Button>
<Button type="button" onClick={() => removeUser(id)}>
Remove
</Button>
</div>
</Title>
<EditorFrame
editor={editor}
value={value}
decorate={decorate}
onChange={(value: Node[]) => setValue(value)}
/>
</Instance>
);
};
export default Client;
const Head = styled(H4)`
margin-right: auto;
`;
As you can see, I've encountered few problems in trying to use y-dat
instead of websocket
:
ydat doesn't have awareness
:
so... how to correctly include awareness
if y-dat
seems to not have it?
I tried to use import * as awarenessProtocol from 'y-protocols/awareness.js'
but without succeeding...
DatProvider
seems to not have connect
and disconnect
properties:
so... how to include these properties?
First, thanks for making this project.
I have been using slate-yjs and udecode/slate-plugins with custom code.
I am experiencing weird behaviours from time to time.
I have a hard time reproducing these bugs locally and deterministically (as someone would expect with a distributed system).
How would you test such an editor ?
I see there is an onChange event in the server-side code. But I'm not sure how to parse the Ydoc to slate JSON format.
const server = Server.configure({
port: 1234,
extensions: [new Logger()],
onChange: async (data) => {
},
async onLoadDocument(data) {
if (data.document.isEmpty('content')) {
const insertDelta = slateNodesToInsertDelta(initialValue);
const sharedRoot = data.document.get('content', Y.XmlText) as Y.XmlText;
sharedRoot.applyDelta(insertDelta);
}
return data.document;
},
});
Any thoughts?
I see the initial value inside Client is set to []
. How is the editor initialized with a non-empty array initial value? I'm seeing Uncaught Error: Cannot get the start point in the node at path [] because it has no start text node.
with a basic setup.
Hi, I wrote a simple unit test to validate the conversion from Slate nodes to Yjs shared objects and reverse and, to my surprise, it fails.
import { slateNodesToInsertDelta, yTextToSlateElement } from '@slate-yjs/core';
import { expect } from 'chai';
import { Node } from 'slate';
import * as Y from 'yjs';
describe('slate', () => {
it('should correctly convert Slate nodes', () => {
const nodes = [
{
children: [
{
text: 'Test:',
},
],
type: 'paragraph',
id: 'ed99db3b-9dc4-47ad-b8b7-4bfb44e3cd1e',
},
{
children: [
{
text: '',
},
{
children: [
{
text: '',
hyperlink: {
url: 'https://www.example.com',
},
underlined: true,
fontColor: '#0000ff',
},
],
type: 'inline-image',
transform: {
angle: 0,
width: '134px',
height: '50px',
},
blobID: '2694b3a2-afae-4fcc-8a60-44334bc3a04b',
blobURL: 'https://www.example.com/image.png',
blobFormats: ['original', 'dataUrl'],
},
{
text: '',
},
],
type: 'paragraph',
id: 'ca804088-30ff-4f51-b8ea-4f99c846fa0e',
},
{
children: [
{
text: '',
},
],
type: 'paragraph',
id: '53bf071f-4929-4c8c-94a4-cebd33c48ddc',
},
{
children: [
{
text: '',
},
],
type: 'paragraph',
id: '9326fbf8-f41c-44c3-9954-c06b0ccfbc30',
},
];
const ydoc = new Y.Doc();
const delta = slateNodesToInsertDelta(nodes);
const xmlText = ydoc.get('test', Y.XmlText) as Y.XmlText;
xmlText.applyDelta(delta, { sanitize: false });
const node = yTextToSlateElement(xmlText);
expect(node.children).to.deep.equal(nodes);
});
});
Test output:
slate
1) should correctly convert Slate nodes
0 passing (31ms)
1 failing
1) slate
should correctly convert Slate nodes:
AssertionError: expected [ { type: 'paragraph', …(2) }, …(3) ] to deeply equal [ { …(3) }, …(3) ]
+ expected - actual
}
{
"children": [
{
+ "text": ""
+ }
+ {
"blobFormats": [
"original"
"dataUrl"
]
"blobID": "2694b3a2-afae-4fcc-8a60-44334bc3a04b"
"blobURL": "https://www.example.com/image.png"
"children": [
{
+ "fontColor": "#0000ff"
+ "hyperlink": {
+ "url": "https://www.example.com"
+ }
"text": ""
+ "underlined": true
}
]
"transform": {
"angle": 0
--
"width": "134px"
}
"type": "inline-image"
}
+ {
+ "text": ""
+ }
]
"id": "ca804088-30ff-4f51-b8ea-4f99c846fa0e"
"type": "paragraph"
}
I took as an example a document created in our webapp with an inline image (Slate automatically surrounds inline elements with empty texts) with an hyperlink attached (an empty text with attributes is perfectly valid in Slate).
Digging into the code, I realized the issue root cause is in the Yjs business logic skipping any insertion of empty strings.
This misalignment breaks synchronization between clients, and also the storing of document data on the server side.
The bridge between Slate and Yjs should be able to fully reconstruct the original node structure, as far as it complies with the Slate model.
Hi,
This section was already working fine, but recently, when I press enter to split text or create a new paragraph, I get this error:
Uncaught (in promise) Error: Unexpected content type in insert operation
After the error, Slate freezes and you can no longer type or edit anything.
slate-yjs version: 3.1.1
slate version: 0.63.0
yjs: 13.5.22
Any idea what might be causing this?
There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.
Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.
Hi, I'm trying to use slate-yjs to see how things can work, and I'm using y-websocket to test things out. I'm having trouble with the initial setup of the editor+slate-yjs. The problem seems to be that withYjs
requires a sharedType
and while shareType
is ok with a []
initial value, the slate editor is not (i.e. an empty document is actually a single block with an empty text node).
So these are the steps I take
emptyDocument
sharedType
with Y.Doc.getArray
new WebsocketProvider
which requires the doc
and also sets the "slug" for the docwithYjs
which requires the sharedType
Then I have a useEffect
that is dependent on provider
that sets the events on the provider
(sync and status) and:
toSharedType
with the current value of the document.The problem is at the very start. User A stars a document with slug X
and User B starts a document with slug X
. If this document does not exist in storage it will be given the empty slate doc, which is:
{
type: 'paragraph',
children: [{text: ''}],
}
And this will be what is called with toSharedType
And when User B comes in, they also get the empty document. Now when they both go online, the sharedType
is updated with two empty documents and becomes this:
[{
type: 'paragraph',
children: [{text: ''}],
},
{
type: 'paragraph',
children: [{text: ''}],
}]
If I do not call toSharedType
on an initial empty document, then the slate editor does not work and i get
react-editor.ts:458 Uncaught Error: Cannot resolve a Slate point from DOM point: [object HTMLDivElement],0
at Object.toSlatePoint (react-editor.ts:458)
at Object.toSlateRange (react-editor.ts:516)
at HTMLDocument.<anonymous> (editable.tsx:422)
at invokeFunc (debounce.js:95)
at leadingEdge (debounce.js:105)
at HTMLDocument.debounced (debounce.js:172)
Any help here would be awesome!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.