├── .github └── workflows │ └── release.yml ├── .gitignore ├── Alfred Workflow ├── Phrasebank Alfred v.0.6.alfredworkflow ├── info.plist ├── phrasebank-searcher-2.js ├── phrasebank-searcher.js └── snippet-triggered-phrase-input.js ├── CHANGELOG.md ├── JSON2MD.js ├── README.md ├── main.js ├── manifest.json ├── package.json ├── rollup.config.js ├── snippet-triggered-phrase-input.js ├── src ├── constants.ts ├── interfaces.ts ├── main.ts ├── phraseType-only-suggester.ts ├── phraseType-or-groups-suggester.ts ├── phrases-suggester.ts ├── settings.ts └── utils.ts ├── styles.css ├── tsconfig.json └── versions.json /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | env: 10 | PLUGIN_NAME: ${{ github.event.repository.name }} 11 | RELEASE_VER: ${{ github.ref }} 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Create release and Upload 20 | id: release 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | run: | 24 | TAG_NAME=${RELEASE_VER##*/} 25 | mkdir "${PLUGIN_NAME}" 26 | assets=() 27 | for f in main.js manifest.json styles.css; do 28 | if [[ -f $f ]]; then 29 | cp $f "${PLUGIN_NAME}/" 30 | assets+=(-a "$f") 31 | fi 32 | done 33 | zip -r "$PLUGIN_NAME".zip "$PLUGIN_NAME" 34 | hub release create "${assets[@]}" -a "$PLUGIN_NAME".zip -m "$TAG_NAME" "$TAG_NAME" 35 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Alfred Workflow/Phrasebank Alfred v.0.6.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkepticMystic/Phrase-Bank/38ef236fb509a6eff83a44841ff9a81a5509054f/Alfred Workflow/Phrasebank Alfred v.0.6.alfredworkflow -------------------------------------------------------------------------------- /Alfred Workflow/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | de.chris-grieser.phrasebank 7 | category 8 | Language 9 | connections 10 | 11 | 0F6C59AD-B421-4B4A-875F-80555B32E69C 12 | 13 | 14 | destinationuid 15 | B6F85FB8-06A8-4EAB-A4A4-94C40CDA11BC 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | 73B01B35-14DF-497A-97BD-CF927F6E19E9 25 | 26 | 27 | destinationuid 28 | 7C59D9D5-E270-4938-859E-1CB426F6E866 29 | modifiers 30 | 0 31 | modifiersubtext 32 | 33 | sourceoutputuid 34 | 158B1BB4-20C0-4B33-8BF5-EB73E2550DA6 35 | vitoclose 36 | 37 | 38 | 39 | destinationuid 40 | E2F1573B-A697-4E35-A86F-E7DF2813EEA5 41 | modifiers 42 | 0 43 | modifiersubtext 44 | 45 | vitoclose 46 | 47 | 48 | 49 | 77559430-B9C7-4DAF-8D9A-D90A208D38A7 50 | 51 | 52 | destinationuid 53 | 7FA67B5E-6A9F-4416-B0A9-D9E2D7CDC22A 54 | modifiers 55 | 0 56 | modifiersubtext 57 | 58 | sourceoutputuid 59 | CADE715E-68B8-4AE9-A70B-A77D2584B3AE 60 | vitoclose 61 | 62 | 63 | 64 | destinationuid 65 | EB16F746-65FB-418A-9AFB-3016A48C93A3 66 | modifiers 67 | 0 68 | modifiersubtext 69 | 70 | vitoclose 71 | 72 | 73 | 74 | 7C59D9D5-E270-4938-859E-1CB426F6E866 75 | 76 | 77 | destinationuid 78 | 7FA67B5E-6A9F-4416-B0A9-D9E2D7CDC22A 79 | modifiers 80 | 0 81 | modifiersubtext 82 | 83 | vitoclose 84 | 85 | 86 | 87 | 7FA67B5E-6A9F-4416-B0A9-D9E2D7CDC22A 88 | 89 | 90 | destinationuid 91 | 4FD9750F-51EA-4B5B-A881-B9297FA29A05 92 | modifiers 93 | 0 94 | modifiersubtext 95 | 96 | vitoclose 97 | 98 | 99 | 100 | A321CC15-C26E-4013-B083-4830FFB77C57 101 | 102 | 103 | destinationuid 104 | 7C59D9D5-E270-4938-859E-1CB426F6E866 105 | modifiers 106 | 0 107 | modifiersubtext 108 | 109 | sourceoutputuid 110 | 158B1BB4-20C0-4B33-8BF5-EB73E2550DA6 111 | vitoclose 112 | 113 | 114 | 115 | destinationuid 116 | D1D13A68-D504-447A-8487-2AF0C3E4253E 117 | modifiers 118 | 0 119 | modifiersubtext 120 | 121 | vitoclose 122 | 123 | 124 | 125 | B6F85FB8-06A8-4EAB-A4A4-94C40CDA11BC 126 | 127 | 128 | destinationuid 129 | 73B01B35-14DF-497A-97BD-CF927F6E19E9 130 | modifiers 131 | 1048576 132 | modifiersubtext 133 | 134 | vitoclose 135 | 136 | 137 | 138 | destinationuid 139 | A321CC15-C26E-4013-B083-4830FFB77C57 140 | modifiers 141 | 0 142 | modifiersubtext 143 | 144 | vitoclose 145 | 146 | 147 | 148 | BCF3836D-1020-4079-B421-B3B99867A6E7 149 | 150 | 151 | destinationuid 152 | B6F85FB8-06A8-4EAB-A4A4-94C40CDA11BC 153 | modifiers 154 | 0 155 | modifiersubtext 156 | 157 | vitoclose 158 | 159 | 160 | 161 | D1D13A68-D504-447A-8487-2AF0C3E4253E 162 | 163 | 164 | destinationuid 165 | E2F1573B-A697-4E35-A86F-E7DF2813EEA5 166 | modifiers 167 | 0 168 | modifiersubtext 169 | 170 | vitoclose 171 | 172 | 173 | 174 | E2F1573B-A697-4E35-A86F-E7DF2813EEA5 175 | 176 | 177 | destinationuid 178 | 77559430-B9C7-4DAF-8D9A-D90A208D38A7 179 | modifiers 180 | 0 181 | modifiersubtext 182 | 183 | vitoclose 184 | 185 | 186 | 187 | 188 | createdby 189 | Chris Grieser 190 | description 191 | Academic Phrase Inserter 192 | disabled 193 | 194 | name 195 | Phrasebank 196 | objects 197 | 198 | 199 | config 200 | 201 | concurrently 202 | 203 | escaping 204 | 0 205 | script 206 | 207 | scriptargtype 208 | 1 209 | scriptfile 210 | snippet-triggered-phrase-input.js 211 | type 212 | 8 213 | 214 | type 215 | alfred.workflow.action.script 216 | uid 217 | 7C59D9D5-E270-4938-859E-1CB426F6E866 218 | version 219 | 2 220 | 221 | 222 | config 223 | 224 | focusedappvariable 225 | 226 | focusedappvariablename 227 | 228 | keyword 229 | %%% 230 | 231 | type 232 | alfred.workflow.trigger.snippet 233 | uid 234 | BCF3836D-1020-4079-B421-B3B99867A6E7 235 | version 236 | 1 237 | 238 | 239 | config 240 | 241 | conditions 242 | 243 | 244 | inputstring 245 | {var:quick_random_insert} 246 | matchcasesensitive 247 | 248 | matchmode 249 | 0 250 | matchstring 251 | false 252 | outputlabel 253 | random 254 | uid 255 | 158B1BB4-20C0-4B33-8BF5-EB73E2550DA6 256 | 257 | 258 | elselabel 259 | select 260 | 261 | type 262 | alfred.workflow.utility.conditional 263 | uid 264 | 73B01B35-14DF-497A-97BD-CF927F6E19E9 265 | version 266 | 1 267 | 268 | 269 | config 270 | 271 | autopaste 272 | 273 | clipboardtext 274 | {query} 275 | ignoredynamicplaceholders 276 | 277 | transient 278 | 279 | 280 | type 281 | alfred.workflow.output.clipboard 282 | uid 283 | 4FD9750F-51EA-4B5B-A881-B9297FA29A05 284 | version 285 | 3 286 | 287 | 288 | config 289 | 290 | alfredfiltersresults 291 | 292 | alfredfiltersresultsmatchmode 293 | 0 294 | argumenttreatemptyqueryasnil 295 | 296 | argumenttrimmode 297 | 0 298 | argumenttype 299 | 1 300 | escaping 301 | 0 302 | keyword 303 | phrase 304 | queuedelaycustom 305 | 3 306 | queuedelayimmediatelyinitially 307 | 308 | queuedelaymode 309 | 0 310 | queuemode 311 | 1 312 | runningsubtext 313 | 314 | script 315 | 316 | scriptargtype 317 | 1 318 | scriptfile 319 | phrasebank-searcher.js 320 | subtext 321 | 322 | title 323 | Select Section... 324 | type 325 | 8 326 | withspace 327 | 328 | 329 | type 330 | alfred.workflow.input.scriptfilter 331 | uid 332 | B6F85FB8-06A8-4EAB-A4A4-94C40CDA11BC 333 | version 334 | 3 335 | 336 | 337 | config 338 | 339 | matchmode 340 | 1 341 | matchstring 342 | \n$ 343 | regexcaseinsensitive 344 | 345 | regexmultiline 346 | 347 | replacestring 348 | 349 | 350 | type 351 | alfred.workflow.utility.replace 352 | uid 353 | 7FA67B5E-6A9F-4416-B0A9-D9E2D7CDC22A 354 | version 355 | 2 356 | 357 | 358 | config 359 | 360 | conditions 361 | 362 | 363 | inputstring 364 | {var:quick_random_insert} 365 | matchcasesensitive 366 | 367 | matchmode 368 | 0 369 | matchstring 370 | true 371 | outputlabel 372 | random 373 | uid 374 | 158B1BB4-20C0-4B33-8BF5-EB73E2550DA6 375 | 376 | 377 | elselabel 378 | select 379 | 380 | type 381 | alfred.workflow.utility.conditional 382 | uid 383 | A321CC15-C26E-4013-B083-4830FFB77C57 384 | version 385 | 1 386 | 387 | 388 | config 389 | 390 | alfredfiltersresults 391 | 392 | alfredfiltersresultsmatchmode 393 | 0 394 | argumenttreatemptyqueryasnil 395 | 396 | argumenttrimmode 397 | 0 398 | argumenttype 399 | 1 400 | escaping 401 | 102 402 | queuedelaycustom 403 | 3 404 | queuedelayimmediatelyinitially 405 | 406 | queuedelaymode 407 | 0 408 | queuemode 409 | 1 410 | runningsubtext 411 | 412 | script 413 | 414 | scriptargtype 415 | 1 416 | scriptfile 417 | phrasebank-searcher-2.js 418 | subtext 419 | 420 | title 421 | Search Phrase... 422 | type 423 | 8 424 | withspace 425 | 426 | 427 | type 428 | alfred.workflow.input.scriptfilter 429 | uid 430 | E2F1573B-A697-4E35-A86F-E7DF2813EEA5 431 | version 432 | 3 433 | 434 | 435 | config 436 | 437 | conditions 438 | 439 | 440 | inputstring 441 | 442 | matchcasesensitive 443 | 444 | matchmode 445 | 1 446 | matchstring 447 | back 448 | outputlabel 449 | phrase 450 | uid 451 | CADE715E-68B8-4AE9-A70B-A77D2584B3AE 452 | 453 | 454 | elselabel 455 | back 456 | 457 | type 458 | alfred.workflow.utility.conditional 459 | uid 460 | 77559430-B9C7-4DAF-8D9A-D90A208D38A7 461 | version 462 | 1 463 | 464 | 465 | config 466 | 467 | triggerid 468 | loop-section-selection 469 | 470 | type 471 | alfred.workflow.trigger.external 472 | uid 473 | 0F6C59AD-B421-4B4A-875F-80555B32E69C 474 | version 475 | 1 476 | 477 | 478 | config 479 | 480 | argument 481 | 482 | passthroughargument 483 | 484 | variables 485 | 486 | oneSection 487 | {query} 488 | 489 | 490 | type 491 | alfred.workflow.utility.argument 492 | uid 493 | D1D13A68-D504-447A-8487-2AF0C3E4253E 494 | version 495 | 1 496 | 497 | 498 | config 499 | 500 | externaltriggerid 501 | loop-section-selection 502 | passinputasargument 503 | 504 | passvariables 505 | 506 | workflowbundleid 507 | self 508 | 509 | type 510 | alfred.workflow.output.callexternaltrigger 511 | uid 512 | EB16F746-65FB-418A-9AFB-3016A48C93A3 513 | version 514 | 1 515 | 516 | 517 | readme 518 | with data from https://www.ref-n-write.com/trial/how-to-write-a-research-paper-academic-phrasebank-vocabulary/ 519 | 520 | potentially also incldue data in ther future: http://phrasebank.manchester.ac.uk 521 | 522 | --- 523 | 524 | Thanks to @argentum and @bri. 525 | 526 | Created by @pseudometa aka [Chris Grieser](https://chris-grieser.de/). 527 | uidata 528 | 529 | 0F6C59AD-B421-4B4A-875F-80555B32E69C 530 | 531 | colorindex 532 | 6 533 | xpos 534 | 40 535 | ypos 536 | 475 537 | 538 | 4FD9750F-51EA-4B5B-A881-B9297FA29A05 539 | 540 | xpos 541 | 925 542 | ypos 543 | 350 544 | 545 | 73B01B35-14DF-497A-97BD-CF927F6E19E9 546 | 547 | xpos 548 | 435 549 | ypos 550 | 340 551 | 552 | 77559430-B9C7-4DAF-8D9A-D90A208D38A7 553 | 554 | xpos 555 | 765 556 | ypos 557 | 465 558 | 559 | 7C59D9D5-E270-4938-859E-1CB426F6E866 560 | 561 | note 562 | insert random 563 | xpos 564 | 615 565 | ypos 566 | 300 567 | 568 | 7FA67B5E-6A9F-4416-B0A9-D9E2D7CDC22A 569 | 570 | xpos 571 | 850 572 | ypos 573 | 380 574 | 575 | A321CC15-C26E-4013-B083-4830FFB77C57 576 | 577 | xpos 578 | 435 579 | ypos 580 | 440 581 | 582 | B6F85FB8-06A8-4EAB-A4A4-94C40CDA11BC 583 | 584 | xpos 585 | 210 586 | ypos 587 | 380 588 | 589 | BCF3836D-1020-4079-B421-B3B99867A6E7 590 | 591 | note 592 | select section, then insert random phrase 593 | xpos 594 | 40 595 | ypos 596 | 320 597 | 598 | D1D13A68-D504-447A-8487-2AF0C3E4253E 599 | 600 | xpos 601 | 550 602 | ypos 603 | 480 604 | 605 | E2F1573B-A697-4E35-A86F-E7DF2813EEA5 606 | 607 | xpos 608 | 615 609 | ypos 610 | 450 611 | 612 | EB16F746-65FB-418A-9AFB-3016A48C93A3 613 | 614 | colorindex 615 | 6 616 | xpos 617 | 925 618 | ypos 619 | 485 620 | 621 | 622 | variables 623 | 624 | quick_random_insert 625 | true 626 | 627 | variablesdontexport 628 | 629 | quick_random_insert 630 | 631 | version 632 | 0.5 633 | webaddress 634 | https://chris-grieser.de/ 635 | 636 | 637 | -------------------------------------------------------------------------------- /Alfred Workflow/phrasebank-searcher-2.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | ObjC.import('stdlib'); 4 | function alfredMatcher (str){ 5 | return str.replace (/[-\(\)_\.]/g," ") + " " + str; 6 | } 7 | /* Randomize array in-place using Durstenfeld shuffle algorithm */ 8 | function shuffleArray(array) { 9 | for (let i = array.length - 1; i > 0; i--) { 10 | const j = Math.floor(Math.random() * (i + 1)); 11 | [array[i], array[j]] = [array[j], array[i]]; 12 | } 13 | } 14 | 15 | let jsonArray = []; 16 | const sectionName = JSON.parse($.getenv('oneSection')).section; 17 | let phrases_array = JSON.parse($.getenv('oneSection')).phrases; 18 | shuffleArray(phrases_array); 19 | 20 | phrases_array.forEach(phrase => { 21 | jsonArray.push({ 22 | 'title': phrase, 23 | 'subtitle': sectionName, 24 | 'match': alfredMatcher(phrase), 25 | 'arg': phrase, 26 | }); 27 | }); 28 | 29 | jsonArray.push({ 30 | 'title': "⬅️ Go back", 31 | 'subtitle': "to section selection", 32 | 'match': "up back section previous", 33 | 'arg': "back", 34 | }); 35 | 36 | JSON.stringify({ items: jsonArray }); 37 | 38 | -------------------------------------------------------------------------------- /Alfred Workflow/phrasebank-searcher.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | ObjC.import('Foundation'); 4 | const readFile = function (path, encoding) { 5 | !encoding && (encoding = $.NSUTF8StringEncoding); 6 | const fm = $.NSFileManager.defaultManager; 7 | const data = fm.contentsAtPath(path); 8 | const str = $.NSString.alloc.initWithDataEncoding(data, encoding); 9 | return ObjC.unwrap(str); 10 | }; 11 | function alfredMatcher (str){ 12 | return str.replace (/[-\(\)_\.]/g," ") + " " + str; 13 | } 14 | 15 | 16 | function mdToJSON(content) { 17 | const lines = content.split('\n'); 18 | let pb = []; 19 | 20 | for (let line of lines) { 21 | // A new heading indicates a new section in the pb 22 | if (line.startsWith('## ')) { 23 | let section = line.slice(3); 24 | pb.push({ section, desc: '', keywords: [], phrases: [] }); 25 | last = pb.length - 1; //last item Index 26 | // Blockquotes indicate description 27 | } else if (line.startsWith('> ')) { 28 | pb[last].desc = line.slice(2); 29 | // Bullets indicates keywords 30 | } else if (line.startsWith('- ')) { 31 | let kws = line.slice(2).split(','); 32 | pb[last].keywords.push(...kws); 33 | // Every other non-blank line is considered a phrase 34 | } else if (line.trim() !== '') { 35 | pb[last].phrases.push(line); 36 | } 37 | } 38 | return pb; 39 | } 40 | 41 | 42 | let jsonArray = []; 43 | const phraseJSON = mdToJSON(readFile("phrasebank.md")); 44 | 45 | phraseJSON.forEach(section => { 46 | let sectionName = section.section; 47 | 48 | jsonArray.push({ 49 | 'title': sectionName, 50 | 'uid': sectionName, 51 | 'subtitle': section.desc, 52 | 'match': section.keywords.join(" ") + " " + sectionName, 53 | 'arg': JSON.stringify(section), 54 | }); 55 | }); 56 | 57 | JSON.stringify({ items: jsonArray }); 58 | -------------------------------------------------------------------------------- /Alfred Workflow/snippet-triggered-phrase-input.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | function run (argv){ 4 | 5 | //input 6 | const phrase_array = JSON.parse(argv.join("")).phrases; 7 | let randomIndex = Math.floor(Math.random() * (phrase_array.length + 1)); 8 | 9 | return phrase_array[randomIndex]; 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.0.0](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.16...1.0.0) (2021-12-09) 6 | 7 | 8 | ### Features 9 | 10 | * :fire: Remove remote PB ([9b37ec8](https://github.com/SkepticMystic/Phrase-Bank/commit/9b37ec86c579969eb0145f4ba32b9213dfbf99fb)) 11 | 12 | ### 0.0.16 (2021-10-08) 13 | 14 | 15 | ### Features 16 | 17 | * :sparkles: `Shift + Enter` works to insert random phrase from selected section ([cca1129](https://github.com/SkepticMystic/Phrase-Bank/commit/cca1129e441188b4cb376149c5091014a33066c3)) 18 | * :sparkles: Add back remote PB ([76c6084](https://github.com/SkepticMystic/Phrase-Bank/commit/76c6084adf93754af0d9e36d1cf9f496fc7469f2)) 19 | * :sparkles: Allow arbitrary content before the phrase banks starts ([0b92457](https://github.com/SkepticMystic/Phrase-Bank/commit/0b92457501c5c0b224d4819817bef50bf591acac)) 20 | * :sparkles: Bottom-up groups! ([b904f92](https://github.com/SkepticMystic/Phrase-Bank/commit/b904f926757872be2fd88a380a4d712615465312)) 21 | * :sparkles: First version that _should_ work ([8046696](https://github.com/SkepticMystic/Phrase-Bank/commit/8046696a55c3e5375bfce3dd628107d60fc793cb)) 22 | * :sparkles: Ignore comments and tables (for now) ([48349e2](https://github.com/SkepticMystic/Phrase-Bank/commit/48349e2b330569c697b06636555f9880e3718b36)) 23 | * :sparkles: Initial plugin code ([9cf0c6a](https://github.com/SkepticMystic/Phrase-Bank/commit/9cf0c6a398509c1eceba140f9ad1f4035162b8dd)) 24 | * :sparkles: Misc improvements. Actaul features are in other commits ([13c2145](https://github.com/SkepticMystic/Phrase-Bank/commit/13c214540a5249437372f311442ac2631c9a2c72)) 25 | * :sparkles: Much more progress! ([c15eb65](https://github.com/SkepticMystic/Phrase-Bank/commit/c15eb65bebcab58ff070b0561ba8c6f96ffc1761)) 26 | * :sparkles: Multiple PB files now get merged into one global PB ([87c2dfe](https://github.com/SkepticMystic/Phrase-Bank/commit/87c2dfe9492512d1b52893e573b8e167e780f60a)) 27 | * :sparkles: Option to go back to section-suggester ([92a1132](https://github.com/SkepticMystic/Phrase-Bank/commit/92a11325677976a06a6ad718cd0f3c19ab21feb9)) 28 | * :sparkles: Option to pull from remote PB ([788d64f](https://github.com/SkepticMystic/Phrase-Bank/commit/788d64f8b5dd61c51fbabf5a8071d57b99e07c83)) 29 | * :sparkles: Support keywords! ([67b4ea1](https://github.com/SkepticMystic/Phrase-Bank/commit/67b4ea1d873758414fbe6d5c746e01b499435fe0)) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * :bug: for of loop to await multiple async events ([1aa13a4](https://github.com/SkepticMystic/Phrase-Bank/commit/1aa13a4f760f4a1d5217676ad9c85ffc7681b423)) 35 | * :bug: Go BACK to first modal ([b4b99b9](https://github.com/SkepticMystic/Phrase-Bank/commit/b4b99b94ea3a78866794425d226ea13d5dd80978)) 36 | * :bug: Handle empty localPBs better ([8344c57](https://github.com/SkepticMystic/Phrase-Bank/commit/8344c571cbc55d1a8173563572bda504e20469d6)) 37 | * :bug: Less harsh pbFile finding ([e256fb0](https://github.com/SkepticMystic/Phrase-Bank/commit/e256fb065a68c4d380b2ccd06b71cb2a85235712)) 38 | * :bug: Less strict on pbFilePaths, better promise awaiting ([2e15449](https://github.com/SkepticMystic/Phrase-Bank/commit/2e1544956632dde7467cd899d91a71c666cbb3af)) 39 | * :bug: Point to remotePB ([13e9094](https://github.com/SkepticMystic/Phrase-Bank/commit/13e9094a8de1660e2fa15134d784dac3ced9a04b)) 40 | * :bug: Shift + Enter now selects a random phrase from that section ([fd2d48b](https://github.com/SkepticMystic/Phrase-Bank/commit/fd2d48bdb15a88a6ffeba7dda235f0bfd98af09f)) 41 | * :bug: skip over empty pbLocalFile paths ([dd93ad5](https://github.com/SkepticMystic/Phrase-Bank/commit/dd93ad5ba49d3186b7d45c1412bcac22d3ee107a)) 42 | * :bug: Trim keyword input ([5a5b48c](https://github.com/SkepticMystic/Phrase-Bank/commit/5a5b48c86b028149c3429261232c33d762d858c0)) 43 | * :bug: Use remotePB with needing localPB ([79fac6c](https://github.com/SkepticMystic/Phrase-Bank/commit/79fac6c8410b8d336390f53c3ee7b42444f977ca)) 44 | * Better grouping ([9c67351](https://github.com/SkepticMystic/Phrase-Bank/commit/9c673516b9a1b17b2a292770ff1357233858883a)) 45 | 46 | ### [0.0.15](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.14...0.0.15) (2021-10-02) 47 | 48 | ### [0.0.14](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.13...0.0.14) (2021-10-02) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * :bug: Handle empty localPBs better ([f801ec6](https://github.com/SkepticMystic/Phrase-Bank/commit/f801ec649a89c93bc2e74b43236c91873387a341)) 54 | 55 | ### [0.0.13](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.12...0.0.13) (2021-10-02) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * :bug: Go BACK to first modal ([d073147](https://github.com/SkepticMystic/Phrase-Bank/commit/d073147e4fb2ee5a802dadb9d243c887ff24c396)) 61 | * :bug: skip over empty pbLocalFile paths ([c140a55](https://github.com/SkepticMystic/Phrase-Bank/commit/c140a556c3d42d73fda6c166e0fe8c915221627c)) 62 | 63 | ### [0.0.12](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.11...0.0.12) (2021-10-02) 64 | 65 | ### [0.0.11](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.10...0.0.11) (2021-10-02) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * :bug: for of loop to await multiple async events ([91d43cf](https://github.com/SkepticMystic/Phrase-Bank/commit/91d43cf2b55eaee36a0600cdfb0b7b33fb52a698)) 71 | * Better grouping ([56e513a](https://github.com/SkepticMystic/Phrase-Bank/commit/56e513a5e6e2bb068f3b9e6b708b7c20db7d8882)) 72 | 73 | ### [0.0.10](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.9...0.0.10) (2021-10-02) 74 | 75 | 76 | ### Features 77 | 78 | * :sparkles: Bottom-up groups! ([1458972](https://github.com/SkepticMystic/Phrase-Bank/commit/1458972a0a32b51e0eb2f5c1d30bac1e7b18eb19)) 79 | 80 | ### [0.0.9](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.8...0.0.9) (2021-10-01) 81 | 82 | 83 | ### Features 84 | 85 | * :sparkles: `Shift + Enter` works to insert random phrase from selected section ([8faee66](https://github.com/SkepticMystic/Phrase-Bank/commit/8faee66455d3f504c368b636ef013d1545edf825)) 86 | * :sparkles: Misc improvements. Actaul features are in other commits ([bbfaab5](https://github.com/SkepticMystic/Phrase-Bank/commit/bbfaab5948f42f07369c6e878f26154cd7cb9818)) 87 | 88 | 89 | ### Bug Fixes 90 | 91 | * :bug: Less harsh pbFile finding ([e55c813](https://github.com/SkepticMystic/Phrase-Bank/commit/e55c8130f591c607b0551893b1f5abc10cff9862)) 92 | 93 | ### [0.0.8](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.7...0.0.8) (2021-10-01) 94 | 95 | 96 | ### Features 97 | 98 | * :sparkles: Allow arbitrary content before the phrase banks starts ([1e14a0d](https://github.com/SkepticMystic/Phrase-Bank/commit/1e14a0d4819686d1fb3b49ba70574a6c914261f5)) 99 | 100 | ### [0.0.7](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.6...0.0.7) (2021-10-01) 101 | 102 | 103 | ### Features 104 | 105 | * :sparkles: Option to go back to section-suggester ([183f1a0](https://github.com/SkepticMystic/Phrase-Bank/commit/183f1a09e7f1d9af715d371b1dc8dcc17fb1add0)) 106 | 107 | 108 | ### Bug Fixes 109 | 110 | * :bug: Shift + Enter now selects a random phrase from that section ([7ce28a7](https://github.com/SkepticMystic/Phrase-Bank/commit/7ce28a75f63f870213f3b2846cb6de20f2893a3f)) 111 | 112 | ### [0.0.6](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.5...0.0.6) (2021-10-01) 113 | 114 | 115 | ### Features 116 | 117 | * :sparkles: Support keywords! ([830f27e](https://github.com/SkepticMystic/Phrase-Bank/commit/830f27e5fe79ae7715382512c098fa2d8ca85603)) 118 | 119 | 120 | ### Bug Fixes 121 | 122 | * :bug: Point to remotePB ([fabd359](https://github.com/SkepticMystic/Phrase-Bank/commit/fabd359c8bea39d7a4294d053d1ab400761c92fe)) 123 | * :bug: Trim keyword input ([1ef3623](https://github.com/SkepticMystic/Phrase-Bank/commit/1ef3623b523eb7fbec9f18ea6e09ef1068d51840)) 124 | 125 | ### [0.0.5](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.4...0.0.5) (2021-10-01) 126 | 127 | 128 | ### Bug Fixes 129 | 130 | * :bug: Less strict on pbFilePaths, better promise awaiting ([4be12a1](https://github.com/SkepticMystic/Phrase-Bank/commit/4be12a13cba2deb77a68ff07b0bfc12f8fc6e300)) 131 | 132 | ### [0.0.4](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.3...0.0.4) (2021-10-01) 133 | 134 | 135 | ### Features 136 | 137 | * :sparkles: Ignore comments and tables (for now) ([7fc5e53](https://github.com/SkepticMystic/Phrase-Bank/commit/7fc5e53cc75dffc8f999c8fdee7cb38b60cf86cc)) 138 | * :sparkles: Option to pull from remote PB ([e0eb5ec](https://github.com/SkepticMystic/Phrase-Bank/commit/e0eb5eca2590120cc2a02de436375b378850ea97)) 139 | 140 | 141 | ### Bug Fixes 142 | 143 | * :bug: Use remotePB with needing localPB ([4e9dfd2](https://github.com/SkepticMystic/Phrase-Bank/commit/4e9dfd228bc09cf506e88150498d2c973a472f21)) 144 | 145 | ### [0.0.3](https://github.com/SkepticMystic/Phrase-Bank/compare/0.0.2...0.0.3) (2021-10-01) 146 | 147 | 148 | ### Features 149 | 150 | * :sparkles: Multiple PB files now get merged into one global PB ([05bcbdf](https://github.com/SkepticMystic/Phrase-Bank/commit/05bcbdfb3430a9c79eccec2e1513687584c352e9)) 151 | 152 | ### 0.0.2 (2021-10-01) 153 | 154 | 155 | ### Features 156 | 157 | * :sparkles: First version that _should_ work ([3408359](https://github.com/SkepticMystic/Phrase-Bank/commit/34083591e4de89b66b3fb92127b57ba4a4276b6a)) 158 | * :sparkles: Initial plugin code ([db3985d](https://github.com/SkepticMystic/Phrase-Bank/commit/db3985da021d53c80a0fd428bc4e2a9a40f65667)) 159 | * :sparkles: Much more progress! ([e957f6c](https://github.com/SkepticMystic/Phrase-Bank/commit/e957f6c59179d6c77a7f65bc40ca293bc04fc829)) 160 | -------------------------------------------------------------------------------- /JSON2MD.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | ObjC.import('Foundation'); 3 | const readFile = function (path, encoding) { 4 | !encoding && (encoding = $.NSUTF8StringEncoding); 5 | const fm = $.NSFileManager.defaultManager; 6 | const data = fm.contentsAtPath(path); 7 | const str = $.NSString.alloc.initWithDataEncoding(data, encoding); 8 | return ObjC.unwrap(str); 9 | }; 10 | 11 | const pbJSON = JSON.parse(readFile ("phrasebank.json")); 12 | 13 | let mdLines = []; 14 | pbJSON.forEach(item => { 15 | mdLines.push("## " + item.section); 16 | mdLines.push(""); //blank line 17 | 18 | if (item.desc) { 19 | mdLines.push("> " + item.desc); 20 | mdLines.push(""); //blank line 21 | } else { 22 | mdLines.push("> (source: ref-n-write)"); 23 | mdLines.push(""); //blank line 24 | } 25 | 26 | 27 | if (item.keywords) { 28 | item.keywords.forEach(keyword =>{ 29 | mdLines.push("- " + keyword); 30 | }); 31 | mdLines.push(""); //blank line 32 | } 33 | 34 | item.phrases.forEach(phrase =>{ 35 | mdLines.push(phrase); 36 | }); 37 | mdLines.push(""); //blank line 38 | mdLines.push(""); //blank line 39 | }); 40 | 41 | let newMD = mdLines.join("\n"); 42 | 43 | 44 | app = Application.currentApplication(); 45 | app.includeStandardAdditions = true; 46 | app.doShellScript('echo "' + newMD + '" > phrases.md'); 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phrase-Bank 2 | 3 | ## Format 4 | 5 | PhraseBank requires a specifc syntax to be used, so that it can parse and display the results. 6 | 7 | Each heading (level 3) represents a phrase type, which can have its own description, keywords, groups, and phrases. 8 | Formatted like so: 9 | 10 | ```md 11 | ### Phrase Type 12 | 13 | ! Group1, Group2 14 | 15 | > Description 16 | 17 | - Keywords in a, comma separated, list 18 | - Or in more bullet points 19 | 20 | One phrase per line 21 | 22 | Another phrase 23 | ``` 24 | 25 | A `group` is a higher-level _grouping_ of different phrase types. It is like a folder for different phrase types, which are themselves subfolders for various phrases. 26 | 27 | This approaching to grouping is bottom-up, meaning each phrase type can belong to multiple groups. 28 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var obsidian = require('obsidian'); 4 | 5 | /*! ***************************************************************************** 6 | Copyright (c) Microsoft Corporation. 7 | 8 | Permission to use, copy, modify, and/or distribute this software for any 9 | purpose with or without fee is hereby granted. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | PERFORMANCE OF THIS SOFTWARE. 18 | ***************************************************************************** */ 19 | /* global Reflect, Promise */ 20 | 21 | var extendStatics = function(d, b) { 22 | extendStatics = Object.setPrototypeOf || 23 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 24 | function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; 25 | return extendStatics(d, b); 26 | }; 27 | 28 | function __extends(d, b) { 29 | if (typeof b !== "function" && b !== null) 30 | throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); 31 | extendStatics(d, b); 32 | function __() { this.constructor = d; } 33 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 34 | } 35 | 36 | var __assign = function() { 37 | __assign = Object.assign || function __assign(t) { 38 | for (var s, i = 1, n = arguments.length; i < n; i++) { 39 | s = arguments[i]; 40 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 41 | } 42 | return t; 43 | }; 44 | return __assign.apply(this, arguments); 45 | }; 46 | 47 | function __awaiter(thisArg, _arguments, P, generator) { 48 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 49 | return new (P || (P = Promise))(function (resolve, reject) { 50 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 51 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 52 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 53 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 54 | }); 55 | } 56 | 57 | function __generator(thisArg, body) { 58 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 59 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 60 | function verb(n) { return function (v) { return step([n, v]); }; } 61 | function step(op) { 62 | if (f) throw new TypeError("Generator is already executing."); 63 | while (_) try { 64 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 65 | if (y = 0, t) op = [op[0] & 2, t.value]; 66 | switch (op[0]) { 67 | case 0: case 1: t = op; break; 68 | case 4: _.label++; return { value: op[1], done: false }; 69 | case 5: _.label++; y = op[1]; op = [0]; continue; 70 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 71 | default: 72 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 73 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 74 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 75 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 76 | if (t[2]) _.ops.pop(); 77 | _.trys.pop(); continue; 78 | } 79 | op = body.call(thisArg, _); 80 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 81 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 82 | } 83 | } 84 | 85 | function __spreadArray(to, from, pack) { 86 | if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { 87 | if (ar || !(i in from)) { 88 | if (!ar) ar = Array.prototype.slice.call(from, 0, i); 89 | ar[i] = from[i]; 90 | } 91 | } 92 | return to.concat(ar || Array.prototype.slice.call(from)); 93 | } 94 | 95 | function getActiveView(plugin) { 96 | return plugin.app.workspace.getActiveViewOfType(obsidian.MarkdownView); 97 | } 98 | function removeDuplicates(a) { 99 | var result = []; 100 | a.forEach(function (item) { 101 | if (result.indexOf(item) < 0) { 102 | result.push(item); 103 | } 104 | }); 105 | return result; 106 | } 107 | 108 | var PBPhrasesFuzzySuggestModal = /** @class */ (function (_super) { 109 | __extends(PBPhrasesFuzzySuggestModal, _super); 110 | function PBPhrasesFuzzySuggestModal(app, plugin, phrases) { 111 | var _this = _super.call(this, app) || this; 112 | _this.app = app; 113 | _this.plugin = plugin; 114 | _this.phrases = __spreadArray(__spreadArray([], phrases, true), ["BACK"], false); 115 | _this.settings = plugin.settings; 116 | return _this; 117 | } 118 | PBPhrasesFuzzySuggestModal.prototype.getItems = function () { 119 | return this.phrases; 120 | }; 121 | PBPhrasesFuzzySuggestModal.prototype.getItemText = function (item) { 122 | return "\uD83D\uDCAC " + item; 123 | }; 124 | PBPhrasesFuzzySuggestModal.prototype.renderSuggestion = function (item, el) { 125 | _super.prototype.renderSuggestion.call(this, item, el); 126 | }; 127 | PBPhrasesFuzzySuggestModal.prototype.onChooseItem = function (item, evt) { 128 | if (item === "BACK") { 129 | this.close(); 130 | new PBPhraseTypeOrGroupsFuzzySuggestModal(this.app, this.plugin).open(); 131 | } 132 | else { 133 | try { 134 | var activeView = getActiveView(this.plugin); 135 | var activeEditor = activeView.editor; 136 | var editorRange = activeEditor.getCursor("from"); 137 | activeEditor.replaceRange(item, editorRange); 138 | } 139 | catch (error) { 140 | console.log(error); 141 | } 142 | } 143 | }; 144 | return PBPhrasesFuzzySuggestModal; 145 | }(obsidian.FuzzySuggestModal)); 146 | 147 | var PBPhraseTypeFuzzySuggestModal = /** @class */ (function (_super) { 148 | __extends(PBPhraseTypeFuzzySuggestModal, _super); 149 | function PBPhraseTypeFuzzySuggestModal(app, plugin, pb) { 150 | var _this = _super.call(this, app) || this; 151 | _this.app = app; 152 | _this.plugin = plugin; 153 | _this.pb = __spreadArray(__spreadArray([], pb, true), [ 154 | { 155 | phraseType: "BACK", 156 | fileName: "", 157 | desc: "", 158 | groups: [], 159 | keywords: [], 160 | phrases: [] 161 | }, 162 | ], false); 163 | _this.settings = plugin.settings; 164 | _this.scope.register(["Shift"], "Enter", function (evt) { 165 | // @ts-ignore 166 | _this.chooser.useSelectedItem(evt); 167 | return false; 168 | }); 169 | return _this; 170 | } 171 | PBPhraseTypeFuzzySuggestModal.prototype.getItems = function () { 172 | return this.pb; 173 | }; 174 | PBPhraseTypeFuzzySuggestModal.prototype.getItemText = function (item) { 175 | return (item.phraseType + "|||" + item.keywords.join(", ") + ", " + item.fileName); 176 | }; 177 | PBPhraseTypeFuzzySuggestModal.prototype.renderSuggestion = function (item, el) { 178 | _super.prototype.renderSuggestion.call(this, item, el); 179 | el.innerText = el.innerText.split("|||")[0]; 180 | this.updateSuggestionElWithDesc(item, el); 181 | }; 182 | PBPhraseTypeFuzzySuggestModal.prototype.updateSuggestionElWithDesc = function (item, el) { 183 | if (item.item.desc) { 184 | el.createEl("div", { 185 | text: item.item.desc, 186 | cls: "PB-Desc" 187 | }); 188 | } 189 | }; 190 | PBPhraseTypeFuzzySuggestModal.prototype.onChooseItem = function (item, evt) { 191 | if (item.phraseType === "BACK") { 192 | this.close(); 193 | new PBPhraseTypeOrGroupsFuzzySuggestModal(this.app, this.plugin).open(); 194 | } 195 | else { 196 | if (!evt.shiftKey) { 197 | this.close(); 198 | new PBPhrasesFuzzySuggestModal(this.app, this.plugin, item.phrases).open(); 199 | } 200 | else { 201 | var randI = Math.floor(Math.random() * (item.phrases.length - 1)); 202 | var randPhrase = item.phrases[randI]; 203 | try { 204 | this.close(); 205 | var activeView = getActiveView(this.plugin); 206 | var activeEditor = activeView.editor; 207 | var editorRange = activeEditor.getCursor("from"); 208 | activeEditor.replaceRange(randPhrase, editorRange); 209 | } 210 | catch (error) { 211 | console.log(error); 212 | } 213 | } 214 | } 215 | }; 216 | return PBPhraseTypeFuzzySuggestModal; 217 | }(obsidian.FuzzySuggestModal)); 218 | 219 | var PBPhraseTypeOrGroupsFuzzySuggestModal = /** @class */ (function (_super) { 220 | __extends(PBPhraseTypeOrGroupsFuzzySuggestModal, _super); 221 | function PBPhraseTypeOrGroupsFuzzySuggestModal(app, plugin) { 222 | var _this = _super.call(this, app) || this; 223 | _this.app = app; 224 | _this.plugin = plugin; 225 | _this.pb = plugin.pb; 226 | _this.settings = plugin.settings; 227 | _this.scope.register(["Shift"], "Enter", function (evt) { 228 | // @ts-ignore 229 | _this.chooser.useSelectedItem(evt); 230 | return false; 231 | }); 232 | return _this; 233 | } 234 | PBPhraseTypeOrGroupsFuzzySuggestModal.prototype.getItems = function () { 235 | var groups = this.pb.map(function (item) { return item.groups; }).flat(3); 236 | var noDupGroups = []; 237 | groups.forEach(function (group) { 238 | if (!noDupGroups.some(function (g) { return g.name === group.name; })) { 239 | noDupGroups.push(group); 240 | } 241 | }); 242 | return __spreadArray(__spreadArray([], noDupGroups, true), this.pb, true); 243 | }; 244 | PBPhraseTypeOrGroupsFuzzySuggestModal.prototype.getItemText = function (item) { 245 | if (item.phraseType) { 246 | return (item.phraseType + 247 | "|||" + 248 | item.keywords.join(", ") + 249 | ", " + 250 | item.fileName); 251 | } 252 | else if (item.name) { 253 | return "📂 " + item.name; 254 | } 255 | }; 256 | PBPhraseTypeOrGroupsFuzzySuggestModal.prototype.renderSuggestion = function (item, el) { 257 | _super.prototype.renderSuggestion.call(this, item, el); 258 | el.innerText = el.innerText.split("|||")[0]; 259 | this.updateSuggestionElWithDesc(item, el); 260 | }; 261 | PBPhraseTypeOrGroupsFuzzySuggestModal.prototype.updateSuggestionElWithDesc = function (item, el) { 262 | if (item.item.desc) { 263 | el.createEl("div", { 264 | text: item.item.desc, 265 | cls: "PB-Desc" 266 | }); 267 | } 268 | }; 269 | PBPhraseTypeOrGroupsFuzzySuggestModal.prototype.onChooseItem = function (item, evt) { 270 | if (item.phraseType) { 271 | if (!evt.shiftKey) { 272 | new PBPhrasesFuzzySuggestModal(this.app, this.plugin, item.phrases, this.settings).open(); 273 | } 274 | else { 275 | var randI = Math.floor(Math.random() * (item.phrases.length - 1)); 276 | var randPhrase = item.phrases[randI]; 277 | try { 278 | this.close(); 279 | var activeView = getActiveView(this.plugin); 280 | var activeEditor = activeView.editor; 281 | var editorRange = activeEditor.getCursor("from"); 282 | activeEditor.replaceRange(randPhrase, editorRange); 283 | } 284 | catch (error) { 285 | console.log(error); 286 | } 287 | } 288 | } 289 | else if (item.name) { 290 | console.log(item.name); 291 | var filteredPB = this.pb.filter(function (pbItem) { 292 | return pbItem.groups.some(function (group) { return group.name === item.name; }); 293 | }); 294 | console.log({ filteredPB: filteredPB }); 295 | this.close(); 296 | new PBPhraseTypeFuzzySuggestModal(this.app, this.plugin, filteredPB, this.settings).open(); 297 | } 298 | }; 299 | return PBPhraseTypeOrGroupsFuzzySuggestModal; 300 | }(obsidian.FuzzySuggestModal)); 301 | 302 | var PBSettingTab = /** @class */ (function (_super) { 303 | __extends(PBSettingTab, _super); 304 | function PBSettingTab(app, plugin) { 305 | var _this = _super.call(this, app, plugin) || this; 306 | _this.plugin = plugin; 307 | return _this; 308 | } 309 | PBSettingTab.prototype.display = function () { 310 | var _this = this; 311 | var containerEl = this.containerEl; 312 | var _a = this.plugin, settings = _a.settings; _a.saveSettings; 313 | containerEl.empty(); 314 | containerEl.createEl("h2", { text: "Settings for Phrase Bank" }); 315 | new obsidian.Setting(containerEl) 316 | .setName("Phrase Bank file names") 317 | .setDesc("Names of your phrase bank.md files in your vault. You can also enter a comma-separated list of pb.md filenames, and the plugin will merge them into one global PB") 318 | .addText(function (tc) { 319 | tc.setValue(settings.pbFileNames.join(", ")); 320 | tc.inputEl.onblur = function () { return __awaiter(_this, void 0, void 0, function () { 321 | var value; 322 | return __generator(this, function (_a) { 323 | switch (_a.label) { 324 | case 0: 325 | value = tc.inputEl.value; 326 | settings.pbFileNames = value.split(",").map(function (path) { return path.trim(); }); 327 | return [4 /*yield*/, this.plugin.saveSettings()]; 328 | case 1: 329 | _a.sent(); 330 | return [4 /*yield*/, this.plugin.refreshPB()]; 331 | case 2: 332 | _a.sent(); 333 | return [2 /*return*/]; 334 | } 335 | }); 336 | }); }; 337 | }); 338 | // new Setting(containerEl) 339 | // .setName("Use Remote Phrase Bank") 340 | // .setDesc( 341 | // "Use the content of the community-maintaned PB from: https://raw.githubusercontent.com/SkepticMystic/Phrase-Bank/main/Phrase%20Bank%20copy.md" 342 | // ) 343 | // .addToggle((tg) => { 344 | // tg.setValue(settings.useRemotePB).onChange(async (val) => { 345 | // settings.useRemotePB = val; 346 | // await this.plugin.saveSettings(); 347 | // await this.plugin.refreshPB(); 348 | // }); 349 | // }); 350 | }; 351 | return PBSettingTab; 352 | }(obsidian.PluginSettingTab)); 353 | 354 | var DEFAULT_SETTINGS = { 355 | pbFileNames: [""] 356 | }; 357 | 358 | var PBPlugin = /** @class */ (function (_super) { 359 | __extends(PBPlugin, _super); 360 | function PBPlugin() { 361 | return _super !== null && _super.apply(this, arguments) || this; 362 | } 363 | PBPlugin.prototype.onload = function () { 364 | return __awaiter(this, void 0, void 0, function () { 365 | var _this = this; 366 | return __generator(this, function (_a) { 367 | switch (_a.label) { 368 | case 0: 369 | console.log("Loading PhraseBank plugin"); 370 | return [4 /*yield*/, this.loadSettings()]; 371 | case 1: 372 | _a.sent(); 373 | this.addCommand({ 374 | id: "phrase-bank-suggestor", 375 | name: "Pick from Phrase Bank", 376 | callback: function () { 377 | return new PBPhraseTypeOrGroupsFuzzySuggestModal(_this.app, _this).open(); 378 | } 379 | }); 380 | this.addCommand({ 381 | id: "refresh-phrase-bank", 382 | name: "Refresh Phrase Bank", 383 | callback: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { 384 | switch (_a.label) { 385 | case 0: return [4 /*yield*/, this.refreshPB()]; 386 | case 1: return [2 /*return*/, _a.sent()]; 387 | } 388 | }); }); } 389 | }); 390 | this.addSettingTab(new PBSettingTab(this.app, this)); 391 | this.pb = []; 392 | this.app.workspace.onLayoutReady(function () { return __awaiter(_this, void 0, void 0, function () { 393 | return __generator(this, function (_a) { 394 | switch (_a.label) { 395 | case 0: 396 | // const resp = await fetch( 397 | // "https://raw.githubusercontent.com/SkepticMystic/Phrase-Bank/main/Phrase%20Banks/Master%20PB.md?token=AQ3RB3AKC7BM2OM35YJWVQTBNFCNQ" 398 | // ); 399 | // this.remotePBmd = await resp.text(); 400 | return [4 /*yield*/, this.refreshPB()]; 401 | case 1: 402 | // const resp = await fetch( 403 | // "https://raw.githubusercontent.com/SkepticMystic/Phrase-Bank/main/Phrase%20Banks/Master%20PB.md?token=AQ3RB3AKC7BM2OM35YJWVQTBNFCNQ" 404 | // ); 405 | // this.remotePBmd = await resp.text(); 406 | _a.sent(); 407 | return [2 /*return*/]; 408 | } 409 | }); 410 | }); }); 411 | return [2 /*return*/]; 412 | } 413 | }); 414 | }); 415 | }; 416 | PBPlugin.prototype.mdToJSON = function (content, fileName) { 417 | var _a; 418 | var lines = content.split("\n"); 419 | var pb = []; 420 | for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) { 421 | var line = lines_1[_i]; 422 | if (pb.length === 0 && !line.startsWith("### ")) ; 423 | else if (line.startsWith("### ")) { 424 | // A new heading indicates a new section in the pb 425 | var section = line.slice(4); 426 | pb.push({ 427 | fileName: fileName, 428 | phraseType: section, 429 | groups: [], 430 | desc: "", 431 | keywords: [], 432 | phrases: [] 433 | }); 434 | } 435 | else if (line.startsWith("!") || line.startsWith("↑")) { 436 | // Groups start with '!' or '↑' 437 | var groups = line.split(/!|↑/)[1].trim().split(","); 438 | groups.forEach(function (group) { 439 | pb.last().groups.push({ name: group.trim(), keywords: [] }); 440 | }); 441 | } 442 | else if (line.startsWith(">")) { 443 | // Blockquotes indicate description 444 | pb.last().desc = line.split(">")[1].trim(); 445 | } 446 | else if (line.startsWith("- ")) { 447 | // Bullets indicates keywords 448 | var kws = line 449 | .slice(2) 450 | .split(",") 451 | .map(function (kw) { return kw.trim(); }); 452 | (_a = pb.last().keywords).push.apply(_a, kws); 453 | } 454 | else if (line.startsWith("%%")) ; 455 | else if (line.startsWith("|")) ; 456 | else if (line.trim() !== "") { 457 | // Every other non-blank line is considered a phrase 458 | pb.last().phrases.push(line); 459 | } 460 | } 461 | return pb; 462 | }; 463 | PBPlugin.prototype.mergePBs = function (localPBs) { 464 | var globalPB = []; 465 | localPBs.forEach(function (localPB) { 466 | localPB.forEach(function (pbItem) { 467 | var existingI = globalPB.findIndex(function (pb) { return pb.phraseType === pbItem.phraseType; }); 468 | if (existingI > -1) { 469 | globalPB[existingI].phrases = removeDuplicates(__spreadArray(__spreadArray([], globalPB[existingI].phrases, true), pbItem.phrases, true)); 470 | globalPB[existingI].keywords = removeDuplicates(__spreadArray(__spreadArray([], globalPB[existingI].keywords, true), pbItem.keywords, true)); 471 | // globalPB[existingPBSection].phrases.push(...pbItem.phrases) 472 | // globalPB[existingPBSection].keywords.push(...pbItem.keywords) 473 | if (globalPB[existingI].desc === "") { 474 | globalPB[existingI].desc = pbItem.desc; 475 | } 476 | if (globalPB[existingI].fileName !== pbItem.fileName) { 477 | globalPB[existingI].fileName = 478 | globalPB[existingI].fileName + (" " + pbItem.fileName); 479 | } 480 | } 481 | else { 482 | globalPB.push(__assign({}, pbItem)); 483 | } 484 | }); 485 | }); 486 | return globalPB; 487 | }; 488 | PBPlugin.prototype.buildLocalPBs = function () { 489 | return __awaiter(this, void 0, void 0, function () { 490 | var localPBs, currFile, _i, _a, path, pbFile, content, localPB; 491 | return __generator(this, function (_b) { 492 | switch (_b.label) { 493 | case 0: 494 | localPBs = []; 495 | currFile = this.app.workspace.getActiveFile(); 496 | _i = 0, _a = this.settings.pbFileNames; 497 | _b.label = 1; 498 | case 1: 499 | if (!(_i < _a.length)) return [3 /*break*/, 5]; 500 | path = _a[_i]; 501 | if (path === "") 502 | return [2 /*return*/]; 503 | pbFile = this.app.metadataCache.getFirstLinkpathDest(path, currFile.path); 504 | if (!pbFile) return [3 /*break*/, 3]; 505 | return [4 /*yield*/, this.app.vault.cachedRead(pbFile)]; 506 | case 2: 507 | content = _b.sent(); 508 | localPB = this.mdToJSON(content, pbFile.basename); 509 | console.log({ localPB: localPB }); 510 | localPBs.push(localPB); 511 | return [3 /*break*/, 4]; 512 | case 3: 513 | new obsidian.Notice(path + " does not exist in your vault."); 514 | _b.label = 4; 515 | case 4: 516 | _i++; 517 | return [3 /*break*/, 1]; 518 | case 5: 519 | console.log({ localPBs: localPBs }); 520 | return [2 /*return*/, localPBs]; 521 | } 522 | }); 523 | }); 524 | }; 525 | // async buildRemotePB() { 526 | // if (this.settings.useRemotePB) { 527 | // const remotePBItemArr = this.mdToJSON(this.remotePBmd, "REMOTE"); 528 | // return remotePBItemArr; 529 | // } 530 | // return []; 531 | // } 532 | PBPlugin.prototype.refreshPB = function () { 533 | return __awaiter(this, void 0, void 0, function () { 534 | var localPBs; 535 | return __generator(this, function (_a) { 536 | switch (_a.label) { 537 | case 0: 538 | if (this.settings.pbFileNames[0] === "" 539 | // && !this.settings.useRemotePB 540 | ) { 541 | new obsidian.Notice("Please enter a path to the phrase bank.md file, or enable the setting to use the remote PB."); 542 | return [2 /*return*/]; 543 | } 544 | return [4 /*yield*/, this.buildLocalPBs()]; 545 | case 1: 546 | localPBs = _a.sent(); 547 | // const remotePB = await this.buildRemotePB(); 548 | console.log({ localPBs: localPBs }); 549 | if (localPBs === null || localPBs === void 0 ? void 0 : localPBs.length) { 550 | this.pb = this.mergePBs(__spreadArray([], localPBs, true)); 551 | } 552 | new obsidian.Notice("Phrase Bank Refreshed!"); 553 | console.log({ pb: this.pb }); 554 | return [2 /*return*/]; 555 | } 556 | }); 557 | }); 558 | }; 559 | PBPlugin.prototype.onunload = function () { 560 | console.log("unloading plugin"); 561 | }; 562 | PBPlugin.prototype.loadSettings = function () { 563 | return __awaiter(this, void 0, void 0, function () { 564 | var _a, _b, _c, _d; 565 | return __generator(this, function (_e) { 566 | switch (_e.label) { 567 | case 0: 568 | _a = this; 569 | _c = (_b = Object).assign; 570 | _d = [{}, DEFAULT_SETTINGS]; 571 | return [4 /*yield*/, this.loadData()]; 572 | case 1: 573 | _a.settings = _c.apply(_b, _d.concat([_e.sent()])); 574 | return [2 /*return*/]; 575 | } 576 | }); 577 | }); 578 | }; 579 | PBPlugin.prototype.saveSettings = function () { 580 | return __awaiter(this, void 0, void 0, function () { 581 | return __generator(this, function (_a) { 582 | switch (_a.label) { 583 | case 0: return [4 /*yield*/, this.saveData(this.settings)]; 584 | case 1: 585 | _a.sent(); 586 | return [2 /*return*/]; 587 | } 588 | }); 589 | }); 590 | }; 591 | return PBPlugin; 592 | }(obsidian.Plugin)); 593 | 594 | module.exports = PBPlugin; 595 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "phrase-bank", 3 | "name": "Phrase Bank", 4 | "version": "1.0.0", 5 | "minAppVersion": "0.12.16", 6 | "description": "", 7 | "author": "SkepticMystic", 8 | "authorUrl": "https://github.com/SkepticMystic/Phrase-Bank", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phrase-bank", 3 | "version": "1.0.0", 4 | "description": "", 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 | "release": "standard-version" 10 | }, 11 | "standard-version": { 12 | "t": "" 13 | }, 14 | "keywords": [], 15 | "author": "SkepticMystic", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@rollup/plugin-commonjs": "^18.0.0", 19 | "@rollup/plugin-node-resolve": "^11.2.1", 20 | "@rollup/plugin-typescript": "^8.2.1", 21 | "@types/json2csv": "^5.0.3", 22 | "@types/node": "^14.14.37", 23 | "obsidian": "^0.12.0", 24 | "rollup": "^2.32.1", 25 | "standard-version": "^9.3.1", 26 | "tslib": "^2.2.0", 27 | "typescript": "^4.2.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | 5 | const isProd = process.env.BUILD === "production"; 6 | 7 | const banner = `/* 8 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP 9 | if you want to view the source visit the plugins github repository 10 | */ 11 | `; 12 | 13 | export default { 14 | input: "src/main.ts", 15 | output: { 16 | format: "cjs", 17 | file: "main.js", 18 | exports: "default", 19 | }, 20 | external: ["obsidian"], 21 | plugins: [typescript(), nodeResolve({ browser: true }), commonjs()], 22 | }; 23 | -------------------------------------------------------------------------------- /snippet-triggered-phrase-input.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | function run (argv){ 4 | 5 | //input 6 | const phrase_array = JSON.parse(argv.join("")).phrases; 7 | let randomIndex = Math.floor(Math.random() * (phrase_array.length + 1)); 8 | 9 | return phrase_array[randomIndex]; 10 | } 11 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { Settings } from "./interfaces"; 2 | 3 | export const DEFAULT_SETTINGS: Settings = { 4 | pbFileNames: [""], 5 | // useRemotePB: false, 6 | }; 7 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export type PBItem = { 2 | fileName: string; 3 | phraseType: string; 4 | groups: GroupItem[]; 5 | desc: string; 6 | keywords: string[]; 7 | phrases: string[]; 8 | }; 9 | 10 | export type GroupItem = { 11 | name: string; 12 | keywords: string[]; 13 | }; 14 | 15 | export interface Settings { 16 | pbFileNames: string[]; 17 | // useRemotePB: boolean; 18 | } 19 | 20 | declare module "obsidian" { 21 | interface App {} 22 | } 23 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Notice, Plugin } from "obsidian"; 2 | import { PBItem, Settings } from "./interfaces"; 3 | import { removeDuplicates } from "./utils"; 4 | import { PBPhraseTypeOrGroupsFuzzySuggestModal } from "./phraseType-or-groups-suggester"; 5 | import { PBSettingTab } from "./Settings"; 6 | import { DEFAULT_SETTINGS } from "./constants"; 7 | 8 | export default class PBPlugin extends Plugin { 9 | settings: Settings; 10 | pb: PBItem[]; 11 | remotePBmd: string; 12 | 13 | async onload() { 14 | console.log("Loading PhraseBank plugin"); 15 | 16 | await this.loadSettings(); 17 | 18 | this.addCommand({ 19 | id: "phrase-bank-suggestor", 20 | name: "Pick from Phrase Bank", 21 | callback: () => 22 | new PBPhraseTypeOrGroupsFuzzySuggestModal(this.app, this).open(), 23 | }); 24 | 25 | this.addCommand({ 26 | id: "refresh-phrase-bank", 27 | name: "Refresh Phrase Bank", 28 | callback: async () => await this.refreshPB(), 29 | }); 30 | 31 | this.addSettingTab(new PBSettingTab(this.app, this)); 32 | 33 | this.pb = []; 34 | this.app.workspace.onLayoutReady(async () => { 35 | // const resp = await fetch( 36 | // "https://raw.githubusercontent.com/SkepticMystic/Phrase-Bank/main/Phrase%20Banks/Master%20PB.md?token=AQ3RB3AKC7BM2OM35YJWVQTBNFCNQ" 37 | // ); 38 | // this.remotePBmd = await resp.text(); 39 | await this.refreshPB(); 40 | }); 41 | } 42 | 43 | mdToJSON(content: string, fileName: string) { 44 | const lines = content.split("\n"); 45 | const pb: PBItem[] = []; 46 | 47 | for (let line of lines) { 48 | if (pb.length === 0 && !line.startsWith("### ")) { 49 | // Skip all lines until the first level 3 heading 50 | } else if (line.startsWith("### ")) { 51 | // A new heading indicates a new section in the pb 52 | const section = line.slice(4); 53 | pb.push({ 54 | fileName, 55 | phraseType: section, 56 | groups: [], 57 | desc: "", 58 | keywords: [], 59 | phrases: [], 60 | }); 61 | } else if (line.startsWith("!") || line.startsWith("↑")) { 62 | // Groups start with '!' or '↑' 63 | const groups = line.split(/!|↑/)[1].trim().split(","); 64 | groups.forEach((group) => { 65 | pb.last().groups.push({ name: group.trim(), keywords: [] }); 66 | }); 67 | } else if (line.startsWith(">")) { 68 | // Blockquotes indicate description 69 | pb.last().desc = line.split(">")[1].trim(); 70 | } else if (line.startsWith("- ")) { 71 | // Bullets indicates keywords 72 | const kws = line 73 | .slice(2) 74 | .split(",") 75 | .map((kw) => kw.trim()); 76 | pb.last().keywords.push(...kws); 77 | } else if (line.startsWith("%%")) { 78 | // Ignore comments 79 | } else if (line.startsWith("|")) { 80 | // Ignore tables for now 81 | } else if (line.trim() !== "") { 82 | // Every other non-blank line is considered a phrase 83 | pb.last().phrases.push(line); 84 | } 85 | } 86 | return pb; 87 | } 88 | 89 | mergePBs(localPBs: PBItem[][]) { 90 | const globalPB: PBItem[] = []; 91 | localPBs.forEach((localPB) => { 92 | localPB.forEach((pbItem) => { 93 | const existingI = globalPB.findIndex( 94 | (pb) => pb.phraseType === pbItem.phraseType 95 | ); 96 | if (existingI > -1) { 97 | globalPB[existingI].phrases = removeDuplicates([ 98 | ...globalPB[existingI].phrases, 99 | ...pbItem.phrases, 100 | ]); 101 | globalPB[existingI].keywords = removeDuplicates([ 102 | ...globalPB[existingI].keywords, 103 | ...pbItem.keywords, 104 | ]); 105 | // globalPB[existingPBSection].phrases.push(...pbItem.phrases) 106 | // globalPB[existingPBSection].keywords.push(...pbItem.keywords) 107 | if (globalPB[existingI].desc === "") { 108 | globalPB[existingI].desc = pbItem.desc; 109 | } 110 | if (globalPB[existingI].fileName !== pbItem.fileName) { 111 | globalPB[existingI].fileName = 112 | globalPB[existingI].fileName + ` ${pbItem.fileName}`; 113 | } 114 | } else { 115 | globalPB.push({ ...pbItem }); 116 | } 117 | }); 118 | }); 119 | return globalPB; 120 | } 121 | 122 | async buildLocalPBs() { 123 | const localPBs: PBItem[][] = []; 124 | const currFile = this.app.workspace.getActiveFile(); 125 | for (let path of this.settings.pbFileNames) { 126 | if (path === "") return; 127 | const pbFile = this.app.metadataCache.getFirstLinkpathDest( 128 | path, 129 | currFile.path 130 | ); 131 | if (pbFile) { 132 | const content = await this.app.vault.cachedRead(pbFile); 133 | const localPB = this.mdToJSON(content, pbFile.basename); 134 | console.log({ localPB }); 135 | localPBs.push(localPB); 136 | } else { 137 | new Notice(`${path} does not exist in your vault.`); 138 | } 139 | } 140 | console.log({ localPBs }); 141 | return localPBs; 142 | } 143 | 144 | // async buildRemotePB() { 145 | // if (this.settings.useRemotePB) { 146 | // const remotePBItemArr = this.mdToJSON(this.remotePBmd, "REMOTE"); 147 | // return remotePBItemArr; 148 | // } 149 | // return []; 150 | // } 151 | 152 | async refreshPB() { 153 | if ( 154 | this.settings.pbFileNames[0] === "" 155 | // && !this.settings.useRemotePB 156 | ) { 157 | new Notice( 158 | "Please enter a path to the phrase bank.md file, or enable the setting to use the remote PB." 159 | ); 160 | return; 161 | } 162 | 163 | const localPBs = await this.buildLocalPBs(); 164 | // const remotePB = await this.buildRemotePB(); 165 | console.log({ localPBs }); 166 | if (localPBs?.length) { 167 | this.pb = this.mergePBs([ 168 | ...localPBs, 169 | // , remotePB 170 | ]); 171 | } else { 172 | // this.pb = remotePB; 173 | } 174 | 175 | new Notice("Phrase Bank Refreshed!"); 176 | console.log({ pb: this.pb }); 177 | } 178 | 179 | onunload() { 180 | console.log("unloading plugin"); 181 | } 182 | 183 | async loadSettings() { 184 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 185 | } 186 | 187 | async saveSettings() { 188 | await this.saveData(this.settings); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/phraseType-only-suggester.ts: -------------------------------------------------------------------------------- 1 | import { App, FuzzyMatch, FuzzySuggestModal } from "obsidian"; 2 | import { PBItem, Settings } from "./interfaces"; 3 | import PBPlugin from "./main"; 4 | import { PBPhrasesFuzzySuggestModal } from "./phrases-suggester"; 5 | import { PBPhraseTypeOrGroupsFuzzySuggestModal } from "./phraseType-or-groups-suggester"; 6 | import { getActiveView } from "./utils"; 7 | 8 | export class PBPhraseTypeFuzzySuggestModal extends FuzzySuggestModal { 9 | app: App; 10 | plugin: PBPlugin; 11 | pb: PBItem[]; 12 | settings: Settings; 13 | 14 | constructor(app: App, plugin: PBPlugin, pb: PBItem[]) { 15 | super(app); 16 | this.app = app; 17 | this.plugin = plugin; 18 | this.pb = [ 19 | ...pb, 20 | { 21 | phraseType: "BACK", 22 | fileName: "", 23 | desc: "", 24 | groups: [], 25 | keywords: [], 26 | phrases: [], 27 | }, 28 | ]; 29 | this.settings = plugin.settings; 30 | this.scope.register(["Shift"], "Enter", (evt: KeyboardEvent) => { 31 | // @ts-ignore 32 | this.chooser.useSelectedItem(evt); 33 | return false; 34 | }); 35 | } 36 | 37 | getItems(): PBItem[] { 38 | return this.pb; 39 | } 40 | 41 | getItemText(item: PBItem): string { 42 | return ( 43 | item.phraseType + "|||" + item.keywords.join(", ") + ", " + item.fileName 44 | ); 45 | } 46 | 47 | renderSuggestion(item: FuzzyMatch, el: HTMLElement) { 48 | super.renderSuggestion(item, el); 49 | el.innerText = el.innerText.split("|||")[0]; 50 | this.updateSuggestionElWithDesc(item, el); 51 | } 52 | 53 | updateSuggestionElWithDesc(item: FuzzyMatch, el: HTMLElement) { 54 | if (item.item.desc) { 55 | var indicatorEl = el.createEl("div", { 56 | text: item.item.desc, 57 | cls: "PB-Desc", 58 | }); 59 | } 60 | } 61 | 62 | onChooseItem(item: PBItem, evt: MouseEvent | KeyboardEvent): void { 63 | if (item.phraseType === "BACK") { 64 | this.close(); 65 | new PBPhraseTypeOrGroupsFuzzySuggestModal(this.app, this.plugin).open(); 66 | } else { 67 | if (!evt.shiftKey) { 68 | this.close(); 69 | new PBPhrasesFuzzySuggestModal( 70 | this.app, 71 | this.plugin, 72 | item.phrases 73 | ).open(); 74 | } else { 75 | const randI = Math.floor(Math.random() * (item.phrases.length - 1)); 76 | const randPhrase = item.phrases[randI]; 77 | try { 78 | this.close(); 79 | const activeView = getActiveView(this.plugin); 80 | const activeEditor = activeView.editor; 81 | const editorRange = activeEditor.getCursor("from"); 82 | activeEditor.replaceRange(randPhrase, editorRange); 83 | } catch (error) { 84 | console.log(error); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/phraseType-or-groups-suggester.ts: -------------------------------------------------------------------------------- 1 | import { App, FuzzyMatch, FuzzySuggestModal } from "obsidian"; 2 | import { getActiveView } from "src/utils"; 3 | import { GroupItem, PBItem } from "src/interfaces"; 4 | import PBPlugin, { Settings } from "src/main"; 5 | import { PBPhrasesFuzzySuggestModal } from "src/phrases-suggester"; 6 | import { PBPhraseTypeFuzzySuggestModal } from "src/phraseType-only-suggester"; 7 | 8 | export class PBPhraseTypeOrGroupsFuzzySuggestModal extends FuzzySuggestModal< 9 | PBItem | GroupItem 10 | > { 11 | app: App; 12 | plugin: PBPlugin; 13 | pb: PBItem[]; 14 | settings: Settings; 15 | 16 | constructor(app: App, plugin: PBPlugin) { 17 | super(app); 18 | this.app = app; 19 | this.plugin = plugin; 20 | this.pb = plugin.pb; 21 | this.settings = plugin.settings; 22 | this.scope.register(["Shift"], "Enter", (evt: KeyboardEvent) => { 23 | // @ts-ignore 24 | this.chooser.useSelectedItem(evt); 25 | return false; 26 | }); 27 | } 28 | 29 | getItems(): (PBItem | GroupItem)[] { 30 | const groups = this.pb.map((item) => item.groups).flat(3); 31 | const noDupGroups: GroupItem[] = []; 32 | groups.forEach((group) => { 33 | if (!noDupGroups.some((g) => g.name === group.name)) { 34 | noDupGroups.push(group); 35 | } 36 | }); 37 | return [...noDupGroups, ...this.pb]; 38 | } 39 | 40 | getItemText(item: PBItem | GroupItem): string { 41 | if (item.phraseType) { 42 | return ( 43 | (item as PBItem).phraseType + 44 | "|||" + 45 | item.keywords.join(", ") + 46 | ", " + 47 | item.fileName 48 | ); 49 | } else if (item.name) { 50 | return "📂 " + (item as GroupItem).name; 51 | } 52 | } 53 | 54 | renderSuggestion(item: FuzzyMatch, el: HTMLElement) { 55 | super.renderSuggestion(item, el); 56 | el.innerText = el.innerText.split("|||")[0]; 57 | this.updateSuggestionElWithDesc(item, el); 58 | } 59 | 60 | updateSuggestionElWithDesc( 61 | item: FuzzyMatch, 62 | el: HTMLElement 63 | ) { 64 | if (item.item.desc) { 65 | var indicatorEl = el.createEl("div", { 66 | text: item.item.desc, 67 | cls: "PB-Desc", 68 | }); 69 | } 70 | } 71 | 72 | onChooseItem( 73 | item: PBItem | GroupItem, 74 | evt: MouseEvent | KeyboardEvent 75 | ): void { 76 | if (item.phraseType) { 77 | if (!evt.shiftKey) { 78 | new PBPhrasesFuzzySuggestModal( 79 | this.app, 80 | this.plugin, 81 | (item as PBItem).phrases, 82 | this.settings 83 | ).open(); 84 | } else { 85 | const randI = Math.floor( 86 | Math.random() * ((item as PBItem).phrases.length - 1) 87 | ); 88 | const randPhrase = (item as PBItem).phrases[randI]; 89 | try { 90 | this.close(); 91 | const activeView = getActiveView(this.plugin); 92 | const activeEditor = activeView.editor; 93 | const editorRange = activeEditor.getCursor("from"); 94 | activeEditor.replaceRange(randPhrase, editorRange); 95 | } catch (error) { 96 | console.log(error); 97 | } 98 | } 99 | } else if (item.name) { 100 | console.log(item.name); 101 | const filteredPB = this.pb.filter((pbItem) => 102 | pbItem.groups.some((group) => group.name === item.name) 103 | ); 104 | console.log({ filteredPB }); 105 | this.close(); 106 | new PBPhraseTypeFuzzySuggestModal( 107 | this.app, 108 | this.plugin, 109 | filteredPB, 110 | this.settings 111 | ).open(); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/phrases-suggester.ts: -------------------------------------------------------------------------------- 1 | import { App, FuzzyMatch, FuzzySuggestModal } from "obsidian"; 2 | import PBPlugin from "src/main"; 3 | import { PBPhraseTypeOrGroupsFuzzySuggestModal } from "src/phraseType-or-groups-suggester"; 4 | import { getActiveView } from "src/utils"; 5 | import { Settings } from "./interfaces"; 6 | 7 | export class PBPhrasesFuzzySuggestModal extends FuzzySuggestModal { 8 | app: App; 9 | plugin: PBPlugin; 10 | phrases: string[]; 11 | settings: Settings; 12 | 13 | constructor(app: App, plugin: PBPlugin, phrases: string[]) { 14 | super(app); 15 | this.app = app; 16 | this.plugin = plugin; 17 | this.phrases = [...phrases, "BACK"]; 18 | this.settings = plugin.settings; 19 | } 20 | 21 | getItems(): string[] { 22 | return this.phrases; 23 | } 24 | 25 | getItemText(item: string): string { 26 | return `💬 ${item}`; 27 | } 28 | 29 | renderSuggestion(item: FuzzyMatch, el: HTMLElement) { 30 | super.renderSuggestion(item, el); 31 | } 32 | 33 | onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void { 34 | if (item === "BACK") { 35 | this.close(); 36 | new PBPhraseTypeOrGroupsFuzzySuggestModal(this.app, this.plugin).open(); 37 | } else { 38 | try { 39 | const activeView = getActiveView(this.plugin); 40 | const activeEditor = activeView.editor; 41 | const editorRange = activeEditor.getCursor("from"); 42 | activeEditor.replaceRange(item, editorRange); 43 | } catch (error) { 44 | console.log(error); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting } from "obsidian"; 2 | import PBPlugin from "src/main"; 3 | 4 | export class PBSettingTab extends PluginSettingTab { 5 | plugin: PBPlugin; 6 | 7 | constructor(app: App, plugin: PBPlugin) { 8 | super(app, plugin); 9 | this.plugin = plugin; 10 | } 11 | 12 | display(): void { 13 | let { containerEl } = this; 14 | const { settings, saveSettings } = this.plugin; 15 | 16 | containerEl.empty(); 17 | containerEl.createEl("h2", { text: "Settings for Phrase Bank" }); 18 | 19 | new Setting(containerEl) 20 | .setName("Phrase Bank file names") 21 | .setDesc( 22 | "Names of your phrase bank.md files in your vault. You can also enter a comma-separated list of pb.md filenames, and the plugin will merge them into one global PB" 23 | ) 24 | .addText((tc) => { 25 | tc.setValue(settings.pbFileNames.join(", ")); 26 | tc.inputEl.onblur = async () => { 27 | const { value } = tc.inputEl; 28 | settings.pbFileNames = value.split(",").map((path) => path.trim()); 29 | await this.plugin.saveSettings(); 30 | await this.plugin.refreshPB(); 31 | }; 32 | }); 33 | 34 | // new Setting(containerEl) 35 | // .setName("Use Remote Phrase Bank") 36 | // .setDesc( 37 | // "Use the content of the community-maintaned PB from: https://raw.githubusercontent.com/SkepticMystic/Phrase-Bank/main/Phrase%20Bank%20copy.md" 38 | // ) 39 | // .addToggle((tg) => { 40 | // tg.setValue(settings.useRemotePB).onChange(async (val) => { 41 | // settings.useRemotePB = val; 42 | // await this.plugin.saveSettings(); 43 | // await this.plugin.refreshPB(); 44 | // }); 45 | // }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownView } from "obsidian"; 2 | import PBPlugin from "src/main"; 3 | 4 | export function getActiveView(plugin: PBPlugin): MarkdownView { 5 | return plugin.app.workspace.getActiveViewOfType(MarkdownView); 6 | } 7 | 8 | export function removeDuplicates(a: T[]) { 9 | var result: T[] = []; 10 | a.forEach((item) => { 11 | if (result.indexOf(item) < 0) { 12 | result.push(item); 13 | } 14 | }); 15 | return result 16 | } -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* Filler */ 2 | .PB-Desc { 3 | font-size: smaller; 4 | opacity: 0.6; 5 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*", "src/*", "**/*.ts"], 3 | "exclude": ["node_modules/*"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "src": ["src/*"] 8 | }, 9 | "lib": ["ES2019", "DOM"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.0.1": "0.12.16" 3 | } 4 | --------------------------------------------------------------------------------