apostil 0.1.5 → 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/README.md +7 -1
- package/dist/index.cjs +96 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +96 -37
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,13 +98,19 @@ Core context provider. Use directly for custom setups. When not using `npx apost
|
|
|
98
98
|
```tsx
|
|
99
99
|
import "apostil/styles.css";
|
|
100
100
|
|
|
101
|
-
<ApostilProvider pageId="my-page" storage={customAdapter}>
|
|
101
|
+
<ApostilProvider pageId="my-page" storage={customAdapter} brandColor="#2563eb">
|
|
102
102
|
{children}
|
|
103
103
|
<CommentOverlay />
|
|
104
104
|
<CommentToggle />
|
|
105
105
|
</ApostilProvider>
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
| Prop | Type | Default | Description |
|
|
109
|
+
|------|------|---------|-------------|
|
|
110
|
+
| `pageId` | `string` | required | Current page identifier |
|
|
111
|
+
| `storage` | `ApostilStorage` | REST `/api/apostil` | Storage adapter |
|
|
112
|
+
| `brandColor` | `string` | `"#171717"` | Accent color for buttons, tabs, and UI elements |
|
|
113
|
+
|
|
108
114
|
### `<CommentOverlay>`
|
|
109
115
|
|
|
110
116
|
Captures clicks in comment mode. Auto-detects z-index to sit above popovers and modals.
|
package/dist/index.cjs
CHANGED
|
@@ -150,6 +150,7 @@ var ApostilContext = (0, import_react.createContext)(null);
|
|
|
150
150
|
function ApostilProvider({
|
|
151
151
|
pageId,
|
|
152
152
|
storage,
|
|
153
|
+
brandColor = "#171717",
|
|
153
154
|
children
|
|
154
155
|
}) {
|
|
155
156
|
const adapter = storage ?? defaultAdapter;
|
|
@@ -167,6 +168,7 @@ function ApostilProvider({
|
|
|
167
168
|
(0, import_react.useEffect)(() => {
|
|
168
169
|
pageIdRef.current = pageId;
|
|
169
170
|
setLoaded(false);
|
|
171
|
+
hadThreadsRef.current = false;
|
|
170
172
|
debug.log("loading threads for pageId:", pageId);
|
|
171
173
|
adapter.load(pageId).then((t) => {
|
|
172
174
|
if (pageIdRef.current === pageId) {
|
|
@@ -176,10 +178,17 @@ function ApostilProvider({
|
|
|
176
178
|
}
|
|
177
179
|
});
|
|
178
180
|
}, [pageId]);
|
|
181
|
+
const hadThreadsRef = (0, import_react.useRef)(false);
|
|
179
182
|
(0, import_react.useEffect)(() => {
|
|
180
|
-
if (loaded
|
|
183
|
+
if (!loaded || pageIdRef.current !== pageId) return;
|
|
184
|
+
if (threads.length > 0) {
|
|
185
|
+
hadThreadsRef.current = true;
|
|
181
186
|
debug.log("saving", threads.length, "threads for pageId:", pageId);
|
|
182
187
|
adapter.save(pageId, threads);
|
|
188
|
+
} else if (hadThreadsRef.current) {
|
|
189
|
+
debug.log("saving empty threads for pageId:", pageId);
|
|
190
|
+
adapter.save(pageId, threads);
|
|
191
|
+
hadThreadsRef.current = false;
|
|
183
192
|
}
|
|
184
193
|
}, [threads, pageId, loaded]);
|
|
185
194
|
const setUser = (0, import_react.useCallback)((name) => {
|
|
@@ -241,6 +250,7 @@ function ApostilProvider({
|
|
|
241
250
|
commentMode,
|
|
242
251
|
activeThreadId,
|
|
243
252
|
sidebarOpen,
|
|
253
|
+
brandColor,
|
|
244
254
|
setCommentMode,
|
|
245
255
|
setActiveThreadId,
|
|
246
256
|
setSidebarOpen,
|
|
@@ -586,11 +596,14 @@ function CommentComposer({
|
|
|
586
596
|
placeholder = "Add a comment...",
|
|
587
597
|
autoFocus = false
|
|
588
598
|
}) {
|
|
599
|
+
const { brandColor } = useApostil();
|
|
589
600
|
const [value, setValue] = (0, import_react3.useState)("");
|
|
590
601
|
const inputRef = (0, import_react3.useRef)(null);
|
|
591
602
|
(0, import_react3.useEffect)(() => {
|
|
592
603
|
if (autoFocus) {
|
|
593
|
-
|
|
604
|
+
requestAnimationFrame(() => {
|
|
605
|
+
inputRef.current?.focus();
|
|
606
|
+
});
|
|
594
607
|
}
|
|
595
608
|
}, [autoFocus]);
|
|
596
609
|
const handleSubmit = () => {
|
|
@@ -622,7 +635,8 @@ function CommentComposer({
|
|
|
622
635
|
{
|
|
623
636
|
onClick: handleSubmit,
|
|
624
637
|
disabled: !value.trim(),
|
|
625
|
-
className: "flex items-center justify-center w-8 h-8 rounded-lg\n
|
|
638
|
+
className: "flex items-center justify-center w-8 h-8 rounded-lg\n text-white disabled:opacity-30\n transition-colors shrink-0",
|
|
639
|
+
style: { backgroundColor: brandColor },
|
|
626
640
|
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Send, { className: "w-3.5 h-3.5" })
|
|
627
641
|
}
|
|
628
642
|
)
|
|
@@ -649,9 +663,17 @@ function ApostilThreadPopover({
|
|
|
649
663
|
const ref = (0, import_react4.useRef)(null);
|
|
650
664
|
const isOpen = activeThreadId === thread.id;
|
|
651
665
|
const [pos, setPos] = (0, import_react4.useState)(null);
|
|
666
|
+
const [flip, setFlip] = (0, import_react4.useState)({ x: false, y: false });
|
|
652
667
|
const updatePos = (0, import_react4.useCallback)(() => {
|
|
653
668
|
setPos(resolvePosition(thread, overlayRef.current));
|
|
654
669
|
}, [thread, overlayRef]);
|
|
670
|
+
(0, import_react4.useEffect)(() => {
|
|
671
|
+
if (!isOpen || !pos || !ref.current) return;
|
|
672
|
+
const rect = ref.current.getBoundingClientRect();
|
|
673
|
+
const flipX = rect.right > window.innerWidth;
|
|
674
|
+
const flipY = rect.bottom > window.innerHeight;
|
|
675
|
+
setFlip({ x: flipX, y: flipY });
|
|
676
|
+
}, [isOpen, pos]);
|
|
655
677
|
(0, import_react4.useEffect)(() => {
|
|
656
678
|
if (!isOpen) return;
|
|
657
679
|
updatePos();
|
|
@@ -680,8 +702,14 @@ function ApostilThreadPopover({
|
|
|
680
702
|
"div",
|
|
681
703
|
{
|
|
682
704
|
ref,
|
|
683
|
-
className: "absolute z-[70]
|
|
684
|
-
style: {
|
|
705
|
+
className: "absolute z-[70]",
|
|
706
|
+
style: {
|
|
707
|
+
left: pos.left,
|
|
708
|
+
top: pos.top,
|
|
709
|
+
marginLeft: flip.x ? -340 : 20,
|
|
710
|
+
marginTop: flip.y ? -12 : -12,
|
|
711
|
+
...flip.y ? { transform: "translateY(-100%)" } : {}
|
|
712
|
+
},
|
|
685
713
|
onClick: (e) => e.stopPropagation(),
|
|
686
714
|
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-80 bg-white rounded-xl shadow-2xl border border-neutral-200 overflow-hidden", children: [
|
|
687
715
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-neutral-100 bg-neutral-50", children: [
|
|
@@ -747,7 +775,7 @@ function ApostilThreadPopover({
|
|
|
747
775
|
var import_react5 = require("react");
|
|
748
776
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
749
777
|
function UserPrompt() {
|
|
750
|
-
const { user, setUser, commentMode } = useApostil();
|
|
778
|
+
const { user, setUser, commentMode, brandColor } = useApostil();
|
|
751
779
|
const [name, setName] = (0, import_react5.useState)("");
|
|
752
780
|
if (user || !commentMode) return null;
|
|
753
781
|
const handleSubmit = () => {
|
|
@@ -783,7 +811,8 @@ function UserPrompt() {
|
|
|
783
811
|
{
|
|
784
812
|
onClick: handleSubmit,
|
|
785
813
|
disabled: !name.trim(),
|
|
786
|
-
className: "w-full py-2 rounded-lg
|
|
814
|
+
className: "w-full py-2 rounded-lg text-white text-sm font-medium\n disabled:opacity-30 transition-colors",
|
|
815
|
+
style: { backgroundColor: brandColor },
|
|
787
816
|
children: "Continue"
|
|
788
817
|
}
|
|
789
818
|
)
|
|
@@ -934,10 +963,12 @@ function findCommentTarget(el, boundary) {
|
|
|
934
963
|
};
|
|
935
964
|
}
|
|
936
965
|
function CommentOverlay() {
|
|
937
|
-
const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId } = useApostil();
|
|
966
|
+
const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId, brandColor } = useApostil();
|
|
938
967
|
const overlayRef = (0, import_react6.useRef)(null);
|
|
939
968
|
const [pendingPin, setPendingPin] = (0, import_react6.useState)(null);
|
|
940
969
|
const [pendingPixel, setPendingPixel] = (0, import_react6.useState)(null);
|
|
970
|
+
const pendingRef = (0, import_react6.useRef)(null);
|
|
971
|
+
const [pendingFlip, setPendingFlip] = (0, import_react6.useState)({ x: false, y: false });
|
|
941
972
|
const handleClick = (0, import_react6.useCallback)(
|
|
942
973
|
(e) => {
|
|
943
974
|
if (!commentMode || !overlayRef.current) return;
|
|
@@ -1014,6 +1045,17 @@ function CommentOverlay() {
|
|
|
1014
1045
|
setPendingPixel(null);
|
|
1015
1046
|
setCommentMode(false);
|
|
1016
1047
|
}, [setCommentMode]);
|
|
1048
|
+
(0, import_react6.useEffect)(() => {
|
|
1049
|
+
if (!pendingPin || !pendingRef.current) return;
|
|
1050
|
+
requestAnimationFrame(() => {
|
|
1051
|
+
if (!pendingRef.current) return;
|
|
1052
|
+
const rect = pendingRef.current.getBoundingClientRect();
|
|
1053
|
+
setPendingFlip({
|
|
1054
|
+
x: rect.right > window.innerWidth,
|
|
1055
|
+
y: rect.bottom > window.innerHeight
|
|
1056
|
+
});
|
|
1057
|
+
});
|
|
1058
|
+
}, [pendingPin]);
|
|
1017
1059
|
(0, import_react6.useEffect)(() => {
|
|
1018
1060
|
const hash = window.location.hash;
|
|
1019
1061
|
console.log("[apostil] hash check:", hash, "threads:", threads.length, threads.map((t) => t.id));
|
|
@@ -1098,35 +1140,46 @@ function CommentOverlay() {
|
|
|
1098
1140
|
children: "+"
|
|
1099
1141
|
}
|
|
1100
1142
|
),
|
|
1101
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1143
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1144
|
+
"div",
|
|
1145
|
+
{
|
|
1146
|
+
ref: pendingRef,
|
|
1147
|
+
className: "absolute w-72",
|
|
1148
|
+
style: {
|
|
1149
|
+
marginLeft: pendingFlip.x ? -308 : 20,
|
|
1150
|
+
marginTop: pendingFlip.y ? void 0 : -12,
|
|
1151
|
+
...pendingFlip.y ? { bottom: 0 } : {}
|
|
1152
|
+
},
|
|
1153
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-3", children: [
|
|
1154
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [
|
|
1155
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-neutral-500", children: "New comment" }),
|
|
1156
|
+
pendingPin.targetLabel && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: pendingPin.targetLabel })
|
|
1157
|
+
] }),
|
|
1158
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1159
|
+
CommentComposer,
|
|
1160
|
+
{
|
|
1161
|
+
onSubmit: handleNewComment,
|
|
1162
|
+
placeholder: "What's on your mind?",
|
|
1163
|
+
autoFocus: true
|
|
1164
|
+
}
|
|
1165
|
+
)
|
|
1166
|
+
] })
|
|
1167
|
+
}
|
|
1168
|
+
)
|
|
1123
1169
|
]
|
|
1124
1170
|
}
|
|
1125
1171
|
)
|
|
1126
1172
|
]
|
|
1127
1173
|
}
|
|
1128
1174
|
),
|
|
1129
|
-
commentMode && !pendingPin && !showingUserPrompt && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1175
|
+
commentMode && !pendingPin && !showingUserPrompt && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1176
|
+
"div",
|
|
1177
|
+
{
|
|
1178
|
+
className: "text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm",
|
|
1179
|
+
style: { backgroundColor: `color-mix(in oklab, ${brandColor} 80%, transparent)` },
|
|
1180
|
+
children: "Click anywhere to add a comment"
|
|
1181
|
+
}
|
|
1182
|
+
) }),
|
|
1130
1183
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(UserPrompt, {})
|
|
1131
1184
|
] });
|
|
1132
1185
|
}
|
|
@@ -1140,7 +1193,8 @@ function CommentToggle() {
|
|
|
1140
1193
|
sidebarOpen,
|
|
1141
1194
|
setSidebarOpen,
|
|
1142
1195
|
unresolvedCount,
|
|
1143
|
-
setActiveThreadId
|
|
1196
|
+
setActiveThreadId,
|
|
1197
|
+
brandColor
|
|
1144
1198
|
} = useApostil();
|
|
1145
1199
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "absolute bottom-5 right-5 z-[65] flex flex-col gap-2 items-end", children: [
|
|
1146
1200
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
@@ -1149,7 +1203,8 @@ function CommentToggle() {
|
|
|
1149
1203
|
onClick: () => setSidebarOpen(!sidebarOpen),
|
|
1150
1204
|
className: `flex items-center justify-center w-10 h-10 rounded-full shadow-lg
|
|
1151
1205
|
transition-all duration-200 hover:scale-105
|
|
1152
|
-
${sidebarOpen ? "
|
|
1206
|
+
${sidebarOpen ? "text-white" : "bg-white text-neutral-600 hover:bg-neutral-50 border border-neutral-200"}`,
|
|
1207
|
+
style: sidebarOpen ? { backgroundColor: brandColor } : void 0,
|
|
1153
1208
|
title: "Toggle comment list",
|
|
1154
1209
|
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(List, { className: "w-4 h-4" })
|
|
1155
1210
|
}
|
|
@@ -1167,7 +1222,8 @@ function CommentToggle() {
|
|
|
1167
1222
|
},
|
|
1168
1223
|
className: `relative flex items-center justify-center w-12 h-12 rounded-full shadow-lg
|
|
1169
1224
|
transition-all duration-200 hover:scale-105
|
|
1170
|
-
${commentMode ? "
|
|
1225
|
+
${commentMode ? "text-white ring-2 ring-neutral-400" : "bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-200"}`,
|
|
1226
|
+
style: commentMode ? { backgroundColor: brandColor } : void 0,
|
|
1171
1227
|
title: commentMode ? "Exit comment mode" : "Add comment",
|
|
1172
1228
|
children: [
|
|
1173
1229
|
commentMode ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(X, { className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageSquare, { className: "w-5 h-5" }),
|
|
@@ -1200,7 +1256,8 @@ function CommentSidebar() {
|
|
|
1200
1256
|
sidebarOpen,
|
|
1201
1257
|
setSidebarOpen,
|
|
1202
1258
|
setActiveThreadId,
|
|
1203
|
-
resolveThread
|
|
1259
|
+
resolveThread,
|
|
1260
|
+
brandColor
|
|
1204
1261
|
} = useApostil();
|
|
1205
1262
|
const [tab, setTab] = (0, import_react7.useState)("page");
|
|
1206
1263
|
const [allPages, setAllPages] = (0, import_react7.useState)([]);
|
|
@@ -1244,7 +1301,8 @@ function CommentSidebar() {
|
|
|
1244
1301
|
"button",
|
|
1245
1302
|
{
|
|
1246
1303
|
onClick: () => setTab("page"),
|
|
1247
|
-
className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "page" ? "
|
|
1304
|
+
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"}`,
|
|
1305
|
+
style: tab === "page" ? { color: brandColor, borderColor: brandColor } : void 0,
|
|
1248
1306
|
children: [
|
|
1249
1307
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FileText, { className: "w-3 h-3" }),
|
|
1250
1308
|
"This Page",
|
|
@@ -1256,7 +1314,8 @@ function CommentSidebar() {
|
|
|
1256
1314
|
"button",
|
|
1257
1315
|
{
|
|
1258
1316
|
onClick: () => setTab("all"),
|
|
1259
|
-
className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "all" ? "
|
|
1317
|
+
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"}`,
|
|
1318
|
+
style: tab === "all" ? { color: brandColor, borderColor: brandColor } : void 0,
|
|
1260
1319
|
children: [
|
|
1261
1320
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Globe, { className: "w-3 h-3" }),
|
|
1262
1321
|
"All Pages"
|