chore: update store types

This commit is contained in:
boojack 2022-05-19 18:32:49 +08:00
parent bc22f69ac5
commit 6fe1db42b5
46 changed files with 353 additions and 318 deletions

View File

@ -38,7 +38,7 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
</div> </div>
<div className="dialog-content-container"> <div className="dialog-content-container">
<p> <p>
Memos is an open source, quickly self-hosted alternative to <a href="https://flomoapp.com">flomo</a>. Memos is an <i>open source</i>, <i>self-hosted</i> knowledge base that works with local SQLite.
</p> </p>
<br /> <br />
<p> <p>

View File

@ -44,7 +44,7 @@ const ConfirmResetOpenIdDialog: React.FC<Props> = ({ destroy }: Props) => {
</div> </div>
<div className="dialog-content-container"> <div className="dialog-content-container">
<p className="warn-text"> <p className="warn-text">
The existing API will be invalidated and a new one will be generated, are you sure you want to reset? The existing API will be invalidated and a new one will be generated, are you sure you want to reset?
</p> </p>
<div className="btns-container"> <div className="btns-container">
<span className="btn cancel-btn" onClick={handleCloseBtnClick}> <span className="btn cancel-btn" onClick={handleCloseBtnClick}>

View File

@ -4,20 +4,21 @@ import { formatMemoContent } from "./Memo";
import Only from "./common/OnlyWhen"; import Only from "./common/OnlyWhen";
import "../less/daily-memo.less"; import "../less/daily-memo.less";
interface DailyMemo extends FormattedMemo { interface DailyMemo extends Memo {
createdAtStr: string;
timeStr: string; timeStr: string;
} }
interface Props { interface Props {
memo: Model.Memo; memo: Memo;
} }
const DailyMemo: React.FC<Props> = (props: Props) => { const DailyMemo: React.FC<Props> = (props: Props) => {
const { memo: propsMemo } = props; const { memo: propsMemo } = props;
const memo: DailyMemo = { const memo: DailyMemo = {
...propsMemo, ...propsMemo,
createdAtStr: utils.getDateTimeString(propsMemo.createdAt), createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
timeStr: utils.getTimeString(propsMemo.createdAt), timeStr: utils.getTimeString(propsMemo.createdTs),
}; };
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []); const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []);

View File

@ -20,7 +20,7 @@ const weekdayChineseStrArray = ["周日", "周一", "周二", "周三", "周四"
const DailyMemoDiaryDialog: React.FC<Props> = (props: Props) => { const DailyMemoDiaryDialog: React.FC<Props> = (props: Props) => {
const loadingState = useLoading(); const loadingState = useLoading();
const [memos, setMemos] = useState<Model.Memo[]>([]); const [memos, setMemos] = useState<Memo[]>([]);
const [currentDateStamp, setCurrentDateStamp] = useState(utils.getDateStampByDate(utils.getDateString(props.currentDateStamp))); const [currentDateStamp, setCurrentDateStamp] = useState(utils.getDateStampByDate(utils.getDateString(props.currentDateStamp)));
const [showDatePicker, toggleShowDatePicker] = useToggle(false); const [showDatePicker, toggleShowDatePicker] = useToggle(false);
const memosElRef = useRef<HTMLDivElement>(null); const memosElRef = useRef<HTMLDivElement>(null);
@ -32,10 +32,10 @@ const DailyMemoDiaryDialog: React.FC<Props> = (props: Props) => {
.getState() .getState()
.memos.filter( .memos.filter(
(a) => (a) =>
utils.getTimeStampByDate(a.createdAt) >= currentDateStamp && utils.getTimeStampByDate(a.createdTs) >= currentDateStamp &&
utils.getTimeStampByDate(a.createdAt) < currentDateStamp + DAILY_TIMESTAMP utils.getTimeStampByDate(a.createdTs) < currentDateStamp + DAILY_TIMESTAMP
) )
.sort((a, b) => utils.getTimeStampByDate(a.createdAt) - utils.getTimeStampByDate(b.createdAt)); .sort((a, b) => utils.getTimeStampByDate(a.createdTs) - utils.getTimeStampByDate(b.createdTs));
setMemos(dailyMemos); setMemos(dailyMemos);
loadingState.setFinish(); loadingState.setFinish();
}; };
@ -115,7 +115,7 @@ const DailyMemoDiaryDialog: React.FC<Props> = (props: Props) => {
) : ( ) : (
<div className="dailymemos-wrapper"> <div className="dailymemos-wrapper">
{memos.map((memo) => ( {memos.map((memo) => (
<DailyMemo key={`${memo.id}-${memo.updatedAt}`} memo={memo} /> <DailyMemo key={`${memo.id}-${memo.updatedTs}`} memo={memo} />
))} ))}
</div> </div>
)} )}

View File

@ -9,16 +9,16 @@ import { formatMemoContent } from "./Memo";
import "../less/memo.less"; import "../less/memo.less";
interface Props { interface Props {
memo: Model.Memo; memo: Memo;
handleDeletedMemoAction: (memoId: string) => void; handleDeletedMemoAction: (memoId: MemoId) => void;
} }
const DeletedMemo: React.FC<Props> = (props: Props) => { const DeletedMemo: React.FC<Props> = (props: Props) => {
const { memo: propsMemo, handleDeletedMemoAction } = props; const { memo: propsMemo, handleDeletedMemoAction } = props;
const memo: FormattedMemo = { const memo = {
...propsMemo, ...propsMemo,
createdAtStr: utils.getDateTimeString(propsMemo.createdAt), createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
deletedAtStr: utils.getDateTimeString(propsMemo.updatedAt ?? Date.now()), deletedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
}; };
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []); const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []);

View File

