This commit is contained in:
thororen 2024-04-17 14:29:47 -04:00
parent 538b87062a
commit ea7451bcdc
326 changed files with 24876 additions and 2280 deletions

View file

@ -0,0 +1,23 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classes } from "@utils/misc";
export default ({ className }: { className?: string; }): JSX.Element => (
<svg
x="0"
y="0"
className={classes("vc-holynotes-icon")}
aria-hidden="true"
role="img"
width="24"
height="24"
viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12 2C6.486 2 2 6.487 2 12C2 17.515 6.486 22 12 22C17.514 22 22 17.515 22 12C22 6.487 17.514 2 12 2ZM12 18.25C11.31 18.25 10.75 17.691 10.75 17C10.75 16.31 11.31 15.75 12 15.75C12.69 15.75 13.25 16.31 13.25 17C13.25 17.691 12.69 18.25 12 18.25ZM13 13.875V15H11V12H12C13.104 12 14 11.103 14 10C14 8.896 13.104 8 12 8C10.896 8 10 8.896 10 10H8C8 7.795 9.795 6 12 6C14.205 6 16 7.795 16 10C16 11.861 14.723 13.429 13 13.875Z"></path>
</svg>
);

View file

@ -0,0 +1,17 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
const component = (props: React.SVGProps<SVGSVGElement>): JSX.Element => (
<svg viewBox="6 3.7 16 16" width={24} height={24} {...(props as React.SVGProps<SVGSVGElement>)}>
<path
fill="currentColor"
d="M15.44,5H12.88v6.42L11.12,9.67,9.38,11.42V5H7a.58.58,0,0,0-.58.58V16.5a.56.56,0,0,0,.09.31l1.18,1.91a.55.55,0,0,0,.49.28H17a.58.58,0,0,0,.58-.58V8.48a.52.52,0,0,0-.07-.27L16,5.31A.6.6,0,0,0,15.44,5Zm.94,12.83H8.52l-.71-1.16h7.4a.58.58,0,0,0,.58-.59V7.41l.59,1.25Z"
/>
</svg>
);
export default component;
export const Popover = component as unknown as (props: unknown) => JSX.Element;

View file

@ -0,0 +1,31 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
const OverFlowIcon = ({ width = 24, height = 24, color = "var(--interactive-normal)", className, ...rest }) => (
<svg
{...rest}
width={width}
height={height}
viewBox="0 0 16 16"
className={className}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.45329 8.53891L3.26217 13.7844C2.95995 14.0719 2.49772 14.0719 2.21328 13.7844C1.92883 13.497 1.92883 13.0299 2.21328 12.7245L6.88884 7.99999L2.21328 3.27543C1.92883 2.988 1.92883 2.50297 2.21328 2.21555C2.49772 1.92812 2.95995 1.92812 3.26217 2.21555L8.45329 7.47903C8.73774 7.76645 8.73774 8.23352 8.45329 8.53891Z"
fill={color}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.4533 8.53891L9.26217 13.7844C8.95995 14.0719 8.49772 14.0719 8.21328 13.7844C7.92883 13.497 7.92883 13.0299 8.21328 12.7245L12.8888 7.99999L8.21328 3.27543C7.92883 2.988 7.92883 2.50297 8.21328 2.21555C8.49772 1.92812 8.95995 1.92812 9.26217 2.21555L14.4533 7.47903C14.7377 7.76645 14.7377 8.23352 14.4533 8.53891Z"
fill={color}
/>
</svg>
);
export default OverFlowIcon;
export const SvgOverFlowIcon = OverFlowIcon as unknown as (props: React.SVGProps<SVGSVGElement>) => JSX.Element;

View file

