Overleaf fileswitcher (no longer maintained)

Custom hotkeys for switching between files on overleaf.com

  1. // ==UserScript==
  2. // @name Overleaf fileswitcher (no longer maintained)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.22
  5. // @license apache2
  6. // @description Custom hotkeys for switching between files on overleaf.com
  7. // @author Aditya Sriram
  8. // @match https://www.overleaf.com/project/*
  9. // @grant none
  10. // @require http://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
  11. // @require http://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.6.1/mousetrap.min.js
  12. // @require http://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.6.1/plugins/global-bind/mousetrap-global-bind.min.js
  13. // ==/UserScript==
  14.  
  15. (function($, undefined) {
  16. // list of allowed extensions (skip files that don't match)
  17. var filetypes = ['tex', 'bib', 'txt', 'pdf', 'cls', 'sty', 'png/jpg'];
  18. var allow_ext = ['tex', 'bib', 'txt']; // in lowercase
  19. var prev_file = undefined;
  20. var cur_file = undefined;
  21. var allow_all_types = false;
  22.  
  23. var shortcuts = {};
  24.  
  25. function keybindg(key, desc, func) {
  26. shortcuts[key] = desc;
  27. Mousetrap.bindGlobal(key, function() {
  28. console.log("triggered " + key);
  29. func();
  30. });
  31. }
  32.  
  33. function getFileName(i,e) {
  34. if (typeof(e) === "undefined") e = i;
  35. return $(e).find('span.ng-binding').eq(0).text().trim();
  36. }
  37.  
  38. function getFilePath(file) {
  39. var parents = file.parents('file-entity');
  40. return parents.map(getFileName).get().reverse();
  41. }
  42.  
  43. function matchExtension(i, f) {
  44. var fname = getFileName(f);
  45. if (allow_all_types) return true;
  46. for (var exts of allow_ext) {
  47. for (var ext of exts.split("/")) {
  48. if (fname.toLowerCase().endsWith(ext))
  49. return true;
  50. }
  51. }
  52. return false;
  53. }
  54.  
  55. function getFileList() {
  56. return $('file-entity:not(:has(div[ng-controller]))');
  57. }
  58.  
  59. function getCurrentFile() {
  60. var files = getFileList();
  61. var current = files.filter(':has(.selected)');
  62. if (current.length == 0) {
  63. return undefined;
  64. } else {
  65. return current.eq(0);
  66. }
  67. }
  68.  
  69. function resizePanes() {
  70. $('a.custom-toggler-west')[0].click();
  71. $('a.custom-toggler-west')[0].click();
  72. }
  73.  
  74. function dispFile(file) {
  75. var filename = getFileName(file);
  76. var filepath = getFilePath(file);
  77. $('#monkey-filename').text("/" + filepath.concat(filename).join("/"));
  78. }
  79.  
  80. function focusFile(file) {
  81. prev_file = cur_file;
  82. file.find('li:not(.ng-scope)').eq(0).click(); // click li to focus file
  83. cur_file = file;
  84. dispFile(file);
  85. resizePanes();
  86. }
  87.  
  88. function switchFile(n) {
  89. var files = getFileList();
  90. files = files.filter(matchExtension);
  91.  
  92. var curidx = files.index(getCurrentFile());
  93. if (curidx < 0) {
  94. curidx = 0;
  95. console.log('no filtered file selected, falling back to first file');
  96. }
  97.  
  98. var newidx = curidx+n;
  99. if (newidx >= files.length) newidx = 0;
  100. if (newidx < 0) newidx = files.length-1;
  101.  
  102. var newfile = files.eq(newidx);
  103. focusFile(newfile);
  104. }
  105.  
  106. function encloser(cmd) {
  107. var fn = function(editor) {
  108. var selection = editor.getSelection();
  109. if (selection.isEmpty()) {
  110. editor.insert("\\" + cmd + "{}");
  111. return editor.navigateLeft(1);
  112. }
  113. var text = editor.getCopyText();
  114. return editor.insert("\\" + cmd + "{" + text + "}");
  115. }
  116. return fn;
  117. }
  118.  
  119. function fileTypeSelector() {
  120. var cursetting = filetypes.map((s,i) => i+1+". "+((allow_ext.includes(s) || allow_all_types) ? s + '*' : s));
  121. var ans = prompt("Select the file types to allow or type 'all'\n" + cursetting.join("\n"));
  122. if (ans && ans.trim().length > 0) {
  123. allow_all_types = (ans.trim() == "all");
  124. allow_ext = [];
  125. for (var c of ans) {
  126. if ('0123456789'.includes(c) !== -1 && parseInt(c) <= filetypes.length)
  127. allow_ext.push(filetypes[parseInt(c)-1]);
  128. }
  129. }
  130. }
  131.  
  132. function init() {
  133. console.log("activating custom overleaf hotkeys...");
  134. var editor = ace.edit($('.ace-editor-body')[0]);
  135. //console.log(editor);
  136. // replace italics hotkey function with custom emphasize function
  137. editor.commands.byName['italics'].exec = encloser('emph');
  138. // ctrl-m to insert '\text'
  139. editor.commands.addCommand({
  140. name: "mathmode",
  141. bindKey: {
  142. win: "Ctrl-M",
  143. mac: "Command-M"
  144. },
  145. exec: encloser('text'),
  146. readOnly: !1
  147. });
  148.  
  149. // add span to display file path
  150. $('span.name.ng-binding').parent().after('<span id="monkey-filename" style="color:white;"></span>');
  151. // on click filepath, show filetype selector
  152. $('#monkey-filename').css("cursor", "pointer").click(fileTypeSelector);
  153.  
  154. prev_file = cur_file = getCurrentFile();
  155. dispFile(getCurrentFile());
  156.  
  157. // add eventlistener to detect file change due to manual user click
  158. $('aside.file-tree').on('click', 'file-entity li.ng-scope', function() {
  159. if (getFileName(getCurrentFile()) != getFileName(cur_file)) {
  160. console.log("manual file change detected");
  161. prev_file = cur_file;
  162. cur_file = getCurrentFile();
  163. dispFile(getCurrentFile());
  164. }
  165. });
  166.  
  167. keybindg('ctrl+shift+pageup', 'Previous File', function() {switchFile(-1);});
  168. keybindg('ctrl+shift+pagedown', 'Next File', function() {switchFile(+1);});
  169. keybindg('ctrl+shift+,', 'Previous File', function() {switchFile(-1);});
  170. keybindg('ctrl+shift+.', 'Next File', function() {switchFile(1);});
  171. keybindg('ctrl+shift+/', 'Past File', function() {focusFile(prev_file);});
  172. console.log('hotkeys:', JSON.parse(JSON.stringify(shortcuts)));
  173. }
  174.  
  175. function wait_to_load(callback) {
  176. if ($('.loading-screen-brand').length > 0) {
  177. console.log("still loading");
  178. window.setTimeout(wait_to_load, 500, callback);
  179. } else {
  180. console.log("detected loading completion");
  181. window.setTimeout(callback, 200);
  182. }
  183. }
  184.  
  185. $(document).ready(function() {
  186. wait_to_load(init);
  187. });
  188.  
  189. })(window.jQuery.noConflict(true));