{"version":3,"file":"UserStore-mj3fXW4D.cjs","names":[],"sources":["../src/extensions/ShowSelection/ShowSelection.ts","../src/user/UserStore.ts"],"sourcesContent":["import { Plugin, PluginKey } from \"prosemirror-state\";\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\nimport {\n  createExtension,\n  createStore,\n} from \"../../editor/BlockNoteExtension.js\";\n\nconst PLUGIN_KEY = new PluginKey(`blocknote-show-selection`);\n\n/**\n * Plugin that shows adds a decoration around the current selection\n * This can be used to highlight the current selection in the UI even when the\n * text editor is not focused.\n */\nexport const ShowSelectionExtension = createExtension(({ editor }) => {\n  const store = createStore(\n    { enabledSet: new Set<string>() },\n    {\n      onUpdate() {\n        editor.transact((tr) => tr.setMeta(PLUGIN_KEY, {}));\n      },\n    },\n  );\n  return {\n    key: \"showSelection\",\n    store,\n    prosemirrorPlugins: [\n      new Plugin({\n        key: PLUGIN_KEY,\n        props: {\n          decorations: (state) => {\n            const { doc, selection } = state;\n            if (store.state.enabledSet.size === 0) {\n              return DecorationSet.empty;\n            }\n            const dec = Decoration.inline(selection.from, selection.to, {\n              \"data-show-selection\": \"true\",\n            });\n            return DecorationSet.create(doc, [dec]);\n          },\n        },\n      }),\n    ],\n    /**\n     * Show or hide the selection decoration\n     *\n     * @param shouldShow - Whether to show the selection decoration\n     * @param key - The key of the selection to show or hide,\n     * this is necessary to prevent disabling ShowSelection from one place\n     * will interfere with other parts of the code that need to show the selection decoration\n     * (e.g.: CreateLinkButton and AIExtension)\n     */\n    showSelection(shouldShow: boolean, key: string) {\n      store.setState({\n        enabledSet: shouldShow\n          ? new Set([...store.state.enabledSet, key])\n          : new Set([...store.state.enabledSet].filter((k) => k !== key)),\n      });\n    },\n  } as const;\n});\n","import { createStore } from \"../editor/BlockNoteExtension.js\";\nimport { Store } from \"../util/Store.js\";\n\n/**\n * A collaborator of the document.\n */\nexport type User = {\n  /**\n   * The {@link User}'s unique identifier\n   */\n  id: string;\n  /**\n   * The {@link User}'s name/label\n   */\n  username: string;\n  /**\n   * The {@link User}'s profile image\n   */\n  avatarUrl: string;\n  /**\n   * The color used to represent the user (e.g. for collaboration cursors).\n   */\n  color?: string;\n  /**\n   * A lighter variant of {@link color}.\n   */\n  colorLight?: string;\n};\n\n/**\n * A store that retrieves and caches information about users, generic over the\n * resolved user type `U`.\n *\n * Created via {@link createUserStore}. Features that need to resolve user ids to\n * user information (comments, suggestions, versions) build one internally and\n * expose it on their extension instance so their UI can read from it.\n */\nexport type UserStore<U extends User = User> = {\n  /**\n   * A store mapping user ids to the resolved {@link User} information.\n   */\n  store: Store<Map<User[\"id\"], U>>;\n  /**\n   * Load information about users based on an array of user ids.\n   *\n   * Users that are already cached or currently being loaded are skipped, so\n   * it is safe to call this often (e.g. on every render).\n   */\n  loadUsers: (userIds: User[\"id\"][]) => Promise<void>;\n  /**\n   * Re-fetch information about users, ignoring the cache. Users that are\n   * currently being loaded are still skipped to avoid duplicate requests.\n   */\n  refetchUsers: (userIds: User[\"id\"][]) => Promise<void>;\n  /**\n   * Retrieve information about a user based on their id, if cached.\n   *\n   * The user has to be loaded via `loadUsers` first.\n   */\n  getUser: (userId: User[\"id\"]) => U | undefined;\n  /**\n   * Manually set information about a user. This is useful if you have a\n   * resolver that returns partial information (e.g. just the username) and you\n   * want to fill in the rest later (e.g. avatarUrl).\n   */\n  setUser: (user: U | U[]) => void;\n};\n\nexport type UserStoreResolver<U extends User = User> = (\n  /**\n   * The user ids to resolve. The resolver should return information for all of\n   * these users, or an empty array if none could be resolved.\n   */\n  userIds: User[\"id\"][],\n  /**\n   * The {@link UserStore} that is calling this resolver. This allows you to return a user synchronously, and update the store later if you need to fetch additional information asynchronously.\n   */\n  store: UserStore<any>,\n) => Promise<U[]>;\n\n/**\n * A resolver callback or an already-built {@link UserStore} — the shape that\n * user-facing options (comments, collaboration) accept so callers can either let\n * the extension build a store or pass a shared one.\n */\nexport type UserStoreOrResolver<U extends User = User> =\n  | ((userIds: User[\"id\"][], store: UserStore<any>) => Promise<U[]>)\n  | UserStore<any>;\n\n/**\n * Creates a {@link UserStore} that retrieves and caches information about users.\n *\n * It does this by calling `resolveUsers` for users that are not yet cached, and\n * stores the results in a BlockNote store so they can be subscribed to (e.g. via\n * `useStore` in React).\n *\n * `resolveUsers` is called with the ids of users that are not yet cached, and\n * should return the information for those users. The type of the returned users\n * is inferred and flows through to {@link UserStore.getUser} and the store, so\n * you can return a type with additional properties and have them be reported\n * back.\n *\n * See [Comments](https://www.blocknotejs.org/docs/features/collaboration/comments) for more info.\n */\nexport function createUserStore<U extends User = User>(\n  resolveUsers: (userIds: User[\"id\"][], store: UserStore<any>) => Promise<U[]>,\n): UserStore<U> {\n  if (!resolveUsers) {\n    throw new Error(\"resolveUsers is required to create a user store\");\n  }\n\n  const store = createStore(new Map<User[\"id\"], U>());\n\n  // Tracks users that are currently being fetched, to avoid duplicate\n  // in-flight requests. This is intentionally kept out of the store as it is\n  // not state that consumers need to subscribe to.\n  const loadingUsers = new Set<User[\"id\"]>();\n\n  const userStore: UserStore<U> = {\n    store,\n    async loadUsers(userIds) {\n      const missingUsers = userIds.filter(\n        (id) => !store.state.has(id) && !loadingUsers.has(id),\n      );\n      await fetchUsers(missingUsers);\n    },\n    async refetchUsers(userIds) {\n      const usersToFetch = userIds.filter((id) => !loadingUsers.has(id));\n      await fetchUsers(usersToFetch);\n    },\n    getUser(userId) {\n      return store.state.get(userId);\n    },\n    setUser(users) {\n      const usersArray = Array.isArray(users) ? users : [users];\n      store.setState((prevState) => {\n        const nextState = new Map(prevState);\n        for (const user of usersArray) {\n          nextState.set(user.id, user);\n        }\n        return nextState;\n      });\n    },\n  };\n\n  async function fetchUsers(userIds: User[\"id\"][]) {\n    if (userIds.length === 0) {\n      return;\n    }\n\n    for (const id of userIds) {\n      loadingUsers.add(id);\n    }\n\n    try {\n      const users = await resolveUsers(userIds, userStore);\n      // Only update the store if any users were actually resolved. Emitting\n      // an update when nothing changed (e.g. when the resolver can't find a\n      // user) would needlessly notify subscribers and, combined with a\n      // subscriber that re-triggers loading, could cause an infinite loop.\n      // See https://github.com/TypeCellOS/BlockNote/issues/1548\n      if (users.length > 0) {\n        store.setState((prevState) => {\n          const nextState = new Map(prevState);\n          for (const user of users) {\n            nextState.set(user.id, user);\n          }\n          return nextState;\n        });\n      }\n    } finally {\n      for (const id of userIds) {\n        // Remove the users from the loading set. On a next call to `loadUsers`\n        // we will either return the cached user, or retry loading the user if\n        // the request failed.\n        loadingUsers.delete(id);\n      }\n    }\n  }\n\n  return userStore;\n}\n\n/**\n * Normalize a {@link UserStoreOrResolver} to a {@link UserStore}:\n * - an existing store is returned as-is (so a single de-duped cache can be\n *   shared across extensions),\n * - a resolver function is wrapped with {@link createUserStore},\n * - `undefined` yields an empty store that resolves nothing (consumers then fall\n *   back to showing raw user ids).\n */\nexport function normalizeToUserStore<U extends User = User>(\n  resolveUsersOrStore?: UserStoreOrResolver<U>,\n): UserStore<U> {\n  if (typeof resolveUsersOrStore === \"function\") {\n    return createUserStore(resolveUsersOrStore);\n  }\n  return resolveUsersOrStore ?? createUserStore<U>(async () => []);\n}\n"],"mappings":"sHAOA,IAAM,EAAa,IAAI,EAAA,UAAU,0BAA0B,EAO9C,EAAyB,EAAA,GAAiB,CAAE,YAAa,CACpE,IAAM,EAAQ,EAAA,EACZ,CAAE,WAAY,IAAI,GAAc,EAChC,CACE,UAAW,CACT,EAAO,SAAU,GAAO,EAAG,QAAQ,EAAY,CAAC,CAAC,CAAC,CACpD,CACF,CACF,EACA,MAAO,CACL,IAAK,gBACL,QACA,mBAAoB,CAClB,IAAI,EAAA,OAAO,CACT,IAAK,EACL,MAAO,CACL,YAAc,GAAU,CACtB,GAAM,CAAE,MAAK,aAAc,EAC3B,GAAI,EAAM,MAAM,WAAW,OAAS,EAClC,OAAO,EAAA,cAAc,MAEvB,IAAM,EAAM,EAAA,WAAW,OAAO,EAAU,KAAM,EAAU,GAAI,CAC1D,sBAAuB,MACzB,CAAC,EACD,OAAO,EAAA,cAAc,OAAO,EAAK,CAAC,CAAG,CAAC,CACxC,CACF,CACF,CAAC,CACH,EAUA,cAAc,EAAqB,EAAa,CAC9C,EAAM,SAAS,CACb,WAAY,EACR,IAAI,IAAI,CAAC,GAAG,EAAM,MAAM,WAAY,CAAG,CAAC,EACxC,IAAI,IAAI,CAAC,GAAG,EAAM,MAAM,UAAU,CAAC,CAAC,OAAQ,GAAM,IAAM,CAAG,CAAC,CAClE,CAAC,CACH,CACF,CACF,CAAC,EC4CD,SAAgB,EACd,EACc,CACd,GAAI,CAAC,EACH,MAAU,MAAM,iDAAiD,EAGnE,IAAM,EAAQ,EAAA,EAAY,IAAI,GAAoB,EAK5C,EAAe,IAAI,IAEnB,EAA0B,CAC9B,QACA,MAAM,UAAU,EAAS,CAIvB,MAAM,EAHe,EAAQ,OAC1B,GAAO,CAAC,EAAM,MAAM,IAAI,CAAE,GAAK,CAAC,EAAa,IAAI,CAAE,CAErC,CAAY,CAC/B,EACA,MAAM,aAAa,EAAS,CAE1B,MAAM,EADe,EAAQ,OAAQ,GAAO,CAAC,EAAa,IAAI,CAAE,CAC/C,CAAY,CAC/B,EACA,QAAQ,EAAQ,CACd,OAAO,EAAM,MAAM,IAAI,CAAM,CAC/B,EACA,QAAQ,EAAO,CACb,IAAM,EAAa,MAAM,QAAQ,CAAK,EAAI,EAAQ,CAAC,CAAK,EACxD,EAAM,SAAU,GAAc,CAC5B,IAAM,EAAY,IAAI,IAAI,CAAS,EACnC,IAAK,IAAM,KAAQ,EACjB,EAAU,IAAI,EAAK,GAAI,CAAI,EAE7B,OAAO,CACT,CAAC,CACH,CACF,EAEA,eAAe,EAAW,EAAuB,CAC3C,KAAQ,SAAW,EAIvB,KAAK,IAAM,KAAM,EACf,EAAa,IAAI,CAAE,EAGrB,GAAI,CACF,IAAM,EAAQ,MAAM,EAAa,EAAS,CAAS,EAM/C,EAAM,OAAS,GACjB,EAAM,SAAU,GAAc,CAC5B,IAAM,EAAY,IAAI,IAAI,CAAS,EACnC,IAAK,IAAM,KAAQ,EACjB,EAAU,IAAI,EAAK,GAAI,CAAI,EAE7B,OAAO,CACT,CAAC,CAEL,QAAU,CACR,IAAK,IAAM,KAAM,EAIf,EAAa,OAAO,CAAE,CAE1B,CA1BqB,CA2BvB,CAEA,OAAO,CACT,CAUA,SAAgB,EACd,EACc,CAId,OAHI,OAAO,GAAwB,WAC1B,EAAgB,CAAmB,EAErC,GAAuB,EAAmB,SAAY,CAAC,CAAC,CACjE"}