@ -0,0 +1,51 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findByProps } from "@webpack";
export default ({ error }: { error?: Error; } = {}) => {
const classes = findByProps("emptyResultsWrap");
if (error) {
// Error
console.log(error);
return (
<div className={classes.emptyResultsWrap}>
<div className={classes.emptyResultsContent} style={{ paddingBottom: "0px" }}>
<div className={classes.errorImage} />
<div className={classes.emptyResultsText}>
There was an error parsing your notes! The issue was logged in your console, press CTRL
+ I to access it! Please visit the support server if you need extra help!
</div>
</div>
</div>
);
} else if (Math.floor(Math.random() * 100) <= 10)
// Easter egg
return (
<div className={classes.emptyResultsWrap}>
<div className={classes.emptyResultsContent} style={{ paddingBottom: "0px" }}>
<div className={`${classes.noResultsImage} ${classes.alt}`} />
<div className={classes.emptyResultsText}>
No notes were found. Empathy banana is here for you.
</div>
</div>
</div>
);
// Empty notebook
else
return (
<div className={classes.emptyResultsWrap}>
<div className={classes.emptyResultsContent} style={{ paddingBottom: "0px" }}>
<div className={classes.noResultsImage} />
<div className={classes.emptyResultsText}>
No notes were found saved in this notebook.
</div>
</div>
</div>
);
};

View file

@ -0,0 +1,89 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { findByProps } from "@webpack";
import { Button, Forms, Text } from "@webpack/common";
import noteHandler from "../../NoteHandler";
import { downloadNotes, uploadNotes } from "../../utils";
export default ({ onClose, ...modalProps }: ModalProps & { onClose: () => void; }) => {
const { colorStatusGreen } = findByProps("colorStatusGreen");
return (
<ModalRoot {...modalProps} className="vc-help-modal" size={ModalSize.MEDIUM}>
<ModalHeader className="notebook-header">
<Text tag="h3">Help Modal</Text>
<ModalCloseButton onClick={onClose} />
</ModalHeader>
<ModalContent>
<div className="vc-help-markdown">
<Text>Adding Notes</Text>
<Forms.FormText>
To add a note right click on a message then hover over the "Note Message" item and click
<br />
the button with the notebook name you would like to note the message to.
<br />
<span style={{ fontWeight: "bold" }} className={colorStatusGreen}>
Protip:
</span>{" "}
Clicking the "Note Message" button by itself will note to Main by default!
</Forms.FormText>
<hr />
<Text>Deleting Notes</Text>
<Forms.FormText>
Note you can either right click the note and hit "Delete Note" or you can hold the
'DELETE' key on your keyboard and click on a note; it's like magic!
</Forms.FormText>
<hr />
<Text>Moving Notes</Text>
<Forms.FormText>
To move a note right click on a note and hover over the "Move Note" item and click on
the button corresponding to the notebook you would like to move the note to.
</Forms.FormText>
<hr />
<Text>Jump To Message</Text>
<Forms.FormText>
To jump to the location that the note was originally located at just right click on the
note and hit "Jump to Message".
</Forms.FormText>
</div>
</ModalContent>
<ModalFooter>
<div className="vc-notebook-display-left">
<Button
look={Button.Looks.FILLED}
color={Button.Colors.GREEN}
style={{ marginRight: "10px" }}
onClick={() => {
noteHandler.refreshAvatars();
}}>Refresh Avatars</Button>
<Button
look={Button.Looks.FILLED}
color={Button.Colors.GREEN}
style={{ marginRight: "10px" }}
onClick={() => {
uploadNotes();
}}>Import Notes</Button>
<Button
look={Button.Looks.FILLED}
color={Button.Colors.GREEN}
style={{ marginRight: "70px" }}
onClick={() => {
downloadNotes();
}}>Export Notes</Button>
<Button
look={Button.Looks.FILLED}
color={Button.Colors.RED}
onClick={() => {
noteHandler.deleteEverything();
}}>Delete All Notes</Button>
</div>
</ModalFooter>
</ModalRoot>
);
};

View file

@ -0,0 +1,28 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { openModal } from "@utils/modal";
import { Button, React } from "@webpack/common";
import NotebookCreateModal from "./NotebookCreateModal";
import NotebookDeleteModal from "./NotebookDeleteModal";
export default ({ notebook, setNotebook }: { notebook: string, setNotebook: React.Dispatch<React.SetStateAction<string>>; }) => {
const isNotMain = notebook !== "Main";
return (
<Button
color={isNotMain ? Button.Colors.RED : Button.Colors.GREEN}
onClick={
isNotMain
? () => openModal(props => <NotebookDeleteModal {...props} notebook={notebook} onChangeTab={setNotebook} />)
: () => openModal(props => <NotebookCreateModal {...props} />)
}
>
{isNotMain ? "Delete Notebook" : "Create Notebook"}
</Button>
);
};

View file

