mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: update i18n
This commit is contained in:
@ -45,15 +45,13 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="dialog-content-container">
|
<div className="dialog-content-container">
|
||||||
<p>
|
<p>{t("slogan")}</p>
|
||||||
Memos is an <i>open source</i>, <i>self-hosted</i> knowledge base that works with a SQLite db file.
|
|
||||||
</p>
|
|
||||||
<br />
|
<br />
|
||||||
<div className="addtion-info-container">
|
<div className="addtion-info-container">
|
||||||
<GitHubBadge />
|
<GitHubBadge />
|
||||||
<Only when={profile !== undefined}>
|
<Only when={profile !== undefined}>
|
||||||
<>
|
<>
|
||||||
version:
|
{t("common.version")}:
|
||||||
<span className="pre-text">
|
<span className="pre-text">
|
||||||
{profile?.version}-{profile?.mode}
|
{profile?.version}-{profile?.mode}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { IMAGE_URL_REG } from "../helpers/consts";
|
import { IMAGE_URL_REG } from "../helpers/consts";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import { memoService } from "../services";
|
import { memoService } from "../services";
|
||||||
import { formatMemoContent } from "../helpers/marked";
|
import { formatMemoContent } from "../helpers/marked";
|
||||||
@ -19,6 +20,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
|||||||
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
|
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
|
||||||
archivedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
|
archivedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
|
||||||
};
|
};
|
||||||
|
const { t } = useI18n();
|
||||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||||
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
||||||
|
|
||||||
@ -60,10 +62,11 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
|||||||
<span className="time-text">Archived at {memo.archivedAtStr}</span>
|
<span className="time-text">Archived at {memo.archivedAtStr}</span>
|
||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
<span className="btn restore-btn" onClick={handleRestoreMemoClick}>
|
<span className="btn restore-btn" onClick={handleRestoreMemoClick}>
|
||||||
Restore
|
{t("common.restore")}
|
||||||
</span>
|
</span>
|
||||||
<span className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`} onClick={handleDeleteMemoClick}>
|
<span className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`} onClick={handleDeleteMemoClick}>
|
||||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
{t("common.delete")}
|
||||||
|
{showConfirmDeleteBtn ? "!" : ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
@ -16,6 +17,7 @@ const validateConfig: ValidatorConfig = {
|
|||||||
interface Props extends DialogProps {}
|
interface Props extends DialogProps {}
|
||||||
|
|
||||||
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||||
|
const { t } = useI18n();
|
||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||||
|
|
||||||
@ -85,10 +87,10 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|||||||
</label>
|
</label>
|
||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
|
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
|
||||||
Cancel
|
{t("common.cancel")}
|
||||||
</span>
|
</span>
|
||||||
<span className="btn confirm-btn" onClick={handleSaveBtnClick}>
|
<span className="btn confirm-btn" onClick={handleSaveBtnClick}>
|
||||||
Save
|
{t("common.save")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
|
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
|
||||||
|
import useI18n from "../../hooks/useI18n";
|
||||||
import useRefresh from "../../hooks/useRefresh";
|
import useRefresh from "../../hooks/useRefresh";
|
||||||
import Only from "../common/OnlyWhen";
|
import Only from "../common/OnlyWhen";
|
||||||
import "../../less/editor.less";
|
import "../../less/editor.less";
|
||||||
@ -37,6 +38,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
|
|||||||
onCancelBtnClick: handleCancelBtnClickCallback,
|
onCancelBtnClick: handleCancelBtnClickCallback,
|
||||||
onContentChange: handleContentChangeCallback,
|
onContentChange: handleContentChangeCallback,
|
||||||
} = props;
|
} = props;
|
||||||
|
const { t } = useI18n();
|
||||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
|
|
||||||
@ -130,12 +132,12 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
|
|||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
<Only when={showCancelBtn}>
|
<Only when={showCancelBtn}>
|
||||||
<button className="action-btn cancel-btn" onClick={handleCommonCancelBtnClick}>
|
<button className="action-btn cancel-btn" onClick={handleCommonCancelBtnClick}>
|
||||||
Cancel editting
|
{t("editor.cancel-edit")}
|
||||||
</button>
|
</button>
|
||||||
</Only>
|
</Only>
|
||||||
<Only when={showConfirmBtn}>
|
<Only when={showConfirmBtn}>
|
||||||
<button className="action-btn confirm-btn" disabled={editorRef.current?.value === ""} onClick={handleCommonConfirmBtnClick}>
|
<button className="action-btn confirm-btn" disabled={editorRef.current?.value === ""} onClick={handleCommonConfirmBtnClick}>
|
||||||
Save <span className="icon-text">✍️</span>
|
{t("editor.save")} <span className="icon-text">✍️</span>
|
||||||
</button>
|
</button>
|
||||||
</Only>
|
</Only>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,8 @@ import { memo, useEffect, useRef, useState } from "react";
|
|||||||
import { indexOf } from "lodash-es";
|
import { indexOf } from "lodash-es";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import "dayjs/locale/zh";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import { IMAGE_URL_REG, UNKNOWN_ID } from "../helpers/consts";
|
import { IMAGE_URL_REG, UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { DONE_BLOCK_REG, formatMemoContent, TODO_BLOCK_REG } from "../helpers/marked";
|
import { DONE_BLOCK_REG, formatMemoContent, TODO_BLOCK_REG } from "../helpers/marked";
|
||||||
import { editorStateService, locationService, memoService, userService } from "../services";
|
import { editorStateService, locationService, memoService, userService } from "../services";
|
||||||
@ -27,20 +29,21 @@ interface State {
|
|||||||
expandButtonStatus: ExpandButtonStatus;
|
expandButtonStatus: ExpandButtonStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFormatedMemoCreatedAtStr = (createdTs: number): string => {
|
export const getFormatedMemoCreatedAtStr = (createdTs: number, locale = "en"): string => {
|
||||||
if (Date.now() - createdTs < 1000 * 60 * 60 * 24) {
|
if (Date.now() - createdTs < 1000 * 60 * 60 * 24) {
|
||||||
return dayjs(createdTs).fromNow();
|
return dayjs(createdTs).locale(locale).fromNow();
|
||||||
} else {
|
} else {
|
||||||
return dayjs(createdTs).format("YYYY/MM/DD HH:mm:ss");
|
return dayjs(createdTs).locale(locale).format("YYYY/MM/DD HH:mm:ss");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Memo: React.FC<Props> = (props: Props) => {
|
const Memo: React.FC<Props> = (props: Props) => {
|
||||||
const memo = props.memo;
|
const memo = props.memo;
|
||||||
|
const { t, locale } = useI18n();
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
expandButtonStatus: -1,
|
expandButtonStatus: -1,
|
||||||
});
|
});
|
||||||
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs));
|
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
||||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
||||||
const isVisitorMode = userService.isVisitorMode();
|
const isVisitorMode = userService.isVisitorMode();
|
||||||
@ -59,10 +62,10 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
|
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
setCreatedAtStr(dayjs(memo.createdTs).fromNow());
|
setCreatedAtStr(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
||||||
}, 1000 * 1);
|
}, 1000 * 1);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [locale]);
|
||||||
|
|
||||||
const handleShowMemoStoryDialog = () => {
|
const handleShowMemoStoryDialog = () => {
|
||||||
showMemoCardDialog(memo);
|
showMemoCardDialog(memo);
|
||||||
@ -186,25 +189,25 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||||
<Icon.MapPin className={`icon-img ${memo.pinned ? "" : "opacity-20"}`} />
|
<Icon.MapPin className={`icon-img ${memo.pinned ? "" : "opacity-20"}`} />
|
||||||
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
|
<span className="tip-text">{memo.pinned ? t("common.unpin") : t("common.pin")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="btn" onClick={handleEditMemoClick}>
|
<div className="btn" onClick={handleEditMemoClick}>
|
||||||
<Icon.Edit3 className="icon-img" />
|
<Icon.Edit3 className="icon-img" />
|
||||||
<span className="tip-text">Edit</span>
|
<span className="tip-text">{t("common.edit")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
||||||
<Icon.Share className="icon-img" />
|
<Icon.Share className="icon-img" />
|
||||||
<span className="tip-text">Share</span>
|
<span className="tip-text">{t("common.share")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="btn" onClick={handleMarkMemoClick}>
|
<span className="btn" onClick={handleMarkMemoClick}>
|
||||||
Mark
|
{t("common.mark")}
|
||||||
</span>
|
</span>
|
||||||
<span className="btn" onClick={handleShowMemoStoryDialog}>
|
<span className="btn" onClick={handleShowMemoStoryDialog}>
|
||||||
View Story
|
View Story
|
||||||
</span>
|
</span>
|
||||||
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
|
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
|
||||||
Archive
|
{t("common.archive")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
import { UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import * as storage from "../helpers/storage";
|
import * as storage from "../helpers/storage";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -16,6 +17,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MemoEditor: React.FC<Props> = () => {
|
const MemoEditor: React.FC<Props> = () => {
|
||||||
|
const { t, locale } = useI18n();
|
||||||
const editorState = useAppSelector((state) => state.editor);
|
const editorState = useAppSelector((state) => state.editor);
|
||||||
const tags = useAppSelector((state) => state.memo.tags);
|
const tags = useAppSelector((state) => state.memo.tags);
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
@ -212,7 +214,7 @@ const MemoEditor: React.FC<Props> = () => {
|
|||||||
() => ({
|
() => ({
|
||||||
className: "memo-editor",
|
className: "memo-editor",
|
||||||
initialContent: getEditorContentCache(),
|
initialContent: getEditorContentCache(),
|
||||||
placeholder: "Any thoughts...",
|
placeholder: t("editor.placeholder"),
|
||||||
fullscreen: state.fullscreen,
|
fullscreen: state.fullscreen,
|
||||||
showConfirmBtn: true,
|
showConfirmBtn: true,
|
||||||
showCancelBtn: isEditing,
|
showCancelBtn: isEditing,
|
||||||
@ -220,7 +222,7 @@ const MemoEditor: React.FC<Props> = () => {
|
|||||||
onCancelBtnClick: handleCancelBtnClick,
|
onCancelBtnClick: handleCancelBtnClick,
|
||||||
onContentChange: handleContentChange,
|
onContentChange: handleContentChange,
|
||||||
}),
|
}),
|
||||||
[isEditing, state.fullscreen]
|
[isEditing, state.fullscreen, locale]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import { locationService, userService } from "../services";
|
import { locationService, userService } from "../services";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||||
@ -13,6 +14,7 @@ interface Props {
|
|||||||
|
|
||||||
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||||
const { shownStatus, setShownStatus } = props;
|
const { shownStatus, setShownStatus } = props;
|
||||||
|
const { t } = useI18n();
|
||||||
const popupElRef = useRef<HTMLDivElement>(null);
|
const popupElRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -63,14 +65,14 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}>
|
<div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}>
|
||||||
<button className="btn action-btn" onClick={handleAboutBtnClick}>
|
<button className="btn action-btn" onClick={handleAboutBtnClick}>
|
||||||
<span className="icon">🤠</span> About
|
<span className="icon">🤠</span> {t("common.about")}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn action-btn" onClick={handlePingBtnClick}>
|
<button className="btn action-btn" onClick={handlePingBtnClick}>
|
||||||
<span className="icon">🎯</span> Ping
|
<span className="icon">🎯</span> Ping
|
||||||
</button>
|
</button>
|
||||||
<Only when={!userService.isVisitorMode()}>
|
<Only when={!userService.isVisitorMode()}>
|
||||||
<button className="btn action-btn" onClick={handleSignOutBtnClick}>
|
<button className="btn action-btn" onClick={handleSignOutBtnClick}>
|
||||||
<span className="icon">👋</span> Sign out
|
<span className="icon">👋</span> {t("common.sign-out")}
|
||||||
</button>
|
</button>
|
||||||
</Only>
|
</Only>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import MyAccountSection from "./Settings/MyAccountSection";
|
import MyAccountSection from "./Settings/MyAccountSection";
|
||||||
import PreferencesSection from "./Settings/PreferencesSection";
|
import PreferencesSection from "./Settings/PreferencesSection";
|
||||||
import MemberSection from "./Settings/MemberSection";
|
import MemberSection from "./Settings/MemberSection";
|
||||||
import "../less/setting-dialog.less";
|
import "../less/setting-dialog.less";
|
||||||
import Icon from "./Icon";
|
|
||||||
|
|
||||||
interface Props extends DialogProps {}
|
interface Props extends DialogProps {}
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ interface State {
|
|||||||
|
|
||||||
const SettingDialog: React.FC<Props> = (props: Props) => {
|
const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
|
const { t } = useI18n();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
selectedSection: "my-account",
|
selectedSection: "my-account",
|
||||||
@ -34,30 +36,30 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
|
|||||||
<Icon.X className="icon-img" />
|
<Icon.X className="icon-img" />
|
||||||
</button>
|
</button>
|
||||||
<div className="section-selector-container">
|
<div className="section-selector-container">
|
||||||
<span className="section-title">Basic</span>
|
<span className="section-title">{t("common.basic")}</span>
|
||||||
<div className="section-items-container">
|
<div className="section-items-container">
|
||||||
<span
|
<span
|
||||||
onClick={() => handleSectionSelectorItemClick("my-account")}
|
onClick={() => handleSectionSelectorItemClick("my-account")}
|
||||||
className={`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`}
|
className={`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`}
|
||||||
>
|
>
|
||||||
<span className="icon-text">🤠</span> My account
|
<span className="icon-text">🤠</span> {t("setting.my-account")}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
onClick={() => handleSectionSelectorItemClick("preferences")}
|
onClick={() => handleSectionSelectorItemClick("preferences")}
|
||||||
className={`section-item ${state.selectedSection === "preferences" ? "selected" : ""}`}
|
className={`section-item ${state.selectedSection === "preferences" ? "selected" : ""}`}
|
||||||
>
|
>
|
||||||
<span className="icon-text">🏟</span> Preferences
|
<span className="icon-text">🏟</span> {t("setting.preference")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{user?.role === "HOST" ? (
|
{user?.role === "HOST" ? (
|
||||||
<>
|
<>
|
||||||
<span className="section-title">Admin</span>
|
<span className="section-title">{t("common.admin")}</span>
|
||||||
<div className="section-items-container">
|
<div className="section-items-container">
|
||||||
<span
|
<span
|
||||||
onClick={() => handleSectionSelectorItemClick("member")}
|
onClick={() => handleSectionSelectorItemClick("member")}
|
||||||
className={`section-item ${state.selectedSection === "member" ? "selected" : ""}`}
|
className={`section-item ${state.selectedSection === "member" ? "selected" : ""}`}
|
||||||
>
|
>
|
||||||
<span className="icon-text">👤</span> Member
|
<span className="icon-text">👤</span> {t("setting.member")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { isEmpty } from "lodash-es";
|
import { isEmpty } from "lodash-es";
|
||||||
|
import useI18n from "../../hooks/useI18n";
|
||||||
import { userService } from "../../services";
|
import { userService } from "../../services";
|
||||||
import { useAppSelector } from "../../store";
|
import { useAppSelector } from "../../store";
|
||||||
import * as api from "../../helpers/api";
|
import * as api from "../../helpers/api";
|
||||||
@ -16,6 +17,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PreferencesSection: React.FC<Props> = () => {
|
const PreferencesSection: React.FC<Props> = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
const currentUser = useAppSelector((state) => state.user.user);
|
const currentUser = useAppSelector((state) => state.user.user);
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
createUserEmail: "",
|
createUserEmail: "",
|
||||||
@ -110,18 +112,18 @@ const PreferencesSection: React.FC<Props> = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="section-container member-section-container">
|
<div className="section-container member-section-container">
|
||||||
<p className="title-text">Create a member</p>
|
<p className="title-text">{t("setting.member-section.create-a-member")}</p>
|
||||||
<div className="create-member-container">
|
<div className="create-member-container">
|
||||||
<div className="input-form-container">
|
<div className="input-form-container">
|
||||||
<span className="field-text">Email</span>
|
<span className="field-text">{t("common.email")}</span>
|
||||||
<input type="email" placeholder="Email" value={state.createUserEmail} onChange={handleEmailInputChange} />
|
<input type="email" placeholder={t("common.email")} value={state.createUserEmail} onChange={handleEmailInputChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="input-form-container">
|
<div className="input-form-container">
|
||||||
<span className="field-text">Password</span>
|
<span className="field-text">{t("common.password")}</span>
|
||||||
<input type="text" placeholder="Password" value={state.createUserPassword} onChange={handlePasswordInputChange} />
|
<input type="text" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
<button onClick={handleCreateUserBtnClick}>Create</button>
|
<button onClick={handleCreateUserBtnClick}>{t("common.create")}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="title-text">Member list</p>
|
<p className="title-text">Member list</p>
|
||||||
@ -140,12 +142,12 @@ const PreferencesSection: React.FC<Props> = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Dropdown className="actions-dropdown">
|
<Dropdown className="actions-dropdown">
|
||||||
{user.rowStatus === "NORMAL" ? (
|
{user.rowStatus === "NORMAL" ? (
|
||||||
<button onClick={() => handleArchiveUserClick(user)}>Archive</button>
|
<button onClick={() => handleArchiveUserClick(user)}>{t("common.archive")}</button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button onClick={() => handleRestoreUserClick(user)}>Restore</button>
|
<button onClick={() => handleRestoreUserClick(user)}>{t("common.restore")}</button>
|
||||||
<button className="delete" onClick={() => handleDeleteUserClick(user)}>
|
<button className="delete" onClick={() => handleDeleteUserClick(user)}>
|
||||||
Delete
|
{t("common.delete")}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import useI18n from "../../hooks/useI18n";
|
||||||
import { useAppSelector } from "../../store";
|
import { useAppSelector } from "../../store";
|
||||||
import { userService } from "../../services";
|
import { userService } from "../../services";
|
||||||
import { validate, ValidatorConfig } from "../../helpers/validator";
|
import { validate, ValidatorConfig } from "../../helpers/validator";
|
||||||
@ -17,6 +18,7 @@ const validateConfig: ValidatorConfig = {
|
|||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
const MyAccountSection: React.FC<Props> = () => {
|
const MyAccountSection: React.FC<Props> = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
const user = useAppSelector((state) => state.user.user as User);
|
const user = useAppSelector((state) => state.user.user as User);
|
||||||
const [username, setUsername] = useState<string>(user.name);
|
const [username, setUsername] = useState<string>(user.name);
|
||||||
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
||||||
@ -68,17 +70,17 @@ const MyAccountSection: React.FC<Props> = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="section-container account-section-container">
|
<div className="section-container account-section-container">
|
||||||
<p className="title-text">Account Information</p>
|
<p className="title-text">{t("setting.account-section.title")}</p>
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
<span className="normal-text">Email:</span>
|
<span className="normal-text">{t("common.email")}:</span>
|
||||||
<span className="normal-text">{user.email}</span>
|
<span className="normal-text">{user.email}</span>
|
||||||
</label>
|
</label>
|
||||||
<label className="form-label input-form-label username-label">
|
<label className="form-label input-form-label username-label">
|
||||||
<span className="normal-text">Username:</span>
|
<span className="normal-text">{t("common.username")}:</span>
|
||||||
<input type="text" value={username} onChange={handleUsernameChanged} />
|
<input type="text" value={username} onChange={handleUsernameChanged} />
|
||||||
<div className={`btns-container ${username === user.name ? "!hidden" : ""}`} onClick={handlePreventDefault}>
|
<div className={`btns-container ${username === user.name ? "!hidden" : ""}`} onClick={handlePreventDefault}>
|
||||||
<span className="btn confirm-btn" onClick={handleConfirmEditUsernameBtnClick}>
|
<span className="btn confirm-btn" onClick={handleConfirmEditUsernameBtnClick}>
|
||||||
Save
|
{t("common.save")}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="btn cancel-btn"
|
className="btn cancel-btn"
|
||||||
@ -86,14 +88,14 @@ const MyAccountSection: React.FC<Props> = () => {
|
|||||||
setUsername(user.name);
|
setUsername(user.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("common.cancel")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label className="form-label password-label">
|
<label className="form-label password-label">
|
||||||
<span className="normal-text">Password:</span>
|
<span className="normal-text">{t("common.password")}:</span>
|
||||||
<span className="btn" onClick={handleChangePasswordBtnClick}>
|
<span className="btn" onClick={handleChangePasswordBtnClick}>
|
||||||
Change it
|
{t("common.change")}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -101,10 +103,9 @@ const MyAccountSection: React.FC<Props> = () => {
|
|||||||
<p className="title-text">Open API</p>
|
<p className="title-text">Open API</p>
|
||||||
<p className="value-text">{openAPIRoute}</p>
|
<p className="value-text">{openAPIRoute}</p>
|
||||||
<span className="reset-btn" onClick={handleResetOpenIdBtnClick}>
|
<span className="reset-btn" onClick={handleResetOpenIdBtnClick}>
|
||||||
Reset API
|
{t("common.reset")} API
|
||||||
</span>
|
</span>
|
||||||
<div className="usage-guide-container">
|
<div className="usage-guide-container">
|
||||||
<p className="title-text">Usage guide:</p>
|
|
||||||
<pre>{`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "Hello #memos from ${window.location.origin}"\n}`}</pre>
|
<pre>{`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "Hello #memos from ${window.location.origin}"\n}`}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { globalService, memoService, userService } from "../../services";
|
import { globalService, userService } from "../../services";
|
||||||
import * as utils from "../../helpers/utils";
|
|
||||||
import { useAppSelector } from "../../store";
|
import { useAppSelector } from "../../store";
|
||||||
import Only from "../common/OnlyWhen";
|
import useI18n from "../../hooks/useI18n";
|
||||||
import toastHelper from "../Toast";
|
|
||||||
import Selector from "../common/Selector";
|
import Selector from "../common/Selector";
|
||||||
import "../../less/settings/preferences-section.less";
|
import "../../less/settings/preferences-section.less";
|
||||||
|
|
||||||
@ -20,66 +18,9 @@ const localeSelectorItems = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const PreferencesSection: React.FC<Props> = () => {
|
const PreferencesSection: React.FC<Props> = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
const { setting } = useAppSelector((state) => state.user.user as User);
|
const { setting } = useAppSelector((state) => state.user.user as User);
|
||||||
|
|
||||||
const handleExportBtnClick = async () => {
|
|
||||||
const formatedMemos = memoService.getState().memos.map((m) => {
|
|
||||||
return {
|
|
||||||
content: m.content,
|
|
||||||
createdTs: m.createdTs,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const jsonStr = JSON.stringify(formatedMemos);
|
|
||||||
const element = document.createElement("a");
|
|
||||||
element.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(jsonStr));
|
|
||||||
element.setAttribute("download", `memos-${utils.getDateTimeString(Date.now())}.json`);
|
|
||||||
element.style.display = "none";
|
|
||||||
document.body.appendChild(element);
|
|
||||||
element.click();
|
|
||||||
document.body.removeChild(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImportBtnClick = async () => {
|
|
||||||
const fileInputEl = document.createElement("input");
|
|
||||||
fileInputEl.type = "file";
|
|
||||||
fileInputEl.accept = "application/JSON";
|
|
||||||
fileInputEl.onchange = () => {
|
|
||||||
if (fileInputEl.files?.length && fileInputEl.files.length > 0) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsText(fileInputEl.files[0]);
|
|
||||||
reader.onload = async (event) => {
|
|
||||||
const memoList = JSON.parse(event.target?.result as string) as Memo[];
|
|
||||||
if (!Array.isArray(memoList)) {
|
|
||||||
toastHelper.error("Unexpected data type.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let succeedAmount = 0;
|
|
||||||
|
|
||||||
for (const memo of memoList) {
|
|
||||||
const content = memo.content || "";
|
|
||||||
const createdTs = (memo as any).createdAt || memo.createdTs || Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const memoCreate = {
|
|
||||||
content,
|
|
||||||
createdTs: Math.floor(utils.getTimeStampByDate(createdTs) / 1000),
|
|
||||||
};
|
|
||||||
await memoService.createMemo(memoCreate);
|
|
||||||
succeedAmount++;
|
|
||||||
} catch (error) {
|
|
||||||
// do nth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await memoService.fetchAllMemos();
|
|
||||||
toastHelper.success(`${succeedAmount} memos successfully imported.`);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fileInputEl.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLocaleChanged = async (value: string) => {
|
const handleLocaleChanged = async (value: string) => {
|
||||||
globalService.setLocale(value as Locale);
|
globalService.setLocale(value as Locale);
|
||||||
await userService.upsertUserSetting("locale", value);
|
await userService.upsertUserSetting("locale", value);
|
||||||
@ -87,22 +28,10 @@ const PreferencesSection: React.FC<Props> = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="section-container preferences-section-container">
|
<div className="section-container preferences-section-container">
|
||||||
{/* Hide export/import buttons */}
|
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
<span className="normal-text">Language:</span>
|
<span className="normal-text">{t("common.language")}:</span>
|
||||||
<Selector className="ml-2 w-28" value={setting.locale} dataSource={localeSelectorItems} handleValueChanged={handleLocaleChanged} />
|
<Selector className="ml-2 w-28" value={setting.locale} dataSource={localeSelectorItems} handleValueChanged={handleLocaleChanged} />
|
||||||
</label>
|
</label>
|
||||||
<Only when={false}>
|
|
||||||
<p className="title-text">Others</p>
|
|
||||||
<div className="btns-container">
|
|
||||||
<button className="btn" onClick={handleExportBtnClick}>
|
|
||||||
Export data as JSON
|
|
||||||
</button>
|
|
||||||
<button className="btn" onClick={handleImportBtnClick}>
|
|
||||||
Import from JSON
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Only>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import toImage from "../labs/html2image";
|
import toImage from "../labs/html2image";
|
||||||
import { ANIMATION_DURATION, IMAGE_URL_REG } from "../helpers/consts";
|
import { ANIMATION_DURATION, IMAGE_URL_REG } from "../helpers/consts";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { formatMemoContent } from "../helpers/marked";
|
import { formatMemoContent } from "../helpers/marked";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
@ -16,6 +17,7 @@ interface Props extends DialogProps {
|
|||||||
|
|
||||||
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { memo: propsMemo, destroy } = props;
|
const { memo: propsMemo, destroy } = props;
|
||||||
|
const { t } = useI18n();
|
||||||
const { user: userinfo } = userService.getState();
|
const { user: userinfo } = userService.getState();
|
||||||
const memo = {
|
const memo = {
|
||||||
...propsMemo,
|
...propsMemo,
|
||||||
@ -73,7 +75,8 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
|||||||
<>
|
<>
|
||||||
<div className="dialog-header-container">
|
<div className="dialog-header-container">
|
||||||
<p className="title-text">
|
<p className="title-text">
|
||||||
<span className="icon-text">🌄</span>Share Memo
|
<span className="icon-text">🌄</span>
|
||||||
|
{t("common.share")} Memo
|
||||||
</p>
|
</p>
|
||||||
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
||||||
<Icon.X className="icon-img" />
|
<Icon.X className="icon-img" />
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { locationService, shortcutService } from "../services";
|
import { locationService, shortcutService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
@ -59,6 +60,7 @@ interface ShortcutContainerProps {
|
|||||||
|
|
||||||
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
||||||
const { shortcut, isActive } = props;
|
const { shortcut, isActive } = props;
|
||||||
|
const { t } = useI18n();
|
||||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||||
|
|
||||||
const handleShortcutClick = () => {
|
const handleShortcutClick = () => {
|
||||||
@ -119,17 +121,18 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
|||||||
<div className="action-btns-wrapper">
|
<div className="action-btns-wrapper">
|
||||||
<div className="action-btns-container">
|
<div className="action-btns-container">
|
||||||
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
||||||
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
{shortcut.rowStatus === "ARCHIVED" ? t("common.unpin") : t("common.pin")}
|
||||||
</span>
|
</span>
|
||||||
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
||||||
Edit
|
{t("common.edit")}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
||||||
onClick={handleDeleteMemoClick}
|
onClick={handleDeleteMemoClick}
|
||||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
onMouseLeave={handleDeleteBtnMouseLeave}
|
||||||
>
|
>
|
||||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
{t("common.delete")}
|
||||||
|
{showConfirmDeleteBtn ? "!" : ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||||
@ -14,14 +15,14 @@ import "../less/siderbar.less";
|
|||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
const Sidebar: React.FC<Props> = () => {
|
const Sidebar: React.FC<Props> = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const handleMyAccountBtnClick = () => {
|
const handleMyAccountBtnClick = () => {
|
||||||
showSettingDialog();
|
showSettingDialog();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResourcesBtnClick = () => {
|
const handleResourcesBtnClick = () => {
|
||||||
showResourcesDialog();
|
showResourcesDialog();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleArchivedBtnClick = () => {
|
const handleArchivedBtnClick = () => {
|
||||||
showArchivedMemoDialog();
|
showArchivedMemoDialog();
|
||||||
};
|
};
|
||||||
@ -37,17 +38,17 @@ const Sidebar: React.FC<Props> = () => {
|
|||||||
<UsageHeatMap />
|
<UsageHeatMap />
|
||||||
<div className="action-btns-container">
|
<div className="action-btns-container">
|
||||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||||
<span className="icon">📅</span> Daily Review
|
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
||||||
</button>
|
</button>
|
||||||
<Only when={!userService.isVisitorMode()}>
|
<Only when={!userService.isVisitorMode()}>
|
||||||
<button className="btn action-btn" onClick={handleResourcesBtnClick}>
|
<button className="btn action-btn" onClick={handleResourcesBtnClick}>
|
||||||
<span className="icon">🌄</span> Resources
|
<span className="icon">🌄</span> {t("sidebar.resources")}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
||||||
<span className="icon">⚙️</span> Setting
|
<span className="icon">⚙️</span> {t("sidebar.setting")}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
||||||
<span className="icon">🗂</span> Archived
|
<span className="icon">🗂</span> {t("sidebar.archived")}
|
||||||
</button>
|
</button>
|
||||||
</Only>
|
</Only>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
|||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { locationService, memoService, userService } from "../services";
|
import { locationService, memoService, userService } from "../services";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
@ -16,6 +17,7 @@ interface Tag {
|
|||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
const TagList: React.FC<Props> = () => {
|
const TagList: React.FC<Props> = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const query = useAppSelector((state) => state.location.query);
|
||||||
const [tags, setTags] = useState<Tag[]>([]);
|
const [tags, setTags] = useState<Tag[]>([]);
|
||||||
@ -75,9 +77,7 @@ const TagList: React.FC<Props> = () => {
|
|||||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
||||||
))}
|
))}
|
||||||
<Only when={!userService.isVisitorMode() && tags.length < 5}>
|
<Only when={!userService.isVisitorMode() && tags.length < 5}>
|
||||||
<p className="tag-tip-container">
|
<p className="tip-text">{t("tag-list.tip-text")}</p>
|
||||||
Enter <span className="code-text">#tag </span> to create a tag
|
|
||||||
</p>
|
|
||||||
</Only>
|
</Only>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .addtion-info-container {
|
> .addtion-info-container {
|
||||||
@apply flex flex-row justify-start items-center;
|
@apply flex flex-row text-sm justify-start items-center;
|
||||||
|
|
||||||
> .github-badge-container {
|
> .github-badge-container {
|
||||||
@apply mr-2;
|
@apply mr-4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,12 +61,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .tag-tip-container {
|
> .tip-text {
|
||||||
@apply w-full mt-2 pl-4 text-sm text-gray-400;
|
@apply w-full mt-2 pl-4 text-sm text-gray-400 font-mono;
|
||||||
|
|
||||||
> .code-text {
|
|
||||||
@apply p-1 mx-1 text-blue-600 font-mono whitespace-pre-line bg-blue-100 rounded;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,56 @@
|
|||||||
"about": "About",
|
"about": "About",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"sign-in": "Sign in"
|
"username": "Username",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"create": "Create",
|
||||||
|
"change": "Change",
|
||||||
|
"reset": "Reset",
|
||||||
|
"language": "Language",
|
||||||
|
"version": "Version",
|
||||||
|
"pin": "Pin",
|
||||||
|
"unpin": "Unpin",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"share": "Share",
|
||||||
|
"mark": "Mark",
|
||||||
|
"archive": "Archive",
|
||||||
|
"basic": "Basic",
|
||||||
|
"admin": "Admin",
|
||||||
|
"sign-in": "Sign in",
|
||||||
|
"sign-out": "Sign out",
|
||||||
|
"back-to-home": "Back to Home"
|
||||||
},
|
},
|
||||||
"slogan": "An open source, self-hosted knowledge base that works with a SQLite db file.",
|
"slogan": "An open source, self-hosted knowledge base that works with a SQLite db file.",
|
||||||
"auth": {
|
"auth": {
|
||||||
"signup-as-host": "Sign up as Host",
|
"signup-as-host": "Sign up as Host",
|
||||||
"host-tip": "You are registering as the Site Host.",
|
"host-tip": "You are registering as the Site Host.",
|
||||||
"not-host-tip": "If you don't have an account, please contact the site host."
|
"not-host-tip": "If you don't have an account, please contact the site host."
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"daily-review": "Daily Review",
|
||||||
|
"resources": "Resources",
|
||||||
|
"setting": "Setting",
|
||||||
|
"archived": "Archived"
|
||||||
|
},
|
||||||
|
"editor": {
|
||||||
|
"save": "Save",
|
||||||
|
"cancel-edit": "Cancel edit",
|
||||||
|
"placeholder": "Any thoughts..."
|
||||||
|
},
|
||||||
|
"tag-list": {
|
||||||
|
"tip-text": "Enter `#tag ` to create a tag"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"my-account": "My Account",
|
||||||
|
"preference": "Preference",
|
||||||
|
"member": "Member",
|
||||||
|
"account-section": {
|
||||||
|
"title": "Account Information"
|
||||||
|
},
|
||||||
|
"member-section": {
|
||||||
|
"create-a-member": "Create a member"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,57 @@
|
|||||||
"about": "关于",
|
"about": "关于",
|
||||||
"email": "邮箱",
|
"email": "邮箱",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"sign-in": "登录"
|
"username": "用户名",
|
||||||
|
"save": "保存",
|
||||||
|
"cancel": "退出",
|
||||||
|
"create": "创建",
|
||||||
|
"change": "修改",
|
||||||
|
"reset": "重置",
|
||||||
|
"restore": "恢复",
|
||||||
|
"language": "语言",
|
||||||
|
"version": "版本",
|
||||||
|
"pin": "置顶",
|
||||||
|
"unpin": "取消置顶",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"share": "分享",
|
||||||
|
"mark": "Mark",
|
||||||
|
"archive": "归档",
|
||||||
|
"basic": "基础",
|
||||||
|
"admin": "管理员",
|
||||||
|
"sign-in": "登录",
|
||||||
|
"sign-out": "退出登录",
|
||||||
|
"back-to-home": "回到主页"
|
||||||
},
|
},
|
||||||
"slogan": "一个开源的、支持私有化部署的碎片化知识卡片管理工具。",
|
"slogan": "一个开源的、支持私有化部署的碎片化知识卡片管理工具。",
|
||||||
"auth": {
|
"auth": {
|
||||||
"signup-as-host": "注册为 Host",
|
"signup-as-host": "注册为 Host",
|
||||||
"host-tip": "你正在注册为 Host 用户账号。",
|
"host-tip": "你正在注册为 Host 用户账号。",
|
||||||
"not-host-tip": "如果你没有账号,请联系站点 Host"
|
"not-host-tip": "如果你没有账号,请联系站点 Host"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"daily-review": "每日回顾",
|
||||||
|
"resources": "资源",
|
||||||
|
"setting": "设置",
|
||||||
|
"archived": "已归档"
|
||||||
|
},
|
||||||
|
"editor": {
|
||||||
|
"save": "记下",
|
||||||
|
"cancel-edit": "退出编辑",
|
||||||
|
"placeholder": "现在的想法是..."
|
||||||
|
},
|
||||||
|
"tag-list": {
|
||||||
|
"tip-text": "输入`#tag `来创建标签"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"my-account": "我的账号",
|
||||||
|
"preference": "偏好设置",
|
||||||
|
"member": "成员",
|
||||||
|
"account-section": {
|
||||||
|
"title": "账号信息"
|
||||||
|
},
|
||||||
|
"member-section": {
|
||||||
|
"create-a-member": "创建成员"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import "../less/auth.less";
|
|||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
const validateConfig: ValidatorConfig = {
|
const validateConfig: ValidatorConfig = {
|
||||||
minLength: 4,
|
minLength: 6,
|
||||||
maxLength: 24,
|
maxLength: 24,
|
||||||
noSpace: true,
|
noSpace: true,
|
||||||
noChinese: true,
|
noChinese: true,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { locationService, userService } from "../services";
|
import { locationService, userService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import Only from "../components/common/OnlyWhen";
|
import Only from "../components/common/OnlyWhen";
|
||||||
import Sidebar from "../components/Sidebar";
|
import Sidebar from "../components/Sidebar";
|
||||||
@ -12,6 +13,7 @@ import toastHelper from "../components/Toast";
|
|||||||
import "../less/home.less";
|
import "../less/home.less";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
|
const { t } = useI18n();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
const location = useAppSelector((state) => state.location);
|
const location = useAppSelector((state) => state.location);
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
@ -58,11 +60,11 @@ function Home() {
|
|||||||
<div className="addtion-btn-container">
|
<div className="addtion-btn-container">
|
||||||
{user ? (
|
{user ? (
|
||||||
<button className="btn" onClick={() => (window.location.href = "/")}>
|
<button className="btn" onClick={() => (window.location.href = "/")}>
|
||||||
<span className="icon">🏠</span> Back to Home
|
<span className="icon">🏠</span> {t("common.back-to-home")}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button className="btn" onClick={() => (window.location.href = "/auth")}>
|
<button className="btn" onClick={() => (window.location.href = "/auth")}>
|
||||||
<span className="icon">👉</span> Sign in
|
<span className="icon">👉</span> {t("common.sign-in")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user