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>
|
||||
</div>
|
||||
<div className="dialog-content-container">
|
||||
<p>
|
||||
Memos is an <i>open source</i>, <i>self-hosted</i> knowledge base that works with a SQLite db file.
|
||||
</p>
|
||||
<p>{t("slogan")}</p>
|
||||
<br />
|
||||
<div className="addtion-info-container">
|
||||
<GitHubBadge />
|
||||
<Only when={profile !== undefined}>
|
||||
<>
|
||||
version:
|
||||
{t("common.version")}:
|
||||
<span className="pre-text">
|
||||
{profile?.version}-{profile?.mode}
|
||||
</span>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { IMAGE_URL_REG } from "../helpers/consts";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import { memoService } from "../services";
|
||||
import { formatMemoContent } from "../helpers/marked";
|
||||
@ -19,6 +20,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
|
||||
archivedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
|
||||
};
|
||||
const { t } = useI18n();
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
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>
|
||||
<div className="btns-container">
|
||||
<span className="btn restore-btn" onClick={handleRestoreMemoClick}>
|
||||
Restore
|
||||
{t("common.restore")}
|
||||
</span>
|
||||
<span className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`} onClick={handleDeleteMemoClick}>
|
||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
||||
{t("common.delete")}
|
||||
{showConfirmDeleteBtn ? "!" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { userService } from "../services";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
@ -16,6 +17,7 @@ const validateConfig: ValidatorConfig = {
|
||||
interface Props extends DialogProps {}
|
||||
|
||||
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
const { t } = useI18n();
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||
|
||||
@ -85,10 +87,10 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
</label>
|
||||
<div className="btns-container">
|
||||
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</span>
|
||||
<span className="btn confirm-btn" onClick={handleSaveBtnClick}>
|
||||
Save
|
||||
{t("common.save")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import useRefresh from "../../hooks/useRefresh";
|
||||
import Only from "../common/OnlyWhen";
|
||||
import "../../less/editor.less";
|
||||
@ -37,6 +38,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
|
||||
onCancelBtnClick: handleCancelBtnClickCallback,
|
||||
onContentChange: handleContentChangeCallback,
|
||||
} = props;
|
||||
const { t } = useI18n();
|
||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||
const refresh = useRefresh();
|
||||
|
||||
@ -130,12 +132,12 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
|
||||
<div className="btns-container">
|
||||
<Only when={showCancelBtn}>
|
||||
<button className="action-btn cancel-btn" onClick={handleCommonCancelBtnClick}>
|
||||
Cancel editting
|
||||
{t("editor.cancel-edit")}
|
||||
</button>
|
||||
</Only>
|
||||
<Only when={showConfirmBtn}>
|
||||
<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>
|
||||
</Only>
|
||||
</div>
|
||||
|
@ -2,6 +2,8 @@ import { memo, useEffect, useRef, useState } from "react";
|
||||
import { indexOf } from "lodash-es";
|
||||
import dayjs from "dayjs";
|
||||
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 { DONE_BLOCK_REG, formatMemoContent, TODO_BLOCK_REG } from "../helpers/marked";
|
||||
import { editorStateService, locationService, memoService, userService } from "../services";
|
||||
@ -27,20 +29,21 @@ interface State {
|
||||
expandButtonStatus: ExpandButtonStatus;
|
||||
}
|
||||
|
||||
export const getFormatedMemoCreatedAtStr = (createdTs: number): string => {
|
||||
export const getFormatedMemoCreatedAtStr = (createdTs: number, locale = "en"): string => {
|
||||
if (Date.now() - createdTs < 1000 * 60 * 60 * 24) {
|
||||
return dayjs(createdTs).fromNow();
|
||||
return dayjs(createdTs).locale(locale).fromNow();
|
||||
} 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 = props.memo;
|
||||
const { t, locale } = useI18n();
|
||||
const [state, setState] = useState<State>({
|
||||
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 imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
||||
const isVisitorMode = userService.isVisitorMode();
|
||||
@ -59,10 +62,10 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
|
||||
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
|
||||
setInterval(() => {
|
||||
setCreatedAtStr(dayjs(memo.createdTs).fromNow());
|
||||
setCreatedAtStr(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
||||
}, 1000 * 1);
|
||||
}
|
||||
}, []);
|
||||
}, [locale]);
|
||||
|
||||
const handleShowMemoStoryDialog = () => {
|
||||
showMemoCardDialog(memo);
|
||||
@ -186,25 +189,25 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
<div className="btns-container">
|
||||
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||
<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 className="btn" onClick={handleEditMemoClick}>
|
||||
<Icon.Edit3 className="icon-img" />
|
||||
<span className="tip-text">Edit</span>
|
||||
<span className="tip-text">{t("common.edit")}</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
||||
<Icon.Share className="icon-img" />
|
||||
<span className="tip-text">Share</span>
|
||||
<span className="tip-text">{t("common.share")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="btn" onClick={handleMarkMemoClick}>
|
||||
Mark
|
||||
{t("common.mark")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleShowMemoStoryDialog}>
|
||||
View Story
|
||||
</span>
|
||||
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
|
||||
Archive
|
||||
{t("common.archive")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { useAppSelector } from "../store";
|
||||
import * as storage from "../helpers/storage";
|
||||
import Icon from "./Icon";
|
||||
@ -16,6 +17,7 @@ interface State {
|
||||
}
|
||||
|
||||
const MemoEditor: React.FC<Props> = () => {
|
||||
const { t, locale } = useI18n();
|
||||
const editorState = useAppSelector((state) => state.editor);
|
||||
const tags = useAppSelector((state) => state.memo.tags);
|
||||
const [state, setState] = useState<State>({
|
||||
@ -212,7 +214,7 @@ const MemoEditor: React.FC<Props> = () => {
|
||||
() => ({
|
||||
className: "memo-editor",
|
||||
initialContent: getEditorContentCache(),
|
||||
placeholder: "Any thoughts...",
|
||||
placeholder: t("editor.placeholder"),
|
||||
fullscreen: state.fullscreen,
|
||||
showConfirmBtn: true,
|
||||
showCancelBtn: isEditing,
|
||||
@ -220,7 +222,7 @@ const MemoEditor: React.FC<Props> = () => {
|
||||
onCancelBtnClick: handleCancelBtnClick,
|
||||
onContentChange: handleContentChange,
|
||||
}),
|
||||
[isEditing, state.fullscreen]
|
||||
[isEditing, state.fullscreen, locale]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import * as api from "../helpers/api";
|
||||
import { locationService, userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import toastHelper from "./Toast";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||
@ -13,6 +14,7 @@ interface Props {
|
||||
|
||||
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||
const { shownStatus, setShownStatus } = props;
|
||||
const { t } = useI18n();
|
||||
const popupElRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -63,14 +65,14 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||
return (
|
||||
<div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}>
|
||||
<button className="btn action-btn" onClick={handleAboutBtnClick}>
|
||||
<span className="icon">🤠</span> About
|
||||
<span className="icon">🤠</span> {t("common.about")}
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handlePingBtnClick}>
|
||||
<span className="icon">🎯</span> Ping
|
||||
</button>
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<button className="btn action-btn" onClick={handleSignOutBtnClick}>
|
||||
<span className="icon">👋</span> Sign out
|
||||
<span className="icon">👋</span> {t("common.sign-out")}
|
||||
</button>
|
||||
</Only>
|
||||
</div>
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import MyAccountSection from "./Settings/MyAccountSection";
|
||||
import PreferencesSection from "./Settings/PreferencesSection";
|
||||
import MemberSection from "./Settings/MemberSection";
|
||||
import "../less/setting-dialog.less";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface Props extends DialogProps {}
|
||||
|
||||
@ -17,6 +18,7 @@ interface State {
|
||||
|
||||
const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy } = props;
|
||||
const { t } = useI18n();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const [state, setState] = useState<State>({
|
||||
selectedSection: "my-account",
|
||||
@ -34,30 +36,30 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||
<Icon.X className="icon-img" />
|
||||
</button>
|
||||
<div className="section-selector-container">
|
||||
<span className="section-title">Basic</span>
|
||||
<span className="section-title">{t("common.basic")}</span>
|
||||
<div className="section-items-container">
|
||||
<span
|
||||
onClick={() => handleSectionSelectorItemClick("my-account")}
|
||||
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
|
||||
onClick={() => handleSectionSelectorItemClick("preferences")}
|
||||
className={`section-item ${state.selectedSection === "preferences" ? "selected" : ""}`}
|
||||
>
|
||||
<span className="icon-text">🏟</span> Preferences
|
||||
<span className="icon-text">🏟</span> {t("setting.preference")}
|
||||
</span>
|
||||
</div>
|
||||
{user?.role === "HOST" ? (
|
||||
<>
|
||||
<span className="section-title">Admin</span>
|
||||
<span className="section-title">{t("common.admin")}</span>
|
||||
<div className="section-items-container">
|
||||
<span
|
||||
onClick={() => handleSectionSelectorItemClick("member")}
|
||||
className={`section-item ${state.selectedSection === "member" ? "selected" : ""}`}
|
||||
>
|
||||
<span className="icon-text">👤</span> Member
|
||||
<span className="icon-text">👤</span> {t("setting.member")}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import { userService } from "../../services";
|
||||
import { useAppSelector } from "../../store";
|
||||
import * as api from "../../helpers/api";
|
||||
@ -16,6 +17,7 @@ interface State {
|
||||
}
|
||||
|
||||
const PreferencesSection: React.FC<Props> = () => {
|
||||
const { t } = useI18n();
|
||||
const currentUser = useAppSelector((state) => state.user.user);
|
||||
const [state, setState] = useState<State>({
|
||||
createUserEmail: "",
|
||||
@ -110,18 +112,18 @@ const PreferencesSection: React.FC<Props> = () => {
|
||||
|
||||
return (
|
||||
<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="input-form-container">
|
||||
<span className="field-text">Email</span>
|
||||
<input type="email" placeholder="Email" value={state.createUserEmail} onChange={handleEmailInputChange} />
|
||||
<span className="field-text">{t("common.email")}</span>
|
||||
<input type="email" placeholder={t("common.email")} value={state.createUserEmail} onChange={handleEmailInputChange} />
|
||||
</div>
|
||||
<div className="input-form-container">
|
||||
<span className="field-text">Password</span>
|
||||
<input type="text" placeholder="Password" value={state.createUserPassword} onChange={handlePasswordInputChange} />
|
||||
<span className="field-text">{t("common.password")}</span>
|
||||
<input type="text" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} />
|
||||
</div>
|
||||
<div className="btns-container">
|
||||
<button onClick={handleCreateUserBtnClick}>Create</button>
|
||||
<button onClick={handleCreateUserBtnClick}>{t("common.create")}</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="title-text">Member list</p>
|
||||
@ -140,12 +142,12 @@ const PreferencesSection: React.FC<Props> = () => {
|
||||
) : (
|
||||
<Dropdown className="actions-dropdown">
|
||||
{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)}>
|
||||
Delete
|
||||
{t("common.delete")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { userService } from "../../services";
|
||||
import { validate, ValidatorConfig } from "../../helpers/validator";
|
||||
@ -17,6 +18,7 @@ const validateConfig: ValidatorConfig = {
|
||||
interface Props {}
|
||||
|
||||
const MyAccountSection: React.FC<Props> = () => {
|
||||
const { t } = useI18n();
|
||||
const user = useAppSelector((state) => state.user.user as User);
|
||||
const [username, setUsername] = useState<string>(user.name);
|
||||
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
||||
@ -68,17 +70,17 @@ const MyAccountSection: React.FC<Props> = () => {
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
<span className="normal-text">Email:</span>
|
||||
<span className="normal-text">{t("common.email")}:</span>
|
||||
<span className="normal-text">{user.email}</span>
|
||||
</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} />
|
||||
<div className={`btns-container ${username === user.name ? "!hidden" : ""}`} onClick={handlePreventDefault}>
|
||||
<span className="btn confirm-btn" onClick={handleConfirmEditUsernameBtnClick}>
|
||||
Save
|
||||
{t("common.save")}
|
||||
</span>
|
||||
<span
|
||||
className="btn cancel-btn"
|
||||
@ -86,14 +88,14 @@ const MyAccountSection: React.FC<Props> = () => {
|
||||
setUsername(user.name);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</span>
|
||||
</div>
|
||||
</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}>
|
||||
Change it
|
||||
{t("common.change")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -101,10 +103,9 @@ const MyAccountSection: React.FC<Props> = () => {
|
||||
<p className="title-text">Open API</p>
|
||||
<p className="value-text">{openAPIRoute}</p>
|
||||
<span className="reset-btn" onClick={handleResetOpenIdBtnClick}>
|
||||
Reset API
|
||||
{t("common.reset")} API
|
||||
</span>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { globalService, memoService, userService } from "../../services";
|
||||
import * as utils from "../../helpers/utils";
|
||||
import { globalService, userService } from "../../services";
|
||||
import { useAppSelector } from "../../store";
|
||||
import Only from "../common/OnlyWhen";
|
||||
import toastHelper from "../Toast";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import Selector from "../common/Selector";
|
||||
import "../../less/settings/preferences-section.less";
|
||||
|
||||
@ -20,66 +18,9 @@ const localeSelectorItems = [
|
||||
];
|
||||
|
||||
const PreferencesSection: React.FC<Props> = () => {
|
||||
const { t } = useI18n();
|
||||
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) => {
|
||||
globalService.setLocale(value as Locale);
|
||||
await userService.upsertUserSetting("locale", value);
|
||||
@ -87,22 +28,10 @@ const PreferencesSection: React.FC<Props> = () => {
|
||||
|
||||
return (
|
||||
<div className="section-container preferences-section-container">
|
||||
{/* Hide export/import buttons */}
|
||||
<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} />
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { userService } from "../services";
|
||||
import toImage from "../labs/html2image";
|
||||
import { ANIMATION_DURATION, IMAGE_URL_REG } from "../helpers/consts";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { formatMemoContent } from "../helpers/marked";
|
||||
import Only from "./common/OnlyWhen";
|
||||
@ -16,6 +17,7 @@ interface Props extends DialogProps {
|
||||
|
||||
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||
const { memo: propsMemo, destroy } = props;
|
||||
const { t } = useI18n();
|
||||
const { user: userinfo } = userService.getState();
|
||||
const memo = {
|
||||
...propsMemo,
|
||||
@ -73,7 +75,8 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||
<>
|
||||
<div className="dialog-header-container">
|
||||
<p className="title-text">
|
||||
<span className="icon-text">🌄</span>Share Memo
|
||||
<span className="icon-text">🌄</span>
|
||||
{t("common.share")} Memo
|
||||
</p>
|
||||
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
||||
<Icon.X className="icon-img" />
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
@ -59,6 +60,7 @@ interface ShortcutContainerProps {
|
||||
|
||||
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
||||
const { shortcut, isActive } = props;
|
||||
const { t } = useI18n();
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
|
||||
const handleShortcutClick = () => {
|
||||
@ -119,17 +121,18 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
||||
<div className="action-btns-wrapper">
|
||||
<div className="action-btns-container">
|
||||
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
||||
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
||||
{shortcut.rowStatus === "ARCHIVED" ? t("common.unpin") : t("common.pin")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
||||
Edit
|
||||
{t("common.edit")}
|
||||
</span>
|
||||
<span
|
||||
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
||||
onClick={handleDeleteMemoClick}
|
||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
||||
>
|
||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
||||
{t("common.delete")}
|
||||
{showConfirmDeleteBtn ? "!" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Icon from "./Icon";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||
@ -14,14 +15,14 @@ import "../less/siderbar.less";
|
||||
interface Props {}
|
||||
|
||||
const Sidebar: React.FC<Props> = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const handleMyAccountBtnClick = () => {
|
||||
showSettingDialog();
|
||||
};
|
||||
|
||||
const handleResourcesBtnClick = () => {
|
||||
showResourcesDialog();
|
||||
};
|
||||
|
||||
const handleArchivedBtnClick = () => {
|
||||
showArchivedMemoDialog();
|
||||
};
|
||||
@ -37,17 +38,17 @@ const Sidebar: React.FC<Props> = () => {
|
||||
<UsageHeatMap />
|
||||
<div className="action-btns-container">
|
||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||
<span className="icon">📅</span> Daily Review
|
||||
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
||||
</button>
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<button className="btn action-btn" onClick={handleResourcesBtnClick}>
|
||||
<span className="icon">🌄</span> Resources
|
||||
<span className="icon">🌄</span> {t("sidebar.resources")}
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
||||
<span className="icon">⚙️</span> Setting
|
||||
<span className="icon">⚙️</span> {t("sidebar.setting")}
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
||||
<span className="icon">🗂</span> Archived
|
||||
<span className="icon">🗂</span> {t("sidebar.archived")}
|
||||
</button>
|
||||
</Only>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, memoService, userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import Icon from "./Icon";
|
||||
import Only from "./common/OnlyWhen";
|
||||
@ -16,6 +17,7 @@ interface Tag {
|
||||
interface Props {}
|
||||
|
||||
const TagList: React.FC<Props> = () => {
|
||||
const { t } = useI18n();
|
||||
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
@ -75,9 +77,7 @@ const TagList: React.FC<Props> = () => {
|
||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
||||
))}
|
||||
<Only when={!userService.isVisitorMode() && tags.length < 5}>
|
||||
<p className="tag-tip-container">
|
||||
Enter <span className="code-text">#tag </span> to create a tag
|
||||
</p>
|
||||
<p className="tip-text">{t("tag-list.tip-text")}</p>
|
||||
</Only>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,10 +18,10 @@
|
||||
}
|
||||
|
||||
> .addtion-info-container {
|
||||
@apply flex flex-row justify-start items-center;
|
||||
@apply flex flex-row text-sm justify-start items-center;
|
||||
|
||||
> .github-badge-container {
|
||||
@apply mr-2;
|
||||
@apply mr-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +61,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
> .tag-tip-container {
|
||||
@apply w-full mt-2 pl-4 text-sm text-gray-400;
|
||||
|
||||
> .code-text {
|
||||
@apply p-1 mx-1 text-blue-600 font-mono whitespace-pre-line bg-blue-100 rounded;
|
||||
}
|
||||
> .tip-text {
|
||||
@apply w-full mt-2 pl-4 text-sm text-gray-400 font-mono;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,56 @@
|
||||
"about": "About",
|
||||
"email": "Email",
|
||||
"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.",
|
||||
"auth": {
|
||||
"signup-as-host": "Sign up as 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."
|
||||
},
|
||||
"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": "关于",
|
||||
"email": "邮箱",
|
||||
"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": "一个开源的、支持私有化部署的碎片化知识卡片管理工具。",
|
||||
"auth": {
|
||||
"signup-as-host": "注册为 Host",
|
||||
"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 {}
|
||||
|
||||
const validateConfig: ValidatorConfig = {
|
||||
minLength: 4,
|
||||
minLength: 6,
|
||||
maxLength: 24,
|
||||
noSpace: true,
|
||||
noChinese: true,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { locationService, userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import Only from "../components/common/OnlyWhen";
|
||||
import Sidebar from "../components/Sidebar";
|
||||
@ -12,6 +13,7 @@ import toastHelper from "../components/Toast";
|
||||
import "../less/home.less";
|
||||
|
||||
function Home() {
|
||||
const { t } = useI18n();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const location = useAppSelector((state) => state.location);
|
||||
const loadingState = useLoading();
|
||||
@ -58,11 +60,11 @@ function Home() {
|
||||
<div className="addtion-btn-container">
|
||||
{user ? (
|
||||
<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 className="btn" onClick={() => (window.location.href = "/auth")}>
|
||||
<span className="icon">👉</span> Sign in
|
||||
<span className="icon">👉</span> {t("common.sign-in")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user