├── .gitattributes ├── .gitignore ├── LICENSE ├── NotesHandler └── index.js ├── README.md ├── assets ├── banner.png └── icon.png ├── components ├── icons │ └── NotebookButton.jsx ├── modals │ ├── BDImport.jsx │ ├── CreateNotebook.jsx │ ├── DeleteNotebook.jsx │ ├── HelpModal.jsx │ └── Notebook.jsx └── sections │ ├── NoResultsMessage.jsx │ ├── NotebookManagementButton.jsx │ └── RenderMessage.jsx ├── index.js ├── manifest.json └── style.scss /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | NotesHandler/notes.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jason 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /NotesHandler/index.js: -------------------------------------------------------------------------------- 1 | /* Credits to Kyza for making this custom SettingsHandler */ 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const notesPath = path.join(__dirname, 'notes.json'); 5 | 6 | class NotesHandler { 7 | constructor() { 8 | this.initNotes(); 9 | } 10 | 11 | initNotes = () => { 12 | if (!fs.existsSync(notesPath)) { 13 | fs.writeFileSync(notesPath, JSON.stringify({ 'Main': {} }, null, '\t')); 14 | } 15 | }; 16 | 17 | getNotes = () => { 18 | this.initNotes(); 19 | return JSON.parse(fs.readFileSync(notesPath)); 20 | }; 21 | 22 | addNote = (noteData, notebook) => { 23 | this.initNotes(); 24 | let notes; 25 | try { notes = this.getNotes(); } 26 | catch { return; } 27 | 28 | let noteFormat = { 29 | [noteData.message.id]: { 30 | id: noteData.message.id, 31 | channel_id: noteData.channel.id, 32 | guild_id: noteData.channel.guild_id, 33 | content: noteData.message.content, 34 | author: { 35 | id: noteData.message.author.id, 36 | avatar: noteData.message.author.avatar, 37 | discriminator: noteData.message.author.discriminator, 38 | username: noteData.message.author.username, 39 | }, 40 | timestamp: noteData.message.timestamp, 41 | attachments: noteData.message.attachments, 42 | embeds: noteData.message.embeds, 43 | reactions: noteData.message.reactions 44 | } 45 | }; 46 | 47 | Object.assign(notes[notebook], noteFormat); 48 | fs.writeFileSync(notesPath, JSON.stringify(notes, null, '\t')); 49 | }; 50 | 51 | deleteNote = (note, notebook) => { 52 | this.initNotes(); 53 | let notes; 54 | try { notes = this.getNotes(); } 55 | catch { return; } 56 | 57 | delete notes[notebook][note]; 58 | 59 | fs.writeFileSync(notesPath, JSON.stringify(notes, null, '\t')); 60 | }; 61 | 62 | moveNote = (note, toNotebook, fromNotebook) => { 63 | this.initNotes(); 64 | let notes; 65 | try { notes = this.getNotes(); } 66 | catch { return; } 67 | 68 | delete notes[fromNotebook][note.id]; 69 | Object.assign(notes[toNotebook], { [note.id]: note }); 70 | fs.writeFileSync(notesPath, JSON.stringify(notes, null, '\t')); 71 | }; 72 | 73 | newNotebook = (name) => { 74 | this.initNotes(); 75 | let notes; 76 | try { notes = this.getNotes(); } 77 | catch { return; } 78 | 79 | Object.assign(notes, { [name]: {} }); 80 | fs.writeFileSync(notesPath, JSON.stringify(notes, null, '\t')); 81 | }; 82 | 83 | deleteNotebook = (notebook) => { 84 | this.initNotes(); 85 | let notes; 86 | try { notes = this.getNotes(); } 87 | catch { return; } 88 | 89 | delete notes[notebook]; 90 | fs.writeFileSync(notesPath, JSON.stringify(notes, null, '\t')); 91 | }; 92 | 93 | parseBDNotes = (data, notebook) => { 94 | this.initNotes(); 95 | let notes; 96 | try { notes = this.getNotes(); } 97 | catch { return; } 98 | if (!Object.keys(notes).includes(notebook)) 99 | Object.assign(notes, { [notebook]: {} }); 100 | let BDNotes = JSON.parse(data).notes; 101 | 102 | for (let guildID in BDNotes) { 103 | for (let channelID in BDNotes[guildID]) { 104 | for (let messageID in BDNotes[guildID][channelID]) { 105 | let note = JSON.parse(BDNotes[guildID][channelID][messageID].message); 106 | Object.assign(notes[notebook], { 107 | [note.id]: { 108 | id: note.id, 109 | channel_id: channelID, 110 | guild_id: guildID, 111 | content: note.content, 112 | author: { 113 | id: note.author.id, 114 | avatar: note.author.avatar, 115 | discriminator: note.author.discriminator, 116 | username: note.author.username, 117 | }, 118 | timestamp: note.timestamp, 119 | attachments: note.attachments, 120 | embeds: note.embeds, 121 | reactions: note.reactions 122 | } 123 | }); 124 | } 125 | } 126 | } 127 | fs.writeFileSync(notesPath, JSON.stringify(notes, null, '\t')); 128 | }; 129 | 130 | refreshAvatars = async () => { 131 | this.initNotes(); 132 | let notes; 133 | try { notes = this.getNotes(); } 134 | catch { return; } 135 | 136 | const { getModule } = require('powercord/webpack'); 137 | const User = getModule(m => m?.prototype?.tag, false); 138 | const getCachedUser = getModule(['getCurrentUser', 'getUser'], false).getUser; 139 | const fetchUser = getModule(['getUser'], false).getUser; 140 | 141 | for (let notebook in notes) { 142 | for (let noteID in notes[notebook]) { 143 | let note = notes[notebook][noteID]; 144 | let user = getCachedUser(note.author.id) 145 | ?? await fetchUser(note.author.id) 146 | ?? new User({ ...note.author }); 147 | 148 | Object.assign(notes[notebook][noteID].author, { 149 | avatar: user.avatar, 150 | discriminator: user.discriminator, 151 | username: user.username 152 | }); 153 | } 154 | } 155 | 156 | fs.writeFileSync(notesPath, JSON.stringify(notes, null, '\t')); 157 | }; 158 | } 159 | 160 | module.exports = NotesHandler; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT NOTICE 2 | ## THIS VERSION WILL NOW BE FOREVER UNMAINTAINED 3 | ### Official development will continue at: https://github.com/swishs-client-mod-plugins/sperm-bank 4 | 5 | 6 | Alternatively you can use [@SammChesse](https://github.com/SammCheese)'s fork https://github.com/SammCheese/holy-notes but powercord is a sinking ship so have fun while it lasts. 7 | 8 | Archived Text Below: 9 | ![Banner](https://github.com/Swishilicous/holy-notes/blob/vizality/assets/banner.png?raw=true) 10 |

