Giter VIP home page Giter VIP logo

slate-yjs's Introduction


Slate-yjs aims to be the goto collaboration solution for slate. Get started in seconds, scale to infinity, customize to your hearts content.



Sponsors · Why Yjs? · Live demo · Documentation · Packages · Products · Questions · Contributing!



Sponsors 💖

I'm currently looking for sponsors to found further development of slate-yjs. These awesome sponsors already fund the development:


Sana Labs


Hosting provided by:



Why Yjs?

Yjs offers a feature-rich rich text CRDT with best-in-class performance. It's used in production by multiple fortune 500 companies and is the core of many collaborative editing applications. Moreover, it offers a very mature ecosystem with server-side solutions like hocuspocus, enabling you to build robust and highly scalable collaborative/offline-first applications.

For more detailed benchmarks about performance, you can take a look here.

Why a CDRT over OT? While many current collaborative text editing applications rely on OT (e.g., google docs with ShareJS), it only provides a subset of the functionally CRDTs offer due to the dependence on a central server. In other words: CRDTS can do everything OT can, but OT simply can't.

You can read more about this here.



Live demo

https://slate-yjs.dev



Documentation

https://docs.slate-yjs.dev



Packages

Slate-yjs's codebase is monorepo managed with yarn workspaces. It consists of a handful of packages—although you won't always use all of them:

Package Version Size Description Changelog
@slate-yjs/core Core slate-yjs binding. CHANGELOG.md
@slate-yjs/react React specific components/utils for slate-yjs. CHANGELOG.md


Products

These products use slate-yjs, and can give you an idea of what's possible:



Questions?

For questions around yjs, head over to the Yjs Community. Trying to build a backend with hocuspocus and have questions? Take a look at the #hocuspocus channel in the TipTap Discord. Having issues with slate? There's a there's a Slack for that as well.

Any questions about slate-yjs? Thead over to the #slate-yjs channel inside the Slate Slack or post something in the Discussions



Contributing!

All contributions are super welcome! Check out the contributing instructions for more info!

Slate-yjs is MIT-licensed.

slate-yjs's People

Contributors

antonniklasson avatar bitphinix avatar brentfarese avatar dependabot[bot] avatar doodlewind avatar github-actions[bot] avatar huanhuanwa avatar ilya2204 avatar inokawa avatar juliankrispel avatar kjohnson-hugo avatar nemanja-tosic avatar pierrelouisdelx avatar renovate-bot avatar renovate[bot] avatar semantic-release-bot avatar vuhuucuong avatar zarv1k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

slate-yjs's Issues

Initial value?

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.

`delta.insert.join` is broken

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.

How to clear cursor state on editor blur

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?

Data duplication on server restart

Data duplication while server restarting.

Steps to reproduce:

  1. Type something in the editor.
  2. Restart the server now.
  3. In the callback onLoadDocument the document comes as empty and again the data get populated to the editor.
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;
},

Issues in substituing `websocket` with `y-dat`

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?

Awareness is copied on independent instances inside one document

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!

image

the document structure is like:

{
       slate1: [],
       slate2: [],
       ...
}

CPU hits 100% when typing fast on large document

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:

  • slate-yjs
  • y-websocket

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.

Screenshot from 2022-01-22 22-21-40-1

Profiling dump (Generated from Chrome)
Is it expected behaviour OR I’m doing something wrong?

withYHistory undo will remove blocks with remote changes in them

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.

SlatePlugin implementation

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

Yjs based history plugin

slate-yjs should have a yjs backed history plugin to allow filtering the undo stack based on change origin without potentially causing editor crashes

Redlining/Versioning/Delta as Slate decorator

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 ;)

I can't seem to start a collaborative session in offline mode and go online after the fact.

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

  1. Get the current value of my document (from storage) - if it's a new document and doesn't exist, set the value to the emptyDocument
  2. Initialize a sharedType with Y.Doc.getArray
  3. Initialize a provider with new WebsocketProvider which requires the doc and also sets the "slug" for the doc
  4. Create the editor with withYjs 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:

  1. Calls 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!

Use React 16 instead of 17

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.

Restore toSlateOps and applySlateOps in 2.0.0

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?

Cursor decorator performance

Discussed in #293

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

[Feature Request]: Sync empty text nodes

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.

npm install error

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:^

Update y-protocols to 1.0.5

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.

Local selections blocked while remote user is making changes

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:

selections.blocked.mov

Any ideas on how to tackle this one?

Slate editor initial sync error

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

Operations are being applied twice

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.

image

Error: Unexpected content type in insert operation

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

Prevent y-websocket initial value from entering slate-history undos

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?

How to save document to database

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?

Can't compile against Yjs 13.5.28+

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.

「Bug」relativePosition and aboslutePosition transform problem

Bug description

The cursor of user Adrain Frami was incorrectly converted, and it should be positioned at the last position of the second paragraph。

Cause

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.

Resolve

First of all, I don’t have a solution to this problem. I understand that it may be related to split_node operation.

export default function splitNode(

I am not sure if the next branch or v4 is trying to solve this problem

When insert_text and set_selection operation happen at the same time, set_selection doesn't work.

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'.

bandicam.2022-02-16.16-24-58-481.mp4

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();
    }
  };
bandicam.2022-02-16.16-48-18-237.mp4

But this solution is not cool because the cursor movement is not good.

bandicam.2022-02-16.16-36-01-958.mp4

How can I make this good?

Linearising document content

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)

Great work!

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!

The automated release is failing 🚨

🚨 The automated release from the 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.


Invalid npm token.

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 📦🚀

Consider removing the React dependency?

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

Problem when pressing enter

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

image

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?

How would you test ?

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 ?

Assumes causal ordering of Yjs events?

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.

Action Required: Fix Renovate Configuration

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.