Overleaf fileswitcher (no longer maintained)

Custom hotkeys for switching between files on overleaf.com

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Overleaf fileswitcher (no longer maintained)
// @namespace    http://tampermonkey.net/
// @version      1.22
// @license      apache2
// @description  Custom hotkeys for switching between files on overleaf.com
// @author       Aditya Sriram
// @match        https://www.overleaf.com/project/*
// @grant        none
// @require      http://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @require      http://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.6.1/mousetrap.min.js
// @require      http://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.6.1/plugins/global-bind/mousetrap-global-bind.min.js
// ==/UserScript==

(function($, undefined) {
    // list of allowed extensions (skip files that don't match)
    var filetypes = ['tex', 'bib', 'txt', 'pdf', 'cls', 'sty', 'png/jpg'];
    var allow_ext = ['tex', 'bib', 'txt']; // in lowercase
    var prev_file = undefined;
    var cur_file = undefined;
    var allow_all_types = false;

    var shortcuts = {};

    function keybindg(key, desc, func) {
        shortcuts[key] = desc;
        Mousetrap.bindGlobal(key, function() {
            console.log("triggered " + key);
            func();
        });
    }

    function getFileName(i,e) {
        if (typeof(e) === "undefined") e = i;
        return $(e).find('span.ng-binding').eq(0).text().trim();
    }

    function getFilePath(file) {
        var parents = file.parents('file-entity');
        return parents.map(getFileName).get().reverse();
    }

    function matchExtension(i, f) {
        var fname = getFileName(f);
        if (allow_all_types) return true;
        for (var exts of allow_ext) {
            for (var ext of exts.split("/")) {
                if (fname.toLowerCase().endsWith(ext))
                    return true;
            }
        }
        return false;
    }

    function getFileList() {
        return $('file-entity:not(:has(div[ng-controller]))');
    }

    function getCurrentFile() {
        var files = getFileList();
        var current = files.filter(':has(.selected)');
        if (current.length == 0) {
            return undefined;
        } else {
            return current.eq(0);
        }
    }

    function resizePanes() {
        $('a.custom-toggler-west')[0].click();
        $('a.custom-toggler-west')[0].click();
    }

    function dispFile(file) {
        var filename = getFileName(file);
        var filepath = getFilePath(file);
        $('#monkey-filename').text("/" + filepath.concat(filename).join("/"));
    }

    function focusFile(file) {
        prev_file = cur_file;
        file.find('li:not(.ng-scope)').eq(0).click(); // click li to focus file
        cur_file = file;
        dispFile(file);
        resizePanes();
    }

    function switchFile(n) {
        var files = getFileList();
        files = files.filter(matchExtension);

        var curidx = files.index(getCurrentFile());
        if (curidx < 0) {
            curidx = 0;
            console.log('no filtered file selected, falling back to first file');
        }

        var newidx = curidx+n;
        if (newidx >= files.length) newidx = 0;
        if (newidx < 0) newidx = files.length-1;

        var newfile = files.eq(newidx);
        focusFile(newfile);
    }

    function encloser(cmd) {
        var fn = function(editor) {
            var selection = editor.getSelection();
            if (selection.isEmpty()) {
                editor.insert("\\" + cmd + "{}");
                return editor.navigateLeft(1);
            }
            var text = editor.getCopyText();
            return editor.insert("\\" + cmd + "{" + text + "}");
        }
        return fn;
    }

    function fileTypeSelector() {
        var cursetting = filetypes.map((s,i) => i+1+". "+((allow_ext.includes(s) || allow_all_types) ? s + '*' : s));
        var ans = prompt("Select the file types to allow or type 'all'\n" + cursetting.join("\n"));
        if (ans && ans.trim().length > 0) {
            allow_all_types = (ans.trim() == "all");
            allow_ext = [];
            for (var c of ans) {
                if ('0123456789'.includes(c) !== -1 && parseInt(c) <= filetypes.length)
                    allow_ext.push(filetypes[parseInt(c)-1]);
            }
        }
    }

    function init() {
        console.log("activating custom overleaf hotkeys...");
        var editor = ace.edit($('.ace-editor-body')[0]);
        //console.log(editor);
        // replace italics hotkey function with custom emphasize function
        editor.commands.byName['italics'].exec = encloser('emph');
        // ctrl-m to insert '\text'
        editor.commands.addCommand({
            name: "mathmode",
            bindKey: {
                win: "Ctrl-M",
                mac: "Command-M"
            },
            exec: encloser('text'),
            readOnly: !1
        });

        // add span to display file path
        $('span.name.ng-binding').parent().after('<span id="monkey-filename" style="color:white;"></span>');
        // on click filepath, show filetype selector
        $('#monkey-filename').css("cursor", "pointer").click(fileTypeSelector);

        prev_file = cur_file = getCurrentFile();
        dispFile(getCurrentFile());

        // add eventlistener to detect file change due to manual user click
        $('aside.file-tree').on('click', 'file-entity li.ng-scope', function() {
            if (getFileName(getCurrentFile()) != getFileName(cur_file)) {
                console.log("manual file change detected");
                prev_file = cur_file;
                cur_file = getCurrentFile();
                dispFile(getCurrentFile());
            }
        });

        keybindg('ctrl+shift+pageup', 'Previous File', function() {switchFile(-1);});
        keybindg('ctrl+shift+pagedown', 'Next File', function() {switchFile(+1);});
        keybindg('ctrl+shift+,', 'Previous File', function() {switchFile(-1);});
        keybindg('ctrl+shift+.', 'Next File', function() {switchFile(1);});
        keybindg('ctrl+shift+/', 'Past File', function() {focusFile(prev_file);});
        console.log('hotkeys:', JSON.parse(JSON.stringify(shortcuts)));
    }

    function wait_to_load(callback) {
        if ($('.loading-screen-brand').length > 0) {
            console.log("still loading");
            window.setTimeout(wait_to_load, 500, callback);
        } else {
            console.log("detected loading completion");
            window.setTimeout(callback, 200);
        }
    }

    $(document).ready(function() {
        wait_to_load(init);
    });

})(window.jQuery.noConflict(true));