File size: 2,386 Bytes
76fc93a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import * as Y from "yjs";

/**
 * Collaborative store for HTML embed content, backed by Y.Map("embeds").
 *
 * Each key is a filename (e.g. "d3-scaling-chart.html") and the value is
 * the raw HTML fragment (not a full document - buildDoc wraps it for display).
 *
 * The ProseMirror htmlEmbed node only stores the `src` attribute as a
 * reference key. The actual HTML lives here so multiple collaborators
 * can edit chart content concurrently.
 */
export function createEmbedStore(ydoc: Y.Doc) {
  const ymap = ydoc.getMap<string>("embeds");

  function get(src: string): string {
    return ymap.get(src) ?? "";
  }

  function set(src: string, html: string) {
    ymap.set(src, html);
  }

  function has(src: string): boolean {
    return ymap.has(src);
  }

  function remove(src: string) {
    ymap.delete(src);
  }

  function rename(oldSrc: string, newSrc: string) {
    const html = ymap.get(oldSrc);
    if (html !== undefined) {
      ydoc.transact(() => {
        ymap.set(newSrc, html);
        ymap.delete(oldSrc);
      });
    }
  }

  /** Apply a search/replace patch to an existing embed. */
  function patch(src: string, search: string, replace: string): boolean {
    const html = ymap.get(src);
    if (!html || !html.includes(search)) return false;
    ymap.set(src, html.replace(search, replace));
    return true;
  }

  function keys(): string[] {
    return Array.from(ymap.keys());
  }

  function getAll(): Record<string, string> {
    const result: Record<string, string> = {};
    ymap.forEach((val, key) => {
      result[key] = val;
    });
    return result;
  }

  /**
   * Observe changes to any embed.
   * Returns an unsubscribe function.
   */
  function observe(callback: () => void) {
    ymap.observe(callback);
    return () => ymap.unobserve(callback);
  }

  /**
   * Observe changes to a specific embed key.
   * Returns an unsubscribe function.
   */
  function observeKey(src: string, callback: (html: string) => void) {
    const handler = (event: Y.YMapEvent<string>) => {
      if (event.keysChanged.has(src)) {
        callback(ymap.get(src) ?? "");
      }
    };
    ymap.observe(handler);
    return () => ymap.unobserve(handler);
  }

  return {
    get,
    set,
    has,
    remove,
    rename,
    patch,
    keys,
    getAll,
    observe,
    observeKey,
  };
}

export type EmbedStore = ReturnType<typeof createEmbedStore>;