├── .eslintignore ├── versions.json ├── src ├── emotions │ ├── locale │ │ ├── Locales.ts │ │ ├── sv │ │ │ └── DefaultEmotions.ts │ │ └── es │ │ │ └── DefaultEmotions.ts │ ├── EmotionSection.ts │ ├── index.ts │ └── DefaultEmotions.ts ├── settings │ ├── PluginSettings.ts │ └── SettingsTab.ts ├── SmileIcon.ts └── Modal.ts ├── emotion-picker.png ├── .editorconfig ├── .gitignore ├── manifest.json ├── tsconfig.json ├── README.md ├── .eslintrc ├── package.json ├── esbuild.config.mjs ├── .github └── workflows │ └── release.yml ├── LICENSE ├── styles.css └── main.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.1": "0.9.12", 3 | "1.0.0": "0.9.7" 4 | } 5 | -------------------------------------------------------------------------------- /src/emotions/locale/Locales.ts: -------------------------------------------------------------------------------- 1 | const locales = ["en", "sv", "es"]; 2 | 3 | export { locales }; 4 | -------------------------------------------------------------------------------- /emotion-picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartungar/obsidian-emotion-picker/HEAD/emotion-picker.png -------------------------------------------------------------------------------- /src/emotions/EmotionSection.ts: -------------------------------------------------------------------------------- 1 | export class EmotionSection { 2 | id: number; 3 | name: string; 4 | color: string; 5 | emotions: string[]; 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | package-lock.json 11 | 12 | # Don't include the compiled main.js file in the repo. 13 | # They should be uploaded to GitHub releases instead. 14 | main.js 15 | 16 | # Exclude sourcemaps 17 | *.map 18 | 19 | # obsidian 20 | data.json 21 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-emotion-picker", 3 | "name": "Emotion Picker", 4 | "version": "1.2.1", 5 | "minAppVersion": "0.12.0", 6 | "description": "A plugin for Obsidian.md that lets you choose an emotion from a list to insert into a note.", 7 | "author": "dartungar", 8 | "authorUrl": "https://dartungar.com", 9 | "fundingUrl": "https://www.paypal.com/paypalme/dartungar", 10 | "isDesktopOnly": false 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "DOM", 14 | "ES5", 15 | "ES6", 16 | "ES7" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Obsidian Emotion Picker 2 | 3 | Paste an emotion / feeling into note, using customizable list of emotions (or sensible default one). 4 | 5 | Click on an emotion to insert it as text at the current cursor position. 6 | 7 | Available options (default values can be set in Settings tab): 8 | 9 | - add comma after (convenient if entering more than one emotion) 10 | - insert as [[link]] 11 | - insert as #tag 12 | - capitalize the word before inserting 13 | 14 | ![Plugin overview](./emotion-picker.png "Plugin overview") 15 | 16 | The primary purpose is to provide an easier way to add entries to daily journal, emotions / mood tracker, etc. 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "parserOptions": { 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "no-unused-vars": "off", 17 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 18 | "@typescript-eslint/ban-ts-comment": "off", 19 | "no-prototype-builtins": "off", 20 | "@typescript-eslint/no-empty-function": "off" 21 | } 22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-emotion-picker", 3 | "version": "0.12.0", 4 | "description": "A plugin for Obsidian.md that lets you choose an emotion from a list to insert into a note.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "node esbuild.config.mjs production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@types/node": "^18.13.0", 15 | "@typescript-eslint/eslint-plugin": "^5.51.0", 16 | "@typescript-eslint/parser": "^5.51.0", 17 | "builtin-modules": "^3.3.0", 18 | "esbuild": "0.17.7", 19 | "obsidian": "^1.1.1", 20 | "tslib": "2.5.0", 21 | "typescript": "4.9.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/emotions/index.ts: -------------------------------------------------------------------------------- 1 | import { EmotionSection } from "./EmotionSection"; 2 | import { DEFAULT_EMOTIONS } from "./DefaultEmotions"; 3 | import { DEFAULT_SWEDISH_EMOTIONS } from "./locale/sv/DefaultEmotions"; 4 | import { DEFAULT_SPANISH_EMOTIONS } from "./locale/es/DefaultEmotions"; 5 | 6 | 7 | 8 | const getDefaultEmotions = (locale: string): EmotionSection[] => { 9 | switch (locale) { 10 | case "sv": 11 | return DEFAULT_SWEDISH_EMOTIONS; 12 | case "es": 13 | return DEFAULT_SPANISH_EMOTIONS; 14 | default: 15 | return DEFAULT_EMOTIONS; 16 | } 17 | }; 18 | 19 | export { 20 | DEFAULT_EMOTIONS, 21 | DEFAULT_SWEDISH_EMOTIONS, 22 | DEFAULT_SPANISH_EMOTIONS, 23 | EmotionSection, 24 | getDefaultEmotions, 25 | }; 26 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = `/* 6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 7 | if you want to view the source, please visit the github repository of this plugin 8 | */ 9 | `; 10 | 11 | const prod = process.argv[2] === "production"; 12 | 13 | esbuild 14 | .build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ["main.ts"], 19 | bundle: true, 20 | external: ["obsidian", "electron", ...builtins], 21 | format: "cjs", 22 | target: "es2016", 23 | logLevel: "info", 24 | sourcemap: prod ? false : "inline", 25 | treeShaking: true, 26 | outfile: "main.js", 27 | }) 28 | .catch(() => process.exit(1)); 29 | -------------------------------------------------------------------------------- /src/settings/PluginSettings.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_EMOTIONS } from "../emotions/index"; 2 | import { EmotionSection } from "../emotions/EmotionSection"; 3 | 4 | export type EmotionPickerLocaleOverride = "en" | string; 5 | 6 | export interface EmotionPickerSettings { 7 | modalHeaderText: string; 8 | useCommaInSeparator: boolean; 9 | addAsLink: boolean; 10 | addAsTag: boolean; 11 | capitalize: boolean; 12 | emotions: EmotionSection[]; 13 | locale: EmotionPickerLocaleOverride; 14 | } 15 | 16 | export class DefaultSettings implements EmotionPickerSettings { 17 | modalHeaderText = "What do you feel?"; 18 | useCommaInSeparator = false; 19 | addAsLink = false; 20 | addAsTag = false; 21 | capitalize = false; 22 | emotions = DEFAULT_EMOTIONS; 23 | locale: "en"; 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "18.x" 19 | 20 | - name: Build plugin 21 | run: | 22 | npm install 23 | npm run build 24 | - name: Create release 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | run: | 28 | tag="${{ github.ref_name }}" 29 | gh release create "$tag" \ 30 | --title="$tag" \ 31 | --draft \ 32 | main.js manifest.json styles.css -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 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. -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | 2 | /* settings */ 3 | .emotion-section-setting { 4 | display: block; 5 | } 6 | 7 | .emotion-section-setting > .setting-item-control { 8 | display: flex; 9 | flex-direction: row; 10 | align-items: flex-start; 11 | justify-content: flex-start; 12 | } 13 | 14 | .emotion-section-setting > .setting-item-control > span { 15 | margin-right: 5px; 16 | } 17 | 18 | .emotion-section-setting > .setting-item-control > input, .emotion-section-setting > .setting-item-control > textarea { 19 | margin-right: 30px; 20 | } 21 | 22 | .emotion-section-setting textarea { 23 | max-height: 200px; 24 | } 25 | 26 | /* modal */ 27 | /* override default Modal styles */ 28 | .modal { 29 | min-width: 230px !important; 30 | } 31 | 32 | .modal-body > div { 33 | display: flex; 34 | flex-direction: row; 35 | flex-wrap: wrap; 36 | justify-content: space-between; 37 | } 38 | .toggles-section { 39 | justify-content: flex-start !important; 40 | } 41 | 42 | .emotion-toggle-label { 43 | position: relative; 44 | padding-left: 5px; 45 | padding-right: 30px; 46 | bottom: 5px; 47 | } 48 | 49 | .emotion-section { 50 | padding: 0.5rem; 51 | width: 130px; 52 | } 53 | 54 | .emotion-element { 55 | text-decoration: underline solid 1px; 56 | cursor: pointer; 57 | transition: linear 0.1s; 58 | } 59 | 60 | .emotion-element:hover { 61 | /* text-decoration: underline solid 1px; */ 62 | font-size: 103%; 63 | text-decoration: underline solid 2px; 64 | } 65 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "obsidian"; 2 | import { DefaultSettings, EmotionPickerSettings } from "src/settings/PluginSettings"; 3 | import { addSmileIcon } from "src/SmileIcon"; 4 | import { EmotionPickerModal } from "./src/Modal"; 5 | import { EmotionPickerSettingsTab } from "src/settings/SettingsTab"; 6 | import { EmotionSection, getDefaultEmotions } from "src/emotions"; 7 | 8 | export default class EmotionPickerPlugin extends Plugin { 9 | settings: EmotionPickerSettings; 10 | 11 | async onload() { 12 | // add new icon for ribbon item 13 | addSmileIcon(); 14 | 15 | this.addRibbonIcon("smile", "Emotions Picker", (evt: MouseEvent) => { 16 | new EmotionPickerModal(this.app, this).open(); 17 | }); 18 | 19 | this.addCommand({ 20 | id: "Open", 21 | name: "Open", 22 | callback: () => { 23 | new EmotionPickerModal(this.app, this).open(); 24 | }, 25 | }); 26 | 27 | await this.loadSettings(); 28 | 29 | this.addSettingTab(new EmotionPickerSettingsTab(this.app, this)); 30 | } 31 | 32 | onunload() {} 33 | 34 | async loadSettings() { 35 | const loadedSettings = await this.loadData(); 36 | this.settings = Object.assign( 37 | {}, 38 | new DefaultSettings(), 39 | loadedSettings 40 | ); 41 | } 42 | 43 | async saveSettings() { 44 | await this.saveData(this.settings); 45 | } 46 | 47 | getEmotionsOrDefault(): EmotionSection[] { 48 | if (this.settings.emotions && this.settings.emotions.length > 0) 49 | return this.settings.emotions; 50 | return getDefaultEmotions(this.settings.locale); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/SmileIcon.ts: -------------------------------------------------------------------------------- 1 | import { addIcon } from "obsidian"; 2 | 3 | export function addSmileIcon(): void { 4 | addIcon( 5 | "smile", 6 | ` 10 | 14 | 17 | ` 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/emotions/locale/sv/DefaultEmotions.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_SWEDISH_EMOTIONS = [ 2 | { 3 | id: 1, 4 | name: "Glädje", 5 | color: "#c1e08d", 6 | emotions: [ 7 | "glad", 8 | "nöjd", 9 | "belåten", 10 | "tillfredsställd", 11 | "lycklig", 12 | "underhållen", 13 | "förtjust", 14 | "munter", 15 | "jovial", 16 | "lycklig", 17 | "stolt", 18 | "segervisst", 19 | "optimistisk", 20 | "ivrig", 21 | "hoppfull", 22 | "entusiastisk", 23 | "upphetsad", 24 | "euforisk", 25 | "besvärjad", 26 | ], 27 | }, 28 | { 29 | id: 2, 30 | name: "Kärleksfull", 31 | color: "#e8c3da", 32 | emotions: [ 33 | "kärleksfull", 34 | "romantisk", 35 | "tillgiven", 36 | "passionerad", 37 | "attraherad", 38 | "sentimental", 39 | "medlidsam", 40 | "tillfredsställd", 41 | "fredfull", 42 | "lättad", 43 | ], 44 | }, 45 | { 46 | id: 3, 47 | name: "Överraskad", 48 | color: "#94d4d3", 49 | emotions: [ 50 | "förvånad", 51 | "chockerad", 52 | "bedrövad", 53 | "förvirrad", 54 | "besviken", 55 | "förvirrad", 56 | "häpna", 57 | "häpnadsväckande", 58 | "rörde", 59 | "rörde", 60 | ], 61 | }, 62 | { 63 | id: 4, 64 | name: "Illa", 65 | color: "#b84444", 66 | emotions: [ 67 | "arg", 68 | "rasande", 69 | "hatfull", 70 | "upprörd", 71 | "frustrerad", 72 | "irriterad", 73 | "frustrerad", 74 | "avundsjuk", 75 | "svartsjuk", 76 | "äcklad", 77 | "föraktfull", 78 | ], 79 | }, 80 | { 81 | id: 5, 82 | name: "Sorg", 83 | color: "#4e72a3", 84 | emotions: [ 85 | "ledsen", 86 | "skadad", 87 | "plågsam", 88 | "deprimerad", 89 | "bedrövad", 90 | "besviken", 91 | "missnöjd", 92 | "skamsen", 93 | "ångerfull", 94 | "skyldig", 95 | "negligerad", 96 | "isolerad", 97 | "ensam", 98 | "sörjande", 99 | "maktlös", 100 | ], 101 | }, 102 | { 103 | id: 6, 104 | name: "Rädsla", 105 | color: "#dbab4b", 106 | emotions: [ 107 | "rädd", 108 | "maktlös", 109 | "panik", 110 | "hystetisk", 111 | "osäker", 112 | "underlägsen", 113 | "inkompetent", 114 | "nervös", 115 | "ångestfull", 116 | "bekymrad", 117 | "hemsk", 118 | "förskräcklig", 119 | ], 120 | }, 121 | ]; 122 | -------------------------------------------------------------------------------- /src/emotions/DefaultEmotions.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_EMOTIONS = [ 2 | { 3 | id: 1, 4 | name: "Joy", 5 | color: "#c1e08d", 6 | emotions: [ 7 | "joyful", 8 | "content", 9 | "pleased", 10 | "satisfied", 11 | "happy", 12 | "amused", 13 | "delighted", 14 | "cheerful", 15 | "jovial", 16 | "blissful", 17 | "proud", 18 | "triumphant", 19 | "optimistic", 20 | "eager", 21 | "hopeful", 22 | "enthusiastic", 23 | "excited", 24 | "euphoric", 25 | "enchanted", 26 | ], 27 | }, 28 | { 29 | id: 2, 30 | name: "Love", 31 | color: "#e8c3da", 32 | emotions: [ 33 | "loving", 34 | "romantic", 35 | "affectionate", 36 | "passionate", 37 | "attracted", 38 | "sentimental", 39 | "compassionate", 40 | "satisfied", 41 | "peaceful", 42 | "relieved", 43 | ], 44 | }, 45 | { 46 | id: 3, 47 | name: "Surprise", 48 | color: "#94d4d3", 49 | emotions: [ 50 | "surprised", 51 | "shocked", 52 | "dismayed", 53 | "confused", 54 | "disillusioned", 55 | "perplexed", 56 | "amazed", 57 | "astonished", 58 | "moved", 59 | "touched", 60 | ], 61 | }, 62 | { 63 | id: 4, 64 | name: "Anger", 65 | color: "#b84444", 66 | emotions: [ 67 | "angry", 68 | "enraged", 69 | "hateful", 70 | "hostile", 71 | "agitated", 72 | "frustrated", 73 | "irritated", 74 | "annoyed", 75 | "aggravated", 76 | "envious", 77 | "jealous", 78 | "disgusted", 79 | "contemptful", 80 | ], 81 | }, 82 | { 83 | id: 5, 84 | name: "Sadness", 85 | color: "#4e72a3", 86 | emotions: [ 87 | "sad", 88 | "hurt", 89 | "agonizing", 90 | "depressed", 91 | "sorrowful", 92 | "disappointed", 93 | "dismayed", 94 | "displeased", 95 | "shameful", 96 | "regretful", 97 | "guilty", 98 | "neglected", 99 | "isolated", 100 | "lonely", 101 | "despaired", 102 | "grieving", 103 | "powerless", 104 | ], 105 | }, 106 | { 107 | id: 6, 108 | name: "Fear", 109 | color: "#dbab4b", 110 | emotions: [ 111 | "fearful", 112 | "scared", 113 | "helpless", 114 | "frightened", 115 | "panicking", 116 | "hystetical", 117 | "insecure", 118 | "inferior", 119 | "inadequate", 120 | "nervous", 121 | "anxious", 122 | "worried", 123 | "dreadful", 124 | "mortified", 125 | ], 126 | }, 127 | ]; 128 | -------------------------------------------------------------------------------- /src/emotions/locale/es/DefaultEmotions.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_SPANISH_EMOTIONS = [ 2 | { 3 | id: 1, 4 | name: "Alegría", 5 | color: "#c1e08d", 6 | emotions: [ 7 | "alegre", 8 | "contento", 9 | "gozoso", 10 | "satisfecho", 11 | "feliz", 12 | "divertido", 13 | "encantado", 14 | "jovial", 15 | "dichoso", 16 | "orgulloso", 17 | "triunfante", 18 | "optimista", 19 | "ansioso", 20 | "esperanzado", 21 | "entusiasmado", 22 | "excitado", 23 | "eufórico", 24 | ], 25 | }, 26 | { 27 | id: 2, 28 | name: "Amor", 29 | color: "#e8c3da", 30 | emotions: [ 31 | "amoroso", 32 | "romántico", 33 | "afectuoso", 34 | "apasionado", 35 | "atraído", 36 | "sentimental", 37 | "compasivo", 38 | "satisfecho", 39 | "pacífico", 40 | "aliviado", 41 | ], 42 | }, 43 | { 44 | id: 3, 45 | name: "Sorpresa", 46 | color: "#94d4d3", 47 | emotions: [ 48 | "sorprendido", 49 | "conmocionado", 50 | "consternado", 51 | "confundido", 52 | "desilusionado", 53 | "perplejo", 54 | "asombrado", 55 | "atónito", 56 | "conmovido", 57 | "tocado", 58 | ], 59 | }, 60 | { 61 | id: 4, 62 | name: "Rabia", 63 | color: "#b84444", 64 | emotions: [ 65 | "enojado", 66 | "enfurecido", 67 | "odioso", 68 | "hostil", 69 | "agitado", 70 | "frustrado", 71 | "irritado", 72 | "agravado", 73 | "envidioso", 74 | "celoso", 75 | "disgustado", 76 | "despreciativo", 77 | ], 78 | }, 79 | { 80 | id: 5, 81 | name: "Tristeza", 82 | color: "#4e72a3", 83 | emotions: [ 84 | "triste", 85 | "herido", 86 | "agonizante", 87 | "deprimido", 88 | "doloroso", 89 | "decepcionado", 90 | "consternado", 91 | "disgustado", 92 | "vergonzoso", 93 | "arrepentido", 94 | "culpable", 95 | "abandonado", 96 | "aislado", 97 | "solitario", 98 | "desesperado", 99 | "afligido", 100 | "impotente", 101 | ], 102 | }, 103 | { 104 | id: 6, 105 | name: "Miedo", 106 | color: "#dbab4b", 107 | emotions: [ 108 | "temeroso", 109 | "asustado", 110 | "impotente", 111 | "atemorizado", 112 | "en pánico", 113 | "histérico", 114 | "inseguro", 115 | "inferior", 116 | "inadecuado", 117 | "nervioso", 118 | "ansioso", 119 | "preocupado", 120 | "terrible", 121 | "mortificado", 122 | ], 123 | }, 124 | ]; 125 | -------------------------------------------------------------------------------- /src/Modal.ts: -------------------------------------------------------------------------------- 1 | import EmotionPickerPlugin from "main"; 2 | import { 3 | App, 4 | Modal, 5 | MarkdownView, 6 | Notice, 7 | EditorPosition, 8 | Editor, 9 | } from "obsidian"; 10 | import { EmotionSection } from "./emotions/EmotionSection"; 11 | import { EmotionPickerSettings } from "./settings/PluginSettings"; 12 | 13 | export class EmotionPickerModal extends Modal { 14 | app: App; 15 | plugin: EmotionPickerPlugin; 16 | content: HTMLElement; 17 | emotions: EmotionSection[]; 18 | editor: Editor; 19 | initialCursorPosition: EditorPosition; 20 | locale: string; 21 | // current settings 22 | state: EmotionPickerSettings; 23 | 24 | constructor(app: App, plugin: EmotionPickerPlugin) { 25 | super(app); 26 | this.app = app; 27 | this.plugin = plugin; 28 | this.locale = this.plugin.settings.locale; 29 | this.emotions = plugin.getEmotionsOrDefault(); 30 | } 31 | 32 | onOpen() { 33 | const { contentEl } = this; 34 | this.state = this.plugin.settings; 35 | contentEl.classList.add("modal-body"); 36 | 37 | this.generateHeading(); 38 | this.generateToggles(); 39 | this.generateContentFromEmotions(); 40 | 41 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 42 | if (view) { 43 | this.editor = view.editor; 44 | this.initialCursorPosition = this.editor.getCursor(); 45 | } else { 46 | new Notice(`Error getting cursor position.`); 47 | this.close(); 48 | } 49 | } 50 | 51 | onClose() { 52 | const { contentEl } = this; 53 | contentEl.empty(); 54 | this.initialCursorPosition = undefined; 55 | } 56 | 57 | generateHeading(): void { 58 | const headingEl = this.contentEl.createEl("h3"); 59 | headingEl.innerText = this.plugin.settings.modalHeaderText; 60 | } 61 | 62 | generateToggles(): void { 63 | const togglesEl = this.contentEl.createDiv(); 64 | togglesEl.addClass("toggles-section"); 65 | 66 | const useCommaToggleEl = this.generateToggleElement( 67 | togglesEl, 68 | "add comma after", 69 | this.state.useCommaInSeparator 70 | ); 71 | useCommaToggleEl.onClickEvent(() => { 72 | this.state.useCommaInSeparator = !this.state.useCommaInSeparator; 73 | }); 74 | 75 | const addAsLinkToggleEl = this.generateToggleElement( 76 | togglesEl, 77 | "add as [[link]]", 78 | this.state.addAsLink 79 | ); 80 | addAsLinkToggleEl.onClickEvent(() => { 81 | this.state.addAsLink = !this.state.addAsLink; 82 | }); 83 | 84 | const addAsTagToggleEl = this.generateToggleElement( 85 | togglesEl, 86 | "add as #tag", 87 | this.state.addAsTag 88 | ); 89 | addAsTagToggleEl.onClickEvent(() => { 90 | this.state.addAsTag = !this.state.addAsTag; 91 | }); 92 | } 93 | 94 | generateContentFromEmotions(): void { 95 | const contentEl = this.contentEl.createDiv(); 96 | 97 | this.emotions.forEach((section) => { 98 | this.generateElementFromEmotionSection(section, contentEl); 99 | }); 100 | } 101 | 102 | generateElementFromEmotionSection( 103 | section: EmotionSection, 104 | baseEl: HTMLElement 105 | ): void { 106 | const sectionEl = baseEl.createDiv(); 107 | sectionEl.classList.add("emotion-section"); 108 | 109 | const heading = sectionEl.createEl("h4"); 110 | heading.innerText = section.name; 111 | heading.style.color = section.color; 112 | heading.style.fontWeight = "bold"; 113 | 114 | section.emotions.forEach((emotionString) => { 115 | const emotionEl = sectionEl.createDiv(); 116 | emotionEl.innerHTML = emotionString; 117 | emotionEl.style.textDecorationColor = section.color; 118 | emotionEl.classList.add("emotion-element"); 119 | emotionEl.onClickEvent(() => { 120 | this.insertText(this.getFinalText(emotionString)); 121 | }); 122 | }); 123 | } 124 | 125 | insertText(text: string): void { 126 | if (this.editor) { 127 | this.editor.replaceRange(text, this.initialCursorPosition); 128 | this.initialCursorPosition.ch += text.length; 129 | } 130 | 131 | new Notice(`Inserted '${text}'.`); 132 | } 133 | 134 | generateToggleElement( 135 | baseEl: HTMLElement, 136 | text: string, 137 | initialState = false 138 | ): HTMLDivElement { 139 | const toggleContainerEl = baseEl.createDiv(); 140 | 141 | const toggleEl = toggleContainerEl.createDiv(); 142 | toggleEl.classList.add("checkbox-container"); 143 | toggleEl.onClickEvent(() => toggleEl.classList.toggle("is-enabled")); 144 | 145 | if (initialState == true) { 146 | toggleEl.classList.add("is-enabled"); 147 | } 148 | 149 | const labelEl = toggleContainerEl.createEl("span"); 150 | labelEl.classList.add("emotion-toggle-label"); 151 | labelEl.textContent = text; 152 | 153 | return toggleContainerEl; 154 | } 155 | 156 | getFinalText(text: string): string { 157 | if (this.state.capitalize) text = this.capitalize(text); 158 | if (this.state.addAsTag) text = `#${text}`; 159 | if (this.state.addAsLink) text = `[[${text}]]`; 160 | if (this.state.useCommaInSeparator) text = text + ", "; 161 | text = " " + text; 162 | return text; 163 | } 164 | 165 | capitalize(text: string): string { 166 | return text.charAt(0).toUpperCase() + text.slice(1); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/settings/SettingsTab.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting } from "obsidian"; 2 | import { locales } from "../emotions/locale/Locales"; 3 | import EmotionPickerPlugin from "../../main"; 4 | import { EmotionPickerLocaleOverride } from "./PluginSettings"; 5 | import { getDefaultEmotions } from "src/emotions"; 6 | export class EmotionPickerSettingsTab extends PluginSettingTab { 7 | plugin: EmotionPickerPlugin; 8 | 9 | constructor(app: App, plugin: EmotionPickerPlugin) { 10 | super(app, plugin); 11 | this.plugin = plugin; 12 | this.plugin.settings.emotions = plugin.getEmotionsOrDefault(); 13 | } 14 | 15 | refresh(): void { 16 | this.display(); 17 | } 18 | 19 | display(): void { 20 | const { containerEl } = this; 21 | 22 | containerEl.empty(); 23 | 24 | containerEl.createEl("h2", { text: "Emotion Picker Settings" }); 25 | 26 | // TODO: customize modal header 27 | 28 | new Setting(containerEl) 29 | .setName("Modal header text") 30 | .setDesc("Customize modal header text") 31 | .addText((textField) => { 32 | textField.setValue(this.plugin.settings.modalHeaderText); 33 | textField.onChange((newValue) => { 34 | this.plugin.settings.modalHeaderText = newValue; 35 | this.plugin.saveSettings(); 36 | }); 37 | }); 38 | 39 | new Setting(containerEl) 40 | .setName("Add comma after") 41 | .setDesc("Add a comma after pasted emotion") 42 | .addToggle((toggle) => 43 | toggle 44 | .setValue(this.plugin.settings.useCommaInSeparator) 45 | .onChange(async (value) => { 46 | this.plugin.settings.useCommaInSeparator = value; 47 | await this.plugin.saveSettings(); 48 | }) 49 | ); 50 | 51 | new Setting(containerEl) 52 | .setName("Insert as link") 53 | .setDesc("Insert emotion as a [[link]]") 54 | .addToggle((toggle) => 55 | toggle 56 | .setValue(this.plugin.settings.addAsLink) 57 | .onChange(async (value) => { 58 | this.plugin.settings.addAsLink = value; 59 | await this.plugin.saveSettings(); 60 | }) 61 | ); 62 | 63 | new Setting(containerEl) 64 | .setName("Insert as tag") 65 | .setDesc("Insert emotion as a #tag") 66 | .addToggle((toggle) => 67 | toggle 68 | .setValue(this.plugin.settings.addAsTag) 69 | .onChange(async (value) => { 70 | this.plugin.settings.addAsTag = value; 71 | await this.plugin.saveSettings(); 72 | }) 73 | ); 74 | 75 | new Setting(containerEl) 76 | .setName("Capitalize") 77 | .setDesc("Capitalize (useful if inserting emotion as link or tag)") 78 | .addToggle((toggle) => 79 | toggle 80 | .setValue(this.plugin.settings.capitalize) 81 | .onChange(async (value) => { 82 | this.plugin.settings.capitalize = value; 83 | await this.plugin.saveSettings(); 84 | }) 85 | ); 86 | 87 | new Setting(this.containerEl) 88 | .setName("Override locale:") 89 | .setDesc( 90 | "Set this if you want to use a locale different from the default. \nWARNING: changing locale will erase your custom emotions for current locale!" 91 | ) 92 | .addDropdown((dropdown) => { 93 | locales.forEach((locale) => { 94 | dropdown.addOption(locale, locale); 95 | }); 96 | dropdown.setValue(this.plugin.settings.locale); 97 | dropdown.onChange(async (value) => { 98 | const isLocaleChanged = value !== this.plugin.settings.locale; 99 | if (isLocaleChanged) { 100 | this.plugin.settings.locale = value as EmotionPickerLocaleOverride; 101 | this.plugin.settings.emotions = getDefaultEmotions(value); 102 | await this.plugin.saveSettings(); 103 | this.refresh(); 104 | } 105 | }); 106 | }); 107 | 108 | containerEl.createEl("h3", { text: "Emotion groups" }); 109 | 110 | // TODO: refresh settings page after saving 111 | 112 | this.plugin.settings.emotions.forEach((es) => { 113 | const setting = new Setting(containerEl); 114 | setting.setClass("emotion-section-setting"); 115 | 116 | const groupNameLabel = createEl("span", { text: "Name: " }); 117 | setting.controlEl.appendChild(groupNameLabel); 118 | 119 | setting.addText((textField) => { 120 | textField 121 | .setValue(es.name) 122 | .setPlaceholder("group name") 123 | .onChange((value) => { 124 | es.name = value; 125 | this.plugin.saveSettings(); 126 | }); 127 | }); 128 | 129 | const colorPickerLabel = createEl("span", { text: "Color: " }); 130 | setting.controlEl.appendChild(colorPickerLabel); 131 | 132 | const colorPicker = createEl( 133 | "input", 134 | { 135 | type: "color", 136 | cls: "settings-color-picker", 137 | }, 138 | (el) => { 139 | el.value = es.color; 140 | el.onchange = () => { 141 | es.color = el.value; 142 | this.plugin.saveSettings(); 143 | }; 144 | } 145 | ); 146 | 147 | setting.controlEl.appendChild(colorPicker); 148 | 149 | const emotionsListLabel = createEl("span", { 150 | text: "Emotions:", 151 | }); 152 | setting.controlEl.appendChild(emotionsListLabel); 153 | 154 | setting.addTextArea((textArea) => { 155 | textArea.inputEl.setAttribute( 156 | "rows", 157 | es.emotions.length.toString() 158 | ); 159 | 160 | textArea 161 | .setPlaceholder("emotions, separated by newline or comma.") 162 | .setValue(es.emotions.join("\n")) 163 | .onChange(async (value) => { 164 | es.emotions = splitByCommaAndNewline(value); 165 | this.plugin.saveSettings(); 166 | }); 167 | }); 168 | 169 | setting.addButton((btn) => { 170 | btn.setButtonText("delete group"); 171 | btn.setClass("settings-delete-btn"); 172 | btn.onClick(() => { 173 | this.plugin.settings.emotions = 174 | this.plugin.settings.emotions.filter( 175 | (e) => e.id !== es.id 176 | ); 177 | this.plugin.saveSettings(); 178 | this.refresh(); 179 | }); 180 | }); 181 | }); 182 | 183 | new Setting(containerEl).addButton((btn) => { 184 | btn.setButtonText("Add group"); 185 | btn.onClick(() => { 186 | this.plugin.settings.emotions.push({ 187 | id: 188 | Math.max( 189 | ...this.plugin.settings.emotions.map((e) => e.id) 190 | ) + 1, // id "generation" 191 | name: "New section", 192 | emotions: [], 193 | color: "#000000", 194 | }); 195 | this.plugin.saveSettings(); 196 | this.refresh(); 197 | }); 198 | }); 199 | } 200 | } 201 | 202 | function splitByCommaAndNewline(rawEmotions: string): string[] { 203 | const splitted: string[] = []; 204 | 205 | rawEmotions 206 | .split("\n") 207 | .forEach((s) => s.split(",").forEach((sp) => splitted.push(sp.trim()))); 208 | 209 | return splitted; 210 | } 211 | 212 | --------------------------------------------------------------------------------