gitalk-react 1.0.0-beta.6 → 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/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 Comment, { type CommentProps } from "./components/comment";
21
- import Svg from "./components/svg";
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
- staggerDelayBy: 150,
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("");
@@ -283,12 +115,22 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
283
115
  : DEFAULT_USER,
284
116
  [propsDefaultAuthor, propsDefaultUser],
285
117
  );
286
-
287
- const [showPopup, setShowPopup] = useState<boolean>(false);
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,
@@ -619,7 +461,7 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
619
461
  data: localComments = [],
620
462
  mutate: setLocalComments,
621
463
  loading: createIssueCommentLoading,
622
- run: runCreateIssueComment,
464
+ runAsync: runCreateIssueComment,
623
465
  } = useRequest(
624
466
  async (): Promise<CommentType[]> => {
625
467
  const { number: currentIssueNumber } = issue as IssueType;
@@ -715,35 +557,6 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
715
557
  updateCountCallback?.(commentsCount);
716
558
  }, [commentsCount, updateCountCallback]);
717
559
 
718
- const {
719
- data: commentHtml = "",
720
- mutate: setCommentHtml,
721
- loading: getCommentHtmlLoading,
722
- run: runGetCommentHtml,
723
- cancel: cancelGetCommentHtml,
724
- } = useRequest(
725
- async () => {
726
- const getPreviewedHtmlRes = await octokit.request("POST /markdown", {
727
- text: inputComment,
728
- });
729
-
730
- if (getPreviewedHtmlRes.status === 200) {
731
- const _commentHtml = getPreviewedHtmlRes.data;
732
- return _commentHtml;
733
- } else {
734
- setAlert(`Preview rendered comment failed: ${getPreviewedHtmlRes}`);
735
- logger.e(`Preview rendered comment failed:`, getPreviewedHtmlRes);
736
- return "";
737
- }
738
- },
739
- {
740
- manual: true,
741
- onBefore: () => {
742
- setCommentHtml("");
743
- },
744
- },
745
- );
746
-
747
560
  const { loading: likeOrDislikeCommentLoading, run: runLikeOrDislikeComment } =
748
561
  useRequest(
749
562
  async (like: boolean, commentId: number, reactionId?: number) => {
@@ -863,29 +676,6 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
863
676
  [initialized, issue],
864
677
  );
865
678
 
866
- const hidePopup = useCallback((e: MouseEvent) => {
867
- const target = e.target as HTMLElement;
868
- if (target && hasClassInParent(target, "gt-user", "gt-popup")) {
869
- return;
870
- }
871
- document.removeEventListener("click", hidePopup);
872
- setShowPopup(false);
873
- }, []);
874
-
875
- const onShowOrHidePopup: React.MouseEventHandler<HTMLDivElement> = (e) => {
876
- e.preventDefault();
877
- e.stopPropagation();
878
-
879
- setShowPopup((visible) => {
880
- if (visible) {
881
- document.removeEventListener("click", hidePopup);
882
- } else {
883
- document.addEventListener("click", hidePopup);
884
- }
885
- return !visible;
886
- });
887
- };
888
-
889
679
  const onLogin = () => {
890
680
  const url = getAuthorizeUrl(clientID);
891
681
  window.location.href = url;
@@ -911,6 +701,14 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
911
701
  setIsInputFocused(false);
912
702
  };
913
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
+
914
712
  const onCommentInputKeyDown: React.KeyboardEventHandler<
915
713
  HTMLTextAreaElement
916
714
  > = (e) => {
@@ -919,41 +717,35 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
919
717
  }
920
718
  };
921
719
 
922
- const onCommentInputPreview: React.MouseEventHandler<
923
- HTMLButtonElement
924
- > = () => {
925
- if (isPreviewComment) {
926
- setIsPreviewComment(false);
927
- cancelGetCommentHtml();
928
- } else {
929
- setIsPreviewComment(true);
930
- runGetCommentHtml();
931
- }
932
- };
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;
933
726
 
