diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index d9dc107a..852777a1 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -1,5 +1,8 @@ { - "about": "About", + "about": { + "notfound": "Set the article alias to `about` to create an about page", + "title": "About" + }, "alert": "Alert", "alias": "Alias", "article": { @@ -70,6 +73,11 @@ "draft": "Draft", "draft_bin": "Draft Bin", "edit": "Edit", + "error": { + "api_url": "API_URL in Pages environment variables should be the Worker address. Please check if API_URL in Pages environment variables is correct.", + "api_url_slash": "API_URL in Pages environment variables should be the Worker address. Please check if API_URL in Pages environment variables is correct and ensure API_URL does not end with a slash (/).", + "github_callback": "GitHub OAuth callback URL should be `https:///user/github/callback`. Please check if the GitHub OAuth callback URL is correct." + }, "feed_card": { "published$time": "Published at {{time}}", "updated$time": "Updated at {{time}}" @@ -101,9 +109,16 @@ }, "listed": "Listed in articles", "login": { - "required": "Login required" + "oauth_only": "Please log in to continue", + "password": { + "placeholder": "Please enter your password" + }, + "required": "Please log in to continue", + "title": "Login", + "username": { + "placeholder": "Please enter your username" + } }, - "logout": "Logout", "next": "Next Page", "preview": "Preview", "previous": "Previous Page", @@ -206,4 +221,4 @@ }, "writing": "Writing", "year$year": "{{year}}" -} +} \ No newline at end of file diff --git a/client/public/locales/ja/translation.json b/client/public/locales/ja/translation.json index e395ece5..3ab41aa4 100644 --- a/client/public/locales/ja/translation.json +++ b/client/public/locales/ja/translation.json @@ -1,5 +1,8 @@ { - "about": "概要", + "about": { + "notfound": "About ページを作成するには、記事のエイリアスを about に設定します", + "title": "概要" + }, "alert": "警告", "alias": "エイリアス", "article": { @@ -70,6 +73,11 @@ "draft": "下書き", "draft_bin": "下書き", "edit": "編集", + "error": { + "api_url": "Pages 環境変数のAPI_URLはWorkerアドレスである必要があります。Pages環境変数のAPI_URLが正しいか確認してください。", + "api_url_slash": "Pages 環境変数のAPI_URLはWorkerアドレスである必要があります。Pages環境変数のAPI_URLが正しいか、またAPI_URLの末尾にスラッシュ(/)がないか確認してください。", + "github_callback": "GitHub OAuthコールバックURLは`https:///user/github/callback`である必要があります。GitHub OAuthコールバックURLが正しいか確認してください。" + }, "feed_card": { "published$time": "{{time}} に公開", "updated$time": "{{time}} に更新" @@ -101,9 +109,16 @@ }, "listed": "記事にリスト", "login": { - "required": "ログインが必要です" + "oauth_only": "続行するにはログインしてください", + "password": { + "placeholder": "パスワードを入力してください" + }, + "required": "続行するにはログインしてください", + "title": "ログイン", + "username": { + "placeholder": "ユーザー名を入力してください" + } }, - "logout": "ログアウト", "next": "次のページ", "preview": "プレビュー", "previous": "前のページ", @@ -206,4 +221,4 @@ }, "writing": "執筆", "year$year": "{{year}} 年" -} +} \ No newline at end of file diff --git a/client/public/locales/zh/translation.json b/client/public/locales/zh/translation.json index e3f8e9dd..3b0c5904 100644 --- a/client/public/locales/zh/translation.json +++ b/client/public/locales/zh/translation.json @@ -1,5 +1,8 @@ { - "about": "关于", + "about": { + "notfound": "将文章别名设置为 about 可以创建关于页面", + "title": "关于" + }, "alert": "提示", "alias": "别名", "article": { @@ -70,6 +73,11 @@ "draft": "草稿", "draft_bin": "草稿箱", "edit": "编辑", + "error": { + "api_url": "Pages 环境变量中 API_URL 应为 Worker 地址,请检查 Pages 环境变量中 API_URL 是否正确", + "api_url_slash": "Pages 环境变量中 API_URL 应为 Worker 地址,请检查 Pages 环境变量中 API_URL 是否正确,且 API_URL 末尾不能有 /", + "github_callback": "GitHub OAuth 回调地址应为 `https:///user/github/callback,请检查 GitHub OAuth 回调地址是否正确" + }, "feed_card": { "published$time": "{{time}} 发布", "updated$time": "{{time}} 更新" @@ -101,9 +109,16 @@ }, "listed": "列出在文章中", "login": { - "required": "需要登录" + "oauth_only": "请登录后继续", + "password": { + "placeholder": "请输入密码" + }, + "required": "请登录后继续", + "title": "登录", + "username": { + "placeholder": "请输入用户名" + } }, - "logout": "退出登录", "next": "下一页", "preview": "预览", "previous": "上一页", diff --git a/client/src/App.tsx b/client/src/App.tsx index 9b283116..7fe3ced8 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -21,9 +21,12 @@ import { Profile, ProfileContext } from './state/profile' import { headersWithAuth } from './utils/auth' import { tryInt } from './utils/int' import { SearchPage } from './page/search.tsx' +import { Tips, TipsPage } from './components/tips.tsx' +import { useTranslation } from 'react-i18next' function App() { const ref = useRef(false) + const { t } = useTranslation() const [profile, setProfile] = useState() const [config, setConfig] = useState(new ConfigWrapper({}, new Map())) useEffect(() => { @@ -133,6 +136,30 @@ function App() { }} + + {_ => ( + + + + )} + + + + {_ => ( + + + + )} + + + + {_ => ( + + + + )} + + {/* Default route in a switch */} 404: No such page! diff --git a/client/src/base.css b/client/src/base.css index 8d9dbcf0..76e26b7a 100644 --- a/client/src/base.css +++ b/client/src/base.css @@ -10,8 +10,8 @@ @apply bg-neutral-100 dark:bg-neutral-700; } - .bg-active { - @apply active:bg-neutral-200 dark:active:bg-neutral-600; + .bg-button { + @apply hover:bg-neutral-200 dark:hover:bg-neutral-600 active:bg-neutral-300 dark:active:bg-neutral-500; } .bg-hover { diff --git a/client/src/components/button.tsx b/client/src/components/button.tsx index 8480484b..0ef4f3be 100644 --- a/client/src/components/button.tsx +++ b/client/src/components/button.tsx @@ -2,7 +2,7 @@ import ReactLoading from "react-loading"; export function Button({ title, onClick, secondary = false }: { title: string, secondary?: boolean, onClick: () => void }) { return ( - ); @@ -10,7 +10,7 @@ export function Button({ title, onClick, secondary = false }: { title: string, s export function ButtonWithLoading({ title, onClick, loading, secondary = false }: { title: string, secondary?: boolean, loading: boolean, onClick: () => void }) { return ( - - } - ) -} - function Menu() { const profile = useContext(ProfileContext); const [isOpen, setOpen] = useState(false) @@ -152,7 +125,7 @@ function Menu() {
- +
@@ -174,7 +147,7 @@ function NavBar({ menu, onClick }: { menu: boolean, onClick?: () => void }) { - + @@ -194,7 +167,7 @@ function LanguageSwitch({ className }: { className?: string }) {
+ className="flex rounded-full border dark:border-neutral-600 px-2 bg-w aspect-[1] items-center justify-center t-primary bg-button"> } @@ -227,14 +200,15 @@ function SearchButton({ className, onClose }: { className?: string, onClose?: () const key = `${encodeURIComponent(value)}` setTimeout(() => { setIsOpened(false) - onClose?.() + if (value.length !== 0) + onClose?.() }, 100) if (value.length !== 0) setLocation(`/search/${key}`) } return (
) +} + + +function UserAvatar({ className, profile, onClose }: { className?: string, profile?: Profile, onClose?: () => void }) { + const { t } = useTranslation() + const { LoginModal, setIsOpened } = useLoginModal(onClose) + const label = t('github_login') + + return (
+ {profile?.avatar ? <> +
+ Avatar +
+ { + removeCookie("token") + window.location.reload() + }} hover={false} /> +
+
+ : <> + + } + +
+ ) } \ No newline at end of file diff --git a/client/src/components/icon.tsx b/client/src/components/icon.tsx index bb9d2b3d..c544b387 100644 --- a/client/src/components/icon.tsx +++ b/client/src/components/icon.tsx @@ -1,6 +1,6 @@ export function Icon({ name, label, className, onClick, hover = true }: { name: string, label: string, className?: string, onClick: () => any, hover?: boolean }) { return ( - ) @@ -8,7 +8,7 @@ export function Icon({ name, label, className, onClick, hover = true }: { name: export function IconSmall({ name, label, className, onClick, hover = true }: { name: string, label: string, className?: string, onClick: () => any, hover?: boolean }) { return ( - ) diff --git a/client/src/components/tips.tsx b/client/src/components/tips.tsx new file mode 100644 index 00000000..e5306d25 --- /dev/null +++ b/client/src/components/tips.tsx @@ -0,0 +1,52 @@ +import { useTranslation } from "react-i18next"; +import { Button } from "./button"; + +export function Tips({ value, type = 'tips' }: { value: string, type?: 'note' | 'tips' | 'warn' | 'error' | 'info' | 'important' }) { + const { t } = useTranslation(); + let className = "" + switch (type) { + case 'note': + className = "markdown-alert-note" + break; + case 'tips': + className = "markdown-alert-tip" + break; + case 'warn': + className = "markdown-alert-warn" + break; + case 'error': + className = "markdown-alert-caution" + break; + case 'info': + className = "markdown-alert-info" + break; + case 'important': + className = "markdown-alert-important" + break; + } + + return ( +
+

{type.toUpperCase()}

+

+ {t(value)} +

+
+ ); +} + +export function TipsPage({ children }: { children: React.ReactNode }) { + const { t } = useTranslation(); + return ( +
+
+

Oops!

+ {children} +
+
+ ); +} diff --git a/client/src/hooks/useLoginModal.tsx b/client/src/hooks/useLoginModal.tsx new file mode 100644 index 00000000..d07c0946 --- /dev/null +++ b/client/src/hooks/useLoginModal.tsx @@ -0,0 +1,74 @@ +import { t } from "i18next"; +import { Button } from "primereact/button"; +import { useCallback, useState } from "react"; +import ReactModal from "react-modal"; +import { Icon } from "../components/icon"; +import { Input } from "../components/input"; +import { oauth_url } from "../main"; + +export function useLoginModal(onClose?: () => void) { + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [isOpened, setIsOpened] = useState(false); + const onLogin = useCallback(() => { + setTimeout(() => { + setIsOpened(false) + onClose?.() + }, 100) + }, [username, password]) + const LoginModal = useCallback(() => { + return ( + setIsOpened(false)} + > +
+

{t('login.title')}

+ {false && <> + + +
+
+ + } +
+

{t('login.oauth_only')}

+
+ { + window.location.href = `${oauth_url}` + }} hover={true} /> +
+
+
+
+ ) + }, [username, password, isOpened, onLogin]) + return { LoginModal, setIsOpened } +} \ No newline at end of file diff --git a/client/src/page/feed.tsx b/client/src/page/feed.tsx index de6f6969..1a7ce358 100644 --- a/client/src/page/feed.tsx +++ b/client/src/page/feed.tsx @@ -14,6 +14,9 @@ import { ProfileContext } from "../state/profile"; import { headersWithAuth } from "../utils/auth"; import { siteName } from "../utils/constants"; import { timeago } from "../utils/timeago"; +import { Button } from "../components/button"; +import { Tips } from "../components/tips"; +import { useLoginModal } from "../hooks/useLoginModal"; type Feed = { id: number; @@ -161,14 +164,15 @@ export function FeedPage({ id, TOC, clean }: { id: string, TOC: () => JSX.Elemen
{error && ( <> -
+

{error}

- + />
)} @@ -227,21 +231,21 @@ export function FeedPage({ id, TOC, clean }: { id: string, TOC: () => JSX.Elemen @@ -318,7 +322,7 @@ export function TOCHeader({ TOC }: { TOC: () => JSX.Element }) { flexDirection: "column", justifyContent: "center", alignItems: "center", - background: "white", + background: "none", }, overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)", @@ -346,12 +350,18 @@ function CommentInput({ const [content, setContent] = useState(""); const [error, setError] = useState(""); const { showAlert, AlertUI } = useAlert(); + const profile = useContext(ProfileContext); + const { LoginModal, setIsOpened } = useLoginModal() function errorHumanize(error: string) { if (error === "Unauthorized") return t("login.required"); else if (error === "Content is required") return t("comment.empty"); return error; } function submit() { + if (!profile) { + setIsOpened(true) + return; + } client.feed .comment({ feed: id }) .post( @@ -374,8 +384,10 @@ function CommentInput({ } return (
-
+
+
+ {profile ? (<>