├── .gitignore ├── README.md ├── dist ├── main.js ├── manifest.json └── styles.css ├── manifest.json ├── package.json ├── rollup.config.js ├── screenshots └── 01.png ├── src ├── DataMuseProvider.ts ├── POSProvider.ts ├── PowerThesaurusProvider.ts ├── main.ts ├── popover.css ├── popover.ts └── types.ts ├── tsconfig.json ├── versions.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | package-lock.json 8 | 9 | # build 10 | *.js.map 11 | 12 | # obsidian 13 | data.json 14 | .env 15 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Obsidian Power Thesaurus Plugin 2 | 3 | Look up synonyms for highlighted english words using [https://www.powerthesaurus.org/](https://www.powerthesaurus.org/). 4 | 5 | Plugin screenshot 1 -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP 3 | if you want to view the source visit the plugins github repository 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var obsidian = require('obsidian'); 9 | 10 | /*! ***************************************************************************** 11 | Copyright (c) Microsoft Corporation. 12 | 13 | Permission to use, copy, modify, and/or distribute this software for any 14 | purpose with or without fee is hereby granted. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 17 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 18 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 19 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 20 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 21 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 22 | PERFORMANCE OF THIS SOFTWARE. 23 | ***************************************************************************** */ 24 | 25 | function __awaiter(thisArg, _arguments, P, generator) { 26 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 27 | return new (P || (P = Promise))(function (resolve, reject) { 28 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 29 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 30 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 31 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 32 | }); 33 | } 34 | 35 | /** 36 | * Transform the case in `value` (`string`) to match that of `base` (`string`). 37 | * 38 | * @param {string} value 39 | * @param {string} base 40 | * @returns {string} 41 | */ 42 | function matchCasing(value, base) { 43 | var index = -1; 44 | /** @type {string} */ 45 | var char; 46 | /** @type {string} */ 47 | var rest; 48 | /** @type {boolean} */ 49 | var cap; 50 | 51 | if (base.toUpperCase() === base) { 52 | return value.toUpperCase() 53 | } 54 | 55 | if (base.toLowerCase() === base) { 56 | return value.toLowerCase() 57 | } 58 | 59 | while (++index < base.length) { 60 | char = base.charAt(index); 61 | 62 | if (char.toUpperCase() !== char.toLowerCase()) { 63 | rest = base.slice(index + 1); 64 | cap = char === char.toUpperCase() && rest === rest.toLowerCase(); 65 | break 66 | } 67 | } 68 | 69 | if (cap) { 70 | index = -1; 71 | 72 | while (++index < value.length) { 73 | char = value.charAt(index).toUpperCase(); 74 | 75 | if (char !== char.toLowerCase()) { 76 | return ( 77 | value.slice(0, index) + char + value.slice(index + 1).toLowerCase() 78 | ) 79 | } 80 | } 81 | } 82 | 83 | return value 84 | } 85 | 86 | function repositionWidget(widget, coords) { 87 | const height = widget.clientHeight; 88 | const width = widget.clientWidth; 89 | if (coords.bottom + height > window.innerHeight) { 90 | widget.style.setProperty("top", `${coords.top - height}px`); 91 | } 92 | if (coords.left + width > window.innerWidth) { 93 | widget.style.setProperty("left", `${window.innerWidth - width - 15}px`); 94 | } 95 | } 96 | function constructThesaurusPopover({ list, editor, selection, }) { 97 | let isDestroyed = false; 98 | const widget = createDiv({ cls: "pt-select" }, (div) => { 99 | list.forEach((synonym, i) => { 100 | div.createDiv({ cls: "pt-select-option" }, (option) => { 101 | var _a; 102 | option.dataset.index = i.toString(); 103 | option.createDiv({ cls: "pt-select-label" }, (label) => { 104 | var _a; 105 | label.createDiv({ cls: "pt-term", text: synonym.term }); 106 | if ((_a = synonym.partsOfSpeech) === null || _a === void 0 ? void 0 : _a.length) { 107 | label.createDiv({ 108 | cls: "pt-meta-pos", 109 | text: synonym.partsOfSpeech.join(", "), 110 | }); 111 | } 112 | }); 113 | if ((_a = synonym.tags) === null || _a === void 0 ? void 0 : _a.length) { 114 | option.createDiv({ 115 | cls: "pt-meta-tags", 116 | text: synonym.tags.join(", "), 117 | }); 118 | } 119 | }); 120 | }); 121 | }); 122 | const clickOutsideHandler = (e) => { 123 | const isTargetInWidget = e.target instanceof Node && widget.contains(e.target); 124 | if (!isTargetInWidget) { 125 | selfDestruct(); 126 | } 127 | }; 128 | const escHandler = (e) => { 129 | if (e.key === "Escape") { 130 | selfDestruct(); 131 | } 132 | }; 133 | const selfDestruct = () => { 134 | if (isDestroyed) 135 | return; 136 | isDestroyed = true; 137 | widget.remove(); 138 | document.body.removeEventListener("pointerdown", clickOutsideHandler); 139 | document.removeEventListener("keydown", escHandler); 140 | }; 141 | let coords; 142 | if (editor.cursorCoords) { 143 | coords = editor.cursorCoords(true, "window"); 144 | } 145 | else if (editor.coordsAtPos) { 146 | const offset = editor.posToOffset(editor.getCursor("from")); 147 | coords = editor.coordsAtPos(offset); 148 | } 149 | else { 150 | return; 151 | } 152 | widget.on("click", ".pt-select-option", (e, el) => { 153 | editor.replaceSelection(matchCasing(list[parseInt(el.dataset.index)].term, selection)); 154 | selfDestruct(); 155 | }); 156 | widget.style.left = `${coords.left}px`; 157 | widget.style.top = `${coords.bottom}px`; 158 | document.body.appendChild(widget); 159 | repositionWidget(widget, coords); 160 | document.body.addEventListener("pointerdown", clickOutsideHandler); 161 | document.addEventListener("keydown", escHandler); 162 | return selfDestruct; 163 | } 164 | 165 | const TEMP_LANG = 'en'; 166 | function getURL(sentence, lang) { 167 | return `https://systran-systran-platform-for-language-processing-v1.p.rapidapi.com/nlp/morphology/extract/pos?input=${encodeURIComponent(sentence)}&lang=${lang}`; 168 | } 169 | function getPOS(line, termIndex) { 170 | var _a; 171 | return __awaiter(this, void 0, void 0, function* () { 172 | const sentences = line.split(/[.!?]/g); 173 | let seen = 0; 174 | for (let sentence of sentences) { 175 | if (seen <= termIndex && termIndex <= seen + sentence.length) { 176 | const result = yield fetch(getURL(sentence, TEMP_LANG), { 177 | "method": "GET", 178 | "headers": { 179 | "x-rapidapi-key": "5e1aff4a8dmsh29e6efbd0a66e0ep1c9b55jsn31b784e15a17", 180 | "x-rapidapi-host": "systran-systran-platform-for-language-processing-v1.p.rapidapi.com" 181 | } 182 | }); 183 | const json = yield result.json(); 184 | const wordIndex = termIndex - seen; 185 | const match = (_a = json.partsOfSpeech) === null || _a === void 0 ? void 0 : _a.find(pos => pos.start === wordIndex); 186 | if (match) { 187 | const posStr = match.pos.split('/')[1]; 188 | if (posStr.startsWith('noun')) 189 | return 2; 190 | if (posStr.startsWith('verb')) 191 | return 6; 192 | if (posStr.startsWith('adj')) 193 | return 1; 194 | if (posStr.startsWith('adv')) 195 | return 4; 196 | } 197 | break; 198 | } 199 | seen += sentence.length + 1; 200 | } 201 | return null; 202 | }); 203 | } 204 | 205 | const apiUrl = "https://api.powerthesaurus.org/"; 206 | const POS_MAP = { 207 | 1: "adj", 208 | 2: "n", 209 | 4: "adv", 210 | 6: "v", 211 | 7: "int", 212 | 9: "conj", 213 | 10: "prep", 214 | }; 215 | class PowerThesaurusProvider { 216 | static appendAttribution(containerEl, selection) { 217 | containerEl.createDiv({ cls: "pt-link-option" }, (div) => { 218 | div.createEl("a", { 219 | cls: "external-link", 220 | text: "See more results", 221 | href: `https://www.powerthesaurus.org/${selection.toLowerCase()}/synonyms`, 222 | }, (a) => { 223 | a.target = "_blank"; 224 | }); 225 | }); 226 | containerEl.createDiv({ cls: "pt-link-option" }, (div) => { 227 | div.createEl("a", { 228 | cls: "external-link pt-powered-by", 229 | text: "Powered by power thesaurus", 230 | href: `https://www.powerthesaurus.org/`, 231 | }, (a) => { 232 | a.target = "_blank"; 233 | }); 234 | }); 235 | } 236 | getSynonyms(term, line, termIndex) { 237 | var _a, _b, _c, _d; 238 | return __awaiter(this, void 0, void 0, function* () { 239 | try { 240 | const pos = yield getPOS(line, termIndex); 241 | const termIdResult = yield getTermId(term); 242 | const terms = (_b = (_a = termIdResult === null || termIdResult === void 0 ? void 0 : termIdResult.data) === null || _a === void 0 ? void 0 : _a.search) === null || _b === void 0 ? void 0 : _b.terms; 243 | if (pos && terms && terms.length) { 244 | const termId = terms[0].id; 245 | const searchResult = yield getRelatedWords(termId, pos); 246 | const edges = (_d = (_c = searchResult === null || searchResult === void 0 ? void 0 : searchResult.data) === null || _c === void 0 ? void 0 : _c.thesauruses) === null || _d === void 0 ? void 0 : _d.edges; 247 | if (edges && edges.length) { 248 | const list = edges.map((edge) => { 249 | var _a, _b, _c, _d, _e, _f; 250 | const tags = (_b = (_a = edge === null || edge === void 0 ? void 0 : edge.node) === null || _a === void 0 ? void 0 : _a.relations) === null || _b === void 0 ? void 0 : _b.tags; 251 | const pos = (_d = (_c = edge === null || edge === void 0 ? void 0 : edge.node) === null || _c === void 0 ? void 0 : _c.relations) === null || _d === void 0 ? void 0 : _d.parts_of_speech.map((p) => POS_MAP[p]); 252 | const term = (_f = (_e = edge === null || edge === void 0 ? void 0 : edge.node) === null || _e === void 0 ? void 0 : _e.targetTerm) === null || _f === void 0 ? void 0 : _f.name; 253 | return { 254 | term, 255 | pos, 256 | tags, 257 | }; 258 | }); 259 | return list; 260 | } 261 | } 262 | } 263 | catch (e) { 264 | console.error(e); 265 | return null; 266 | } 267 | }); 268 | } 269 | } 270 | const SEARCH_QUERY = `query SEARCH_QUERY($query: String!) { 271 | search(query: $query) { 272 | terms { 273 | id 274 | name 275 | slug 276 | counters 277 | __typename 278 | } 279 | list 280 | correctedFrom 281 | __typename 282 | } 283 | }`; 284 | function getTermId(term) { 285 | return __awaiter(this, void 0, void 0, function* () { 286 | const response = yield fetch(apiUrl, { 287 | method: "POST", 288 | // mode: 'no-cors', 289 | headers: { 290 | "Content-Type": "application/json", 291 | "User-Agent": "Obsidian-PowerThesaurus/0.0.1", 292 | }, 293 | body: JSON.stringify({ 294 | operationName: "SEARCH_QUERY", 295 | variables: { query: term }, 296 | query: SEARCH_QUERY, 297 | }), 298 | }); 299 | return response.json(); 300 | }); 301 | } 302 | const THESAURUSES_QUERY = `query THESAURUSES_QUERY($after: String, $first: Int, $before: String, $last: Int, $termID: ID!, $list: List!, $sort: ThesaurusSorting!, $tagID: Int, $posID: Int, $syllables: Int, $type: Type) { 303 | thesauruses( 304 | termId: $termID 305 | sort: $sort 306 | list: $list 307 | after: $after 308 | first: $first 309 | before: $before 310 | last: $last 311 | tagId: $tagID 312 | partOfSpeechId: $posID 313 | syllables: $syllables 314 | type: $type 315 | ) { 316 | pageInfo { 317 | hasNextPage 318 | hasPreviousPage 319 | startCursor 320 | endCursor 321 | __typename 322 | } 323 | edges { 324 | node { 325 | _type 326 | id 327 | isPinned 328 | targetTerm { 329 | id 330 | name 331 | slug 332 | __typename 333 | } 334 | relations 335 | rating 336 | vote { 337 | voteType 338 | id 339 | __typename 340 | } 341 | votes 342 | __typename 343 | } 344 | __typename 345 | } 346 | __typename 347 | } 348 | }`; 349 | function getRelatedWords(termId, pos = null) { 350 | return __awaiter(this, void 0, void 0, function* () { 351 | const response = yield fetch(apiUrl, { 352 | method: "POST", 353 | // mode: 'no-cors', 354 | headers: { 355 | "Content-Type": "application/json", 356 | "User-Agent": "Obsidian-PowerThesaurus/0.0.1", 357 | }, 358 | body: JSON.stringify({ 359 | operationName: "THESAURUSES_QUERY", 360 | variables: { 361 | list: "SYNONYM", 362 | termID: termId, 363 | sort: { field: "RATING", direction: "DESC" }, 364 | tagID: null, 365 | limit: 20, 366 | syllables: null, 367 | query: null, 368 | posID: null, 369 | first: 20, 370 | after: "", 371 | }, 372 | query: THESAURUSES_QUERY, 373 | }), 374 | }); 375 | return response.json(); 376 | }); 377 | } 378 | 379 | const spaceRegEx = /\s/; 380 | class PowerThesaurusPlugin extends obsidian.Plugin { 381 | constructor() { 382 | super(...arguments); 383 | this.destroyPopover = null; 384 | this.isPopoverLoading = false; 385 | this.handlePointerUp = obsidian.debounce(() => { 386 | const activeLeaf = this.app.workspace.activeLeaf; 387 | if (activeLeaf && activeLeaf.view instanceof obsidian.MarkdownView) { 388 | const view = activeLeaf.view; 389 | if (view.getMode() === "source") { 390 | const selection = view.editor.getSelection(); 391 | if (!selection || spaceRegEx.test(selection)) 392 | return; 393 | const cursor = view.editor.getCursor("from"); 394 | const line = view.editor.getLine(cursor.line); 395 | this.isPopoverLoading = true; 396 | new PowerThesaurusProvider() 397 | .getSynonyms(selection, line, cursor.ch) 398 | .then((list) => { 399 | if ((list === null || list === void 0 ? void 0 : list.length) && this.isPopoverLoading) { 400 | this.isPopoverLoading = false; 401 | this.destroyPopover = constructThesaurusPopover({ 402 | list, 403 | selection, 404 | editor: view.editor, 405 | }); 406 | } 407 | }) 408 | .catch((e) => { 409 | console.error(e); 410 | }); 411 | } 412 | } 413 | }, 300, true); 414 | } 415 | onload() { 416 | return __awaiter(this, void 0, void 0, function* () { 417 | document.on("pointerup", ".CodeMirror-line", this.handlePointerUp); 418 | this.registerDomEvent(window, "keydown", () => { 419 | if (this.isPopoverLoading) { 420 | this.isPopoverLoading = false; 421 | } 422 | if (this.destroyPopover) { 423 | this.destroyPopover(); 424 | } 425 | }); 426 | }); 427 | } 428 | onunload() { 429 | document.off("pointerup", ".CodeMirror-line", this.handlePointerUp); 430 | if (this.isPopoverLoading) { 431 | this.isPopoverLoading = false; 432 | } 433 | if (this.destroyPopover) { 434 | this.destroyPopover(); 435 | this.destroyPopover = null; 436 | } 437 | } 438 | } 439 | 440 | module.exports = PowerThesaurusPlugin; 441 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsiLi4vbm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsIi4uL25vZGVfbW9kdWxlcy9tYXRjaC1jYXNpbmcvaW5kZXguanMiLCIuLi9zcmMvcG9wb3Zlci50cyIsIi4uL3NyYy9QT1NQcm92aWRlci50cyIsIi4uL3NyYy9Qb3dlclRoZXNhdXJ1c1Byb3ZpZGVyLnRzIiwiLi4vc3JjL21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOm51bGwsIm5hbWVzIjpbIlBsdWdpbiIsImRlYm91bmNlIiwiTWFya2Rvd25WaWV3Il0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBdURBO0FBQ08sU0FBUyxTQUFTLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDLEVBQUUsU0FBUyxFQUFFO0FBQzdELElBQUksU0FBUyxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsT0FBTyxLQUFLLFlBQVksQ0FBQyxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsQ0FBQyxVQUFVLE9BQU8sRUFBRSxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFO0FBQ2hILElBQUksT0FBTyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLEVBQUUsVUFBVSxPQUFPLEVBQUUsTUFBTSxFQUFFO0FBQy9ELFFBQVEsU0FBUyxTQUFTLENBQUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtBQUNuRyxRQUFRLFNBQVMsUUFBUSxDQUFDLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLENB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 442 | -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-powerthesaurus", 3 | "name": "Power Thesaurus", 4 | "version": "1.0.1", 5 | "minAppVersion": "0.9.12", 6 | "description": "Look up synonyms for selected english words using powerthesaurus.org", 7 | "author": "mgmeyers", 8 | "authorUrl": "https://github.com/mgmeyers/obsidian-powerthesaurus", 9 | "isDesktopOnly": true 10 | } 11 | -------------------------------------------------------------------------------- /dist/styles.css: -------------------------------------------------------------------------------- 1 | .pt-select { 2 | min-width: 210px; 3 | max-width: 250px; 4 | max-height: 200px; 5 | background-color: var(--background-primary); 6 | border: 1px solid var(--background-modifier-border); 7 | position: absolute; 8 | z-index: var(--layer-popover); 9 | border-radius: 5px; 10 | box-shadow: 0px 15px 25px rgba(0, 0, 0, 0.2); 11 | font-size: 14px; 12 | overflow-y: auto; 13 | overflow-x: hidden; 14 | line-height: 1.4; 15 | } 16 | 17 | .pt-select-option { 18 | cursor: pointer; 19 | padding: 10px; 20 | border-bottom: 1px solid var(--background-modifier-border); 21 | } 22 | 23 | .pt-link-option:hover, 24 | .pt-select-option:hover { 25 | background-color: var(--background-secondary); 26 | } 27 | 28 | .pt-link-option { 29 | cursor: pointer; 30 | border-bottom: 1px solid var(--background-modifier-border); 31 | } 32 | 33 | .pt-link-option > a { 34 | color: var(--text-normal); 35 | text-decoration: none; 36 | background-position: center right 10px; 37 | display: block; 38 | padding: 10px; 39 | } 40 | 41 | .pt-select > .pt-link-option:last-child, 42 | .pt-select > .pt-select-option:last-child { 43 | border-bottom: none; 44 | } 45 | 46 | .pt-select-label { 47 | display: flex; 48 | justify-content: space-between; 49 | align-items: center; 50 | } 51 | 52 | .pt-meta-tags, 53 | .pt-meta-pos { 54 | font-size: 12px; 55 | color: var(--text-muted); 56 | } 57 | 58 | .pt-meta-pos { 59 | display: inline-block; 60 | margin-left: 10px; 61 | } 62 | 63 | .pt-link-option > a.pt-powered-by { 64 | color: var(--text-accent); 65 | padding: 6px 10px; 66 | font-size: 11px; 67 | } 68 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-powerthesaurus", 3 | "name": "Power Thesaurus", 4 | "version": "1.0.1", 5 | "minAppVersion": "0.9.12", 6 | "description": "Look up synonyms for selected english words using powerthesaurus.org", 7 | "author": "mgmeyers", 8 | "authorUrl": "https://github.com/mgmeyers/obsidian-powerthesaurus", 9 | "isDesktopOnly": true 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "0.12.0", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js --environment BUILD:production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "^18.0.0", 15 | "@rollup/plugin-node-resolve": "^11.2.1", 16 | "@rollup/plugin-typescript": "^8.2.1", 17 | "@types/node": "^14.14.37", 18 | "obsidian": "^0.12.0", 19 | "rollup": "^2.32.1", 20 | "tslib": "^2.2.0", 21 | "typescript": "^4.2.4" 22 | }, 23 | "dependencies": { 24 | "dotenv": "^9.0.2", 25 | "match-casing": "^2.0.0", 26 | "rollup-plugin-copy": "^3.4.0", 27 | "rollup-plugin-css-only": "^3.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 4 | import commonjs from "@rollup/plugin-commonjs"; 5 | import copy from "rollup-plugin-copy"; 6 | import css from "rollup-plugin-css-only"; 7 | 8 | dotenv.config(); 9 | 10 | const isProd = process.env.BUILD === "production"; 11 | 12 | const banner = `/* 13 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP 14 | if you want to view the source visit the plugins github repository 15 | */ 16 | `; 17 | 18 | const output = [ 19 | { 20 | input: "./src/main.ts", 21 | output: { 22 | dir: "./dist", 23 | sourcemap: "inline", 24 | sourcemapExcludeSources: isProd, 25 | format: "cjs", 26 | exports: "default", 27 | banner, 28 | }, 29 | external: ["obsidian"], 30 | plugins: [ 31 | css({ output: "styles.css" }), 32 | typescript(), 33 | nodeResolve({ browser: true }), 34 | commonjs(), 35 | copy({ 36 | targets: [{ src: "./manifest.json", dest: "./dist" }], 37 | }), 38 | ], 39 | }, 40 | ]; 41 | 42 | if (process.env.PLUGIN_DEST) { 43 | output.push({ 44 | input: "./src/main.ts", 45 | output: { 46 | dir: process.env.PLUGIN_DEST, 47 | sourcemap: "inline", 48 | sourcemapExcludeSources: isProd, 49 | format: "cjs", 50 | exports: "default", 51 | banner, 52 | }, 53 | external: ["obsidian"], 54 | plugins: [ 55 | css({ output: "styles.css" }), 56 | typescript(), 57 | nodeResolve({ browser: true }), 58 | commonjs(), 59 | copy({ 60 | targets: [{ src: "./manifest.json", dest: process.env.PLUGIN_DEST }], 61 | }), 62 | ], 63 | }); 64 | } 65 | 66 | export default output; 67 | -------------------------------------------------------------------------------- /screenshots/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgmeyers/obsidian-powerthesaurus/74bb3f4f2fe24dccb62a88fc2aff6237c17758bf/screenshots/01.png -------------------------------------------------------------------------------- /src/DataMuseProvider.ts: -------------------------------------------------------------------------------- 1 | import { Synonym, SynonymProvider } from "./types"; 2 | 3 | const apiUrl = "https://api.datamuse.com/words?md=p&rel_syn="; 4 | 5 | async function getDefinition(term: string, lc?: string, rc?: string) { 6 | let url = `https://api.datamuse.com/words?sp=${term}&md=pd&max=1`; 7 | 8 | if (lc) { 9 | url += `&lc=${lc}`; 10 | } 11 | 12 | if (rc) { 13 | url += `&rc=${rc}`; 14 | } 15 | 16 | const res = await fetch(url); 17 | const json = (await res.json()) as Array<{ 18 | word: string; 19 | tags: string[]; 20 | defs: string[]; 21 | }>; 22 | 23 | if (json.length) { 24 | return json[0].defs.map((def) => { 25 | const split = def.split(/\t/); 26 | return { 27 | pos: split[0], 28 | def: split[1], 29 | }; 30 | }); 31 | } 32 | 33 | return null; 34 | } 35 | 36 | async function getMeansLike(def: string, pos: string): Promise { 37 | let url = `https://api.datamuse.com/words?md=p&ml=${def.replace(/\s/g, "+")}`; 38 | 39 | const res = await fetch(url); 40 | const json = (await res.json()) as Array<{ word: string; tags: string[] }>; 41 | 42 | return json 43 | .filter((r) => r.tags?.contains("syn") || r.tags?.contains(pos)) 44 | .map((r) => { 45 | return { 46 | term: r.word, 47 | tags: [], 48 | partsOfSpeech: r.tags, 49 | }; 50 | }) 51 | .slice(0, 10); 52 | } 53 | 54 | export class DataMuseProvider implements SynonymProvider { 55 | async getSynonyms( 56 | term: string, 57 | line: string, 58 | termIndex: number 59 | ): Promise { 60 | try { 61 | const sentences = line.split(/(?:\.!;:\(\)\[\]\?)/g); 62 | 63 | let seen = 0; 64 | let lc: string; 65 | let rc: string; 66 | 67 | for (let sentence of sentences) { 68 | if (seen <= termIndex && termIndex <= seen + sentence.length) { 69 | const targetSentence = sentence.trim(); 70 | const beforeWord = targetSentence 71 | .substring(0, termIndex - seen - 1) 72 | .trim(); 73 | const afterWord = targetSentence 74 | .substr(termIndex + term.length) 75 | .trim(); 76 | 77 | if (beforeWord) { 78 | lc = beforeWord.split(/\s+/g).pop(); 79 | } 80 | 81 | if (afterWord) { 82 | rc = afterWord.split(/\s+/g).shift(); 83 | } 84 | 85 | break; 86 | } 87 | 88 | seen += sentence.length + 1; 89 | } 90 | 91 | let url = apiUrl + term; 92 | 93 | if (lc) { 94 | url += `&lc=${lc}`; 95 | } 96 | 97 | if (rc) { 98 | url += `&rc=${rc}`; 99 | } 100 | 101 | const response = await fetch(url); 102 | const data = (await response.json()) as Array<{ 103 | word: string; 104 | tags: string[]; 105 | }>; 106 | 107 | return data.map((d) => { 108 | return { 109 | term: d.word, 110 | tags: [], 111 | partsOfSpeech: d.tags, 112 | }; 113 | }); 114 | } catch (e) { 115 | console.error(e); 116 | return null; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/POSProvider.ts: -------------------------------------------------------------------------------- 1 | const TEMP_LANG = 'en'; 2 | 3 | function getURL(sentence: string, lang: string) { 4 | return `https://systran-systran-platform-for-language-processing-v1.p.rapidapi.com/nlp/morphology/extract/pos?input=${encodeURIComponent(sentence)}&lang=${lang}` 5 | } 6 | 7 | interface POSResponse { 8 | partsOfSpeech: Array<{ 9 | start: number, 10 | end: number, 11 | text: string, 12 | pos: string 13 | }> 14 | } 15 | 16 | export async function getPOS(line: string, termIndex: number) { 17 | const sentences = line.split(/[.!?]/g); 18 | let seen = 0; 19 | 20 | for (let sentence of sentences) { 21 | if (seen <= termIndex && termIndex <= seen + sentence.length) { 22 | const result = await fetch(getURL(sentence, TEMP_LANG), { 23 | "method": "GET", 24 | "headers": { 25 | "x-rapidapi-key": "5e1aff4a8dmsh29e6efbd0a66e0ep1c9b55jsn31b784e15a17", 26 | "x-rapidapi-host": "systran-systran-platform-for-language-processing-v1.p.rapidapi.com" 27 | } 28 | }) 29 | 30 | const json = await result.json() as POSResponse; 31 | 32 | const wordIndex = termIndex - seen; 33 | const match = json.partsOfSpeech?.find(pos => pos.start === wordIndex); 34 | 35 | if (match) { 36 | const posStr = match.pos.split('/')[1]; 37 | 38 | if (posStr.startsWith('noun')) return 2; 39 | if (posStr.startsWith('verb')) return 6; 40 | if (posStr.startsWith('adj')) return 1; 41 | if (posStr.startsWith('adv')) return 4; 42 | } 43 | 44 | break; 45 | } 46 | 47 | seen += sentence.length + 1; 48 | } 49 | 50 | return null 51 | } -------------------------------------------------------------------------------- /src/PowerThesaurusProvider.ts: -------------------------------------------------------------------------------- 1 | import { getPOS } from "./POSProvider"; 2 | import { Synonym, SynonymProvider } from "./types"; 3 | 4 | const apiUrl = "https://api.powerthesaurus.org/"; 5 | 6 | const POS_MAP: { [k: number]: string } = { 7 | 1: "adj", 8 | 2: "n", 9 | 4: "adv", 10 | 6: "v", 11 | 7: "int", 12 | 9: "conj", 13 | 10: "prep", 14 | }; 15 | 16 | export class PowerThesaurusProvider implements SynonymProvider { 17 | static appendAttribution(containerEl: HTMLElement, selection: string) { 18 | containerEl.createDiv({ cls: "pt-link-option" }, (div) => { 19 | div.createEl( 20 | "a", 21 | { 22 | cls: "external-link", 23 | text: "See more results", 24 | href: `https://www.powerthesaurus.org/${selection.toLowerCase()}/synonyms`, 25 | }, 26 | (a) => { 27 | a.target = "_blank"; 28 | } 29 | ); 30 | }); 31 | 32 | containerEl.createDiv({ cls: "pt-link-option" }, (div) => { 33 | div.createEl( 34 | "a", 35 | { 36 | cls: "external-link pt-powered-by", 37 | text: "Powered by power thesaurus", 38 | href: `https://www.powerthesaurus.org/`, 39 | }, 40 | (a) => { 41 | a.target = "_blank"; 42 | } 43 | ); 44 | }); 45 | } 46 | async getSynonyms(term: string, line: string, termIndex: number): Promise { 47 | try { 48 | const pos = await getPOS(line, termIndex); 49 | const termIdResult = await getTermId(term); 50 | const terms = termIdResult?.data?.search?.terms; 51 | 52 | if (pos && terms && terms.length) { 53 | const termId = terms[0].id; 54 | const searchResult = await getRelatedWords(termId, pos); 55 | 56 | const edges = searchResult?.data?.thesauruses?.edges; 57 | 58 | if (edges && edges.length) { 59 | const list = edges.map((edge: any) => { 60 | const tags = edge?.node?.relations?.tags; 61 | const pos = edge?.node?.relations?.parts_of_speech.map((p: number) => POS_MAP[p]); 62 | const term = edge?.node?.targetTerm?.name; 63 | 64 | return { 65 | term, 66 | pos, 67 | tags, 68 | }; 69 | }); 70 | 71 | return list; 72 | } 73 | } 74 | } catch (e) { 75 | console.error(e); 76 | return null; 77 | } 78 | } 79 | } 80 | 81 | 82 | export const SEARCH_QUERY = `query SEARCH_QUERY($query: String!) { 83 | search(query: $query) { 84 | terms { 85 | id 86 | name 87 | slug 88 | counters 89 | __typename 90 | } 91 | list 92 | correctedFrom 93 | __typename 94 | } 95 | }`; 96 | 97 | async function getTermId(term: string) { 98 | const response = await fetch(apiUrl, { 99 | method: "POST", 100 | // mode: 'no-cors', 101 | headers: { 102 | "Content-Type": "application/json", 103 | "User-Agent": "Obsidian-PowerThesaurus/0.0.1", 104 | }, 105 | body: JSON.stringify({ 106 | operationName: "SEARCH_QUERY", 107 | variables: { query: term }, 108 | query: SEARCH_QUERY, 109 | }), 110 | }); 111 | 112 | return response.json(); 113 | } 114 | 115 | export const THESAURUSES_QUERY = `query THESAURUSES_QUERY($after: String, $first: Int, $before: String, $last: Int, $termID: ID!, $list: List!, $sort: ThesaurusSorting!, $tagID: Int, $posID: Int, $syllables: Int, $type: Type) { 116 | thesauruses( 117 | termId: $termID 118 | sort: $sort 119 | list: $list 120 | after: $after 121 | first: $first 122 | before: $before 123 | last: $last 124 | tagId: $tagID 125 | partOfSpeechId: $posID 126 | syllables: $syllables 127 | type: $type 128 | ) { 129 | pageInfo { 130 | hasNextPage 131 | hasPreviousPage 132 | startCursor 133 | endCursor 134 | __typename 135 | } 136 | edges { 137 | node { 138 | _type 139 | id 140 | isPinned 141 | targetTerm { 142 | id 143 | name 144 | slug 145 | __typename 146 | } 147 | relations 148 | rating 149 | vote { 150 | voteType 151 | id 152 | __typename 153 | } 154 | votes 155 | __typename 156 | } 157 | __typename 158 | } 159 | __typename 160 | } 161 | }`; 162 | 163 | async function getRelatedWords(termId: string, pos: number = null) { 164 | const response = await fetch(apiUrl, { 165 | method: "POST", 166 | // mode: 'no-cors', 167 | headers: { 168 | "Content-Type": "application/json", 169 | "User-Agent": "Obsidian-PowerThesaurus/0.0.1", 170 | }, 171 | body: JSON.stringify({ 172 | operationName: "THESAURUSES_QUERY", 173 | variables: { 174 | list: "SYNONYM", 175 | termID: termId, 176 | sort: { field: "RATING", direction: "DESC" }, 177 | tagID: null, 178 | limit: 20, 179 | syllables: null, 180 | query: null, 181 | posID: null, 182 | first: 20, 183 | after: "", 184 | }, 185 | query: THESAURUSES_QUERY, 186 | }), 187 | }); 188 | 189 | return response.json(); 190 | } 191 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, debounce, MarkdownView } from "obsidian"; 2 | 3 | import { constructThesaurusPopover } from "./popover"; 4 | import { PowerThesaurusProvider } from "./PowerThesaurusProvider"; 5 | 6 | const spaceRegEx = /\s/; 7 | 8 | export default class PowerThesaurusPlugin extends Plugin { 9 | destroyPopover: (() => void) | null = null; 10 | isPopoverLoading = false; 11 | 12 | handlePointerUp = debounce( 13 | () => { 14 | const activeLeaf = this.app.workspace.activeLeaf; 15 | 16 | if (activeLeaf && activeLeaf.view instanceof MarkdownView) { 17 | const view = activeLeaf.view; 18 | 19 | if (view.getMode() === "source") { 20 | const selection = view.editor.getSelection(); 21 | 22 | if (!selection || spaceRegEx.test(selection)) return; 23 | 24 | const cursor = view.editor.getCursor("from"); 25 | const line = view.editor.getLine(cursor.line); 26 | 27 | this.isPopoverLoading = true; 28 | 29 | new PowerThesaurusProvider() 30 | .getSynonyms(selection, line, cursor.ch) 31 | .then((list) => { 32 | if (list?.length && this.isPopoverLoading) { 33 | this.isPopoverLoading = false; 34 | this.destroyPopover = constructThesaurusPopover({ 35 | list, 36 | selection, 37 | editor: view.editor, 38 | }); 39 | } 40 | }) 41 | .catch((e) => { 42 | console.error(e); 43 | }); 44 | } 45 | } 46 | }, 47 | 300, 48 | true 49 | ); 50 | 51 | async onload() { 52 | document.on("pointerup", ".CodeMirror-line", this.handlePointerUp); 53 | 54 | this.registerDomEvent(window, "keydown", () => { 55 | if (this.isPopoverLoading) { 56 | this.isPopoverLoading = false; 57 | } 58 | 59 | if (this.destroyPopover) { 60 | this.destroyPopover(); 61 | } 62 | }); 63 | } 64 | 65 | onunload() { 66 | document.off("pointerup", ".CodeMirror-line", this.handlePointerUp); 67 | 68 | if (this.isPopoverLoading) { 69 | this.isPopoverLoading = false; 70 | } 71 | 72 | if (this.destroyPopover) { 73 | this.destroyPopover(); 74 | this.destroyPopover = null; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/popover.css: -------------------------------------------------------------------------------- 1 | .pt-select { 2 | min-width: 210px; 3 | max-width: 250px; 4 | max-height: 200px; 5 | background-color: var(--background-primary); 6 | border: 1px solid var(--background-modifier-border); 7 | position: absolute; 8 | z-index: var(--layer-popover); 9 | border-radius: 5px; 10 | box-shadow: 0px 15px 25px rgba(0, 0, 0, 0.2); 11 | font-size: 14px; 12 | overflow-y: auto; 13 | overflow-x: hidden; 14 | line-height: 1.4; 15 | } 16 | 17 | .pt-select-option { 18 | cursor: pointer; 19 | padding: 10px; 20 | border-bottom: 1px solid var(--background-modifier-border); 21 | } 22 | 23 | .pt-link-option:hover, 24 | .pt-select-option:hover { 25 | background-color: var(--background-secondary); 26 | } 27 | 28 | .pt-link-option { 29 | cursor: pointer; 30 | border-bottom: 1px solid var(--background-modifier-border); 31 | } 32 | 33 | .pt-link-option > a { 34 | color: var(--text-normal); 35 | text-decoration: none; 36 | background-position: center right 10px; 37 | display: block; 38 | padding: 10px; 39 | } 40 | 41 | .pt-select > .pt-link-option:last-child, 42 | .pt-select > .pt-select-option:last-child { 43 | border-bottom: none; 44 | } 45 | 46 | .pt-select-label { 47 | display: flex; 48 | justify-content: space-between; 49 | align-items: center; 50 | } 51 | 52 | .pt-meta-tags, 53 | .pt-meta-pos { 54 | font-size: 12px; 55 | color: var(--text-muted); 56 | } 57 | 58 | .pt-meta-pos { 59 | display: inline-block; 60 | margin-left: 10px; 61 | } 62 | 63 | .pt-link-option > a.pt-powered-by { 64 | color: var(--text-accent); 65 | padding: 6px 10px; 66 | font-size: 11px; 67 | } 68 | -------------------------------------------------------------------------------- /src/popover.ts: -------------------------------------------------------------------------------- 1 | import { Editor } from "obsidian"; 2 | import { Synonym } from "./types"; 3 | import { matchCasing } from "match-casing"; 4 | 5 | import "./popover.css"; 6 | 7 | function repositionWidget( 8 | widget: HTMLDivElement, 9 | coords: { left: number; top: number; bottom: number } 10 | ) { 11 | const height = widget.clientHeight; 12 | const width = widget.clientWidth; 13 | 14 | if (coords.bottom + height > window.innerHeight) { 15 | widget.style.setProperty("top", `${coords.top - height}px`); 16 | } 17 | 18 | if (coords.left + width > window.innerWidth) { 19 | widget.style.setProperty("left", `${window.innerWidth - width - 15}px`); 20 | } 21 | } 22 | 23 | interface ConstructThesaurusPopoverParams { 24 | list: Synonym[]; 25 | editor: Editor; 26 | selection: string; 27 | } 28 | 29 | export function constructThesaurusPopover({ 30 | list, 31 | editor, 32 | selection, 33 | }: ConstructThesaurusPopoverParams) { 34 | let isDestroyed = false; 35 | 36 | const widget = createDiv({ cls: "pt-select" }, (div) => { 37 | list.forEach((synonym, i) => { 38 | div.createDiv({ cls: "pt-select-option" }, (option) => { 39 | option.dataset.index = i.toString(); 40 | 41 | option.createDiv({ cls: "pt-select-label" }, (label) => { 42 | label.createDiv({ cls: "pt-term", text: synonym.term }); 43 | if (synonym.partsOfSpeech?.length) { 44 | label.createDiv({ 45 | cls: "pt-meta-pos", 46 | text: synonym.partsOfSpeech.join(", "), 47 | }); 48 | } 49 | }); 50 | 51 | if (synonym.tags?.length) { 52 | option.createDiv({ 53 | cls: "pt-meta-tags", 54 | text: synonym.tags.join(", "), 55 | }); 56 | } 57 | }); 58 | }); 59 | }); 60 | 61 | const clickOutsideHandler = (e: MouseEvent) => { 62 | const isTargetInWidget = 63 | e.target instanceof Node && widget.contains(e.target); 64 | 65 | if (!isTargetInWidget) { 66 | selfDestruct(); 67 | } 68 | }; 69 | 70 | const escHandler = (e: KeyboardEvent) => { 71 | if (e.key === "Escape") { 72 | selfDestruct(); 73 | } 74 | }; 75 | 76 | const selfDestruct = () => { 77 | if (isDestroyed) return; 78 | 79 | isDestroyed = true; 80 | widget.remove(); 81 | document.body.removeEventListener("pointerdown", clickOutsideHandler); 82 | document.removeEventListener("keydown", escHandler); 83 | }; 84 | 85 | let coords: { top: number; left: number; bottom: number }; 86 | 87 | if ((editor as any).cursorCoords) { 88 | coords = (editor as any).cursorCoords(true, "window"); 89 | } else if ((editor as any).coordsAtPos) { 90 | const offset = editor.posToOffset(editor.getCursor("from")); 91 | coords = (editor as any).coordsAtPos(offset); 92 | } else { 93 | return; 94 | } 95 | 96 | widget.on("click", ".pt-select-option", (e, el) => { 97 | editor.replaceSelection( 98 | matchCasing(list[parseInt(el.dataset.index)].term, selection) 99 | ); 100 | selfDestruct(); 101 | }); 102 | 103 | widget.style.left = `${coords.left}px`; 104 | widget.style.top = `${coords.bottom}px`; 105 | 106 | document.body.appendChild(widget); 107 | 108 | repositionWidget(widget, coords); 109 | 110 | document.body.addEventListener("pointerdown", clickOutsideHandler); 111 | document.addEventListener("keydown", escHandler); 112 | 113 | return selfDestruct; 114 | } 115 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Synonym { 2 | term: string; 3 | partsOfSpeech: string[]; 4 | tags: string[]; 5 | } 6 | 7 | export interface Definition { 8 | term: string; 9 | partOfSpeech: string; 10 | tags: string[]; 11 | definition: string; 12 | pronunciation: string; 13 | } 14 | 15 | 16 | export interface SynonymProvider { 17 | getSynonyms(word: string, line: string, wordIndex: number): Promise 18 | } -------------------------------------------------------------------------------- /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 | "scripthost", 16 | "es2015" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.1": "0.9.12", 3 | "1.0.0": "0.9.12" 4 | } 5 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@nodelib/fs.scandir@2.1.4": 6 | version "2.1.4" 7 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" 8 | integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== 9 | dependencies: 10 | "@nodelib/fs.stat" "2.0.4" 11 | run-parallel "^1.1.9" 12 | 13 | "@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": 14 | version "2.0.4" 15 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" 16 | integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== 17 | 18 | "@nodelib/fs.walk@^1.2.3": 19 | version "1.2.6" 20 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" 21 | integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== 22 | dependencies: 23 | "@nodelib/fs.scandir" "2.1.4" 24 | fastq "^1.6.0" 25 | 26 | "@rollup/plugin-commonjs@^18.0.0": 27 | version "18.1.0" 28 | resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-18.1.0.tgz#5a760d757af168a50727c0ae080251fbfcc5eb02" 29 | integrity sha512-h3e6T9rUxVMAQswpDIobfUHn/doMzM9sgkMrsMWCFLmB84PSoC8mV8tOloAJjSRwdqhXBqstlX2BwBpHJvbhxg== 30 | dependencies: 31 | "@rollup/pluginutils" "^3.1.0" 32 | commondir "^1.0.1" 33 | estree-walker "^2.0.1" 34 | glob "^7.1.6" 35 | is-reference "^1.2.1" 36 | magic-string "^0.25.7" 37 | resolve "^1.17.0" 38 | 39 | "@rollup/plugin-node-resolve@^11.2.1": 40 | version "11.2.1" 41 | resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60" 42 | integrity sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg== 43 | dependencies: 44 | "@rollup/pluginutils" "^3.1.0" 45 | "@types/resolve" "1.17.1" 46 | builtin-modules "^3.1.0" 47 | deepmerge "^4.2.2" 48 | is-module "^1.0.0" 49 | resolve "^1.19.0" 50 | 51 | "@rollup/plugin-typescript@^8.2.1": 52 | version "8.2.1" 53 | resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.2.1.tgz#f1a32d4030cc83432ce36a80a922280f0f0b5d44" 54 | integrity sha512-Qd2E1pleDR4bwyFxqbjt4eJf+wB0UKVMLc7/BAFDGVdAXQMCsD4DUv5/7/ww47BZCYxWtJqe1Lo0KVNswBJlRw== 55 | dependencies: 56 | "@rollup/pluginutils" "^3.1.0" 57 | resolve "^1.17.0" 58 | 59 | "@rollup/pluginutils@4": 60 | version "4.1.0" 61 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.0.tgz#0dcc61c780e39257554feb7f77207dceca13c838" 62 | integrity sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ== 63 | dependencies: 64 | estree-walker "^2.0.1" 65 | picomatch "^2.2.2" 66 | 67 | "@rollup/pluginutils@^3.1.0": 68 | version "3.1.0" 69 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" 70 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== 71 | dependencies: 72 | "@types/estree" "0.0.39" 73 | estree-walker "^1.0.1" 74 | picomatch "^2.2.2" 75 | 76 | "@types/codemirror@0.0.108": 77 | version "0.0.108" 78 | resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.108.tgz#e640422b666bf49251b384c390cdeb2362585bde" 79 | integrity sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw== 80 | dependencies: 81 | "@types/tern" "*" 82 | 83 | "@types/estree@*": 84 | version "0.0.47" 85 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" 86 | integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== 87 | 88 | "@types/estree@0.0.39": 89 | version "0.0.39" 90 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 91 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 92 | 93 | "@types/fs-extra@^8.0.1": 94 | version "8.1.1" 95 | resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" 96 | integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== 97 | dependencies: 98 | "@types/node" "*" 99 | 100 | "@types/glob@^7.1.1": 101 | version "7.1.3" 102 | resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" 103 | integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== 104 | dependencies: 105 | "@types/minimatch" "*" 106 | "@types/node" "*" 107 | 108 | "@types/minimatch@*": 109 | version "3.0.4" 110 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" 111 | integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== 112 | 113 | "@types/node@*": 114 | version "15.3.0" 115 | resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26" 116 | integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ== 117 | 118 | "@types/node@^14.14.37": 119 | version "14.14.45" 120 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.45.tgz#ec2dfb5566ff814d061aef7e141575aedba245cf" 121 | integrity sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw== 122 | 123 | "@types/resolve@1.17.1": 124 | version "1.17.1" 125 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" 126 | integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== 127 | dependencies: 128 | "@types/node" "*" 129 | 130 | "@types/tern@*": 131 | version "0.23.3" 132 | resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460" 133 | integrity sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w== 134 | dependencies: 135 | "@types/estree" "*" 136 | 137 | array-union@^2.1.0: 138 | version "2.1.0" 139 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 140 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 141 | 142 | balanced-match@^1.0.0: 143 | version "1.0.2" 144 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 145 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 146 | 147 | brace-expansion@^1.1.7: 148 | version "1.1.11" 149 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 150 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 151 | dependencies: 152 | balanced-match "^1.0.0" 153 | concat-map "0.0.1" 154 | 155 | braces@^3.0.1: 156 | version "3.0.2" 157 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 158 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 159 | dependencies: 160 | fill-range "^7.0.1" 161 | 162 | builtin-modules@^3.1.0: 163 | version "3.2.0" 164 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" 165 | integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== 166 | 167 | colorette@^1.1.0: 168 | version "1.2.2" 169 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" 170 | integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== 171 | 172 | commondir@^1.0.1: 173 | version "1.0.1" 174 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" 175 | integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= 176 | 177 | concat-map@0.0.1: 178 | version "0.0.1" 179 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 180 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 181 | 182 | deepmerge@^4.2.2: 183 | version "4.2.2" 184 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" 185 | integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== 186 | 187 | dir-glob@^3.0.1: 188 | version "3.0.1" 189 | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 190 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 191 | dependencies: 192 | path-type "^4.0.0" 193 | 194 | dotenv@^9.0.2: 195 | version "9.0.2" 196 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" 197 | integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== 198 | 199 | estree-walker@^1.0.1: 200 | version "1.0.1" 201 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" 202 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== 203 | 204 | estree-walker@^2.0.1: 205 | version "2.0.2" 206 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 207 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 208 | 209 | fast-glob@^3.0.3: 210 | version "3.2.5" 211 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" 212 | integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== 213 | dependencies: 214 | "@nodelib/fs.stat" "^2.0.2" 215 | "@nodelib/fs.walk" "^1.2.3" 216 | glob-parent "^5.1.0" 217 | merge2 "^1.3.0" 218 | micromatch "^4.0.2" 219 | picomatch "^2.2.1" 220 | 221 | fastq@^1.6.0: 222 | version "1.11.0" 223 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" 224 | integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== 225 | dependencies: 226 | reusify "^1.0.4" 227 | 228 | fill-range@^7.0.1: 229 | version "7.0.1" 230 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 231 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 232 | dependencies: 233 | to-regex-range "^5.0.1" 234 | 235 | fs-extra@^8.1.0: 236 | version "8.1.0" 237 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 238 | integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== 239 | dependencies: 240 | graceful-fs "^4.2.0" 241 | jsonfile "^4.0.0" 242 | universalify "^0.1.0" 243 | 244 | fs.realpath@^1.0.0: 245 | version "1.0.0" 246 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 247 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 248 | 249 | fsevents@~2.3.1: 250 | version "2.3.2" 251 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 252 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 253 | 254 | function-bind@^1.1.1: 255 | version "1.1.1" 256 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 257 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 258 | 259 | glob-parent@^5.1.0: 260 | version "5.1.2" 261 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 262 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 263 | dependencies: 264 | is-glob "^4.0.1" 265 | 266 | glob@^7.1.3, glob@^7.1.6: 267 | version "7.1.7" 268 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" 269 | integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== 270 | dependencies: 271 | fs.realpath "^1.0.0" 272 | inflight "^1.0.4" 273 | inherits "2" 274 | minimatch "^3.0.4" 275 | once "^1.3.0" 276 | path-is-absolute "^1.0.0" 277 | 278 | globby@10.0.1: 279 | version "10.0.1" 280 | resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22" 281 | integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A== 282 | dependencies: 283 | "@types/glob" "^7.1.1" 284 | array-union "^2.1.0" 285 | dir-glob "^3.0.1" 286 | fast-glob "^3.0.3" 287 | glob "^7.1.3" 288 | ignore "^5.1.1" 289 | merge2 "^1.2.3" 290 | slash "^3.0.0" 291 | 292 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 293 | version "4.2.6" 294 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" 295 | integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== 296 | 297 | has@^1.0.3: 298 | version "1.0.3" 299 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 300 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 301 | dependencies: 302 | function-bind "^1.1.1" 303 | 304 | ignore@^5.1.1: 305 | version "5.1.8" 306 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" 307 | integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== 308 | 309 | inflight@^1.0.4: 310 | version "1.0.6" 311 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 312 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 313 | dependencies: 314 | once "^1.3.0" 315 | wrappy "1" 316 | 317 | inherits@2: 318 | version "2.0.4" 319 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 320 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 321 | 322 | is-core-module@^2.2.0: 323 | version "2.4.0" 324 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" 325 | integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== 326 | dependencies: 327 | has "^1.0.3" 328 | 329 | is-extglob@^2.1.1: 330 | version "2.1.1" 331 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 332 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 333 | 334 | is-glob@^4.0.1: 335 | version "4.0.1" 336 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 337 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 338 | dependencies: 339 | is-extglob "^2.1.1" 340 | 341 | is-module@^1.0.0: 342 | version "1.0.0" 343 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" 344 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= 345 | 346 | is-number@^7.0.0: 347 | version "7.0.0" 348 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 349 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 350 | 351 | is-plain-object@^3.0.0: 352 | version "3.0.1" 353 | resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" 354 | integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== 355 | 356 | is-reference@^1.2.1: 357 | version "1.2.1" 358 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" 359 | integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== 360 | dependencies: 361 | "@types/estree" "*" 362 | 363 | jsonfile@^4.0.0: 364 | version "4.0.0" 365 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 366 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 367 | optionalDependencies: 368 | graceful-fs "^4.1.6" 369 | 370 | magic-string@^0.25.7: 371 | version "0.25.7" 372 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" 373 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== 374 | dependencies: 375 | sourcemap-codec "^1.4.4" 376 | 377 | match-casing@^2.0.0: 378 | version "2.0.0" 379 | resolved "https://registry.yarnpkg.com/match-casing/-/match-casing-2.0.0.tgz#76d56d1fd6ff75fc02b71f1a17c6180e12153f22" 380 | integrity sha512-yWG59YBJvFZLJC9PTtpckce1JVu3j0C4Am9/AO65qmug4iHdpMdzj2BC7FGoN5kGsjr1jS8i6zdeVy05P7SjWg== 381 | 382 | merge2@^1.2.3, merge2@^1.3.0: 383 | version "1.4.1" 384 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 385 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 386 | 387 | micromatch@^4.0.2: 388 | version "4.0.4" 389 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" 390 | integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== 391 | dependencies: 392 | braces "^3.0.1" 393 | picomatch "^2.2.3" 394 | 395 | minimatch@^3.0.4: 396 | version "3.0.4" 397 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 398 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 399 | dependencies: 400 | brace-expansion "^1.1.7" 401 | 402 | moment@2.29.1: 403 | version "2.29.1" 404 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" 405 | integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== 406 | 407 | obsidian@^0.12.0: 408 | version "0.12.2" 409 | resolved "https://registry.yarnpkg.com/obsidian/-/obsidian-0.12.2.tgz#0a4a41063c089502b5edc4a3edf5da4ba38afdba" 410 | integrity sha512-lvrJ6GEUkKrHCpbSDO6NdGdkSYQ/0ElGYizdcw4mRlMiHTOFgYjbqv+fEMkyLIneF0uB7epf9YNQEyFtRF2ESA== 411 | dependencies: 412 | "@types/codemirror" "0.0.108" 413 | moment "2.29.1" 414 | 415 | once@^1.3.0: 416 | version "1.4.0" 417 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 418 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 419 | dependencies: 420 | wrappy "1" 421 | 422 | path-is-absolute@^1.0.0: 423 | version "1.0.1" 424 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 425 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 426 | 427 | path-parse@^1.0.6: 428 | version "1.0.6" 429 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 430 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 431 | 432 | path-type@^4.0.0: 433 | version "4.0.0" 434 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 435 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 436 | 437 | picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: 438 | version "2.2.3" 439 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" 440 | integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== 441 | 442 | queue-microtask@^1.2.2: 443 | version "1.2.3" 444 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 445 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 446 | 447 | resolve@^1.17.0, resolve@^1.19.0: 448 | version "1.20.0" 449 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 450 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 451 | dependencies: 452 | is-core-module "^2.2.0" 453 | path-parse "^1.0.6" 454 | 455 | reusify@^1.0.4: 456 | version "1.0.4" 457 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 458 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 459 | 460 | rollup-plugin-copy@^3.4.0: 461 | version "3.4.0" 462 | resolved "https://registry.yarnpkg.com/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz#f1228a3ffb66ffad8606e2f3fb7ff23141ed3286" 463 | integrity sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ== 464 | dependencies: 465 | "@types/fs-extra" "^8.0.1" 466 | colorette "^1.1.0" 467 | fs-extra "^8.1.0" 468 | globby "10.0.1" 469 | is-plain-object "^3.0.0" 470 | 471 | rollup-plugin-css-only@^3.1.0: 472 | version "3.1.0" 473 | resolved "https://registry.yarnpkg.com/rollup-plugin-css-only/-/rollup-plugin-css-only-3.1.0.tgz#6a701cc5b051c6b3f0961e69b108a9a118e1b1df" 474 | integrity sha512-TYMOE5uoD76vpj+RTkQLzC9cQtbnJNktHPB507FzRWBVaofg7KhIqq1kGbcVOadARSozWF883Ho9KpSPKH8gqA== 475 | dependencies: 476 | "@rollup/pluginutils" "4" 477 | 478 | rollup@^2.32.1: 479 | version "2.48.0" 480 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.48.0.tgz#fceb01ed771f991f29f7bd2ff7838146e55acb74" 481 | integrity sha512-wl9ZSSSsi5579oscSDYSzGn092tCS076YB+TQrzsGuSfYyJeep8eEWj0eaRjuC5McuMNmcnR8icBqiE/FWNB1A== 482 | optionalDependencies: 483 | fsevents "~2.3.1" 484 | 485 | run-parallel@^1.1.9: 486 | version "1.2.0" 487 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" 488 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 489 | dependencies: 490 | queue-microtask "^1.2.2" 491 | 492 | slash@^3.0.0: 493 | version "3.0.0" 494 | resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 495 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 496 | 497 | sourcemap-codec@^1.4.4: 498 | version "1.4.8" 499 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" 500 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== 501 | 502 | to-regex-range@^5.0.1: 503 | version "5.0.1" 504 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 505 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 506 | dependencies: 507 | is-number "^7.0.0" 508 | 509 | tslib@^2.2.0: 510 | version "2.2.0" 511 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" 512 | integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== 513 | 514 | typescript@^4.2.4: 515 | version "4.2.4" 516 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" 517 | integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== 518 | 519 | universalify@^0.1.0: 520 | version "0.1.2" 521 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 522 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 523 | 524 | wrappy@1: 525 | version "1.0.2" 526 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 527 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 528 | --------------------------------------------------------------------------------