mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-25 22:37:02 -04:00
forked!!
This commit is contained in:
parent
538b87062a
commit
ea7451bcdc
326 changed files with 24876 additions and 2280 deletions
23
src/equicordplugins/holyNotes/components/icons/HelpIcon.tsx
Normal file
23
src/equicordplugins/holyNotes/components/icons/HelpIcon.tsx
Normal 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>
|
||||
);
|
|
@ -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;
|
|
@ -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;
|
51
src/equicordplugins/holyNotes/components/modals/Error.tsx
Normal file
51
src/equicordplugins/holyNotes/components/modals/Error.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
185
src/equicordplugins/holyNotes/components/modals/NoteBookTab.tsx
Normal file
185
src/equicordplugins/holyNotes/components/modals/NoteBookTab.tsx
Normal 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
|
||||
};
|
||||
}
|
174
src/equicordplugins/holyNotes/components/modals/Notebook.tsx
Normal file
174
src/equicordplugins/holyNotes/components/modals/Notebook.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue