@blocklet/discuss-kit-post 2.1.161 → 2.1.163

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/api.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { Post } from './types';
1
2
  export declare const api: import('axios').AxiosInstance;
2
- export declare const fetchPost: (postId: string, locale: string) => Promise<any>;
3
+ export declare const fetchPost: (postId: string, locale: string) => Promise<Post | null>;
3
4
  export declare const updatePostContent: (id: string, payload: {
4
5
  title: string;
5
6
  content: string;
@@ -0,0 +1,18 @@
1
+ import { SetState } from 'ahooks/lib/useSetState';
2
+ export type Status = 'idle' | 'saving' | 'saved';
3
+ type SaveAction<S, T> = (s: S) => Promise<T>;
4
+ interface Option<S, T> {
5
+ save: SaveAction<S, T>;
6
+ paused?: boolean;
7
+ wait?: number;
8
+ }
9
+ declare const useAutoSaveState: <S extends Record<string, any>, T>(initialState: S, { save, paused, wait }: Option<S, T>) => {
10
+ state: S;
11
+ setState: SetState<S>;
12
+ status: Status;
13
+ saved: T | undefined;
14
+ saveNow: () => Promise<void>;
15
+ setInitialState: (s: S) => void;
16
+ error: Error | null;
17
+ };
18
+ export { useAutoSaveState };
@@ -0,0 +1,8 @@
1
+ import { Status } from './auto-save';
2
+ interface Props {
3
+ status: Status;
4
+ error?: Error | null;
5
+ [key: string]: any;
6
+ }
7
+ export default function DraftStatus({ status, error, ...rest }: Props): import("react/jsx-runtime").JSX.Element | null;
8
+ export {};
package/dist/edit.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { Post } from './types';
2
+ interface Props {
3
+ post: Post;
4
+ onSave: (post: Post) => void;
5
+ onUpdate: (post: Post) => void;
6
+ onCancel: () => void;
7
+ }
8
+ export declare function PostEdit(props: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
package/dist/index.es.js CHANGED
@@ -1,11 +1,11 @@
1
1
  var _a, _b;
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { Box, IconButton, Button } from "@mui/material";
4
- import { useRequest } from "ahooks";
3
+ import { useMediaQuery, Box, CircularProgress, Tooltip, Stack, Button, IconButton } from "@mui/material";
4
+ import { useSetState, useGetState, useDocumentVisibility, useRequest, useThrottleEffect, useTimeout } from "ahooks";
5
5
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
6
6
  import { SessionContext } from "@arcblock/did-connect/lib/Session";
7
- import { useContext, useState } from "react";
8
- import { EditorPreview, InternalThemeProvider, UploaderProvider, DefaultEditorConfigProvider, LazyEditor } from "@blocklet/discuss-kit-ux";
7
+ import { useRef, useState, useCallback, useEffect, useContext } from "react";
8
+ import { DirtyPromptContainer, utils, toast, LazyEditor, EditorPreview, InternalThemeProvider, UploaderProvider, DefaultEditorConfigProvider } from "@blocklet/discuss-kit-ux";
9
9
  import { Edit } from "@mui/icons-material";
10
10
  import { createAxios } from "@blocklet/js-sdk";
11
11
  import { joinURL, withQuery } from "ufo";
@@ -32,6 +32,173 @@ const updatePostContent = async (id, payload) => {
32
32
  const { data } = await api.put(`/posts/${id}/content`, payload);
33
33
  return data;
34
34
  };
35
+ const useStatus = (saving) => {
36
+ const [status, setStatus] = useState("idle");
37
+ useEffect(() => {
38
+ if (saving) {
39
+ setStatus("saving");
40
+ } else {
41
+ setStatus((prev) => prev === "saving" ? "saved" : "idle");
42
+ }
43
+ }, [saving]);
44
+ useTimeout(
45
+ () => {
46
+ setStatus("idle");
47
+ },
48
+ status === "saved" ? 1500 : void 0
49
+ );
50
+ return status;
51
+ };
52
+ const useAutoSaveState = (initialState, { save, paused, wait = 3e4 }) => {
53
+ const [state, setStateInternal] = useSetState(initialState);
54
+ const [pendingState, setPendingState, getPendingState] = useGetState();
55
+ const latestState = pendingState || state;
56
+ const isInitialState = useRef(true);
57
+ const [error, setError] = useState(null);
58
+ const documentVisibility = useDocumentVisibility();
59
+ const { dirty, reset, markDirty } = DirtyPromptContainer.useContainer();
60
+ const { t } = useLocaleContext();
61
+ const handleSave = async (s) => {
62
+ try {
63
+ const result = await utils.minDelay(save(s), 1500);
64
+ const pendingState2 = getPendingState();
65
+ if (pendingState2) {
66
+ setStateInternal(pendingState2);
67
+ setPendingState(null);
68
+ } else {
69
+ reset();
70
+ setError(null);
71
+ }
72
+ return result;
73
+ } catch (e) {
74
+ setError(e);
75
+ toast.error(t("draftStatus.error"));
76
+ throw e;
77
+ }
78
+ };
79
+ const { data: saved, loading, run, runAsync } = useRequest(handleSave, { manual: true });
80
+ const status = useStatus(loading);
81
+ const canSave = !isInitialState.current && !paused && !loading && dirty;
82
+ useThrottleEffect(
83
+ () => {
84
+ if (paused || loading || !dirty || isInitialState.current) {
85
+ return;
86
+ }
87
+ run(state);
88
+ },
89
+ [state, paused],
90
+ { wait, leading: false }
91
+ );
92
+ const setState = (s) => {
93
+ if (loading) {
94
+ setPendingState({ ...state, ...s });
95
+ } else {
96
+ setStateInternal(s);
97
+ }
98
+ if (isInitialState.current) {
99
+ isInitialState.current = false;
100
+ }
101
+ if (!isInitialState.current) {
102
+ markDirty();
103
+ }
104
+ };
105
+ const setInitialState = useCallback(
106
+ (s) => {
107
+ setStateInternal(s);
108
+ isInitialState.current = true;
109
+ },
110
+ [setStateInternal]
111
+ );
112
+ const saveNow = useCallback(async () => {
113
+ if (canSave) {
114
+ await runAsync(state);
115
+ }
116
+ }, [canSave, runAsync, state]);
117
+ useEffect(() => {
118
+ if (documentVisibility === "hidden") {
119
+ saveNow();
120
+ }
121
+ }, [documentVisibility, saveNow]);
122
+ return { state: latestState, setState, setInitialState, status, saved, saveNow, error };
123
+ };
124
+ function DraftStatus({ status, error, ...rest }) {
125
+ const { t } = useLocaleContext();
126
+ const downMd = useMediaQuery((theme) => theme.breakpoints.down("md"));
127
+ if (downMd) {
128
+ return status !== "idle" ? /* @__PURE__ */ jsx(Box, { sx: { display: { xs: "inline-block", md: "none", lineHeight: 1 } }, component: CircularProgress, size: 16 }) : null;
129
+ }
130
+ return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, ...rest, children: [
131
+ /* @__PURE__ */ jsx(Tooltip, { title: error == null ? void 0 : error.message, children: /* @__PURE__ */ jsx(Box, { sx: { width: 8, height: 8, borderRadius: "100%", bgcolor: error ? "error.main" : "success.main" } }) }),
132
+ /* @__PURE__ */ jsx(Box, { sx: { color: "grey.600", fontSize: 13, lineHeight: 1 }, component: "span", className: "draft-status-text", children: error ? t("draftStatus.error") : t(`draftStatus.${status}`) })
133
+ ] });
134
+ }
135
+ function InternalPostEdit({ post, onSave, onUpdate, onCancel, ...rest }) {
136
+ const { reset, check } = DirtyPromptContainer.useContainer();
137
+ const save = async (state) => {
138
+ if (!post) {
139
+ throw new Error("Invalid state");
140
+ }
141
+ try {
142
+ const updated = await updatePostContent(post.id, {
143
+ title: state.title,
144
+ content: state.content,
145
+ locale: post.locale,
146
+ contentVersion: post.contentVersion
147
+ });
148
+ onUpdate(updated);
149
+ return updated;
150
+ } catch (e) {
151
+ console.error(e);
152
+ throw e;
153
+ }
154
+ };
155
+ const {
156
+ state: autoSaveState,
157
+ setState,
158
+ status,
159
+ error,
160
+ saveNow
161
+ } = useAutoSaveState({ title: (post == null ? void 0 : post.title) || "", content: (post == null ? void 0 : post.content) || "" }, { save });
162
+ const handleContentChange = ({ content }) => {
163
+ setState({ content });
164
+ };
165
+ const handleSave = async () => {
166
+ const updated = await updatePostContent(post.id, {
167
+ title: autoSaveState.title,
168
+ content: autoSaveState.content,
169
+ locale: post.locale,
170
+ contentVersion: post.contentVersion
171
+ });
172
+ onSave(updated);
173
+ };
174
+ const handleCancel = async () => {
175
+ if (await check()) {
176
+ onCancel();
177
+ reset();
178
+ }
179
+ };
180
+ return /* @__PURE__ */ jsxs(Box, { ...rest, children: [
181
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
182
+ /* @__PURE__ */ jsxs(Box, { sx: { mb: 1, display: "flex", gap: 1 }, children: [
183
+ /* @__PURE__ */ jsx(Button, { variant: "contained", color: "primary", onClick: () => handleSave(), children: "Save" }),
184
+ /* @__PURE__ */ jsx(Button, { variant: "outlined", onClick: handleCancel, children: "Cancel" })
185
+ ] }),
186
+ /* @__PURE__ */ jsx(DraftStatus, { status, error })
187
+ ] }),
188
+ /* @__PURE__ */ jsx(
189
+ LazyEditor,
190
+ {
191
+ initialContent: post.content || "",
192
+ onChange: handleContentChange,
193
+ autoFocus: false,
194
+ onSave: () => saveNow()
195
+ }
196
+ )
197
+ ] });
198
+ }
199
+ function PostEdit(props) {
200
+ return /* @__PURE__ */ jsx(DirtyPromptContainer.Provider, { children: /* @__PURE__ */ jsx(InternalPostEdit, { ...props }) });
201
+ }
35
202
  function InternalPost({ postId, sx, ...rest }) {
36
203
  var _a2, _b2;
37
204
  const { locale } = useLocaleContext();
@@ -49,18 +216,15 @@ function InternalPost({ postId, sx, ...rest }) {
49
216
  if (!post) {
50
217
  return null;
51
218
  }
52
- const handleSave = async () => {
53
- const updated = await updatePostContent(postId, {
54
- title: post.title,
55
- content: post.content,
56
- locale: post.locale,
57
- contentVersion: post.contentVersion
58
- });
219
+ const handleUpdate = (saved) => {
59
220
  mutate((prev) => ({
60
221
  ...prev,
61
- content: updated.content,
62
- contentVersion: updated.contentVersion
222
+ content: saved.content,
223
+ contentVersion: saved.contentVersion
63
224
  }));
225
+ };
226
+ const handleSave = (saved) => {
227
+ handleUpdate(saved);
64
228
  setEditable(false);
65
229
  };
66
230
  const mergedSx = [
@@ -83,22 +247,7 @@ function InternalPost({ postId, sx, ...rest }) {
83
247
  children: /* @__PURE__ */ jsx(Edit, {})
84
248
  }
85
249
  ),
86
- editable ? /* @__PURE__ */ jsxs(Box, { sx: {}, children: [
87
- /* @__PURE__ */ jsxs(Box, { sx: { mb: 1, display: "flex", gap: 1 }, children: [
88
- /* @__PURE__ */ jsx(Button, { variant: "contained", color: "primary", onClick: () => handleSave(), children: "Save" }),
89
- /* @__PURE__ */ jsx(Button, { variant: "outlined", onClick: () => setEditable(false), children: "Cancel" })
90
- ] }),
91
- /* @__PURE__ */ jsx(
92
- LazyEditor,
93
- {
94
- initialContent: post.content || "",
95
- onChange: ({ content }) => mutate((prev) => ({
96
- ...prev,
97
- content
98
- }))
99
- }
100
- )
101
- ] }) : /* @__PURE__ */ jsx(EditorPreview, { content: post.content || "" })
250
+ editable ? /* @__PURE__ */ jsx(PostEdit, { post, onSave: handleSave, onUpdate: handleUpdate, onCancel: () => setEditable(false) }) : /* @__PURE__ */ jsx(EditorPreview, { content: post.content || "" })
102
251
  ] }) }) }) });
