Fork me on GitHub
#hyperfiddle
<
2024-02-15
>
wei08:02:19

(edit) TLDR: the issue was that save changes (state/get-doc) which creates a new atom. wrapping with e/snapshot fixed the issue. updated the gist not sure if this is an electric question or a prosemirror question. here's my setup for an rich text editor that saves on change:

(e/defn Saver [!doc]
  (e/client
   (let [doc (e/watch !doc)]
     (e/discard
      (e/server
       (e/discard
        (state/save-doc! (e/client (serialize-node doc)))))))))

(e/defn Editor [& [width]]
  (e/client
   (let [schema (create-schema)
         !doc (atom (deserialize-node schema (e/server (state/get-doc))))]
     (Saver. !doc)
     (div (div (props {:id "proposal-editor" :class "prose"}))
          (div (props {:id "content"}))
          (println "Rendering editor view")
          (EditorView.
           (util/get-element-by-id "proposal-editor")
           #?(:cljs #js {:state (EditorState.create. #js {:doc @!doc :plugins plugins})}))
          nil))))
I have a plugin that updates the !doc atom which triggers Saver to persist the doc to the server. it seems to work except that every change produces a new instance of the editor (see video). I can make the issue go away by commenting out this line:
(Saver. !doc)
what is the Saver electric function doing that might be messing with prosemirror? "Rendering editor view" only gets printed once, so I don't think re-rendering is causing the issue. full code in case it's helpful: https://gist.github.com/yayitswei/eb1796428911db7865ff0c310ce80614

henrik08:02:56

My guess would be that (state/get-doc) updates reactively? Try wrapping it with e/snapshot.

wei08:02:19

wow that did it.. amazing, thank you!

👍 1
wei08:02:33

why does the println not trigger then?

henrik08:02:37

It doesn’t trigger at all? That I wouldn’t know. It looks like it should trigger exactly once.

henrik08:02:49

If you mean (println "Rendering editor view")

wei08:02:51

oh yeah it triggers once. i thought if the atom updated, it would trigger again

henrik08:02:11

No, it has no dynamic dependencies, so it won’t trigger again.

wei08:02:28

i see, so reactivity is fine-grained within an e/fn

henrik08:02:56

Yes, it’s very precise. Hence the need for the occasional snapshot to escape from it in situations like these.

👍 1
wei08:02:58

does @!doc create a dependency, or does that just get evaluated once?

henrik08:02:31

Unless the atom instance changes, it won’t. This was the case for you: get-doc updates, which means a new atom is created, which triggers the deref, since the this is now a new atom. Now your atom is stable, so the deref won’t re-run.

wei08:02:10

aha, makes sense

wei08:02:37

thanks again for your help. so this is single-player which is sufficient for now. any high-level tips or resources for making it multiplayer? prosemirror seems to have an opinionated way to do it, but i haven't tried using it with electric yet

henrik08:02:29

I don’t know how you can make it work for multiplayer without using a CRDT. You can make it “safe” for multiplayer by locking the text field if someone is editing it.

👍 1
henrik08:02:55

I.e., guarantee that only one person ever edits the text field at the time, semaphore-style.

wei08:02:40

makes sense. locking would work for now i think. i'd need a timeout to release the lock, right?

wei08:02:25

or, i could lock it if someone just has that page open, and release on page close

henrik08:02:03

Or when the editor has focus. But yeah, you’d probably need a timeout anyway, in case someone clocks out for the day and leaves the cursor in the field.

henrik08:02:32

Or a way to “steal” the semaphore

wei08:02:27

yup that'll work

wei08:02:43

i'm hoping someone publishes a electric+CRDT example I can study someday 😀 in the meantime this will def work for my purposes 🙏

Kurt Harriger20:02:35

Funny you say that. Several years ago I was working at Atlassian implementing collaborative in cljc for editing in for prosemirror specifically to support migration to confluences new editor that replaced xwiki. Unfortunately its closed source and somewhat non-trivial especially table.s However prosemirror was pretty easy to work with as the events are fairly semantic and I remember seeing a couple open source libraries collaborative editing libraries although in javascript. I’ve not looked into any of them too deeply but this one looks promising https://github.com/inkandswitch/peritext https://www.inkandswitch.com/peritext/

👍 1
Kurt Harriger20:02:01

or maybe https://github.com/yjs/yjs-demos looks more active and recent

👍 1
wei21:02:16

Thanks for the resources and validation of prosemirror. I actually switched over from lexical and the API feels much simpler.

henrik07:02:37

You probably saw that I recently wrote a collaborative text editor in Electric + Peritext recently. I’m pretty sure it’s two firsts: 1) Collaborative RT in pure Electric, 2) Peritext with blocks, but so it’s also at an early stage at the moment. For getting something out quickly, Yjs and Prosemirror ought to be one of the safest choices, despite the edge cases and problems it has.

🙌 1
wei13:02:19

yes, i remember commenting on it. very impressive demo!