├── .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 | 
10 |
11 |
12 |
13 |
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 | {
56 | NotesHandler.parseBDNotes(personalPinsData, notebook);
57 | closeModal();
58 | }}>
59 | Parse Data
60 |
61 |
65 | Cancel
66 |
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 | {
29 | if (notebookName !== '')
30 | NotesHandler.newNotebook(notebookName);
31 | closeModal();
32 | }}
33 | color={Button.Colors.GREEN}>
34 | Create Notebook
35 |
36 |
40 | Cancel
41 |
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 | {
35 | NotesHandler.deleteNotebook(notebook);
36 | setNotebook('Main');
37 | closeModal();
38 | }}
39 | color={Button.Colors.RED}>
40 | Delete
41 |
42 |
46 | Cancel
47 |
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 | openModal(() => )}>
45 | Import from BD
46 |
47 |
51 | Cancel
52 |
53 |
54 | {
58 | if (refreshText === '...')
59 | return setRefreshText('i\'m already trying dumbass');
60 | setRefreshText('...');
61 | await NotesHandler.refreshAvatars();
62 | closeModal();
63 | setRefreshText('Refresh Avatars');
64 | }}>
65 | {refreshText}
66 |
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 |
118 | Cancel
119 |
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 | openModal(() => )}>
14 | Delete Notebook
15 |
16 | >;
17 | } else {
18 | return <>
19 | openModal(() => )}>
22 | Create Notebook
23 |
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 | }
--------------------------------------------------------------------------------