@ -0,0 +1,185 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classes } from "@utils/misc";
import { findByProps } from "@webpack";
import { Button, Clickable, Menu, Popout, React } from "@webpack/common";
import { SvgOverFlowIcon } from "../icons/overFlowIcon";
export function NoteBookTabs({ tabs, selectedTabId, onSelectTab }: { tabs: string[], selectedTabId: string, onSelectTab: (tab: string) => void }) {
const tabBarRef = React.useRef<HTMLDivElement>(null);
const widthRef = React.useRef<number>(0);
const tabWidthMapRef = React.useRef(new Map());
const [overflowedTabs, setOverflowedTabs] = React.useState<string[]>([]);
const resizeObserverRef = React.useRef<ResizeObserver | null>(null);
const [show, setShow] = React.useState(false);
const { isNotNullish } = findByProps("isNotNullish");
const { overflowIcon } = findByProps("overflowIcon");
const handleResize = React.useCallback(() => {
if (!tabBarRef.current) return;
const overflowed = [] as string[];
const totalWidth = tabBarRef.current.clientWidth;
if (totalWidth !== widthRef.current) {
// Thanks to daveyy1 for the help with this
let width = 0;
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
const tabRef = tabWidthMapRef.current.get(tab);
if (!tabRef) continue;
width += tabRef.width;
if (width > totalWidth) {
overflowed.push(tab);
}
}
setOverflowedTabs(overflowed);
}
}, [tabs, selectedTabId]);
React.useEffect(() => {
handleResize();
resizeObserverRef.current = new ResizeObserver(handleResize);
if (tabBarRef.current) resizeObserverRef.current.observe(tabBarRef.current);
return () => {
if (resizeObserverRef.current) resizeObserverRef.current.disconnect();
};
}, [handleResize]);
const TabItem = React.forwardRef(function ({ id, selected, onClick, children }: { id: string, selected: boolean, onClick: () => void, children: React.ReactNode }, ref) {
return (
<Clickable
className={classes("vc-notebook-tabbar-item", selected ? "vc-notebook-selected" : "")}
data-tab-id={id}
// @ts-expect-error
innerRef={ref}
onClick={onClick}
>
{children}
</Clickable>
);
});
const renderOverflowMenu = React.useCallback((closePopout: () => void) => {
return (
<Menu.Menu
navId="notebook-tabs"
aria-label="Notebook Tabs"
variant="fixed"
onClose={closePopout}
onSelect={closePopout}
>
{tabs.map(tab => {
return overflowedTabs.includes(tab) && selectedTabId !== tab ? (
<Menu.MenuItem
id={tab}
label={tab}
action={() => onSelectTab(tab)}
/>
) : null;
}).filter(isNotNullish)}
</Menu.Menu>
);
}, [tabs, selectedTabId, onSelectTab, overflowedTabs]);
return (
<div
className={classes("vc-notebook-tabbar")}
ref={tabBarRef}
>
{tabs.map(tab => {
if (!overflowedTabs.includes(tab)) {
return (
<TabItem
id={tab}
selected={selectedTabId === tab}
ref={(el: HTMLElement | null) => {
const width = tabWidthMapRef.current.get(tab)?.width ?? 0;
tabWidthMapRef.current.set(tab, {
node: el,
width: el ? el.getBoundingClientRect().width : width
});
}}
onClick={selectedTabId !== tab ? () => onSelectTab(tab) : () => {}}
>
{tab}
</TabItem>
);
}
return null;
}).filter(isNotNullish)}
{overflowedTabs.length > 0 && (
<Popout
shouldShow={show}
onRequestClose={() => setShow(false)}
renderPopout={() => renderOverflowMenu(() => setShow(false))}
position="bottom"
align="right"
spacing={0}
>
{props => (
<Button
{...props}
className={"vc-notebook-overflow-chevron"}
size={Button.Sizes.ICON}
look={Button.Looks.BLANK}
onClick={() => setShow(v => !v)}
>
<SvgOverFlowIcon className={classes(overflowIcon)} width={16} height={16}/>
</Button>
)}
</Popout>
)}
</div >
);
}
export function CreateTabBar({ tabs, firstSelectedTab, onChangeTab }) {
const tabKeys = Object.keys(tabs);
const mainTabIndex = tabKeys.indexOf("Main");
if (mainTabIndex !== -1 && mainTabIndex !== 0) {
tabKeys.splice(mainTabIndex, 1);
tabKeys.unshift("Main");
}
const [selectedTab, setSelectedTab] = React.useState(
firstSelectedTab || (tabKeys.length > 0 ? tabKeys[0] : null)
);
const renderSelectedTab = React.useCallback(() => {
const selectedTabId = tabKeys.find(tab => tab === selectedTab);
return selectedTabId;
}, [tabs, selectedTab]);
return {
TabBar: <NoteBookTabs
tabs={tabKeys}
selectedTabId={selectedTab}
onSelectTab={tab => {
setSelectedTab(tab);
if (onChangeTab) onChangeTab(tab);
}} />,
renderSelectedTab,
selectedTab
};
}

