rte-builder 1.0.0 → 2.0.0
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 +219 -132
- package/dist/index.css +997 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +538 -2
- package/dist/index.d.ts +538 -2
- package/dist/index.js +1397 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1383 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6971,7 +6971,11 @@ var init_LexicalEditorComponent = __esm({
|
|
|
6971
6971
|
// src/index.tsx
|
|
6972
6972
|
var index_exports = {};
|
|
6973
6973
|
__export(index_exports, {
|
|
6974
|
+
CollaborationProvider: () => CollaborationProvider,
|
|
6975
|
+
CommentsPanel: () => CommentsPanel,
|
|
6976
|
+
CommentsProvider: () => CommentsProvider,
|
|
6974
6977
|
DEFAULT_FEATURES: () => DEFAULT_FEATURES,
|
|
6978
|
+
DEFAULT_REACTION_EMOJIS: () => DEFAULT_REACTION_EMOJIS,
|
|
6975
6979
|
EMOJI_CATEGORIES: () => EMOJI_CATEGORIES,
|
|
6976
6980
|
EditorRegistry: () => EditorRegistry,
|
|
6977
6981
|
Emoji: () => Emoji,
|
|
@@ -6979,12 +6983,15 @@ __export(index_exports, {
|
|
|
6979
6983
|
Fullscreen: () => Fullscreen,
|
|
6980
6984
|
Indent: () => Indent,
|
|
6981
6985
|
LineHeight: () => LineHeight,
|
|
6986
|
+
PresenceIndicator: () => PresenceIndicator,
|
|
6982
6987
|
Print: () => Print,
|
|
6983
6988
|
RichTextEditor: () => RichTextEditor,
|
|
6984
6989
|
TipTapAdapter: () => TipTapAdapter,
|
|
6985
6990
|
TipTapEditorComponent: () => TipTapEditorComponent,
|
|
6986
6991
|
TipTapToolbar: () => TipTapToolbar,
|
|
6987
6992
|
UnifiedEditor: () => UnifiedEditor,
|
|
6993
|
+
VersionHistoryPanel: () => VersionHistoryPanel,
|
|
6994
|
+
VersionHistoryProvider: () => VersionHistoryProvider,
|
|
6988
6995
|
Video: () => Video,
|
|
6989
6996
|
blogToolbar: () => blogToolbar,
|
|
6990
6997
|
codeToolbar: () => codeToolbar,
|
|
@@ -7006,7 +7013,13 @@ __export(index_exports, {
|
|
|
7006
7013
|
registerAdapter: () => registerAdapter,
|
|
7007
7014
|
simpleToolbar: () => simpleToolbar,
|
|
7008
7015
|
toolbarPresets: () => toolbarPresets,
|
|
7009
|
-
unregisterAdapter: () => unregisterAdapter
|
|
7016
|
+
unregisterAdapter: () => unregisterAdapter,
|
|
7017
|
+
useCollaboration: () => useCollaboration,
|
|
7018
|
+
useCollaborationOptional: () => useCollaborationOptional,
|
|
7019
|
+
useComments: () => useComments,
|
|
7020
|
+
useCommentsOptional: () => useCommentsOptional,
|
|
7021
|
+
useVersionHistory: () => useVersionHistory,
|
|
7022
|
+
useVersionHistoryOptional: () => useVersionHistoryOptional
|
|
7010
7023
|
});
|
|
7011
7024
|
module.exports = __toCommonJS(index_exports);
|
|
7012
7025
|
|
|
@@ -8675,9 +8688,1382 @@ init_Emoji();
|
|
|
8675
8688
|
init_Fullscreen();
|
|
8676
8689
|
init_Print();
|
|
8677
8690
|
init_Indent();
|
|
8691
|
+
|
|
8692
|
+
// src/collaboration/CollaborationContext.tsx
|
|
8693
|
+
var import_react12 = require("react");
|
|
8694
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
8695
|
+
var initialState = {
|
|
8696
|
+
status: "disconnected",
|
|
8697
|
+
users: [],
|
|
8698
|
+
error: void 0,
|
|
8699
|
+
isHost: false
|
|
8700
|
+
};
|
|
8701
|
+
function collaborationReducer(state, action) {
|
|
8702
|
+
switch (action.type) {
|
|
8703
|
+
case "SET_STATUS":
|
|
8704
|
+
return { ...state, status: action.status, error: action.status === "error" ? state.error : void 0 };
|
|
8705
|
+
case "SET_USERS":
|
|
8706
|
+
return { ...state, users: action.users };
|
|
8707
|
+
case "ADD_USER":
|
|
8708
|
+
if (state.users.find((u) => u.id === action.user.id)) {
|
|
8709
|
+
return {
|
|
8710
|
+
...state,
|
|
8711
|
+
users: state.users.map((u) => u.id === action.user.id ? action.user : u)
|
|
8712
|
+
};
|
|
8713
|
+
}
|
|
8714
|
+
return { ...state, users: [...state.users, action.user] };
|
|
8715
|
+
case "REMOVE_USER":
|
|
8716
|
+
return { ...state, users: state.users.filter((u) => u.id !== action.userId) };
|
|
8717
|
+
case "UPDATE_CURSOR":
|
|
8718
|
+
return {
|
|
8719
|
+
...state,
|
|
8720
|
+
users: state.users.map(
|
|
8721
|
+
(u) => u.id === action.userId ? { ...u, cursor: action.cursor, lastActive: Date.now() } : u
|
|
8722
|
+
)
|
|
8723
|
+
};
|
|
8724
|
+
case "SET_ERROR":
|
|
8725
|
+
return { ...state, status: "error", error: action.error };
|
|
8726
|
+
case "CLEAR_ERROR":
|
|
8727
|
+
return { ...state, error: void 0 };
|
|
8728
|
+
case "SET_HOST":
|
|
8729
|
+
return { ...state, isHost: action.isHost };
|
|
8730
|
+
default:
|
|
8731
|
+
return state;
|
|
8732
|
+
}
|
|
8733
|
+
}
|
|
8734
|
+
var CollaborationContext = (0, import_react12.createContext)(null);
|
|
8735
|
+
function generateUserColor() {
|
|
8736
|
+
const colors = [
|
|
8737
|
+
"#f87171",
|
|
8738
|
+
"#fb923c",
|
|
8739
|
+
"#fbbf24",
|
|
8740
|
+
"#a3e635",
|
|
8741
|
+
"#4ade80",
|
|
8742
|
+
"#2dd4bf",
|
|
8743
|
+
"#22d3ee",
|
|
8744
|
+
"#60a5fa",
|
|
8745
|
+
"#a78bfa",
|
|
8746
|
+
"#e879f9"
|
|
8747
|
+
];
|
|
8748
|
+
return colors[Math.floor(Math.random() * colors.length)];
|
|
8749
|
+
}
|
|
8750
|
+
function CollaborationProvider({
|
|
8751
|
+
children,
|
|
8752
|
+
config,
|
|
8753
|
+
onStatusChange,
|
|
8754
|
+
onUsersChange
|
|
8755
|
+
}) {
|
|
8756
|
+
const [state, dispatch] = (0, import_react12.useReducer)(collaborationReducer, initialState);
|
|
8757
|
+
const wsRef = (0, import_react12.useRef)(null);
|
|
8758
|
+
const reconnectTimeoutRef = (0, import_react12.useRef)(null);
|
|
8759
|
+
const reconnectAttemptsRef = (0, import_react12.useRef)(0);
|
|
8760
|
+
(0, import_react12.useEffect)(() => {
|
|
8761
|
+
onStatusChange?.(state.status);
|
|
8762
|
+
}, [state.status, onStatusChange]);
|
|
8763
|
+
(0, import_react12.useEffect)(() => {
|
|
8764
|
+
onUsersChange?.(state.users);
|
|
8765
|
+
}, [state.users, onUsersChange]);
|
|
8766
|
+
const connect = (0, import_react12.useCallback)(async () => {
|
|
8767
|
+
if (!config) return;
|
|
8768
|
+
dispatch({ type: "SET_STATUS", status: "connecting" });
|
|
8769
|
+
try {
|
|
8770
|
+
if (config.provider === "websocket" && config.serverUrl) {
|
|
8771
|
+
const url = new URL(config.serverUrl);
|
|
8772
|
+
url.searchParams.set("room", config.roomId);
|
|
8773
|
+
if (config.token) {
|
|
8774
|
+
url.searchParams.set("token", config.token);
|
|
8775
|
+
}
|
|
8776
|
+
const ws = new WebSocket(url.toString());
|
|
8777
|
+
wsRef.current = ws;
|
|
8778
|
+
ws.onopen = () => {
|
|
8779
|
+
dispatch({ type: "SET_STATUS", status: "connected" });
|
|
8780
|
+
reconnectAttemptsRef.current = 0;
|
|
8781
|
+
ws.send(JSON.stringify({
|
|
8782
|
+
type: "join",
|
|
8783
|
+
user: {
|
|
8784
|
+
id: config.user.id,
|
|
8785
|
+
name: config.user.name,
|
|
8786
|
+
avatar: config.user.avatar,
|
|
8787
|
+
color: config.user.color || generateUserColor()
|
|
8788
|
+
}
|
|
8789
|
+
}));
|
|
8790
|
+
};
|
|
8791
|
+
ws.onmessage = (event) => {
|
|
8792
|
+
try {
|
|
8793
|
+
const message = JSON.parse(event.data);
|
|
8794
|
+
handleCollaborationEvent(message);
|
|
8795
|
+
} catch {
|
|
8796
|
+
console.error("Failed to parse collaboration message");
|
|
8797
|
+
}
|
|
8798
|
+
};
|
|
8799
|
+
ws.onclose = () => {
|
|
8800
|
+
dispatch({ type: "SET_STATUS", status: "disconnected" });
|
|
8801
|
+
handleReconnect();
|
|
8802
|
+
};
|
|
8803
|
+
ws.onerror = () => {
|
|
8804
|
+
dispatch({ type: "SET_ERROR", error: "Connection error" });
|
|
8805
|
+
};
|
|
8806
|
+
} else if (config.provider === "webrtc") {
|
|
8807
|
+
dispatch({ type: "SET_ERROR", error: "WebRTC provider not yet implemented" });
|
|
8808
|
+
} else if (config.provider === "custom") {
|
|
8809
|
+
dispatch({ type: "SET_STATUS", status: "connected" });
|
|
8810
|
+
}
|
|
8811
|
+
} catch (error) {
|
|
8812
|
+
dispatch({ type: "SET_ERROR", error: error instanceof Error ? error.message : "Connection failed" });
|
|
8813
|
+
}
|
|
8814
|
+
}, [config]);
|
|
8815
|
+
const handleCollaborationEvent = (0, import_react12.useCallback)((event) => {
|
|
8816
|
+
switch (event.type) {
|
|
8817
|
+
case "user-joined":
|
|
8818
|
+
dispatch({ type: "ADD_USER", user: event.user });
|
|
8819
|
+
break;
|
|
8820
|
+
case "user-left":
|
|
8821
|
+
dispatch({ type: "REMOVE_USER", userId: event.userId });
|
|
8822
|
+
break;
|
|
8823
|
+
case "cursor-moved":
|
|
8824
|
+
dispatch({ type: "UPDATE_CURSOR", userId: event.userId, cursor: event.cursor });
|
|
8825
|
+
break;
|
|
8826
|
+
case "status-changed":
|
|
8827
|
+
dispatch({ type: "SET_STATUS", status: event.status });
|
|
8828
|
+
break;
|
|
8829
|
+
case "error":
|
|
8830
|
+
dispatch({ type: "SET_ERROR", error: event.message });
|
|
8831
|
+
break;
|
|
8832
|
+
}
|
|
8833
|
+
}, []);
|
|
8834
|
+
const handleReconnect = (0, import_react12.useCallback)(() => {
|
|
8835
|
+
if (!config?.autoReconnect) return;
|
|
8836
|
+
const maxAttempts = config.maxReconnectAttempts ?? 5;
|
|
8837
|
+
const interval = config.reconnectInterval ?? 3e3;
|
|
8838
|
+
if (reconnectAttemptsRef.current >= maxAttempts) {
|
|
8839
|
+
dispatch({ type: "SET_ERROR", error: "Max reconnection attempts reached" });
|
|
8840
|
+
return;
|
|
8841
|
+
}
|
|
8842
|
+
dispatch({ type: "SET_STATUS", status: "reconnecting" });
|
|
8843
|
+
reconnectAttemptsRef.current++;
|
|
8844
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
8845
|
+
connect();
|
|
8846
|
+
}, interval);
|
|
8847
|
+
}, [config, connect]);
|
|
8848
|
+
const disconnect = (0, import_react12.useCallback)(() => {
|
|
8849
|
+
if (reconnectTimeoutRef.current) {
|
|
8850
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
8851
|
+
}
|
|
8852
|
+
if (wsRef.current) {
|
|
8853
|
+
wsRef.current.close();
|
|
8854
|
+
wsRef.current = null;
|
|
8855
|
+
}
|
|
8856
|
+
dispatch({ type: "SET_STATUS", status: "disconnected" });
|
|
8857
|
+
dispatch({ type: "SET_USERS", users: [] });
|
|
8858
|
+
}, []);
|
|
8859
|
+
const updateCursor = (0, import_react12.useCallback)((cursor) => {
|
|
8860
|
+
if (!config || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
|
|
8861
|
+
wsRef.current.send(JSON.stringify({
|
|
8862
|
+
type: "cursor",
|
|
8863
|
+
userId: config.user.id,
|
|
8864
|
+
cursor
|
|
8865
|
+
}));
|
|
8866
|
+
}, [config]);
|
|
8867
|
+
(0, import_react12.useEffect)(() => {
|
|
8868
|
+
return () => {
|
|
8869
|
+
disconnect();
|
|
8870
|
+
};
|
|
8871
|
+
}, [disconnect]);
|
|
8872
|
+
const value = {
|
|
8873
|
+
state,
|
|
8874
|
+
config: config ?? null,
|
|
8875
|
+
connect,
|
|
8876
|
+
disconnect,
|
|
8877
|
+
updateCursor,
|
|
8878
|
+
isEnabled: !!config
|
|
8879
|
+
};
|
|
8880
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CollaborationContext.Provider, { value, children });
|
|
8881
|
+
}
|
|
8882
|
+
function useCollaboration() {
|
|
8883
|
+
const context = (0, import_react12.useContext)(CollaborationContext);
|
|
8884
|
+
if (!context) {
|
|
8885
|
+
throw new Error("useCollaboration must be used within a CollaborationProvider");
|
|
8886
|
+
}
|
|
8887
|
+
return context;
|
|
8888
|
+
}
|
|
8889
|
+
function useCollaborationOptional() {
|
|
8890
|
+
return (0, import_react12.useContext)(CollaborationContext);
|
|
8891
|
+
}
|
|
8892
|
+
|
|
8893
|
+
// src/collaboration/PresenceIndicator.tsx
|
|
8894
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
8895
|
+
function getInitials(name) {
|
|
8896
|
+
return name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2);
|
|
8897
|
+
}
|
|
8898
|
+
function UserAvatar({ user, showName }) {
|
|
8899
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
8900
|
+
"div",
|
|
8901
|
+
{
|
|
8902
|
+
className: "rte-presence-avatar",
|
|
8903
|
+
style: { borderColor: user.color },
|
|
8904
|
+
title: user.name,
|
|
8905
|
+
children: [
|
|
8906
|
+
user.avatar ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: user.avatar, alt: user.name }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { backgroundColor: user.color }, children: getInitials(user.name) }),
|
|
8907
|
+
user.isActive && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rte-presence-active-dot" }),
|
|
8908
|
+
showName && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rte-presence-name", children: user.name })
|
|
8909
|
+
]
|
|
8910
|
+
}
|
|
8911
|
+
);
|
|
8912
|
+
}
|
|
8913
|
+
function StatusBadge({ status }) {
|
|
8914
|
+
const statusConfig = {
|
|
8915
|
+
connected: { label: "Connected", color: "#22c55e" },
|
|
8916
|
+
connecting: { label: "Connecting...", color: "#eab308" },
|
|
8917
|
+
reconnecting: { label: "Reconnecting...", color: "#f97316" },
|
|
8918
|
+
disconnected: { label: "Disconnected", color: "#6b7280" },
|
|
8919
|
+
error: { label: "Error", color: "#ef4444" }
|
|
8920
|
+
};
|
|
8921
|
+
const config = statusConfig[status] || statusConfig.disconnected;
|
|
8922
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rte-presence-status", style: { color: config.color }, children: [
|
|
8923
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rte-presence-status-dot", style: { backgroundColor: config.color } }),
|
|
8924
|
+
config.label
|
|
8925
|
+
] });
|
|
8926
|
+
}
|
|
8927
|
+
function PresenceIndicator({
|
|
8928
|
+
maxAvatars = 5,
|
|
8929
|
+
showNames = false,
|
|
8930
|
+
className = ""
|
|
8931
|
+
}) {
|
|
8932
|
+
const collaboration = useCollaborationOptional();
|
|
8933
|
+
if (!collaboration?.isEnabled) {
|
|
8934
|
+
return null;
|
|
8935
|
+
}
|
|
8936
|
+
const { state } = collaboration;
|
|
8937
|
+
const visibleUsers = state.users.slice(0, maxAvatars);
|
|
8938
|
+
const remainingCount = Math.max(0, state.users.length - maxAvatars);
|
|
8939
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `rte-presence-indicator ${className}`, children: [
|
|
8940
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusBadge, { status: state.status }),
|
|
8941
|
+
state.users.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rte-presence-avatars", children: [
|
|
8942
|
+
visibleUsers.map((user) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(UserAvatar, { user, showName: showNames }, user.id)),
|
|
8943
|
+
remainingCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rte-presence-avatar rte-presence-more", children: [
|
|
8944
|
+
"+",
|
|
8945
|
+
remainingCount
|
|
8946
|
+
] })
|
|
8947
|
+
] }),
|
|
8948
|
+
state.error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rte-presence-error", title: state.error, children: "!" })
|
|
8949
|
+
] });
|
|
8950
|
+
}
|
|
8951
|
+
|
|
8952
|
+
// src/comments/types.ts
|
|
8953
|
+
var DEFAULT_REACTION_EMOJIS = ["\u{1F44D}", "\u{1F44E}", "\u2764\uFE0F", "\u{1F389}", "\u{1F604}", "\u{1F615}", "\u{1F440}", "\u{1F680}"];
|
|
8954
|
+
|
|
8955
|
+
// src/comments/CommentsContext.tsx
|
|
8956
|
+
var import_react13 = require("react");
|
|
8957
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
8958
|
+
var initialState2 = {
|
|
8959
|
+
threads: [],
|
|
8960
|
+
activeThreadId: null,
|
|
8961
|
+
isPanelOpen: false,
|
|
8962
|
+
filter: "all",
|
|
8963
|
+
currentUser: null
|
|
8964
|
+
};
|
|
8965
|
+
function generateId() {
|
|
8966
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
8967
|
+
}
|
|
8968
|
+
function commentsReducer(state, action) {
|
|
8969
|
+
switch (action.type) {
|
|
8970
|
+
case "SET_THREADS":
|
|
8971
|
+
return { ...state, threads: action.threads };
|
|
8972
|
+
case "ADD_THREAD":
|
|
8973
|
+
return { ...state, threads: [...state.threads, action.thread] };
|
|
8974
|
+
case "DELETE_THREAD":
|
|
8975
|
+
return {
|
|
8976
|
+
...state,
|
|
8977
|
+
threads: state.threads.filter((t) => t.id !== action.threadId),
|
|
8978
|
+
activeThreadId: state.activeThreadId === action.threadId ? null : state.activeThreadId
|
|
8979
|
+
};
|
|
8980
|
+
case "RESOLVE_THREAD":
|
|
8981
|
+
return {
|
|
8982
|
+
...state,
|
|
8983
|
+
threads: state.threads.map(
|
|
8984
|
+
(t) => t.id === action.threadId ? { ...t, status: "resolved", resolvedAt: Date.now(), resolvedBy: action.resolvedBy } : t
|
|
8985
|
+
)
|
|
8986
|
+
};
|
|
8987
|
+
case "REOPEN_THREAD":
|
|
8988
|
+
return {
|
|
8989
|
+
...state,
|
|
8990
|
+
threads: state.threads.map(
|
|
8991
|
+
(t) => t.id === action.threadId ? { ...t, status: "open", resolvedAt: void 0, resolvedBy: void 0 } : t
|
|
8992
|
+
)
|
|
8993
|
+
};
|
|
8994
|
+
case "ADD_COMMENT":
|
|
8995
|
+
return {
|
|
8996
|
+
...state,
|
|
8997
|
+
threads: state.threads.map(
|
|
8998
|
+
(t) => t.id === action.threadId ? { ...t, comments: [...t.comments, action.comment] } : t
|
|
8999
|
+
)
|
|
9000
|
+
};
|
|
9001
|
+
case "UPDATE_COMMENT":
|
|
9002
|
+
return {
|
|
9003
|
+
...state,
|
|
9004
|
+
threads: state.threads.map(
|
|
9005
|
+
(t) => t.id === action.threadId ? {
|
|
9006
|
+
...t,
|
|
9007
|
+
comments: t.comments.map(
|
|
9008
|
+
(c) => c.id === action.comment.id ? action.comment : c
|
|
9009
|
+
)
|
|
9010
|
+
} : t
|
|
9011
|
+
)
|
|
9012
|
+
};
|
|
9013
|
+
case "DELETE_COMMENT":
|
|
9014
|
+
return {
|
|
9015
|
+
...state,
|
|
9016
|
+
threads: state.threads.map(
|
|
9017
|
+
(t) => t.id === action.threadId ? { ...t, comments: t.comments.filter((c) => c.id !== action.commentId) } : t
|
|
9018
|
+
)
|
|
9019
|
+
};
|
|
9020
|
+
case "ADD_REACTION":
|
|
9021
|
+
return {
|
|
9022
|
+
...state,
|
|
9023
|
+
threads: state.threads.map(
|
|
9024
|
+
(t) => t.id === action.threadId ? {
|
|
9025
|
+
...t,
|
|
9026
|
+
comments: t.comments.map((c) => {
|
|
9027
|
+
if (c.id !== action.commentId) return c;
|
|
9028
|
+
const reactions = c.reactions || [];
|
|
9029
|
+
const existingReaction = reactions.find((r) => r.emoji === action.emoji);
|
|
9030
|
+
if (existingReaction) {
|
|
9031
|
+
return {
|
|
9032
|
+
...c,
|
|
9033
|
+
reactions: reactions.map(
|
|
9034
|
+
(r) => r.emoji === action.emoji ? { ...r, users: [...r.users, action.user] } : r
|
|
9035
|
+
)
|
|
9036
|
+
};
|
|
9037
|
+
}
|
|
9038
|
+
return {
|
|
9039
|
+
...c,
|
|
9040
|
+
reactions: [...reactions, { emoji: action.emoji, users: [action.user] }]
|
|
9041
|
+
};
|
|
9042
|
+
})
|
|
9043
|
+
} : t
|
|
9044
|
+
)
|
|
9045
|
+
};
|
|
9046
|
+
case "REMOVE_REACTION":
|
|
9047
|
+
return {
|
|
9048
|
+
...state,
|
|
9049
|
+
threads: state.threads.map(
|
|
9050
|
+
(t) => t.id === action.threadId ? {
|
|
9051
|
+
...t,
|
|
9052
|
+
comments: t.comments.map((c) => {
|
|
9053
|
+
if (c.id !== action.commentId) return c;
|
|
9054
|
+
return {
|
|
9055
|
+
...c,
|
|
9056
|
+
reactions: (c.reactions || []).map(
|
|
9057
|
+
(r) => r.emoji === action.emoji ? { ...r, users: r.users.filter((u) => u.id !== action.userId) } : r
|
|
9058
|
+
).filter((r) => r.users.length > 0)
|
|
9059
|
+
};
|
|
9060
|
+
})
|
|
9061
|
+
} : t
|
|
9062
|
+
)
|
|
9063
|
+
};
|
|
9064
|
+
case "SET_ACTIVE_THREAD":
|
|
9065
|
+
return { ...state, activeThreadId: action.threadId };
|
|
9066
|
+
case "TOGGLE_PANEL":
|
|
9067
|
+
return { ...state, isPanelOpen: action.isOpen ?? !state.isPanelOpen };
|
|
9068
|
+
case "SET_FILTER":
|
|
9069
|
+
return { ...state, filter: action.filter };
|
|
9070
|
+
case "SET_CURRENT_USER":
|
|
9071
|
+
return { ...state, currentUser: action.user };
|
|
9072
|
+
default:
|
|
9073
|
+
return state;
|
|
9074
|
+
}
|
|
9075
|
+
}
|
|
9076
|
+
var CommentsContext = (0, import_react13.createContext)(null);
|
|
9077
|
+
function CommentsProvider({
|
|
9078
|
+
children,
|
|
9079
|
+
config,
|
|
9080
|
+
initialThreads = [],
|
|
9081
|
+
onThreadsChange
|
|
9082
|
+
}) {
|
|
9083
|
+
const [state, dispatch] = (0, import_react13.useReducer)(commentsReducer, {
|
|
9084
|
+
...initialState2,
|
|
9085
|
+
threads: initialThreads,
|
|
9086
|
+
currentUser: config?.currentUser ?? null
|
|
9087
|
+
});
|
|
9088
|
+
const subscribersRef = { current: /* @__PURE__ */ new Set() };
|
|
9089
|
+
(0, import_react13.useEffect)(() => {
|
|
9090
|
+
if (config?.onLoad) {
|
|
9091
|
+
config.onLoad().then((threads) => {
|
|
9092
|
+
dispatch({ type: "SET_THREADS", threads });
|
|
9093
|
+
});
|
|
9094
|
+
}
|
|
9095
|
+
}, [config]);
|
|
9096
|
+
(0, import_react13.useEffect)(() => {
|
|
9097
|
+
onThreadsChange?.(state.threads);
|
|
9098
|
+
if (config?.onSave) {
|
|
9099
|
+
config.onSave(state.threads);
|
|
9100
|
+
}
|
|
9101
|
+
}, [state.threads, onThreadsChange, config]);
|
|
9102
|
+
(0, import_react13.useEffect)(() => {
|
|
9103
|
+
if (config?.currentUser) {
|
|
9104
|
+
dispatch({ type: "SET_CURRENT_USER", user: config.currentUser });
|
|
9105
|
+
}
|
|
9106
|
+
}, [config?.currentUser]);
|
|
9107
|
+
const emitEvent = (0, import_react13.useCallback)((event) => {
|
|
9108
|
+
subscribersRef.current.forEach((callback) => callback(event));
|
|
9109
|
+
}, []);
|
|
9110
|
+
const createThread = (0, import_react13.useCallback)((range, initialComment) => {
|
|
9111
|
+
if (!state.currentUser) return null;
|
|
9112
|
+
const threadId = generateId();
|
|
9113
|
+
const commentId = generateId();
|
|
9114
|
+
const now = Date.now();
|
|
9115
|
+
const comment = {
|
|
9116
|
+
id: commentId,
|
|
9117
|
+
threadId,
|
|
9118
|
+
content: initialComment,
|
|
9119
|
+
author: state.currentUser,
|
|
9120
|
+
createdAt: now,
|
|
9121
|
+
isEdited: false
|
|
9122
|
+
};
|
|
9123
|
+
const thread = {
|
|
9124
|
+
id: threadId,
|
|
9125
|
+
range,
|
|
9126
|
+
comments: [comment],
|
|
9127
|
+
status: "open",
|
|
9128
|
+
createdAt: now
|
|
9129
|
+
};
|
|
9130
|
+
dispatch({ type: "ADD_THREAD", thread });
|
|
9131
|
+
emitEvent({ type: "thread-created", thread });
|
|
9132
|
+
return thread;
|
|
9133
|
+
}, [state.currentUser, emitEvent]);
|
|
9134
|
+
const deleteThread = (0, import_react13.useCallback)((threadId) => {
|
|
9135
|
+
if (config?.allowDelete === false) return;
|
|
9136
|
+
dispatch({ type: "DELETE_THREAD", threadId });
|
|
9137
|
+
emitEvent({ type: "thread-deleted", threadId });
|
|
9138
|
+
}, [config, emitEvent]);
|
|
9139
|
+
const resolveThread = (0, import_react13.useCallback)((threadId) => {
|
|
9140
|
+
if (!state.currentUser || config?.allowResolve === false) return;
|
|
9141
|
+
dispatch({ type: "RESOLVE_THREAD", threadId, resolvedBy: state.currentUser });
|
|
9142
|
+
emitEvent({ type: "thread-resolved", threadId, resolvedBy: state.currentUser });
|
|
9143
|
+
}, [state.currentUser, config, emitEvent]);
|
|
9144
|
+
const reopenThread = (0, import_react13.useCallback)((threadId) => {
|
|
9145
|
+
dispatch({ type: "REOPEN_THREAD", threadId });
|
|
9146
|
+
emitEvent({ type: "thread-reopened", threadId });
|
|
9147
|
+
}, [emitEvent]);
|
|
9148
|
+
const addComment = (0, import_react13.useCallback)((threadId, content) => {
|
|
9149
|
+
if (!state.currentUser) return null;
|
|
9150
|
+
const comment = {
|
|
9151
|
+
id: generateId(),
|
|
9152
|
+
threadId,
|
|
9153
|
+
content,
|
|
9154
|
+
author: state.currentUser,
|
|
9155
|
+
createdAt: Date.now(),
|
|
9156
|
+
isEdited: false
|
|
9157
|
+
};
|
|
9158
|
+
dispatch({ type: "ADD_COMMENT", threadId, comment });
|
|
9159
|
+
emitEvent({ type: "comment-added", threadId, comment });
|
|
9160
|
+
return comment;
|
|
9161
|
+
}, [state.currentUser, emitEvent]);
|
|
9162
|
+
const updateComment = (0, import_react13.useCallback)((threadId, commentId, content) => {
|
|
9163
|
+
if (config?.allowEdit === false) return;
|
|
9164
|
+
const thread = state.threads.find((t) => t.id === threadId);
|
|
9165
|
+
const existingComment = thread?.comments.find((c) => c.id === commentId);
|
|
9166
|
+
if (!existingComment) return;
|
|
9167
|
+
const updatedComment = {
|
|
9168
|
+
...existingComment,
|
|
9169
|
+
content,
|
|
9170
|
+
updatedAt: Date.now(),
|
|
9171
|
+
isEdited: true
|
|
9172
|
+
};
|
|
9173
|
+
dispatch({ type: "UPDATE_COMMENT", threadId, comment: updatedComment });
|
|
9174
|
+
emitEvent({ type: "comment-updated", threadId, comment: updatedComment });
|
|
9175
|
+
}, [state.threads, config, emitEvent]);
|
|
9176
|
+
const deleteComment = (0, import_react13.useCallback)((threadId, commentId) => {
|
|
9177
|
+
if (config?.allowDelete === false) return;
|
|
9178
|
+
dispatch({ type: "DELETE_COMMENT", threadId, commentId });
|
|
9179
|
+
emitEvent({ type: "comment-deleted", threadId, commentId });
|
|
9180
|
+
}, [config, emitEvent]);
|
|
9181
|
+
const addReaction = (0, import_react13.useCallback)((threadId, commentId, emoji) => {
|
|
9182
|
+
if (!state.currentUser || config?.allowReactions === false) return;
|
|
9183
|
+
const allowedEmojis = config?.reactionEmojis ?? DEFAULT_REACTION_EMOJIS;
|
|
9184
|
+
if (!allowedEmojis.includes(emoji)) return;
|
|
9185
|
+
dispatch({ type: "ADD_REACTION", threadId, commentId, emoji, user: state.currentUser });
|
|
9186
|
+
emitEvent({ type: "reaction-added", threadId, commentId, emoji, user: state.currentUser });
|
|
9187
|
+
}, [state.currentUser, config, emitEvent]);
|
|
9188
|
+
const removeReaction = (0, import_react13.useCallback)((threadId, commentId, emoji) => {
|
|
9189
|
+
if (!state.currentUser) return;
|
|
9190
|
+
dispatch({ type: "REMOVE_REACTION", threadId, commentId, emoji, userId: state.currentUser.id });
|
|
9191
|
+
emitEvent({ type: "reaction-removed", threadId, commentId, emoji, userId: state.currentUser.id });
|
|
9192
|
+
}, [state.currentUser, emitEvent]);
|
|
9193
|
+
const setActiveThread = (0, import_react13.useCallback)((threadId) => {
|
|
9194
|
+
dispatch({ type: "SET_ACTIVE_THREAD", threadId });
|
|
9195
|
+
}, []);
|
|
9196
|
+
const togglePanel = (0, import_react13.useCallback)((isOpen) => {
|
|
9197
|
+
dispatch({ type: "TOGGLE_PANEL", isOpen });
|
|
9198
|
+
}, []);
|
|
9199
|
+
const setFilter = (0, import_react13.useCallback)((filter) => {
|
|
9200
|
+
dispatch({ type: "SET_FILTER", filter });
|
|
9201
|
+
}, []);
|
|
9202
|
+
const getThreadByRange = (0, import_react13.useCallback)((from, to) => {
|
|
9203
|
+
return state.threads.find((t) => t.range.from === from && t.range.to === to);
|
|
9204
|
+
}, [state.threads]);
|
|
9205
|
+
const getFilteredThreads = (0, import_react13.useCallback)(() => {
|
|
9206
|
+
if (state.filter === "all") return state.threads;
|
|
9207
|
+
return state.threads.filter((t) => t.status === state.filter);
|
|
9208
|
+
}, [state.threads, state.filter]);
|
|
9209
|
+
const subscribe = (0, import_react13.useCallback)((callback) => {
|
|
9210
|
+
subscribersRef.current.add(callback);
|
|
9211
|
+
return () => {
|
|
9212
|
+
subscribersRef.current.delete(callback);
|
|
9213
|
+
};
|
|
9214
|
+
}, []);
|
|
9215
|
+
const value = {
|
|
9216
|
+
state,
|
|
9217
|
+
config: config ?? null,
|
|
9218
|
+
createThread,
|
|
9219
|
+
deleteThread,
|
|
9220
|
+
resolveThread,
|
|
9221
|
+
reopenThread,
|
|
9222
|
+
addComment,
|
|
9223
|
+
updateComment,
|
|
9224
|
+
deleteComment,
|
|
9225
|
+
addReaction,
|
|
9226
|
+
removeReaction,
|
|
9227
|
+
setActiveThread,
|
|
9228
|
+
togglePanel,
|
|
9229
|
+
setFilter,
|
|
9230
|
+
getThreadByRange,
|
|
9231
|
+
getFilteredThreads,
|
|
9232
|
+
subscribe,
|
|
9233
|
+
isEnabled: !!config
|
|
9234
|
+
};
|
|
9235
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CommentsContext.Provider, { value, children });
|
|
9236
|
+
}
|
|
9237
|
+
function useComments() {
|
|
9238
|
+
const context = (0, import_react13.useContext)(CommentsContext);
|
|
9239
|
+
if (!context) {
|
|
9240
|
+
throw new Error("useComments must be used within a CommentsProvider");
|
|
9241
|
+
}
|
|
9242
|
+
return context;
|
|
9243
|
+
}
|
|
9244
|
+
function useCommentsOptional() {
|
|
9245
|
+
return (0, import_react13.useContext)(CommentsContext);
|
|
9246
|
+
}
|
|
9247
|
+
|
|
9248
|
+
// src/comments/CommentsPanel.tsx
|
|
9249
|
+
var import_react14 = require("react");
|
|
9250
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
9251
|
+
function formatRelativeTime(timestamp) {
|
|
9252
|
+
const now = Date.now();
|
|
9253
|
+
const diff = now - timestamp;
|
|
9254
|
+
const minutes = Math.floor(diff / 6e4);
|
|
9255
|
+
const hours = Math.floor(diff / 36e5);
|
|
9256
|
+
const days = Math.floor(diff / 864e5);
|
|
9257
|
+
if (minutes < 1) return "Just now";
|
|
9258
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
9259
|
+
if (hours < 24) return `${hours}h ago`;
|
|
9260
|
+
if (days < 7) return `${days}d ago`;
|
|
9261
|
+
return new Date(timestamp).toLocaleDateString();
|
|
9262
|
+
}
|
|
9263
|
+
function CommentItem({
|
|
9264
|
+
comment,
|
|
9265
|
+
threadId,
|
|
9266
|
+
isFirst
|
|
9267
|
+
}) {
|
|
9268
|
+
const comments = useCommentsOptional();
|
|
9269
|
+
const [isEditing, setIsEditing] = (0, import_react14.useState)(false);
|
|
9270
|
+
const [editContent, setEditContent] = (0, import_react14.useState)(comment.content);
|
|
9271
|
+
const [showReactions, setShowReactions] = (0, import_react14.useState)(false);
|
|
9272
|
+
if (!comments) return null;
|
|
9273
|
+
const { config, updateComment, deleteComment, addReaction, removeReaction, state } = comments;
|
|
9274
|
+
const canEdit = config?.allowEdit !== false && comment.author.id === state.currentUser?.id;
|
|
9275
|
+
const canDelete = config?.allowDelete !== false && comment.author.id === state.currentUser?.id;
|
|
9276
|
+
const canReact = config?.allowReactions !== false;
|
|
9277
|
+
const reactionEmojis = config?.reactionEmojis ?? DEFAULT_REACTION_EMOJIS;
|
|
9278
|
+
const handleSaveEdit = () => {
|
|
9279
|
+
updateComment(threadId, comment.id, editContent);
|
|
9280
|
+
setIsEditing(false);
|
|
9281
|
+
};
|
|
9282
|
+
const handleToggleReaction = (emoji) => {
|
|
9283
|
+
const reaction = comment.reactions?.find((r) => r.emoji === emoji);
|
|
9284
|
+
const hasReacted = reaction?.users.some((u) => u.id === state.currentUser?.id);
|
|
9285
|
+
if (hasReacted) {
|
|
9286
|
+
removeReaction(threadId, comment.id, emoji);
|
|
9287
|
+
} else {
|
|
9288
|
+
addReaction(threadId, comment.id, emoji);
|
|
9289
|
+
}
|
|
9290
|
+
setShowReactions(false);
|
|
9291
|
+
};
|
|
9292
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `rte-comment ${isFirst ? "rte-comment-first" : ""}`, children: [
|
|
9293
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-comment-header", children: [
|
|
9294
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-comment-author", children: [
|
|
9295
|
+
comment.author.avatar ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("img", { src: comment.author.avatar, alt: comment.author.name, className: "rte-comment-avatar" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-comment-avatar-placeholder", children: comment.author.name.charAt(0).toUpperCase() }),
|
|
9296
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "rte-comment-author-name", children: comment.author.name })
|
|
9297
|
+
] }),
|
|
9298
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "rte-comment-time", children: [
|
|
9299
|
+
formatRelativeTime(comment.createdAt),
|
|
9300
|
+
comment.isEdited && " (edited)"
|
|
9301
|
+
] })
|
|
9302
|
+
] }),
|
|
9303
|
+
isEditing ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-comment-edit", children: [
|
|
9304
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9305
|
+
"textarea",
|
|
9306
|
+
{
|
|
9307
|
+
value: editContent,
|
|
9308
|
+
onChange: (e) => setEditContent(e.target.value),
|
|
9309
|
+
className: "rte-comment-edit-input"
|
|
9310
|
+
}
|
|
9311
|
+
),
|
|
9312
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-comment-edit-actions", children: [
|
|
9313
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: handleSaveEdit, className: "rte-comment-btn-save", children: "Save" }),
|
|
9314
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: () => setIsEditing(false), className: "rte-comment-btn-cancel", children: "Cancel" })
|
|
9315
|
+
] })
|
|
9316
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-comment-content", dangerouslySetInnerHTML: { __html: comment.content } }),
|
|
9317
|
+
comment.reactions && comment.reactions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-comment-reactions", children: comment.reactions.map((reaction) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
9318
|
+
"button",
|
|
9319
|
+
{
|
|
9320
|
+
className: `rte-comment-reaction ${reaction.users.some((u) => u.id === state.currentUser?.id) ? "active" : ""}`,
|
|
9321
|
+
onClick: () => handleToggleReaction(reaction.emoji),
|
|
9322
|
+
title: reaction.users.map((u) => u.name).join(", "),
|
|
9323
|
+
children: [
|
|
9324
|
+
reaction.emoji,
|
|
9325
|
+
" ",
|
|
9326
|
+
reaction.users.length
|
|
9327
|
+
]
|
|
9328
|
+
},
|
|
9329
|
+
reaction.emoji
|
|
9330
|
+
)) }),
|
|
9331
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-comment-actions", children: [
|
|
9332
|
+
canReact && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-comment-reaction-picker", children: [
|
|
9333
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9334
|
+
"button",
|
|
9335
|
+
{
|
|
9336
|
+
className: "rte-comment-action-btn",
|
|
9337
|
+
onClick: () => setShowReactions(!showReactions),
|
|
9338
|
+
children: "\u{1F60A}"
|
|
9339
|
+
}
|
|
9340
|
+
),
|
|
9341
|
+
showReactions && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-comment-reaction-dropdown", children: reactionEmojis.map((emoji) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: () => handleToggleReaction(emoji), children: emoji }, emoji)) })
|
|
9342
|
+
] }),
|
|
9343
|
+
canEdit && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { className: "rte-comment-action-btn", onClick: () => setIsEditing(true), children: "Edit" }),
|
|
9344
|
+
canDelete && !isFirst && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9345
|
+
"button",
|
|
9346
|
+
{
|
|
9347
|
+
className: "rte-comment-action-btn rte-comment-action-delete",
|
|
9348
|
+
onClick: () => deleteComment(threadId, comment.id),
|
|
9349
|
+
children: "Delete"
|
|
9350
|
+
}
|
|
9351
|
+
)
|
|
9352
|
+
] })
|
|
9353
|
+
] });
|
|
9354
|
+
}
|
|
9355
|
+
function ThreadItem({ thread }) {
|
|
9356
|
+
const comments = useCommentsOptional();
|
|
9357
|
+
const [replyContent, setReplyContent] = (0, import_react14.useState)("");
|
|
9358
|
+
const [showReply, setShowReply] = (0, import_react14.useState)(false);
|
|
9359
|
+
if (!comments) return null;
|
|
9360
|
+
const { state, setActiveThread, addComment, resolveThread, reopenThread, deleteThread, config } = comments;
|
|
9361
|
+
const isActive = state.activeThreadId === thread.id;
|
|
9362
|
+
const canResolve = config?.allowResolve !== false;
|
|
9363
|
+
const handleReply = () => {
|
|
9364
|
+
if (replyContent.trim()) {
|
|
9365
|
+
addComment(thread.id, replyContent);
|
|
9366
|
+
setReplyContent("");
|
|
9367
|
+
setShowReply(false);
|
|
9368
|
+
}
|
|
9369
|
+
};
|
|
9370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
9371
|
+
"div",
|
|
9372
|
+
{
|
|
9373
|
+
className: `rte-thread ${isActive ? "rte-thread-active" : ""} ${thread.status === "resolved" ? "rte-thread-resolved" : ""}`,
|
|
9374
|
+
onClick: () => setActiveThread(thread.id),
|
|
9375
|
+
children: [
|
|
9376
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-thread-header", children: [
|
|
9377
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-thread-quote", children: [
|
|
9378
|
+
'"',
|
|
9379
|
+
thread.range.text.slice(0, 50),
|
|
9380
|
+
'..."'
|
|
9381
|
+
] }),
|
|
9382
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-thread-meta", children: [
|
|
9383
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: `rte-thread-status rte-thread-status-${thread.status}`, children: thread.status }),
|
|
9384
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "rte-thread-count", children: [
|
|
9385
|
+
thread.comments.length,
|
|
9386
|
+
" comment",
|
|
9387
|
+
thread.comments.length !== 1 ? "s" : ""
|
|
9388
|
+
] })
|
|
9389
|
+
] })
|
|
9390
|
+
] }),
|
|
9391
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-thread-comments", children: thread.comments.map((comment, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9392
|
+
CommentItem,
|
|
9393
|
+
{
|
|
9394
|
+
comment,
|
|
9395
|
+
threadId: thread.id,
|
|
9396
|
+
isFirst: index === 0
|
|
9397
|
+
},
|
|
9398
|
+
comment.id
|
|
9399
|
+
)) }),
|
|
9400
|
+
thread.status === "open" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-thread-reply", children: showReply ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
9401
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9402
|
+
"textarea",
|
|
9403
|
+
{
|
|
9404
|
+
value: replyContent,
|
|
9405
|
+
onChange: (e) => setReplyContent(e.target.value),
|
|
9406
|
+
placeholder: "Write a reply...",
|
|
9407
|
+
className: "rte-thread-reply-input"
|
|
9408
|
+
}
|
|
9409
|
+
),
|
|
9410
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-thread-reply-actions", children: [
|
|
9411
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: handleReply, className: "rte-btn-primary", disabled: !replyContent.trim(), children: "Reply" }),
|
|
9412
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: () => setShowReply(false), className: "rte-btn-secondary", children: "Cancel" })
|
|
9413
|
+
] })
|
|
9414
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: () => setShowReply(true), className: "rte-thread-reply-btn", children: "Reply" }) }),
|
|
9415
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-thread-actions", children: [
|
|
9416
|
+
canResolve && thread.status === "open" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: () => resolveThread(thread.id), className: "rte-btn-resolve", children: "\u2713 Resolve" }),
|
|
9417
|
+
thread.status === "resolved" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: () => reopenThread(thread.id), className: "rte-btn-reopen", children: "Reopen" }),
|
|
9418
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9419
|
+
"button",
|
|
9420
|
+
{
|
|
9421
|
+
onClick: (e) => {
|
|
9422
|
+
e.stopPropagation();
|
|
9423
|
+
deleteThread(thread.id);
|
|
9424
|
+
},
|
|
9425
|
+
className: "rte-btn-delete-thread",
|
|
9426
|
+
children: "Delete"
|
|
9427
|
+
}
|
|
9428
|
+
)
|
|
9429
|
+
] })
|
|
9430
|
+
]
|
|
9431
|
+
}
|
|
9432
|
+
);
|
|
9433
|
+
}
|
|
9434
|
+
function CommentsPanel({ position = "right", className = "" }) {
|
|
9435
|
+
const comments = useCommentsOptional();
|
|
9436
|
+
if (!comments?.isEnabled || !comments.state.isPanelOpen) {
|
|
9437
|
+
return null;
|
|
9438
|
+
}
|
|
9439
|
+
const { state, togglePanel, setFilter, getFilteredThreads } = comments;
|
|
9440
|
+
const filteredThreads = getFilteredThreads();
|
|
9441
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `rte-comments-panel rte-comments-panel-${position} ${className}`, children: [
|
|
9442
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rte-comments-panel-header", children: [
|
|
9443
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h3", { children: "Comments" }),
|
|
9444
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { onClick: () => togglePanel(false), className: "rte-comments-close-btn", children: "\xD7" })
|
|
9445
|
+
] }),
|
|
9446
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-comments-filters", children: ["all", "open", "resolved"].map((filter) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
9447
|
+
"button",
|
|
9448
|
+
{
|
|
9449
|
+
className: `rte-comments-filter ${state.filter === filter ? "active" : ""}`,
|
|
9450
|
+
onClick: () => setFilter(filter),
|
|
9451
|
+
children: [
|
|
9452
|
+
filter.charAt(0).toUpperCase() + filter.slice(1),
|
|
9453
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "rte-comments-filter-count", children: filter === "all" ? state.threads.length : state.threads.filter((t) => t.status === filter).length })
|
|
9454
|
+
]
|
|
9455
|
+
},
|
|
9456
|
+
filter
|
|
9457
|
+
)) }),
|
|
9458
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-comments-list", children: filteredThreads.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rte-comments-empty", children: state.filter === "all" ? "No comments yet. Select text and add a comment." : `No ${state.filter} comments.` }) : filteredThreads.map((thread) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ThreadItem, { thread }, thread.id)) })
|
|
9459
|
+
] });
|
|
9460
|
+
}
|
|
9461
|
+
|
|
9462
|
+
// src/history/VersionHistoryContext.tsx
|
|
9463
|
+
var import_react15 = require("react");
|
|
9464
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
9465
|
+
var initialState3 = {
|
|
9466
|
+
versions: [],
|
|
9467
|
+
viewingVersionId: null,
|
|
9468
|
+
isPanelOpen: false,
|
|
9469
|
+
isComparing: false,
|
|
9470
|
+
compareFromId: null,
|
|
9471
|
+
compareToId: null,
|
|
9472
|
+
autoSaveEnabled: true,
|
|
9473
|
+
autoSaveInterval: 6e4
|
|
9474
|
+
};
|
|
9475
|
+
function generateId2() {
|
|
9476
|
+
return `v-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
9477
|
+
}
|
|
9478
|
+
function versionHistoryReducer(state, action) {
|
|
9479
|
+
switch (action.type) {
|
|
9480
|
+
case "SET_VERSIONS":
|
|
9481
|
+
return { ...state, versions: action.versions };
|
|
9482
|
+
case "ADD_VERSION":
|
|
9483
|
+
return {
|
|
9484
|
+
...state,
|
|
9485
|
+
versions: [action.version, ...state.versions]
|
|
9486
|
+
};
|
|
9487
|
+
case "DELETE_VERSION":
|
|
9488
|
+
return {
|
|
9489
|
+
...state,
|
|
9490
|
+
versions: state.versions.filter((v) => v.id !== action.versionId),
|
|
9491
|
+
viewingVersionId: state.viewingVersionId === action.versionId ? null : state.viewingVersionId
|
|
9492
|
+
};
|
|
9493
|
+
case "PIN_VERSION":
|
|
9494
|
+
return {
|
|
9495
|
+
...state,
|
|
9496
|
+
versions: state.versions.map(
|
|
9497
|
+
(v) => v.id === action.versionId ? { ...v, isPinned: true } : v
|
|
9498
|
+
)
|
|
9499
|
+
};
|
|
9500
|
+
case "UNPIN_VERSION":
|
|
9501
|
+
return {
|
|
9502
|
+
...state,
|
|
9503
|
+
versions: state.versions.map(
|
|
9504
|
+
(v) => v.id === action.versionId ? { ...v, isPinned: false } : v
|
|
9505
|
+
)
|
|
9506
|
+
};
|
|
9507
|
+
case "RENAME_VERSION":
|
|
9508
|
+
return {
|
|
9509
|
+
...state,
|
|
9510
|
+
versions: state.versions.map(
|
|
9511
|
+
(v) => v.id === action.versionId ? { ...v, title: action.title } : v
|
|
9512
|
+
)
|
|
9513
|
+
};
|
|
9514
|
+
case "SET_VIEWING_VERSION":
|
|
9515
|
+
return { ...state, viewingVersionId: action.versionId };
|
|
9516
|
+
case "TOGGLE_PANEL":
|
|
9517
|
+
return { ...state, isPanelOpen: action.isOpen ?? !state.isPanelOpen };
|
|
9518
|
+
case "START_COMPARE":
|
|
9519
|
+
return {
|
|
9520
|
+
...state,
|
|
9521
|
+
isComparing: true,
|
|
9522
|
+
compareFromId: action.fromId,
|
|
9523
|
+
compareToId: action.toId
|
|
9524
|
+
};
|
|
9525
|
+
case "STOP_COMPARE":
|
|
9526
|
+
return {
|
|
9527
|
+
...state,
|
|
9528
|
+
isComparing: false,
|
|
9529
|
+
compareFromId: null,
|
|
9530
|
+
compareToId: null
|
|
9531
|
+
};
|
|
9532
|
+
case "SET_AUTO_SAVE":
|
|
9533
|
+
return { ...state, autoSaveEnabled: action.enabled };
|
|
9534
|
+
case "SET_AUTO_SAVE_INTERVAL":
|
|
9535
|
+
return { ...state, autoSaveInterval: action.interval };
|
|
9536
|
+
default:
|
|
9537
|
+
return state;
|
|
9538
|
+
}
|
|
9539
|
+
}
|
|
9540
|
+
function countWords(text) {
|
|
9541
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
9542
|
+
}
|
|
9543
|
+
function computeDiff(oldText, newText) {
|
|
9544
|
+
const changes = [];
|
|
9545
|
+
const oldLines = oldText.split("\n");
|
|
9546
|
+
const newLines = newText.split("\n");
|
|
9547
|
+
let i = 0;
|
|
9548
|
+
let j = 0;
|
|
9549
|
+
while (i < oldLines.length || j < newLines.length) {
|
|
9550
|
+
if (i >= oldLines.length) {
|
|
9551
|
+
changes.push({
|
|
9552
|
+
type: "addition",
|
|
9553
|
+
content: newLines[j],
|
|
9554
|
+
position: j
|
|
9555
|
+
});
|
|
9556
|
+
j++;
|
|
9557
|
+
} else if (j >= newLines.length) {
|
|
9558
|
+
changes.push({
|
|
9559
|
+
type: "deletion",
|
|
9560
|
+
content: oldLines[i],
|
|
9561
|
+
position: i
|
|
9562
|
+
});
|
|
9563
|
+
i++;
|
|
9564
|
+
} else if (oldLines[i] === newLines[j]) {
|
|
9565
|
+
i++;
|
|
9566
|
+
j++;
|
|
9567
|
+
} else {
|
|
9568
|
+
const oldLineInNew = newLines.indexOf(oldLines[i], j);
|
|
9569
|
+
const newLineInOld = oldLines.indexOf(newLines[j], i);
|
|
9570
|
+
if (oldLineInNew === -1 && newLineInOld === -1) {
|
|
9571
|
+
changes.push({
|
|
9572
|
+
type: "modification",
|
|
9573
|
+
content: `${oldLines[i]} -> ${newLines[j]}`,
|
|
9574
|
+
position: i
|
|
9575
|
+
});
|
|
9576
|
+
i++;
|
|
9577
|
+
j++;
|
|
9578
|
+
} else if (oldLineInNew === -1) {
|
|
9579
|
+
changes.push({
|
|
9580
|
+
type: "deletion",
|
|
9581
|
+
content: oldLines[i],
|
|
9582
|
+
position: i
|
|
9583
|
+
});
|
|
9584
|
+
i++;
|
|
9585
|
+
} else {
|
|
9586
|
+
changes.push({
|
|
9587
|
+
type: "addition",
|
|
9588
|
+
content: newLines[j],
|
|
9589
|
+
position: j
|
|
9590
|
+
});
|
|
9591
|
+
j++;
|
|
9592
|
+
}
|
|
9593
|
+
}
|
|
9594
|
+
}
|
|
9595
|
+
return changes;
|
|
9596
|
+
}
|
|
9597
|
+
var VersionHistoryContext = (0, import_react15.createContext)(null);
|
|
9598
|
+
function VersionHistoryProvider({
|
|
9599
|
+
children,
|
|
9600
|
+
config,
|
|
9601
|
+
initialVersions = [],
|
|
9602
|
+
onVersionsChange,
|
|
9603
|
+
getCurrentContent
|
|
9604
|
+
}) {
|
|
9605
|
+
const [state, dispatch] = (0, import_react15.useReducer)(versionHistoryReducer, {
|
|
9606
|
+
...initialState3,
|
|
9607
|
+
versions: initialVersions,
|
|
9608
|
+
autoSaveEnabled: config?.autoSave ?? true,
|
|
9609
|
+
autoSaveInterval: config?.autoSaveInterval ?? 6e4
|
|
9610
|
+
});
|
|
9611
|
+
const subscribersRef = (0, import_react15.useRef)(/* @__PURE__ */ new Set());
|
|
9612
|
+
const autoSaveTimerRef = (0, import_react15.useRef)(null);
|
|
9613
|
+
const lastContentRef = (0, import_react15.useRef)("");
|
|
9614
|
+
const versionNumberRef = (0, import_react15.useRef)(initialVersions.length);
|
|
9615
|
+
(0, import_react15.useEffect)(() => {
|
|
9616
|
+
if (config?.onLoad) {
|
|
9617
|
+
config.onLoad().then((versions) => {
|
|
9618
|
+
dispatch({ type: "SET_VERSIONS", versions });
|
|
9619
|
+
versionNumberRef.current = versions.length;
|
|
9620
|
+
});
|
|
9621
|
+
}
|
|
9622
|
+
}, [config]);
|
|
9623
|
+
(0, import_react15.useEffect)(() => {
|
|
9624
|
+
onVersionsChange?.(state.versions);
|
|
9625
|
+
if (config?.onSave) {
|
|
9626
|
+
config.onSave(state.versions);
|
|
9627
|
+
}
|
|
9628
|
+
}, [state.versions, onVersionsChange, config]);
|
|
9629
|
+
(0, import_react15.useEffect)(() => {
|
|
9630
|
+
if (!state.autoSaveEnabled || !config || !getCurrentContent) return;
|
|
9631
|
+
autoSaveTimerRef.current = setInterval(() => {
|
|
9632
|
+
const { html, json } = getCurrentContent();
|
|
9633
|
+
if (html !== lastContentRef.current) {
|
|
9634
|
+
lastContentRef.current = html;
|
|
9635
|
+
createVersion(html, json, { isAutoSave: true });
|
|
9636
|
+
}
|
|
9637
|
+
}, state.autoSaveInterval);
|
|
9638
|
+
return () => {
|
|
9639
|
+
if (autoSaveTimerRef.current) {
|
|
9640
|
+
clearInterval(autoSaveTimerRef.current);
|
|
9641
|
+
}
|
|
9642
|
+
};
|
|
9643
|
+
}, [state.autoSaveEnabled, state.autoSaveInterval, config, getCurrentContent]);
|
|
9644
|
+
const emitEvent = (0, import_react15.useCallback)((event) => {
|
|
9645
|
+
subscribersRef.current.forEach((callback) => callback(event));
|
|
9646
|
+
}, []);
|
|
9647
|
+
const createVersion = (0, import_react15.useCallback)((content, jsonContent, options) => {
|
|
9648
|
+
if (!config?.currentUser) return null;
|
|
9649
|
+
const maxVersions = config.maxVersions ?? 100;
|
|
9650
|
+
let versions = state.versions;
|
|
9651
|
+
if (versions.length >= maxVersions) {
|
|
9652
|
+
const oldestUnpinned = [...versions].reverse().find((v) => !v.isPinned);
|
|
9653
|
+
if (oldestUnpinned) {
|
|
9654
|
+
versions = versions.filter((v) => v.id !== oldestUnpinned.id);
|
|
9655
|
+
}
|
|
9656
|
+
}
|
|
9657
|
+
versionNumberRef.current++;
|
|
9658
|
+
const textContent = content.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
9659
|
+
const version = {
|
|
9660
|
+
id: generateId2(),
|
|
9661
|
+
number: versionNumberRef.current,
|
|
9662
|
+
title: options?.title,
|
|
9663
|
+
content,
|
|
9664
|
+
jsonContent,
|
|
9665
|
+
textContent,
|
|
9666
|
+
author: config.currentUser,
|
|
9667
|
+
createdAt: Date.now(),
|
|
9668
|
+
wordCount: countWords(textContent),
|
|
9669
|
+
characterCount: textContent.length,
|
|
9670
|
+
isAutoSave: options?.isAutoSave ?? false,
|
|
9671
|
+
isPinned: false
|
|
9672
|
+
};
|
|
9673
|
+
dispatch({ type: "ADD_VERSION", version });
|
|
9674
|
+
emitEvent({ type: "version-created", version });
|
|
9675
|
+
return version;
|
|
9676
|
+
}, [config, state.versions, emitEvent]);
|
|
9677
|
+
const deleteVersion = (0, import_react15.useCallback)((versionId) => {
|
|
9678
|
+
dispatch({ type: "DELETE_VERSION", versionId });
|
|
9679
|
+
emitEvent({ type: "version-deleted", versionId });
|
|
9680
|
+
}, [emitEvent]);
|
|
9681
|
+
const restoreVersion = (0, import_react15.useCallback)((versionId) => {
|
|
9682
|
+
const version = state.versions.find((v) => v.id === versionId);
|
|
9683
|
+
if (!version) return null;
|
|
9684
|
+
config?.onRestore?.(version);
|
|
9685
|
+
emitEvent({ type: "version-restored", versionId });
|
|
9686
|
+
return version.content;
|
|
9687
|
+
}, [state.versions, config, emitEvent]);
|
|
9688
|
+
const pinVersion = (0, import_react15.useCallback)((versionId) => {
|
|
9689
|
+
dispatch({ type: "PIN_VERSION", versionId });
|
|
9690
|
+
emitEvent({ type: "version-pinned", versionId });
|
|
9691
|
+
}, [emitEvent]);
|
|
9692
|
+
const unpinVersion = (0, import_react15.useCallback)((versionId) => {
|
|
9693
|
+
dispatch({ type: "UNPIN_VERSION", versionId });
|
|
9694
|
+
emitEvent({ type: "version-unpinned", versionId });
|
|
9695
|
+
}, [emitEvent]);
|
|
9696
|
+
const renameVersion = (0, import_react15.useCallback)((versionId, title) => {
|
|
9697
|
+
dispatch({ type: "RENAME_VERSION", versionId, title });
|
|
9698
|
+
emitEvent({ type: "version-renamed", versionId, title });
|
|
9699
|
+
}, [emitEvent]);
|
|
9700
|
+
const viewVersion = (0, import_react15.useCallback)((versionId) => {
|
|
9701
|
+
dispatch({ type: "SET_VIEWING_VERSION", versionId });
|
|
9702
|
+
}, []);
|
|
9703
|
+
const getVersionContent = (0, import_react15.useCallback)((versionId) => {
|
|
9704
|
+
const version = state.versions.find((v) => v.id === versionId);
|
|
9705
|
+
return version?.content ?? null;
|
|
9706
|
+
}, [state.versions]);
|
|
9707
|
+
const compareVersions = (0, import_react15.useCallback)((fromId, toId) => {
|
|
9708
|
+
const fromVersion = state.versions.find((v) => v.id === fromId);
|
|
9709
|
+
const toVersion = state.versions.find((v) => v.id === toId);
|
|
9710
|
+
if (!fromVersion || !toVersion) return null;
|
|
9711
|
+
const changes = computeDiff(fromVersion.textContent, toVersion.textContent);
|
|
9712
|
+
return {
|
|
9713
|
+
fromVersionId: fromId,
|
|
9714
|
+
toVersionId: toId,
|
|
9715
|
+
changes,
|
|
9716
|
+
stats: {
|
|
9717
|
+
additions: changes.filter((c) => c.type === "addition").length,
|
|
9718
|
+
deletions: changes.filter((c) => c.type === "deletion").length,
|
|
9719
|
+
modifications: changes.filter((c) => c.type === "modification").length
|
|
9720
|
+
}
|
|
9721
|
+
};
|
|
9722
|
+
}, [state.versions]);
|
|
9723
|
+
const startCompare = (0, import_react15.useCallback)((fromId, toId) => {
|
|
9724
|
+
dispatch({ type: "START_COMPARE", fromId, toId });
|
|
9725
|
+
}, []);
|
|
9726
|
+
const stopCompare = (0, import_react15.useCallback)(() => {
|
|
9727
|
+
dispatch({ type: "STOP_COMPARE" });
|
|
9728
|
+
}, []);
|
|
9729
|
+
const togglePanel = (0, import_react15.useCallback)((isOpen) => {
|
|
9730
|
+
dispatch({ type: "TOGGLE_PANEL", isOpen });
|
|
9731
|
+
}, []);
|
|
9732
|
+
const setAutoSave = (0, import_react15.useCallback)((enabled) => {
|
|
9733
|
+
dispatch({ type: "SET_AUTO_SAVE", enabled });
|
|
9734
|
+
emitEvent({ type: "auto-save-toggled", enabled });
|
|
9735
|
+
}, [emitEvent]);
|
|
9736
|
+
const getVersion = (0, import_react15.useCallback)((versionId) => {
|
|
9737
|
+
return state.versions.find((v) => v.id === versionId);
|
|
9738
|
+
}, [state.versions]);
|
|
9739
|
+
const getLatestVersion = (0, import_react15.useCallback)(() => {
|
|
9740
|
+
return state.versions[0];
|
|
9741
|
+
}, [state.versions]);
|
|
9742
|
+
const getPinnedVersions = (0, import_react15.useCallback)(() => {
|
|
9743
|
+
return state.versions.filter((v) => v.isPinned);
|
|
9744
|
+
}, [state.versions]);
|
|
9745
|
+
const subscribe = (0, import_react15.useCallback)((callback) => {
|
|
9746
|
+
subscribersRef.current.add(callback);
|
|
9747
|
+
return () => {
|
|
9748
|
+
subscribersRef.current.delete(callback);
|
|
9749
|
+
};
|
|
9750
|
+
}, []);
|
|
9751
|
+
const value = {
|
|
9752
|
+
state,
|
|
9753
|
+
config: config ?? null,
|
|
9754
|
+
createVersion,
|
|
9755
|
+
deleteVersion,
|
|
9756
|
+
restoreVersion,
|
|
9757
|
+
pinVersion,
|
|
9758
|
+
unpinVersion,
|
|
9759
|
+
renameVersion,
|
|
9760
|
+
viewVersion,
|
|
9761
|
+
getVersionContent,
|
|
9762
|
+
compareVersions,
|
|
9763
|
+
startCompare,
|
|
9764
|
+
stopCompare,
|
|
9765
|
+
togglePanel,
|
|
9766
|
+
setAutoSave,
|
|
9767
|
+
getVersion,
|
|
9768
|
+
getLatestVersion,
|
|
9769
|
+
getPinnedVersions,
|
|
9770
|
+
subscribe,
|
|
9771
|
+
isEnabled: !!config
|
|
9772
|
+
};
|
|
9773
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(VersionHistoryContext.Provider, { value, children });
|
|
9774
|
+
}
|
|
9775
|
+
function useVersionHistory() {
|
|
9776
|
+
const context = (0, import_react15.useContext)(VersionHistoryContext);
|
|
9777
|
+
if (!context) {
|
|
9778
|
+
throw new Error("useVersionHistory must be used within a VersionHistoryProvider");
|
|
9779
|
+
}
|
|
9780
|
+
return context;
|
|
9781
|
+
}
|
|
9782
|
+
function useVersionHistoryOptional() {
|
|
9783
|
+
return (0, import_react15.useContext)(VersionHistoryContext);
|
|
9784
|
+
}
|
|
9785
|
+
|
|
9786
|
+
// src/history/VersionHistoryPanel.tsx
|
|
9787
|
+
var import_react16 = require("react");
|
|
9788
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
9789
|
+
function formatDate(timestamp) {
|
|
9790
|
+
const date = new Date(timestamp);
|
|
9791
|
+
const now = /* @__PURE__ */ new Date();
|
|
9792
|
+
const isToday = date.toDateString() === now.toDateString();
|
|
9793
|
+
if (isToday) {
|
|
9794
|
+
return `Today at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
|
|
9795
|
+
}
|
|
9796
|
+
const yesterday = new Date(now);
|
|
9797
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
9798
|
+
if (date.toDateString() === yesterday.toDateString()) {
|
|
9799
|
+
return `Yesterday at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
|
|
9800
|
+
}
|
|
9801
|
+
return date.toLocaleDateString([], {
|
|
9802
|
+
month: "short",
|
|
9803
|
+
day: "numeric",
|
|
9804
|
+
hour: "2-digit",
|
|
9805
|
+
minute: "2-digit"
|
|
9806
|
+
});
|
|
9807
|
+
}
|
|
9808
|
+
function VersionItem({
|
|
9809
|
+
version,
|
|
9810
|
+
isViewing,
|
|
9811
|
+
isCompareFrom,
|
|
9812
|
+
isCompareTo,
|
|
9813
|
+
onView,
|
|
9814
|
+
onRestore,
|
|
9815
|
+
onPin,
|
|
9816
|
+
onRename,
|
|
9817
|
+
onDelete,
|
|
9818
|
+
onCompareSelect
|
|
9819
|
+
}) {
|
|
9820
|
+
const [isRenaming, setIsRenaming] = (0, import_react16.useState)(false);
|
|
9821
|
+
const [newTitle, setNewTitle] = (0, import_react16.useState)(version.title || "");
|
|
9822
|
+
const handleRename = () => {
|
|
9823
|
+
onRename(newTitle);
|
|
9824
|
+
setIsRenaming(false);
|
|
9825
|
+
};
|
|
9826
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
9827
|
+
"div",
|
|
9828
|
+
{
|
|
9829
|
+
className: `rte-version-item ${isViewing ? "rte-version-viewing" : ""} ${isCompareFrom ? "rte-version-compare-from" : ""} ${isCompareTo ? "rte-version-compare-to" : ""}`,
|
|
9830
|
+
children: [
|
|
9831
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-header", onClick: onView, children: [
|
|
9832
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-info", children: [
|
|
9833
|
+
isRenaming ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
9834
|
+
"input",
|
|
9835
|
+
{
|
|
9836
|
+
type: "text",
|
|
9837
|
+
value: newTitle,
|
|
9838
|
+
onChange: (e) => setNewTitle(e.target.value),
|
|
9839
|
+
onBlur: handleRename,
|
|
9840
|
+
onKeyDown: (e) => e.key === "Enter" && handleRename(),
|
|
9841
|
+
className: "rte-version-rename-input",
|
|
9842
|
+
onClick: (e) => e.stopPropagation(),
|
|
9843
|
+
autoFocus: true
|
|
9844
|
+
}
|
|
9845
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "rte-version-title", children: [
|
|
9846
|
+
version.title || `Version ${version.number}`,
|
|
9847
|
+
version.isPinned && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-version-pin-icon", children: "\u{1F4CC}" }),
|
|
9848
|
+
version.isAutoSave && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-version-auto-badge", children: "Auto" })
|
|
9849
|
+
] }),
|
|
9850
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-version-time", children: formatDate(version.createdAt) })
|
|
9851
|
+
] }),
|
|
9852
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-author", children: [
|
|
9853
|
+
version.author.avatar ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("img", { src: version.author.avatar, alt: version.author.name, className: "rte-version-avatar" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "rte-version-avatar-placeholder", children: version.author.name.charAt(0).toUpperCase() }),
|
|
9854
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: version.author.name })
|
|
9855
|
+
] })
|
|
9856
|
+
] }),
|
|
9857
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-stats", children: [
|
|
9858
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { children: [
|
|
9859
|
+
version.wordCount,
|
|
9860
|
+
" words"
|
|
9861
|
+
] }),
|
|
9862
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { children: [
|
|
9863
|
+
version.characterCount,
|
|
9864
|
+
" chars"
|
|
9865
|
+
] })
|
|
9866
|
+
] }),
|
|
9867
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-actions", children: [
|
|
9868
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: onRestore, className: "rte-version-btn", title: "Restore this version", children: "\u21BA Restore" }),
|
|
9869
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: onCompareSelect, className: "rte-version-btn", title: "Compare with another version", children: "\u21C4 Compare" }),
|
|
9870
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: onPin, className: "rte-version-btn", title: version.isPinned ? "Unpin" : "Pin", children: version.isPinned ? "\u{1F4CC} Unpin" : "\u{1F4CC} Pin" }),
|
|
9871
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
9872
|
+
"button",
|
|
9873
|
+
{
|
|
9874
|
+
onClick: () => setIsRenaming(true),
|
|
9875
|
+
className: "rte-version-btn",
|
|
9876
|
+
title: "Rename version",
|
|
9877
|
+
children: "\u270F\uFE0F"
|
|
9878
|
+
}
|
|
9879
|
+
),
|
|
9880
|
+
!version.isPinned && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: onDelete, className: "rte-version-btn rte-version-btn-delete", title: "Delete", children: "\u{1F5D1}\uFE0F" })
|
|
9881
|
+
] })
|
|
9882
|
+
]
|
|
9883
|
+
}
|
|
9884
|
+
);
|
|
9885
|
+
}
|
|
9886
|
+
function ComparisonView({
|
|
9887
|
+
fromVersion,
|
|
9888
|
+
toVersion,
|
|
9889
|
+
onClose
|
|
9890
|
+
}) {
|
|
9891
|
+
const versionHistory = useVersionHistoryOptional();
|
|
9892
|
+
if (!versionHistory) return null;
|
|
9893
|
+
const comparison = versionHistory.compareVersions(fromVersion.id, toVersion.id);
|
|
9894
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-comparison", children: [
|
|
9895
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-comparison-header", children: [
|
|
9896
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h4", { children: "Comparing Versions" }),
|
|
9897
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: onClose, className: "rte-version-comparison-close", children: "\xD7" })
|
|
9898
|
+
] }),
|
|
9899
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-comparison-info", children: [
|
|
9900
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-comparison-from", children: [
|
|
9901
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-version-comparison-label", children: "From:" }),
|
|
9902
|
+
fromVersion.title || `Version ${fromVersion.number}`
|
|
9903
|
+
] }),
|
|
9904
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-version-comparison-arrow", children: "\u2192" }),
|
|
9905
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-comparison-to", children: [
|
|
9906
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-version-comparison-label", children: "To:" }),
|
|
9907
|
+
toVersion.title || `Version ${toVersion.number}`
|
|
9908
|
+
] })
|
|
9909
|
+
] }),
|
|
9910
|
+
comparison && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-comparison-stats", children: [
|
|
9911
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "rte-comparison-stat rte-comparison-additions", children: [
|
|
9912
|
+
"+",
|
|
9913
|
+
comparison.stats.additions,
|
|
9914
|
+
" additions"
|
|
9915
|
+
] }),
|
|
9916
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "rte-comparison-stat rte-comparison-deletions", children: [
|
|
9917
|
+
"-",
|
|
9918
|
+
comparison.stats.deletions,
|
|
9919
|
+
" deletions"
|
|
9920
|
+
] }),
|
|
9921
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "rte-comparison-stat rte-comparison-modifications", children: [
|
|
9922
|
+
"~",
|
|
9923
|
+
comparison.stats.modifications,
|
|
9924
|
+
" modifications"
|
|
9925
|
+
] })
|
|
9926
|
+
] }),
|
|
9927
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "rte-version-comparison-diff", children: comparison?.changes.map((change, index) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `rte-diff-line rte-diff-${change.type}`, children: [
|
|
9928
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-diff-indicator", children: change.type === "addition" ? "+" : change.type === "deletion" ? "-" : "~" }),
|
|
9929
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rte-diff-content", children: change.content })
|
|
9930
|
+
] }, index)) })
|
|
9931
|
+
] });
|
|
9932
|
+
}
|
|
9933
|
+
function VersionHistoryPanel({ position = "right", className = "" }) {
|
|
9934
|
+
const versionHistory = useVersionHistoryOptional();
|
|
9935
|
+
const [compareFromId, setCompareFromId] = (0, import_react16.useState)(null);
|
|
9936
|
+
if (!versionHistory?.isEnabled || !versionHistory.state.isPanelOpen) {
|
|
9937
|
+
return null;
|
|
9938
|
+
}
|
|
9939
|
+
const {
|
|
9940
|
+
state,
|
|
9941
|
+
togglePanel,
|
|
9942
|
+
viewVersion,
|
|
9943
|
+
restoreVersion,
|
|
9944
|
+
pinVersion,
|
|
9945
|
+
unpinVersion,
|
|
9946
|
+
renameVersion,
|
|
9947
|
+
deleteVersion,
|
|
9948
|
+
setAutoSave,
|
|
9949
|
+
getVersion,
|
|
9950
|
+
createVersion,
|
|
9951
|
+
stopCompare
|
|
9952
|
+
} = versionHistory;
|
|
9953
|
+
const handleRestore = (versionId) => {
|
|
9954
|
+
const content = restoreVersion(versionId);
|
|
9955
|
+
if (content) {
|
|
9956
|
+
viewVersion(null);
|
|
9957
|
+
}
|
|
9958
|
+
};
|
|
9959
|
+
const handleCompareSelect = (versionId) => {
|
|
9960
|
+
if (!compareFromId) {
|
|
9961
|
+
setCompareFromId(versionId);
|
|
9962
|
+
} else {
|
|
9963
|
+
versionHistory.startCompare(compareFromId, versionId);
|
|
9964
|
+
setCompareFromId(null);
|
|
9965
|
+
}
|
|
9966
|
+
};
|
|
9967
|
+
const handleCancelCompare = () => {
|
|
9968
|
+
setCompareFromId(null);
|
|
9969
|
+
stopCompare();
|
|
9970
|
+
};
|
|
9971
|
+
const compareFromVersion = state.compareFromId ? getVersion(state.compareFromId) : void 0;
|
|
9972
|
+
const compareToVersion = state.compareToId ? getVersion(state.compareToId) : void 0;
|
|
9973
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `rte-version-panel rte-version-panel-${position} ${className}`, children: [
|
|
9974
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-panel-header", children: [
|
|
9975
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { children: "Version History" }),
|
|
9976
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: () => togglePanel(false), className: "rte-version-close-btn", children: "\xD7" })
|
|
9977
|
+
] }),
|
|
9978
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-autosave", children: [
|
|
9979
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "rte-version-autosave-label", children: [
|
|
9980
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
9981
|
+
"input",
|
|
9982
|
+
{
|
|
9983
|
+
type: "checkbox",
|
|
9984
|
+
checked: state.autoSaveEnabled,
|
|
9985
|
+
onChange: (e) => setAutoSave(e.target.checked)
|
|
9986
|
+
}
|
|
9987
|
+
),
|
|
9988
|
+
"Auto-save versions"
|
|
9989
|
+
] }),
|
|
9990
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
9991
|
+
"button",
|
|
9992
|
+
{
|
|
9993
|
+
onClick: () => createVersion("", void 0, { isAutoSave: false }),
|
|
9994
|
+
className: "rte-version-save-btn",
|
|
9995
|
+
children: "Save Now"
|
|
9996
|
+
}
|
|
9997
|
+
)
|
|
9998
|
+
] }),
|
|
9999
|
+
compareFromId && !state.isComparing && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-compare-mode", children: [
|
|
10000
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Select another version to compare" }),
|
|
10001
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: handleCancelCompare, children: "Cancel" })
|
|
10002
|
+
] }),
|
|
10003
|
+
state.isComparing && compareFromVersion && compareToVersion && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
10004
|
+
ComparisonView,
|
|
10005
|
+
{
|
|
10006
|
+
fromVersion: compareFromVersion,
|
|
10007
|
+
toVersion: compareToVersion,
|
|
10008
|
+
onClose: handleCancelCompare
|
|
10009
|
+
}
|
|
10010
|
+
),
|
|
10011
|
+
!state.isComparing && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "rte-version-list", children: state.versions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "rte-version-empty", children: "No versions saved yet. Changes will be auto-saved periodically." }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
10012
|
+
state.versions.some((v) => v.isPinned) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-section", children: [
|
|
10013
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "rte-version-section-title", children: "\u{1F4CC} Pinned" }),
|
|
10014
|
+
state.versions.filter((v) => v.isPinned).map((version) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
10015
|
+
VersionItem,
|
|
10016
|
+
{
|
|
10017
|
+
version,
|
|
10018
|
+
isViewing: state.viewingVersionId === version.id,
|
|
10019
|
+
isCompareFrom: compareFromId === version.id,
|
|
10020
|
+
isCompareTo: false,
|
|
10021
|
+
onView: () => viewVersion(version.id),
|
|
10022
|
+
onRestore: () => handleRestore(version.id),
|
|
10023
|
+
onPin: () => unpinVersion(version.id),
|
|
10024
|
+
onRename: (title) => renameVersion(version.id, title),
|
|
10025
|
+
onDelete: () => deleteVersion(version.id),
|
|
10026
|
+
onCompareSelect: () => handleCompareSelect(version.id)
|
|
10027
|
+
},
|
|
10028
|
+
version.id
|
|
10029
|
+
))
|
|
10030
|
+
] }),
|
|
10031
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-section", children: [
|
|
10032
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "rte-version-section-title", children: "Recent" }),
|
|
10033
|
+
state.versions.filter((v) => !v.isPinned).map((version) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
10034
|
+
VersionItem,
|
|
10035
|
+
{
|
|
10036
|
+
version,
|
|
10037
|
+
isViewing: state.viewingVersionId === version.id,
|
|
10038
|
+
isCompareFrom: compareFromId === version.id,
|
|
10039
|
+
isCompareTo: false,
|
|
10040
|
+
onView: () => viewVersion(version.id),
|
|
10041
|
+
onRestore: () => handleRestore(version.id),
|
|
10042
|
+
onPin: () => pinVersion(version.id),
|
|
10043
|
+
onRename: (title) => renameVersion(version.id, title),
|
|
10044
|
+
onDelete: () => deleteVersion(version.id),
|
|
10045
|
+
onCompareSelect: () => handleCompareSelect(version.id)
|
|
10046
|
+
},
|
|
10047
|
+
version.id
|
|
10048
|
+
))
|
|
10049
|
+
] })
|
|
10050
|
+
] }) }),
|
|
10051
|
+
state.viewingVersionId && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "rte-version-viewing-indicator", children: [
|
|
10052
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { children: [
|
|
10053
|
+
"Viewing: ",
|
|
10054
|
+
getVersion(state.viewingVersionId)?.title || `Version ${getVersion(state.viewingVersionId)?.number}`
|
|
10055
|
+
] }),
|
|
10056
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { onClick: () => viewVersion(null), className: "rte-version-back-btn", children: "\u2190 Back to current" })
|
|
10057
|
+
] })
|
|
10058
|
+
] });
|
|
10059
|
+
}
|
|
8678
10060
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8679
10061
|
0 && (module.exports = {
|
|
10062
|
+
CollaborationProvider,
|
|
10063
|
+
CommentsPanel,
|
|
10064
|
+
CommentsProvider,
|
|
8680
10065
|
DEFAULT_FEATURES,
|
|
10066
|
+
DEFAULT_REACTION_EMOJIS,
|
|
8681
10067
|
EMOJI_CATEGORIES,
|
|
8682
10068
|
EditorRegistry,
|
|
8683
10069
|
Emoji,
|
|
@@ -8685,12 +10071,15 @@ init_Indent();
|
|
|
8685
10071
|
Fullscreen,
|
|
8686
10072
|
Indent,
|
|
8687
10073
|
LineHeight,
|
|
10074
|
+
PresenceIndicator,
|
|
8688
10075
|
Print,
|
|
8689
10076
|
RichTextEditor,
|
|
8690
10077
|
TipTapAdapter,
|
|
8691
10078
|
TipTapEditorComponent,
|
|
8692
10079
|
TipTapToolbar,
|
|
8693
10080
|
UnifiedEditor,
|
|
10081
|
+
VersionHistoryPanel,
|
|
10082
|
+
VersionHistoryProvider,
|
|
8694
10083
|
Video,
|
|
8695
10084
|
blogToolbar,
|
|
8696
10085
|
codeToolbar,
|
|
@@ -8712,6 +10101,12 @@ init_Indent();
|
|
|
8712
10101
|
registerAdapter,
|
|
8713
10102
|
simpleToolbar,
|
|
8714
10103
|
toolbarPresets,
|
|
8715
|
-
unregisterAdapter
|
|
10104
|
+
unregisterAdapter,
|
|
10105
|
+
useCollaboration,
|
|
10106
|
+
useCollaborationOptional,
|
|
10107
|
+
useComments,
|
|
10108
|
+
useCommentsOptional,
|
|
10109
|
+
useVersionHistory,
|
|
10110
|
+
useVersionHistoryOptional
|
|
8716
10111
|
});
|
|
8717
10112
|
//# sourceMappingURL=index.js.map
|