934
- const onReplyComment: CommentProps["onReply"] = (repliedComment) => {
935
- const { body: repliedCommentBody = "", user: repliedCommentUser } =
936
- repliedComment;
937
- let repliedCommentBodyArray = repliedCommentBody.split("\n");
938
- const repliedCommentUsername = repliedCommentUser?.login;
939
-
940
- if (repliedCommentUsername) {
941
- repliedCommentBodyArray.unshift(`@${repliedCommentUsername}`);
942
- }
943
- repliedCommentBodyArray = repliedCommentBodyArray.map(
944
- (text) => `> ${text}`,
945
- );
946
-
947
- if (inputComment) {
948
- repliedCommentBodyArray.unshift("", "");
949
- }
727
+ if (repliedCommentUsername) {
728
+ repliedCommentBodyArray.unshift(`@${repliedCommentUsername}`);
729
+ }
730
+ repliedCommentBodyArray = repliedCommentBodyArray.map(
731
+ (text) => `> ${text}`,
732
+ );
950
733
 
951
- repliedCommentBodyArray.push("", "");
734
+ setInputComment((prevComment) => {
735
+ if (prevComment) {
736
+ repliedCommentBodyArray.unshift("", "");
737
+ }
738
+ repliedCommentBodyArray.push("", "");
739
+ const newComment = `${prevComment}${repliedCommentBodyArray.join("\n")}`;
740
+ return newComment;
741
+ });
952
742
 
953
- const newComment = `${inputComment}${repliedCommentBodyArray.join("\n")}`;
954
- setInputComment(newComment);
955
- textareaRef.current?.focus();
956
- };
743
+ setTimeout(() => {
744
+ textareaRef.current?.focus();
745
+ });
746
+ },
747
+ [],
748
+ );
957
749
 
