diff --git a/web/.eslintrc.json b/web/.eslintrc.json index 06133427..b083b79c 100644 --- a/web/.eslintrc.json +++ b/web/.eslintrc.json @@ -23,7 +23,6 @@ ], "@typescript-eslint/no-empty-interface": ["off"], "@typescript-eslint/no-explicit-any": ["off"], - "react/react-in-jsx-scope": "off", - "@typescript-eslint/no-namespace": "off" + "react/react-in-jsx-scope": "off" } } diff --git a/web/package.json b/web/package.json index 2ceebc09..9bc6e9f1 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "memos", - "version": "2.0.6", + "version": "0.0.1", "scripts": { "dev": "vite", "build": "tsc && vite build", @@ -9,6 +9,7 @@ }, "dependencies": { "@reduxjs/toolkit": "^1.8.1", + "axios": "^0.27.2", "lodash-es": "^4.17.21", "react": "^18.1.0", "react-dom": "^18.1.0", diff --git a/web/src/components/AboutSiteDialog.tsx b/web/src/components/AboutSiteDialog.tsx index 79caff95..b36478d9 100644 --- a/web/src/components/AboutSiteDialog.tsx +++ b/web/src/components/AboutSiteDialog.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import api from "../helpers/api"; +import * as api from "../helpers/api"; import Only from "./common/OnlyWhen"; import { showDialog } from "./Dialog"; import "../less/about-site-dialog.less"; @@ -11,7 +11,10 @@ const AboutSiteDialog: React.FC = ({ destroy }: Props) => { useEffect(() => { try { - api.getSystemStatus().then(({ profile }) => { + api.getSystemStatus().then(({ data }) => { + const { + data: { profile }, + } = data; setProfile(profile); }); } catch (error) { diff --git a/web/src/components/DailyMemo.tsx b/web/src/components/DailyMemo.tsx index a412a19f..67833911 100644 --- a/web/src/components/DailyMemo.tsx +++ b/web/src/components/DailyMemo.tsx @@ -1,5 +1,5 @@ import { IMAGE_URL_REG } from "../helpers/consts"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import { formatMemoContent } from "./Memo"; import Only from "./common/OnlyWhen"; import "../less/daily-memo.less"; diff --git a/web/src/components/DailyMemoDiaryDialog.tsx b/web/src/components/DailyMemoDiaryDialog.tsx index 3c4d533b..24f52f23 100644 --- a/web/src/components/DailyMemoDiaryDialog.tsx +++ b/web/src/components/DailyMemoDiaryDialog.tsx @@ -4,7 +4,7 @@ import toImage from "../labs/html2image"; import useToggle from "../hooks/useToggle"; import useLoading from "../hooks/useLoading"; import { DAILY_TIMESTAMP } from "../helpers/consts"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import { showDialog } from "./Dialog"; import showPreviewImageDialog from "./PreviewImageDialog"; import DailyMemo from "./DailyMemo"; diff --git a/web/src/components/DeletedMemo.tsx b/web/src/components/DeletedMemo.tsx index 9877d5f7..b4f1c510 100644 --- a/web/src/components/DeletedMemo.tsx +++ b/web/src/components/DeletedMemo.tsx @@ -1,5 +1,5 @@ import { IMAGE_URL_REG } from "../helpers/consts"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import useToggle from "../hooks/useToggle"; import { memoService } from "../services"; import Only from "./common/OnlyWhen"; diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index 41b1348e..500ef266 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -2,7 +2,7 @@ import { memo } from "react"; import { escape } from "lodash-es"; import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG, UNKNOWN_ID } from "../helpers/consts"; import { parseMarkedToHtml, parseRawTextToHtml } from "../helpers/marked"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import useToggle from "../hooks/useToggle"; import { editorStateService, memoService } from "../services"; import Only from "./common/OnlyWhen"; diff --git a/web/src/components/MemoCardDialog.tsx b/web/src/components/MemoCardDialog.tsx index 5293e0eb..de035c20 100644 --- a/web/src/components/MemoCardDialog.tsx +++ b/web/src/components/MemoCardDialog.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; import { IMAGE_URL_REG, MEMO_LINK_REG, UNKNOWN_ID } from "../helpers/consts"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import { editorStateService, memoService } from "../services"; import { parseHtmlToRawText } from "../helpers/marked"; import { formatMemoContent } from "./Memo"; diff --git a/web/src/components/MemoEditor.tsx b/web/src/components/MemoEditor.tsx index 463e0f9a..6e36fcd0 100644 --- a/web/src/components/MemoEditor.tsx +++ b/web/src/components/MemoEditor.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from "react"; import { editorStateService, locationService, memoService, resourceService } from "../services"; import { useAppSelector } from "../store"; import { UNKNOWN_ID } from "../helpers/consts"; -import { storage } from "../helpers/storage"; +import * as storage from "../helpers/storage"; import useToggle from "../hooks/useToggle"; import toastHelper from "./Toast"; import Editor, { EditorRefActions } from "./Editor/Editor"; diff --git a/web/src/components/MemoFilter.tsx b/web/src/components/MemoFilter.tsx index f9d765b1..ec33b800 100644 --- a/web/src/components/MemoFilter.tsx +++ b/web/src/components/MemoFilter.tsx @@ -1,6 +1,6 @@ import { useAppSelector } from "../store"; import { locationService, shortcutService } from "../services"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import { getTextWithMemoType } from "../helpers/filter"; import "../less/memo-filter.less"; diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx index bddcc1ed..70c61692 100644 --- a/web/src/components/MemoList.tsx +++ b/web/src/components/MemoList.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { locationService, memoService, shortcutService } from "../services"; import { useAppSelector } from "../store"; import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import { checkShouldShowMemoWithFilters } from "../helpers/filter"; import Memo from "./Memo"; import toastHelper from "./Toast"; @@ -79,6 +79,7 @@ const MemoList: React.FC = () => { const pinnedMemos = shownMemos.filter((m) => m.pinned); const unpinnedMemos = shownMemos.filter((m) => !m.pinned); const sortedMemos = pinnedMemos.concat(unpinnedMemos).filter((m) => m.rowStatus === "NORMAL"); + console.log(memos.length, sortedMemos.length); useEffect(() => { memoService diff --git a/web/src/components/MemoTrashDialog.tsx b/web/src/components/MemoTrashDialog.tsx index dab6754f..23f2dc7d 100644 --- a/web/src/components/MemoTrashDialog.tsx +++ b/web/src/components/MemoTrashDialog.tsx @@ -14,7 +14,6 @@ const MemoTrashDialog: React.FC = (props: Props) => { const [deletedMemos, setDeletedMemos] = useState([]); useEffect(() => { - memoService.fetchAllMemos(); memoService .fetchDeletedMemos() .then((result) => { diff --git a/web/src/components/PreviewImageDialog.tsx b/web/src/components/PreviewImageDialog.tsx index 23f02cb7..3d76fdca 100644 --- a/web/src/components/PreviewImageDialog.tsx +++ b/web/src/components/PreviewImageDialog.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import { showDialog } from "./Dialog"; import "../less/preview-image-dialog.less"; diff --git a/web/src/components/Settings/MemberSection.tsx b/web/src/components/Settings/MemberSection.tsx index cdb2ff9f..a92b8931 100644 --- a/web/src/components/Settings/MemberSection.tsx +++ b/web/src/components/Settings/MemberSection.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { isEmpty } from "lodash-es"; -import api from "../../helpers/api"; +import * as api from "../../helpers/api"; import toastHelper from "../Toast"; import "../../less/settings/member-section.less"; @@ -23,7 +23,7 @@ const PreferencesSection: React.FC = () => { }, []); const fetchUserList = async () => { - const data = await api.getUserList(); + const { data } = (await api.getUserList()).data; setUserList(data); }; diff --git a/web/src/components/Settings/PreferencesSection.tsx b/web/src/components/Settings/PreferencesSection.tsx index 93903f25..4aaff607 100644 --- a/web/src/components/Settings/PreferencesSection.tsx +++ b/web/src/components/Settings/PreferencesSection.tsx @@ -1,5 +1,5 @@ import { memoService } from "../../services"; -import utils from "../../helpers/utils"; +import * as utils from "../../helpers/utils"; import toastHelper from "../Toast"; import "../../less/settings/preferences-section.less"; diff --git a/web/src/components/ShareMemoImageDialog.tsx b/web/src/components/ShareMemoImageDialog.tsx index d237a5d0..2418ed93 100644 --- a/web/src/components/ShareMemoImageDialog.tsx +++ b/web/src/components/ShareMemoImageDialog.tsx @@ -2,7 +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 utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import { showDialog } from "./Dialog"; import { formatMemoContent } from "./Memo"; import Only from "./common/OnlyWhen"; diff --git a/web/src/components/ShortcutList.tsx b/web/src/components/ShortcutList.tsx index 94cc8419..3fbbd486 100644 --- a/web/src/components/ShortcutList.tsx +++ b/web/src/components/ShortcutList.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { locationService, shortcutService } from "../services"; import { useAppSelector } from "../store"; import { UNKNOWN_ID } from "../helpers/consts"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import useToggle from "../hooks/useToggle"; import useLoading from "../hooks/useLoading"; import toastHelper from "./Toast"; diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index 6a32502c..b60a4bd4 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -1,5 +1,5 @@ import { useAppSelector } from "../store"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import showDailyMemoDiaryDialog from "./DailyMemoDiaryDialog"; import showSettingDialog from "./SettingDialog"; import showMemoTrashDialog from "./MemoTrashDialog"; diff --git a/web/src/components/TagList.tsx b/web/src/components/TagList.tsx index 9ab38ede..97b0be6f 100644 --- a/web/src/components/TagList.tsx +++ b/web/src/components/TagList.tsx @@ -3,7 +3,7 @@ import { useAppSelector } from "../store"; import { locationService, memoService } from "../services"; import useToggle from "../hooks/useToggle"; import Only from "./common/OnlyWhen"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import "../less/tag-list.less"; interface Tag { diff --git a/web/src/components/UsageHeatMap.tsx b/web/src/components/UsageHeatMap.tsx index ced02b0f..7ba67989 100644 --- a/web/src/components/UsageHeatMap.tsx +++ b/web/src/components/UsageHeatMap.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useAppSelector } from "../store"; import { locationService } from "../services"; import { DAILY_TIMESTAMP } from "../helpers/consts"; -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import "../less/usage-heat-map.less"; const tableConfig = { diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts index 082f6c85..4d033816 100644 --- a/web/src/helpers/api.ts +++ b/web/src/helpers/api.ts @@ -1,209 +1,99 @@ +import axios from "axios"; + type ResponseObject = { data: T; error?: string; message?: string; }; -type RequestConfig = { - method: string; - url: string; - data?: any; - dataType?: "json" | "file"; -}; - -async function request(config: RequestConfig): Promise { - const { method, url, data, dataType } = config; - const requestConfig: RequestInit = { - method, - }; - - if (data !== undefined) { - if (dataType === "file") { - requestConfig.body = data; - } else { - requestConfig.headers = { - "Content-Type": "application/json", - }; - requestConfig.body = JSON.stringify(data); - } - } - - const response = await fetch(url, requestConfig); - const responseData = (await response.json()) as ResponseObject; - - if (responseData.error || responseData.message) { - throw new Error(responseData.error || responseData.message); - } - - return responseData.data; +export function getSystemStatus() { + return axios.get>("/api/status"); } -namespace api { - export function getSystemStatus() { - return request({ - method: "GET", - url: "/api/status", - }); - } - - export function login(email: string, password: string) { - return request({ - method: "POST", - url: "/api/auth/login", - data: { - email, - password, - }, - }); - } - - export function signup(email: string, password: string, role: UserRole) { - return request({ - method: "POST", - url: "/api/auth/signup", - data: { - email, - password, - role, - name: email, - }, - }); - } - - export function signout() { - return request({ - method: "POST", - url: "/api/auth/logout", - }); - } - - export function createUser(userCreate: UserCreate) { - return request({ - method: "POST", - url: "/api/user", - data: userCreate, - }); - } - - export function getUser() { - return request({ - method: "GET", - url: "/api/user/me", - }); - } - - export function getUserList() { - return request({ - method: "GET", - url: "/api/user", - }); - } - - export function patchUser(userPatch: UserPatch) { - return request({ - method: "PATCH", - url: "/api/user/me", - data: userPatch, - }); - } - - export function getMyMemos() { - return request({ - method: "GET", - url: "/api/memo", - }); - } - - export function getMyArchivedMemos() { - return request({ - method: "GET", - url: "/api/memo?rowStatus=ARCHIVED", - }); - } - - export function createMemo(memoCreate: MemoCreate) { - return request({ - method: "POST", - url: "/api/memo", - data: memoCreate, - }); - } - - export function patchMemo(memoPatch: MemoPatch) { - return request({ - method: "PATCH", - url: `/api/memo/${memoPatch.id}`, - data: { - memoPatch, - }, - }); - } - - 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 deleteMemo(memoId: MemoId) { - return request({ - method: "DELETE", - url: `/api/memo/${memoId}`, - }); - } - - export function getMyShortcuts() { - return request({ - method: "GET", - url: "/api/shortcut", - }); - } - - export function createShortcut(shortcutCreate: ShortcutCreate) { - return request({ - method: "POST", - url: "/api/shortcut", - data: shortcutCreate, - }); - } - - export function patchShortcut(shortcutPatch: ShortcutPatch) { - return request({ - method: "PATCH", - url: `/api/shortcut/${shortcutPatch.id}`, - data: shortcutPatch, - }); - } - - export function deleteShortcutById(shortcutId: ShortcutId) { - return request({ - method: "DELETE", - url: `/api/shortcut/${shortcutId}`, - }); - } - - export function uploadFile(formData: FormData) { - return request({ - method: "POST", - url: "/api/resource", - data: formData, - dataType: "file", - }); - } +export function login(email: string, password: string) { + return axios.post>("/api/auth/login", { + email, + password, + }); } -export default api; +export function signup(email: string, password: string, role: UserRole) { + return axios.post>("/api/auth/signup", { + email, + password, + role, + name: email, + }); +} + +export function signout() { + return axios.post("/api/auth/logout"); +} + +export function createUser(userCreate: UserCreate) { + return axios.post>("/api/user", userCreate); +} + +export function getUser() { + return axios.get>("/api/user/me"); +} + +export function getUserList() { + return axios.get>("/api/user"); +} + +export function patchUser(userPatch: UserPatch) { + return axios.patch>("/api/user/me", userPatch); +} + +export function getMyMemos() { + return axios.get>("/api/memo"); +} + +export function getMyArchivedMemos() { + return axios.get>("/api/memo?rowStatus=ARCHIVED"); +} + +export function createMemo(memoCreate: MemoCreate) { + return axios.post>("/api/memo", memoCreate); +} + +export function patchMemo(memoPatch: MemoPatch) { + return axios.patch>(`/api/memo/${memoPatch.id}`, memoPatch); +} + +export function pinMemo(memoId: MemoId) { + return axios.post(`/api/memo/${memoId}/organizer`, { + pinned: true, + }); +} + +export function unpinMemo(memoId: MemoId) { + return axios.post(`/api/memo/${memoId}/organizer`, { + pinned: false, + }); +} + +export function deleteMemo(memoId: MemoId) { + return axios.delete(`/api/memo/${memoId}`); +} + +export function getMyShortcuts() { + return axios.get>("/api/shortcut"); +} + +export function createShortcut(shortcutCreate: ShortcutCreate) { + return axios.post>("/api/shortcut", shortcutCreate); +} + +export function patchShortcut(shortcutPatch: ShortcutPatch) { + return axios.patch>(`/api/shortcut/${shortcutPatch.id}`, shortcutPatch); +} + +export function deleteShortcutById(shortcutId: ShortcutId) { + return axios.delete(`/api/shortcut/${shortcutId}`); +} + +export function uploadFile(formData: FormData) { + return axios.post>("/api/resource", formData); +} diff --git a/web/src/helpers/storage.ts b/web/src/helpers/storage.ts index fd5afef3..391539aa 100644 --- a/web/src/helpers/storage.ts +++ b/web/src/helpers/storage.ts @@ -14,52 +14,50 @@ type StorageKey = keyof StorageData; /** * storage helper */ -export namespace storage { - export function get(keys: StorageKey[]): Partial { - const data: Partial = {}; +export function get(keys: StorageKey[]): Partial { + const data: Partial = {}; - for (const key of keys) { - try { - const stringifyValue = localStorage.getItem(key); - if (stringifyValue !== null) { - const val = JSON.parse(stringifyValue); - data[key] = val; - } - } catch (error: any) { - console.error("Get storage failed in ", key, error); - } - } - - return data; - } - - export function set(data: Partial) { - for (const key in data) { - try { - const stringifyValue = JSON.stringify(data[key as StorageKey]); - localStorage.setItem(key, stringifyValue); - } catch (error: any) { - console.error("Save storage failed in ", key, error); + for (const key of keys) { + try { + const stringifyValue = localStorage.getItem(key); + if (stringifyValue !== null) { + const val = JSON.parse(stringifyValue); + data[key] = val; } + } catch (error: any) { + console.error("Get storage failed in ", key, error); } } - export function remove(keys: StorageKey[]) { - for (const key of keys) { - try { - localStorage.removeItem(key); - } catch (error: any) { - console.error("Remove storage failed in ", key, error); - } + return data; +} + +export function set(data: Partial) { + for (const key in data) { + try { + const stringifyValue = JSON.stringify(data[key as StorageKey]); + localStorage.setItem(key, stringifyValue); + } catch (error: any) { + console.error("Save storage failed in ", key, error); } } - - export function emitStorageChangedEvent() { - const iframeEl = document.createElement("iframe"); - iframeEl.style.display = "none"; - document.body.appendChild(iframeEl); - - iframeEl.contentWindow?.localStorage.setItem("t", Date.now().toString()); - iframeEl.remove(); - } +} + +export function remove(keys: StorageKey[]) { + for (const key of keys) { + try { + localStorage.removeItem(key); + } catch (error: any) { + console.error("Remove storage failed in ", key, error); + } + } +} + +export function emitStorageChangedEvent() { + const iframeEl = document.createElement("iframe"); + iframeEl.style.display = "none"; + document.body.appendChild(iframeEl); + + iframeEl.contentWindow?.localStorage.setItem("t", Date.now().toString()); + iframeEl.remove(); } diff --git a/web/src/helpers/utils.ts b/web/src/helpers/utils.ts index 35061057..94f3fd9c 100644 --- a/web/src/helpers/utils.ts +++ b/web/src/helpers/utils.ts @@ -1,223 +1,219 @@ -namespace utils { - export function getNowTimeStamp(): number { - return Date.now(); +export function getNowTimeStamp(): number { + return Date.now(); +} + +export function getOSVersion(): "Windows" | "MacOS" | "Linux" | "Unknown" { + const appVersion = navigator.userAgent; + let detectedOS: "Windows" | "MacOS" | "Linux" | "Unknown" = "Unknown"; + + if (appVersion.indexOf("Win") != -1) { + detectedOS = "Windows"; + } else if (appVersion.indexOf("Mac") != -1) { + detectedOS = "MacOS"; + } else if (appVersion.indexOf("Linux") != -1) { + detectedOS = "Linux"; } - export function getOSVersion(): "Windows" | "MacOS" | "Linux" | "Unknown" { - const appVersion = navigator.userAgent; - let detectedOS: "Windows" | "MacOS" | "Linux" | "Unknown" = "Unknown"; + return detectedOS; +} - if (appVersion.indexOf("Win") != -1) { - detectedOS = "Windows"; - } else if (appVersion.indexOf("Mac") != -1) { - detectedOS = "MacOS"; - } else if (appVersion.indexOf("Linux") != -1) { - detectedOS = "Linux"; +export function getTimeStampByDate(t: Date | number | string): number { + if (typeof t === "string") { + t = t.replaceAll("-", "/"); + } + const d = new Date(t); + + return d.getTime(); +} + +export function getDateStampByDate(t: Date | number | string): number { + const d = new Date(getTimeStampByDate(t)); + + return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); +} + +export function getDateString(t: Date | number | string): string { + const d = new Date(getTimeStampByDate(t)); + + const year = d.getFullYear(); + const month = d.getMonth() + 1; + const date = d.getDate(); + + return `${year}/${month}/${date}`; +} + +export function getDataStringWithTs(ts: number): string { + return getDateTimeString(ts * 1000); +} + +export function getTimeString(t: Date | number | string): string { + const d = new Date(getTimeStampByDate(t)); + + const hours = d.getHours(); + const mins = d.getMinutes(); + + const hoursStr = hours < 10 ? "0" + hours : hours; + const minsStr = mins < 10 ? "0" + mins : mins; + + return `${hoursStr}:${minsStr}`; +} + +// For example: 2021-4-8 17:52:17 +export function getDateTimeString(t: Date | number | string): string { + const d = new Date(getTimeStampByDate(t)); + + const year = d.getFullYear(); + const month = d.getMonth() + 1; + const date = d.getDate(); + const hours = d.getHours(); + const mins = d.getMinutes(); + const secs = d.getSeconds(); + + const monthStr = month < 10 ? "0" + month : month; + const dateStr = date < 10 ? "0" + date : date; + const hoursStr = hours < 10 ? "0" + hours : hours; + const minsStr = mins < 10 ? "0" + mins : mins; + const secsStr = secs < 10 ? "0" + secs : secs; + + return `${year}/${monthStr}/${dateStr} ${hoursStr}:${minsStr}:${secsStr}`; +} + +export function dedupe(data: T[]): T[] { + return Array.from(new Set(data)); +} + +export function dedupeObjectWithId(data: T[]): T[] { + const idSet = new Set(); + const result = []; + + for (const d of data) { + if (!idSet.has(d.id)) { + idSet.add(d.id); + result.push(d); } - - return detectedOS; } - export function getTimeStampByDate(t: Date | number | string): number { - if (typeof t === "string") { - t = t.replaceAll("-", "/"); + return result; +} + +export function debounce(fn: FunctionType, delay: number) { + let timer: number | null = null; + + return () => { + if (timer) { + clearTimeout(timer); + timer = setTimeout(fn, delay); + } else { + timer = setTimeout(fn, delay); } - const d = new Date(t); + }; +} - return d.getTime(); - } +export function throttle(fn: FunctionType, delay: number) { + let valid = true; - export function getDateStampByDate(t: Date | number | string): number { - const d = new Date(getTimeStampByDate(t)); - - return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); - } - - export function getDateString(t: Date | number | string): string { - const d = new Date(getTimeStampByDate(t)); - - const year = d.getFullYear(); - const month = d.getMonth() + 1; - const date = d.getDate(); - - return `${year}/${month}/${date}`; - } - - export function getDataStringWithTs(ts: number): string { - return getDateTimeString(ts * 1000); - } - - export function getTimeString(t: Date | number | string): string { - const d = new Date(getTimeStampByDate(t)); - - const hours = d.getHours(); - const mins = d.getMinutes(); - - const hoursStr = hours < 10 ? "0" + hours : hours; - const minsStr = mins < 10 ? "0" + mins : mins; - - return `${hoursStr}:${minsStr}`; - } - - // For example: 2021-4-8 17:52:17 - export function getDateTimeString(t: Date | number | string): string { - const d = new Date(getTimeStampByDate(t)); - - const year = d.getFullYear(); - const month = d.getMonth() + 1; - const date = d.getDate(); - const hours = d.getHours(); - const mins = d.getMinutes(); - const secs = d.getSeconds(); - - const monthStr = month < 10 ? "0" + month : month; - const dateStr = date < 10 ? "0" + date : date; - const hoursStr = hours < 10 ? "0" + hours : hours; - const minsStr = mins < 10 ? "0" + mins : mins; - const secsStr = secs < 10 ? "0" + secs : secs; - - return `${year}/${monthStr}/${dateStr} ${hoursStr}:${minsStr}:${secsStr}`; - } - - export function dedupe(data: T[]): T[] { - return Array.from(new Set(data)); - } - - export function dedupeObjectWithId(data: T[]): T[] { - const idSet = new Set(); - const result = []; - - for (const d of data) { - if (!idSet.has(d.id)) { - idSet.add(d.id); - result.push(d); - } + return () => { + if (!valid) { + return false; } + valid = false; + setTimeout(() => { + fn(); + valid = true; + }, delay); + }; +} - return result; - } +export function transformObjectToParamsString(object: KVObject): string { + const params = []; + const keys = Object.keys(object).sort(); - export function debounce(fn: FunctionType, delay: number) { - let timer: number | null = null; - - return () => { - if (timer) { - clearTimeout(timer); - timer = setTimeout(fn, delay); - } else { - timer = setTimeout(fn, delay); - } - }; - } - - export function throttle(fn: FunctionType, delay: number) { - let valid = true; - - return () => { - if (!valid) { - return false; - } - valid = false; - setTimeout(() => { - fn(); - valid = true; - }, delay); - }; - } - - export function transformObjectToParamsString(object: KVObject): string { - const params = []; - const keys = Object.keys(object).sort(); - - for (const key of keys) { - const val = object[key]; - if (val) { - if (typeof val === "object") { - params.push(...transformObjectToParamsString(val).split("&")); - } else { - params.push(`${key}=${val}`); - } - } - } - - return params.join("&"); - } - - export function transformParamsStringToObject(paramsString: string): KVObject { - const object: KVObject = {}; - const params = paramsString.split("&"); - - for (const p of params) { - const [key, val] = p.split("="); - if (key && val) { - object[key] = val; - } - } - - return object; - } - - export function filterObjectNullKeys(object: KVObject): KVObject { - if (!object) { - return {}; - } - - const finalObject: KVObject = {}; - const keys = Object.keys(object).sort(); - - for (const key of keys) { - const val = object[key]; + for (const key of keys) { + const val = object[key]; + if (val) { if (typeof val === "object") { - const temp = filterObjectNullKeys(JSON.parse(JSON.stringify(val))); - if (temp && Object.keys(temp).length > 0) { - finalObject[key] = temp; - } + params.push(...transformObjectToParamsString(val).split("&")); } else { - if (val) { - finalObject[key] = val; - } + params.push(`${key}=${val}`); } } - - return finalObject; } - export async function copyTextToClipboard(text: string) { - if (navigator.clipboard && navigator.clipboard.writeText) { - try { - await navigator.clipboard.writeText(text); - } catch (error: unknown) { - console.warn("Copy to clipboard failed.", error); + return params.join("&"); +} + +export function transformParamsStringToObject(paramsString: string): KVObject { + const object: KVObject = {}; + const params = paramsString.split("&"); + + for (const p of params) { + const [key, val] = p.split("="); + if (key && val) { + object[key] = val; + } + } + + return object; +} + +export function filterObjectNullKeys(object: KVObject): KVObject { + if (!object) { + return {}; + } + + const finalObject: KVObject = {}; + const keys = Object.keys(object).sort(); + + for (const key of keys) { + const val = object[key]; + if (typeof val === "object") { + const temp = filterObjectNullKeys(JSON.parse(JSON.stringify(val))); + if (temp && Object.keys(temp).length > 0) { + finalObject[key] = temp; } } else { - console.warn("Copy to clipboard failed, methods not supports."); + if (val) { + finalObject[key] = val; + } } } - export function getImageSize(src: string): Promise<{ width: number; height: number }> { - return new Promise((resolve) => { - const imgEl = new Image(); + return finalObject; +} - imgEl.onload = () => { - const { width, height } = imgEl; - - if (width > 0 && height > 0) { - resolve({ width, height }); - } else { - resolve({ width: 0, height: 0 }); - } - }; - - imgEl.onerror = () => { - resolve({ width: 0, height: 0 }); - }; - - imgEl.className = "hidden"; - imgEl.src = src; - document.body.appendChild(imgEl); - imgEl.remove(); - }); +export async function copyTextToClipboard(text: string) { + if (navigator.clipboard && navigator.clipboard.writeText) { + try { + await navigator.clipboard.writeText(text); + } catch (error: unknown) { + console.warn("Copy to clipboard failed.", error); + } + } else { + console.warn("Copy to clipboard failed, methods not supports."); } } -export default utils; +export function getImageSize(src: string): Promise<{ width: number; height: number }> { + return new Promise((resolve) => { + const imgEl = new Image(); + + imgEl.onload = () => { + const { width, height } = imgEl; + + if (width > 0 && height > 0) { + resolve({ width, height }); + } else { + resolve({ width: 0, height: 0 }); + } + }; + + imgEl.onerror = () => { + resolve({ width: 0, height: 0 }); + }; + + imgEl.className = "hidden"; + imgEl.src = src; + document.body.appendChild(imgEl); + imgEl.remove(); + }); +} diff --git a/web/src/pages/Signin.tsx b/web/src/pages/Signin.tsx index 660cc265..94a71656 100644 --- a/web/src/pages/Signin.tsx +++ b/web/src/pages/Signin.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import api from "../helpers/api"; +import * as api from "../helpers/api"; import { validate, ValidatorConfig } from "../helpers/validator"; import useLoading from "../hooks/useLoading"; import { locationService, userService } from "../services"; @@ -23,7 +23,8 @@ const Signin: React.FC = () => { const actionBtnLoadingState = useLoading(false); useEffect(() => { - api.getSystemStatus().then((status) => { + api.getSystemStatus().then(({ data }) => { + const { data: status } = data; setSiteOwner(status.owner); if (status.profile.mode === "dev") { setEmail("demo@usememos.com"); diff --git a/web/src/services/locationService.ts b/web/src/services/locationService.ts index 882b2e05..8cf80e84 100644 --- a/web/src/services/locationService.ts +++ b/web/src/services/locationService.ts @@ -1,4 +1,4 @@ -import utils from "../helpers/utils"; +import * as utils from "../helpers/utils"; import store from "../store"; import { setQuery, setPathname, Query } from "../store/modules/location"; diff --git a/web/src/services/memoService.ts b/web/src/services/memoService.ts index 73729d06..55ab99db 100644 --- a/web/src/services/memoService.ts +++ b/web/src/services/memoService.ts @@ -1,4 +1,4 @@ -import api from "../helpers/api"; +import * as api from "../helpers/api"; import { TAG_REG } from "../helpers/consts"; import { createMemo, patchMemo, setMemos, setTags } from "../store/modules/memo"; import store from "../store"; @@ -17,7 +17,7 @@ const memoService = { }, fetchAllMemos: async () => { - const data = await api.getMyMemos(); + const { data } = (await api.getMyMemos()).data; const memos = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => convertResponseModelMemo(m)); store.dispatch(setMemos(memos)); @@ -25,7 +25,7 @@ const memoService = { }, fetchDeletedMemos: async () => { - const data = await api.getMyArchivedMemos(); + const { data } = (await api.getMyArchivedMemos()).data; const deletedMemos = data.map((m) => { return convertResponseModelMemo(m); }); @@ -60,13 +60,13 @@ const memoService = { }, createMemo: async (memoCreate: MemoCreate) => { - const data = await api.createMemo(memoCreate); + const { data } = (await api.createMemo(memoCreate)).data; const memo = convertResponseModelMemo(data); store.dispatch(createMemo(memo)); }, patchMemo: async (memoPatch: MemoPatch): Promise => { - const data = await api.patchMemo(memoPatch); + const { data } = (await api.patchMemo(memoPatch)).data; const memo = convertResponseModelMemo(data); store.dispatch(patchMemo(memo)); return memo; diff --git a/web/src/services/resourceService.ts b/web/src/services/resourceService.ts index 96ae99ae..3a1a529c 100644 --- a/web/src/services/resourceService.ts +++ b/web/src/services/resourceService.ts @@ -1,4 +1,4 @@ -import api from "../helpers/api"; +import * as api from "../helpers/api"; const resourceService = { /** @@ -6,7 +6,7 @@ const resourceService = { * @param file file * @returns resource: id, filename */ - async upload(file: File) { + async upload(file: File): Promise { const { name: filename, size } = file; if (size > 64 << 20) { @@ -15,7 +15,7 @@ const resourceService = { const formData = new FormData(); formData.append("file", file, filename); - const data = await api.uploadFile(formData); + const { data } = (await api.uploadFile(formData)).data; return data; }, diff --git a/web/src/services/shortcutService.ts b/web/src/services/shortcutService.ts index 26a87e46..3d4f51e7 100644 --- a/web/src/services/shortcutService.ts +++ b/web/src/services/shortcutService.ts @@ -1,4 +1,4 @@ -import api from "../helpers/api"; +import * as api from "../helpers/api"; import store from "../store/"; import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../store/modules/shortcut"; @@ -16,8 +16,8 @@ const shortcutService = { }, getMyAllShortcuts: async () => { - const rawData = await api.getMyShortcuts(); - const shortcuts = rawData.map((s) => convertResponseModelShortcut(s)); + const { data } = (await api.getMyShortcuts()).data; + const shortcuts = data.map((s) => convertResponseModelShortcut(s)); store.dispatch(setShortcuts(shortcuts)); }, @@ -32,13 +32,13 @@ const shortcutService = { }, createShortcut: async (shortcutCreate: ShortcutCreate) => { - const data = await api.createShortcut(shortcutCreate); + const { data } = (await api.createShortcut(shortcutCreate)).data; const shortcut = convertResponseModelShortcut(data); store.dispatch(createShortcut(shortcut)); }, patchShortcut: async (shortcutPatch: ShortcutPatch) => { - const data = await api.patchShortcut(shortcutPatch); + const { data } = (await api.patchShortcut(shortcutPatch)).data; const shortcut = convertResponseModelShortcut(data); store.dispatch(patchShortcut(shortcut)); }, diff --git a/web/src/services/userService.ts b/web/src/services/userService.ts index acfa5378..98365d21 100644 --- a/web/src/services/userService.ts +++ b/web/src/services/userService.ts @@ -1,4 +1,4 @@ -import api from "../helpers/api"; +import * as api from "../helpers/api"; import store from "../store"; import { setUser, patchUser } from "../store/modules/user"; @@ -16,7 +16,7 @@ const userService = { }, doSignIn: async () => { - const user = await api.getUser(); + const { data: user } = (await api.getUser()).data; if (user) { store.dispatch(setUser(convertResponseModelUser(user))); } else { @@ -31,7 +31,7 @@ const userService = { }, patchUser: async (userPatch: UserPatch): Promise => { - const data = await api.patchUser(userPatch); + const { data } = (await api.patchUser(userPatch)).data; const user = convertResponseModelUser(data); store.dispatch(patchUser(user)); }, diff --git a/web/yarn.lock b/web/yarn.lock index 2431e718..8d2cb10c 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -589,6 +589,11 @@ array.prototype.flatmap@^1.2.5: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + autoprefixer@^10.4.2: version "10.4.4" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" @@ -601,6 +606,14 @@ autoprefixer@^10.4.2: picocolors "^1.0.0" postcss-value-parser "^4.2.0" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -716,6 +729,13 @@ color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -785,6 +805,11 @@ defined@^1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + detective@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" @@ -1234,6 +1259,20 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== +follow-redirects@^1.14.9: + version "1.15.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" + integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" @@ -1665,6 +1704,18 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"