Compare commits
13 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6916db84b | ||
|
|
195e4df1b6 | ||
|
|
831826d071 | ||
|
|
c1d71da4f9 | ||
|
|
6e2384a001 | ||
|
|
75d05be5c8 | ||
|
|
1e80fe564f | ||
|
|
0f9a59d97e | ||
|
|
37123587c6 | ||
|
|
6303889f2e | ||
|
|
f4f5958f11 | ||
|
|
a50e64b8bf | ||
|
|
61d9ec11e6 |
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Erica Xu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
97
README.md
|
|
@ -1,96 +1,45 @@
|
||||||
# Obsidian Sample Plugin
|
# Gemmy - Obsidian Unhelper
|
||||||
|
|
||||||
This is a sample plugin for Obsidian (https://obsidian.md).
|
<img src="https://user-images.githubusercontent.com/1171143/229297707-5efa8761-ef55-4d01-a105-88a347bc6cf0.png" width="300">
|
||||||
|
|
||||||
This project uses Typescript to provide type checking and documentation.
|
Not sure if an AI assistant is going to be helpful in your workflow?
|
||||||
The repo depends on the latest plugin API (obsidian.d.ts) in Typescript Definition format, which contains TSDoc comments describing what it does.
|
|
||||||
|
|
||||||
**Note:** The Obsidian API is still in early alpha and is subject to change at any time!
|
Don't worry, we've eliminated the guesswork for you because Gemmy is 100% going to be unhelpful.
|
||||||
|
|
||||||
This sample plugin demonstrates some of the basic functionality the plugin API can do.
|
Introducing Obsidian's first assistant — Gemmy, the Obsidian Unhelper. It has two major features:
|
||||||
- Changes the default font color to red using `styles.css`.
|
|
||||||
- Adds a ribbon icon, which shows a Notice when clicked.
|
|
||||||
- Adds a command "Open Sample Modal" which opens a Modal.
|
|
||||||
- Adds a plugin setting tab to the settings page.
|
|
||||||
- Registers a global click event and output 'click' to the console.
|
|
||||||
- Registers a global interval which logs 'setInterval' to the console.
|
|
||||||
|
|
||||||
## First time developing plugins?
|
## Randomly saying things
|
||||||
|
|
||||||
Quick starting guide for new plugin devs:
|
Gemmy seeks validation by randomly saying things every now and then.
|
||||||
|
|
||||||
- Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with.
|
You can customize how often this happens. Or just don't summon Gemmy.
|
||||||
- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it).
|
|
||||||
- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder.
|
|
||||||
- Install NodeJS, then run `npm i` in the command line under your repo folder.
|
|
||||||
- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`.
|
|
||||||
- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`.
|
|
||||||
- Reload Obsidian to load the new version of your plugin.
|
|
||||||
- Enable plugin in settings window.
|
|
||||||
- For updates to the Obsidian API run `npm update` in the command line under your repo folder.
|
|
||||||
|
|
||||||
## Releasing new releases
|
## Writing mode
|
||||||
|
|
||||||
- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release.
|
Gemmy pops out to [motivate you](https://user-images.githubusercontent.com/1171143/229297494-8a0cae3f-4c8e-47a9-801b-dd1c52dfc917.png) when you have stopped typing for long enough. Again, you can customize how long that should be.
|
||||||
- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible.
|
|
||||||
- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases
|
|
||||||
- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release.
|
|
||||||
- Publish the release.
|
|
||||||
|
|
||||||
> You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`.
|
Risk: Gemmy might actually be helpful with writing if you use this mode. We're not responsible for any extra productivity Gemmy might bring.
|
||||||
> The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json`
|
|
||||||
|
|
||||||
## Adding your plugin to the community plugin list
|
|
||||||
|
|
||||||
- Check https://github.com/obsidianmd/obsidian-releases/blob/master/plugin-review.md
|
|
||||||
- Publish an initial version.
|
|
||||||
- Make sure you have a `README.md` file in the root of your repo.
|
|
||||||
- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin.
|
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
- Clone this repo.
|
Install Gemmy. There are four commands in the command palette when you type "Gemmy". You can figure out the rest on your own.
|
||||||
- `npm i` or `yarn` to install dependencies
|
|
||||||
- `npm run dev` to start compilation in watch mode.
|
|
||||||
|
|
||||||
## Manually installing the plugin
|
---
|
||||||
|
|
||||||
- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.
|
Special thanks: [Rigmarole](https://rigmarolestudio.com/) for creating and animating Gemmy
|
||||||
|
|
||||||
## Improve code quality with eslint (optional)
|
Credit: the talented Obsidian mods team, including argentum, CawlinTeffid, cotmax, Eleanor Konik, koala, Leah, rigmarole, ryanjamurphy & WhiteNoise
|
||||||
- [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code.
|
|
||||||
- To use eslint with this project, make sure to install eslint from terminal:
|
|
||||||
- `npm install -g eslint`
|
|
||||||
- To use eslint to analyze this project use this command:
|
|
||||||
- `eslint main.ts`
|
|
||||||
- eslint will then create a report with suggestions for code improvement by file and line number.
|
|
||||||
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder:
|
|
||||||
- `eslint .\src\`
|
|
||||||
|
|
||||||
## Funding URL
|
The code is under MIT license, whereas the Gemmy character is under CC BY 3.0 license.
|
||||||
|
|
||||||
You can include funding URLs where people who use your plugin can financially support it.
|
Obsidian logo is adapted from:
|
||||||
|
|
||||||
The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file:
|
[Crystal shard icon](https://icon-icons.com/icon/Crystal-Shard/88819)
|
||||||
|
By [Chanut is Industries](https://icon-icons.com/users/W52nHhY3W1VlvwyJTwS4d/icon-sets/)
|
||||||
|
License: Attribution 3.0 Unported (CC BY 3.0)
|
||||||
|
|
||||||
```json
|
---
|
||||||
{
|
|
||||||
"fundingUrl": "https://buymeacoffee.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have multiple URLs, you can also do:
|
## Happy April Fools'
|
||||||
|
|
||||||
```json
|
Gemmy was proudly released on April 1, 2023.
|
||||||
{
|
|
||||||
"fundingUrl": {
|
|
||||||
"Buy Me a Coffee": "https://buymeacoffee.com",
|
|
||||||
"GitHub Sponsor": "https://github.com/sponsors",
|
|
||||||
"Patreon": "https://www.patreon.com/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Documentation
|
|
||||||
|
|
||||||
See https://github.com/obsidianmd/obsidian-api
|
|
||||||
|
|
|
||||||
BIN
animations/gemmy_angry.gif
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
animations/gemmy_disappear.gif
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
animations/gemmy_disappoint.gif
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
animations/gemmy_emerge.gif
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
animations/gemmy_idle.gif
Normal file
|
After Width: | Height: | Size: 237 KiB |
BIN
animations/gemmy_lookAround.gif
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
animations/gemmy_pop.gif
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
gemmy_emerge.gif
|
Before Width: | Height: | Size: 467 KiB |
205
main.ts
|
|
@ -1,21 +1,22 @@
|
||||||
import { App, debounce, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
import { App, debounce, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||||
import { NORMAL_BASE64, SPEAKING_BASE64 } from 'resources';
|
import EMERGE_MOTION from './animations/gemmy_emerge.gif';
|
||||||
import EMERGE_MOTION_BASE64 from './gemmy_emerge.gif';
|
import POP_MOTION from './animations/gemmy_pop.gif';
|
||||||
|
import DISAPPEAR_MOTION from './animations/gemmy_disappear.gif';
|
||||||
// Remember to rename these classes and interfaces!
|
import ANGRY_MOTION from './animations/gemmy_angry.gif';
|
||||||
|
import LOOK_MOTION from './animations/gemmy_lookAround.gif'
|
||||||
|
import IDLE_MOTION from './animations/gemmy_idle.gif'
|
||||||
|
import DISAPPOINT_IMG from './animations/gemmy_disappoint.gif'
|
||||||
|
|
||||||
interface GemmySettings {
|
interface GemmySettings {
|
||||||
show: boolean;
|
// how often does Gemmy talk in idle mode, in minutes
|
||||||
// in minutes
|
idleTalkFrequency: number;
|
||||||
talkFrequency: number;
|
|
||||||
// the number of minutes you must write before Gemmy appears to mock you
|
// the number of minutes you must write before Gemmy appears to mock you
|
||||||
writingModeDeadline: number;
|
writingModeGracePeriod: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: GemmySettings = {
|
const DEFAULT_SETTINGS: GemmySettings = {
|
||||||
show: true,
|
idleTalkFrequency: 5,
|
||||||
talkFrequency: 5,
|
writingModeGracePeriod: 5
|
||||||
writingModeDeadline: 0.1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,56 +26,54 @@ const GEMMY_IDLE_QUOTES = [
|
||||||
"You have plugins that you can update!",
|
"You have plugins that you can update!",
|
||||||
"Hi I'm Gemmy! Like Clippy but shinier!",
|
"Hi I'm Gemmy! Like Clippy but shinier!",
|
||||||
"Everything is connected. Everything.",
|
"Everything is connected. Everything.",
|
||||||
`It looks like you’re writing a note.
|
"Can’t decide which note to work on? Try the Random Note core plugin!",
|
||||||
|
"Are you sure you don’t want to upload all your notes just so we can chat?",
|
||||||
Would you like help?
|
"How tall would all your notes be if you stacked them up?",
|
||||||
- Get help with writing the note
|
"Wanna teach me to say things? Find me on GitHub and open a pull request!",
|
||||||
- Just type the note without help
|
"Have you considered using Comic Sans?",
|
||||||
- [ ] Don’t show me this tip again`,
|
"A blank page is just a masterpiece in waiting"
|
||||||
'Can’t decide which note to work on? Try the Random Note core plugin!',
|
|
||||||
'Are you sure you don’t want to upload all your notes so you can talk',
|
|
||||||
'How tall would all your notes be if you stacked them up?'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const WRITING_MODE_QUOTES = [
|
const WRITING_MODE_QUOTES = [
|
||||||
`Is that the best you can do? Keep writing!`,
|
"Is that the best you can do? Keep writing!",
|
||||||
`Write first, editor later.`,
|
"Write first, edit later.",
|
||||||
`I love hearing your keyboard. Don't stop.`,
|
"I love hearing your keyboard. Don't stop.",
|
||||||
`How about we review some old notes today?`,
|
"How about we review some old notes today?",
|
||||||
`Stuck? Try journaling what happened today and see if that gives you inspiration.`,
|
"Stuck? Try journaling what happened today and see if that gives you inspiration.",
|
||||||
`Maybe it's time to go get some water or coffee.`,
|
"Maybe it's time to go get some water or coffee.",
|
||||||
`Anything is better than a blank page, even me. Write something!`
|
"Anything is better than a blank page, even me. Write something!",
|
||||||
|
"Oh, taking a break? Don’t let the ideas get cold!",
|
||||||
|
"What’s this? Writer’s block already?",
|
||||||
|
"You’ve got this! Every keystroke is a step closer to brilliance.",
|
||||||
|
"You’re doing amazing! Keep the momentum going.",
|
||||||
|
"Bold choice to open Obsidian and then do absolutely nothing.",
|
||||||
|
"Is this a journal or a staring contest with the cursor?",
|
||||||
|
"Hmm, I didn’t realize staring at the screen was a new note-taking strategy."
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: use settings for these
|
const BUBBLE_DURATION = 5000;
|
||||||
const TALK_FREQUENCY = 5000;
|
|
||||||
const BUBBLE_DURATION = 1000;
|
|
||||||
|
|
||||||
export default class Gemmy extends Plugin {
|
export default class Gemmy extends Plugin {
|
||||||
settings: GemmySettings;
|
settings: GemmySettings;
|
||||||
gemmyEl: HTMLElement;
|
gemmyEl: HTMLElement;
|
||||||
imageEl: HTMLElement;
|
imageEl: HTMLElement;
|
||||||
intervalId: number;
|
|
||||||
inWritingMode: boolean = false;
|
inWritingMode: boolean = false;
|
||||||
|
idleTimeout: number;
|
||||||
writingModeTimeout: number;
|
writingModeTimeout: number;
|
||||||
appeared: boolean = false;
|
appeared: boolean = false;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
|
|
||||||
// TOOD: prettier speech bubbles
|
let gemmyEl = this.gemmyEl = createDiv('gemmy-container');
|
||||||
let gemmyEl = this.gemmyEl = document.body.createDiv('gemmy-container');
|
|
||||||
gemmyEl.setAttribute('aria-label-position', 'top');
|
gemmyEl.setAttribute('aria-label-position', 'top');
|
||||||
gemmyEl.setAttribute('aria-label-delay', '0');
|
gemmyEl.setAttribute('aria-label-delay', '0');
|
||||||
gemmyEl.setAttribute('aria-label-classes', 'gemmy-tooltip');
|
gemmyEl.setAttribute('aria-label-classes', 'gemmy-tooltip');
|
||||||
|
|
||||||
let gemmyImageEl = this.imageEl = gemmyEl.createEl('img', {});
|
this.imageEl = gemmyEl.createEl('img', {});
|
||||||
gemmyImageEl.setAttribute('src', NORMAL_BASE64);
|
|
||||||
|
|
||||||
gemmyEl.hide();
|
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'gemmy:show',
|
id: 'show',
|
||||||
name: 'Show Gemmy',
|
name: 'Show Gemmy',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.appear();
|
this.appear();
|
||||||
|
|
@ -82,7 +81,7 @@ export default class Gemmy extends Plugin {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'gemmy:hide',
|
id: 'hide',
|
||||||
name: 'Hide Gemmy',
|
name: 'Hide Gemmy',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.disappear();
|
this.disappear();
|
||||||
|
|
@ -90,7 +89,7 @@ export default class Gemmy extends Plugin {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'gemmy:enter-writing-mode',
|
id: 'enter-writing-mode',
|
||||||
name: 'Enter writing mode',
|
name: 'Enter writing mode',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.enterWritingMode();
|
this.enterWritingMode();
|
||||||
|
|
@ -98,10 +97,10 @@ export default class Gemmy extends Plugin {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'gemmy:exit-writing-mode',
|
id: 'leave-writing-mode',
|
||||||
name: 'Exit writing mode',
|
name: 'Leave writing mode',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.exitWritingMode();
|
this.leaveWritingMode();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -114,7 +113,7 @@ export default class Gemmy extends Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saySomething(GEMMY_IDLE_QUOTES, true);
|
this.saySomething(GEMMY_IDLE_QUOTES, true);
|
||||||
this.intervalId && clearInterval(this.intervalId);
|
this.idleTimeout && clearTimeout(this.idleTimeout);
|
||||||
|
|
||||||
});
|
});
|
||||||
this.gemmyEl.addEventListener('mouseleave', () => {
|
this.gemmyEl.addEventListener('mouseleave', () => {
|
||||||
|
|
@ -122,11 +121,11 @@ export default class Gemmy extends Plugin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageEl.setAttribute('src', NORMAL_BASE64);
|
this.imageEl.setAttribute('src', IDLE_MOTION);
|
||||||
this.restartIdleInterval();
|
this.startNextIdleTimeout();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.restartIdleInterval();
|
this.startNextIdleTimeout();
|
||||||
|
|
||||||
// debounce editor-change event on workspace
|
// debounce editor-change event on workspace
|
||||||
this.registerEvent(this.app.workspace.on('editor-change', debounce(() => {
|
this.registerEvent(this.app.workspace.on('editor-change', debounce(() => {
|
||||||
|
|
@ -137,31 +136,53 @@ export default class Gemmy extends Plugin {
|
||||||
this.disappear();
|
this.disappear();
|
||||||
this.setWritingModeTimeout();
|
this.setWritingModeTimeout();
|
||||||
}, 500)));
|
}, 500)));
|
||||||
|
|
||||||
|
app.workspace.onLayoutReady(this.appear.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
appear() {
|
appear() {
|
||||||
let { gemmyEl, imageEl } = this;
|
let { gemmyEl, imageEl } = this;
|
||||||
|
|
||||||
imageEl.setAttribute('src', EMERGE_MOTION_BASE64);
|
imageEl.setAttribute('src', EMERGE_MOTION);
|
||||||
setTimeout(() => {
|
|
||||||
imageEl.setAttribute('src', NORMAL_BASE64);
|
// Quicker if we're in writing mode
|
||||||
this.appeared = true;
|
if (this.inWritingMode) {
|
||||||
|
imageEl.setAttribute('src', POP_MOTION);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.appeared = true;
|
||||||
|
|
||||||
if (this.inWritingMode) {
|
|
||||||
this.saySomething(WRITING_MODE_QUOTES, true);
|
this.saySomething(WRITING_MODE_QUOTES, true);
|
||||||
}
|
}, 1800);
|
||||||
}, 4800);
|
}
|
||||||
|
else {
|
||||||
|
imageEl.setAttribute('src', EMERGE_MOTION);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
imageEl.setAttribute('src', IDLE_MOTION);
|
||||||
|
this.appeared = true;
|
||||||
|
}, 3800);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(gemmyEl);
|
||||||
gemmyEl.show();
|
gemmyEl.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
disappear() {
|
disappear() {
|
||||||
|
this.idleTimeout && window.clearTimeout(this.idleTimeout);
|
||||||
|
this.writingModeTimeout && window.clearTimeout(this.writingModeTimeout);
|
||||||
|
|
||||||
|
this.imageEl.setAttribute('src', DISAPPEAR_MOTION);
|
||||||
|
// remote tooltip
|
||||||
this.gemmyEl.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, clientX: 10, clientY: 10 }));
|
this.gemmyEl.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, clientX: 10, clientY: 10 }));
|
||||||
this.gemmyEl.hide();
|
setTimeout(() => {
|
||||||
|
this.gemmyEl.hide();
|
||||||
|
this.appeared = false;
|
||||||
|
}, 1300);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enterWritingMode() {
|
enterWritingMode() {
|
||||||
let { gemmyEl } = this;
|
|
||||||
this.inWritingMode = true;
|
this.inWritingMode = true;
|
||||||
|
|
||||||
this.disappear();
|
this.disappear();
|
||||||
|
|
@ -169,9 +190,9 @@ export default class Gemmy extends Plugin {
|
||||||
this.setWritingModeTimeout();
|
this.setWritingModeTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
exitWritingMode() {
|
leaveWritingMode() {
|
||||||
this.inWritingMode = false;
|
this.inWritingMode = false;
|
||||||
this.appear();
|
this.disappear();
|
||||||
|
|
||||||
window.clearTimeout(this.writingModeTimeout);
|
window.clearTimeout(this.writingModeTimeout);
|
||||||
}
|
}
|
||||||
|
|
@ -187,17 +208,27 @@ export default class Gemmy extends Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appear();
|
this.appear();
|
||||||
}, this.settings.writingModeDeadline * 60000);
|
}, this.settings.writingModeGracePeriod * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
restartIdleInterval() {
|
startNextIdleTimeout() {
|
||||||
this.intervalId = this.registerInterval(window.setInterval(() => {
|
// if the set time is 5 minutes, this will set timeout to be a random time between 4-6 minutes
|
||||||
|
// the range will be 80% - 120%
|
||||||
|
let randomFactor = 0.8 + 0.4 * Math.random();
|
||||||
|
let randomizedTimeout = randomFactor * this.settings.idleTalkFrequency * 60000;
|
||||||
|
|
||||||
|
if (this.idleTimeout) {
|
||||||
|
window.clearTimeout(this.idleTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.idleTimeout = window.setTimeout(() => {
|
||||||
if (this.inWritingMode) {
|
if (this.inWritingMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saySomething(GEMMY_IDLE_QUOTES, false);
|
this.saySomething(GEMMY_IDLE_QUOTES, false);
|
||||||
}, TALK_FREQUENCY));
|
this.startNextIdleTimeout();
|
||||||
|
}, randomizedTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
saySomething(quotes: string[], persistent: boolean) {
|
saySomething(quotes: string[], persistent: boolean) {
|
||||||
|
|
@ -210,18 +241,27 @@ export default class Gemmy extends Plugin {
|
||||||
this.gemmyEl.setAttr('aria-label', randomThing);
|
this.gemmyEl.setAttr('aria-label', randomThing);
|
||||||
this.gemmyEl.setAttr('aria-label-position', 'top');
|
this.gemmyEl.setAttr('aria-label-position', 'top');
|
||||||
this.gemmyEl.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, clientX: 10, clientY: 10 }))
|
this.gemmyEl.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, clientX: 10, clientY: 10 }))
|
||||||
this.imageEl.setAttribute('src', SPEAKING_BASE64);
|
|
||||||
|
if (this.inWritingMode) {
|
||||||
|
this.imageEl.setAttribute('src', ANGRY_MOTION);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.imageEl.setAttribute('src', DISAPPOINT_IMG);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.imageEl.setAttribute('src', LOOK_MOTION);
|
||||||
|
}
|
||||||
|
|
||||||
if (!persistent) {
|
if (!persistent) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.gemmyEl.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, clientX: 10, clientY: 10 }));
|
this.gemmyEl.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, clientX: 10, clientY: 10 }));
|
||||||
this.imageEl.setAttribute('src', NORMAL_BASE64);
|
this.imageEl.setAttribute('src', IDLE_MOTION);
|
||||||
}, BUBBLE_DURATION);
|
}, BUBBLE_DURATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
|
this.disappear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings() {
|
async loadSettings() {
|
||||||
|
|
@ -233,7 +273,6 @@ export default class Gemmy extends Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: proper setting tab
|
|
||||||
class GemmySettingTab extends PluginSettingTab {
|
class GemmySettingTab extends PluginSettingTab {
|
||||||
plugin: Gemmy;
|
plugin: Gemmy;
|
||||||
|
|
||||||
|
|
@ -247,18 +286,28 @@ class GemmySettingTab extends PluginSettingTab {
|
||||||
|
|
||||||
containerEl.empty();
|
containerEl.empty();
|
||||||
|
|
||||||
containerEl.createEl('h2', { text: 'Settings for my awesome plugin.' });
|
new Setting(containerEl)
|
||||||
|
.setName('Idle talk frequency')
|
||||||
|
.setDesc('How often does Gemmy speak when idle, in minutes.')
|
||||||
|
.addSlider(slider => slider
|
||||||
|
.setLimits(5, 60, 5)
|
||||||
|
.setValue(this.plugin.settings.idleTalkFrequency)
|
||||||
|
.setDynamicTooltip()
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.idleTalkFrequency = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}));
|
||||||
|
|
||||||
// new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
// .setName('Setting #1')
|
.setName('Writing mode grace period')
|
||||||
// .setDesc('It\'s a secret')
|
.setDesc('How soon Gemmy starts to get disappointed after you stop tying in writing mode, in seconds.')
|
||||||
// .addText(text => text
|
.addSlider(slider => slider
|
||||||
// .setPlaceholder('Enter your secret')
|
.setLimits(5, 180, 5)
|
||||||
// .setValue(this.plugin.settings.mySetting)
|
.setDynamicTooltip()
|
||||||
// .onChange(async (value) => {
|
.setValue(this.plugin.settings.writingModeGracePeriod)
|
||||||
// console.log('Secret: ' + value);
|
.onChange(async (value) => {
|
||||||
// this.plugin.settings.mySetting = value;
|
this.plugin.settings.writingModeGracePeriod = value;
|
||||||
// await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
// }));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "gemmy",
|
"id": "gemmy",
|
||||||
"name": "Gemmy",
|
"name": "Gemmy",
|
||||||
"version": "0.0.1",
|
"version": "1.0.1",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "2023 April Fool's plugin brought to you by Obsidian",
|
"description": "2023 April Fool's plugin brought to you by Obsidian",
|
||||||
"author": "Obsidian",
|
"author": "Obsidian",
|
||||||
|
|
|
||||||
15
styles.css
|
|
@ -15,6 +15,9 @@ If your plugin does not need CSS, delete this file.
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gemmy-container img {
|
.gemmy-container img {
|
||||||
|
|
@ -23,6 +26,14 @@ If your plugin does not need CSS, delete this file.
|
||||||
border-radius: var(--gemmy-size);
|
border-radius: var(--gemmy-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gemmy-tooltip {
|
.gemmy-container[aria-label]::before {
|
||||||
|
--background-modifier-message: #FFFFCB;
|
||||||
|
content: attr(aria-label);
|
||||||
|
color: #202020;
|
||||||
font-size: var(--font-ui-medium);
|
font-size: var(--font-ui-medium);
|
||||||
}
|
border-radius: 20px;
|
||||||
|
padding: var(--size-4-3) var(--size-4-6);
|
||||||
|
width: 150px;
|
||||||
|
background: var(--background-modifier-message);
|
||||||
|
}
|
||||||
|
|
||||||