diff --git a/src/plugins/holynotes/components/icons/HelpIcon.tsx b/src/plugins/holynotes/components/icons/HelpIcon.tsx new file mode 100644 index 000000000..3a6a489ed --- /dev/null +++ b/src/plugins/holynotes/components/icons/HelpIcon.tsx @@ -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 => ( + + + +); diff --git a/src/plugins/holynotes/components/modals/Notebook.tsx b/src/plugins/holynotes/components/modals/Notebook.tsx new file mode 100644 index 000000000..91b88776f --- /dev/null +++ b/src/plugins/holynotes/components/modals/Notebook.tsx @@ -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 ( + + + + + + NOTEBOOK + + openModal()}> + + + + setSearch(e)} + /> + + + + + + {Object.keys(await noteHandler.getAllNotes()).map(notebook => ( + + {notebook} + + ))} + + + + + + {} + + + + + + + + ); +}; diff --git a/src/plugins/holynotes/index.tsx b/src/plugins/holynotes/index.tsx index 015c7b218..f031bbc41 100644 --- a/src/plugins/holynotes/index.tsx +++ b/src/plugins/holynotes/index.tsx @@ -16,11 +16,25 @@ * along with this program. If not, see . */ +import "./style.css"; + +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addButton, removeButton } from "@api/MessagePopover"; import { Devs } from "@utils/constants"; +import { openModal } from "@utils/modal"; import definePlugin from "@utils/types"; +import Message from "discord-types/general"; 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({ name: "HolyNotes", @@ -31,20 +45,20 @@ export default definePlugin({ toolboxActions: { async "Open Notes"() { - + openModal(props => ); } }, + contextMenus: { + "message": messageContextMenuPatch + }, + store: HolyNoteStore, async start() { - addButton("HolyNotes", message => { - console.log("HolyNotes", message); - + addButton("HolyNotes", (message) => { return { label: "Save Note", icon: NoteButtonPopover, - onClick: () => { - console.log("Clicked on Save Note"); - } + onClick: () => noteHandler.addNote(message, "Main") }; }); }, @@ -53,3 +67,4 @@ export default definePlugin({ removeButton("HolyNotes"); } }); + diff --git a/src/plugins/holynotes/noteHandler.ts b/src/plugins/holynotes/noteHandler.ts new file mode 100644 index 000000000..a4dc4f46d --- /dev/null +++ b/src/plugins/holynotes/noteHandler.ts @@ -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> { + 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 { + // 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, + }); + }; +}); diff --git a/src/plugins/holynotes/style.css b/src/plugins/holynotes/style.css new file mode 100644 index 000000000..8f890926b --- /dev/null +++ b/src/plugins/holynotes/style.css @@ -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; +} diff --git a/src/plugins/holynotes/types.ts b/src/plugins/holynotes/types.ts new file mode 100644 index 000000000..0163cf60c --- /dev/null +++ b/src/plugins/holynotes/types.ts @@ -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[]; + } + } diff --git a/src/plugins/holynotes/utils.ts b/src/plugins/holynotes/utils.ts new file mode 100644 index 000000000..c04435e10 --- /dev/null +++ b/src/plugins/holynotes/utils.ts @@ -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");