103
252
  }
104
253
  function Post(props) {
package/dist/index.umd.js CHANGED
@@ -26,6 +26,173 @@
26
26
  const { data } = await api.put(`/posts/${id}/content`, payload);
27
27
  return data;
28
28
  };
29
+ const useStatus = (saving) => {
30
+ const [status, setStatus] = react.useState("idle");
31
+ react.useEffect(() => {
32
+ if (saving) {
33
+ setStatus("saving");
34
+ } else {
35
+ setStatus((prev) => prev === "saving" ? "saved" : "idle");
36
+ }
37
+ }, [saving]);
38
+ ahooks.useTimeout(
39
+ () => {
40
+ setStatus("idle");
41
+ },
42
+ status === "saved" ? 1500 : void 0
43
+ );
44
+ return status;
45
+ };
46
+ const useAutoSaveState = (initialState, { save, paused, wait = 3e4 }) => {
47
+ const [state, setStateInternal] = ahooks.useSetState(initialState);
48
+ const [pendingState, setPendingState, getPendingState] = ahooks.useGetState();
49
+ const latestState = pendingState || state;
50
+ const isInitialState = react.useRef(true);
51
+ const [error, setError] = react.useState(null);
52
+ const documentVisibility = ahooks.useDocumentVisibility();
53
+ const { dirty, reset, markDirty } = discussKitUx.DirtyPromptContainer.useContainer();
54
+ const { t } = context.useLocaleContext();
55
+ const handleSave = async (s) => {
56
+ try {
57
+ const result = await discussKitUx.utils.minDelay(save(s), 1500);
58
+ const pendingState2 = getPendingState();
59
+ if (pendingState2) {
60
+ setStateInternal(pendingState2);
61
+ setPendingState(null);
62
+ } else {
63
+ reset();
64
+ setError(null);
65
+ }
66
+ return result;
67
+ } catch (e) {
68
+ setError(e);
69
+ discussKitUx.toast.error(t("draftStatus.error"));
70
+ throw e;
71
+ }
72
+ };
73
+ const { data: saved, loading, run, runAsync } = ahooks.useRequest(handleSave, { manual: true });
74
+ const status = useStatus(loading);
75
+ const canSave = !isInitialState.current && !paused && !loading && dirty;
76
+ ahooks.useThrottleEffect(
77
+ () => {
78
+ if (paused || loading || !dirty || isInitialState.current) {
79
+ return;
80
+ }
81
+ run(state);
82
+ },
83
+ [state, paused],
84
+ { wait, leading: false }
85
+ );
86
+ const setState = (s) => {
87
+ if (loading) {
88
+ setPendingState({ ...state, ...s });
89
+ } else {
90
+ setStateInternal(s);
91
+ }
92
+ if (isInitialState.current) {
93
+ isInitialState.current = false;
94
+ }
95
+ if (!isInitialState.current) {
96
+ markDirty();
97
+ }
98
+ };
99
+ const setInitialState = react.useCallback(
100
+ (s) => {
101
+ setStateInternal(s);
102
+ isInitialState.current = true;
103
+ },
104
+ [setStateInternal]
105
+ );
106
+ const saveNow = react.useCallback(async () => {
107
+ if (canSave) {
108
+ await runAsync(state);
109
+ }
110
+ }, [canSave, runAsync, state]);
111
+ react.useEffect(() => {
112
+ if (documentVisibility === "hidden") {
113
+ saveNow();
114
+ }
115
+ }, [documentVisibility, saveNow]);
116
+ return { state: latestState, setState, setInitialState, status, saved, saveNow, error };
117
+ };
118
+ function DraftStatus({ status, error, ...rest }) {
119
+ const { t } = context.useLocaleContext();
120
+ const downMd = material.useMediaQuery((theme) => theme.breakpoints.down("md"));
121
+ if (downMd) {
122
+ return status !== "idle" ? /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { display: { xs: "inline-block", md: "none", lineHeight: 1 } }, component: material.CircularProgress, size: 16 }) : null;
123
+ }
124
+ return /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, ...rest, children: [
125
+ /* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title: error == null ? void 0 : error.message, children: /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { width: 8, height: 8, borderRadius: "100%", bgcolor: error ? "error.main" : "success.main" } }) }),
126
+ /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { color: "grey.600", fontSize: 13, lineHeight: 1 }, component: "span", className: "draft-status-text", children: error ? t("draftStatus.error") : t(`draftStatus.${status}`) })
127
+ ] });
128
+ }
129
+ function InternalPostEdit({ post, onSave, onUpdate, onCancel, ...rest }) {
130
+ const { reset, check } = discussKitUx.DirtyPromptContainer.useContainer();
131
+ const save = async (state) => {
132
+ if (!post) {
133
+ throw new Error("Invalid state");
134
+ }
135
+ try {
136
+ const updated = await updatePostContent(post.id, {
137
+ title: state.title,
138
+ content: state.content,
139
+ locale: post.locale,
140
+ contentVersion: post.contentVersion
141
+ });
142
+ onUpdate(updated);
143
+ return updated;
144
+ } catch (e) {
145
+ console.error(e);
146
+ throw e;
147
+ }
148
+ };
149
+ const {
150
+ state: autoSaveState,
151
+ setState,
152
+ status,
153
+ error,
154
+ saveNow
155
+ } = useAutoSaveState({ title: (post == null ? void 0 : post.title) || "", content: (post == null ? void 0 : post.content) || "" }, { save });
156
+ const handleContentChange = ({ content }) => {
157
+ setState({ content });
158
+ };
159
+ const handleSave = async () => {
160
+ const updated = await updatePostContent(post.id, {
161
+ title: autoSaveState.title,
162
+ content: autoSaveState.content,
163
+ locale: post.locale,
164
+ contentVersion: post.contentVersion
165
+ });
166
+ onSave(updated);
167
+ };
168
+ const handleCancel = async () => {
169
+ if (await check()) {
170
+ onCancel();
171
+ reset();
172
+ }
173
+ };
174
+ return /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { ...rest, children: [
175
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
176
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { mb: 1, display: "flex", gap: 1 }, children: [
177
+ /* @__PURE__ */ jsxRuntime.jsx(material.Button, { variant: "contained", color: "primary", onClick: () => handleSave(), children: "Save" }),
178
+ /* @__PURE__ */ jsxRuntime.jsx(material.Button, { variant: "outlined", onClick: handleCancel, children: "Cancel" })
179
+ ] }),
180
+ /* @__PURE__ */ jsxRuntime.jsx(DraftStatus, { status, error })
181
+ ] }),
182
+ /* @__PURE__ */ jsxRuntime.jsx(
183
+ discussKitUx.LazyEditor,
184
+ {
185
+ initialContent: post.content || "",
186
+ onChange: handleContentChange,
187
+ autoFocus: false,
188
+ onSave: () => saveNow()
189
+ }
190
+ )
191
+ ] });
192
+ }
193
+ function PostEdit(props) {
194
+ return /* @__PURE__ */ jsxRuntime.jsx(discussKitUx.DirtyPromptContainer.Provider, { children: /* @__PURE__ */ jsxRuntime.jsx(InternalPostEdit, { ...props }) });
195
+ }
29
196
  function InternalPost({ postId, sx, ...rest }) {
30
197
  var _a2, _b2;
31
198
  const { locale } = context.useLocaleContext();
@@ -43,18 +210,15 @@
43
210
  if (!post) {
44
211
  return null;
45
212
  }
46
- const handleSave = async () => {
47
- const updated = await updatePostContent(postId, {
48
- title: post.title,
49
- content: post.content,
50
- locale: post.locale,
51
- contentVersion: post.contentVersion
52
- });
213
+ const handleUpdate = (saved) => {
53
214
  mutate((prev) => ({
54
215
  ...prev,
55
- content: updated.content,
56
- contentVersion: updated.contentVersion
216
+ content: saved.content,
217
+ contentVersion: saved.contentVersion
57
218
  }));
219
+ };
220
+ const handleSave = (saved) => {
221
+ handleUpdate(saved);
58
222
  setEditable(false);
59
223
  };
60
224
  const mergedSx = [
@@ -77,22 +241,7 @@
77
241
  children: /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.Edit, {})
78
242
  }
79
243
  ),
