diff --git a/store/migration/10001__schema.sql b/store/migration/10001__schema.sql index 28f04bae..cffe57d6 100644 --- a/store/migration/10001__schema.sql +++ b/store/migration/10001__schema.sql @@ -31,7 +31,7 @@ CREATE TABLE memo ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), - -- allowed row status are 'NORMAL', 'PINNED', 'HIDDEN'. + -- allowed row status are 'NORMAL', 'ARCHIVED', 'HIDDEN'. row_status TEXT NOT NULL DEFAULT 'NORMAL', content TEXT NOT NULL DEFAULT '', creator_id INTEGER NOT NULL, @@ -64,7 +64,7 @@ CREATE TABLE shortcut ( title TEXT NOT NULL DEFAULT '', payload TEXT NOT NULL DEFAULT '', creator_id INTEGER NOT NULL, - -- allowed row status are 'NORMAL', 'PINNED'. + -- allowed row status are 'NORMAL', 'ARCHIVED'. row_status TEXT NOT NULL DEFAULT 'NORMAL', FOREIGN KEY(creator_id) REFERENCES users(id) ); diff --git a/web/public/icons/add.svg b/web/public/icons/add.svg new file mode 100644 index 00000000..05a02317 --- /dev/null +++ b/web/public/icons/add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index e980a387..5966d632 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -29,6 +29,26 @@ const Memo: React.FC = (props: Props) => { showMemoCardDialog(memo); }; + const handleTogglePinMemoBtnClick = async () => { + try { + if (memo.rowStatus === "ARCHIVED") { + await memoService.unpinMemo(memo.id); + memoService.editMemo({ + ...memo, + rowStatus: "NORMAL", + }); + } else { + await memoService.pinMemo(memo.id); + memoService.editMemo({ + ...memo, + rowStatus: "ARCHIVED", + }); + } + } catch (error) { + // do nth + } + }; + const handleMarkMemoClick = () => { globalStateService.setMarkMemoId(memo.id); }; @@ -86,6 +106,9 @@ const Memo: React.FC = (props: Props) => {
{memo.createdAtStr} + + PINNED +
@@ -96,6 +119,9 @@ const Memo: React.FC = (props: Props) => { View Story + + {memo.rowStatus === "NORMAL" ? "Pin" : "Unpin"} + Mark diff --git a/web/src/components/MemoFilter.tsx b/web/src/components/MemoFilter.tsx index c1ab0084..cf02b561 100644 --- a/web/src/components/MemoFilter.tsx +++ b/web/src/components/MemoFilter.tsx @@ -50,7 +50,7 @@ const MemoFilter: React.FC = () => { locationService.setFromAndToQuery(0, 0); }} > - 🗓️ {utils.getDateString(duration.from)} 至 {utils.getDateString(duration.to)} + 🗓️ {utils.getDateString(duration.from)} to {utils.getDateString(duration.to)}
) : null}
= () => { }) : memos; + const pinnedMemos = shownMemos.filter((m) => m.rowStatus === "ARCHIVED"); + const unpinnedMemos = shownMemos.filter((m) => m.rowStatus === "NORMAL"); + const sortedMemos = pinnedMemos.concat(unpinnedMemos); + useEffect(() => { memoService .fetchAllMemos() @@ -84,7 +88,7 @@ const MemoList: React.FC = () => { memoService.updateTagsState(); }) .catch(() => { - toastHelper.error("😭 Refresh failed, please try again later."); + toastHelper.error("😭 Fetching failed, please try again later."); }); }, []); @@ -107,14 +111,14 @@ const MemoList: React.FC = () => { return (
- {shownMemos.map((memo) => ( + {sortedMemos.map((memo) => ( ))}

{isFetching ? "Fetching data..." - : shownMemos.length === 0 + : sortedMemos.length === 0 ? "Oops, there is nothing" : showMemoFilter ? "" diff --git a/web/src/components/ShortcutList.tsx b/web/src/components/ShortcutList.tsx index 43ba70ee..944ecebf 100644 --- a/web/src/components/ShortcutList.tsx +++ b/web/src/components/ShortcutList.tsx @@ -1,12 +1,12 @@ import { useContext, useEffect } from "react"; +import { locationService, shortcutService } from "../services"; import appContext from "../stores/appContext"; import useToggle from "../hooks/useToggle"; import useLoading from "../hooks/useLoading"; -import Only from "./common/OnlyWhen"; import utils from "../helpers/utils"; +import Only from "./common/OnlyWhen"; import toastHelper from "./Toast"; -import { locationService, shortcutService } from "../services"; -import showCreateQueryDialog from "./CreateShortcutDialog"; +import showCreateShortcutDialog from "./CreateShortcutDialog"; import "../less/shortcut-list.less"; interface Props {} @@ -19,9 +19,13 @@ const ShortcutList: React.FC = () => { }, } = useContext(appContext); const loadingState = useLoading(); - const sortedShortcuts = shortcuts - .sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)) - .sort((a, b) => utils.getTimeStampByDate(b.updatedAt) - utils.getTimeStampByDate(a.updatedAt)); + const pinnedShortcuts = shortcuts + .filter((s) => s.rowStatus === "ARCHIVED") + .sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)); + const unpinnedShortcuts = shortcuts + .filter((s) => s.rowStatus === "NORMAL") + .sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)); + const sortedShortcuts = pinnedShortcuts.concat(unpinnedShortcuts); useEffect(() => { shortcutService @@ -38,13 +42,13 @@ const ShortcutList: React.FC = () => {

Shortcuts - showCreateQueryDialog()}> - + + showCreateShortcutDialog()}> + add shortcut

- showCreateQueryDialog()}> + showCreateShortcutDialog()}> New shortcut
@@ -92,12 +96,12 @@ const ShortcutContainer: React.FC = (props: ShortcutCont } }; - const handleEditQueryBtnClick = (event: React.MouseEvent) => { + const handleEditShortcutBtnClick = (event: React.MouseEvent) => { event.stopPropagation(); - showCreateQueryDialog(shortcut.id); + showCreateShortcutDialog(shortcut.id); }; - const handlePinQueryBtnClick = async (event: React.MouseEvent) => { + const handlePinShortcutBtnClick = async (event: React.MouseEvent) => { event.stopPropagation(); try { @@ -136,10 +140,10 @@ const ShortcutContainer: React.FC = (props: ShortcutCont
- + {shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"} - + Edit = () => { > ); })} - {nullCell.map((v, i) => ( + {nullCell.map((_, i) => ( ))}
diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts index f9baf5cd..9c57826f 100644 --- a/web/src/helpers/api.ts +++ b/web/src/helpers/api.ts @@ -113,7 +113,7 @@ namespace api { export function getMyMemos() { return request({ method: "GET", - url: "/api/memo?rowStatus=NORMAL", + url: "/api/memo", }); } @@ -144,6 +144,26 @@ namespace api { }); } + export function pinMemo(memoId: string) { + return request({ + method: "PATCH", + url: `/api/memo/${memoId}`, + data: { + rowStatus: "ARCHIVED", + }, + }); + } + + export function unpinMemo(shortcutId: string) { + return request({ + method: "PATCH", + url: `/api/memo/${shortcutId}`, + data: { + rowStatus: "NORMAL", + }, + }); + } + export function hideMemo(memoId: string) { return request({ method: "PATCH", diff --git a/web/src/less/memo-filter.less b/web/src/less/memo-filter.less index f38223ad..d15affd3 100644 --- a/web/src/less/memo-filter.less +++ b/web/src/less/memo-filter.less @@ -2,7 +2,7 @@ .filter-query-container { .flex(row, flex-start, flex-start); - @apply w-full flex-wrap p-2 pb-1 text-sm leading-7; + @apply w-full flex-wrap p-2 pb-1 text-sm font-mono leading-7; > .tip-text { @apply mr-2; @@ -10,10 +10,6 @@ > .filter-item-container { @apply px-2 mr-2 cursor-pointer bg-gray-200 rounded whitespace-nowrap truncate hover:line-through; - max-width: 200px; - - > .icon-text { - letter-spacing: 2px; - } + max-width: 256px; } } diff --git a/web/src/less/memo-list.less b/web/src/less/memo-list.less index ad331caa..1172f0e4 100644 --- a/web/src/less/memo-list.less +++ b/web/src/less/memo-list.less @@ -28,6 +28,6 @@ } &.completed { - @apply pb-28; + @apply pb-40; } } diff --git a/web/src/less/memo.less b/web/src/less/memo.less index 77a6e7bb..b1e9a9e5 100644 --- a/web/src/less/memo.less +++ b/web/src/less/memo.less @@ -3,7 +3,7 @@ .memo-wrapper { .flex(column, flex-start, flex-start); - @apply w-full max-w-full p-4 px-6 mt-2 first:mt-2 bg-white rounded-lg border border-transparent hover:border-gray-200; + @apply w-full max-w-full p-4 pb-3 mt-2 bg-white rounded-lg border border-transparent hover:border-gray-200; &.deleted-memo { @apply border-gray-200; @@ -11,7 +11,7 @@ > .memo-top-wrapper { .flex(row, space-between, center); - @apply w-full h-6 mb-1; + @apply w-full h-6 mb-2; > .time-text { @apply text-xs text-gray-400 cursor-pointer; diff --git a/web/src/less/shortcut-list.less b/web/src/less/shortcut-list.less index d2e55fbd..cd4b8952 100644 --- a/web/src/less/shortcut-list.less +++ b/web/src/less/shortcut-list.less @@ -6,28 +6,26 @@ .hide-scroll-bar(); > .title-text { - .flex(row, space-between, center); + .flex(row, flex-start, center); @apply w-full px-4; > .normal-text { - @apply text-xs leading-6 font-bold text-black opacity-50; + @apply text-xs leading-6 font-mono text-gray-400; } > .btn { - @apply hidden px-1 text-lg leading-6; - } + .flex(column, center, center); + @apply w-5 h-5 bg-gray-200 rounded ml-2 hover:opacity-80; - &:hover, - &:active { - > .btn { - @apply block; + > img { + @apply w-4 h-4 opacity-80; } } } > .create-shortcut-btn-container { - .flex(row, center, center); - @apply w-full mt-4 mb-2; + .flex(row, flex-start, center); + @apply w-full mt-4 mb-2 ml-4; > .btn { @apply flex p-2 px-4 rounded-lg text-sm border border-dashed border-blue-600; diff --git a/web/src/services/locationService.ts b/web/src/services/locationService.ts index d8867e88..0138584d 100644 --- a/web/src/services/locationService.ts +++ b/web/src/services/locationService.ts @@ -42,7 +42,7 @@ class LocationService { state.query.tag = urlParams.get("tag") ?? ""; state.query.type = (urlParams.get("type") ?? "") as MemoSpecType; state.query.text = urlParams.get("text") ?? ""; - state.query.shortcutId = urlParams.get("filter") ?? ""; + state.query.shortcutId = urlParams.get("shortcutId") ?? ""; const from = parseInt(urlParams.get("from") ?? "0"); const to = parseInt(urlParams.get("to") ?? "0"); if (to > from && to !== 0) { diff --git a/web/src/services/memoService.ts b/web/src/services/memoService.ts index b0e73ab8..89ac6917 100644 --- a/web/src/services/memoService.ts +++ b/web/src/services/memoService.ts @@ -17,7 +17,7 @@ class MemoService { } const data = await api.getMyMemos(); - const memos: Model.Memo[] = data.map((m) => this.convertResponseModelMemo(m)); + const memos: Model.Memo[] = data.filter((m) => m.rowStatus !== "HIDDEN").map((m) => this.convertResponseModelMemo(m)); appStore.dispatch({ type: "SET_MEMOS", payload: { @@ -133,6 +133,14 @@ class MemoService { return this.convertResponseModelMemo(memo); } + public async pinMemo(memoId: string) { + await api.pinMemo(memoId); + } + + public async unpinMemo(memoId: string) { + await api.unpinMemo(memoId); + } + private convertResponseModelMemo(memo: Model.Memo): Model.Memo { return { ...memo, diff --git a/web/src/types/models.d.ts b/web/src/types/models.d.ts index 0113ecc5..38a3aadf 100644 --- a/web/src/types/models.d.ts +++ b/web/src/types/models.d.ts @@ -15,7 +15,7 @@ declare namespace Model { interface Memo extends BaseModel { content: string; - rowStatus: "NORMAL" | "HIDDEN"; + rowStatus: "NORMAL" | "ARCHIVED" | "HIDDEN"; } interface Shortcut extends BaseModel {