gitalk-react 1.0.0-beta.5 → 1.0.0-beta.7
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-zh-CN.md +13 -0
- package/README.md +13 -0
- package/dist/gitalk-dark.css +1 -1
- package/dist/gitalk-light.css +1 -1
- package/dist/gitalk.d.ts +5 -1
- package/dist/gitalk.js +3417 -3295
- package/dist/gitalk.umd.cjs +25 -25
- package/lib/components/comment-textarea.tsx +164 -0
- package/lib/components/comment.tsx +38 -6
- package/lib/components/comments-list.tsx +106 -0
- package/lib/components/meta.tsx +129 -0
- package/lib/constants/index.ts +9 -1
- package/lib/contexts/I18nContext.ts +4 -2
- package/lib/gitalk.tsx +197 -559
- package/lib/i18n/index.ts +1 -1
- package/lib/interfaces/index.ts +166 -0
- package/lib/services/graphql/comment.ts +1 -2
- package/lib/themes/base.scss +42 -4
- package/lib/themes/gitalk-dark.scss +4 -0
- package/lib/themes/gitalk-light.scss +4 -0
- package/package.json +6 -2
package/lib/gitalk.tsx
CHANGED
|
@@ -2,35 +2,31 @@ import "./i18n";
|
|
|
2
2
|
|
|
3
3
|
import { useRequest } from "ahooks";
|
|
4
4
|
import React, {
|
|
5
|
-
forwardRef,
|
|
6
5
|
useCallback,
|
|
7
6
|
useEffect,
|
|
8
7
|
useMemo,
|
|
9
8
|
useRef,
|
|
10
9
|
useState,
|
|
11
10
|
} from "react";
|
|
12
|
-
import FlipMove from "react-flip-move";
|
|
13
11
|
|
|
14
|
-
import ArrowDown from "./assets/arrow-down.svg?raw";
|
|
15
|
-
import Github from "./assets/github.svg?raw";
|
|
16
|
-
import Tip from "./assets/tip.svg?raw";
|
|
17
|
-
import Action from "./components/action";
|
|
18
|
-
import Avatar from "./components/avatar";
|
|
19
12
|
import Button from "./components/button";
|
|
20
|
-
import
|
|
21
|
-
import
|
|
13
|
+
import { type CommentProps } from "./components/comment";
|
|
14
|
+
import CommentTextarea from "./components/comment-textarea";
|
|
15
|
+
import CommentsList from "./components/comments-list";
|
|
16
|
+
import Meta from "./components/meta";
|
|
22
17
|
import {
|
|
23
18
|
ACCESS_TOKEN_KEY,
|
|
24
19
|
DATE_FNS_LOCALE_MAP,
|
|
20
|
+
DEFAULT_FLIP_MOVE_OPTIONS,
|
|
25
21
|
DEFAULT_LABELS,
|
|
22
|
+
DEFAULT_PROXY,
|
|
26
23
|
DEFAULT_USER,
|
|
27
|
-
HOMEPAGE,
|
|
28
|
-
VERSION,
|
|
29
24
|
} from "./constants";
|
|
30
|
-
import I18nContext from "./contexts/I18nContext";
|
|
25
|
+
import I18nContext, { type I18nContextValue } from "./contexts/I18nContext";
|
|
31
26
|
import i18n, { type Lang } from "./i18n";
|
|
32
27
|
import type {
|
|
33
28
|
Comment as CommentType,
|
|
29
|
+
GitalkProps,
|
|
34
30
|
Issue as IssueType,
|
|
35
31
|
User as UserType,
|
|
36
32
|
} from "./interfaces";
|
|
@@ -41,168 +37,9 @@ import {
|
|
|
41
37
|
import getOctokitInstance from "./services/request";
|
|
42
38
|
import { getAccessToken, getAuthorizeUrl } from "./services/user";
|
|
43
39
|
import { supportsCSSVariables, supportsES2020 } from "./utils/compatibility";
|
|
44
|
-
import { hasClassInParent } from "./utils/dom";
|
|
45
40
|
import logger from "./utils/logger";
|
|
46
41
|
import { parseSearchQuery, stringifySearchQuery } from "./utils/query";
|
|
47
42
|
|
|
48
|
-
export interface GitalkProps
|
|
49
|
-
extends Omit<
|
|
50
|
-
React.DetailedHTMLProps<
|
|
51
|
-
React.HTMLAttributes<HTMLDivElement>,
|
|
52
|
-
HTMLDivElement
|
|
53
|
-
>,
|
|
54
|
-
"id" | "title"
|
|
55
|
-
> {
|
|
56
|
-
/**
|
|
57
|
-
* GitHub Application Client ID.
|
|
58
|
-
*/
|
|
59
|
-
clientID: string;
|
|
60
|
-
/**
|
|
61
|
-
* GitHub Application Client Secret.
|
|
62
|
-
*/
|
|
63
|
-
clientSecret: string;
|
|
64
|
-
/**
|
|
65
|
-
* GitHub repository owner.
|
|
66
|
-
* Can be personal user or organization.
|
|
67
|
-
*/
|
|
68
|
-
owner: string;
|
|
69
|
-
/**
|
|
70
|
-
* Name of Github repository.
|
|
71
|
-
*/
|
|
72
|
-
repo: string;
|
|
73
|
-
/**
|
|
74
|
-
* GitHub repository owner and collaborators.
|
|
75
|
-
* (Users who having write access to this repository)
|
|
76
|
-
*/
|
|
77
|
-
admin: string[];
|
|
78
|
-
/**
|
|
79
|
-
* The unique id of the page.
|
|
80
|
-
* Length must less than 50.
|
|
81
|
-
*
|
|
82
|
-
* @default location.host + location.pathname
|
|
83
|
-
*/
|
|
84
|
-
id?: string;
|
|
85
|
-
/**
|
|
86
|
-
* The issue ID of the page.
|
|
87
|
-
* If the number attribute is not defined, issue will be located using id.
|
|
88
|
-
*/
|
|
89
|
-
number?: number;
|
|
90
|
-
/**
|
|
91
|
-
* GitHub issue labels.
|
|
92
|
-
*
|
|
93
|
-
* @default ['Gitalk']
|
|
94
|
-
*/
|
|
95
|
-
labels?: string[];
|
|
96
|
-
/**
|
|
97
|
-
* GitHub issue title.
|
|
98
|
-
*
|
|
99
|
-
* @default document.title
|
|
100
|
-
*/
|
|
101
|
-
title?: string;
|
|
102
|
-
/**
|
|
103
|
-
* GitHub issue body.
|
|
104
|
-
*
|
|
105
|
-
* @default location.href + header.meta[description]
|
|
106
|
-
*/
|
|
107
|
-
body?: string;
|
|
108
|
-
/**
|
|
109
|
-
* Localization language key.
|
|
110
|
-
*
|
|
111
|
-
* @default navigator.language
|
|
112
|
-
*/
|
|
113
|
-
language?: Lang;
|
|
114
|
-
/**
|
|
115
|
-
* Pagination size, with maximum 100.
|
|
116
|
-
*
|
|
117
|
-
* @default 10
|
|
118
|
-
*/
|
|
119
|
-
perPage?: number;
|
|
120
|
-
/**
|
|
121
|
-
* Comment sorting direction.
|
|
122
|
-
* Available values are `last` and `first`.
|
|
123
|
-
*
|
|
124
|
-
* @default "last"
|
|
125
|
-
*/
|
|
126
|
-
pagerDirection?: "last" | "first";
|
|
127
|
-
/**
|
|
128
|
-
* By default, Gitalk will create a corresponding github issue for your every single page automatically when the logined user is belong to the admin users.
|
|
129
|
-
* You can create it manually by setting this option to true.
|
|
130
|
-
*
|
|
131
|
-
* @default false
|
|
132
|
-
*/
|
|
133
|
-
createIssueManually?: boolean;
|
|
134
|
-
/**
|
|
135
|
-
* Enable hot key (cmd|ctrl + enter) submit comment.
|
|
136
|
-
*
|
|
137
|
-
* @default true
|
|
138
|
-
*/
|
|
139
|
-
enableHotKey?: boolean;
|
|
140
|
-
/**
|
|
141
|
-
* Facebook-like distraction free mode.
|
|
142
|
-
*
|
|
143
|
-
* @default false
|
|
144
|
-
*/
|
|
145
|
-
distractionFreeMode?: boolean;
|
|
146
|
-
/**
|
|
147
|
-
* Comment list animation.
|
|
148
|
-
*
|
|
149
|
-
* @default
|
|
150
|
-
* ```ts
|
|
151
|
-
* {
|
|
152
|
-
* staggerDelayBy: 150,
|
|
153
|
-
* appearAnimation: 'accordionVertical',
|
|
154
|
-
* enterAnimation: 'accordionVertical',
|
|
155
|
-
* leaveAnimation: 'accordionVertical',
|
|
156
|
-
* }
|
|
157
|
-
* ```
|
|
158
|
-
* @link https://github.com/joshwcomeau/react-flip-move/blob/master/documentation/enter_leave_animations.md
|
|
159
|
-
*/
|
|
160
|
-
flipMoveOptions?: FlipMove.FlipMoveProps;
|
|
161
|
-
/**
|
|
162
|
-
* GitHub oauth request reverse proxy for CORS.
|
|
163
|
-
* [Why need this?](https://github.com/isaacs/github/issues/330)
|
|
164
|
-
*
|
|
165
|
-
* @default "https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token"
|
|
166
|
-
*/
|
|
167
|
-
proxy?: string;
|
|
168
|
-
/**
|
|
169
|
-
* Default user field if comments' author is not provided
|
|
170
|
-
*
|
|
171
|
-
* @default
|
|
172
|
-
* ```ts
|
|
173
|
-
* {
|
|
174
|
-
* avatar_url: "//avatars1.githubusercontent.com/u/29697133?s=50",
|
|
175
|
-
* login: "null",
|
|
176
|
-
* html_url: ""
|
|
177
|
-
* }
|
|
178
|
-
*/
|
|
179
|
-
defaultUser?: CommentType["user"];
|
|
180
|
-
/**
|
|
181
|
-
* Default user field if comments' author is not provided
|
|
182
|
-
*
|
|
183
|
-
* @deprecated use `defaultUser`
|
|
184
|
-
*/
|
|
185
|
-
defaultAuthor?: IssueCommentsQLResponse["repository"]["issue"]["comments"]["nodes"][number]["author"];
|
|
186
|
-
/**
|
|
187
|
-
* Callback method invoked when updating the number of comments.
|
|
188
|
-
*
|
|
189
|
-
* @param count comments number
|
|
190
|
-
*/
|
|
191
|
-
updateCountCallback?: (count: number) => void;
|
|
192
|
-
/**
|
|
193
|
-
* Callback method invoked when a new issue is successfully created.
|
|
194
|
-
*
|
|
195
|
-
* @param issue created issue
|
|
196
|
-
*/
|
|
197
|
-
onCreateIssue?: (issue: IssueType) => void;
|
|
198
|
-
/**
|
|
199
|
-
* Callback method invoked when a new comment is successfully created.
|
|
200
|
-
*
|
|
201
|
-
* @param comment created comment
|
|
202
|
-
*/
|
|
203
|
-
onCreateComment?: (comment: CommentType) => void;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
43
|
const isModernBrowser = supportsCSSVariables() && supportsES2020();
|
|
207
44
|
|
|
208
45
|
const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
@@ -227,15 +64,11 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
227
64
|
createIssueManually = false,
|
|
228
65
|
enableHotKey = true,
|
|
229
66
|
distractionFreeMode = false,
|
|
230
|
-
flipMoveOptions =
|
|
231
|
-
|
|
232
|
-
appearAnimation: "accordionVertical",
|
|
233
|
-
enterAnimation: "accordionVertical",
|
|
234
|
-
leaveAnimation: "accordionVertical",
|
|
235
|
-
},
|
|
236
|
-
proxy = "https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token",
|
|
67
|
+
flipMoveOptions = DEFAULT_FLIP_MOVE_OPTIONS,
|
|
68
|
+
proxy = DEFAULT_PROXY,
|
|
237
69
|
defaultUser: propsDefaultUser,
|
|
238
70
|
defaultAuthor: propsDefaultAuthor,
|
|
71
|
+
collapsedHeight: propsCollapsedHeight,
|
|
239
72
|
updateCountCallback,
|
|
240
73
|
onCreateIssue,
|
|
241
74
|
onCreateComment,
|
|
@@ -258,7 +91,6 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
258
91
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
259
92
|
const [inputComment, setInputComment] = useState<string>("");
|
|
260
93
|
const [isInputFocused, setIsInputFocused] = useState<boolean>(false);
|
|
261
|
-
const [isPreviewComment, setIsPreviewComment] = useState<boolean>(false);
|
|
262
94
|
|
|
263
95
|
const [commentsCount, setCommentsCount] = useState<number>(0);
|
|
264
96
|
const [commentsCursor, setCommentsCursor] = useState("");
|
|
@@ -267,7 +99,7 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
267
99
|
() => (propsPerPage > 100 ? 100 : propsPerPage < 0 ? 10 : propsPerPage),
|
|
268
100
|
[propsPerPage],
|
|
269
101
|
);
|
|
270
|
-
|
|
102
|
+
/** Current sort order, have effect when user is logged */
|
|
271
103
|
const [commentsPagerDirection, setCommentsPagerDirection] =
|
|
272
104
|
useState(pagerDirection);
|
|
273
105
|
const defaultUser = useMemo(
|
|
@@ -283,12 +115,22 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
283
115
|
: DEFAULT_USER,
|
|
284
116
|
[propsDefaultAuthor, propsDefaultUser],
|
|
285
117
|
);
|
|
286
|
-
|
|
287
|
-
|
|
118
|
+
const collapsedHeight =
|
|
119
|
+
propsCollapsedHeight && propsCollapsedHeight > 0
|
|
120
|
+
? propsCollapsedHeight
|
|
121
|
+
: undefined;
|
|
288
122
|
|
|
289
123
|
const [alert, setAlert] = useState<string>("");
|
|
290
124
|
|
|
291
125
|
const polyglot = useMemo(() => i18n(language), [language]);
|
|
126
|
+
const i18nContextValue: I18nContextValue = useMemo(
|
|
127
|
+
() => ({
|
|
128
|
+
language,
|
|
129
|
+
polyglot,
|
|
130
|
+
dateFnsLocaleMap: DATE_FNS_LOCALE_MAP,
|
|
131
|
+
}),
|
|
132
|
+
[language, polyglot],
|
|
133
|
+
);
|
|
292
134
|
|
|
293
135
|
const {
|
|
294
136
|
data: accessToken = localStorage.getItem(ACCESS_TOKEN_KEY) ?? undefined,
|
|
@@ -490,6 +332,7 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
490
332
|
const {
|
|
491
333
|
data: comments = [],
|
|
492
334
|
mutate: setComments,
|
|
335
|
+
run: runGetComments,
|
|
493
336
|
loading: getCommentsLoading,
|
|
494
337
|
} = useRequest(
|
|
495
338
|
async (): Promise<CommentType[]> => {
|
|
@@ -500,7 +343,7 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
500
343
|
if (user) {
|
|
501
344
|
// Get comments via GraphQL, witch requires being logged and able to sort
|
|
502
345
|
const query = getIssueCommentsQL({
|
|
503
|
-
pagerDirection,
|
|
346
|
+
pagerDirection: commentsPagerDirection,
|
|
504
347
|
});
|
|
505
348
|
|
|
506
349
|
const getIssueCommentsRes: IssueCommentsQLResponse =
|
|
@@ -540,11 +383,14 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
540
383
|
_comments,
|
|
541
384
|
);
|
|
542
385
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
386
|
+
const commentsPageInfo =
|
|
387
|
+
getIssueCommentsRes.repository.issue.comments.pageInfo;
|
|
388
|
+
const commentsPageCursor =
|
|
389
|
+
commentsPageInfo.startCursor || commentsPageInfo.endCursor || "";
|
|
390
|
+
setCommentsCursor(commentsPageCursor);
|
|
546
391
|
|
|
547
|
-
if (
|
|
392
|
+
if (commentsPagerDirection === "last")
|
|
393
|
+
return [..._comments, ...comments];
|
|
548
394
|
else return [...comments, ..._comments];
|
|
549
395
|
} else {
|
|
550
396
|
setAlert(
|
|
@@ -589,9 +435,7 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
589
435
|
_comments,
|
|
590
436
|
);
|
|
591
437
|
|
|
592
|
-
|
|
593
|
-
setCommentsLoaded(true);
|
|
594
|
-
}
|
|
438
|
+
setCommentsPage((prev) => prev + 1);
|
|
595
439
|
|
|
596
440
|
return [...comments, ..._comments];
|
|
597
441
|
} else {
|
|
@@ -608,8 +452,8 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
608
452
|
return comments;
|
|
609
453
|
},
|
|
610
454
|
{
|
|
611
|
-
|
|
612
|
-
|
|
455
|
+
manual: true,
|
|
456
|
+
ready: !!owner && !!repo && !!issue && !getUserLoading,
|
|
613
457
|
},
|
|
614
458
|
);
|
|
615
459
|
|
|
@@ -617,7 +461,7 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
617
461
|
data: localComments = [],
|
|
618
462
|
mutate: setLocalComments,
|
|
619
463
|
loading: createIssueCommentLoading,
|
|
620
|
-
|
|
464
|
+
runAsync: runCreateIssueComment,
|
|
621
465
|
} = useRequest(
|
|
622
466
|
async (): Promise<CommentType[]> => {
|
|
623
467
|
const { number: currentIssueNumber } = issue as IssueType;
|
|
@@ -649,6 +493,8 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
649
493
|
logger.s(`Create issue comment successfully.`);
|
|
650
494
|
|
|
651
495
|
setInputComment("");
|
|
496
|
+
setCommentsCount((prev) => prev + 1);
|
|
497
|
+
|
|
652
498
|
onCreateComment?.(createdIssueComment);
|
|
653
499
|
|
|
654
500
|
return localComments.concat([createdIssueComment]);
|
|
@@ -666,63 +512,50 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
666
512
|
|
|
667
513
|
useEffect(() => {
|
|
668
514
|
setComments([]);
|
|
669
|
-
setCommentsCount(0);
|
|
515
|
+
setCommentsCount(issue?.comments ?? 0);
|
|
670
516
|
setCommentsCursor("");
|
|
671
517
|
setCommentsPage(1);
|
|
672
|
-
setCommentsLoaded(false);
|
|
673
518
|
setLocalComments([]);
|
|
674
519
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
}, [issue,
|
|
520
|
+
setTimeout(() => {
|
|
521
|
+
runGetComments();
|
|
522
|
+
});
|
|
523
|
+
}, [issue, runGetComments, setComments, setLocalComments]);
|
|
524
|
+
|
|
525
|
+
useEffect(() => {
|
|
526
|
+
setComments([]);
|
|
527
|
+
setCommentsCursor("");
|
|
528
|
+
setCommentsPage(1);
|
|
529
|
+
|
|
530
|
+
setTimeout(() => {
|
|
531
|
+
runGetComments();
|
|
532
|
+
});
|
|
533
|
+
}, [user, commentsPagerDirection, setComments, runGetComments]);
|
|
679
534
|
|
|
680
535
|
/** sorted all comments */
|
|
681
|
-
const
|
|
682
|
-
const
|
|
536
|
+
const loadedComments = useMemo(() => {
|
|
537
|
+
const _loadedComments: CommentType[] = [];
|
|
538
|
+
|
|
539
|
+
// filter duplicate comments if exist
|
|
540
|
+
const commentIdsSet = new Set();
|
|
541
|
+
for (const comment of comments.concat(localComments)) {
|
|
542
|
+
if (!commentIdsSet.has(comment.id)) {
|
|
543
|
+
commentIdsSet.add(comment.id);
|
|
544
|
+
_loadedComments.push(comment);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
683
547
|
|
|
684
|
-
if (commentsPagerDirection === "last"
|
|
548
|
+
if (!!user && commentsPagerDirection === "last") {
|
|
685
549
|
// sort comments by date DESC
|
|
686
|
-
|
|
550
|
+
_loadedComments.reverse();
|
|
687
551
|
}
|
|
688
552
|
|
|
689
|
-
return
|
|
553
|
+
return _loadedComments;
|
|
690
554
|
}, [comments, commentsPagerDirection, localComments, user]);
|
|
691
555
|
|
|
692
|
-
const allCommentsCount = commentsCount + (localComments ?? []).length;
|
|
693
|
-
|
|
694
556
|
useEffect(() => {
|
|
695
|
-
updateCountCallback?.(
|
|
696
|
-
}, [
|
|
697
|
-
|
|
698
|
-
const {
|
|
699
|
-
data: commentHtml = "",
|
|
700
|
-
mutate: setCommentHtml,
|
|
701
|
-
loading: getCommentHtmlLoading,
|
|
702
|
-
run: runGetCommentHtml,
|
|
703
|
-
cancel: cancelGetCommentHtml,
|
|
704
|
-
} = useRequest(
|
|
705
|
-
async () => {
|
|
706
|
-
const getPreviewedHtmlRes = await octokit.request("POST /markdown", {
|
|
707
|
-
text: inputComment,
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
if (getPreviewedHtmlRes.status === 200) {
|
|
711
|
-
const _commentHtml = getPreviewedHtmlRes.data;
|
|
712
|
-
return _commentHtml;
|
|
713
|
-
} else {
|
|
714
|
-
setAlert(`Preview rendered comment failed: ${getPreviewedHtmlRes}`);
|
|
715
|
-
logger.e(`Preview rendered comment failed:`, getPreviewedHtmlRes);
|
|
716
|
-
return "";
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
manual: true,
|
|
721
|
-
onBefore: () => {
|
|
722
|
-
setCommentHtml("");
|
|
723
|
-
},
|
|
724
|
-
},
|
|
725
|
-
);
|
|
557
|
+
updateCountCallback?.(commentsCount);
|
|
558
|
+
}, [commentsCount, updateCountCallback]);
|
|
726
559
|
|
|
727
560
|
const { loading: likeOrDislikeCommentLoading, run: runLikeOrDislikeComment } =
|
|
728
561
|
useRequest(
|
|
@@ -843,29 +676,6 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
843
676
|
[initialized, issue],
|
|
844
677
|
);
|
|
845
678
|
|
|
846
|
-
const hidePopup = useCallback((e: MouseEvent) => {
|
|
847
|
-
const target = e.target as HTMLElement;
|
|
848
|
-
if (target && hasClassInParent(target, "gt-user", "gt-popup")) {
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
document.removeEventListener("click", hidePopup);
|
|
852
|
-
setShowPopup(false);
|
|
853
|
-
}, []);
|
|
854
|
-
|
|
855
|
-
const onShowOrHidePopup: React.MouseEventHandler<HTMLDivElement> = (e) => {
|
|
856
|
-
e.preventDefault();
|
|
857
|
-
e.stopPropagation();
|
|
858
|
-
|
|
859
|
-
setShowPopup((visible) => {
|
|
860
|
-
if (visible) {
|
|
861
|
-
document.removeEventListener("click", hidePopup);
|
|
862
|
-
} else {
|
|
863
|
-
document.addEventListener("click", hidePopup);
|
|
864
|
-
}
|
|
865
|
-
return !visible;
|
|
866
|
-
});
|
|
867
|
-
};
|
|
868
|
-
|
|
869
679
|
const onLogin = () => {
|
|
870
680
|
const url = getAuthorizeUrl(clientID);
|
|
871
681
|
window.location.href = url;
|
|
@@ -891,6 +701,14 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
891
701
|
setIsInputFocused(false);
|
|
892
702
|
};
|
|
893
703
|
|
|
704
|
+
useEffect(() => {
|
|
705
|
+
const textarea = textareaRef.current;
|
|
706
|
+
if (textarea) {
|
|
707
|
+
textarea.style.height = "auto";
|
|
708
|
+
textarea.style.height = `${textarea.scrollHeight + 2}px`;
|
|
709
|
+
}
|
|
710
|
+
}, [inputComment]);
|
|
711
|
+
|
|
894
712
|
const onCommentInputKeyDown: React.KeyboardEventHandler<
|
|
895
713
|
HTMLTextAreaElement
|
|
896
714
|
> = (e) => {
|
|
@@ -899,41 +717,35 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
899
717
|
}
|
|
900
718
|
};
|
|
901
719
|
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
} else {
|
|
909
|
-
setIsPreviewComment(true);
|
|
910
|
-
runGetCommentHtml();
|
|
911
|
-
}
|
|
912
|
-
};
|
|
913
|
-
|
|
914
|
-
const onReplyComment: CommentProps["onReply"] = (repliedComment) => {
|
|
915
|
-
const { body: repliedCommentBody = "", user: repliedCommentUser } =
|
|
916
|
-
repliedComment;
|
|
917
|
-
let repliedCommentBodyArray = repliedCommentBody.split("\n");
|
|
918
|
-
const repliedCommentUsername = repliedCommentUser?.login;
|
|
919
|
-
|
|
920
|
-
if (repliedCommentUsername) {
|
|
921
|
-
repliedCommentBodyArray.unshift(`@${repliedCommentUsername}`);
|
|
922
|
-
}
|
|
923
|
-
repliedCommentBodyArray = repliedCommentBodyArray.map(
|
|
924
|
-
(text) => `> ${text}`,
|
|
925
|
-
);
|
|
720
|
+
const onReplyComment: CommentProps["onReply"] = useCallback(
|
|
721
|
+
(repliedComment) => {
|
|
722
|
+
const { body: repliedCommentBody = "", user: repliedCommentUser } =
|
|
723
|
+
repliedComment;
|
|
724
|
+
let repliedCommentBodyArray = repliedCommentBody.split("\n");
|
|
725
|
+
const repliedCommentUsername = repliedCommentUser?.login;
|
|
926
726
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
727
|
+
if (repliedCommentUsername) {
|
|
728
|
+
repliedCommentBodyArray.unshift(`@${repliedCommentUsername}`);
|
|
729
|
+
}
|
|
730
|
+
repliedCommentBodyArray = repliedCommentBodyArray.map(
|
|
731
|
+
(text) => `> ${text}`,
|
|
732
|
+
);
|
|
930
733
|
|
|
931
|
-
|
|
734
|
+
setInputComment((prevComment) => {
|
|
735
|
+
if (prevComment) {
|
|
736
|
+
repliedCommentBodyArray.unshift("", "");
|
|
737
|
+
}
|
|
738
|
+
repliedCommentBodyArray.push("", "");
|
|
739
|
+
const newComment = `${prevComment}${repliedCommentBodyArray.join("\n")}`;
|
|
740
|
+
return newComment;
|
|
741
|
+
});
|
|
932
742
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
743
|
+
setTimeout(() => {
|
|
744
|
+
textareaRef.current?.focus();
|
|
745
|
+
});
|
|
746
|
+
},
|
|
747
|
+
[],
|
|
748
|
+
);
|
|
937
749
|
|
|
938
750
|
if (!isModernBrowser) {
|
|
939
751
|
logger.e(
|
|
@@ -960,284 +772,110 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
|
|
|
960
772
|
return null;
|
|
961
773
|
}
|
|
962
774
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
<div
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const renderIssueNotInitialized = () => {
|
|
973
|
-
return (
|
|
974
|
-
<div className="gt-no-init" key="no-init">
|
|
975
|
-
<p
|
|
976
|
-
dangerouslySetInnerHTML={{
|
|
977
|
-
__html: polyglot.t("no-found-related", {
|
|
978
|
-
link: `<a href="https://github.com/${owner}/${repo}/issues" target="_blank" rel="noopener noreferrer">Issues</a>`,
|
|
979
|
-
}),
|
|
980
|
-
}}
|
|
981
|
-
/>
|
|
982
|
-
<p>
|
|
983
|
-
{polyglot.t("please-contact", {
|
|
984
|
-
user: admin.map((u) => `@${u}`).join(" "),
|
|
985
|
-
})}
|
|
986
|
-
</p>
|
|
987
|
-
{isAdmin ? (
|
|
988
|
-
<p>
|
|
989
|
-
<Button
|
|
990
|
-
onClick={runCreateIssue}
|
|
991
|
-
isLoading={createIssueLoading}
|
|
992
|
-
text={polyglot.t("init-issue")}
|
|
993
|
-
/>
|
|
994
|
-
</p>
|
|
995
|
-
) : null}
|
|
996
|
-
{!user && (
|
|
997
|
-
<Button
|
|
998
|
-
className="gt-btn-login"
|
|
999
|
-
onClick={onLogin}
|
|
1000
|
-
text={polyglot.t("login-with-github")}
|
|
1001
|
-
/>
|
|
1002
|
-
)}
|
|
1003
|
-
</div>
|
|
1004
|
-
);
|
|
1005
|
-
};
|
|
775
|
+
return (
|
|
776
|
+
<I18nContext.Provider value={i18nContextValue}>
|
|
777
|
+
<div
|
|
778
|
+
className={`gt-container${isInputFocused ? " gt-input-focused" : ""} ${className}`}
|
|
779
|
+
{...restProps}
|
|
780
|
+
>
|
|
781
|
+
{/* Alert */}
|
|
782
|
+
{alert && <div className="gt-error">{alert}</div>}
|
|
1006
783
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
<Svg className="gt-ico-github" icon={Github} />
|
|
1020
|
-
</a>
|
|
1021
|
-
)}
|
|
1022
|
-
<div className="gt-header-comment">
|
|
1023
|
-
<textarea
|
|
1024
|
-
ref={textareaRef}
|
|
1025
|
-
className="gt-header-textarea"
|
|
1026
|
-
style={{ display: isPreviewComment ? "none" : undefined }}
|
|
1027
|
-
value={inputComment}
|
|
1028
|
-
onChange={(e) => setInputComment(e.target.value)}
|
|
1029
|
-
onFocus={onCommentInputFocus}
|
|
1030
|
-
onBlur={onCommentInputBlur}
|
|
1031
|
-
onKeyDown={onCommentInputKeyDown}
|
|
1032
|
-
placeholder={polyglot.t("leave-a-comment")}
|
|
1033
|
-
/>
|
|
1034
|
-
<div
|
|
1035
|
-
className="gt-header-preview markdown-body"
|
|
1036
|
-
style={{ display: isPreviewComment ? undefined : "none" }}
|
|
1037
|
-
dangerouslySetInnerHTML={{
|
|
1038
|
-
__html: commentHtml,
|
|
1039
|
-
}}
|
|
1040
|
-
/>
|
|
1041
|
-
<div className="gt-header-controls">
|
|
1042
|
-
<a
|
|
1043
|
-
className="gt-header-controls-tip"
|
|
1044
|
-
href="https://guides.github.com/features/mastering-markdown/"
|
|
1045
|
-
target="_blank"
|
|
1046
|
-
rel="noopener noreferrer"
|
|
1047
|
-
>
|
|
1048
|
-
<Svg
|
|
1049
|
-
className="gt-ico-tip"
|
|
1050
|
-
icon={Tip}
|
|
1051
|
-
text={polyglot.t("support-markdown")}
|
|
784
|
+
{initialized ? (
|
|
785
|
+
issueCreated ? (
|
|
786
|
+
<>
|
|
787
|
+
{/* Meta */}
|
|
788
|
+
<Meta
|
|
789
|
+
issue={issue}
|
|
790
|
+
user={user}
|
|
791
|
+
commentsCount={commentsCount}
|
|
792
|
+
pagerDirection={commentsPagerDirection}
|
|
793
|
+
onPagerDirectionChange={setCommentsPagerDirection}
|
|
794
|
+
onLogin={onLogin}
|
|
795
|
+
onLogout={onLogout}
|
|
1052
796
|
/>
|
|
1053
|
-
</a>
|
|
1054
|
-
|
|
1055
|
-
<Button
|
|
1056
|
-
className="gt-btn-preview gt-btn--secondary"
|
|
1057
|
-
onClick={onCommentInputPreview}
|
|
1058
|
-
text={
|
|
1059
|
-
isPreviewComment ? polyglot.t("edit") : polyglot.t("preview")
|
|
1060
|
-
}
|
|
1061
|
-
isLoading={getCommentHtmlLoading}
|
|
1062
|
-
disabled={false}
|
|
1063
|
-
/>
|
|
1064
|
-
|
|
1065
|
-
{user ? (
|
|
1066
|
-
<Button
|
|
1067
|
-
className="gt-btn-public"
|
|
1068
|
-
onClick={runCreateIssueComment}
|
|
1069
|
-
text={polyglot.t("comment")}
|
|
1070
|
-
isLoading={createIssueCommentLoading}
|
|
1071
|
-
disabled={createIssueCommentLoading || !inputComment}
|
|
1072
|
-
/>
|
|
1073
|
-
) : (
|
|
1074
|
-
<Button
|
|
1075
|
-
className="gt-btn-login"
|
|
1076
|
-
onClick={onLogin}
|
|
1077
|
-
text={polyglot.t("login-with-github")}
|
|
1078
|
-
/>
|
|
1079
|
-
)}
|
|
1080
|
-
</div>
|
|
1081
|
-
</div>
|
|
1082
|
-
</div>
|
|
1083
|
-
);
|
|
1084
|
-
};
|
|
1085
|
-
|
|
1086
|
-
// Why forwardRef? https://www.npmjs.com/package/react-flip-move#usage-with-functional-components
|
|
1087
|
-
const CommentWithForwardedRef = forwardRef<
|
|
1088
|
-
HTMLDivElement,
|
|
1089
|
-
{ comment: CommentType }
|
|
1090
|
-
>(({ comment }, ref) => {
|
|
1091
|
-
const {
|
|
1092
|
-
id: commentId,
|
|
1093
|
-
user: commentAuthor,
|
|
1094
|
-
reactionsHeart: commentReactionsHeart,
|
|
1095
|
-
} = comment;
|
|
1096
|
-
|
|
1097
|
-
const commentAuthorName = commentAuthor?.login;
|
|
1098
|
-
const isAuthor =
|
|
1099
|
-
!!user && !!commentAuthorName && user.login === commentAuthorName;
|
|
1100
|
-
const isAdmin =
|
|
1101
|
-
!!commentAuthorName &&
|
|
1102
|
-
!!admin.find(
|
|
1103
|
-
(username) =>
|
|
1104
|
-
username.toLowerCase() === commentAuthorName.toLowerCase(),
|
|
1105
|
-
);
|
|
1106
|
-
const heartReactionId = commentReactionsHeart?.nodes.find(
|
|
1107
|
-
(node) => node.user.login === user?.login,
|
|
1108
|
-
)?.databaseId;
|
|
1109
|
-
|
|
1110
|
-
return (
|
|
1111
|
-
<div ref={ref}>
|
|
1112
|
-
<Comment
|
|
1113
|
-
comment={comment}
|
|
1114
|
-
isAuthor={isAuthor}
|
|
1115
|
-
isAdmin={isAdmin}
|
|
1116
|
-
onReply={onReplyComment}
|
|
1117
|
-
onLike={(like) => {
|
|
1118
|
-
runLikeOrDislikeComment(like, commentId, heartReactionId);
|
|
1119
|
-
}}
|
|
1120
|
-
likeLoading={likeOrDislikeCommentLoading}
|
|
1121
|
-
/>
|
|
1122
|
-
</div>
|
|
1123
|
-
);
|
|
1124
|
-
});
|
|
1125
797
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
/>
|
|
1147
|
-
</div>
|
|
1148
|
-
) : null}
|
|
1149
|
-
</div>
|
|
1150
|
-
);
|
|
1151
|
-
};
|
|
1152
|
-
|
|
1153
|
-
const renderMeta = () => {
|
|
1154
|
-
const isDesc = commentsPagerDirection === "last";
|
|
798
|
+
{/* Comment textarea */}
|
|
799
|
+
<CommentTextarea
|
|
800
|
+
value={inputComment}
|
|
801
|
+
onChange={(e) => setInputComment(e.target.value)}
|
|
802
|
+
onFocus={onCommentInputFocus}
|
|
803
|
+
onBlur={onCommentInputBlur}
|
|
804
|
+
onKeyDown={onCommentInputKeyDown}
|
|
805
|
+
placeholder={polyglot.t("leave-a-comment")}
|
|
806
|
+
octokit={octokit}
|
|
807
|
+
user={user}
|
|
808
|
+
onLogin={onLogin}
|
|
809
|
+
onCreateComment={async () => {
|
|
810
|
+
await runCreateIssueComment();
|
|
811
|
+
}}
|
|
812
|
+
createCommentLoading={createIssueCommentLoading}
|
|
813
|
+
onPreviewError={(e) => {
|
|
814
|
+
setAlert(`Preview rendered comment failed: ${e}`);
|
|
815
|
+
logger.e(`Preview rendered comment failed:`, e);
|
|
816
|
+
}}
|
|
817
|
+
/>
|
|
1155
818
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
{user
|
|
1170
|
-
? [
|
|
1171
|
-
<Action
|
|
1172
|
-
key={"sort-asc"}
|
|
1173
|
-
className={`gt-action-sortasc${!isDesc ? " is--active" : ""}`}
|
|
1174
|
-
onClick={() => setCommentsPagerDirection("first")}
|
|
1175
|
-
text={polyglot.t("sort-asc")}
|
|
1176
|
-
/>,
|
|
1177
|
-
<Action
|
|
1178
|
-
key={"sort-desc"}
|
|
1179
|
-
className={`gt-action-sortdesc${isDesc ? " is--active" : ""}`}
|
|
1180
|
-
onClick={() => setCommentsPagerDirection("last")}
|
|
1181
|
-
text={polyglot.t("sort-desc")}
|
|
1182
|
-
/>,
|
|
1183
|
-
]
|
|
1184
|
-
: null}
|
|
1185
|
-
{user ? (
|
|
1186
|
-
<Action
|
|
1187
|
-
className="gt-action-logout"
|
|
1188
|
-
onClick={onLogout}
|
|
1189
|
-
text={polyglot.t("logout")}
|
|
819
|
+
{/* Comments */}
|
|
820
|
+
<CommentsList
|
|
821
|
+
comments={loadedComments}
|
|
822
|
+
commentsCount={commentsCount}
|
|
823
|
+
onGetComments={runGetComments}
|
|
824
|
+
getCommentsLoading={getCommentsLoading}
|
|
825
|
+
flipMoveOptions={flipMoveOptions}
|
|
826
|
+
user={user}
|
|
827
|
+
admin={admin}
|
|
828
|
+
onReply={onReplyComment}
|
|
829
|
+
onLike={runLikeOrDislikeComment}
|
|
830
|
+
likeLoading={likeOrDislikeCommentLoading}
|
|
831
|
+
collapsedHeight={collapsedHeight}
|
|
1190
832
|
/>
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
>
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
833
|
+
</>
|
|
834
|
+
) : (
|
|
835
|
+
// Issue not created placeholder
|
|
836
|
+
<div className="gt-no-init" key="no-init">
|
|
837
|
+
<p
|
|
838
|
+
dangerouslySetInnerHTML={{
|
|
839
|
+
__html: polyglot.t("no-found-related", {
|
|
840
|
+
link: `<a href="https://github.com/${owner}/${repo}/issues" target="_blank" rel="noopener noreferrer">Issues</a>`,
|
|
841
|
+
}),
|
|
842
|
+
}}
|
|
843
|
+
/>
|
|
844
|
+
<p>
|
|
845
|
+
{polyglot.t("please-contact", {
|
|
846
|
+
user: admin.map((u) => `@${u}`).join(" "),
|
|
847
|
+
})}
|
|
848
|
+
</p>
|
|
849
|
+
{isAdmin ? (
|
|
850
|
+
<p>
|
|
851
|
+
<Button
|
|
852
|
+
onClick={runCreateIssue}
|
|
853
|
+
isLoading={createIssueLoading}
|
|
854
|
+
text={polyglot.t("init-issue")}
|
|
855
|
+
/>
|
|
856
|
+
</p>
|
|
857
|
+
) : null}
|
|
858
|
+
{!user && (
|
|
859
|
+
<Button
|
|
860
|
+
className="gt-btn-login"
|
|
861
|
+
onClick={onLogin}
|
|
862
|
+
text={polyglot.t("login-with-github")}
|
|
863
|
+
/>
|
|
864
|
+
)}
|
|
1206
865
|
</div>
|
|
866
|
+
)
|
|
867
|
+
) : (
|
|
868
|
+
// Loading issue placeholder
|
|
869
|
+
<div className="gt-initing">
|
|
870
|
+
<i className="gt-loader" />
|
|
871
|
+
<p className="gt-initing-text">{polyglot.t("init")}</p>
|
|
1207
872
|
</div>
|
|
1208
873
|
)}
|
|
1209
|
-
<div className="gt-user">
|
|
1210
|
-
<div
|
|
1211
|
-
className={`gt-user-inner${showPopup ? " is--poping" : ""}`}
|
|
1212
|
-
onClick={onShowOrHidePopup}
|
|
1213
|
-
>
|
|
1214
|
-
<span className="gt-user-name">
|
|
1215
|
-
{user?.login ?? polyglot.t("anonymous")}
|
|
1216
|
-
</span>
|
|
1217
|
-
<Svg className="gt-ico-arrdown" icon={ArrowDown} />
|
|
1218
|
-
</div>
|
|
1219
|
-
</div>
|
|
1220
|
-
</div>
|
|
1221
|
-
);
|
|
1222
|
-
};
|
|
1223
|
-
|
|
1224
|
-
return (
|
|
1225
|
-
<I18nContext.Provider
|
|
1226
|
-
value={{ language, polyglot, dateFnsLocaleMap: DATE_FNS_LOCALE_MAP }}
|
|
1227
|
-
>
|
|
1228
|
-
<div
|
|
1229
|
-
className={`gt-container${isInputFocused ? " gt-input-focused" : ""} ${className}`}
|
|
1230
|
-
{...restProps}
|
|
1231
|
-
>
|
|
1232
|
-
{alert && <div className="gt-error">{alert}</div>}
|
|
1233
|
-
{initialized
|
|
1234
|
-
? issueCreated
|
|
1235
|
-
? [renderMeta(), renderHeader(), renderCommentList()]
|
|
1236
|
-
: renderIssueNotInitialized()
|
|
1237
|
-
: renderInitializing()}
|
|
1238
874
|
</div>
|
|
1239
875
|
</I18nContext.Provider>
|
|
1240
876
|
);
|
|
1241
877
|
};
|
|
1242
878
|
|
|
879
|
+
export type { GitalkProps };
|
|
880
|
+
|
|
1243
881
|
export default Gitalk;
|