├── .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 |
--------------------------------------------------------------------------------