@natoe/colab 0.1.24 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1942,6 +1942,20 @@ function MoreIcon({ size = 18, color = "currentColor" }) {
1942
1942
  }
1943
1943
  );
1944
1944
  }
1945
+ function AlertIcon({ size = 14, color = "currentColor" }) {
1946
+ return /* @__PURE__ */ jsx(
1947
+ "svg",
1948
+ {
1949
+ xmlns: "http://www.w3.org/2000/svg",
1950
+ width: size,
1951
+ height: size,
1952
+ viewBox: "0 0 24 24",
1953
+ fill: color,
1954
+ "aria-hidden": "true",
1955
+ children: /* @__PURE__ */ jsx("path", { d: "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" })
1956
+ }
1957
+ );
1958
+ }
1945
1959
  var VIEWPORT_MARGIN = 8;
1946
1960
  var TRIGGER_GAP = 4;
1947
1961
  function MessageActionsMenu({
@@ -1950,6 +1964,9 @@ function MessageActionsMenu({
1950
1964
  onPin,
1951
1965
  onUnpin,
1952
1966
  onCopy,
1967
+ onReport,
1968
+ onBlock,
1969
+ canModerate = false,
1953
1970
  pinDisabled = false,
1954
1971
  anchorRight = false,
1955
1972
  className
@@ -2088,6 +2105,36 @@ function MessageActionsMenu({
2088
2105
  /* @__PURE__ */ jsx("span", { children: "Copy text" })
2089
2106
  ]
2090
2107
  }
2108
+ ),
2109
+ canModerate && (onReport || onBlock) && /* @__PURE__ */ jsx("div", { style: styles4.divider }),
2110
+ canModerate && onReport && /* @__PURE__ */ jsxs(
2111
+ "button",
2112
+ {
2113
+ type: "button",
2114
+ role: "menuitem",
2115
+ onClick: () => run(() => onReport(message)),
2116
+ style: styles4.item,
2117
+ children: [
2118
+ /* @__PURE__ */ jsx(AlertIcon, { size: 16, color: COLOR.neutral700 }),
2119
+ /* @__PURE__ */ jsx("span", { children: "Report message" })
2120
+ ]
2121
+ }
2122
+ ),
2123
+ canModerate && onBlock && /* @__PURE__ */ jsxs(
2124
+ "button",
2125
+ {
2126
+ type: "button",
2127
+ role: "menuitem",
2128
+ onClick: () => run(() => onBlock(message)),
2129
+ style: { ...styles4.item, ...styles4.itemDanger },
2130
+ children: [
2131
+ /* @__PURE__ */ jsx(AlertIcon, { size: 16, color: "#c0392b" }),
2132
+ /* @__PURE__ */ jsxs("span", { children: [
2133
+ "Block ",
2134
+ message.senderName
2135
+ ] })
2136
+ ]
2137
+ }
2091
2138
  )
2092
2139
  ]
2093
2140
  }
@@ -2166,6 +2213,14 @@ var styles4 = {
2166
2213
  itemDisabled: {
2167
2214
  color: COLOR.neutral400,
2168
2215
  cursor: "not-allowed"
2216
+ },
2217
+ itemDanger: {
2218
+ color: "#c0392b"
2219
+ },
2220
+ divider: {
2221
+ height: "1px",
2222
+ backgroundColor: COLOR.neutral200,
2223
+ margin: "4px 0"
2169
2224
  }
2170
2225
  };
2171
2226
  function FileIcon({ size = 18, color = "currentColor" }) {
@@ -2182,20 +2237,6 @@ function FileIcon({ size = 18, color = "currentColor" }) {
2182
2237
  }
2183
2238
  );
2184
2239
  }
2185
- function AlertIcon({ size = 14, color = "currentColor" }) {
2186
- return /* @__PURE__ */ jsx(
2187
- "svg",
2188
- {
2189
- xmlns: "http://www.w3.org/2000/svg",
2190
- width: size,
2191
- height: size,
2192
- viewBox: "0 0 24 24",
2193
- fill: color,
2194
- "aria-hidden": "true",
2195
- children: /* @__PURE__ */ jsx("path", { d: "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" })
2196
- }
2197
- );
2198
- }
2199
2240
  function RoleAvatarIcon({
2200
2241
  role,
2201
2242
  size = 18,
@@ -2297,6 +2338,8 @@ function MessageBubble({
2297
2338
  onPin,
2298
2339
  onUnpin,
2299
2340
  onCopy,
2341
+ onReport,
2342
+ onBlock,
2300
2343
  onReplyJumpTo,
2301
2344
  onRetry,
2302
2345
  pinDisabled = false,
@@ -2305,7 +2348,8 @@ function MessageBubble({
2305
2348
  if (message.type === "system") {
2306
2349
  return /* @__PURE__ */ jsx(SystemBubble, { message });
2307
2350
  }
2308
- const hasActions = !!(onReply || onPin || onUnpin || onCopy);
2351
+ const canModerate = !isOwn && !!(onReport || onBlock);
2352
+ const hasActions = !!(onReply || onPin || onUnpin || onCopy) || canModerate;
2309
2353
  const actionsSlot = hasActions ? /* @__PURE__ */ jsx("div", { style: styles5.actionsSlot, children: /* @__PURE__ */ jsx(
2310
2354
  MessageActionsMenu,
2311
2355
  {
@@ -2317,6 +2361,9 @@ function MessageBubble({
2317
2361
  onUnpin: onUnpin ?? (() => {
2318
2362
  }),
2319
2363
  onCopy,
2364
+ onReport,
2365
+ onBlock,
2366
+ canModerate,
2320
2367
  pinDisabled,
2321
2368
  anchorRight: !isOwn
2322
2369
  }
@@ -2853,6 +2900,9 @@ var MessageList = forwardRef(function MessageList2({
2853
2900
  onPin,
2854
2901
  onUnpin,
2855
2902
  onCopy,
2903
+ onReport,
2904
+ onBlock,
2905
+ blockedUserIds,
2856
2906
  onRetry,
2857
2907
  pinDisabled,
2858
2908
  className
@@ -2956,7 +3006,7 @@ var MessageList = forwardRef(function MessageList2({
2956
3006
  messages.length === 0 && isLoading && /* @__PURE__ */ jsx("div", { style: styles6.firstLoad, children: /* @__PURE__ */ jsx(Spinner, { size: 20 }) }),
2957
3007
  (() => {
2958
3008
  let lastValidBucket = null;
2959
- return messages.map((message) => {
3009
+ return messages.filter((m) => !blockedUserIds?.includes(m.senderId)).map((message) => {
2960
3010
  const currentBucket = dayBucketKey(message.insertedAt);
2961
3011
  const showDivider = !!currentBucket && currentBucket !== lastValidBucket;
2962
3012
  if (currentBucket) lastValidBucket = currentBucket;
@@ -2979,6 +3029,8 @@ var MessageList = forwardRef(function MessageList2({
2979
3029
  onPin,
2980
3030
  onUnpin,
2981
3031
  onCopy,
3032
+ onReport,
3033
+ onBlock,
2982
3034
  onRetry,
2983
3035
  pinDisabled
2984
3036
  }
@@ -4598,6 +4650,9 @@ function CollabPanel({
4598
4650
  onPin: handlePin,
4599
4651
  onUnpin: handleUnpin,
4600
4652
  onCopy: handleCopy,
4653
+ onReport: config.onReportMessage ? (m) => config.onReportMessage(m) : void 0,
4654
+ onBlock: config.onBlockUser ? (m) => config.onBlockUser(m.senderId, m.senderName) : void 0,
4655
+ blockedUserIds: config.blockedUserIds,
4601
4656
  onRetry: retryMessage,
4602
4657
  pinDisabled
4603
4658
  }
@@ -5016,6 +5071,8 @@ function useInlineCollab({
5016
5071
  const elementRef = useRef(null);
5017
5072
  const observerRef = useRef(null);
5018
5073
  const subscribedConversationIdRef = useRef(null);
5074
+ const latestMessageIdRef = useRef(null);
5075
+ const lastMarkedReadIdRef = useRef(null);
5019
5076
  const channelSubscriptionRef = useRef(null);
5020
5077
  const trimToLimit = useCallback(
5021
5078
  (msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
@@ -5025,6 +5082,18 @@ function useInlineCollab({
5025
5082
  () => buildChannelName(patientData),
5026
5083
  [patientData]
5027
5084
  );
5085
+ useEffect(() => {
5086
+ latestMessageIdRef.current = messages[messages.length - 1]?.id ?? null;
5087
+ }, [messages]);
5088
+ const markLatestRead = useCallback(
5089
+ (conversationId) => {
5090
+ const messageId = latestMessageIdRef.current;
5091
+ if (!messageId || lastMarkedReadIdRef.current === messageId) return;
5092
+ lastMarkedReadIdRef.current = messageId;
5093
+ socket.markAsRead(conversationId, messageId);
5094
+ },
5095
+ [socket]
5096
+ );
5028
5097
  useEffect(() => {
5029
5098
  let cancelled = false;
5030
5099
  const load = async () => {
@@ -5158,6 +5227,7 @@ function useInlineCollab({
5158
5227
  try {
5159
5228
  await socket.sendMessage(preview.conversationId, payload);
5160
5229
  setUnreadCount(0);
5230
+ markLatestRead(preview.conversationId);
5161
5231
  } catch (err) {
5162
5232
  setError(err instanceof Error ? err.message : "Failed to send message");
5163
5233
  config.onError?.({
@@ -5176,7 +5246,8 @@ function useInlineCollab({
5176
5246
  invalidatePreview,
5177
5247
  subscribe,
5178
5248
  socket,
5179
- config
5249
+ config,
5250
+ markLatestRead
5180
5251
  ]
5181
5252
  );
5182
5253
  const sendMessage = useCallback(