@ -1,6 +1,6 @@
import { memo } from "react"; import { memo } from "react";
import { escape } from "lodash-es"; import { escape } from "lodash-es";
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts"; import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG, UNKNOWN_ID } from "../helpers/consts";
import { parseMarkedToHtml, parseRawTextToHtml } from "../helpers/marked"; import { parseMarkedToHtml, parseRawTextToHtml } from "../helpers/marked";
import utils from "../helpers/utils"; import utils from "../helpers/utils";
import useToggle from "../hooks/useToggle"; import useToggle from "../hooks/useToggle";
@ -13,14 +13,14 @@ import toastHelper from "./Toast";
import "../less/memo.less"; import "../less/memo.less";
interface Props { interface Props {
memo: Model.Memo; memo: Memo;
} }
const Memo: React.FC<Props> = (props: Props) => { const Memo: React.FC<Props> = (props: Props) => {
const { memo: propsMemo } = props; const { memo: propsMemo } = props;
const memo: FormattedMemo = { const memo = {
...propsMemo, ...propsMemo,
createdAtStr: utils.getDateTimeString(propsMemo.createdAt), createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
}; };
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []); const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []);
@ -31,17 +31,17 @@ const Memo: React.FC<Props> = (props: Props) => {
const handleTogglePinMemoBtnClick = async () => { const handleTogglePinMemoBtnClick = async () => {
try { try {
if (memo.rowStatus === "ARCHIVED") { if (memo.pinned) {
await memoService.unpinMemo(memo.id); await memoService.unpinMemo(memo.id);
memoService.editMemo({ memoService.editMemo({
...memo, ...memo,
rowStatus: "NORMAL", pinned: false,
}); });
} else { } else {
await memoService.pinMemo(memo.id); await memoService.pinMemo(memo.id);
memoService.editMemo({ memoService.editMemo({
...memo, ...memo,
rowStatus: "ARCHIVED", pinned: true,
}); });
} }
} catch (error) { } catch (error) {
@ -66,7 +66,7 @@ const Memo: React.FC<Props> = (props: Props) => {
} }
if (globalStateService.getState().editMemoId === memo.id) { if (globalStateService.getState().editMemoId === memo.id) {
globalStateService.setEditMemoId(""); globalStateService.setEditMemoId(UNKNOWN_ID);
} }
} else { } else {
toggleConfirmDeleteBtn(); toggleConfirmDeleteBtn();
@ -88,7 +88,7 @@ const Memo: React.FC<Props> = (props: Props) => {
if (targetEl.className === "memo-link-text") { if (targetEl.className === "memo-link-text") {
const memoId = targetEl.dataset?.value; const memoId = targetEl.dataset?.value;
const memoTemp = memoService.getMemoById(memoId ?? ""); const memoTemp = memoService.getMemoById(Number(memoId) ?? UNKNOWN_ID);
if (memoTemp) { if (memoTemp) {
showMemoCardDialog(memoTemp); showMemoCardDialog(memoTemp);
@ -102,11 +102,11 @@ const Memo: React.FC<Props> = (props: Props) => {
}; };
return ( return (
<div className={`memo-wrapper ${"memos-" + memo.id} ${memo.rowStatus}`} onMouseLeave={handleMouseLeaveMemoWrapper}> <div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned ? "pinned" : ""}`} onMouseLeave={handleMouseLeaveMemoWrapper}>
<div className="memo-top-wrapper"> <div className="memo-top-wrapper">
<span className="time-text" onClick={handleShowMemoStoryDialog}> <span className="time-text" onClick={handleShowMemoStoryDialog}>
{memo.createdAtStr} {memo.createdAtStr}
<Only when={memo.rowStatus === "ARCHIVED"}> <Only when={memo.pinned}>
<span className="ml-2">PINNED</span> <span className="ml-2">PINNED</span>
</Only> </Only>
</span> </span>
@ -120,7 +120,7 @@ const Memo: React.FC<Props> = (props: Props) => {
View Story View Story
</span> </span>
<span className="btn" onClick={handleTogglePinMemoBtnClick}> <span className="btn" onClick={handleTogglePinMemoBtnClick}>
{memo.rowStatus === "NORMAL" ? "Pin" : "Unpin"} {memo.pinned ? "Unpin" : "Pin"}
</span> </span>
<span className="btn" onClick={handleMarkMemoClick}> <span className="btn" onClick={handleMarkMemoClick}>
Mark Mark

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { IMAGE_URL_REG, MEMO_LINK_REG } from "../helpers/consts"; import { IMAGE_URL_REG, MEMO_LINK_REG, UNKNOWN_ID } from "../helpers/consts";
import utils from "../helpers/utils"; import utils from "../helpers/utils";
import { globalStateService, memoService } from "../services"; import { globalStateService, memoService } from "../services";
import { parseHtmlToRawText } from "../helpers/marked"; import { parseHtmlToRawText } from "../helpers/marked";
@ -11,18 +11,18 @@ import Image from "./Image";
import "../less/memo-card-dialog.less"; import "../less/memo-card-dialog.less";
import "../less/memo-content.less"; import "../less/memo-content.less";
interface LinkedMemo extends FormattedMemo { interface LinkedMemo extends Memo {
createdAtStr: string;
dateStr: string; dateStr: string;
} }
interface Props extends DialogProps { interface Props extends DialogProps {
memo: Model.Memo; memo: Memo;
} }
const MemoCardDialog: React.FC<Props> = (props: Props) => { const MemoCardDialog: React.FC<Props> = (props: Props) => {
const [memo, setMemo] = useState<FormattedMemo>({ const [memo, setMemo] = useState<Memo>({
...props.memo, ...props.memo,
createdAtStr: utils.getDateTimeString(props.memo.createdAt),
}); });
const [linkMemos, setLinkMemos] = useState<LinkedMemo[]>([]); const [linkMemos, setLinkMemos] = useState<LinkedMemo[]>([]);
const [linkedMemos, setLinkedMemos] = useState<LinkedMemo[]>([]); const [linkedMemos, setLinkedMemos] = useState<LinkedMemo[]>([]);
@ -36,12 +36,12 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
for (const matchRes of matchedArr) { for (const matchRes of matchedArr) {
if (matchRes && matchRes.length === 3) { if (matchRes && matchRes.length === 3) {
const id = matchRes[2]; const id = matchRes[2];
const memoTemp = memoService.getMemoById(id); const memoTemp = memoService.getMemoById(Number(id));
if (memoTemp) { if (memoTemp) {
linkMemos.push({ linkMemos.push({
...memoTemp, ...memoTemp,
createdAtStr: utils.getDateTimeString(memoTemp.createdAt), createdAtStr: utils.getDateTimeString(memoTemp.createdTs),
dateStr: utils.getDateString(memoTemp.createdAt), dateStr: utils.getDateString(memoTemp.createdTs),
}); });
} }
} }
@ -51,11 +51,11 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
const linkedMemos = await memoService.getLinkedMemos(memo.id); const linkedMemos = await memoService.getLinkedMemos(memo.id);
setLinkedMemos( setLinkedMemos(
linkedMemos linkedMemos
.sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)) .sort((a, b) => utils.getTimeStampByDate(b.createdTs) - utils.getTimeStampByDate(a.createdTs))
.map((m) => ({ .map((m) => ({
...m, ...m,
createdAtStr: utils.getDateTimeString(m.createdAt), createdAtStr: utils.getDateTimeString(m.createdTs),
dateStr: utils.getDateString(m.createdAt), dateStr: utils.getDateString(m.createdTs),
})) }))
); );
} catch (error) { } catch (error) {
@ -71,12 +71,12 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
if (targetEl.className === "memo-link-text") { if (targetEl.className === "memo-link-text") {
const nextMemoId = targetEl.dataset?.value; const nextMemoId = targetEl.dataset?.value;
const memoTemp = memoService.getMemoById(nextMemoId ?? ""); const memoTemp = memoService.getMemoById(Number(nextMemoId) ?? UNKNOWN_ID);
if (memoTemp) { if (memoTemp) {
const nextMemo = { const nextMemo = {
...memoTemp, ...memoTemp,
createdAtStr: utils.getDateTimeString(memoTemp.createdAt), createdAtStr: utils.getDateTimeString(memoTemp.createdTs),
}; };
setLinkMemos([]); setLinkMemos([]);
setLinkedMemos([]); setLinkedMemos([]);
@ -88,7 +88,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
} }
}, []); }, []);
const handleLinkedMemoClick = useCallback((memo: FormattedMemo) => { const handleLinkedMemoClick = useCallback((memo: Memo) => {
setLinkMemos([]); setLinkMemos([]);
setLinkedMemos([]); setLinkedMemos([]);
setMemo(memo); setMemo(memo);
@ -103,7 +103,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
<> <>
<div className="memo-card-container"> <div className="memo-card-container">
<div className="header-container"> <div className="header-container">
<p className="time-text">{memo.createdAtStr}</p> <p className="time-text">{utils.getDateTimeString(memo.createdTs)}</p>
<div className="btns-container"> <div className="btns-container">
<button className="btn edit-btn" onClick={handleEditMemoBtnClick}> <button className="btn edit-btn" onClick={handleEditMemoBtnClick}>
<img className="icon-img" src="/icons/edit.svg" /> <img className="icon-img" src="/icons/edit.svg" />
@ -179,7 +179,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
); );
}; };
export default function showMemoCardDialog(memo: Model.Memo): void { export default function showMemoCardDialog(memo: Memo): void {
showDialog( showDialog(
{ {
className: "memo-card-dialog", className: "memo-card-dialog",

View File

@ -1,7 +1,7 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef } from "react"; import React, { useCallback, useContext, useEffect, useMemo, useRef } from "react";
import appContext from "../stores/appContext"; import appContext from "../stores/appContext";
import { globalStateService, locationService, memoService, resourceService } from "../services"; import { globalStateService, locationService, memoService, resourceService } from "../services";
import utils from "../helpers/utils"; import { UNKNOWN_ID } from "../helpers/consts";
import { storage } from "../helpers/storage"; import { storage } from "../helpers/storage";
import useToggle from "../hooks/useToggle"; import useToggle from "../hooks/useToggle";
import toastHelper from "./Toast"; import toastHelper from "./Toast";
@ -53,14 +53,14 @@ const MemoEditor: React.FC<Props> = () => {
const tagSeletorRef = useRef<HTMLDivElement>(null); const tagSeletorRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
if (globalState.markMemoId) { if (globalState.markMemoId !== UNKNOWN_ID) {
const editorCurrentValue = editorRef.current?.getContent(); const editorCurrentValue = editorRef.current?.getContent();
const memoLinkText = `${editorCurrentValue ? "\n" : ""}Mark: [@MEMO](${globalState.markMemoId})`; const memoLinkText = `${editorCurrentValue ? "\n" : ""}Mark: [@MEMO](${globalState.markMemoId})`;
editorRef.current?.insertText(memoLinkText); editorRef.current?.insertText(memoLinkText);
globalStateService.setMarkMemoId(""); globalStateService.setMarkMemoId(UNKNOWN_ID);
} }
if (globalState.editMemoId && globalState.editMemoId !== prevGlobalStateRef.current.editMemoId) { if (globalState.editMemoId !== UNKNOWN_ID && globalState.editMemoId !== prevGlobalStateRef.current.editMemoId) {
const editMemo = memoService.getMemoById(globalState.editMemoId); const editMemo = memoService.getMemoById(globalState.editMemoId);
if (editMemo) { if (editMemo) {
editorRef.current?.setContent(editMemo.content ?? ""); editorRef.current?.setContent(editMemo.content ?? "");
@ -147,15 +147,15 @@ const MemoEditor: React.FC<Props> = () => {
const { editMemoId } = globalStateService.getState(); const { editMemoId } = globalStateService.getState();
try { try {
if (editMemoId) { if (editMemoId !== UNKNOWN_ID) {
const prevMemo = memoService.getMemoById(editMemoId); const prevMemo = memoService.getMemoById(editMemoId);
if (prevMemo && prevMemo.content !== content) { if (prevMemo && prevMemo.content !== content) {
const editedMemo = await memoService.updateMemo(prevMemo.id, content); const editedMemo = await memoService.updateMemo(prevMemo.id, content);
editedMemo.updatedAt = utils.getDateTimeString(Date.now()); editedMemo.createdTs = Date.now();
memoService.editMemo(editedMemo); memoService.editMemo(editedMemo);
} }
globalStateService.setEditMemoId(""); globalStateService.setEditMemoId(UNKNOWN_ID);
} else { } else {
const newMemo = await memoService.createMemo(content); const newMemo = await memoService.createMemo(content);
memoService.pushMemo(newMemo); memoService.pushMemo(newMemo);
@ -169,7 +169,7 @@ const MemoEditor: React.FC<Props> = () => {
}, []); }, []);
const handleCancelBtnClick = useCallback(() => { const handleCancelBtnClick = useCallback(() => {
globalStateService.setEditMemoId(""); globalStateService.setEditMemoId(UNKNOWN_ID);
editorRef.current?.setContent(""); editorRef.current?.setContent("");
setEditorContentCache(""); setEditorContentCache("");
}, []); }, []);
@ -259,7 +259,7 @@ const MemoEditor: React.FC<Props> = () => {
} }
}, []); }, []);
const isEditing = Boolean(globalState.editMemoId); const isEditing = globalState.editMemoId !== UNKNOWN_ID;
const editorConfig = useMemo( const editorConfig = useMemo(
() => ({ () => ({

View File

@ -53,7 +53,7 @@ const MemoList: React.FC<Props> = () => {
if ( if (
duration && duration &&
duration.from < duration.to && duration.from < duration.to &&
(utils.getTimeStampByDate(memo.createdAt) < duration.from || utils.getTimeStampByDate(memo.createdAt) > duration.to) (utils.getTimeStampByDate(memo.createdTs) < duration.from || utils.getTimeStampByDate(memo.createdTs) > duration.to)
) { ) {
shouldShow = false; shouldShow = false;
} }
@ -76,8 +76,8 @@ const MemoList: React.FC<Props> = () => {
}) })
: memos; : memos;
const pinnedMemos = shownMemos.filter((m) => m.rowStatus === "ARCHIVED"); const pinnedMemos = shownMemos.filter((m) => m.pinned);
const unpinnedMemos = shownMemos.filter((m) => m.rowStatus === "NORMAL"); const unpinnedMemos = shownMemos.filter((m) => !m.pinned);
const sortedMemos = pinnedMemos.concat(unpinnedMemos); const sortedMemos = pinnedMemos.concat(unpinnedMemos);
useEffect(() => { useEffect(() => {
@ -112,7 +112,7 @@ const MemoList: React.FC<Props> = () => {
return ( return (
<div className={`memo-list-container ${isFetching ? "" : "completed"}`} onClick={handleMemoListClick} ref={wrapperElement}> <div className={`memo-list-container ${isFetching ? "" : "completed"}`} onClick={handleMemoListClick} ref={wrapperElement}>
{sortedMemos.map((memo) => ( {sortedMemos.map((memo) => (
<Memo key={`${memo.id}-${memo.updatedAt}`} memo={memo} /> <Memo key={`${memo.id}-${memo.updatedTs}`} memo={memo} />
))} ))}
<div className="status-text-container"> <div className="status-text-container">
<p className="status-text"> <p className="status-text">

View File

@ -11,7 +11,7 @@ interface Props extends DialogProps {}
const MemoTrashDialog: React.FC<Props> = (props: Props) => { const MemoTrashDialog: React.FC<Props> = (props: Props) => {
const { destroy } = props; const { destroy } = props;
const loadingState = useLoading(); const loadingState = useLoading();
const [deletedMemos, setDeletedMemos] = useState<Model.Memo[]>([]); const [deletedMemos, setDeletedMemos] = useState<Memo[]>([]);
useEffect(() => { useEffect(() => {
memoService.fetchAllMemos(); memoService.fetchAllMemos();
@ -31,7 +31,7 @@ const MemoTrashDialog: React.FC<Props> = (props: Props) => {
locationService.clearQuery(); locationService.clearQuery();
}, []); }, []);
const handleDeletedMemoAction = useCallback((memoId: string) => { const handleDeletedMemoAction = useCallback((memoId: MemoId) => {
setDeletedMemos((deletedMemos) => deletedMemos.filter((memo) => memo.id !== memoId)); setDeletedMemos((deletedMemos) => deletedMemos.filter((memo) => memo.id !== memoId));
}, []); }, []);
@ -58,7 +58,7 @@ const MemoTrashDialog: React.FC<Props> = (props: Props) => {
) : ( ) : (
<div className="deleted-memos-container"> <div className="deleted-memos-container">
{deletedMemos.map((memo) => ( {deletedMemos.map((memo) => (
<DeletedMemo key={`${memo.id}-${memo.updatedAt}`} memo={memo} handleDeletedMemoAction={handleDeletedMemoAction} /> <DeletedMemo key={`${memo.id}-${memo.updatedTs}`} memo={memo} handleDeletedMemoAction={handleDeletedMemoAction} />
))} ))}
</div> </div>
)} )}

View File

@ -16,7 +16,7 @@ const PreferencesSection: React.FC<Props> = () => {
createUserEmail: "", createUserEmail: "",
createUserPassword: "", createUserPassword: "",
}); });
const [userList, setUserList] = useState<Model.User[]>([]); const [userList, setUserList] = useState<User[]>([]);
useEffect(() => { useEffect(() => {
fetchUserList(); fetchUserList();
@ -47,7 +47,7 @@ const PreferencesSection: React.FC<Props> = () => {
return; return;
} }
const userCreate: API.UserCreate = { const userCreate: UserCreate = {
email: state.createUserEmail, email: state.createUserEmail,
password: state.createUserPassword, password: state.createUserPassword,
role: "USER", role: "USER",
@ -83,10 +83,18 @@ const PreferencesSection: React.FC<Props> = () => {
</div> </div>
</div> </div>
<p className="title-text">Member list</p> <p className="title-text">Member list</p>
<div className="member-container field-container">
<span className="field-text">ID</span>
<span className="field-text">EMAIL</span>
</div>
{userList.map((user) => ( {userList.map((user) => (
<div key={user.id} className="user-container"> <div key={user.id} className="member-container">
<span className="field-text id-text">{user.id}</span> <span className="field-text id-text">{user.id}</span>
<span className="field-text">{user.email}</span> <span className="field-text email-text">{user.email}</span>
{/* TODO */}
{/* <div className="buttons-container">
<span>delete</span>
</div> */}
</div> </div>
))} ))}
</div> </div>

View File

@ -1,7 +1,6 @@
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import appContext from "../../stores/appContext"; import appContext from "../../stores/appContext";
import { userService } from "../../services"; import { userService } from "../../services";
import utils from "../../helpers/utils";
import { validate, ValidatorConfig } from "../../helpers/validator"; import { validate, ValidatorConfig } from "../../helpers/validator";
import toastHelper from "../Toast"; import toastHelper from "../Toast";
import showChangePasswordDialog from "../ChangePasswordDialog"; import showChangePasswordDialog from "../ChangePasswordDialog";
@ -19,7 +18,7 @@ interface Props {}
const MyAccountSection: React.FC<Props> = () => { const MyAccountSection: React.FC<Props> = () => {
const { userState } = useContext(appContext); const { userState } = useContext(appContext);
const user = userState.user as Model.User; const user = userState.user as User;
const [username, setUsername] = useState<string>(user.name); const [username, setUsername] = useState<string>(user.name);
const openAPIRoute = `${window.location.origin}/h/${user.openId}/memo`; const openAPIRoute = `${window.location.origin}/h/${user.openId}/memo`;
@ -69,10 +68,6 @@ const MyAccountSection: React.FC<Props> = () => {
<span className="normal-text">Email:</span> <span className="normal-text">Email:</span>
<span className="normal-text">{user.email}</span> <span className="normal-text">{user.email}</span>
</label> </label>
<label className="form-label">
<span className="normal-text">Created at:</span>
<span className="normal-text">{utils.getDateString(user.createdAt)}</span>
</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">Username:</span>
<input type="text" value={username} onChange={handleUsernameChanged} /> <input type="text" value={username} onChange={handleUsernameChanged} />

View File

@ -10,7 +10,7 @@ const PreferencesSection: React.FC<Props> = () => {
const formatedMemos = memoService.getState().memos.map((m) => { const formatedMemos = memoService.getState().memos.map((m) => {
return { return {
content: m.content, content: m.content,
createdAt: m.createdAt, createdTs: m.createdTs,
}; };
}); });
@ -33,7 +33,7 @@ const PreferencesSection: React.FC<Props> = () => {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsText(fileInputEl.files[0]); reader.readAsText(fileInputEl.files[0]);
reader.onload = async (event) => { reader.onload = async (event) => {
const memoList = JSON.parse(event.target?.result as string) as Model.Memo[]; const memoList = JSON.parse(event.target?.result as string) as Memo[];
if (!Array.isArray(memoList)) { if (!Array.isArray(memoList)) {
toastHelper.error("Unexpected data type."); toastHelper.error("Unexpected data type.");
} }
@ -42,7 +42,7 @@ const PreferencesSection: React.FC<Props> = () => {
for (const memo of memoList) { for (const memo of memoList) {
const content = memo.content || ""; const content = memo.content || "";
const createdAt = memo.createdAt || utils.getDateTimeString(Date.now()); const createdAt = utils.getDateTimeString(memo.createdTs || Date.now());
try { try {
await memoService.importMemo(content, createdAt); await memoService.importMemo(content, createdAt);

View File

@ -10,15 +10,15 @@ import toastHelper from "./Toast";
import "../less/share-memo-image-dialog.less"; import "../less/share-memo-image-dialog.less";
interface Props extends DialogProps { interface Props extends DialogProps {
memo: Model.Memo; memo: Memo;
} }
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 { user: userinfo } = userService.getState(); const { user: userinfo } = userService.getState();
const memo: FormattedMemo = { const memo = {
...propsMemo, ...propsMemo,
createdAtStr: utils.getDateTimeString(propsMemo.createdAt), createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
}; };
const memoImgUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []); const memoImgUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []);
@ -106,7 +106,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
); );
}; };
export default function showShareMemoImageDialog(memo: Model.Memo): void { export default function showShareMemoImageDialog(memo: Memo): void {
showDialog( showDialog(
{ {
className: "share-memo-image-dialog", className: "share-memo-image-dialog",

View File

@ -20,10 +20,10 @@ const ShortcutList: React.FC<Props> = () => {
const loadingState = useLoading(); const loadingState = useLoading();
const pinnedShortcuts = shortcuts const pinnedShortcuts = shortcuts
.filter((s) => s.rowStatus === "ARCHIVED") .filter((s) => s.rowStatus === "ARCHIVED")
.sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)); .sort((a, b) => utils.getTimeStampByDate(b.createdTs) - utils.getTimeStampByDate(a.createdTs));
const unpinnedShortcuts = shortcuts const unpinnedShortcuts = shortcuts
.filter((s) => s.rowStatus === "NORMAL") .filter((s) => s.rowStatus === "NORMAL")
.sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)); .sort((a, b) => utils.getTimeStampByDate(b.createdTs) - utils.getTimeStampByDate(a.createdTs));
const sortedShortcuts = pinnedShortcuts.concat(unpinnedShortcuts); const sortedShortcuts = pinnedShortcuts.concat(unpinnedShortcuts);
useEffect(() => { useEffect(() => {
@ -55,7 +55,7 @@ const ShortcutList: React.FC<Props> = () => {
}; };
interface ShortcutContainerProps { interface ShortcutContainerProps {
shortcut: Model.Shortcut; shortcut: Shortcut;
isActive: boolean; isActive: boolean;
} }

View File

@ -17,7 +17,7 @@ const Sidebar: React.FC<Props> = () => {
memoState: { memos, tags }, memoState: { memos, tags },
userState: { user }, userState: { user },
} = useContext(appContext); } = useContext(appContext);
const createdDays = user ? Math.ceil((Date.now() - utils.getTimeStampByDate(user.createdAt)) / 1000 / 3600 / 24) : 0; const createdDays = user ? Math.ceil((Date.now() - utils.getTimeStampByDate(user.createdTs)) / 1000 / 3600 / 24) : 0;
const handleMyAccountBtnClick = () => { const handleMyAccountBtnClick = () => {
showSettingDialog(); showSettingDialog();

View File

@ -77,7 +77,7 @@ const TagList: React.FC<Props> = () => {
))} ))}
<Only when={tags.length < 5 && memoService.initialized}> <Only when={tags.length < 5 && memoService.initialized}>
<p className="tag-tip-container"> <p className="tag-tip-container">
Enter <span className="code-text">#Tag </span> to create a tag Enter <span className="code-text">#tag </span> to create a tag
</p> </p>
</Only> </Only>
</div> </div>

View File

@ -1,5 +1,3 @@
import utils from "./utils";
type ResponseObject<T> = { type ResponseObject<T> = {
data: T; data: T;
error?: string; error?: string;
@ -42,29 +40,14 @@ async function request<T>(config: RequestConfig): Promise<T> {
namespace api { namespace api {
export function getSystemStatus() { export function getSystemStatus() {
return request<API.SystemStatus>({ return request<SystemStatus>({
method: "GET", method: "GET",
url: "/api/status", url: "/api/status",
}); });
} }
export function getUserList() {
return request<Model.User[]>({
method: "GET",
url: "/api/user",
});
}
export function createUser(userCreate: API.UserCreate) {
return request<Model.User[]>({
method: "POST",
url: "/api/user",
data: userCreate,
});
}
export function login(email: string, password: string) { export function login(email: string, password: string) {
return request<Model.User>({ return request<User>({
method: "POST", method: "POST",
url: "/api/auth/login", url: "/api/auth/login",
data: { data: {
@ -75,7 +58,7 @@ namespace api {
} }
export function signup(email: string, password: string, role: UserRole) { export function signup(email: string, password: string, role: UserRole) {
return request<Model.User>({ return request<User>({
method: "POST", method: "POST",
url: "/api/auth/signup", url: "/api/auth/signup",
data: { data: {
@ -94,70 +77,89 @@ namespace api {
}); });
} }
export function getUserInfo() { export function createUser(userCreate: UserCreate) {
return request<Model.User>({ return request<User[]>({
method: "POST",
url: "/api/user",
data: userCreate,
});
}
export function getUser() {
return request<User>({
method: "GET", method: "GET",
url: "/api/user/me", url: "/api/user/me",
}); });
} }
export function updateUserinfo(userinfo: Partial<{ name: string; password: string; resetOpenId: boolean }>) { export function getUserList() {
return request<Model.User>({ return request<User[]>({
method: "PATCH", method: "GET",
url: "/api/user/me", url: "/api/user",
data: userinfo,
}); });
} }
export function resetOpenId() { export function patchUser(userPatch: UserPatch) {
return request<string>({ return request<User>({
method: "POST", method: "PATCH",
url: "/api/user/open_id/new", url: "/api/user/me",
data: userPatch,
}); });
} }
export function getMyMemos() { export function getMyMemos() {
return request<Model.Memo[]>({ return request<Memo[]>({
method: "GET", method: "GET",
url: "/api/memo", url: "/api/memo",
}); });
} }
export function getMyDeletedMemos() { export function getMyArchivedMemos() {
return request<Model.Memo[]>({ return request<Memo[]>({
method: "GET", method: "GET",
url: "/api/memo?rowStatus=HIDDEN", url: "/api/memo?rowStatus=ARCHIVED",
}); });
} }
export function createMemo(content: string, createdAt?: string) { export function createMemo(memoCreate: MemoCreate) {
const data: any = { return request<Memo>({
content,
};
if (createdAt) {
const createdTms = utils.getTimeStampByDate(createdAt);
data.createdTs = Math.floor(createdTms / 1000);
}
return request<Model.Memo>({
method: "POST", method: "POST",
url: "/api/memo", url: "/api/memo",
data: data, data: memoCreate,
}); });
} }
export function updateMemo(memoId: string, content: string) { export function patchMemo(memoPatch: MemoPatch) {
return request<Model.Memo>({ return request<Memo>({
method: "PATCH", method: "PATCH",
url: `/api/memo/${memoId}`, url: `/api/memo/${memoPatch.id}`,
data: { data: {
content, memoPatch,
}, },
}); });
} }
export function pinMemo(memoId: string) { export function pinMemo(memoId: MemoId) {
return request({
method: "POST",
url: `/api/memo/${memoId}/organizer`,
data: {
pinned: true,
},
});
}
export function unpinMemo(memoId: MemoId) {
return request({
method: "POST",
url: `/api/memo/${memoId}/organizer`,
data: {
pinned: false,
},
});
}
export function archiveMemo(memoId: MemoId) {
return request({ return request({
method: "PATCH", method: "PATCH",
url: `/api/memo/${memoId}`, url: `/api/memo/${memoId}`,
@ -167,27 +169,7 @@ namespace api {
}); });
} }
export function unpinMemo(shortcutId: string) { export function restoreMemo(memoId: MemoId) {
return request({
method: "PATCH",
url: `/api/memo/${shortcutId}`,
data: {
rowStatus: "NORMAL",
},
});
}
export function hideMemo(memoId: string) {
return request({
method: "PATCH",
url: `/api/memo/${memoId}`,
data: {
rowStatus: "HIDDEN",
},
});
}
export function restoreMemo(memoId: string) {
return request({ return request({
method: "PATCH", method: "PATCH",
url: `/api/memo/${memoId}`, url: `/api/memo/${memoId}`,
@ -197,7 +179,7 @@ namespace api {
}); });
} }
export function deleteMemo(memoId: string) { export function deleteMemo(memoId: MemoId) {
return request({ return request({
method: "DELETE", method: "DELETE",
url: `/api/memo/${memoId}`, url: `/api/memo/${memoId}`,
@ -205,14 +187,14 @@ namespace api {
} }
export function getMyShortcuts() { export function getMyShortcuts() {
return request<Model.Shortcut[]>({ return request<Shortcut[]>({
method: "GET", method: "GET",
url: "/api/shortcut", url: "/api/shortcut",
}); });
} }
export function createShortcut(title: string, payload: string) { export function createShortcut(title: string, payload: string) {
return request<Model.Shortcut>({ return request<Shortcut>({
method: "POST", method: "POST",
url: "/api/shortcut", url: "/api/shortcut",
data: { data: {
@ -223,7 +205,7 @@ namespace api {
} }
export function updateShortcut(shortcutId: string, title: string, payload: string) { export function updateShortcut(shortcutId: string, title: string, payload: string) {
return request<Model.Shortcut>({ return request<Shortcut>({
method: "PATCH", method: "PATCH",
url: `/api/shortcut/${shortcutId}`, url: `/api/shortcut/${shortcutId}`,
data: { data: {
@ -261,7 +243,7 @@ namespace api {
} }
export function uploadFile(formData: FormData) { export function uploadFile(formData: FormData) {
return request<Model.Resource>({ return request<Resource>({
method: "POST", method: "POST",
url: "/api/resource", url: "/api/resource",
data: formData, data: formData,

View File

@ -1,3 +1,6 @@
// UNKNOWN_ID is the symbol for unknown id
export const UNKNOWN_ID = -1;
// default animation duration // default animation duration
export const ANIMATION_DURATION = 200; export const ANIMATION_DURATION = 200;

View File

@ -90,7 +90,7 @@ export const getDefaultFilter = (): BaseFilter => {
}; };
}; };
export const checkShouldShowMemoWithFilters = (memo: Model.Memo, filters: Filter[]) => { export const checkShouldShowMemoWithFilters = (memo: Memo, filters: Filter[]) => {
let shouldShow = true; let shouldShow = true;
for (const f of filters) { for (const f of filters) {
@ -106,7 +106,7 @@ export const checkShouldShowMemoWithFilters = (memo: Model.Memo, filters: Filter
return shouldShow; return shouldShow;
}; };
export const checkShouldShowMemo = (memo: Model.Memo, filter: Filter) => { export const checkShouldShowMemo = (memo: Memo, filter: Filter) => {
const { const {
type, type,
value: { operator, value }, value: { operator, value },

View File

@ -83,8 +83,8 @@ namespace utils {
return Array.from(new Set(data)); return Array.from(new Set(data));
} }
export function dedupeObjectWithId<T extends { id: string }>(data: T[]): T[] { export function dedupeObjectWithId<T extends { id: string | number }>(data: T[]): T[] {
const idSet = new Set<string>(); const idSet = new Set<string | number>();
const result = []; const result = [];
for (const d of data) { for (const d of data) {

View File

@ -9,7 +9,7 @@
@apply border-gray-200; @apply border-gray-200;
} }
&.ARCHIVED { &.pinned {
@apply border-gray-200 border-2; @apply border-gray-200 border-2;
} }

View File

@ -25,15 +25,29 @@
} }
} }
> .user-container { > .field-container {
@apply w-full mb-4 grid grid-cols-5; > .field-text {
@apply text-gray-400 text-sm;
}
}
> .member-container {
@apply w-full grid grid-cols-5 border-b py-2;
> .field-text { > .field-text {
@apply text-base mr-4 w-16; @apply text-base pl-2 mr-4 w-16;
&.id-text { &.id-text {
@apply font-mono; @apply font-mono text-gray-600;
} }
&.email-text {
@apply col-span-3;
}
}
> .buttons-container {
@apply col-span-1;
} }
} }
} }

View File

@ -42,7 +42,7 @@
> .shortcut-container { > .shortcut-container {
.flex(row, space-between, center); .flex(row, space-between, center);
@apply w-full h-10 py-0 px-4 mt-1 rounded-lg text-sm cursor-pointer select-none shrink-0; @apply w-full h-10 py-0 px-4 mt-2 rounded-lg text-base cursor-pointer select-none shrink-0;
&:hover { &:hover {
background-color: @bg-gray; background-color: @bg-gray;

View File

@ -5,14 +5,21 @@
@apply w-full h-full bg-white; @apply w-full h-full bg-white;
> .page-container { > .page-container {
@apply w-80 max-w-full p-4 -mt-16; @apply w-80 max-w-full py-4 -mt-16;
> .page-header-container { > .page-header-container {
.flex(row, space-between, center); @apply flex flex-col justify-start items-start w-full mb-4;
@apply w-full mb-4;
> .title-text { > .title-text {
@apply text-2xl; @apply text-2xl;
> .icon-text {
@apply text-4xl;
}
}
> .slogan-text {
@apply mt-2 text-sm text-gray-600;
} }
} }
@ -22,7 +29,7 @@
> .form-item-container { > .form-item-container {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);
@apply relative w-full text-base my-2; @apply relative w-full text-base mt-2;
> .normal-text { > .normal-text {
@apply absolute top-3 left-3 px-1 leading-10 flex-shrink-0 text-base cursor-text text-gray-400 bg-transparent transition-all select-none; @apply absolute top-3 left-3 px-1 leading-10 flex-shrink-0 text-base cursor-text text-gray-400 bg-transparent transition-all select-none;
@ -36,7 +43,7 @@
@apply py-2; @apply py-2;
> input { > input {
@apply w-full py-3 px-3 text-base rounded-lg border border-solid border-gray-400; @apply w-full py-3 px-3 text-base shadow-inner rounded-lg border border-solid border-gray-400;
} }
&:hover { &:hover {
@ -51,24 +58,16 @@
@apply w-full mt-2; @apply w-full mt-2;
> .btn { > .btn {
@apply px-1 py-2 text-sm rounded; @apply px-1 py-2 text-sm rounded hover:opacity-80;
&:hover {
@apply opacity-80;
}
&.disabled {
@apply text-gray-400 cursor-not-allowed;
}
&.signin-btn { &.signin-btn {
@apply bg-green-600 text-white px-3; @apply bg-green-600 text-white px-3 shadow;
}
&.requesting { &.requesting {
@apply cursor-wait opacity-80; @apply cursor-wait opacity-80;
} }
} }
}
> .btn-text { > .btn-text {
@apply text-sm; @apply text-sm;

View File

@ -27,7 +27,7 @@
.tag-item-container { .tag-item-container {
.flex(row, space-between, center); .flex(row, space-between, center);
@apply w-full h-10 py-0 px-4 rounded-lg text-sm shrink-0 select-none cursor-pointer; @apply w-full h-10 py-0 px-4 rounded-lg text-base shrink-0 select-none cursor-pointer;
&:hover { &:hover {
background-color: @bg-gray; background-color: @bg-gray;

View File

@ -17,7 +17,7 @@ const validateConfig: ValidatorConfig = {
const Signin: React.FC<Props> = () => { const Signin: React.FC<Props> = () => {
const pageLoadingState = useLoading(true); const pageLoadingState = useLoading(true);
const [siteOwner, setSiteOwner] = useState<Model.User>(); const [siteOwner, setSiteOwner] = useState<User>();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const actionBtnLoadingState = useLoading(false); const actionBtnLoadingState = useLoading(false);
@ -128,12 +128,15 @@ const Signin: React.FC<Props> = () => {
}; };
return ( return (
<div className="page-wrapper signin"> <div className={`page-wrapper signin ${pageLoadingState.isLoading ? "hidden" : ""}`}>
<div className="page-container"> <div className="page-container">
<div className="page-header-container"> <div className="page-header-container">
<p className="title-text"> <p className="title-text">
<span className="icon-text"></span> Memos <span className="icon-text"></span> Memos
</p> </p>
<p className="slogan-text">
An <i>open source</i>, <i>self-hosted</i> knowledge base that works with local SQLite.
</p>
</div> </div>
<div className="page-content-container"> <div className="page-content-container">
<div className="form-item-container input-form-container"> <div className="form-item-container input-form-container">
@ -150,7 +153,7 @@ const Signin: React.FC<Props> = () => {
Login as Guest Login as Guest
</button> </button>
<span className="split-text">/</span> <span className="split-text">/</span>
{siteOwner || pageLoadingState.isLoading ? ( {siteOwner ? (
<button <button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`} className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSigninBtnsClick()} onClick={() => handleSigninBtnsClick()}
@ -167,7 +170,7 @@ const Signin: React.FC<Props> = () => {
)} )}
</div> </div>
<p className="tip-text"> <p className="tip-text">
{siteOwner || pageLoadingState.isLoading {siteOwner
? "If you don't have an account, please contact the site owner or login as guest." ? "If you don't have an account, please contact the site owner or login as guest."
: "You are registering as the site owner."} : "You are registering as the site owner."}
</p> </p>

View File

@ -18,7 +18,7 @@ class GlobalStateService {
return appStore.getState().globalState; return appStore.getState().globalState;
}; };
public setEditMemoId = (editMemoId: string) => { public setEditMemoId = (editMemoId: MemoId) => {
appStore.dispatch({ appStore.dispatch({
type: "SET_EDIT_MEMO_ID", type: "SET_EDIT_MEMO_ID",
payload: { payload: {
@ -27,7 +27,7 @@ class GlobalStateService {
}); });
}; };
public setMarkMemoId = (markMemoId: string) => { public setMarkMemoId = (markMemoId: MemoId) => {
appStore.dispatch({ appStore.dispatch({
type: "SET_MARK_MEMO_ID", type: "SET_MARK_MEMO_ID",
payload: { payload: {

View File

@ -17,7 +17,7 @@ class MemoService {
} }
const data = await api.getMyMemos(); const data = await api.getMyMemos();
const memos: Model.Memo[] = data.filter((m) => m.rowStatus !== "HIDDEN").map((m) => this.convertResponseModelMemo(m)); const memos: Memo[] = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => this.convertResponseModelMemo(m));
appStore.dispatch({ appStore.dispatch({
type: "SET_MEMOS", type: "SET_MEMOS",
payload: { payload: {
@ -37,14 +37,14 @@ class MemoService {
return false; return false;
} }
const data = await api.getMyDeletedMemos(); const data = await api.getMyArchivedMemos();
const deletedMemos: Model.Memo[] = data.map((m) => { const deletedMemos: Memo[] = data.map((m) => {
return this.convertResponseModelMemo(m); return this.convertResponseModelMemo(m);
}); });
return deletedMemos; return deletedMemos;
} }
public pushMemo(memo: Model.Memo) { public pushMemo(memo: Memo) {
appStore.dispatch({ appStore.dispatch({
type: "INSERT_MEMO", type: "INSERT_MEMO",
payload: { payload: {
@ -55,7 +55,7 @@ class MemoService {
}); });
} }
public getMemoById(id: string) { public getMemoById(id: MemoId) {
for (const m of this.getState().memos) { for (const m of this.getState().memos) {
if (m.id === id) { if (m.id === id) {
return m; return m;
@ -65,8 +65,8 @@ class MemoService {
return null; return null;
} }
public async hideMemoById(id: string) { public async hideMemoById(id: MemoId) {
await api.hideMemo(id); await api.archiveMemo(id);
appStore.dispatch({ appStore.dispatch({
type: "DELETE_MEMO_BY_ID", type: "DELETE_MEMO_BY_ID",
payload: { payload: {
@ -75,17 +75,17 @@ class MemoService {
}); });
} }
public async restoreMemoById(id: string) { public async restoreMemoById(id: MemoId) {
await api.restoreMemo(id); await api.restoreMemo(id);
memoService.clearMemos(); memoService.clearMemos();
memoService.fetchAllMemos(); memoService.fetchAllMemos();
} }
public async deleteMemoById(id: string) { public async deleteMemoById(id: MemoId) {
await api.deleteMemo(id); await api.deleteMemo(id);
} }
public editMemo(memo: Model.Memo) { public editMemo(memo: Memo) {
appStore.dispatch({ appStore.dispatch({
type: "EDIT_MEMO", type: "EDIT_MEMO",
payload: memo, payload: memo,
@ -118,39 +118,48 @@ class MemoService {
}); });
} }
public async getLinkedMemos(memoId: string): Promise<Model.Memo[]> { public async getLinkedMemos(memoId: MemoId): Promise<Memo[]> {
const { memos } = this.getState(); const { memos } = this.getState();
return memos.filter((m) => m.content.includes(memoId)); return memos.filter((m) => m.content.includes(`${memoId}`));
} }
public async createMemo(content: string): Promise<Model.Memo> { public async createMemo(content: string): Promise<Memo> {
const memo = await api.createMemo(content); const memo = await api.createMemo({
content,
});
return this.convertResponseModelMemo(memo); return this.convertResponseModelMemo(memo);
} }
public async updateMemo(memoId: string, content: string): Promise<Model.Memo> { public async updateMemo(memoId: MemoId, content: string): Promise<Memo> {
const memo = await api.updateMemo(memoId, content); const memo = await api.patchMemo({
id: memoId,
content,
});
return this.convertResponseModelMemo(memo); return this.convertResponseModelMemo(memo);
} }
public async pinMemo(memoId: string) { public async pinMemo(memoId: MemoId) {
await api.pinMemo(memoId); await api.pinMemo(memoId);
} }
public async unpinMemo(memoId: string) { public async unpinMemo(memoId: MemoId) {
await api.unpinMemo(memoId); await api.unpinMemo(memoId);
} }
public async importMemo(content: string, createdAt: string) { public async importMemo(content: string, createdAt: string) {
await api.createMemo(content, createdAt); const createdTs = Math.floor(utils.getTimeStampByDate(createdAt) / 1000);
await api.createMemo({
content,
createdTs,
});
} }
private convertResponseModelMemo(memo: Model.Memo): Model.Memo { private convertResponseModelMemo(memo: Memo): Memo {
return { return {
...memo, ...memo,
id: String(memo.id), createdTs: memo.createdTs * 1000,
createdAt: utils.getDataStringWithTs(memo.createdTs), updatedTs: memo.updatedTs * 1000,
updatedAt: utils.getDataStringWithTs(memo.updatedTs),
}; };
} }
} }

View File

@ -1,7 +1,6 @@
import userService from "./userService"; import userService from "./userService";
import api from "../helpers/api"; import api from "../helpers/api";
import appStore from "../stores/appStore"; import appStore from "../stores/appStore";
import utils from "../helpers/utils";
class ShortcutService { class ShortcutService {
public getState() { public getState() {
@ -33,7 +32,7 @@ class ShortcutService {
return null; return null;
} }
public pushShortcut(shortcut: Model.Shortcut) { public pushShortcut(shortcut: Shortcut) {
appStore.dispatch({ appStore.dispatch({
type: "INSERT_SHORTCUT", type: "INSERT_SHORTCUT",
payload: { payload: {
@ -44,7 +43,7 @@ class ShortcutService {
}); });
} }
public editShortcut(shortcut: Model.Shortcut) { public editShortcut(shortcut: Shortcut) {
appStore.dispatch({ appStore.dispatch({
type: "UPDATE_SHORTCUT", type: "UPDATE_SHORTCUT",
payload: shortcut, payload: shortcut,
@ -79,12 +78,11 @@ class ShortcutService {
await api.unpinShortcut(shortcutId); await api.unpinShortcut(shortcutId);
} }
public convertResponseModelShortcut(shortcut: Model.Shortcut): Model.Shortcut { public convertResponseModelShortcut(shortcut: Shortcut): Shortcut {
return { return {
...shortcut, ...shortcut,
id: String(shortcut.id), createdTs: shortcut.createdTs * 1000,
createdAt: utils.getDataStringWithTs(shortcut.createdTs), updatedTs: shortcut.updatedTs * 1000,
updatedAt: utils.getDataStringWithTs(shortcut.updatedTs),
}; };
} }
} }

View File

@ -1,5 +1,4 @@
import api from "../helpers/api"; import api from "../helpers/api";
import utils from "../helpers/utils";
import appStore from "../stores/appStore"; import appStore from "../stores/appStore";
class UserService { class UserService {
@ -8,7 +7,7 @@ class UserService {
} }
public async doSignIn() { public async doSignIn() {
const user = await api.getUserInfo(); const user = await api.getUser();
if (user) { if (user) {
appStore.dispatch({ appStore.dispatch({
type: "LOGIN", type: "LOGIN",
@ -33,19 +32,19 @@ class UserService {
} }
public async updateUsername(name: string): Promise<void> { public async updateUsername(name: string): Promise<void> {
await api.updateUserinfo({ await api.patchUser({
name, name,
}); });
} }
public async updatePassword(password: string): Promise<void> { public async updatePassword(password: string): Promise<void> {
await api.updateUserinfo({ await api.patchUser({
password, password,
}); });
} }
public async resetOpenId(): Promise<string> { public async resetOpenId(): Promise<string> {
const user = await api.updateUserinfo({ const user = await api.patchUser({
resetOpenId: true, resetOpenId: true,
}); });
appStore.dispatch({ appStore.dispatch({
@ -55,11 +54,11 @@ class UserService {
return user.openId; return user.openId;
} }
private convertResponseModelUser(user: Model.User): Model.User { private convertResponseModelUser(user: User): User {
return { return {
...user, ...user,
createdAt: utils.getDataStringWithTs(user.createdTs), createdTs: user.createdTs * 1000,
updatedAt: utils.getDataStringWithTs(user.updatedTs), updatedTs: user.updatedTs * 1000,
}; };
} }
} }

View File

@ -1,3 +1,5 @@
import { UNKNOWN_ID } from "../helpers/consts";
export interface AppSetting { export interface AppSetting {
shouldSplitMemoWord: boolean; shouldSplitMemoWord: boolean;
shouldHideImageUrl: boolean; shouldHideImageUrl: boolean;
@ -5,21 +7,21 @@ export interface AppSetting {
} }
export interface State extends AppSetting { export interface State extends AppSetting {
markMemoId: string; markMemoId: MemoId;
editMemoId: string; editMemoId: MemoId;
} }
interface SetMarkMemoIdAction { interface SetMarkMemoIdAction {
type: "SET_MARK_MEMO_ID"; type: "SET_MARK_MEMO_ID";
payload: { payload: {
markMemoId: string; markMemoId: MemoId;
}; };
} }
interface SetEditMemoIdAction { interface SetEditMemoIdAction {
type: "SET_EDIT_MEMO_ID"; type: "SET_EDIT_MEMO_ID";
payload: { payload: {
editMemoId: string; editMemoId: MemoId;
}; };
} }
@ -65,8 +67,8 @@ export function reducer(state: State, action: Actions) {
} }
export const defaultState: State = { export const defaultState: State = {
markMemoId: "", markMemoId: UNKNOWN_ID,
editMemoId: "", editMemoId: UNKNOWN_ID,
shouldSplitMemoWord: true, shouldSplitMemoWord: true,
shouldHideImageUrl: true, shouldHideImageUrl: true,
shouldUseMarkdownParser: true, shouldUseMarkdownParser: true,

View File

@ -1,14 +1,14 @@
import utils from "../helpers/utils"; import utils from "../helpers/utils";
export interface State { export interface State {
memos: Model.Memo[]; memos: Memo[];
tags: string[]; tags: string[];
} }
interface SetMemosAction { interface SetMemosAction {
type: "SET_MEMOS"; type: "SET_MEMOS";
payload: { payload: {
memos: Model.Memo[]; memos: Memo[];
}; };
} }
@ -22,20 +22,20 @@ interface SetTagsAction {
interface InsertMemoAction { interface InsertMemoAction {
type: "INSERT_MEMO"; type: "INSERT_MEMO";
payload: { payload: {
memo: Model.Memo; memo: Memo;
}; };
} }
interface DeleteMemoByIdAction { interface DeleteMemoByIdAction {
type: "DELETE_MEMO_BY_ID"; type: "DELETE_MEMO_BY_ID";
payload: { payload: {
id: string; id: MemoId;
}; };
} }
interface EditMemoByIdAction { interface EditMemoByIdAction {
type: "EDIT_MEMO"; type: "EDIT_MEMO";
payload: Model.Memo; payload: Memo;
} }
export type Actions = SetMemosAction | SetTagsAction | InsertMemoAction | DeleteMemoByIdAction | EditMemoByIdAction; export type Actions = SetMemosAction | SetTagsAction | InsertMemoAction | DeleteMemoByIdAction | EditMemoByIdAction;
@ -43,9 +43,7 @@ export type Actions = SetMemosAction | SetTagsAction | InsertMemoAction | Delete
export function reducer(state: State, action: Actions): State { export function reducer(state: State, action: Actions): State {
switch (action.type) { switch (action.type) {
case "SET_MEMOS": { case "SET_MEMOS": {
const memos = utils.dedupeObjectWithId( const memos = utils.dedupeObjectWithId(action.payload.memos.sort((a, b) => b.createdTs - a.createdTs));
action.payload.memos.sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt))
);
return { return {
...state, ...state,
@ -59,10 +57,7 @@ export function reducer(state: State, action: Actions): State {
}; };
} }
case "INSERT_MEMO": { case "INSERT_MEMO": {
const memos = utils.dedupeObjectWithId( const memos = utils.dedupeObjectWithId([action.payload.memo, ...state.memos].sort((a, b) => b.createdTs - a.createdTs));
[action.payload.memo, ...state.memos].sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt))
);
return { return {
...state, ...state,
memos, memos,

View File

@ -1,20 +1,20 @@
import utils from "../helpers/utils"; import utils from "../helpers/utils";
export interface State { export interface State {
shortcuts: Model.Shortcut[]; shortcuts: Shortcut[];
} }
interface SetShortcutsAction { interface SetShortcutsAction {
type: "SET_SHORTCUTS"; type: "SET_SHORTCUTS";
payload: { payload: {
shortcuts: Model.Shortcut[]; shortcuts: Shortcut[];
}; };
} }
interface InsertShortcutAction { interface InsertShortcutAction {
type: "INSERT_SHORTCUT"; type: "INSERT_SHORTCUT";
payload: { payload: {
shortcut: Model.Shortcut; shortcut: Shortcut;
}; };
} }
@ -27,7 +27,7 @@ interface DeleteShortcutByIdAction {
interface UpdateShortcutAction { interface UpdateShortcutAction {
type: "UPDATE_SHORTCUT"; type: "UPDATE_SHORTCUT";
payload: Model.Shortcut; payload: Shortcut;
} }
export type Actions = SetShortcutsAction | InsertShortcutAction | DeleteShortcutByIdAction | UpdateShortcutAction; export type Actions = SetShortcutsAction | InsertShortcutAction | DeleteShortcutByIdAction | UpdateShortcutAction;
@ -37,8 +37,8 @@ export function reducer(state: State, action: Actions): State {
case "SET_SHORTCUTS": { case "SET_SHORTCUTS": {
const shortcuts = utils.dedupeObjectWithId( const shortcuts = utils.dedupeObjectWithId(
action.payload.shortcuts action.payload.shortcuts
.sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)) .sort((a, b) => utils.getTimeStampByDate(b.createdTs) - utils.getTimeStampByDate(a.createdTs))
.sort((a, b) => utils.getTimeStampByDate(b.updatedAt) - utils.getTimeStampByDate(a.updatedAt)) .sort((a, b) => utils.getTimeStampByDate(b.updatedTs) - utils.getTimeStampByDate(a.updatedTs))
); );
return { return {
@ -49,7 +49,7 @@ export function reducer(state: State, action: Actions): State {
case "INSERT_SHORTCUT": { case "INSERT_SHORTCUT": {
const shortcuts = utils.dedupeObjectWithId( const shortcuts = utils.dedupeObjectWithId(
[action.payload.shortcut, ...state.shortcuts].sort( [action.payload.shortcut, ...state.shortcuts].sort(
(a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt) (a, b) => utils.getTimeStampByDate(b.createdTs) - utils.getTimeStampByDate(a.createdTs)
) )
); );

View File

@ -1,5 +1,5 @@
export interface State { export interface State {
user: Model.User | null; user: User | null;
} }
interface SignInAction { interface SignInAction {

View File

@ -1,13 +0,0 @@
declare namespace API {
interface SystemStatus {
owner: Model.User;
profile: Profile;
}
interface UserCreate {
email: string;
password: string;
name: string;
role: UserRole;
}
}

View File

@ -1,37 +0,0 @@
type UserRole = "OWNER" | "USER";
declare namespace Model {
interface BaseModel {
id: string;
createdTs: number;
updatedTs: number;
createdAt: string;
updatedAt: string;
}
interface User extends BaseModel {
role: UserRole;
email: string;
name: string;
openId: string;
}
interface Memo extends BaseModel {
content: string;
rowStatus: "NORMAL" | "ARCHIVED" | "HIDDEN";
}
interface Shortcut extends BaseModel {
title: string;
payload: string;
rowStatus: "NORMAL" | "ARCHIVED";
}
interface Resource extends BaseModel {
filename: string;
type: string;
size: string;
createdAt: string;
}
}

View File

@ -1,4 +0,0 @@
interface Profile {
mode: string;
version: string;
}

1
web/src/types/modules/common.d.ts vendored Normal file
View File

@ -0,0 +1 @@
type RowStatus = "NORMAL" | "ARCHIVED";

24
web/src/types/modules/memo.d.ts vendored Normal file
View File

@ -0,0 +1,24 @@
type MemoId = number;
interface Memo {
id: MemoId;
creatorId: UserId;
createdTs: TimeStamp;
updatedTs: TimeStamp;
rowStatus: RowStatus;
content: string;
pinned: boolean;
}
interface MemoCreate {
content: string;
createdTs?: TimeStamp;
}
interface MemoPatch {
id: MemoId;
content?: string;
rowStatus?: RowStatus;
}

12
web/src/types/modules/resource.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
type ResourceId = number;
interface Resource {
id: string;
createdTs: TimeStamp;
updatedTs: TimeStamp;
filename: string;
type: string;
size: string;
}

12
web/src/types/modules/shortcut.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
type ShortcutId = number;
interface Shortcut {
id: string;
rowStatus: RowStatus;
createdTs: TimeStamp;
updatedTs: TimeStamp;
title: string;
payload: string;
}

9
web/src/types/modules/system.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
interface Profile {
mode: string;
version: string;
}
interface SystemStatus {
owner: User;
profile: Profile;
}

28
web/src/types/modules/user.d.ts vendored Normal file
View File

@ -0,0 +1,28 @@
type UserId = number;
type UserRole = "OWNER" | "USER";
interface User {
id: UserId;
createdTs: TimeStamp;
updatedTs: TimeStamp;
rowStatus: RowStatus;
role: UserRole;
email: string;
name: string;
openId: string;
}
interface UserCreate {
email: string;
password: string;
name: string;
role: UserRole;
}
interface UserPatch {
name?: string;
password?: string;
resetOpenId?: boolean;
}

View File

@ -5,8 +5,3 @@ interface DialogProps {
interface DialogCallback { interface DialogCallback {
destroy: FunctionType; destroy: FunctionType;
} }
interface FormattedMemo extends Model.Memo {
createdAtStr: string;
deletedAtStr?: string;
}