View file

@ -0,0 +1,174 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { findByProps } from "@webpack";
import { ContextMenuApi, Flex, FluxDispatcher, Menu, React, Text, TextInput } from "@webpack/common";
import noteHandler from "../../NoteHandler";
import { HolyNotes } from "../../types";
import HelpIcon from "../icons/HelpIcon";
import Errors from "./Error";
import HelpModal from "./HelpModal";
import ManageNotebookButton from "./ManageNotebookButton";
import { CreateTabBar } from "./NoteBookTab";
import { RenderMessage } from "./RenderMessage";
const renderNotebook = ({
notes, notebook, updateParent, sortDirection, sortType, searchInput, closeModal
}: {
notes: Record<string, HolyNotes.Note>;
notebook: string;
updateParent: () => void;
sortDirection: boolean;
sortType: boolean;
searchInput: string;
closeModal: () => void;
}) => {
const messageArray = Object.values(notes).map(note => (
<RenderMessage
note={note}
notebook={notebook}
updateParent={updateParent}
fromDeleteModal={false}
closeModal={closeModal}
/>
));
if (sortType)
messageArray.sort(
(a, b) =>
new Date(b.props.note?.timestamp)?.getTime() - new Date(a.props.note?.timestamp)?.getTime(),
);
if (sortDirection) messageArray.reverse();
const filteredMessages = messageArray.filter(message =>
message.props.note?.content?.toLowerCase().includes(searchInput.toLowerCase()),
);
return filteredMessages.length > 0 ? filteredMessages : <Errors />;
};
export const NoteModal = (props: ModalProps & { onClose: () => void; }) => {
const [sortType, setSortType] = React.useState(true);
const [searchInput, setSearch] = React.useState("");
const [sortDirection, setSortDirection] = React.useState(true);
const [currentNotebook, setCurrentNotebook] = React.useState("Main");
const { quickSelect, quickSelectLabel, quickSelectQuick, quickSelectValue, quickSelectArrow } = findByProps("quickSelect");
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void;
const notes = noteHandler.getNotes(currentNotebook);
if (!notes) return <></>;
const { TabBar, selectedTab } = CreateTabBar({ tabs: noteHandler.getAllNotes(), firstSelectedTab: currentNotebook, onChangeTab: setCurrentNotebook });
return (
<ErrorBoundary>
<ModalRoot {...props} className={classes("vc-notebook")} size={ModalSize.LARGE}>
<Flex className={classes("vc-notebook-flex")} direction={Flex.Direction.VERTICAL} style={{ width: "100%" }}>
<div className={classes("vc-notebook-top-section")}>
<ModalHeader className={classes("vc-notebook-header-main")}>
<Text
variant="heading-lg/semibold"
style={{ flexGrow: 1 }}
className={classes("vc-notebook-heading")}>
NOTEBOOK
</Text>
<div className={classes("vc-notebook-flex", "vc-help-icon")} onClick={() => openModal(HelpModal)}>
<HelpIcon />
</div>
<div style={{ marginBottom: "10px" }} className={classes("vc-notebook-search")}>
<TextInput
autoFocus={false}
placeholder="Search for a message..."
onChange={e => setSearch(e)}
/>
</div>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<div className={classes("vc-notebook-tabbar-container")}>
{TabBar}
</div>
</div>
<ModalContent style={{ marginTop: "20px" }}>
<ErrorBoundary>
{renderNotebook({
notes,
notebook: currentNotebook,
updateParent: () => forceUpdate(),
sortDirection: sortDirection,
sortType: sortType,
searchInput: searchInput,
closeModal: props.onClose,
})}
</ErrorBoundary>
</ModalContent>
</Flex>
<ModalFooter>
<ManageNotebookButton notebook={currentNotebook} setNotebook={setCurrentNotebook} />
<div className={classes("sort-button-container", "vc-notebook-display-left")}>
<Flex
align={Flex.Align.CENTER}
className={quickSelect}
onClick={(event: React.MouseEvent<HTMLDivElement>) => {
ContextMenuApi.openContextMenu(event, () => (
<Menu.Menu
navId="sort-menu"
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
aria-label="Sort Menu"
>
<Menu.MenuItem
label="Ascending / Date Added"
id="ada"
action={() => {
setSortDirection(true);
setSortType(true);
}} /><Menu.MenuItem
label="Ascending / Message Date"
id="amd"
action={() => {
setSortDirection(true);
setSortType(false);
}} /><Menu.MenuItem
label="Descending / Date Added"
id="dda"
action={() => {
setSortDirection(false);
setSortType(true);
}} /><Menu.MenuItem
label="Descending / Message Date"
id="dmd"
action={() => {
setSortDirection(false);
setSortType(false);
}} />
</Menu.Menu>
));
}}
>
<Text className={quickSelectLabel}>Change Sorting:</Text>
<Flex grow={0} align={Flex.Align.CENTER} className={quickSelectQuick}>
<Text className={quickSelectValue}>
{sortDirection ? "Ascending" : "Descending"} /{" "}
{sortType ? "Date Added" : "Message Date"}
</Text>
<div className={quickSelectArrow} />
</Flex>
</Flex>
</div>
</ModalFooter>
</ModalRoot>
</ErrorBoundary>
);
};

