gitalk-react 1.0.0-beta.1

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.
Files changed (47) hide show
  1. package/LICENSE +9 -0
  2. package/README-zh-CN.md +257 -0
  3. package/README.md +257 -0
  4. package/dist/gitalk-dark.css +1 -0
  5. package/dist/gitalk-light.css +1 -0
  6. package/dist/gitalk.d.ts +445 -0
  7. package/dist/gitalk.js +12560 -0
  8. package/dist/gitalk.umd.cjs +121 -0
  9. package/lib/assets/arrow-down.svg +1 -0
  10. package/lib/assets/edit.svg +3 -0
  11. package/lib/assets/github.svg +3 -0
  12. package/lib/assets/heart-filled.svg +3 -0
  13. package/lib/assets/heart.svg +3 -0
  14. package/lib/assets/reply.svg +3 -0
  15. package/lib/assets/tip.svg +8 -0
  16. package/lib/components/action.tsx +21 -0
  17. package/lib/components/avatar.tsx +42 -0
  18. package/lib/components/button.tsx +35 -0
  19. package/lib/components/comment.tsx +153 -0
  20. package/lib/components/svg.tsx +29 -0
  21. package/lib/constants/index.ts +43 -0
  22. package/lib/contexts/I18nContext.ts +18 -0
  23. package/lib/gitalk.tsx +1231 -0
  24. package/lib/i18n/de.json +20 -0
  25. package/lib/i18n/en.json +20 -0
  26. package/lib/i18n/es-ES.json +20 -0
  27. package/lib/i18n/fa.json +20 -0
  28. package/lib/i18n/fr.json +20 -0
  29. package/lib/i18n/index.ts +40 -0
  30. package/lib/i18n/ja.json +20 -0
  31. package/lib/i18n/ko.json +20 -0
  32. package/lib/i18n/pl.json +21 -0
  33. package/lib/i18n/ru.json +20 -0
  34. package/lib/i18n/zh-CN.json +20 -0
  35. package/lib/i18n/zh-TW.json +20 -0
  36. package/lib/interfaces/index.ts +30 -0
  37. package/lib/services/graphql/comment.ts +85 -0
  38. package/lib/services/request.ts +24 -0
  39. package/lib/services/user.ts +40 -0
  40. package/lib/themes/base.scss +592 -0
  41. package/lib/themes/gitalk-dark.scss +24 -0
  42. package/lib/themes/gitalk-light.scss +24 -0
  43. package/lib/utils/compatibility.ts +35 -0
  44. package/lib/utils/dom.ts +15 -0
  45. package/lib/utils/logger.ts +56 -0
  46. package/lib/utils/query.ts +19 -0
  47. package/package.json +83 -0
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" p-id="1619"><path d="M511.872 676.8c-0.003 0-0.006 0-0.008 0-9.137 0-17.379-3.829-23.21-9.97l-251.277-265.614c-5.415-5.72-8.743-13.464-8.744-21.984 0-17.678 14.33-32.008 32.008-32.008 9.157 0 17.416 3.845 23.25 10.009l228.045 241.103 228.224-241.088c5.855-6.165 14.113-10.001 23.266-10.001 8.516 0 16.256 3.32 21.998 8.736 12.784 12.145 13.36 32.434 1.264 45.233l-251.52 265.6c-5.844 6.155-14.086 9.984-23.223 9.984-0.025 0-0.051 0-0.076 0z" p-id="1620"></path></svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M785.333333 85.333333C774.666667 85.333333 763.2 90.133333 754.666667 98.666667L682.666667 170.666667 853.333333 341.333333 925.333333 269.333333C942.4 252.266667 942.4 222.133333 925.333333 209.333333L814.666667 98.666667C806.133333 90.133333 796 85.333333 785.333333 85.333333zM640 217.333333 85.333333 768 85.333333 938.666667 256 938.666667 806.666667 384 640 217.333333z"></path>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M64 524C64 719.602 189.356 885.926 364.113 947.017 387.65799 953 384 936.115 384 924.767L384 847.107C248.118 863.007 242.674 773.052 233.5 758.001 215 726.501 171.5 718.501 184.5 703.501 215.5 687.501 247 707.501 283.5 761.501 309.956 800.642 361.366 794.075 387.658 787.497 393.403 763.997 405.637 743.042 422.353 726.638 281.774 701.609 223 615.67 223 513.5 223 464.053 239.322 418.406 271.465 381.627 251.142 320.928 273.421 269.19 276.337 261.415 334.458 256.131 394.888 302.993 399.549 306.685 432.663 297.835 470.341 293 512.5 293 554.924 293 592.81 297.896 626.075 306.853 637.426 298.219 693.46 258.054 747.5 262.966 750.382 270.652 772.185 321.292 753.058 381.083 785.516 417.956 802 463.809 802 513.5 802 615.874 742.99 701.953 601.803 726.786 625.381 750.003 640 782.295 640 818.008L640 930.653C640.752 939.626 640 948.664978 655.086 948.665 832.344 888.962 960 721.389 960 524 960 276.576 759.424 76 512 76 264.577 76 64 276.576 64 524Z"></path>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg t="1512463363724" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <path d="M527.061333 166.528A277.333333 277.333333 0 0 1 1000.618667 362.666667a277.333333 277.333333 0 0 1-81.28 196.138666l-377.173334 377.173334a42.666667 42.666667 0 0 1-60.330666 0l-377.173334-377.173334a277.376 277.376 0 0 1 392.277334-392.277333l15.061333 15.061333 15.061333-15.061333z"></path>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <path d="M527.061333 166.528A277.333333 277.333333 0 0 1 1000.618667 362.666667a277.333333 277.333333 0 0 1-81.28 196.138666l-377.173334 377.173334a42.666667 42.666667 0 0 1-60.330666 0l-377.173334-377.173334a277.376 277.376 0 0 1 392.277334-392.277333l15.061333 15.061333 15.061333-15.061333z m286.72 377.173333l45.226667-45.226666a192 192 0 0 0-135.808-327.893334 192 192 0 0 0-135.808 56.32l-45.226667 45.226667a42.666667 42.666667 0 0 1-60.330666 0l-45.226667-45.226667a192.042667 192.042667 0 0 0-271.616 271.573334L512 845.482667l301.781333-301.781334z"></path>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 1332 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M529.066665 273.066666 529.066665 0 51.2 477.866666 529.066665 955.733335 529.066665 675.84C870.4 675.84 1109.333335 785.066665 1280 1024 1211.733335 682.666665 1006.933335 341.333334 529.066665 273.066666"></path>
3
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M512 366.949535c-16.065554 0-29.982212 13.405016-29.982212 29.879884l0 359.070251c0 16.167882 13.405016 29.879884 29.982212 29.879884 15.963226 0 29.879884-13.405016 29.879884-29.879884L541.879884 396.829419C541.879884 380.763865 528.474868 366.949535 512 366.949535L512 366.949535z"
3
+ p-id="3083"></path>
4
+ <path d="M482.017788 287.645048c0-7.776956 3.274508-15.553912 8.80024-21.181973 5.525732-5.525732 13.302688-8.80024 21.181973-8.80024 7.776956 0 15.553912 3.274508 21.079644 8.80024 5.525732 5.62806 8.80024 13.405016 8.80024 21.181973 0 7.776956-3.274508 15.656241-8.80024 21.181973-5.525732 5.525732-13.405016 8.697911-21.079644 8.697911-7.879285 0-15.656241-3.274508-21.181973-8.697911C485.292295 303.301289 482.017788 295.524333 482.017788 287.645048L482.017788 287.645048z"
5
+ p-id="3084"></path>
6
+ <path d="M512 946.844409c-239.8577 0-434.895573-195.037873-434.895573-434.895573 0-239.8577 195.037873-434.895573 434.895573-434.895573 239.755371 0 434.895573 195.037873 434.895573 434.895573C946.895573 751.806535 751.755371 946.844409 512 946.844409zM512 126.17088c-212.740682 0-385.880284 173.037274-385.880284 385.777955 0 212.740682 173.037274 385.777955 385.880284 385.777955 212.740682 0 385.777955-173.037274 385.777955-385.777955C897.777955 299.208154 724.740682 126.17088 512 126.17088z"
7
+ p-id="3085"></path>
8
+ </svg>
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+
3
+ export interface ActionProps
4
+ extends React.DetailedHTMLProps<
5
+ React.AnchorHTMLAttributes<HTMLAnchorElement>,
6
+ HTMLAnchorElement
7
+ > {
8
+ text: string;
9
+ }
10
+
11
+ const Action: React.FC<ActionProps> = ({
12
+ text,
13
+ className = "",
14
+ ...restProps
15
+ }) => (
16
+ <a className={`gt-action ${className}`} {...restProps}>
17
+ <span className="gt-action-text">{text}</span>
18
+ </a>
19
+ );
20
+
21
+ export default Action;
@@ -0,0 +1,42 @@
1
+ import React, { useState } from "react";
2
+
3
+ import { DEFAULT_AVATAR } from "../constants";
4
+
5
+ export interface AvatarProps
6
+ extends React.DetailedHTMLProps<
7
+ React.AnchorHTMLAttributes<HTMLAnchorElement>,
8
+ HTMLAnchorElement
9
+ > {
10
+ src?: string;
11
+ alt?: string;
12
+ defaultSrc?: string;
13
+ }
14
+
15
+ const Avatar: React.FC<AvatarProps> = ({
16
+ src,
17
+ alt,
18
+ defaultSrc = DEFAULT_AVATAR,
19
+ className = "",
20
+ ...restProps
21
+ }) => {
22
+ const [imgSrc, setImgSrc] = useState<string>(src ?? defaultSrc);
23
+
24
+ return (
25
+ <a
26
+ className={`gt-avatar ${className}`}
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ {...restProps}
30
+ >
31
+ <img
32
+ src={imgSrc}
33
+ alt={`@${alt}`}
34
+ onError={() => {
35
+ setImgSrc(defaultSrc);
36
+ }}
37
+ />
38
+ </a>
39
+ );
40
+ };
41
+
42
+ export default Avatar;
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+
3
+ export interface ButtonProps
4
+ extends React.DetailedHTMLProps<
5
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
6
+ HTMLButtonElement
7
+ > {
8
+ text: string;
9
+ isLoading?: boolean;
10
+ }
11
+
12
+ const Button: React.FC<ButtonProps> = ({
13
+ text,
14
+ isLoading = false,
15
+ className = "",
16
+ style,
17
+ disabled: propsDisabled,
18
+ ...restProps
19
+ }) => {
20
+ const disabled = propsDisabled !== undefined ? propsDisabled : isLoading;
21
+
22
+ return (
23
+ <button
24
+ className={`gt-btn ${className}`}
25
+ style={{ cursor: disabled ? "not-allowed" : undefined, ...style }}
26
+ disabled={disabled}
27
+ {...restProps}
28
+ >
29
+ <span className="gt-btn-text">{text}</span>
30
+ {isLoading && <span className="gt-btn-loading gt-spinner" />}
31
+ </button>
32
+ );
33
+ };
34
+
35
+ export default Button;
@@ -0,0 +1,153 @@
1
+ import { formatDistanceToNow, parseISO } from "date-fns";
2
+ import React, { useContext, useEffect, useMemo, useRef } from "react";
3
+
4
+ import Edit from "../assets/edit.svg?raw";
5
+ import Heart from "../assets/heart.svg?raw";
6
+ import HeartFilled from "../assets/heart-filled.svg?raw";
7
+ import Reply from "../assets/reply.svg?raw";
8
+ import I18nContext from "../contexts/I18nContext";
9
+ import type { Comment as CommentType } from "../interfaces";
10
+ import Avatar from "./avatar";
11
+ import Svg from "./svg";
12
+
13
+ export interface CommentProps
14
+ extends React.DetailedHTMLProps<
15
+ React.HTMLAttributes<HTMLDivElement>,
16
+ HTMLDivElement
17
+ > {
18
+ comment: CommentType;
19
+ isAuthor: boolean;
20
+ isAdmin: boolean;
21
+ onReply: (comment: CommentType) => void;
22
+ onLike: (like: boolean, comment: CommentType) => void;
23
+ likeLoading: boolean;
24
+ }
25
+
26
+ const Comment: React.FC<CommentProps> = ({
27
+ comment,
28
+ isAuthor,
29
+ isAdmin,
30
+ onReply,
31
+ onLike,
32
+ likeLoading,
33
+ className = "",
34
+ ...restProps
35
+ }) => {
36
+ const ref = useRef<HTMLDivElement>(null);
37
+
38
+ const { language, polyglot, dateFnsLocaleMap } = useContext(I18nContext);
39
+
40
+ const { body_html, created_at, user, reactionsHeart, html_url } = comment;
41
+ const commentHtmlUrl = html_url.includes("github.com")
42
+ ? html_url
43
+ : `https://github.com/${html_url}`;
44
+
45
+ const reactionsHeartCountText = useMemo(() => {
46
+ const totalCount = reactionsHeart?.totalCount ?? 0;
47
+ return totalCount > 100 ? "100+" : String(totalCount);
48
+ }, [reactionsHeart]);
49
+
50
+ useEffect(() => {
51
+ const commentElement = ref.current;
52
+
53
+ if (commentElement) {
54
+ const emailResponse = commentElement.querySelector(
55
+ ".email-hidden-toggle>a",
56
+ );
57
+
58
+ if (emailResponse) {
59
+ const onEmailClick = (e: Event) => {
60
+ e.preventDefault();
61
+ commentElement
62
+ ?.querySelector(".email-hidden-reply")
63
+ ?.classList.toggle("expanded");
64
+ };
65
+ emailResponse.addEventListener("click", onEmailClick, true);
66
+
67
+ return () =>
68
+ emailResponse.removeEventListener("click", onEmailClick, true);
69
+ }
70
+ }
71
+
72
+ return () => {};
73
+ }, []);
74
+
75
+ return (
76
+ <div
77
+ ref={ref}
78
+ className={`gt-comment ${isAdmin ? "gt-comment-admin" : ""} ${className}`}
79
+ {...restProps}
80
+ >
81
+ <Avatar
82
+ className="gt-comment-avatar"
83
+ src={user?.avatar_url}
84
+ alt={user?.login}
85
+ href={user?.html_url}
86
+ />
87
+
88
+ <div className="gt-comment-content">
89
+ <div className="gt-comment-header">
90
+ <a
91
+ className="gt-comment-username"
92
+ href={user?.html_url}
93
+ target="_blank"
94
+ rel="noopener noreferrer"
95
+ >
96
+ {user?.login}
97
+ </a>
98
+ <div className="gt-comment-date" title={created_at}>
99
+ {polyglot.t("commented") +
100
+ " " +
101
+ formatDistanceToNow(parseISO(created_at), {
102
+ addSuffix: true,
103
+ locale: dateFnsLocaleMap[language],
104
+ })}
105
+ </div>
106
+ <div className="gt-comment-actions">
107
+ <a
108
+ className="gt-comment-like"
109
+ title="Like"
110
+ onClick={() => {
111
+ if (reactionsHeart && !likeLoading)
112
+ onLike(!reactionsHeart.viewerHasReacted, comment);
113
+ }}
114
+ >
115
+ <Svg
116
+ className="gt-ico-heart"
117
+ icon={reactionsHeart?.viewerHasReacted ? HeartFilled : Heart}
118
+ text={reactionsHeartCountText}
119
+ />
120
+ </a>
121
+ {isAuthor && (
122
+ // TODO: 支持在 Gitalk 里编辑
123
+ <a
124
+ href={commentHtmlUrl}
125
+ className="gt-comment-edit"
126
+ title="Edit"
127
+ target="_blank"
128
+ rel="noopener noreferrer"
129
+ >
130
+ <Svg className="gt-ico-edit" icon={Edit} />
131
+ </a>
132
+ )}
133
+ <a
134
+ className="gt-comment-reply"
135
+ title="Reply"
136
+ onClick={() => onReply(comment)}
137
+ >
138
+ <Svg className="gt-ico-reply" icon={Reply} />
139
+ </a>
140
+ </div>
141
+ </div>
142
+ <div
143
+ className="gt-comment-body markdown-body"
144
+ dangerouslySetInnerHTML={{
145
+ __html: body_html ?? "",
146
+ }}
147
+ />
148
+ </div>
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default Comment;
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+
3
+ export interface SVGProps
4
+ extends React.DetailedHTMLProps<
5
+ React.HTMLAttributes<HTMLSpanElement>,
6
+ HTMLSpanElement
7
+ > {
8
+ icon: string;
9
+ text?: string;
10
+ }
11
+
12
+ const Svg: React.FC<SVGProps> = ({
13
+ icon,
14
+ text,
15
+ className = "",
16
+ ...restProps
17
+ }) => (
18
+ <span className={`gt-ico ${className}`} {...restProps}>
19
+ <span
20
+ className="gt-svg"
21
+ dangerouslySetInnerHTML={{
22
+ __html: icon,
23
+ }}
24
+ />
25
+ {text && <span className="gt-ico-text">{text}</span>}
26
+ </span>
27
+ );
28
+
29
+ export default Svg;
@@ -0,0 +1,43 @@
1
+ import {
2
+ de,
3
+ enUS,
4
+ es,
5
+ faIR,
6
+ fr,
7
+ ja,
8
+ ko,
9
+ type Locale,
10
+ pl,
11
+ ru,
12
+ zhCN,
13
+ zhTW,
14
+ } from "date-fns/locale";
15
+
16
+ import packageJson from "../../package.json";
17
+ import { type Lang } from "../i18n";
18
+
19
+ export const VERSION = packageJson.version;
20
+ export const HOMEPAGE = packageJson.homepage;
21
+
22
+ export const DEFAULT_LANG: Lang = "en";
23
+ export const DEFAULT_LABELS = ["Gitalk"];
24
+ export const DEFAULT_AVATAR =
25
+ "https://cdn.jsdelivr.net/npm/gitalk@1/src/assets/icon/github.svg";
26
+
27
+ export const ACCESS_TOKEN_KEY = "GT_ACCESS_TOKEN";
28
+ export const STORED_COMMENTS_KEY = "GT_COMMENT";
29
+
30
+ export const DATE_FNS_LOCALE_MAP: Record<Lang, Locale> = {
31
+ zh: zhCN,
32
+ "zh-CN": zhCN,
33
+ "zh-TW": zhTW,
34
+ en: enUS,
35
+ "es-ES": es,
36
+ fr,
37
+ ru,
38
+ de,
39
+ pl,
40
+ ko,
41
+ fa: faIR,
42
+ ja,
43
+ };
@@ -0,0 +1,18 @@
1
+ import { type Locale } from "date-fns";
2
+ import type Polyglot from "node-polyglot";
3
+ import { createContext } from "react";
4
+
5
+ import { DATE_FNS_LOCALE_MAP, DEFAULT_LANG } from "../constants";
6
+ import i18n, { type Lang } from "../i18n";
7
+
8
+ export const I18nContext = createContext<{
9
+ language: Lang;
10
+ polyglot: Polyglot;
11
+ dateFnsLocaleMap: Record<string, Locale>;
12
+ }>({
13
+ language: DEFAULT_LANG,
14
+ polyglot: i18n(DEFAULT_LANG),
15
+ dateFnsLocaleMap: DATE_FNS_LOCALE_MAP,
16
+ });
17
+
18
+ export default I18nContext;