refactor: more TS code

This commit is contained in:
Tristan Yang
2022-07-29 08:49:34 +08:00
parent 43a13f8d08
commit 96017c8fc6
21 changed files with 150 additions and 90 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ export const authApi = createApi({
} }
}), }),
// 更新 device token // 更新 device token
updateDeviceToken: builder.mutation<void, { device_token: string }>({ updateDeviceToken: builder.mutation<void, string>({
query: (device_token) => ({ query: (device_token) => ({
url: "/token/device_token", url: "/token/device_token",
method: "PUT", method: "PUT",
+1 -1
View File
@@ -19,7 +19,7 @@ const favoritesSlice = createSlice({
addFavorite(state, action: PayloadAction<Favorite>) { addFavorite(state, action: PayloadAction<Favorite>) {
state.push(action.payload); state.push(action.payload);
}, },
deleteFavorite(state, action: PayloadAction<number>) { deleteFavorite(state, action: PayloadAction<string>) {
const id = action.payload; const id = action.payload;
const idx = state.findIndex((f) => f.id == id); const idx = state.findIndex((f) => f.id == id);
if (idx > -1) { if (idx > -1) {
+7 -7
View File
@@ -1,13 +1,6 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import BASE_URL, { ContentTypes } from "../config"; import BASE_URL, { ContentTypes } from "../config";
import { isImage } from "../../common/utils"; import { isImage } from "../../common/utils";
export interface State {
[key: number]: object;
replying: {
[key: string | number]: number;
};
}
export interface MessagePayload { export interface MessagePayload {
mid: number; mid: number;
sending: boolean; sending: boolean;
@@ -21,6 +14,13 @@ export interface MessagePayload {
download?: string; download?: string;
thumbnail?: string; thumbnail?: string;
} }
export interface State {
[key: number]: MessagePayload;
replying: {
[key: string | number]: number;
};
}
const initialState: State = { const initialState: State = {
replying: {} replying: {}
}; };
+14 -5
View File
@@ -1,3 +1,4 @@
import { FC, ReactElement, ReactNode } from "react";
import Tippy from "@tippyjs/react"; import Tippy from "@tippyjs/react";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import ContextMenu from "../ContextMenu"; import ContextMenu from "../ContextMenu";
@@ -11,8 +12,16 @@ import IconSelect from "../../../assets/icons/select.svg";
import { updateSelectMessages } from "../../../app/slices/ui"; import { updateSelectMessages } from "../../../app/slices/ui";
import useSendMessage from "../../hook/useSendMessage"; import useSendMessage from "../../hook/useSendMessage";
import useMessageOperation from "./useMessageOperation"; import useMessageOperation from "./useMessageOperation";
type Props = {
export default function MessageContextMenu({ context: "user" | "channel";
contextId: number;
mid: number;
visible: boolean;
hide: () => void;
editMessage: () => void;
children: ReactElement;
};
const MessageContextMenu: FC<Props> = ({
context, context,
contextId, contextId,
mid, mid,
@@ -20,10 +29,9 @@ export default function MessageContextMenu({
hide, hide,
editMessage, editMessage,
children children
}) { }) => {
const { const {
copyContent, copyContent,
// isMarkdown,
canEdit, canEdit,
canPin, canPin,
canDelete, canDelete,
@@ -109,4 +117,5 @@ export default function MessageContextMenu({
</Tippy> </Tippy>
</> </>
); );
} };
export default MessageContextMenu;
+2 -2
View File
@@ -52,10 +52,10 @@ type Props = {
}; };
const EditMessage: FC<Props> = ({ mid, cancelEdit }) => { const EditMessage: FC<Props> = ({ mid, cancelEdit }) => {
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const msg = useAppSelector((store) => store.message[mid] || {}); const msg = useAppSelector((store) => store.message[mid]);
const [shift, setShift] = useState(false); const [shift, setShift] = useState(false);
const [enter, setEnter] = useState(false); const [enter, setEnter] = useState(false);
const [currMsg, setCurrMsg] = useState(msg.content); const [currMsg, setCurrMsg] = useState(msg?.content);
const [edit, { isLoading: isEditing, isSuccess }] = useEditMessageMutation(); const [edit, { isLoading: isEditing, isSuccess }] = useEditMessageMutation();
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
@@ -12,9 +12,9 @@ const StyledFav = styled.div`
background-color: #f4f4f5; background-color: #f4f4f5;
`; `;
type Props = { type Props = {
id?: number; id?: string;
}; };
const FavoredMessage: FC<Props> = ({ id }) => { const FavoredMessage: FC<Props> = ({ id = "" }) => {
const { favorites } = useFavMessage({}); const { favorites } = useFavMessage({});
const [msgs, setMsgs] = useState<ReactElement | null>(null); const [msgs, setMsgs] = useState<ReactElement | null>(null);
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect, useState, FC, ReactElement } from "react";
import styled from "styled-components"; import styled from "styled-components";
import StyledMsg from "./styled"; import StyledMsg from "./styled";
import renderContent from "./renderContent"; import renderContent from "./renderContent";
@@ -29,9 +29,15 @@ const StyledForward = styled.div`
color: #98a2b3; color: #98a2b3;
} }
`; `;
const ForwardedMessage = ({ context, to, from_uid, id }) => { type Props = {
context: "user" | "channel";
to: number;
from_uid: number;
id: string;
};
const ForwardedMessage: FC<Props> = ({ context, to, from_uid, id }) => {
const { normalizeMessage, messages } = useNormalizeMessage(); const { normalizeMessage, messages } = useNormalizeMessage();
const [forwards, setForwards] = useState(null); const [forwards, setForwards] = useState<ReactElement | null>(null);
useEffect(() => { useEffect(() => {
if (id) { if (id) {
normalizeMessage(id); normalizeMessage(id);
@@ -50,15 +56,7 @@ const ForwardedMessage = ({ context, to, from_uid, id }) => {
</h4> </h4>
<div className="list"> <div className="list">
{messages.map((msg, idx) => { {messages.map((msg, idx) => {
const { const { user = {}, download, content, content_type, properties, thumbnail } = msg;
user = {},
// created_at,
download,
content,
content_type,
properties,
thumbnail
} = msg;
return ( return (
<StyledMsg className="archive" key={idx}> <StyledMsg className="archive" key={idx}>
{user && ( {user && (
+2 -2
View File
@@ -1,4 +1,4 @@
import { FC, ReactNode } from "react"; // import { FC, ReactNode } from "react";
import Tippy from "@tippyjs/react"; import Tippy from "@tippyjs/react";
import styled from "styled-components"; import styled from "styled-components";
import Profile from "../Profile"; import Profile from "../Profile";
@@ -19,7 +19,7 @@ interface Props {
textOnly?: boolean; textOnly?: boolean;
} }
const Mention: FC<Props> = ({ uid, popover = true, cid, textOnly = false }) => { const Mention = ({ uid, popover = true, cid, textOnly = false }: Props) => {
const usersData = useAppSelector((store) => store.users.byId); const usersData = useAppSelector((store) => store.users.byId);
const user = usersData[uid]; const user = usersData[uid];
if (!user) return null; if (!user) return null;
@@ -14,7 +14,7 @@ const PreviewMessage: FC<Props> = ({ mid = 0 }) => {
return { msg: store.message[mid], usersData: store.users.byId }; return { msg: store.message[mid], usersData: store.users.byId };
}); });
if (!msg) return null; if (!msg) return null;
const { from_uid, created_at, content_type, content, thumbnail, properties } = msg; const { from_uid, created_at, content_type, content, thumbnail = "", properties } = msg;
const { name, avatar } = usersData[from_uid] || {}; const { name, avatar } = usersData[from_uid] || {};
return ( return (
<StyledWrapper className={`preview`}> <StyledWrapper className={`preview`}>
+22 -6
View File
@@ -1,9 +1,10 @@
import { FC } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { getEmojiDataFromNative } from "emoji-mart"; import { getEmojiDataFromNative } from "emoji-mart";
import AppleEmojiData from "emoji-mart/data/apple.json"; import AppleEmojiData from "emoji-mart/data/apple.json";
import Tippy from "@tippyjs/react"; import Tippy from "@tippyjs/react";
import { hideAll } from "tippy.js"; import { hideAll } from "tippy.js";
import ReactionItem from "../ReactionItem"; import ReactionItem, { Emojis } from "../ReactionItem";
import ReactionPicker from "./ReactionPicker"; import ReactionPicker from "./ReactionPicker";
import Tooltip from "../Tooltip"; import Tooltip from "../Tooltip";
import { useReactMessageMutation } from "../../../app/services/message"; import { useReactMessageMutation } from "../../../app/services/message";
@@ -104,12 +105,20 @@ const StyledDetails = styled.div`
color: #1d2939; color: #1d2939;
} }
`; `;
const ReactionDetails = ({ uids = [], emoji, index }) => { const ReactionDetails = ({
uids = [],
emoji,
index
}: {
uids: number[];
emoji?: keyof Emojis;
index: number;
}) => {
const usersData = useAppSelector((store) => store.users.byId); const usersData = useAppSelector((store) => store.users.byId);
const names = uids.map((id) => { const names = uids.map((id) => {
return usersData[id]?.name; return usersData[id]?.name;
}); });
const emojiData = getEmojiDataFromNative(emoji, "apple", AppleEmojiData); const emojiData = getEmojiDataFromNative(emoji || "", "apple", AppleEmojiData);
const prefixDesc = const prefixDesc =
names.length > 3 names.length > 3
? `${names.join(", ")} and ${names.length - 3} others reacted with` ? `${names.join(", ")} and ${names.length - 3} others reacted with`
@@ -127,14 +136,20 @@ const ReactionDetails = ({ uids = [], emoji, index }) => {
</StyledDetails> </StyledDetails>
); );
}; };
export default function Reaction({ mid, reactions = null }) { type Props = {
mid: number;
reactions?: {
[key in keyof Emojis]: number[];
};
};
const Reaction: FC<Props> = ({ mid, reactions = null }) => {
const [reactWithEmoji] = useReactMessageMutation(); const [reactWithEmoji] = useReactMessageMutation();
const { currUid } = useAppSelector((store) => { const { currUid } = useAppSelector((store) => {
return { return {
currUid: store.authData.user?.uid currUid: store.authData.user?.uid
}; };
}); });
const handleReact = (emoji) => { const handleReact = (emoji: string) => {
reactWithEmoji({ mid, action: emoji }); reactWithEmoji({ mid, action: emoji });
}; };
console.log("curr reactions", reactions); console.log("curr reactions", reactions);
@@ -178,4 +193,5 @@ export default function Reaction({ mid, reactions = null }) {
</Tooltip> </Tooltip>
</StyledWrapper> </StyledWrapper>
); );
} };
export default Reaction;
@@ -1,3 +1,4 @@
import { FC } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { useReactMessageMutation } from "../../../app/services/message"; import { useReactMessageMutation } from "../../../app/services/message";
import { Emojis } from "../../../app/config"; import { Emojis } from "../../../app/config";
@@ -34,8 +35,11 @@ const StyledPicker = styled.div`
} }
} }
`; `;
type Props = {
export default function ReactionPicker({ mid, hidePicker }) { mid: number;
hidePicker: () => void;
};
const ReactionPicker: FC<Props> = ({ mid, hidePicker }) => {
const [reactMessage, { isLoading }] = useReactMessageMutation(); const [reactMessage, { isLoading }] = useReactMessageMutation();
const { reactionData, currUid } = useAppSelector((store) => { const { reactionData, currUid } = useAppSelector((store) => {
return { return {
@@ -68,4 +72,5 @@ export default function ReactionPicker({ mid, hidePicker }) {
</ul> </ul>
</StyledPicker> </StyledPicker>
); );
} };
export default ReactionPicker;
+2 -4
View File
@@ -95,8 +95,6 @@ const Styled = styled.div`
color: #555; color: #555;
} }
} }
/* padding-left: 10px; */
`; `;
const renderContent = (data) => { const renderContent = (data) => {
@@ -162,11 +160,11 @@ const Reply: FC<ReplyProps> = ({ mid, interactive = true }) => {
const { mid } = evt.currentTarget.dataset; const { mid } = evt.currentTarget.dataset;
const msgEle = document.querySelector<HTMLDivElement>(`[data-msg-mid='${mid}']`); const msgEle = document.querySelector<HTMLDivElement>(`[data-msg-mid='${mid}']`);
if (msgEle) { if (msgEle) {
msgEle.dataset.highlight = true; msgEle.dataset.highlight = "true";
msgEle.scrollIntoView({ behavior: "smooth", block: "center" }); msgEle.scrollIntoView({ behavior: "smooth", block: "center" });
setTimeout(() => { setTimeout(() => {
console.log("scroll view", msgEle); console.log("scroll view", msgEle);
msgEle.dataset.highlight = false; msgEle.dataset.highlight = "false";
}, 3000); }, 3000);
} }
}; };
+18 -7
View File
@@ -8,11 +8,22 @@ import ForwardedMessage from "./ForwardedMessage";
import MarkdownRender from "../MarkdownRender"; import MarkdownRender from "../MarkdownRender";
import FileMessage from "../FileMessage"; import FileMessage from "../FileMessage";
import URLPreview from "./URLPreview"; import URLPreview from "./URLPreview";
type Props = {
context: "user" | "channel";
to?: number;
from_uid?: number;
created_at: number;
properties?: object;
content_type;
content: string;
download: string;
thumbnail: string;
edited?: boolean | number;
};
const renderContent = ({ const renderContent = ({
context = null, context,
to = null, to = 0,
from_uid, from_uid = 0,
created_at, created_at,
properties, properties,
content_type, content_type,
@@ -20,7 +31,7 @@ const renderContent = ({
download, download,
thumbnail, thumbnail,
edited = false edited = false
}) => { }: Props) => {
let ctn = null; let ctn = null;
switch (content_type) { switch (content_type) {
case ContentTypes.text: case ContentTypes.text:
@@ -39,13 +50,13 @@ const renderContent = ({
{reactStringReplace(content, /(\s{1}@[0-9]+\s{1})/g, (match, idx) => { {reactStringReplace(content, /(\s{1}@[0-9]+\s{1})/g, (match, idx) => {
console.log("match", match); console.log("match", match);
const uid = match.trim().slice(1); const uid = match.trim().slice(1);
return <Mention key={idx} uid={uid} cid={to} />; return <Mention key={idx} uid={+uid} cid={to} />;
})} })}
{/* {content.replace(/\s{1}\@[1-9]+\s{1}/g,)} */} {/* {content.replace(/\s{1}\@[1-9]+\s{1}/g,)} */}
{/* {new RegExp(/\s{1}\@[1-9]+\s{1}/g).exec(content)} */} {/* {new RegExp(/\s{1}\@[1-9]+\s{1}/g).exec(content)} */}
</Linkit> </Linkit>
{edited && ( {edited && (
<span className="edited" title={dayjs(edited).format("YYYY-MM-DD h:mm:ss A")}> <span className="edited" title={dayjs(+edited).format("YYYY-MM-DD h:mm:ss A")}>
(edited) (edited)
</span> </span>
)} )}
@@ -26,9 +26,9 @@ export default function useMessageOperation({ mid, context, contextId }: Params)
}; };
}); });
const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage( const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage(
context == "channel" ? contextId : undefined context == "channel" ? contextId : 0
); );
const [mids, setMids] = useState([]); const [mids, setMids] = useState<number[]>([]);
const [pinModalVisible, setPinModalVisible] = useState(false); const [pinModalVisible, setPinModalVisible] = useState(false);
const [deleteModalVisible, setDeleteModalVisible] = useState(false); const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const [forwardModalVisible, setForwardModalVisible] = useState(false); const [forwardModalVisible, setForwardModalVisible] = useState(false);
@@ -53,10 +53,9 @@ export default function useMessageOperation({ mid, context, contextId }: Params)
// forward message // forward message
const forwardEle = document.querySelector( const forwardEle = document.querySelector(
`[data-msg-mid='${mid}'] .down [data-forwarded-mids]` `[data-msg-mid='${mid}'] .down [data-forwarded-mids]`
); ) as HTMLDivElement;
if (forwardEle) { if (forwardEle) {
console.log("ddddd", mid, forwardEle.dataset); const mids = forwardEle.dataset.forwardedMids?.split(",").map((m) => +m) || [];
const mids = forwardEle.dataset.forwardedMids.split(",");
setMids(mids); setMids(mids);
} }
} else { } else {
@@ -1,10 +0,0 @@
import styled from "styled-components";
const Styled = styled.div`
display: flex;
`;
export default function StyledCombobox({ store }) {
console.log("combox wtf", store.get.state());
return <Styled>StyledCombobox</Styled>;
}
+3 -3
View File
@@ -8,7 +8,7 @@ import EmojiHeart from "../../assets/icons/emoji.heart.svg";
import EmojiRocket from "../../assets/icons/emoji.rocket.svg"; import EmojiRocket from "../../assets/icons/emoji.rocket.svg";
import EmojiLook from "../../assets/icons/emoji.look.svg"; import EmojiLook from "../../assets/icons/emoji.look.svg";
interface Emojis { export interface Emojis {
"👍": ReactElement; "👍": ReactElement;
"👎": ReactElement; "👎": ReactElement;
"😄": ReactElement; "😄": ReactElement;
@@ -31,11 +31,11 @@ const emojis: Emojis = {
}; };
interface Props { interface Props {
native?: keyof Emojis; native: keyof Emojis;
} }
const ReactionItem: FC<Props> = ({ native }) => { const ReactionItem: FC<Props> = ({ native }) => {
if (!native) return null; // if (!native) return null;
return emojis[native] ?? null; return emojis[native] ?? null;
}; };
+11 -3
View File
@@ -8,6 +8,7 @@ import pictureIcon from "../../../assets/icons/picture.svg?url";
import { getFileIcon, isImage } from "../../utils"; import { getFileIcon, isImage } from "../../utils";
import useSendMessage from "../../hook/useSendMessage"; import useSendMessage from "../../hook/useSendMessage";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import { MessagePayload } from "../../../app/slices/message";
const Styled = styled.div` const Styled = styled.div`
background-color: #f3f4f6; background-color: #f3f4f6;
@@ -74,10 +75,9 @@ const Styled = styled.div`
position: absolute; position: absolute;
top: 16px; top: 16px;
right: 16px; right: 16px;
/* transform: translateY(-50%); */
} }
`; `;
const renderContent = (data) => { const renderContent = (data: MessagePayload) => {
const { content_type, content, thumbnail = "", properties } = data; const { content_type, content, thumbnail = "", properties } = data;
let res = null; let res = null;
switch (content_type) { switch (content_type) {
@@ -120,7 +120,15 @@ const renderContent = (data) => {
return res; return res;
}; };
export default function Replying({ context, id, mid }) { export default function Replying({
context,
id,
mid
}: {
context: "user" | "channel";
id: number;
mid: number;
}) {
const { removeReplying } = useSendMessage({ to: id, context }); const { removeReplying } = useSendMessage({ to: id, context });
const { msg, usersData } = useAppSelector((store) => { const { msg, usersData } = useAppSelector((store) => {
return { usersData: store.users.byId, msg: store.message[mid] }; return { usersData: store.users.byId, msg: store.message[mid] };
+13 -5
View File
@@ -1,4 +1,4 @@
import { ChangeEvent, useRef } from "react"; import { ChangeEvent, useRef, FC } from "react";
import styled from "styled-components"; import styled from "styled-components";
import Tooltip from "../Tooltip"; import Tooltip from "../Tooltip";
import AddIcon from "../../../assets/icons/add.solid.svg"; import AddIcon from "../../../assets/icons/add.solid.svg";
@@ -42,15 +42,22 @@ const Styled = styled.div`
} }
} }
`; `;
type Props = {
export default function Toolbar({ toggleMarkdownFullscreen: () => void;
fullscreen: boolean;
toggleMode: () => void;
mode: "markdown" | "text";
to: number;
context: "user" | "channel";
};
const Toolbar: FC<Props> = ({
toggleMarkdownFullscreen, toggleMarkdownFullscreen,
fullscreen, fullscreen,
toggleMode, toggleMode,
mode, mode,
to, to,
context context
}) { }) => {
const { addStageFile } = useUploadFile({ context, id: to }); const { addStageFile } = useUploadFile({ context, id: to });
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const handleUpload = (evt: ChangeEvent<HTMLInputElement>) => { const handleUpload = (evt: ChangeEvent<HTMLInputElement>) => {
@@ -105,4 +112,5 @@ export default function Toolbar({
</Tooltip> </Tooltip>
</Styled> </Styled>
); );
} };
export default Toolbar;
@@ -1,4 +1,4 @@
import { useState } from "react"; import { ChangeEvent, useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import Modal from "../../Modal"; import Modal from "../../Modal";
import Button from "../../styled/Button"; import Button from "../../styled/Button";
@@ -48,9 +48,17 @@ const StyledWrapper = styled.div`
} }
`; `;
export default function EditFileDetails({ name, closeModal, updateName }) { export default function EditFileDetails({
name,
closeModal,
updateName
}: {
name: string;
closeModal: () => void;
updateName: (name: string) => void;
}) {
const [fileName, setFileName] = useState(name); const [fileName, setFileName] = useState(name);
const handleNameChange = (evt) => { const handleNameChange = (evt: ChangeEvent<HTMLInputElement>) => {
setFileName(evt.target.value); setFileName(evt.target.value);
}; };
const handleUpdate = () => { const handleUpdate = () => {
@@ -7,24 +7,34 @@ import useUploadFile from "../../../hook/useUploadFile";
import EditIcon from "../../../../assets/icons/edit.svg"; import EditIcon from "../../../../assets/icons/edit.svg";
import DeleteIcon from "../../../../assets/icons/delete.svg"; import DeleteIcon from "../../../../assets/icons/delete.svg";
export default function UploadFileList({ context = "", id = null }) { type EditProps = {
index: number;
name: string;
};
export default function UploadFileList({
context,
id
}: {
context: "user" | "channel";
id: number;
}) {
const eidtor = useMixedEditor(`${context}_${id}`); const eidtor = useMixedEditor(`${context}_${id}`);
const [editInfo, setEditInfo] = useState(null); const [editInfo, setEditInfo] = useState<EditProps | null>(null);
const { stageFiles, updateStageFile, removeStageFile } = useUploadFile({ const { stageFiles, updateStageFile, removeStageFile } = useUploadFile({
context, context,
id id
}); });
const toggleModalVisible = (info) => { const toggleModalVisible = (info: EditProps) => {
setEditInfo((prev) => (prev ? null : info)); setEditInfo((prev) => (prev ? null : info));
}; };
const handleOpenEditModal = (idx) => { const handleOpenEditModal = (idx: number) => {
const info = stageFiles[idx]; const info = stageFiles[idx];
if (!info) return; if (!info) return;
toggleModalVisible({ ...info, index: idx }); toggleModalVisible({ ...info, index: idx });
}; };
const updateFileName = (name) => { const updateFileName = (name: string) => {
if (!name) return; if (!name || !editInfo) return;
const { index } = editInfo; const { index } = editInfo;
updateStageFile(index, { name }); updateStageFile(index, { name });
}; };
+2 -2
View File
@@ -17,7 +17,7 @@ export default function useFavMessage({
return { favs: store.favorites }; return { favs: store.favorites };
}); });
const addFavorite = async (mid = []) => { const addFavorite = async (mid: number | number[]) => {
const mids = Array.isArray(mid) ? mid.map((i) => +i) : [+mid]; const mids = Array.isArray(mid) ? mid.map((i) => +i) : [+mid];
if (mids.length == 0) return; if (mids.length == 0) return;
const resp = await addFav(mids); const resp = await addFav(mids);
@@ -29,7 +29,7 @@ export default function useFavMessage({
removeFav(id); removeFav(id);
}; };
const isFavorited = (mid = null) => { const isFavorited = (mid = 0) => {
if (!mid) return false; if (!mid) return false;
let mids: number[] = []; let mids: number[] = [];
favorites.forEach((f: Favorite) => { favorites.forEach((f: Favorite) => {