View file

@ -0,0 +1,40 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { Button, React, Text, TextInput } from "@webpack/common";
import noteHandler from "../../NoteHandler";
export default (props: ModalProps & { onClose: () => void; }) => {
const [notebookName, setNotebookName] = React.useState("");
const handleCreateNotebook = React.useCallback(() => {
if (notebookName !== "") noteHandler.newNoteBook(notebookName);
props.onClose();
}, [notebookName]);
return (
<div>
<ModalRoot className="vc-create-notebook" size={ModalSize.SMALL} {...props}>
<ModalHeader className="vc-notebook-header">
<Text tag="h3">Create Notebook</Text>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent>
<TextInput
value={notebookName}
placeholder="Notebook Name"
onChange={value => setNotebookName(value)}
style={{ marginBottom: "10px" }} />
</ModalContent>
<ModalFooter>
<Button onClick={handleCreateNotebook} color={Button.Colors.GREEN}>Create Notebook</Button>
</ModalFooter>
</ModalRoot>
</div>
);
};

View file

@ -0,0 +1,57 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { Button, React, Text } from "@webpack/common";
import noteHandler from "../../NoteHandler";
import Error from "./Error";
import { RenderMessage } from "./RenderMessage";
export default ({ onClose, notebook, onChangeTab, ...props }: ModalProps & { onClose: () => void; notebook: string; onChangeTab: React.Dispatch<React.SetStateAction<string>>; }) => {
const notes = noteHandler.getNotes(notebook);
const handleDelete = () => {
onClose();
onChangeTab("Main");
noteHandler.deleteNotebook(notebook);
};
return (
<ModalRoot
{...props}
className="vc-delete-notebook"
size={ModalSize.LARGE}>
<ModalHeader>
<Text tag="h3">Confirm Deletion</Text>
<ModalCloseButton onClick={onClose} />
</ModalHeader>
<ModalContent>
<ErrorBoundary>
{notes && Object.keys(notes).length > 0 ? (
Object.values(notes).map(note => (
<RenderMessage
note={note}
notebook={notebook}
fromDeleteModal={true} />
))
) : (
<Error />
)}
</ErrorBoundary>
</ModalContent>
<ModalFooter>
<Button
color={Button.Colors.RED}
onClick={handleDelete}
>
DELETE
</Button>
</ModalFooter>
</ModalRoot>
);
};

View file