958
750
  if (!isModernBrowser) {
959
751
  logger.e(
@@ -980,284 +772,110 @@ const Gitalk: React.FC<GitalkProps> = (props) => {
980
772
  return null;
981
773
  }
982
774
 
983
- const renderInitializing = () => {
984
- return (
985
- <div className="gt-initing">
986
- <i className="gt-loader" />
987
- <p className="gt-initing-text">{polyglot.t("init")}</p>
988
- </div>
989
- );
990
- };
991
-
992
- const renderIssueNotInitialized = () => {
993
- return (
994
- <div className="gt-no-init" key="no-init">
995
- <p
996
- dangerouslySetInnerHTML={{
997
- __html: polyglot.t("no-found-related", {
998
- link: `<a href="https://github.com/${owner}/${repo}/issues" target="_blank" rel="noopener noreferrer">Issues</a>`,
999
- }),
1000
- }}
1001
- />
1002
- <p>
1003
- {polyglot.t("please-contact", {
1004
- user: admin.map((u) => `@${u}`).join(" "),
1005
- })}
1006
- </p>
1007
- {isAdmin ? (
1008
- <p>
1009
- <Button
1010
- onClick={runCreateIssue}
1011
- isLoading={createIssueLoading}
1012
- text={polyglot.t("init-issue")}
1013
- />
1014
- </p>
1015
- ) : null}
1016
- {!user && (
1017
- <Button
1018
- className="gt-btn-login"
1019
- onClick={onLogin}
1020
- text={polyglot.t("login-with-github")}
1021
- />
1022
- )}
1023
- </div>
1024
- );
1025
- };
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>}
1026
783
 
1027
- const renderHeader = () => {
1028
- return (
1029
- <div className="gt-header" key="header">
1030
- {user ? (
1031
- <Avatar
1032
- className="gt-header-avatar"
1033
- src={user.avatar_url}
1034
- alt={user.login}
1035
- href={user.html_url}
1036
- />
1037
- ) : (
1038
- <a className="gt-avatar-github" onClick={onLogin}>
1039
- <Svg className="gt-ico-github" icon={Github} />
1040
- </a>
1041
- )}
1042
- <div className="gt-header-comment">
1043
- <textarea
1044
- ref={textareaRef}
1045
- className="gt-header-textarea"
1046
- style={{ display: isPreviewComment ? "none" : undefined }}
1047
- value={inputComment}
1048
- onChange={(e) => setInputComment(e.target.value)}
1049
- onFocus={onCommentInputFocus}
1050
- onBlur={onCommentInputBlur}
1051
- onKeyDown={onCommentInputKeyDown}
1052
- placeholder={polyglot.t("leave-a-comment")}
1053
- />
1054
- <div
1055
- className="gt-header-preview markdown-body"
1056
- style={{ display: isPreviewComment ? undefined : "none" }}
1057
- dangerouslySetInnerHTML={{
1058
- __html: commentHtml,
1059
- }}
1060
- />
1061
- <div className="gt-header-controls">
1062
- <a
1063
- className="gt-header-controls-tip"
1064
- href="https://guides.github.com/features/mastering-markdown/"
1065
- target="_blank"
1066
- rel="noopener noreferrer"
1067
- >
1068
- <Svg
1069
- className="gt-ico-tip"
1070
- icon={Tip}
1071
- text={polyglot.t("support-markdown")}
1072
- />
1073
- </a>
1074
-
1075
- <Button
1076
- className="gt-btn-preview gt-btn--secondary"
1077
- onClick={onCommentInputPreview}
1078
- text={
1079
- isPreviewComment ? polyglot.t("edit") : polyglot.t("preview")
1080
- }
1081
- isLoading={getCommentHtmlLoading}
1082
- disabled={false}
1083
- />
1084
-
1085
- {user ? (
1086
- <Button
1087
- className="gt-btn-public"
1088
- onClick={runCreateIssueComment}
1089
- text={polyglot.t("comment")}
1090
- isLoading={createIssueCommentLoading}
1091
- disabled={createIssueCommentLoading || !inputComment}
1092
- />
1093
- ) : (
1094
- <Button
1095
- className="gt-btn-login"
1096
- onClick={onLogin}
1097
- text={polyglot.t("login-with-github")}
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}
1098
796
  />
1099
- )}
1100
- </div>
1101
- </div>
1102
- </div>
1103
- );
1104
- };
1105
-
1106
- // Why forwardRef? https://www.npmjs.com/package/react-flip-move#usage-with-functional-components
1107
- const CommentWithForwardedRef = forwardRef<
1108
- HTMLDivElement,
1109
- { comment: CommentType }
1110
- >(({ comment }, ref) => {
1111
- const {
1112
- id: commentId,
1113
- user: commentAuthor,
1114
- reactionsHeart: commentReactionsHeart,
1115
- } = comment;
1116
-
1117
- const commentAuthorName = commentAuthor?.login;
1118
- const isAuthor =
1119
- !!user && !!commentAuthorName && user.login === commentAuthorName;
1120
- const isAdmin =
1121
- !!commentAuthorName &&
1122
- !!admin.find(
1123
- (username) =>
1124
- username.toLowerCase() === commentAuthorName.toLowerCase(),
1125
- );
1126
- const heartReactionId = commentReactionsHeart?.nodes.find(
1127
- (node) => node.user.login === user?.login,
1128
- )?.databaseId;
1129
-
1130
- return (
1131
- <div ref={ref}>
1132
- <Comment
1133
- comment={comment}
1134
- isAuthor={isAuthor}
1135
- isAdmin={isAdmin}
1136
- onReply={onReplyComment}
1137
- onLike={(like) => {
1138
- runLikeOrDislikeComment(like, commentId, heartReactionId);
1139
- }}
1140
- likeLoading={likeOrDislikeCommentLoading}
1141
- />
1142
- </div>
1143
- );
1144
- });
1145
797
 
