├── manifest.json
├── style.css
├── LICENSE
├── README.md
└── index.js
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WPM",
3 | "description": "View words per minute on a nice little indicator.",
4 | "author": "rom#5692",
5 | "version": "1.2.0",
6 | "license": "MIT"
7 | }
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | #wpm-indicator-text {
2 | color: rgba(255, 255, 255, 0.5);
3 | user-select: none;
4 | pointer-events: none;
5 | display: block;
6 | position: absolute;
7 | bottom: .2em;
8 | z-index: 1;
9 | }
10 | .message-1PNnaP #wpm-indicator-text {
11 | right: unset;
12 | left: 5px;
13 | }
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Roman F.
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Welcome to wpm 👋
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | > View words per minute on a nice little indicator.
10 |
11 | ### 🏠 [Homepage](https://github.com/romdotdog/wpm)
12 |
13 | ## Install
14 |
15 | ```sh
16 | git clone https://github.com/romdotdog/wpm
17 | ```
18 |
19 | ## Author
20 |
21 | 👤 **rom**
22 |
23 | * Website: https://rom.dog
24 | * Github: [@romdotdog](https://github.com/romdotdog)
25 | * Discord: rom#5692
26 |
27 | ## 🤝 Contributing
28 |
29 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/romdotdog/wpm/issues).
30 |
31 | ## Show your support
32 |
33 | Give a ⭐️ if this project helped you!
34 |
35 | ## 📝 License
36 |
37 | Copyright © 2020 [romdotdog](https://github.com/romdotdog).
38 | This project is [MIT](https://github.com/romdotdog/wpm/blob/main/LICENSE) licensed.
39 |
40 | ***
41 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_
42 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const { Plugin } = require('powercord/entities');
2 | const {
3 | getModule,
4 | getModuleByDisplayName,
5 | React,
6 | Flux
7 | } = require("powercord/webpack");
8 | const { inject, uninject } = require('powercord/injector');
9 |
10 | module.exports = class WPMPlugin extends Plugin {
11 | start = 0;
12 |
13 | resetTime() {
14 | this.start = new Date().getTime();
15 | }
16 |
17 | timeSinceReset() {
18 | return (new Date().getTime() - this.start) / 1000 / 60;
19 | }
20 |
21 | async startPlugin() {
22 | this.loadStylesheet('style.css');
23 |
24 | this.resetTime();
25 | // from https://github.com/Inve1951/BetterDiscordStuff/blob/master/plugins/CharacterCounter.plugin.js
26 |
27 | const channelStore = await getModule(["hasChannel", "getChannel"]);
28 | const channelIdStore = await getModule(["getChannelId", "getLastSelectedChannelId"]);
29 |
30 | const WPM = Flux.connectStores([channelIdStore], (props) => {
31 | const channel = channelStore.getChannel(channelIdStore.getChannelId());
32 | props.rateLimitPerUser = channel.rateLimitPerUser;
33 | return props;
34 | })(({ initValue, rateLimitPerUser }) => {
35 | const [value, setValue] = React.useState(initValue || "");
36 | this.setValue = setValue;
37 |
38 | if (value.trim().length < 2) {
39 | // reset time at zero or one character
40 | this.resetTime();
41 | }
42 |
43 | const right = rateLimitPerUser ? 360 : 16;
44 | const wpm = value.split(" ").length / this.timeSinceReset();
45 | return React.createElement(
46 | "span",
47 | {
48 | id: "wpm-indicator-text",
49 | style: {
50 | right
51 | }
52 | },
53 | `${isFinite(wpm) ? Math.floor(wpm) : 0} WPM`
54 | );
55 | });
56 |
57 | const ChannelEditorContainer = await getModuleByDisplayName('ChannelEditorContainer');
58 |
59 | inject('wpm-hook', ChannelEditorContainer.prototype, 'render', (args, res) => {
60 | setTimeout(() => {
61 | const text = document.querySelector('[data-slate-editor="true"]')?.innerText;
62 | if (text && this.setValue) {
63 | this.setValue(text);
64 | }
65 | });
66 | return res;
67 | });
68 |
69 | const TypingUsers = await getModule(m => m.default && m.default.displayName === 'FluxContainer(TypingUsers)');
70 |
71 | inject('wpm-indicator', TypingUsers.default.prototype, 'render', (args, res) => React.createElement(
72 | React.Fragment, null, res, React.createElement(WPM, { initValue: document.querySelector('[data-slate-editor="true"]')?.innerText })
73 | ));
74 | }
75 |
76 | pluginWillUnload() {
77 | uninject('wpm-indicator');
78 | uninject('wpm-hook');
79 | }
80 | };
81 |
--------------------------------------------------------------------------------