- 把玩 flomo 后有感而作的开源项目
+ Memos is an open source, self-hosted alternative to flomo.
-
特点:精美且细节的视觉样式、体验优良的交互逻辑
+
Built with `Golang` and `React`.
🏗 This project is working in progress,
and very pleasure to welcome your{" "}
diff --git a/web/src/components/ChangePasswordDialog.tsx b/web/src/components/ChangePasswordDialog.tsx
index e29d8fd6..8823a039 100644
--- a/web/src/components/ChangePasswordDialog.tsx
+++ b/web/src/components/ChangePasswordDialog.tsx
@@ -44,19 +44,19 @@ const ChangePasswordDialog: React.FC = ({ destroy }: Props) => {
const handleSaveBtnClick = async () => {
if (oldPassword === "" || newPassword === "" || newPasswordAgain === "") {
- toastHelper.error("密码不能为空");
+ toastHelper.error("Please fill in all fields.");
return;
}
if (newPassword !== newPasswordAgain) {
- toastHelper.error("新密码两次输入不一致");
+ toastHelper.error("New passwords do not match.");
setNewPasswordAgain("");
return;
}
const passwordValidResult = validate(newPassword, validateConfig);
if (!passwordValidResult.result) {
- toastHelper.error("密码 " + passwordValidResult.reason);
+ toastHelper.error("Password " + passwordValidResult.reason);
return;
}
@@ -64,13 +64,13 @@ const ChangePasswordDialog: React.FC = ({ destroy }: Props) => {
const isValid = await userService.checkPasswordValid(oldPassword);
if (!isValid) {
- toastHelper.error("旧密码不匹配");
+ toastHelper.error("Old password is invalid.");
setOldPassword("");
return;
}
await userService.updatePassword(newPassword);
- toastHelper.info("密码修改成功!");
+ toastHelper.info("Password changed.");
handleCloseBtnClick();
} catch (error: any) {
toastHelper.error(error);
@@ -80,30 +80,30 @@ const ChangePasswordDialog: React.FC = ({ destroy }: Props) => {
return (
<>
-
修改密码
+
Change Password
diff --git a/web/src/components/ConfirmResetOpenIdDialog.tsx b/web/src/components/ConfirmResetOpenIdDialog.tsx
index 8b3bb313..ffdc942d 100644
--- a/web/src/components/ConfirmResetOpenIdDialog.tsx
+++ b/web/src/components/ConfirmResetOpenIdDialog.tsx
@@ -27,29 +27,31 @@ const ConfirmResetOpenIdDialog: React.FC = ({ destroy }: Props) => {
try {
await userService.resetOpenId();
} catch (error) {
- toastHelper.error("请求重置 Open API 失败");
+ toastHelper.error("Request reset open API failed.");
return;
}
- toastHelper.success("重置成功!");
+ toastHelper.success("Reset open API succeeded.");
handleCloseBtnClick();
};
return (
<>
-
重置 Open API
+
Reset Open API
-
⚠️ 现有 API 将失效,并生成新的 API,确定要重置吗?
+
+ ⚠️ The existing API will be invalidated and a new one will be generated, are you sure you want to reset?
+
- 取消
+ Cancel
- 确定重置!
+ Reset!
diff --git a/web/src/components/CreateQueryDialog.tsx b/web/src/components/CreateShortcutDialog.tsx
similarity index 83%
rename from web/src/components/CreateQueryDialog.tsx
rename to web/src/components/CreateShortcutDialog.tsx
index 65a48922..b58405f8 100644
--- a/web/src/components/CreateQueryDialog.tsx
+++ b/web/src/components/CreateShortcutDialog.tsx
@@ -1,18 +1,18 @@
import { memo, useCallback, useEffect, useState } from "react";
-import { memoService, queryService } from "../services";
+import { memoService, shortcutService } from "../services";
import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
import useLoading from "../hooks/useLoading";
import { showDialog } from "./Dialog";
import toastHelper from "./Toast";
import Selector from "./common/Selector";
-import "../less/create-query-dialog.less";
+import "../less/create-shortcut-dialog.less";
interface Props extends DialogProps {
- queryId?: string;
+ shortcutId?: string;
}
-const CreateQueryDialog: React.FC = (props: Props) => {
- const { destroy, queryId } = props;
+const CreateShortcutDialog: React.FC = (props: Props) => {
+ const { destroy, shortcutId } = props;
const [title, setTitle] = useState("");
const [filters, setFilters] = useState([]);
@@ -23,15 +23,15 @@ const CreateQueryDialog: React.FC = (props: Props) => {
}).length;
useEffect(() => {
- const queryTemp = queryService.getQueryById(queryId ?? "");
- if (queryTemp) {
- setTitle(queryTemp.title);
- const temp = JSON.parse(queryTemp.querystring);
+ const shortcutTemp = shortcutService.getShortcutById(shortcutId ?? "");
+ if (shortcutTemp) {
+ setTitle(shortcutTemp.title);
+ const temp = JSON.parse(shortcutTemp.payload);
if (Array.isArray(temp)) {
setFilters(temp);
}
}
- }, [queryId]);
+ }, [shortcutId]);
const handleTitleInputChange = (e: React.ChangeEvent) => {
const text = e.target.value as string;
@@ -40,17 +40,17 @@ const CreateQueryDialog: React.FC = (props: Props) => {
const handleSaveBtnClick = async () => {
if (!title) {
- toastHelper.error("标题不能为空!");
+ toastHelper.error("Title is required");
return;
}
try {
- if (queryId) {
- const editedQuery = await queryService.updateQuery(queryId, title, JSON.stringify(filters));
- queryService.editQuery(editedQuery);
+ if (shortcutId) {
+ const editedShortcut = await shortcutService.updateShortcut(shortcutId, title, JSON.stringify(filters));
+ shortcutService.editShortcut(shortcutService.convertResponseModelShortcut(editedShortcut));
} else {
- const query = await queryService.createQuery(title, JSON.stringify(filters));
- queryService.pushQuery(query);
+ const shortcut = await shortcutService.createShortcut(title, JSON.stringify(filters));
+ shortcutService.pushShortcut(shortcutService.convertResponseModelShortcut(shortcut));
}
} catch (error: any) {
toastHelper.error(error.message);
@@ -62,7 +62,7 @@ const CreateQueryDialog: React.FC = (props: Props) => {
if (filters.length > 0) {
const lastFilter = filters[filters.length - 1];
if (lastFilter.value.value === "") {
- toastHelper.info("先完善上一个过滤器吧");
+ toastHelper.info("Please fill in previous filter value");
return;
}
}
@@ -90,7 +90,7 @@ const CreateQueryDialog: React.FC = (props: Props) => {
🔖
- {queryId ? "编辑检索" : "创建检索"}
+ {shortcutId ? "Edit Shortcut" : "Create Shortcut"}
- 标题
+ Title
-
过滤器
+
Filter
{filters.map((f, index) => {
return (
@@ -116,7 +116,7 @@ const CreateQueryDialog: React.FC
= (props: Props) => {
);
})}
- 添加筛选条件
+ New Filter
@@ -125,10 +125,10 @@ const CreateQueryDialog: React.FC
= (props: Props) => {
- 符合条件的 Memo 有 {shownMemoLength} 条
+ {shownMemoLength} eligible memo
@@ -298,12 +298,12 @@ const FilterInputer: React.FC = (props: MemoFilterInpute
const MemoFilterInputer: React.FC = memo(FilterInputer);
-export default function showCreateQueryDialog(queryId?: string): void {
+export default function showCreateShortcutDialog(shortcutId?: string): void {
showDialog(
{
- className: "create-query-dialog",
+ className: "create-shortcut-dialog",
},
- CreateQueryDialog,
- { queryId }
+ CreateShortcutDialog,
+ { shortcutId }
);
}
diff --git a/web/src/components/DailyMemoDiaryDialog.tsx b/web/src/components/DailyMemoDiaryDialog.tsx
index 78d140d4..c8ac6215 100644
--- a/web/src/components/DailyMemoDiaryDialog.tsx
+++ b/web/src/components/DailyMemoDiaryDialog.tsx
@@ -106,11 +106,11 @@ const DailyMemoDiaryDialog: React.FC = (props: Props) => {
/>
{loadingState.isLoading ? (
) : memos.length === 0 ? (
-
空空如也
+
Oops, there is nothing.
) : (
diff --git a/web/src/components/DeletedMemo.tsx b/web/src/components/DeletedMemo.tsx
index 6d50f80a..8dcc1c9e 100644
--- a/web/src/components/DeletedMemo.tsx
+++ b/web/src/components/DeletedMemo.tsx
@@ -18,7 +18,7 @@ const DeletedMemo: React.FC
= (props: Props) => {
const memo: FormattedMemo = {
...propsMemo,
createdAtStr: utils.getDateTimeString(propsMemo.createdAt),
- deletedAtStr: utils.getDateTimeString(propsMemo.deletedAt ?? Date.now()),
+ deletedAtStr: utils.getDateTimeString(propsMemo.updatedAt ?? Date.now()),
};
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []);
@@ -40,7 +40,7 @@ const DeletedMemo: React.FC = (props: Props) => {
try {
await memoService.restoreMemoById(memo.id);
handleDeletedMemoAction(memo.id);
- toastHelper.info("恢复成功");
+ toastHelper.info("Restored successfully");
} catch (error: any) {
toastHelper.error(error.message);
}
@@ -55,7 +55,7 @@ const DeletedMemo: React.FC = (props: Props) => {
return (
-
删除于 {memo.deletedAtStr}
+
Deleted at {memo.deletedAtStr}
@@ -63,10 +63,10 @@ const DeletedMemo: React.FC = (props: Props) => {
- 恢复
+ Restore
- {showConfirmDeleteBtn ? "确定删除!" : "完全删除"}
+ {showConfirmDeleteBtn ? "Delete!" : "Delete"}
diff --git a/web/src/components/Editor/Editor.tsx b/web/src/components/Editor/Editor.tsx
index 42aa3492..3e1165e3 100644
--- a/web/src/components/Editor/Editor.tsx
+++ b/web/src/components/Editor/Editor.tsx
@@ -176,12 +176,12 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef
diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx
index 9e82fbd0..5067e03b 100644
--- a/web/src/components/Memo.tsx
+++ b/web/src/components/Memo.tsx
@@ -93,19 +93,19 @@ const Memo: React.FC
= (props: Props) => {
- 查看详情
+ View Story
Mark
- 分享
+ Share
- 编辑
+ Edit
- {showConfirmDeleteBtn ? "确定删除!" : "删除"}
+ {showConfirmDeleteBtn ? "Delete!" : "Delete"}
@@ -151,7 +151,7 @@ export function formatMemoContent(content: string) {
.replace(LINK_REG, "$1")
.replace(MEMO_LINK_REG, "$1");
- // 中英文之间加空格
+ // Add space in english and chinese
if (shouldSplitMemoWord) {
content = content
.replace(/([\u4e00-\u9fa5])([A-Za-z0-9?.,;[\]]+)/g, "$1 $2")
diff --git a/web/src/components/MemoCardDialog.tsx b/web/src/components/MemoCardDialog.tsx
index 58c94db6..503ba308 100644
--- a/web/src/components/MemoCardDialog.tsx
+++ b/web/src/components/MemoCardDialog.tsx
@@ -148,7 +148,7 @@ const MemoCardDialog: React.FC = (props: Props) => {
{linkMemos.length > 0 ? (
-
关联了 {linkMemos.length} 个 MEMO
+
{linkMemos.length} related MEMO
{linkMemos.map((m) => {
const rawtext = parseHtmlToRawText(formatMemoContent(m.content)).replaceAll("\n", " ");
return (
@@ -162,7 +162,7 @@ const MemoCardDialog: React.FC
= (props: Props) => {
) : null}
{linkedMemos.length > 0 ? (
-
{linkedMemos.length} 个链接至此的 MEMO
+
{linkedMemos.length} linked MEMO
{linkedMemos.map((m) => {
const rawtext = parseHtmlToRawText(formatMemoContent(m.content)).replaceAll("\n", " ");
return (
diff --git a/web/src/components/MemoEditor.tsx b/web/src/components/MemoEditor.tsx
index 403eda57..3c1d23a8 100644
--- a/web/src/components/MemoEditor.tsx
+++ b/web/src/components/MemoEditor.tsx
@@ -130,7 +130,7 @@ const MemoEditor: React.FC
= () => {
try {
const image = await resourceService.upload(file);
- const url = `/r/${image.id}/${image.filename}`;
+ const url = `/h/r/${image.id}/${image.filename}`;
return url;
} catch (error: any) {
@@ -140,7 +140,7 @@ const MemoEditor: React.FC = () => {
const handleSaveBtnClick = useCallback(async (content: string) => {
if (content === "") {
- toastHelper.error("内容不能为空呀");
+ toastHelper.error("Content can't be empty");
return;
}
@@ -270,7 +270,7 @@ const MemoEditor: React.FC = () => {
() => ({
className: "memo-editor",
initialContent: getEditorContentCache(),
- placeholder: "现在的想法是...",
+ placeholder: "Any thoughts...",
showConfirmBtn: true,
showCancelBtn: showEditStatus,
onConfirmBtnClick: handleSaveBtnClick,
@@ -282,7 +282,7 @@ const MemoEditor: React.FC = () => {
return (
-
正在修改中...
+
Editting...
= () => {
locationState: { query },
} = useContext(appContext);
- const { tag: tagQuery, duration, type: memoType, text: textQuery, filter } = query;
- const queryFilter = queryService.getQueryById(filter);
+ const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
+ const queryFilter = shortcutService.getShortcutById(shortcutId);
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter);
return (
-
筛选:
+
Filter:
{
- locationService.setMemoFilter("");
+ locationService.setMemoShortcut("");
}}
>
🔖 {queryFilter?.title}
diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx
index 4c283895..f4169a44 100644
--- a/web/src/components/MemoList.tsx
+++ b/web/src/components/MemoList.tsx
@@ -1,6 +1,6 @@
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import appContext from "../stores/appContext";
-import { locationService, memoService, queryService } from "../services";
+import { locationService, memoService, shortcutService } from "../services";
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts";
import utils from "../helpers/utils";
import { checkShouldShowMemoWithFilters } from "../helpers/filter";
@@ -18,8 +18,8 @@ const MemoList: React.FC
= () => {
const [isFetching, setFetchStatus] = useState(true);
const wrapperElement = useRef(null);
- const { tag: tagQuery, duration, type: memoType, text: textQuery, filter: queryId } = query;
- const queryFilter = queryService.getQueryById(queryId);
+ const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
+ const queryFilter = shortcutService.getShortcutById(shortcutId);
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter);
const shownMemos =
@@ -28,7 +28,7 @@ const MemoList: React.FC = () => {
let shouldShow = true;
if (queryFilter) {
- const filters = JSON.parse(queryFilter.querystring) as Filter[];
+ const filters = JSON.parse(queryFilter.payload) as Filter[];
if (Array.isArray(filters)) {
shouldShow = checkShouldShowMemoWithFilters(memo, filters);
}
@@ -83,7 +83,7 @@ const MemoList: React.FC = () => {
setFetchStatus(false);
})
.catch(() => {
- toastHelper.error("😭 请求数据失败了");
+ toastHelper.error("😭 Refresh failed, please try again later.");
});
}, []);
@@ -111,7 +111,13 @@ const MemoList: React.FC = () => {
))}
- {isFetching ? "努力请求数据中..." : shownMemos.length === 0 ? "空空如也" : showMemoFilter ? "" : "所有数据加载完啦 🎉"}
+ {isFetching
+ ? "Fetching data..."
+ : shownMemos.length === 0
+ ? "Oops, there is nothing"
+ : showMemoFilter
+ ? ""
+ : "Fetching completed 🎉"}
diff --git a/web/src/components/MemosHeader.tsx b/web/src/components/MemosHeader.tsx
index e71b8f2b..70fdc43e 100644
--- a/web/src/components/MemosHeader.tsx
+++ b/web/src/components/MemosHeader.tsx
@@ -1,7 +1,7 @@
import { useCallback, useContext, useEffect, useState } from "react";
import appContext from "../stores/appContext";
import SearchBar from "./SearchBar";
-import { globalStateService, memoService, queryService } from "../services";
+import { globalStateService, memoService, shortcutService } from "../services";
import Only from "./common/OnlyWhen";
import "../less/memos-header.less";
@@ -12,22 +12,22 @@ interface Props {}
const MemosHeader: React.FC
= () => {
const {
locationState: {
- query: { filter },
+ query: { shortcutId },
},
globalState: { isMobileView },
- queryState: { queries },
+ shortcutState: { shortcuts },
} = useContext(appContext);
const [titleText, setTitleText] = useState("MEMOS");
useEffect(() => {
- const query = queryService.getQueryById(filter);
+ const query = shortcutService.getShortcutById(shortcutId);
if (query) {
setTitleText(query.title);
} else {
setTitleText("MEMOS");
}
- }, [filter, queries]);
+ }, [shortcutId, shortcuts]);
const handleMemoTextClick = useCallback(() => {
const now = Date.now();
diff --git a/web/src/components/MenuBtnsPopup.tsx b/web/src/components/MenuBtnsPopup.tsx
index 5414ed68..aafd89f7 100644
--- a/web/src/components/MenuBtnsPopup.tsx
+++ b/web/src/components/MenuBtnsPopup.tsx
@@ -49,16 +49,16 @@ const MenuBtnsPopup: React.FC = (props: Props) => {
return (
);
diff --git a/web/src/components/MyAccountSection.tsx b/web/src/components/MyAccountSection.tsx
index f573ae83..7dfc74be 100644
--- a/web/src/components/MyAccountSection.tsx
+++ b/web/src/components/MyAccountSection.tsx
@@ -20,8 +20,8 @@ interface Props {}
const MyAccountSection: React.FC = () => {
const { userState } = useContext(appContext);
const user = userState.user as Model.User;
- const [username, setUsername] = useState(user.username);
- const openAPIRoute = `${window.location.origin}/api/whs/memo/${user.openId}`;
+ const [username, setUsername] = useState(user.name);
+ const openAPIRoute = `${window.location.origin}/h/${user.openId}/memo`;
const handleUsernameChanged = (e: React.ChangeEvent) => {
const nextUsername = e.target.value as string;
@@ -29,18 +29,18 @@ const MyAccountSection: React.FC = () => {
};
const handleConfirmEditUsernameBtnClick = async () => {
- if (user.username === "guest") {
- toastHelper.info("🈲 不要修改我的用户名");
+ if (user.name === "guest") {
+ toastHelper.info("Do not change my username");
return;
}
- if (username === user.username) {
+ if (username === user.name) {
return;
}
const usernameValidResult = validate(username, validateConfig);
if (!usernameValidResult.result) {
- toastHelper.error("用户名 " + usernameValidResult.reason);
+ toastHelper.error("Username " + usernameValidResult.reason);
return;
}
@@ -48,21 +48,21 @@ const MyAccountSection: React.FC = () => {
const isUsable = await userService.checkUsernameUsable(username);
if (!isUsable) {
- toastHelper.error("用户名无法使用");
+ toastHelper.error("Username is not available");
return;
}
await userService.updateUsername(username);
await userService.doSignIn();
- toastHelper.info("修改成功~");
+ toastHelper.info("Username changed");
} catch (error: any) {
toastHelper.error(error.message);
}
};
const handleChangePasswordBtnClick = () => {
- if (user.username === "guest") {
- toastHelper.info("🈲 不要修改我的密码");
+ if (user.name === "guest") {
+ toastHelper.info("Do not change my password");
return;
}
@@ -81,47 +81,47 @@ const MyAccountSection: React.FC = () => {
return (
<>
-
基本信息
+
Account Information