80
- editable ? /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: {}, children: [
81
- /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { mb: 1, display: "flex", gap: 1 }, children: [
82
- /* @__PURE__ */ jsxRuntime.jsx(material.Button, { variant: "contained", color: "primary", onClick: () => handleSave(), children: "Save" }),
83
- /* @__PURE__ */ jsxRuntime.jsx(material.Button, { variant: "outlined", onClick: () => setEditable(false), children: "Cancel" })
84
- ] }),
85
- /* @__PURE__ */ jsxRuntime.jsx(
86
- discussKitUx.LazyEditor,
87
- {
88
- initialContent: post.content || "",
89
- onChange: ({ content }) => mutate((prev) => ({
90
- ...prev,
91
- content
92
- }))
93
- }
94
- )
95
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(discussKitUx.EditorPreview, { content: post.content || "" })
244
+ editable ? /* @__PURE__ */ jsxRuntime.jsx(PostEdit, { post, onSave: handleSave, onUpdate: handleUpdate, onCancel: () => setEditable(false) }) : /* @__PURE__ */ jsxRuntime.jsx(discussKitUx.EditorPreview, { content: post.content || "" })
96
245
  ] }) }) }) });
97
246
  }
98
247
  function Post(props) {
@@ -0,0 +1,10 @@
1
+ export interface Post {
2
+ id: string;
3
+ type: 'blog' | 'post' | 'doc' | 'bookmark';
4
+ title: string;
5
+ content: string;
6
+ locale?: string;
7
+ contentVersion: number;
8
+ createdAt: Date;
9
+ updatedAt: Date;
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/discuss-kit-post",
3
- "version": "2.1.161",
3
+ "version": "2.1.163",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -22,7 +22,7 @@
22
22
  "@mui/icons-material": "^5.16.7",
23
23
  "ahooks": "^3.8.1",
24
24
  "ufo": "1.3.1",
25
- "@blocklet/discuss-kit-ux": "^2.1.161"
25
+ "@blocklet/discuss-kit-ux": "^2.1.163"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "@arcblock/did-connect": "^2.10.36",