1146
- const renderCommentList = () => {
1147
- return (
1148
- <div className="gt-comments" key="comments">
1149
- <FlipMove {...flipMoveOptions}>
1150
- {loadedComments.map((comment) => (
1151
- <CommentWithForwardedRef key={comment.id} comment={comment} />
1152
- ))}
1153
- </FlipMove>
1154
- {!commentsCount && (
1155
- <p className="gt-comments-null">
1156
- {polyglot.t("first-comment-person")}
1157
- </p>
1158
- )}
1159
- {commentsCount > loadedComments.length ? (
1160
- <div className="gt-comments-controls">
1161
- <Button
1162
- className="gt-btn-loadmore"
1163
- onClick={runGetComments}
1164
- isLoading={getCommentsLoading}
1165
- text={polyglot.t("load-more")}
1166
- />
1167
- </div>
1168
- ) : null}
1169
- </div>
1170
- );
1171
- };
1172
-
1173
- const renderMeta = () => {
1174
- 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
+ />
1175
818
 
1176
- return (
1177
- <div className="gt-meta" key="meta">
1178
- <span
1179
- className="gt-counts"
1180
- dangerouslySetInnerHTML={{
1181
- __html: polyglot.t("counts", {
1182
- counts: `<a class="gt-link gt-link-counts" href="${issue?.html_url}" target="_blank" rel="noopener noreferrer">${commentsCount}</a>`,
1183
- smart_count: commentsCount,
1184
- }),
1185
- }}
1186
- />
1187
- {showPopup && (
1188
- <div className="gt-popup">
1189
- {user
1190
- ? [
1191
- <Action
1192
- key={"sort-asc"}
1193
- className={`gt-action-sortasc${!isDesc ? " is--active" : ""}`}
1194
- onClick={() => setCommentsPagerDirection("first")}
1195
- text={polyglot.t("sort-asc")}
1196
- />,
1197
- <Action
1198
- key={"sort-desc"}
1199
- className={`gt-action-sortdesc${isDesc ? " is--active" : ""}`}
1200
- onClick={() => setCommentsPagerDirection("last")}
1201
- text={polyglot.t("sort-desc")}
1202
- />,
1203
- ]
1204
- : null}
1205
- {user ? (
1206
- <Action
1207
- className="gt-action-logout"
1208
- onClick={onLogout}
1209
- 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}
832
+ />
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
+ }}
1210
843
  />
1211
- ) : (
1212
- <a className="gt-action gt-action-login" onClick={onLogin}>
1213
- {polyglot.t("login-with-github")}
1214
- </a>
1215
- )}
1216
- <div className="gt-copyright">
1217
- <a
1218
- className="gt-link gt-link-project"
1219
- href={HOMEPAGE}
1220
- target="_blank"
1221
- rel="noopener noreferrer"
1222
- >
1223
- GitalkR
1224
- </a>
1225
- <span className="gt-version">{VERSION}</span>
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
+ )}
1226
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>
1227
872
  </div>
1228
873
  )}
1229
- <div className="gt-user">
1230
- <div
1231
- className={`gt-user-inner${showPopup ? " is--poping" : ""}`}
1232
- onClick={onShowOrHidePopup}
1233
- >
1234
- <span className="gt-user-name">
1235
- {user?.login ?? polyglot.t("anonymous")}
1236
- </span>
1237
- <Svg className="gt-ico-arrdown" icon={ArrowDown} />
1238
- </div>
1239
- </div>
1240
- </div>
1241
- );
1242
- };
1243
-
1244
- return (
1245
- <I18nContext.Provider
1246
- value={{ language, polyglot, dateFnsLocaleMap: DATE_FNS_LOCALE_MAP }}
1247
- >
1248
- <div
1249
- className={`gt-container${isInputFocused ? " gt-input-focused" : ""} ${className}`}
1250
- {...restProps}
1251
- >
1252
- {alert && <div className="gt-error">{alert}</div>}
1253
- {initialized
1254
- ? issueCreated
1255
- ? [renderMeta(), renderHeader(), renderCommentList()]
1256
- : renderIssueNotInitialized()
1257
- : renderInitializing()}
1258
874
  </div>
1259
875
  </I18nContext.Provider>
1260
876
  );
1261
877
  };
1262
878
 
879
+ export type { GitalkProps };
880
+
1263
881
  export default Gitalk;