@ -0,0 +1,188 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classes } from "@utils/misc";
import { ModalProps } from "@utils/modal";
import { findByCode, findByProps } from "@webpack";
import { Clipboard, ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React } from "@webpack/common";
import noteHandler from "../../NoteHandler";
import { HolyNotes } from "../../types";
export const RenderMessage = ({
note,
notebook,
updateParent,
fromDeleteModal,
closeModal,
}: {
note: HolyNotes.Note;
notebook: string;
updateParent?: () => void;
fromDeleteModal: boolean;
closeModal?: () => void;
}) => {
const ChannelMessage = findByProps("ThreadStarterChatMessage").default;
const { message, groupStart, cozyMessage } = findByProps("cozyMessage");
const User = findByCode("isClyde(){");
const Message = findByCode("isEdited(){");
const Channel = findByProps("ChannelRecordBase").ChannelRecordBase;
const [isHoldingDelete, setHoldingDelete] = React.useState(false);
React.useEffect(() => {
const deleteHandler = (e: { key: string; type: string; }) =>
e.key.toLowerCase() === "delete" && setHoldingDelete(e.type.toLowerCase() === "keydown");
document.addEventListener("keydown", deleteHandler);
document.addEventListener("keyup", deleteHandler);
return () => {
document.removeEventListener("keydown", deleteHandler);
document.removeEventListener("keyup", deleteHandler);
};
}, []);
return (
<div
className="vc-holy-note"
style={{
marginBottom: "8px",
marginTop: "8px",
paddingTop: "4px",
paddingBottom: "4px",
}}
onClick={() => {
if (isHoldingDelete && !fromDeleteModal) {
noteHandler.deleteNote(note.id, notebook);
updateParent?.();
}
}}
onContextMenu={(event: any) => {
if (!fromDeleteModal)
// @ts-ignore
return ContextMenuApi.openContextMenu(event, (props: any) => (
// @ts-ignore
<NoteContextMenu
{...Object.assign({}, props, { onClose: close })}
note={note}
notebook={notebook}
updateParent={updateParent}
closeModal={closeModal}
/>
));
}}
>
<ChannelMessage
className={classes("vc-holy-render", message, groupStart, cozyMessage)}
key={note.id}
groupId={note.id}
id={note.id}
compact={false}
isHighlight={false}
isLastItem={false}
renderContentOnly={false}
// @ts-ignore
channel={new Channel({ id: "holy-notes" })}
message={
new Message(
Object.assign(
{ ...note },
{
author: new User({ ...note?.author }),
timestamp: new Date(note?.timestamp),
// @ts-ignore
embeds: note?.embeds?.map((embed: { timestamp: string | number | Date; }) =>
embed.timestamp
? Object.assign(embed, {
timestamp: new Date(embed.timestamp),
})
: embed,
),
},
),
)
}
/>
</div>
);
};
const NoteContextMenu = (
props: ModalProps & {
updateParent?: () => void;
notebook: string;
note: HolyNotes.Note;
closeModal?: () => void;
}) => {
const { note, notebook, updateParent, closeModal } = props;
return (
<Menu.Menu
navId="holynotes"
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
aria-label="Holy Notes"
>
<Menu.MenuItem
label="Jump To Message"
id="jump"
action={() => {
NavigationRouter.transitionTo(`/channels/${note.guild_id ?? "@me"}/${note.channel_id}/${note.id}`);
closeModal?.();
}}
/>
<Menu.MenuItem
label="Copy Text"
id="copy-text"
action={() => Clipboard.copy(note.content)}
/>
{note?.attachments.length ? (
<Menu.MenuItem
label="Copy Attachment URL"
id="copy-url"
action={() => Clipboard.copy(note.attachments[0].url)}
/>) : null}
<Menu.MenuItem
color="danger"
label="Delete Note"
id="delete"
action={() => {
noteHandler.deleteNote(note.id, notebook);
updateParent?.();
}}
/>
{Object.keys(noteHandler.getAllNotes()).length !== 1 ? (
<Menu.MenuItem
label="Move Note"
id="move-note"
>
{Object.keys(noteHandler.getAllNotes()).map((key: string) => {
if (key !== notebook) {
return (
<Menu.MenuItem
label={`Move to ${key}`}
id={key}
action={() => {
noteHandler.moveNote(note, notebook, key);
updateParent?.();
}}
/>
);
}
})}
</Menu.MenuItem>
) : null}
<Menu.MenuItem
label="Copy ID"
id="copy-id"
action={() => Clipboard.copy(note.id)}
/>
</Menu.Menu>
);
};