11 | GitHub repo size 12 | Version 13 | Stars 14 |

15 | 16 | # Holy Notes 17 | Welcome to the dark side. This is a port of Holy Notes for powercord. 18 | 19 | ### Installation 20 | For installation, go to **Plugins -> Open a CMD / Powershell / Terminal / Gitbash** in the folder, and enter the following: 21 | ``` 22 | git clone https://github.com/swishs-client-mod-plugins/holy-notes 23 | ``` 24 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swishs-client-mod-plugins/holy-notes/5cddbc2a747fde5a5f8091d3dd3a0f4d4beca73a/assets/banner.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swishs-client-mod-plugins/holy-notes/5cddbc2a747fde5a5f8091d3dd3a0f4d4beca73a/assets/icon.png -------------------------------------------------------------------------------- /components/icons/NotebookButton.jsx: -------------------------------------------------------------------------------- 1 | const { React } = require('powercord/webpack'); 2 | 3 | module.exports = React.memo( 4 | (props) => ( 5 | 6 | 10 | 11 | ) 12 | ); -------------------------------------------------------------------------------- /components/modals/BDImport.jsx: -------------------------------------------------------------------------------- 1 | const { Modal } = require('powercord/components/modal'); 2 | const { close: closeModal } = require('powercord/modal'); 3 | const { React, getModuleByDisplayName } = require('powercord/webpack'); 4 | const { FormTitle, AdvancedScrollerThin, Button } = require('powercord/components'); 5 | const { useState } = React; 6 | 7 | const Divider = getModuleByDisplayName('Divider', false); 8 | const TextArea = getModuleByDisplayName('TextArea', false); 9 | const TextInput = getModuleByDisplayName('TextInput', false); 10 | const NotesHandler = new (require('../../NotesHandler'))(); 11 | module.exports = () => { 12 | const [personalPinsData, setPersonalPinsData] = useState(''); 13 | const [notebook, setNotebook] = useState('PersonalPins'); 14 | return ( 15 | 16 | 17 | Import from PersonalPins 18 | 19 | 20 | 21 | 22 | 23 | Input the text from your "PersonalPins.config.json" file located in your BD Plugins folder. 24 | 25 | 42 | 43 | Add to Notebook 44 | 50 | 51 | 52 | 53 | 61 | 67 | 68 | 69 | ); 70 | }; -------------------------------------------------------------------------------- /components/modals/CreateNotebook.jsx: -------------------------------------------------------------------------------- 1 | const { Modal } = require('powercord/components/modal'); 2 | const { close: closeModal } = require('powercord/modal'); 3 | const { FormTitle, Button } = require('powercord/components'); 4 | const { React, getModuleByDisplayName } = require('powercord/webpack'); 5 | const { useState } = React; 6 | 7 | const TextInput = getModuleByDisplayName('TextInput', false); 8 | const NotesHandler = new (require('../../NotesHandler'))(); 9 | module.exports = () => { 10 | const [notebookName, setNotebookName] = useState(''); 11 | return ( 12 | 13 | 14 | Create Notebook 15 | 16 | 17 | 18 | Notebook Name 19 | 25 | 26 | 27 | 36 | 42 | 43 | 44 | ); 45 | }; -------------------------------------------------------------------------------- /components/modals/DeleteNotebook.jsx: -------------------------------------------------------------------------------- 1 | const { FormTitle, Button } = require('powercord/components'); 2 | const { Modal } = require('powercord/components/modal'); 3 | const { AdvancedScrollerThin } = require('powercord/components'); 4 | const { close: closeModal } = require('powercord/modal'); 5 | const { React } = require('powercord/webpack'); 6 | 7 | const NoResultsMessage = require('../sections/NoResultsMessage'); 8 | const RenderMessage = require('../sections/RenderMessage'); 9 | const NotesHandler = new (require('../../NotesHandler'))(); 10 | 11 | module.exports = ({ notebook, setNotebook }) => { 12 | const notes = NotesHandler.getNotes()[notebook]; 13 | return ( 14 | 15 | 16 | Confirm Deletion 17 | 18 | 19 | 20 | 21 | {JSON.stringify(notes) === '{}' || !notes 22 | ? 23 | : Object.keys(notes).map(note => 24 | 29 | )} 30 | 31 | 32 | 33 | 42 | 48 | 49 | 50 | ); 51 | }; -------------------------------------------------------------------------------- /components/modals/HelpModal.jsx: -------------------------------------------------------------------------------- 1 | const { Modal } = require('powercord/components/modal'); 2 | const { close: closeModal, open: openModal } = require('powercord/modal'); 3 | const { React, getModule, getModuleByDisplayName } = require('powercord/webpack'); 4 | const { FormTitle, Button, AdvancedScrollerThin } = require('powercord/components'); 5 | 6 | const BDModal = require('./BDImport'); 7 | const Colors = getModule(['colorStatusGreen'], false); 8 | const FormText = getModuleByDisplayName('FormText', false); 9 | const NotesHandler = new (require('../../NotesHandler'))(); 10 | 11 | module.exports = () => { 12 | const [refreshText, setRefreshText] = React.useState('Refresh Avatars'); 13 | return ( 14 | 15 | 16 | Help Modal 17 | 18 | 19 | 20 | 21 |
22 | Adding Notes 23 | 24 | To add a note right click on a message then hover over the "Note Message" item and click the button with the notebook name you would like to note the message to.

