mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-01-11 10:26:21 +00:00
Setup Modal/NoteHandler
This commit is contained in:
parent
75f9d2ca05
commit
fba84bd081
7 changed files with 404 additions and 7 deletions
22
src/plugins/holynotes/components/icons/HelpIcon.tsx
Normal file
22
src/plugins/holynotes/components/icons/HelpIcon.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* 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="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>
|
||||||
|
);
|
76
src/plugins/holynotes/components/modals/Notebook.tsx
Normal file
76
src/plugins/holynotes/components/modals/Notebook.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
|
import { React, TabBar, Text, TextInput } from "@webpack/common";
|
||||||
|
import noteHandler from "plugins/holynotes/noteHandler";
|
||||||
|
import HelpIcon from "../icons/HelpIcon";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const NoteModal = async (props) => {
|
||||||
|
const [sortType, setSortType] = React.useState(true);
|
||||||
|
const [searchInput, setSearch] = React.useState("");
|
||||||
|
const [sortDirection, setSortDirection] = React.useState(true);
|
||||||
|
const [currentNotebook, setCurrentNotebook] = React.useState("Main");
|
||||||
|
|
||||||
|
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void;
|
||||||
|
const notes = noteHandler.getNotes(currentNotebook);
|
||||||
|
|
||||||
|
if (!notes) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalRoot {...props} className="notebook" size="large" style={{ borderRadius: "8px" }}>
|
||||||
|
<Flex className="notebook-flex" style={{ width: "100%" }}>
|
||||||
|
<div className="notebook-topSection">
|
||||||
|
<ModalHeader className="notebook-header-main">
|
||||||
|
<Text
|
||||||
|
variant="heading-lg/semibold"
|
||||||
|
style={{ flexGrow: 1 }}
|
||||||
|
className="notebook-heading">
|
||||||
|
NOTEBOOK
|
||||||
|
</Text>
|
||||||
|
<div className="notebook-flex help-icon" onClick={() => openModal()}>
|
||||||
|
<HelpIcon />
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: "10px" }} className="notebook-search">
|
||||||
|
<TextInput
|
||||||
|
autoFocus={false}
|
||||||
|
placeholder="Search for a message..."
|
||||||
|
onChange={(e) => setSearch(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ModalCloseButton onClick={props.onClose} />
|
||||||
|
</ModalHeader>
|
||||||
|
<div className="notebook-tabbar-Container">
|
||||||
|
<TabBar
|
||||||
|
type="top"
|
||||||
|
look="brand"
|
||||||
|
className="notebook-tabbar-Bar notebook-tabbar"
|
||||||
|
selectedItem={currentNotebook}
|
||||||
|
onItemSelect={setCurrentNotebook}>
|
||||||
|
{Object.keys(await noteHandler.getAllNotes()).map(notebook => (
|
||||||
|
<TabBar.Item key={notebook} id={notebook} className="notebook-tabbar-barItem notebook-tabbar-item">
|
||||||
|
{notebook}
|
||||||
|
</TabBar.Item>
|
||||||
|
))}
|
||||||
|
</TabBar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ModalContent style={{ marginTop:"20px" }}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
{}
|
||||||
|
</ErrorBoundary>
|
||||||
|
</ModalContent>
|
||||||
|
</Flex>
|
||||||
|
<ModalFooter>
|
||||||
|
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
};
|
|
@ -16,11 +16,25 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./style.css";
|
||||||
|
|
||||||
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
import Message from "discord-types/general";
|
||||||
|
|
||||||
import { Popover as NoteButtonPopover } from "./components/icons/NoteButton";
|
import { Popover as NoteButtonPopover } from "./components/icons/NoteButton";
|
||||||
|
import { NoteModal } from "./components/modals/Notebook";
|
||||||
|
import noteHandler from "./noteHandler";
|
||||||
|
import { HolyNoteStore } from "./utils";
|
||||||
|
|
||||||
|
const messageContextMenuPatch: NavContextMenuPatchCallback = async (children, { message }: { message: Message; }) => {
|
||||||
|
|
||||||
|
console.log(await noteHandler.getAllNotes());
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "HolyNotes",
|
name: "HolyNotes",
|
||||||
|
@ -31,20 +45,20 @@ export default definePlugin({
|
||||||
|
|
||||||
toolboxActions: {
|
toolboxActions: {
|
||||||
async "Open Notes"() {
|
async "Open Notes"() {
|
||||||
|
openModal(props => <NoteModal {...props} />);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
contextMenus: {
|
||||||
|
"message": messageContextMenuPatch
|
||||||
|
},
|
||||||
|
store: HolyNoteStore,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
addButton("HolyNotes", message => {
|
addButton("HolyNotes", (message) => {
|
||||||
console.log("HolyNotes", message);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: "Save Note",
|
label: "Save Note",
|
||||||
icon: NoteButtonPopover,
|
icon: NoteButtonPopover,
|
||||||
onClick: () => {
|
onClick: () => noteHandler.addNote(message, "Main")
|
||||||
console.log("Clicked on Save Note");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -53,3 +67,4 @@ export default definePlugin({
|
||||||
removeButton("HolyNotes");
|
removeButton("HolyNotes");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
120
src/plugins/holynotes/noteHandler.ts
Normal file
120
src/plugins/holynotes/noteHandler.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataStore } from "@api/index";
|
||||||
|
import { ChannelStore, Toasts, lodash } from "@webpack/common";
|
||||||
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
|
||||||
|
import { Discord, HolyNotes } from "./types";
|
||||||
|
import { HolyNoteStore } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
|
export default new (class NoteHandler {
|
||||||
|
private _formatNote(channel: Channel, message: Message): HolyNotes.Note {
|
||||||
|
return {
|
||||||
|
id: message.id,
|
||||||
|
channel_id: message.channel_id,
|
||||||
|
guild_id: channel.guild_id,
|
||||||
|
content: message.content,
|
||||||
|
author: {
|
||||||
|
id: message.author.id,
|
||||||
|
avatar: message.author.avatar,
|
||||||
|
discriminator: message.author.discriminator,
|
||||||
|
username: message.author.username,
|
||||||
|
},
|
||||||
|
flags: message.flags,
|
||||||
|
// Moment has a toString() function, this doesn't convert to '[object Object]'.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||||
|
timestamp: message.timestamp.toString(),
|
||||||
|
attachments: message.attachments as Discord.Attachment[],
|
||||||
|
embeds: message.embeds,
|
||||||
|
reactions: message.reactions as Discord.Reaction[],
|
||||||
|
stickerItems: message.stickerItems,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async getNotes(notebook?: string): Promise<Record<string, HolyNotes.Note>> {
|
||||||
|
if (await DataStore.keys(HolyNoteStore).then(keys => keys.includes(notebook))) {
|
||||||
|
return await DataStore.get(notebook, HolyNoteStore) ?? {};
|
||||||
|
} else {
|
||||||
|
return this.newNoteBook(notebook).then(() => this.getNotes(notebook));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAllNotes(): Promise<HolyNotes.Note[]> {
|
||||||
|
// Needs fucking fixing for fuck sakes VEN GIVE ME IMAGE PERMS
|
||||||
|
return { ...await DataStore.values(HolyNoteStore) } ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addNote = async (message: Message, notebook: string) => {
|
||||||
|
const notes = this.getNotes(notebook);
|
||||||
|
const channel = ChannelStore.getChannel(message.channel_id);
|
||||||
|
const newNotes = Object.assign({ [notebook]: { [message.id]: this._formatNote(channel, message) } }, notes);
|
||||||
|
|
||||||
|
await DataStore.set(notebook, newNotes, HolyNoteStore);
|
||||||
|
|
||||||
|
Toasts.show({
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: `Successfully added note to ${notebook}.`,
|
||||||
|
type: Toasts.Type.SUCCESS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public deleteNote = async (noteId: string, notebook: string) => {
|
||||||
|
const notes = this.getNotes(notebook);
|
||||||
|
|
||||||
|
await DataStore.set(notebook, lodash.omit(notes, noteId), HolyNoteStore);
|
||||||
|
|
||||||
|
Toasts.show({
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: `Successfully deleted note from ${notebook}.`,
|
||||||
|
type: Toasts.Type.SUCCESS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public moveNote = async (note: HolyNotes.Note, from: string, to: string) => {
|
||||||
|
const origNotebook = this.getNotes(from);
|
||||||
|
const newNoteBook = lodash.clone(this.getNotes(to));
|
||||||
|
|
||||||
|
newNoteBook[note.id] = note;
|
||||||
|
|
||||||
|
await DataStore.set(from, lodash.omit(origNotebook, note.id), HolyNoteStore);
|
||||||
|
await DataStore.set(to, newNoteBook, HolyNoteStore);
|
||||||
|
|
||||||
|
Toasts.show({
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: `Successfully moved note from ${from} to ${to}.`,
|
||||||
|
type: Toasts.Type.SUCCESS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public newNoteBook = async (notebookName: string) => {
|
||||||
|
if (await DataStore.keys().then(keys => keys.includes(notebookName))) {
|
||||||
|
Toasts.show({
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: `Notebook ${notebookName} already exists.`,
|
||||||
|
type: Toasts.Type.FAILURE,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await DataStore.set(notebookName, {}, HolyNoteStore);
|
||||||
|
Toasts.show({
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: `Successfully created ${notebookName}.`,
|
||||||
|
type: Toasts.Type.SUCCESS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public deleteNotebook = async (notebookName: string) => {
|
||||||
|
await DataStore.del(notebookName);
|
||||||
|
Toasts.show({
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: `Successfully deleted ${notebookName}.`,
|
||||||
|
type: Toasts.Type.SUCCESS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
106
src/plugins/holynotes/style.css
Normal file
106
src/plugins/holynotes/style.css
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
$modifier: var(--background-modifier-accent);
|
||||||
|
*/
|
||||||
|
.notebook-search {
|
||||||
|
flex: auto;
|
||||||
|
margin-right: 15px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
background-color: var(--background-tertiary);
|
||||||
|
border: solid 2px var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-header {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 16px 16px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-header-main {
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
background-color: var(--background-tertiary);
|
||||||
|
box-shadow: 0 1px 0 0 var(--background-tertiary), 0 1px 2px 0 var(--background-tertiary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-heading {
|
||||||
|
max-width: 110px;
|
||||||
|
margin-right: 0px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-tabbar {
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 20px 0;
|
||||||
|
background-color: var(--background-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-tabbar-item {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-display-left {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-flex {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-flex .help-icon {
|
||||||
|
width: 23px;
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: 0px;
|
||||||
|
margin: 0px 14px 6px 0px;
|
||||||
|
color: var(--interactive-normal);
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-flex .help-icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-markdown {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-markdown hr {
|
||||||
|
border: 0;
|
||||||
|
height: 2px;
|
||||||
|
margin: 12px 0px 16px 0px;
|
||||||
|
background-color: var(--background-modifier-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.holy-note [class*="buttonContainer"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holy-note [class*="messageListItem"] {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-tabbar-Bar {
|
||||||
|
align-items: stretch;
|
||||||
|
display: flex;
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-tabbar-Container {
|
||||||
|
border-bottom: 1px solid var(--profile-body-divider-color);
|
||||||
|
margin: 20px 12px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-tabbar-barItem {
|
||||||
|
display: inline;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
height: 39px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-topSection {
|
||||||
|
margin-bottom: calc(-8px + .5*(var(--custom-user-profile-modal-header-avatar-size) + var(--custom-user-profile-modal-header-total-avatar-border-size)));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
47
src/plugins/holynotes/types.ts
Normal file
47
src/plugins/holynotes/types.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Embed, MessageAttachment, MessageReaction } from "discord-types/general";
|
||||||
|
|
||||||
|
export declare namespace Discord {
|
||||||
|
export interface Sticker {
|
||||||
|
format_type: number;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Attachment extends MessageAttachment {
|
||||||
|
sensitive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Reaction extends MessageReaction {
|
||||||
|
burst_colors: string[];
|
||||||
|
borst_count: number;
|
||||||
|
count_details: { burst: number; normal: number };
|
||||||
|
me_burst: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare namespace HolyNotes {
|
||||||
|
export interface Note {
|
||||||
|
id: string;
|
||||||
|
channel_id: string;
|
||||||
|
guild_id: string;
|
||||||
|
content: string;
|
||||||
|
author: {
|
||||||
|
id: string;
|
||||||
|
avatar: string;
|
||||||
|
discriminator: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
flags: number;
|
||||||
|
timestamp: string;
|
||||||
|
attachments: Discord.Attachment[];
|
||||||
|
embeds: Embed[];
|
||||||
|
reactions: Discord.Reaction[];
|
||||||
|
stickerItems: Discord.Sticker[];
|
||||||
|
}
|
||||||
|
}
|
11
src/plugins/holynotes/utils.ts
Normal file
11
src/plugins/holynotes/utils.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createStore } from "@api/DataStore";
|
||||||
|
import { Settings } from "@api/Settings";
|
||||||
|
|
||||||
|
|
||||||
|
export const HolyNoteStore = createStore("HolyNotesData", "HolyNotesStore");
|
Loading…
Reference in a new issue