apostil 0.1.4 → 0.1.6

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.js CHANGED
@@ -93,6 +93,7 @@ var ApostilContext = createContext(null);
93
93
  function ApostilProvider({
94
94
  pageId,
95
95
  storage,
96
+ brandColor = "#171717",
96
97
  children
97
98
  }) {
98
99
  const adapter = storage ?? defaultAdapter;
@@ -110,6 +111,7 @@ function ApostilProvider({
110
111
  useEffect(() => {
111
112
  pageIdRef.current = pageId;
112
113
  setLoaded(false);
114
+ hadThreadsRef.current = false;
113
115
  debug.log("loading threads for pageId:", pageId);
114
116
  adapter.load(pageId).then((t) => {
115
117
  if (pageIdRef.current === pageId) {
@@ -119,10 +121,17 @@ function ApostilProvider({
119
121
  }
120
122
  });
121
123
  }, [pageId]);
124
+ const hadThreadsRef = useRef(false);
122
125
  useEffect(() => {
123
- if (loaded && pageIdRef.current === pageId && threads.length > 0) {
126
+ if (!loaded || pageIdRef.current !== pageId) return;
127
+ if (threads.length > 0) {
128
+ hadThreadsRef.current = true;
124
129
  debug.log("saving", threads.length, "threads for pageId:", pageId);
125
130
  adapter.save(pageId, threads);
131
+ } else if (hadThreadsRef.current) {
132
+ debug.log("saving empty threads for pageId:", pageId);
133
+ adapter.save(pageId, threads);
134
+ hadThreadsRef.current = false;
126
135
  }
127
136
  }, [threads, pageId, loaded]);
128
137
  const setUser = useCallback((name) => {
@@ -184,6 +193,7 @@ function ApostilProvider({
184
193
  commentMode,
185
194
  activeThreadId,
186
195
  sidebarOpen,
196
+ brandColor,
187
197
  setCommentMode,
188
198
  setActiveThreadId,
189
199
  setSidebarOpen,
@@ -529,11 +539,14 @@ function CommentComposer({
529
539
  placeholder = "Add a comment...",
530
540
  autoFocus = false
531
541
  }) {
542
+ const { brandColor } = useApostil();
532
543
  const [value, setValue] = useState3("");
533
544
  const inputRef = useRef3(null);
534
545
  useEffect3(() => {
535
546
  if (autoFocus) {
536
- inputRef.current?.focus();
547
+ requestAnimationFrame(() => {
548
+ inputRef.current?.focus();
549
+ });
537
550
  }
538
551
  }, [autoFocus]);
539
552
  const handleSubmit = () => {
@@ -565,7 +578,8 @@ function CommentComposer({
565
578
  {
566
579
  onClick: handleSubmit,
567
580
  disabled: !value.trim(),
568
- className: "flex items-center justify-center w-8 h-8 rounded-lg\n bg-neutral-900 text-white disabled:opacity-30\n hover:bg-neutral-700 transition-colors shrink-0",
581
+ className: "flex items-center justify-center w-8 h-8 rounded-lg\n text-white disabled:opacity-30\n transition-colors shrink-0",
582
+ style: { backgroundColor: brandColor },
569
583
  children: /* @__PURE__ */ jsx4(Send, { className: "w-3.5 h-3.5" })
570
584
  }
571
585
  )
@@ -592,9 +606,17 @@ function ApostilThreadPopover({
592
606
  const ref = useRef4(null);
593
607
  const isOpen = activeThreadId === thread.id;
594
608
  const [pos, setPos] = useState4(null);
609
+ const [flip, setFlip] = useState4({ x: false, y: false });
595
610
  const updatePos = useCallback3(() => {
596
611
  setPos(resolvePosition(thread, overlayRef.current));
597
612
  }, [thread, overlayRef]);
613
+ useEffect4(() => {
614
+ if (!isOpen || !pos || !ref.current) return;
615
+ const rect = ref.current.getBoundingClientRect();
616
+ const flipX = rect.right > window.innerWidth;
617
+ const flipY = rect.bottom > window.innerHeight;
618
+ setFlip({ x: flipX, y: flipY });
619
+ }, [isOpen, pos]);
598
620
  useEffect4(() => {
599
621
  if (!isOpen) return;
600
622
  updatePos();
@@ -623,8 +645,14 @@ function ApostilThreadPopover({
623
645
  "div",
624
646
  {
625
647
  ref,
626
- className: "absolute z-[70] ml-5 -mt-3",
627
- style: { left: pos.left, top: pos.top },
648
+ className: "absolute z-[70]",
649
+ style: {
650
+ left: pos.left,
651
+ top: pos.top,
652
+ marginLeft: flip.x ? -340 : 20,
653
+ marginTop: flip.y ? -12 : -12,
654
+ ...flip.y ? { transform: "translateY(-100%)" } : {}
655
+ },
628
656
  onClick: (e) => e.stopPropagation(),
629
657
  children: /* @__PURE__ */ jsxs4("div", { className: "w-80 bg-white rounded-xl shadow-2xl border border-neutral-200 overflow-hidden", children: [
630
658
  /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-neutral-100 bg-neutral-50", children: [
@@ -690,7 +718,7 @@ function ApostilThreadPopover({
690
718
  import { useState as useState5 } from "react";
691
719
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
692
720
  function UserPrompt() {
693
- const { user, setUser, commentMode } = useApostil();
721
+ const { user, setUser, commentMode, brandColor } = useApostil();
694
722
  const [name, setName] = useState5("");
695
723
  if (user || !commentMode) return null;
696
724
  const handleSubmit = () => {
@@ -726,7 +754,8 @@ function UserPrompt() {
726
754
  {
727
755
  onClick: handleSubmit,
728
756
  disabled: !name.trim(),
729
- className: "w-full py-2 rounded-lg bg-neutral-900 text-white text-sm font-medium\n disabled:opacity-30 hover:bg-neutral-700 transition-colors",
757
+ className: "w-full py-2 rounded-lg text-white text-sm font-medium\n disabled:opacity-30 transition-colors",
758
+ style: { backgroundColor: brandColor },
730
759
  children: "Continue"
731
760
  }
732
761
  )
@@ -877,10 +906,12 @@ function findCommentTarget(el, boundary) {
877
906
  };
878
907
  }
879
908
  function CommentOverlay() {
880
- const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId } = useApostil();
909
+ const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId, brandColor } = useApostil();
881
910
  const overlayRef = useRef5(null);
882
911
  const [pendingPin, setPendingPin] = useState6(null);
883
912
  const [pendingPixel, setPendingPixel] = useState6(null);
913
+ const pendingRef = useRef5(null);
914
+ const [pendingFlip, setPendingFlip] = useState6({ x: false, y: false });
884
915
  const handleClick = useCallback4(
885
916
  (e) => {
886
917
  if (!commentMode || !overlayRef.current) return;
@@ -957,6 +988,17 @@ function CommentOverlay() {
957
988
  setPendingPixel(null);
958
989
  setCommentMode(false);
959
990
  }, [setCommentMode]);
991
+ useEffect5(() => {
992
+ if (!pendingPin || !pendingRef.current) return;
993
+ requestAnimationFrame(() => {
994
+ if (!pendingRef.current) return;
995
+ const rect = pendingRef.current.getBoundingClientRect();
996
+ setPendingFlip({
997
+ x: rect.right > window.innerWidth,
998
+ y: rect.bottom > window.innerHeight
999
+ });
1000
+ });
1001
+ }, [pendingPin]);
960
1002
  useEffect5(() => {
961
1003
  const hash = window.location.hash;
962
1004
  console.log("[apostil] hash check:", hash, "threads:", threads.length, threads.map((t) => t.id));
@@ -1041,35 +1083,46 @@ function CommentOverlay() {
1041
1083
  children: "+"
1042
1084
  }
1043
1085
  ),
1044
- /* @__PURE__ */ jsx7("div", { className: "absolute ml-5 -mt-3 w-72", children: /* @__PURE__ */ jsxs6("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-3", children: [
1045
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 mb-2", children: [
1046
- /* @__PURE__ */ jsx7("p", { className: "text-xs text-neutral-500", children: "New comment" }),
1047
- pendingPin.targetLabel && /* @__PURE__ */ jsx7("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: pendingPin.targetLabel })
1048
- ] }),
1049
- /* @__PURE__ */ jsx7(
1050
- CommentComposer,
1051
- {
1052
- onSubmit: handleNewComment,
1053
- placeholder: "What's on your mind?",
1054
- autoFocus: true
1055
- }
1056
- ),
1057
- /* @__PURE__ */ jsx7(
1058
- "button",
1059
- {
1060
- onClick: cancelPending,
1061
- className: "mt-2 text-xs text-neutral-400 hover:text-neutral-600 transition-colors",
1062
- children: "Cancel"
1063
- }
1064
- )
1065
- ] }) })
1086
+ /* @__PURE__ */ jsx7(
1087
+ "div",
1088
+ {
1089
+ ref: pendingRef,
1090
+ className: "absolute w-72",
1091
+ style: {
1092
+ marginLeft: pendingFlip.x ? -308 : 20,
1093
+ marginTop: pendingFlip.y ? void 0 : -12,
1094
+ ...pendingFlip.y ? { bottom: 0 } : {}
1095
+ },
1096
+ children: /* @__PURE__ */ jsxs6("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-3", children: [
1097
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 mb-2", children: [
1098
+ /* @__PURE__ */ jsx7("p", { className: "text-xs text-neutral-500", children: "New comment" }),
1099
+ pendingPin.targetLabel && /* @__PURE__ */ jsx7("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: pendingPin.targetLabel })
1100
+ ] }),
1101
+ /* @__PURE__ */ jsx7(
1102
+ CommentComposer,
1103
+ {
1104
+ onSubmit: handleNewComment,
1105
+ placeholder: "What's on your mind?",
1106
+ autoFocus: true
1107
+ }
1108
+ )
1109
+ ] })
1110
+ }
1111
+ )
1066
1112
  ]
1067
1113
  }
1068
1114
  )
1069
1115
  ]
1070
1116
  }
1071
1117
  ),
1072
- commentMode && !pendingPin && !showingUserPrompt && /* @__PURE__ */ jsx7("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none", children: /* @__PURE__ */ jsx7("div", { className: "bg-neutral-900/80 text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm", children: "Click anywhere to add a comment" }) }),
1118
+ commentMode && !pendingPin && !showingUserPrompt && /* @__PURE__ */ jsx7("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none", children: /* @__PURE__ */ jsx7(
1119
+ "div",
1120
+ {
1121
+ className: "text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm",
1122
+ style: { backgroundColor: `color-mix(in oklab, ${brandColor} 80%, transparent)` },
1123
+ children: "Click anywhere to add a comment"
1124
+ }
1125
+ ) }),
1073
1126
  /* @__PURE__ */ jsx7(UserPrompt, {})
1074
1127
  ] });
1075
1128
  }
@@ -1083,7 +1136,8 @@ function CommentToggle() {
1083
1136
  sidebarOpen,
1084
1137
  setSidebarOpen,
1085
1138
  unresolvedCount,
1086
- setActiveThreadId
1139
+ setActiveThreadId,
1140
+ brandColor
1087
1141
  } = useApostil();
1088
1142
  return /* @__PURE__ */ jsxs7("div", { className: "absolute bottom-5 right-5 z-[65] flex flex-col gap-2 items-end", children: [
1089
1143
  /* @__PURE__ */ jsx8(
@@ -1092,7 +1146,8 @@ function CommentToggle() {
1092
1146
  onClick: () => setSidebarOpen(!sidebarOpen),
1093
1147
  className: `flex items-center justify-center w-10 h-10 rounded-full shadow-lg
1094
1148
  transition-all duration-200 hover:scale-105
1095
- ${sidebarOpen ? "bg-neutral-900 text-white" : "bg-white text-neutral-600 hover:bg-neutral-50 border border-neutral-200"}`,
1149
+ ${sidebarOpen ? "text-white" : "bg-white text-neutral-600 hover:bg-neutral-50 border border-neutral-200"}`,
1150
+ style: sidebarOpen ? { backgroundColor: brandColor } : void 0,
1096
1151
  title: "Toggle comment list",
1097
1152
  children: /* @__PURE__ */ jsx8(List, { className: "w-4 h-4" })
1098
1153
  }
@@ -1110,7 +1165,8 @@ function CommentToggle() {
1110
1165
  },
1111
1166
  className: `relative flex items-center justify-center w-12 h-12 rounded-full shadow-lg
1112
1167
  transition-all duration-200 hover:scale-105
1113
- ${commentMode ? "bg-neutral-900 text-white ring-2 ring-neutral-400" : "bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-200"}`,
1168
+ ${commentMode ? "text-white ring-2 ring-neutral-400" : "bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-200"}`,
1169
+ style: commentMode ? { backgroundColor: brandColor } : void 0,
1114
1170
  title: commentMode ? "Exit comment mode" : "Add comment",
1115
1171
  children: [
1116
1172
  commentMode ? /* @__PURE__ */ jsx8(X, { className: "w-5 h-5" }) : /* @__PURE__ */ jsx8(MessageSquare, { className: "w-5 h-5" }),
@@ -1143,7 +1199,8 @@ function CommentSidebar() {
1143
1199
  sidebarOpen,
1144
1200
  setSidebarOpen,
1145
1201
  setActiveThreadId,
1146
- resolveThread
1202
+ resolveThread,
1203
+ brandColor
1147
1204
  } = useApostil();
1148
1205
  const [tab, setTab] = useState7("page");
1149
1206
  const [allPages, setAllPages] = useState7([]);
@@ -1187,7 +1244,8 @@ function CommentSidebar() {
1187
1244
  "button",
1188
1245
  {
1189
1246
  onClick: () => setTab("page"),
1190
- className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "page" ? "text-neutral-900 border-b-2 border-neutral-900" : "text-neutral-400 hover:text-neutral-600"}`,
1247
+ className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "page" ? "border-b-2" : "text-neutral-400 hover:text-neutral-600"}`,
1248
+ style: tab === "page" ? { color: brandColor, borderColor: brandColor } : void 0,
1191
1249
  children: [
1192
1250
  /* @__PURE__ */ jsx9(FileText, { className: "w-3 h-3" }),
1193
1251
  "This Page",
@@ -1199,7 +1257,8 @@ function CommentSidebar() {
1199
1257
  "button",
1200
1258
  {
1201
1259
  onClick: () => setTab("all"),
1202
- className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "all" ? "text-neutral-900 border-b-2 border-neutral-900" : "text-neutral-400 hover:text-neutral-600"}`,
1260
+ className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "all" ? "border-b-2" : "text-neutral-400 hover:text-neutral-600"}`,
1261
+ style: tab === "all" ? { color: brandColor, borderColor: brandColor } : void 0,
1203
1262
  children: [
1204
1263
  /* @__PURE__ */ jsx9(Globe, { className: "w-3 h-3" }),
1205
1264
  "All Pages"