25 | Protip: Clicking the "Note Message" button by itself will note to Main by default! 26 |

27 | Deleting Notes 28 | 29 | 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! 30 |
31 | Moving Notes 32 | 33 | To move a note right click on a note and hover over the "Move Note" item and click on the button corrosponding to the notebook you would like to move the note to. 34 |
35 | Jump to Message 36 | 37 | To jump to the location that the note was originally located at just right click on the note and hit "Jump to Message". 38 | 39 |
40 |
41 |
42 | 43 | 47 | 53 |
54 | 67 |
68 |
69 |
70 | ); 71 | }; -------------------------------------------------------------------------------- /components/modals/Notebook.jsx: -------------------------------------------------------------------------------- 1 | const { Modal } = require('powercord/components/modal'); 2 | const { close: closeModal, open: openModal } = require('powercord/modal'); 3 | const { React, React: { useState }, getModule, contextMenu } = require('powercord/webpack'); 4 | const { TabBar, AdvancedScrollerThin, Button, FormTitle, Icon, Text, Flex } = require('powercord/components'); 5 | 6 | const HelpModal = require('./HelpModal'); 7 | const RenderMessage = require('../sections/RenderMessage'); 8 | const NoResultsMessage = require('../sections/NoResultsMessage'); 9 | const NotebookManagementButton = require('../sections/NotebookManagementButton'); 10 | 11 | const Classes = { 12 | TabBar: getModule(['tabBarContainer'], false), 13 | QuickSelect: getModule(['quickSelect'], false) 14 | }; 15 | 16 | const NotesHandler = new (require('../../NotesHandler'))(); 17 | const ContextMenu = getModule(['MenuGroup', 'MenuItem'], false); 18 | const SearchBar = getModule(m => m.defaultProps?.useKeyboardNavigation, false); 19 | 20 | const NotebookRender = ({ notes, notebook, updateParent, sortDirection, sortType, searchInput }) => { 21 | if (Object.keys(notes).length === 0) { 22 | return ; 23 | } else { 24 | let messageArray; 25 | sortType ? 26 | messageArray = Object.keys(notes).map(note => 27 | 33 | ) : 34 | messageArray = Object.keys(notes).map(note => 35 | 41 | ).sort((a, b) => new Date(b.props.note.timestamp) - new Date(a.props.note.timestamp)); 42 | if (sortDirection) messageArray.reverse(); 43 | 44 | /* Search Filter */ 45 | if (searchInput && searchInput !== '') 46 | messageArray = messageArray.filter(m => 47 | m.props.note.content.toLowerCase() 48 | .indexOf(searchInput.trim()) > -1); 49 | return messageArray; 50 | } 51 | }; 52 | 53 | module.exports = () => { 54 | const [sortType, setSortType] = useState(true); 55 | const [searchInput, setSearchInput] = useState(''); 56 | const [sortDirection, setSortDirection] = useState(true); 57 | const [currentNotebook, setCurrentNotebook] = useState('Main'); 58 | // since hooks don't have a native forceUpdate() function this is the easisest workaround 59 | const forceUpdate = useState(0)[1]; 60 | const notes = NotesHandler.getNotes()[currentNotebook]; 61 | if (!notes) return <>; 62 | return ( 63 | 64 | 65 |
66 | 67 | 68 | NOTEBOOK 69 | 70 | openModal(() => )} /> 73 | setSearchInput(query)} 79 | onClear={() => setSearchInput('')} 80 | query={searchInput} /> 81 | 82 | 83 |
84 | 89 | {Object.keys(NotesHandler.getNotes()).map(notebook => 90 | 91 | {notebook} 92 | 93 | )} 94 | 95 |
96 |
97 | 98 | 99 | forceUpdate(u => ~u)} 103 | sortDirection={sortDirection} 104 | sortType={sortType} 105 | searchInput={searchInput} /> 106 | 107 | 108 |
109 | 110 | 113 | 120 |
121 | { 122 | contextMenu.openContextMenu(event, () => ( 123 | 124 | { setSortDirection(true); setSortType(true); }} /> 127 | { setSortDirection(true); setSortType(false); }} /> 130 | { setSortDirection(false); setSortType(true); }} /> 133 | { setSortDirection(false); setSortType(false); }} /> 136 | 137 | )); 138 | }}> 139 | Change Sorting: 140 | 141 | 142 | {sortDirection ? 'Ascending' : 'Descending'} / 143 | {sortType ? ' Date Added' : ' Message Date'} 144 | 145 |
146 | 147 | 148 |
149 |
150 | 151 | 152 | ); 153 | }; -------------------------------------------------------------------------------- /components/sections/NoResultsMessage.jsx: -------------------------------------------------------------------------------- 1 | const { React, getModule } = require('powercord/webpack'); 2 | 3 | const classes = { 4 | ...getModule(['emptyResultsWrap'], false) 5 | }; 6 | 7 | module.exports = ({ error }) => { 8 | if (error) console.log(error); 9 | if (error) { // not functional yet 10 | return ( 11 |
12 |
13 |
14 |
15 | 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! 16 |
17 |
18 |
19 | ); 20 | } else if (Math.floor(Math.random() * 100) <= 10) { 21 | return ( 22 |
23 |
24 |
25 |
26 | No notes were found. Empathy banana is here for you. 27 |
28 |
29 |
30 | ); 31 | } else { 32 | return ( 33 |
34 |
35 |
36 |
37 | No notes were found saved in this notebook. 38 |
39 |
40 |
41 | ); 42 | } 43 | }; -------------------------------------------------------------------------------- /components/sections/NotebookManagementButton.jsx: -------------------------------------------------------------------------------- 1 | const { Button } = require('powercord/components'); 2 | const { open: openModal } = require('powercord/modal'); 3 | const { React } = require('powercord/webpack'); 4 | 5 | const DeleteNotebook = require('../modals/DeleteNotebook'); 6 | const CreateNotebook = require('../modals/CreateNotebook'); 7 | 8 | module.exports = (args) => { 9 | if (args.notebook != 'Main') { 10 | return <> 11 | 16 | ; 17 | } else { 18 | return <> 19 | 24 | ; 25 | } 26 | }; -------------------------------------------------------------------------------- /components/sections/RenderMessage.jsx: -------------------------------------------------------------------------------- 1 | const { getModule, contextMenu, React, React: { useEffect } } = require('powercord/webpack'); 2 | const { clipboard } = require('electron'); 3 | 4 | const User = getModule(m => m.prototype?.tag, false); 5 | const NotesHandler = new (require('../../NotesHandler'))(); 6 | const ContextMenu = getModule(['MenuGroup', 'MenuItem'], false); 7 | const transitionTo = getModule(['transitionTo'], false).transitionTo; 8 | const Timestamp = getModule(m => m.prototype?.toDate && m.prototype.month, false); 9 | const ChannelMessage = getModule(m => m.type?.displayName === 'ChannelMessage', false); 10 | const Message = getModule(m => m.prototype?.getReaction && m.prototype.isSystemDM, false); 11 | const Channel = getModule(m => m.prototype?.getGuildId, false); 12 | 13 | module.exports = ({ note, notebook, updateParent, fromDeleteModal, closeModal }) => { 14 | const classes = getModule(['cozyMessage'], false); 15 | 16 | let isHoldingDelete; 17 | useEffect(() => { 18 | const deleteHandler = (e) => e.key === 'Delete' && (isHoldingDelete = e.type === 'keydown'); 19 | 20 | document.addEventListener('keydown', deleteHandler); 21 | document.addEventListener('keyup', deleteHandler); 22 | 23 | return () => { 24 | document.removeEventListener('keydown', deleteHandler); 25 | document.removeEventListener('keyup', deleteHandler); 26 | }; 27 | }, []); 28 | 29 | return ( 30 |
31 | embed.timestamp ? Object.assign(embed, { 49 | timestamp: new Timestamp(new Date(embed.timestamp)) 50 | }) : embed) 51 | }) 52 | ) 53 | } 54 | channel={new Channel({ id: 'holy-notes' })} 55 | onClick={() => { 56 | if (isHoldingDelete && !fromDeleteModal) { 57 | NotesHandler.deleteNote(note.id, notebook); 58 | updateParent(); 59 | } 60 | }} 61 | onContextMenu={event => { 62 | if (!fromDeleteModal) 63 | return ( 64 | contextMenu.openContextMenu(event, () => 65 | 71 | ) 72 | ); 73 | }} 74 | /> 75 |
76 | ); 77 | }; 78 | 79 | const NoteContextMenu = ({ note, notebook, updateParent, closeModal }) => { 80 | return <> 81 | 82 | { 85 | transitionTo(`/channels/${note.guild_id ?? '@me'}/${note.channel_id}/${note.id}`); 86 | closeModal(); 87 | }} /> 88 | clipboard.writeText(note.content)} /> 91 | { 95 | NotesHandler.deleteNote(note.id, notebook); 96 | updateParent(); 97 | }} /> 98 | {Object.keys(NotesHandler.getNotes()).length !== 1 ? 99 | 101 | {Object.keys(NotesHandler.getNotes()).map(key => { 102 | if (key != notebook) { 103 | return ( 104 | { 107 | NotesHandler.moveNote(note, key, notebook); 108 | updateParent(); 109 | }} /> 110 | ); 111 | } 112 | } 113 | )} 114 | : null} 115 | clipboard.writeText(note.id)} /> 118 | 119 | ; 120 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { Plugin } = require('powercord/entities'); 2 | const { Tooltip } = require('powercord/components'); 3 | const { inject, uninject } = require('powercord/injector'); 4 | const { React, getModule, getModuleByDisplayName } = require('powercord/webpack'); 5 | const { open: openModal } = require('powercord/modal'); 6 | const { findInReactTree } = require('powercord/util'); 7 | 8 | const NotesHandler = new (require('./NotesHandler'))(); 9 | const NotebookButton = require('./components/icons/NotebookButton'); 10 | const NotebookModal = require('./components/modals/Notebook'); 11 | 12 | module.exports = class Notebook extends Plugin { 13 | async startPlugin() { 14 | this._injectHeaderBarContainer(); 15 | this._injectContextMenu(); 16 | 17 | this.loadStylesheet('style.scss'); 18 | } 19 | 20 | pluginWillUnload() { 21 | uninject('holy-header-bar'); 22 | uninject('holy-context-menu'); 23 | }; 24 | 25 | // creds to juby 26 | async lazyPatchContextMenu(displayName, patch) { 27 | const filter = m => m.default && m.default.displayName === displayName; 28 | const m = getModule(filter, false); 29 | if (m) patch(m); 30 | else { 31 | const module = getModule(['openContextMenuLazy'], false); 32 | inject('holy-notes-context-lazy-menu', module, 'openContextMenuLazy', args => { 33 | const lazyRender = args[1]; 34 | args[1] = async () => { 35 | const render = await lazyRender(args[0]); 36 | 37 | return config => { 38 | const menu = render(config); 39 | if (menu?.type?.displayName === displayName && patch) { 40 | uninject('holy-notes-context-lazy-menu'); 41 | patch(getModule(filter, false)); 42 | patch = false; 43 | } 44 | return menu; 45 | }; 46 | }; 47 | return args; 48 | }, 49 | true 50 | ); 51 | } 52 | } 53 | 54 | async _injectHeaderBarContainer() { 55 | const HeaderBarContainer = await getModuleByDisplayName('HeaderBarContainer'); 56 | const classes = await getModule(['iconWrapper', 'clickable']); 57 | inject('holy-header-bar', HeaderBarContainer.prototype, 'render', (args, res) => { 58 | res.props.toolbar.props.children.push( 59 | React.createElement(Tooltip, { 60 | text: 'Notebook', position: 'bottom' 61 | }, React.createElement('div', { 62 | className: `note-button ${classes.iconWrapper} ${classes.clickable}` 63 | }, React.createElement(NotebookButton, { 64 | className: `note-button ${classes.icon}`, 65 | onClick: () => 66 | openModal(() => React.createElement(NotebookModal)) 67 | })))); 68 | return res; 69 | }); 70 | } 71 | 72 | async _injectContextMenu() { 73 | const Menu = await getModule(['MenuGroup', 'MenuItem']); 74 | this.lazyPatchContextMenu('MessageContextMenu', MessageContextMenu => { 75 | inject('holy-context-menu', MessageContextMenu, 'default', (args, res) => { 76 | if (!findInReactTree(res, c => c.props?.id == 'notebook')) res.props.children.splice(4, 0, 77 | React.createElement(Menu.MenuGroup, null, React.createElement(Menu.MenuItem, { 78 | action: () => NotesHandler.addNote(args[0], 'Main'), 79 | id: 'notebook', label: 'Note Message' 80 | }, Object.keys(NotesHandler.getNotes()).map(notebook => 81 | React.createElement(Menu.MenuItem, { 82 | label: `Add to ${notebook}`, id: notebook, 83 | action: () => NotesHandler.addNote(args[0], notebook) 84 | })) 85 | ))); 86 | return res; 87 | }); 88 | MessageContextMenu.default.displayName = 'MessageContextMenu'; 89 | }); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Holy Notes", 3 | "description": "Adds message noting functionality to Discord.", 4 | "version": "1.1.1", 5 | "author": "Swishilicous#3200", 6 | "license": "MIT" 7 | } -------------------------------------------------------------------------------- /style.scss: -------------------------------------------------------------------------------- 1 | .notebook { 2 | &-search { 3 | flex: auto; 4 | margin-right: 15px; 5 | margin-bottom: 6px; 6 | background-color: var(--background-primary); 7 | } 8 | 9 | &-header { 10 | margin-bottom: 10px; 11 | padding: 16px 16px 10px 16px; 12 | &-main { 13 | padding-top: 15px; 14 | padding-left: 10px; 15 | padding-bottom: 0px; 16 | background-color: var(--background-tertiary); 17 | box-shadow: 0 1px 0 0 var(--background-tertiary), 0 1px 2px 0 var(--background-tertiary) !important; 18 | } 19 | } 20 | 21 | &-heading { 22 | max-width: 95px; 23 | margin-right: 0px; 24 | padding-bottom: 6px; 25 | transform: scale(0.85); 26 | } 27 | 28 | &-tabbar { 29 | margin: 0; 30 | padding: 10px 20px 0; 31 | background-color: var(--background-tertiary); 32 | &-item { 33 | font-size: 14px; 34 | } 35 | } 36 | 37 | &-display-left { 38 | flex: auto; 39 | display: flex; 40 | } 41 | 42 | &-flex { 43 | overflow: hidden; 44 | .help-icon { 45 | width: 17px; 46 | opacity: .5; 47 | cursor: pointer; 48 | padding-left: 0px; 49 | &:hover { opacity: 1; } 50 | margin: 0px 14px 6px 0px; 51 | color: var(--interactive-normal); 52 | transition: opacity .2s ease-in-out; 53 | } 54 | } 55 | } 56 | 57 | .help-markdown { 58 | margin-top: 6px; 59 | hr { 60 | border: 0; 61 | height: 2px; 62 | margin: 12px 0px 16px 0px; 63 | background-color: var(--background-modifier-accent); 64 | } 65 | } --------------------------------------------------------------------------------