-
Notifications
You must be signed in to change notification settings - Fork 1
/
brotkeys.js
803 lines (707 loc) · 50.8 KB
/
brotkeys.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
// (c) Eric Mink aka LucidBrot 2018
// version 2.2.1
// keep in mind that multiple HotkeyManagers only work as long as you don't unregister them via the hotkeys library.
// and multiple managers might autogenerate the same link hints for different content.
class HotkeyManager {
/*
wordMap: Map of [strings that exist on the page, functions as reaction on the string]
If a word starts with another word, the shorter will not leave fmode when its function is called.
interruptMap: Map of [char, function]
ignoreTheseKeys: optional - an array of keys to completely ignore. e.g. ["j", 'K'] for scrolling
*/
constructor(wordMap, interruptMap, ignoreTheseKeys){
/** <CONFIG/> **/
// characters that are preferredly used for autogenerating link hints
this.HOMEROW_CHARS = ['f', 'j', 'd', 's', 'g', 'h', 'k','l'];
// characters that are used before deciding to make the word longer, as a second choice after all homerow combinations have been used
this.OTHER_OKAY_CHARS = ['q','w','e','r','t','u','i','o','p','v','n','m'];
// css class of the buttons that appear as link hints
this.LINKHINT_STYLE_CLASS = "eric-reverse";
// css class added to the link hints when overlay mode is on. See notesToSelf/overlayMode.md
this.LINKHINT_OVERLAY_CONTAINER_STYLE_CLASS = "LB-overlay-container";
// Differ between images and not images
this.LINKHINT_OVERLAY_TEXT_CLASS = "LB-overlay-wrapped-link-hint-text";
this.LINKHINT_OVERLAY_IMAGE_CLASS = "LB-overlay-wrapped-link-hint-image";
// internal config. It doesn't matter what you set here
this.AUTOGEN_LINKHINT_ATTRIBUTE = "brotkeysid"; // used for counting all anchors. Throwaway property.
// and then we add a UID to it so it works even with multiple HotkeyManagers
this.UID = HotkeyManager.genHotkeyManagerUID();
this.AUTOGEN_LINKHINT_ATTRIBUTE += this.UID;
this.SWAP_CLASS_NAME_DEFAULT = "LB-SS-swap1"; // used for all link hints in order to swap them on and off if no other are provided
// fake enum for adding more options later, for autogeneration of link hints
// never use 0 in enums, since it could compare as equal to null or undefined or false
this.GenerationEnum = Object.freeze({"tag_anchor":0b01,"class_tagged":0b10});
this.GENERATION_CLASS_TAG = "BKH"; // default for class_tagged is the class "BKH", but this could be easily changed
/** <CONFIG/> **/
// initialize the string of what the user has entered to nothing
this.current_link_word = "";
// set up the value for entering f_mode
this.F_MODE_PREFIX_CHAR = "f";
// fake enum for easier changes instead of magic strings
this.ModeEnum = Object.freeze({"f_mode":1, "pre_f_mode":2, "all_disabled":3});
// this.mode is used to distinguish between no f pressed yet, and when the user is entering the actual word. Avoids using hotkeys.setScope()
this.mode = this.ModeEnum.pre_f_mode;
/*public*/ this.log_prefix = ""; // set this if you want. It's only used for logging
// set up special keys that will interrupt the search for words at any time (and should thus not be used within valid words)
this.interruptMap_whenInFMode = interruptMap;
this.wordMap = wordMap;
// config for key case insensitivity
this.fmode_caseInsensitivity = true;
this.interrupt_caseInsensitivity = true;
this.word_caseInsensitivity = true;
this.ignore_ShiftAndCapslock_inWordMode = true;
// ignore no keys by default. Lower case stored, behaviour is case-insensitive ignoring.
this.ignoredKeys = [];
if (ignoreTheseKeys != undefined) {
this.ignoredKeys = ignoreTheseKeys.map(function(b){return b.toLowerCase();});
const that = this;
this.HOMEROW_CHARS = this.HOMEROW_CHARS.filter(function(key){return !that.ignoredKeys.includes(key)});
this.OTHER_OKAY_CHARS = this.OTHER_OKAY_CHARS.filter(function(key){return !that.ignoredKeys.includes(key)});
}
// config for using overlay mode or in-content mode. True for overlay mode.
this.setOverlayMode(true);
// this css was not yet loaded (assumption may be wrong if you have multiple managers. But that just means that it will be loaded multiple times, which shouldn't be too bad.)
this.__loadNeededJSCSSForStyleSwapping_alreadyLoaded = false;
// set this to 0 if you need autogenerate to start overwriting previously generated link hints
this.global_index = 0;
// special iframe handling that is only implemented for autogenerateWithinId
this.iframedocs_that_we_autogenerated_within = new Set();
this.hotkeys_init();
}
log_verbose(text){
console.log(this.log_prefix+text);
}
log_error(text){
console.log("%c[E] "+this.log_prefix+text, 'background: #222; color: #bada55');
}
log_happy(text){
/*
https://stackoverflow.com/a/21457293/2550406
(c) bartburkhardt cc-by-sa 3.0
*/
const css = "text-shadow: -1px -1px hsl(0,100%,50%), 1px 1px hsl(5.4, 100%, 50%), 3px 2px hsl(10.8, 100%, 50%), 5px 3px hsl(16.2, 100%, 50%), 7px 4px hsl(21.6, 100%, 50%), 9px 5px hsl(27, 100%, 50%), 11px 6px hsl(32.4, 100%, 50%), 13px 7px hsl(37.8, 100%, 50%), 14px 8px hsl(43.2, 100%, 50%), 16px 9px hsl(48.6, 100%, 50%), 18px 10px hsl(54, 100%, 50%), 20px 11px hsl(59.4, 100%, 50%), 22px 12px hsl(64.8, 100%, 50%), 23px 13px hsl(70.2, 100%, 50%), 25px 14px hsl(75.6, 100%, 50%), 27px 15px hsl(81, 100%, 50%), 28px 16px hsl(86.4, 100%, 50%), 30px 17px hsl(91.8, 100%, 50%), 32px 18px hsl(97.2, 100%, 50%), 33px 19px hsl(102.6, 100%, 50%), 35px 20px hsl(108, 100%, 50%), 36px 21px hsl(113.4, 100%, 50%), 38px 22px hsl(118.8, 100%, 50%), 39px 23px hsl(124.2, 100%, 50%), 41px 24px hsl(129.6, 100%, 50%), 42px 25px hsl(135, 100%, 50%), 43px 26px hsl(140.4, 100%, 50%), 45px 27px hsl(145.8, 100%, 50%), 46px 28px hsl(151.2, 100%, 50%), 47px 29px hsl(156.6, 100%, 50%), 48px 30px hsl(162, 100%, 50%), 49px 31px hsl(167.4, 100%, 50%), 50px 32px hsl(172.8, 100%, 50%), 51px 33px hsl(178.2, 100%, 50%), 52px 34px hsl(183.6, 100%, 50%), 53px 35px hsl(189, 100%, 50%), 54px 36px hsl(194.4, 100%, 50%), 55px 37px hsl(199.8, 100%, 50%), 55px 38px hsl(205.2, 100%, 50%), 56px 39px hsl(210.6, 100%, 50%), 57px 40px hsl(216, 100%, 50%), 57px 41px hsl(221.4, 100%, 50%), 58px 42px hsl(226.8, 100%, 50%), 58px 43px hsl(232.2, 100%, 50%), 58px 44px hsl(237.6, 100%, 50%), 59px 45px hsl(243, 100%, 50%), 59px 46px hsl(248.4, 100%, 50%), 59px 47px hsl(253.8, 100%, 50%), 59px 48px hsl(259.2, 100%, 50%), 59px 49px hsl(264.6, 100%, 50%), 60px 50px hsl(270, 100%, 50%), 59px 51px hsl(275.4, 100%, 50%), 59px 52px hsl(280.8, 100%, 50%), 59px 53px hsl(286.2, 100%, 50%), 59px 54px hsl(291.6, 100%, 50%), 59px 55px hsl(297, 100%, 50%), 58px 56px hsl(302.4, 100%, 50%), 58px 57px hsl(307.8, 100%, 50%), 58px 58px hsl(313.2, 100%, 50%), 57px 59px hsl(318.6, 100%, 50%), 57px 60px hsl(324, 100%, 50%), 56px 61px hsl(329.4, 100%, 50%), 55px 62px hsl(334.8, 100%, 50%), 55px 63px hsl(340.2, 100%, 50%), 54px 64px hsl(345.6, 100%, 50%), 53px 65px hsl(351, 100%, 50%), 52px 66px hsl(356.4, 100%, 50%), 51px 67px hsl(361.8, 100%, 50%), 50px 68px hsl(367.2, 100%, 50%), 49px 69px hsl(372.6, 100%, 50%), 48px 70px hsl(378, 100%, 50%), 47px 71px hsl(383.4, 100%, 50%), 46px 72px hsl(388.8, 100%, 50%), 45px 73px hsl(394.2, 100%, 50%), 43px 74px hsl(399.6, 100%, 50%), 42px 75px hsl(405, 100%, 50%), 41px 76px hsl(410.4, 100%, 50%), 39px 77px hsl(415.8, 100%, 50%), 38px 78px hsl(421.2, 100%, 50%), 36px 79px hsl(426.6, 100%, 50%), 35px 80px hsl(432, 100%, 50%), 33px 81px hsl(437.4, 100%, 50%), 32px 82px hsl(442.8, 100%, 50%), 30px 83px hsl(448.2, 100%, 50%), 28px 84px hsl(453.6, 100%, 50%), 27px 85px hsl(459, 100%, 50%), 25px 86px hsl(464.4, 100%, 50%), 23px 87px hsl(469.8, 100%, 50%), 22px 88px hsl(475.2, 100%, 50%), 20px 89px hsl(480.6, 100%, 50%), 18px 90px hsl(486, 100%, 50%), 16px 91px hsl(491.4, 100%, 50%), 14px 92px hsl(496.8, 100%, 50%), 13px 93px hsl(502.2, 100%, 50%), 11px 94px hsl(507.6, 100%, 50%), 9px 95px hsl(513, 100%, 50%), 7px 96px hsl(518.4, 100%, 50%), 5px 97px hsl(523.8, 100%, 50%), 3px 98px hsl(529.2, 100%, 50%), 1px 99px hsl(534.6, 100%, 50%), 7px 100px hsl(540, 100%, 50%), -1px 101px hsl(545.4, 100%, 50%), -3px 102px hsl(550.8, 100%, 50%), -5px 103px hsl(556.2, 100%, 50%), -7px 104px hsl(561.6, 100%, 50%), -9px 105px hsl(567, 100%, 50%), -11px 106px hsl(572.4, 100%, 50%), -13px 107px hsl(577.8, 100%, 50%), -14px 108px hsl(583.2, 100%, 50%), -16px 109px hsl(588.6, 100%, 50%), -18px 110px hsl(594, 100%, 50%), -20px 111px hsl(599.4, 100%, 50%), -22px 112px hsl(604.8, 100%, 50%), -23px 113px hsl(610.2, 100%, 50%), -25px 114px hsl(615.6, 100%, 50%), -27px 115px hsl(621, 100%, 50%), -28px 116px hsl(626.4, 100%, 50%), -30px 117px hsl(631.8, 100%, 50%), -32px 118px hsl(637.2, 100%, 50%), -33px 119px hsl(642.6, 100%, 50%), -35px 120px hsl(648, 100%, 50%), -36px 121px hsl(653.4, 100%, 50%), -38px 122px hsl(658.8, 100%, 50%), -39px 123px hsl(664.2, 100%, 50%), -41px 124px hsl(669.6, 100%, 50%), -42px 125px hsl(675, 100%, 50%), -43px 126px hsl(680.4, 100%, 50%), -45px 127px hsl(685.8, 100%, 50%), -46px 128px hsl(691.2, 100%, 50%), -47px 129px hsl(696.6, 100%, 50%), -48px 130px hsl(702, 100%, 50%), -49px 131px hsl(707.4, 100%, 50%), -50px 132px hsl(712.8, 100%, 50%), -51px 133px hsl(718.2, 100%, 50%), -52px 134px hsl(723.6, 100%, 50%), -53px 135px hsl(729, 100%, 50%), -54px 136px hsl(734.4, 100%, 50%), -55px 137px hsl(739.8, 100%, 50%), -55px 138px hsl(745.2, 100%, 50%), -56px 139px hsl(750.6, 100%, 50%), -57px 140px hsl(756, 100%, 50%), -57px 141px hsl(761.4, 100%, 50%), -58px 142px hsl(766.8, 100%, 50%), -58px 143px hsl(772.2, 100%, 50%), -58px 144px hsl(777.6, 100%, 50%), -59px 145px hsl(783, 100%, 50%), -59px 146px hsl(788.4, 100%, 50%), -59px 147px hsl(793.8, 100%, 50%), -59px 148px hsl(799.2, 100%, 50%), -59px 149px hsl(804.6, 100%, 50%), -60px 150px hsl(810, 100%, 50%), -59px 151px hsl(815.4, 100%, 50%), -59px 152px hsl(820.8, 100%, 50%), -59px 153px hsl(826.2, 100%, 50%), -59px 154px hsl(831.6, 100%, 50%), -59px 155px hsl(837, 100%, 50%), -58px 156px hsl(842.4, 100%, 50%), -58px 157px hsl(847.8, 100%, 50%), -58px 158px hsl(853.2, 100%, 50%), -57px 159px hsl(858.6, 100%, 50%), -57px 160px hsl(864, 100%, 50%), -56px 161px hsl(869.4, 100%, 50%), -55px 162px hsl(874.8, 100%, 50%), -55px 163px hsl(880.2, 100%, 50%), -54px 164px hsl(885.6, 100%, 50%), -53px 165px hsl(891, 100%, 50%), -52px 166px hsl(896.4, 100%, 50%), -51px 167px hsl(901.8, 100%, 50%), -50px 168px hsl(907.2, 100%, 50%), -49px 169px hsl(912.6, 100%, 50%), -48px 170px hsl(918, 100%, 50%), -47px 171px hsl(923.4, 100%, 50%), -46px 172px hsl(928.8, 100%, 50%), -45px 173px hsl(934.2, 100%, 50%), -43px 174px hsl(939.6, 100%, 50%), -42px 175px hsl(945, 100%, 50%), -41px 176px hsl(950.4, 100%, 50%), -39px 177px hsl(955.8, 100%, 50%), -38px 178px hsl(961.2, 100%, 50%), -36px 179px hsl(966.6, 100%, 50%), -35px 180px hsl(972, 100%, 50%), -33px 181px hsl(977.4, 100%, 50%), -32px 182px hsl(982.8, 100%, 50%), -30px 183px hsl(988.2, 100%, 50%), -28px 184px hsl(993.6, 100%, 50%), -27px 185px hsl(999, 100%, 50%), -25px 186px hsl(1004.4, 100%, 50%), -23px 187px hsl(1009.8, 100%, 50%), -22px 188px hsl(1015.2, 100%, 50%), -20px 189px hsl(1020.6, 100%, 50%), -18px 190px hsl(1026, 100%, 50%), -16px 191px hsl(1031.4, 100%, 50%), -14px 192px hsl(1036.8, 100%, 50%), -13px 193px hsl(1042.2, 100%, 50%), -11px 194px hsl(1047.6, 100%, 50%), -9px 195px hsl(1053, 100%, 50%), -7px 196px hsl(1058.4, 100%, 50%), -5px 197px hsl(1063.8, 100%, 50%), -3px 198px hsl(1069.2, 100%, 50%), -1px 199px hsl(1074.6, 100%, 50%), -1px 200px hsl(1080, 100%, 50%), 1px 201px hsl(1085.4, 100%, 50%), 3px 202px hsl(1090.8, 100%, 50%), 5px 203px hsl(1096.2, 100%, 50%), 7px 204px hsl(1101.6, 100%, 50%), 9px 205px hsl(1107, 100%, 50%), 11px 206px hsl(1112.4, 100%, 50%), 13px 207px hsl(1117.8, 100%, 50%), 14px 208px hsl(1123.2, 100%, 50%), 16px 209px hsl(1128.6, 100%, 50%), 18px 210px hsl(1134, 100%, 50%), 20px 211px hsl(1139.4, 100%, 50%), 22px 212px hsl(1144.8, 100%, 50%), 23px 213px hsl(1150.2, 100%, 50%), 25px 214px hsl(1155.6, 100%, 50%), 27px 215px hsl(1161, 100%, 50%), 28px 216px hsl(1166.4, 100%, 50%), 30px 217px hsl(1171.8, 100%, 50%), 32px 218px hsl(1177.2, 100%, 50%), 33px 219px hsl(1182.6, 100%, 50%), 35px 220px hsl(1188, 100%, 50%), 36px 221px hsl(1193.4, 100%, 50%), 38px 222px hsl(1198.8, 100%, 50%), 39px 223px hsl(1204.2, 100%, 50%), 41px 224px hsl(1209.6, 100%, 50%), 42px 225px hsl(1215, 100%, 50%), 43px 226px hsl(1220.4, 100%, 50%), 45px 227px hsl(1225.8, 100%, 50%), 46px 228px hsl(1231.2, 100%, 50%), 47px 229px hsl(1236.6, 100%, 50%), 48px 230px hsl(1242, 100%, 50%), 49px 231px hsl(1247.4, 100%, 50%), 50px 232px hsl(1252.8, 100%, 50%), 51px 233px hsl(1258.2, 100%, 50%), 52px 234px hsl(1263.6, 100%, 50%), 53px 235px hsl(1269, 100%, 50%), 54px 236px hsl(1274.4, 100%, 50%), 55px 237px hsl(1279.8, 100%, 50%), 55px 238px hsl(1285.2, 100%, 50%), 56px 239px hsl(1290.6, 100%, 50%), 57px 240px hsl(1296, 100%, 50%), 57px 241px hsl(1301.4, 100%, 50%), 58px 242px hsl(1306.8, 100%, 50%), 58px 243px hsl(1312.2, 100%, 50%), 58px 244px hsl(1317.6, 100%, 50%), 59px 245px hsl(1323, 100%, 50%), 59px 246px hsl(1328.4, 100%, 50%), 59px 247px hsl(1333.8, 100%, 50%), 59px 248px hsl(1339.2, 100%, 50%), 59px 249px hsl(1344.6, 100%, 50%), 60px 250px hsl(1350, 100%, 50%), 59px 251px hsl(1355.4, 100%, 50%), 59px 252px hsl(1360.8, 100%, 50%), 59px 253px hsl(1366.2, 100%, 50%), 59px 254px hsl(1371.6, 100%, 50%), 59px 255px hsl(1377, 100%, 50%), 58px 256px hsl(1382.4, 100%, 50%), 58px 257px hsl(1387.8, 100%, 50%), 58px 258px hsl(1393.2, 100%, 50%), 57px 259px hsl(1398.6, 100%, 50%), 57px 260px hsl(1404, 100%, 50%), 56px 261px hsl(1409.4, 100%, 50%), 55px 262px hsl(1414.8, 100%, 50%), 55px 263px hsl(1420.2, 100%, 50%), 54px 264px hsl(1425.6, 100%, 50%), 53px 265px hsl(1431, 100%, 50%), 52px 266px hsl(1436.4, 100%, 50%), 51px 267px hsl(1441.8, 100%, 50%), 50px 268px hsl(1447.2, 100%, 50%), 49px 269px hsl(1452.6, 100%, 50%), 48px 270px hsl(1458, 100%, 50%), 47px 271px hsl(1463.4, 100%, 50%), 46px 272px hsl(1468.8, 100%, 50%), 45px 273px hsl(1474.2, 100%, 50%), 43px 274px hsl(1479.6, 100%, 50%), 42px 275px hsl(1485, 100%, 50%), 41px 276px hsl(1490.4, 100%, 50%), 39px 277px hsl(1495.8, 100%, 50%), 38px 278px hsl(1501.2, 100%, 50%), 36px 279px hsl(1506.6, 100%, 50%), 35px 280px hsl(1512, 100%, 50%), 33px 281px hsl(1517.4, 100%, 50%), 32px 282px hsl(1522.8, 100%, 50%), 30px 283px hsl(1528.2, 100%, 50%), 28px 284px hsl(1533.6, 100%, 50%), 27px 285px hsl(1539, 100%, 50%), 25px 286px hsl(1544.4, 100%, 50%), 23px 287px hsl(1549.8, 100%, 50%), 22px 288px hsl(1555.2, 100%, 50%), 20px 289px hsl(1560.6, 100%, 50%), 18px 290px hsl(1566, 100%, 50%), 16px 291px hsl(1571.4, 100%, 50%), 14px 292px hsl(1576.8, 100%, 50%), 13px 293px hsl(1582.2, 100%, 50%), 11px 294px hsl(1587.6, 100%, 50%), 9px 295px hsl(1593, 100%, 50%), 7px 296px hsl(1598.4, 100%, 50%), 5px 297px hsl(1603.8, 100%, 50%), 3px 298px hsl(1609.2, 100%, 50%), 1px 299px hsl(1614.6, 100%, 50%), 2px 300px hsl(1620, 100%, 50%), -1px 301px hsl(1625.4, 100%, 50%), -3px 302px hsl(1630.8, 100%, 50%), -5px 303px hsl(1636.2, 100%, 50%), -7px 304px hsl(1641.6, 100%, 50%), -9px 305px hsl(1647, 100%, 50%), -11px 306px hsl(1652.4, 100%, 50%), -13px 307px hsl(1657.8, 100%, 50%), -14px 308px hsl(1663.2, 100%, 50%), -16px 309px hsl(1668.6, 100%, 50%), -18px 310px hsl(1674, 100%, 50%), -20px 311px hsl(1679.4, 100%, 50%), -22px 312px hsl(1684.8, 100%, 50%), -23px 313px hsl(1690.2, 100%, 50%), -25px 314px hsl(1695.6, 100%, 50%), -27px 315px hsl(1701, 100%, 50%), -28px 316px hsl(1706.4, 100%, 50%), -30px 317px hsl(1711.8, 100%, 50%), -32px 318px hsl(1717.2, 100%, 50%), -33px 319px hsl(1722.6, 100%, 50%), -35px 320px hsl(1728, 100%, 50%), -36px 321px hsl(1733.4, 100%, 50%), -38px 322px hsl(1738.8, 100%, 50%), -39px 323px hsl(1744.2, 100%, 50%), -41px 324px hsl(1749.6, 100%, 50%), -42px 325px hsl(1755, 100%, 50%), -43px 326px hsl(1760.4, 100%, 50%), -45px 327px hsl(1765.8, 100%, 50%), -46px 328px hsl(1771.2, 100%, 50%), -47px 329px hsl(1776.6, 100%, 50%), -48px 330px hsl(1782, 100%, 50%), -49px 331px hsl(1787.4, 100%, 50%), -50px 332px hsl(1792.8, 100%, 50%), -51px 333px hsl(1798.2, 100%, 50%), -52px 334px hsl(1803.6, 100%, 50%), -53px 335px hsl(1809, 100%, 50%), -54px 336px hsl(1814.4, 100%, 50%), -55px 337px hsl(1819.8, 100%, 50%), -55px 338px hsl(1825.2, 100%, 50%), -56px 339px hsl(1830.6, 100%, 50%), -57px 340px hsl(1836, 100%, 50%), -57px 341px hsl(1841.4, 100%, 50%), -58px 342px hsl(1846.8, 100%, 50%), -58px 343px hsl(1852.2, 100%, 50%), -58px 344px hsl(1857.6, 100%, 50%), -59px 345px hsl(1863, 100%, 50%), -59px 346px hsl(1868.4, 100%, 50%), -59px 347px hsl(1873.8, 100%, 50%), -59px 348px hsl(1879.2, 100%, 50%), -59px 349px hsl(1884.6, 100%, 50%), -60px 350px hsl(1890, 100%, 50%), -59px 351px hsl(1895.4, 100%, 50%), -59px 352px hsl(1900.8, 100%, 50%), -59px 353px hsl(1906.2, 100%, 50%), -59px 354px hsl(1911.6, 100%, 50%), -59px 355px hsl(1917, 100%, 50%), -58px 356px hsl(1922.4, 100%, 50%), -58px 357px hsl(1927.8, 100%, 50%), -58px 358px hsl(1933.2, 100%, 50%), -57px 359px hsl(1938.6, 100%, 50%), -57px 360px hsl(1944, 100%, 50%), -56px 361px hsl(1949.4, 100%, 50%), -55px 362px hsl(1954.8, 100%, 50%), -55px 363px hsl(1960.2, 100%, 50%), -54px 364px hsl(1965.6, 100%, 50%), -53px 365px hsl(1971, 100%, 50%), -52px 366px hsl(1976.4, 100%, 50%), -51px 367px hsl(1981.8, 100%, 50%), -50px 368px hsl(1987.2, 100%, 50%), -49px 369px hsl(1992.6, 100%, 50%), -48px 370px hsl(1998, 100%, 50%), -47px 371px hsl(2003.4, 100%, 50%), -46px 372px hsl(2008.8, 100%, 50%), -45px 373px hsl(2014.2, 100%, 50%), -43px 374px hsl(2019.6, 100%, 50%), -42px 375px hsl(2025, 100%, 50%), -41px 376px hsl(2030.4, 100%, 50%), -39px 377px hsl(2035.8, 100%, 50%), -38px 378px hsl(2041.2, 100%, 50%), -36px 379px hsl(2046.6, 100%, 50%), -35px 380px hsl(2052, 100%, 50%), -33px 381px hsl(2057.4, 100%, 50%), -32px 382px hsl(2062.8, 100%, 50%), -30px 383px hsl(2068.2, 100%, 50%), -28px 384px hsl(2073.6, 100%, 50%), -27px 385px hsl(2079, 100%, 50%), -25px 386px hsl(2084.4, 100%, 50%), -23px 387px hsl(2089.8, 100%, 50%), -22px 388px hsl(2095.2, 100%, 50%), -20px 389px hsl(2100.6, 100%, 50%), -18px 390px hsl(2106, 100%, 50%), -16px 391px hsl(2111.4, 100%, 50%), -14px 392px hsl(2116.8, 100%, 50%), -13px 393px hsl(2122.2, 100%, 50%), -11px 394px hsl(2127.6, 100%, 50%), -9px 395px hsl(2133, 100%, 50%), -7px 396px hsl(2138.4, 100%, 50%), -5px 397px hsl(2143.8, 100%, 50%), -3px 398px hsl(2149.2, 100%, 50%), -1px 399px hsl(2154.6, 100%, 50%); font-size: 40px;";
console.log("%c%s", css, this.log_prefix+text);
}
enter_f_mode(){
this.mode = this.ModeEnum.f_mode;
this.callNotifyFModeFunction(true);
}
leave_f_mode(){
this.current_link_word = "";
this.mode = this.ModeEnum.pre_f_mode;
this.callNotifyFModeFunction(false);
}
abort_f_mode(){
this.log_verbose("Abort f mode");
this.leave_f_mode();
}
disable(){ // disables all reactions to key presses. Call enable_f_mode() with either true or false to re-enable again.
this.mode = this.ModeEnum.all_disabled;
}
// The name says "enable f mode", but the internal workings are actually just setting fmode to always be on if it is disabled.
// enabled = true means that the first character will not be part of the word but instead launch the listening for the word
enable_f_mode(enabled){ // true by default
if(enabled){
this.leave_f_mode = function(){
this.current_link_word = "";
this.mode = this.ModeEnum.pre_f_mode;
};
this.leave_f_mode();
} else {
this.leave_f_mode = function(){
this.current_link_word = "";
this.mode = this.ModeEnum.f_mode;
};
this.leave_f_mode();
}
}
set_f_mode_character(character){
if(character.length !== 1){this.log_error("set_f_mode_character("+character+") failed because it is not a single character");}
else {
this.F_MODE_PREFIX_CHAR = character;
}
}
setNotifyMeFunction(notifyFunc /*params: (current_word ,remaining_words_possible)*/){
if(notifyFunc===undefined){
this.notifyMeFunc = undefined;
return;
}
if(!(notifyFunc.length===2)){this.log_error("NotifyMe function does not accept the right number of parameters");}
this.notifyMeFunc = notifyFunc;
}
callNotifyMeFunction(current_word, remaining_words_possible){
if(this.notifyMeFunc !== undefined){
this.notifyMeFunc(current_word, remaining_words_possible);
}
}
setNotifyFModeFunction(fmodeNotifyFunc /*params: (bool_entering_fmode)*/){
this.notify_f_mode_function = fmodeNotifyFunc;
}
callNotifyFModeFunction(entering_fmode){
if(this.notify_f_mode_function!==undefined){
this.notify_f_mode_function(entering_fmode);
}
}
setOverlayMode(should_use_overlay_mode){
this.overlayMode = should_use_overlay_mode;
}
continue_link_access(key){
if(this.word_caseInsensitivity){key = key.toLowerCase();}
this.current_link_word += key;
const link_words = Array.from(this.wordMap.keys());
// find out whether and more importantly at which index there are remaining possibilities
let counter = 0;
let index = -1;
let i;
let notify_words_possible = [];
for(i=0; i<this.wordMap.size; i++){
if(link_words[i].startsWith(this.current_link_word)){
counter++; notify_words_possible.push(link_words[i]);
index = i;
}
// if there are multiple possibilities, we can wait for more user input
if(counter>1){
// unless the currently finished word is a subset of the future words. In that case, execute the word without leaving f_mode
let func = this.wordMap.get(this.current_link_word);
// notify before executing
this.callNotifyMeFunction(this.current_link_word, notify_words_possible);
// noinspection EqualityComparisonWithCoercionJS
if(func != undefined){
this.log_verbose("The word \""+this.current_link_word+"\" is the start of a longer word. Executing it without leaving fmode.");
func();
}
return;
}
}
// notify before potentially executing
this.callNotifyMeFunction(this.current_link_word, notify_words_possible);
if(counter === 0){
if(this.ignore_ShiftAndCapslock_inWordMode && (key==="shift" || key==="capslock")){
// ignore shift or capslock key if we're in word mode and it was not specified in the remaining possible words
this.log_verbose("ignoring "+key+" because there are no possible matches containing it and this.ignore_ShiftAndCapslock_inWordMode equals true");
this.current_link_word = this.current_link_word.slice(0, -(key.length)); // remove last character again
return;
}
this.log_verbose(key+" not found in available word options. Leaving f_mode.");
this.leave_f_mode();
return;
}
// counter equals 1
// it's obvious now what we will access, but we only do that if the user has typed the whole word
if(this.current_link_word.length === link_words[index].length){
this.wordMap.get(this.current_link_word)(); // execute stored function
this.leave_f_mode();
} else {
// TODO: optimization possible in case where we don't want notifications: store the obvious word
}
}
/*boolean*/ shouldInterruptOnKey(eventkey){
if(this.interrupt_caseInsensitivity){eventkey = eventkey.toLowerCase();}
return Array.from(this.interruptMap_whenInFMode.keys()).includes(eventkey);
}
executeInterruption(eventkey){
if(this.interrupt_caseInsensitivity){eventkey = eventkey.toLowerCase();}
this.interruptMap_whenInFMode.get(eventkey)();
}
/*boolean*/ shouldEnterFModeOnKey(eventkey){
if(this.fmode_caseInsensitivity){eventkey = eventkey.toLowerCase();}
// noinspection EqualityComparisonWithCoercionJS
return (eventkey == this.F_MODE_PREFIX_CHAR);
}
/*boolean*/ shouldIgnoreKey(eventkey){
return this.ignoredKeys.includes(eventkey.toLowerCase());
}
hotkeys_handler_on_key_press(event, handler, that){
if(that.shouldIgnoreKey(event.key)){
return;
}
switch(that.mode){
case that.ModeEnum.f_mode:
if(that.shouldInterruptOnKey(event.key)){
that.executeInterruption(event.key);
} else {
that.continue_link_access(event.key);
}
break;
case that.ModeEnum.pre_f_mode:
if(that.shouldEnterFModeOnKey(event.key)){
that.callNotifyMeFunction(""/*empty current word*/, Array.from(that.wordMap.keys()) /*all words still possible*/);
that.enter_f_mode();
}
break;
case that.ModeEnum.all_disabled: // just for completeness. if disabled, this shouldn't even be called.
break;
default:
this.log_error("[W:] hotkeys encountered unexpected mode");
}
}
hotkeys_init(){
const that = this; // because "this" changes value in different functions, but "that" remains this.
// treats any capital key like a lowercase key, handles all key presses
// noinspection JSUnresolvedFunction
hotkeys('*', function(event, handler){that.hotkeys_handler_on_key_press(event, handler, that)});
this.current_link_word="";
}
// Automatic generation of link hints in f-mode
// Param: generationTarget can be any of the HotkeyManager.GenerationEnum, bitwise OR'ed together.
// If an element is of tag type <a> (anchor) AND of class "BKH", it will be treated only once, even if both are specified.
// at time of writing this (24.08.2018), the only options are class_tagged (with class name BKH) and tag_anchor.
autogenerate(generationTarget, /*optional*/ css_class_name, /*optional*/ arbitrary_swap_class_name) {
// use default GENERATION_CLASS_TAG unless the optional css_class_name parameter is specified
let generationClassTag = (css_class_name === undefined) ? this.GENERATION_CLASS_TAG : css_class_name;
let swap_class = (arbitrary_swap_class_name === undefined) ? this.SWAP_CLASS_NAME_DEFAULT : arbitrary_swap_class_name;
// fetch list of elements to be worked on
let elems_to_gen;
let g;
fetching_elems:
{
// every bit corresponds to one flag. Test if the relevant bit is set.
if (generationTarget === this.GenerationEnum.class_tagged) {
elems_to_gen = document.getElementsByClassName(generationClassTag);
g = "classes tagged "+generationClassTag;
break fetching_elems;
}
if (generationTarget=== this.GenerationEnum.tag_anchor) {
elems_to_gen = document.getElementsByTagName("a");
g = "anchor elements";
break fetching_elems;
}
/*default:*/ break fetching_elems;
}
// fetching elements done. They are now in elems_to_gen, which is a HTMLCollection
this.log_verbose("Autogenerating for the "+g+". ");
const num_elems_to_gen_for = elems_to_gen.length + this.wordMap.size; // need to fit at least this many link hints
let letters = this.computeLettersArray();
let min_len = this.computeMinLength(num_elems_to_gen_for, letters);
// For each element, create a tag
[...Array.from(elems_to_gen)].forEach(function(item, index){
// noinspection JSPotentiallyInvalidUsageOfClassThis
let link_hint_text = this.generateLinkHintText(item, min_len, letters); // generate link hint
item.setAttribute(this.AUTOGEN_LINKHINT_ATTRIBUTE, this.global_index); // give it a unique id based on index
let f = new Function("document.querySelector(\"["+this.AUTOGEN_LINKHINT_ATTRIBUTE+"='"+this.global_index+"']\").click();");
this.wordMap.set(link_hint_text, f); // current value in wordMap is there, but action is undefined. Set up action.
// noinspection JSPotentiallyInvalidUsageOfClassThis
this.addGraphicLinkHint(item, link_hint_text, swap_class); // add the graphics
this.global_index++; // increase global index that persists over multiple autogenerations
}.bind(this));
HotkeyManager.showKeys(false, swap_class, this.iframedocs_that_we_autogenerated_within); // set display to none, even if the css class was not loaded before
}
// same as autogenerate, but you can specify the ID of an element where it calls getElementsByClassName
autogenerateWithinId(containerID, generationTarget, /*optional*/ css_class_name, /*optional*/ arbitrary_swap_class_name) {
let container = HotkeyManager.getElementOrIframeBodyById(document, containerID);
// in case this is an iframe, keep track of it for use in showKeys
this.keepTrackOfIframesWeAutogeneratedWithin(document, containerID);
// use default GENERATION_CLASS_TAG unless the optional css_class_name parameter is specified
let generationClassTag = (css_class_name === undefined) ? this.GENERATION_CLASS_TAG : css_class_name;
let swap_class = (arbitrary_swap_class_name === undefined) ? this.SWAP_CLASS_NAME_DEFAULT : arbitrary_swap_class_name;
// fetch list of elements to be worked on
let elems_to_gen;
let g;
fetching_elems:
{
// every bit corresponds to one flag. Test if the relevant bit is set.
// noinspection JSBitwiseOperatorUsage
if ((generationTarget & this.GenerationEnum.class_tagged) === this.GenerationEnum.class_tagged) {
elems_to_gen = container.getElementsByClassName(generationClassTag);
g = "classes tagged "+generationClassTag;
break fetching_elems;
}
// noinspection JSBitwiseOperatorUsage
if ((generationTarget & this.GenerationEnum.tag_anchor) === this.GenerationEnum.tag_anchor) {
elems_to_gen = container.getElementsByTagName("a");
g = "anchor elements";
break fetching_elems;
}
/*default:*/ break fetching_elems;
}
// fetching elements done. They are now in elems_to_gen, which is a HTMLCollection
this.log_verbose("Autogenerating for the "+g+". ");
const num_elems_to_gen_for = elems_to_gen.length + this.wordMap.size; // need to fit at least this many link hints
let letters = this.computeLettersArray();
let min_len = this.computeMinLength(num_elems_to_gen_for, letters);
// For each element, create a tag
[...Array.from(elems_to_gen)].forEach(function(item, index){
// noinspection JSPotentiallyInvalidUsageOfClassThis
let link_hint_text = this.generateLinkHintText(item, min_len, letters); // generate link hint
item.setAttribute(this.AUTOGEN_LINKHINT_ATTRIBUTE, this.global_index); // give it a unique id based on index
let f = new Function(`HotkeyManager.getElementOrIframeBodyById(document, \"${containerID}\").querySelector(\"[`+this.AUTOGEN_LINKHINT_ATTRIBUTE+"='"+this.global_index+"']\").click();");
this.wordMap.set(link_hint_text, f); // current value in wordMap is there, but action is undefined. Set up action.
// noinspection JSPotentiallyInvalidUsageOfClassThis
this.addGraphicLinkHint(item, link_hint_text, swap_class); // add the graphics
this.global_index++; // increase global index that persists over multiple autogenerations
}.bind(this));
HotkeyManager.showKeys(false, swap_class, this.iframedocs_that_we_autogenerated_within); // set display to none, even if the css class was not loaded before
}
computeLettersArray(){
let homerow_chars = this.HOMEROW_CHARS;
let other_okay_chars = this.OTHER_OKAY_CHARS;
// get rid of important function keys
homerow_chars = homerow_chars.filter(char => char !== this.F_MODE_PREFIX_CHAR);
other_okay_chars = other_okay_chars.filter(char => char !== this.F_MODE_PREFIX_CHAR);
// compute the minimal number of letters needed
return homerow_chars.concat(other_okay_chars);
}
/*int*/ computeMinLength(num_elems_to_gen_for, letters){
let unavailable_words = Array.from(this.wordMap, ([word, action]) => word).concat(Array.from(this.interruptMap_whenInFMode, ([word, action]) => word));
// {num_letters}^{min_length} = num_possible_words ===> min_length = log_{num_letters}{num_possible_words}
function minlen(num){
return Math.ceil(Math.log(num)/Math.log(letters.length));
}
let min_length = minlen(num_elems_to_gen_for);
// make sure the minimal length works even with pre-set triggers. As reason, see the comment below, tagged with ##comment1##
function num_lost_words(pre_set_word_array_array, min_length){
let lost_options = 0;
for(let pre_set_word_array of pre_set_word_array_array){
let lost_len = min_length - pre_set_word_array.length;
lost_options += Math.pow(letters.length,lost_len);
}
return lost_options;
}
let _tmp = 0;
while(minlen(num_lost_words(unavailable_words, min_length)+num_elems_to_gen_for) > min_length){
min_length++;
_tmp++;
}
/*##comment1##
// This is no longer an issue, but the explanation is left here to show why the code above (referencing this comment) is needed.
Make sure you don't have many too short words in the wordMap before running autogenerate, since this might cause it to wrongly estimate the number of characters needed.
E.g. if you have already manually mapped "a" to some action, and then you run autogenerate on 100 anchor elements.
Autogenerate realizes that it will need log(100)=7 characters to handle all these.
But it is not allowed to generate any word that starts with "a", since your trigger already exists on that, and cancels the evaluation.
It is also not allowed to generate any word that is the beginning of your "a", since that would cancel the evaluation of your "a".
So we lose a large part of options just because of your "a".
If a problem appears because of that, it will be logged. Just make sure to test your site with the developer tools open.
*/
return min_length;
}
// Returns an unused link hint, consisting of the letters, of length min_length. Or more if the last generated linkhint was longer.
/*String*/ generateLinkHintText(element, min_length, letters){
// noinspection JSUnusedLocalSymbols
let unavailable_words = Array.from(this.wordMap, ([word, action]) => word).concat(Array.from(this.interruptMap_whenInFMode, ([word, action]) => word));
// compute an available word of minimal length, favoring the homerow chars.
// first, set up storage and an initial guess
let word;
// word is an array which contains the character index of each letter
// noinspection EqualityComparisonWithCoercionJS
if (this._generateLinkHintText_lastWordGenerated != undefined){
word = this._generateLinkHintText_lastWordGenerated;
// continue later at the position where this stored word left off
// It's a surefire wrong guess, but it's a good enough guess (close enough to actual values)
// (The logic is that this was set last time and we advance in order)
} else {
// start at the start
word = Array(min_length).fill(0);
}
// if the word that was last generated is shorter than the provided min_length, artificially lengthen it.
while(word.length < min_length){
word.push(letters[0])
}
function to_word(word_array){let w="";// noinspection JSUnusedLocalSymbols
word_array.forEach(function(letter_number, elem_index){w = w.concat(letters[letter_number])});
return w;
}
// recursive search
let initial_guess = word.slice();
word = this.recGenWordGo(letters, unavailable_words, initial_guess); // resulting word must be a word array
if(word === null){
this.log_error("Failed to generate a link hint of length "+initial_guess.length+" when only considering words 'larger' than "+to_word(initial_guess));
}
// keep track of the last word array generated, in case there are many
this._generateLinkHintText_lastWordGenerated = word;
let w = to_word(word);
// add the word to the unavailable words, but don't add an action yet
this.wordMap.set(w,undefined);
// and return the generated word
return String(w);
}
// finds a word_array of length of the initial attempt, or null
// avoids any options "below" the initial attempt
recGenWordGo(letters, not_ok_words, initial_attempt){
if(initial_attempt.some(elem => ((elem >= letters.length) || elem < 0) && this.log_error("[recGenWordGo] initial attempt is not allowed to be "+elem))){
return null;
}
// string from array
function word(word_array){let w="";// noinspection JSUnusedLocalSymbols
word_array.forEach(letter_index => w = w.concat(letters[letter_index]));
return w;
}
// boolean
function is_ok_word(word_array){
let as_word = word(word_array);
return (!not_ok_words.includes(as_word)) && // But what if not_ok_words contains a word that is shorter than our attempt? Then it's not ok if our attempt starts with it
(!not_ok_words.some(not_ok_word => as_word.startsWith(not_ok_word))) && // And we also don't want our attempt to be the beginning of a not-ok-word. We are not allowed to overwrite those
(!not_ok_words.some(not_ok_word => not_ok_word.startsWith(as_word)));
}
if(initial_attempt.length <= 0){return null;}
if(is_ok_word(initial_attempt)){return initial_attempt;}
return this.recGenWord(letters, initial_attempt, is_ok_word);
}
// returns the word array, if found, or null
recGenWord (/*char_array*/ letters, /*int_array*/ initial_attempt, /*function*/ is_ok_word){
// case : available word can be found by just modifying the last digit
let last_digit; let found_word = null;
let current_attempt = initial_attempt;
for(last_digit = initial_attempt[initial_attempt.length-1]; last_digit < letters.length; last_digit++){
current_attempt[current_attempt.length -1] = last_digit;
if(is_ok_word(current_attempt)){found_word = current_attempt; break;}
}
if(found_word != null){
return found_word;
} else {
if(initial_attempt.length <= 1){
return null;
}
}
// case : we need to try all options of the left digits for all options of the rightmost digit
// i.e. we first try to modify only the 2 rightmost digits, then 3, etc.
// deeper recursive calls try the rightmost
if(initial_attempt.length <= 1){
console.log("[W] [recGenWord] You aren't supposed to run this code.\n" +
"initial_attempt: "+initial_attempt+"\n" +
"current_attempt: "+current_attempt+"\n" +
"found_word: "+found_word);
}
for(let left_digit = initial_attempt[0]; left_digit < letters.length; left_digit++) {
let new_is_ok_word = (function(left_digit){return function (word_array) {
if(word_array.length === 0){
console.log("word_array had length 0, so it's trivially an ok word.\n word_array: "+word_array);
return true;
}
// array with left digit as the leftmost entry of the upper call
let expanded_word_array = word_array.slice();
expanded_word_array.unshift(left_digit);
return is_ok_word(expanded_word_array);
};})(left_digit); // hack to enable function declaration within the for loop.
// above is basically just function new_is_ok_word (word_array) { ... }
// find working word if the leftmost digit were fixed to left_digit
// by recursing to only the right part and then prepend the left part again in the end.
found_word = this.recGenWord(letters, initial_attempt.slice(1), new_is_ok_word);
if(found_word !== null){
found_word.unshift(left_digit);
return found_word;
} else {
// allow all numbers again after the first try, since we only block those above the first one
// left_digit is updated as well (in the for loop head)
initial_attempt[initial_attempt.length -1]=0;
}
}
return null; // not found
}
// adds the link hint as text, for debug purposes
static addLinkHint(element, linkHint){
element.text += " [" + linkHint + "] ";
}
// "legacy" link hint that only works for text, but is simpler for text than the wrapped option
addBeautifulLinkHint(element, linkHint, swap_class){
if (!this.overlayMode){
element.innerHTML += `<kbd class=\"${swap_class} ${this.LINKHINT_STYLE_CLASS}\">${linkHint}</kbd>`;
} else {
// overlay Mode requires a container
element.innerHTML += `<span class=\"${this.LINKHINT_OVERLAY_CONTAINER_STYLE_CLASS}\"><kbd class=\"${swap_class} ${this.LINKHINT_STYLE_CLASS} ${this.LINKHINT_OVERLAY_STYLE_CLASS}\">${linkHint}</kbd></span>`;
}
}
// decides whether to use addBeautifulLinkHint or addWrappedLinkHint
addGraphicLinkHint(element, linkHint, swap_class){
// Option 1: create a wrapper around the element, works also with images
// Option 2: just add the link hint directly
// Option 1 is more likely to break things, but is conventient because it is the same for images and for text.
// We use option 2 here, for text, and option 1 for images.
if (element instanceof HTMLImageElement){
this.addWrappedLinkHint(element, linkHint, swap_class);
} else {
this.addBeautifulLinkHint(element, linkHint, swap_class);
}
}
// add link hint, also for images, by using a wrapper div
addWrappedLinkHint(element, linkHint, swap_class){
if (element == undefined){
this.log_error("Element is null! Element: \n"+element);
return;
}
if (!this.overlayMode){
// simply show link hint within text
// of course, this does not work with images, but that's the user's choice when they deactivate overlayMode.
element.innerHTML += `<kbd class=\"${swap_class} ${this.LINKHINT_STYLE_CLASS}\">${linkHint}</kbd>`;
} else {
let linkhint_overlay_class = undefined;
if (element instanceof HTMLImageElement){
linkhint_overlay_class = this.LINKHINT_OVERLAY_IMAGE_CLASS;
} else {
linkhint_overlay_class = this.LINKHINT_OVERLAY_TEXT_CLASS;
}
// assumes that the element has a parent - i.e. there is no link hint in the document itself
let parent = element.parentNode;
let elemHTML = element.outerHTML;
if ( parent != undefined ){
element.outerHTML =
`<div class="${this.LINKHINT_OVERLAY_CONTAINER_STYLE_CLASS}">
${elemHTML}
<kbd class=\"${swap_class} ${this.LINKHINT_STYLE_CLASS} ${linkhint_overlay_class}\">${linkHint}</kbd>
</div>`;
} else {
this.log_error("Element's parent is null. Element: \n"+elemHTML);
}
}
}
// uses global variable _brotkeysjs__src__path;
// does not load the js and css if it was already loaded.
loadNeededJSCSSForStyleSwapping(){
// abort early
if(this.__loadNeededJSCSSForStyleSwapping_alreadyLoaded === true){return;}
var jsFileLocation = _brotkeysjs__src__path.replace('brotkeys.js','');
// taken from http://www.javascriptkit.com/javatutors/loadjavascriptcss.shtml
function loadjscssfile(filename, filetype){
if (filetype==="js"){ //if filename is a external JavaScript file
var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", filename)
}
else if (filetype==="css"){ //if filename is an external CSS file
var fileref=document.createElement("link");
fileref.setAttribute("rel", "stylesheet");
fileref.setAttribute("type", "text/css");
fileref.setAttribute("href", filename)
}
if ((typeof fileref)!=undefined)
document.getElementsByTagName("head")[0].appendChild(fileref)
}
/*
USAGE:
loadjscssfile("javascript.php", "js") //dynamically load "javascript.php" as a JavaScript file
loadjscssfile("mystyle.css", "css") ////dynamically load and add this .css file
*/
loadjscssfile(jsFileLocation+"keys.css", "css");
this.__loadNeededJSCSSForStyleSwapping_alreadyLoaded = true;
}
// if iframedocs_that_should_be_traversed_as_well is not undefined, it should be a set of elements. Usually some iframe.contentWindow.document or similar
static showKeys(pls_show_keys, of_class, iframedocs_that_should_be_traversed_as_well /*optional*/){
let new_display = pls_show_keys ? "inline" : "none";
let elem;
let elems = Array.from(document.getElementsByClassName(of_class));
for (elem of elems) {elem.style.display=new_display;}
if (iframedocs_that_should_be_traversed_as_well != undefined) {
// special handling for iframes
for (let iframedoc of iframedocs_that_should_be_traversed_as_well) {
if (iframedoc != document) {
// because we already handled the document above
let elems = Array.from(iframedoc.getElementsByClassName(of_class));
for (elem of elems) { elem.style.display=new_display;}
}
}
}
}
// gets element with id within doc
static getElementOrIframeBodyById(doc, id){
let elem = doc.getElementById(id);
if (elem.tagName == "IFRAME") {
return elem.contentWindow.document;
}
return elem;
}
keepTrackOfIframesWeAutogeneratedWithin(doc, id){
let elem = doc.getElementById(id);
if (elem.tagName == "IFRAME") {
this.iframedocs_that_we_autogenerated_within.add(elem.contentWindow.document);
}
}
// function which generates another function
// which takes a boolean whether we're entering f-mode, and then does the
// showing / hiding the link hints as a reaction to them being pressed
genToggleKeysOnNotify(swapClass){
if(swapClass===undefined){swapClass = this.SWAP_CLASS_NAME_DEFAULT;}
this.loadNeededJSCSSForStyleSwapping();
return function(entering) {
if (entering) {
HotkeyManager.showKeys(true, swapClass, this.iframedocs_that_we_autogenerated_within);
} else {
HotkeyManager.showKeys(false, swapClass, this.iframedocs_that_we_autogenerated_within);
}
}.bind(this);
}
// generate an identifier unique among HotkeyManagers
static genHotkeyManagerUID(){
let prev = HotkeyManager.latest_uid
if (prev == undefined) {
prev = -1;
}
let uid = prev + 1;
HotkeyManager.latest_uid = uid;
return uid;
}
}
var _brotkeysjs__src__path;
function _brotkeys_global_init(){ // get script tag that is the last at current evaluation time
var scripts = document.getElementsByTagName("script");
_brotkeysjs__src__path = scripts[scripts.length-1].src;
}
_brotkeys_global_init();
/*
Usage of autogenerate examples below:
run brotkeys_autogenerate_manager_for_class_tag("arbitrary_class"); or run brotkeys_autogenerate_manager_for_anchors();
Be aware that running both will create two independent managers, which means some words might trigger multiple actions when entered. Or undefined behaviour.
If you need anything more specific, it's better to use autogenerate_manager_... only as an inspiration to write your own function.
It should be possible to run manager.autogenerate(manager.GenerationEnum.tag_anchor); on an existing manager as done in these functions,
then run manager.autogenerate(manager.GenerationEnum.class_tagged, "arbitrary_class"); to modify the same manager's content without duplicating trigger words.
However, in that case it might happen that different lengths are used. So if you want to do that, giving everything a class tag is probably easier than figuring out how this code works.
(Hint: you can do that from javascript, no need to add that class manually.)
If you just run multiple autogenerates on the same manager, the words will become longer very quickly!
*/
function brotkeys_autogenerate_manager_for_anchors(){
var manager;
// words of the form [f]abcdefg unless enable_f_mode is set to false
// DONT INCLUDE AN UPPERCASE X, because that is used to immediately abort f mode here. (Because it's set below)
var wordMap = new Map([
// define some manually made input/action pairs
["secret", function(){window.open("https://github.com/lucidBrot/Brotkeys.js", "_self");}],
]);
// single characters that can interrupt at any time during the word-typing mode
var interruptMap = new Map([
["X", function(){manager.abort_f_mode();}], // abort fmode on fX
["D", function(){console.log("user disabled shortcuts"); manager.disable();}], // completely disable Brotkeys on fD
]);
manager = new HotkeyManager(wordMap, interruptMap);
manager.interrupt_caseInsensitivity = false; // case sensitive
// load neccessary css for style swapping (needed for showing link hints with the internal manager.addBeautifulLinkHint or manager.addWrappedLinkHint)
manager.loadNeededJSCSSForStyleSwapping();
// please notify me on entering and leaving fmode by calling this function.
// This function causes the link hints to appear or disappear
// Use any string you wish here. Different strings in different instances of HotkeyManager mean different classes are shown/hidden.
let swap_class = "LB-Swap-Class";
var notifyFModeFunc = manager.genToggleKeysOnNotify(swap_class);
manager.setNotifyFModeFunction(notifyFModeFunc);
manager.log_prefix = "[M1] "; // feel free to modify this as you see fit
manager.autogenerate(manager.GenerationEnum.tag_anchor, undefined, swap_class); // autogenerate tags
// we don't care about the second argument since we're using tag_anchor
// we use the swap_class parameter to determine what class to use. all that is important here is that it's the same as in the genToggleKeysOnNotify call.
}
function brotkeys_autogenerate_manager_for_class_tag(css_class_name){
var manager;
// words of the form [f]abcdefg unless enable_f_mode is set to false
// DONT INCLUDE AN UPPERCASE X, because that is used to immediately abort f mode here. (Because it's set below)
var wordMap = new Map([
// define some manually made input/action pairs
["secret", function(){window.open("https://github.com/lucidBrot/Brotkeys.js", "_self");}],
]);
// single characters that can interrupt at any time during the word-typing mode
var interruptMap = new Map([
["X", function(){manager.abort_f_mode();}], // abort fmode on fX
["D", function(){console.log("user disabled shortcuts"); manager.disable();}], // completely disable Brotkeys on fD
]);
manager = new HotkeyManager(wordMap, interruptMap);
manager.interrupt_caseInsensitivity = false; // case sensitive
// load neccessary css for style swapping (needed for showing link hints with the internal manager.addBeautifulLinkHints)
manager.loadNeededJSCSSForStyleSwapping();
// please notify me on entering and leaving fmode by calling this function.
// This function causes the link hints to appear or disappear
// Use any string you wish here. Different strings in different instances of HotkeyManager mean different classes are shown/hidden.
var notifyFModeFunc = manager.genToggleKeysOnNotify(manager.SWAP_CLASS_NAME_DEFAULT);
manager.setNotifyFModeFunction(notifyFModeFunc);
manager.log_prefix = "[M1] "; // feel free to modify this as you see fit
manager.autogenerate(manager.GenerationEnum.class_tagged, css_class_name, manager.SWAP_CLASS_NAME_DEFAULT); // autogenerate tags (and onClick behaviour when the user triggers brotkeys) for elements with the given class
// the last argument is for internal workings and could be left unset - but it is clearer this way, since genToggleKeysOnNotify needs the same class name as argument
}
/*
TODO: add option to explicitly ignore specific elements for autogeneration
TODO: add option to explicitly ignore elements for autogeneration that already have a brotkeysid from that manager
TODO: add space before link hint
*/