这篇文章上次修改于 398 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

参考GitHub,为GitLab站点在查看Markdown文件页面滚动时标题浮动置顶功能,通过标题栏可调出文档目录进行跳转等快捷操作。

发现在GitLab中阅读仓库中Markdown文档时,标题栏不能悬浮置顶显示,与Github体验稍微有些差异。可以通过油猴插件执行自定义脚本实现。

效果如图:
Snipaste_2023-03-29_10-21-27.png

脚本功能大体就是获取标题div,在需要浮动时设置其浮动,不需要时还原;另外由于浮动后宽度无法再根据父盒子进行填充,需要获取正确的宽度后添加样式保证样式不变。

脚本如下:

// ==UserScript==
// @name         GitLab站点Markdown内容页标题浮动
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  参考GitHub,为GitLab站点在查看Markdown文件页面滚动时标题浮动置顶功能(标题栏可调出文档目录进行快速跳转)
// @match        *://*/*.md
// @match        *://*/*.md#*
// @author       doufu.de
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';

    if (document.title.indexOf('GitLab') == -1) {
        return;
    }
    console.log('油猴脚本 - GitLab站点Markdown内容页标题浮动');

    //需要等待页面加载结束后执行
    //windows.onload等方法回调时文档仍未加载完成,故采用定时器方式
    var try_count = 0;
    var start_waiter = setInterval(function () {
        var loaded = initEnv();
        if (loaded == true) {
            updateFloat();
            startsSrollListener();
            clearInterval(start_waiter);
        }
        else {
            try_count = try_count + 1;
            if (try_count > 100) {
                clearInterval(start_waiter);
                console.error('初始化超时');
            }
        }
    }, 1000);

    var div_file_holder = null; //父div,用于获取设置浮动后边框样式
    var div_file_title = null;  //需要浮动的div
    var div_blob_viewer = null; //浮动边框下的div,根据其顶部位置判断是否需要浮动及设置浮动后div的宽度
    var resizeObserver = null;  //监视网页大小改变,重设浮动后div宽度

    function initEnv() {
        if (div_file_holder == null) {
            div_file_holder = document.querySelector('div[class*="file-holder"]')
        }
        if (div_file_title == null) {
            div_file_title = document.querySelector('div[class*="js-file-title"]');
            if (div_file_title != null) {
                var height = getHeaderHeight(true);
                var css = `.gitlab-md-banner-float{
                    position:fixed;
                    top: ` + height + `px;
                    z-index:9999;
                    border-radius:0px !important;
                }`;
                GM_addStyle(css);
                //console.debug(css);
            }
        }
        if (div_blob_viewer == null) {
            div_blob_viewer = document.querySelector(
                'div[class*="blob-viewer"][data-rich-type*="markup"');
            if (div_blob_viewer != null) {
                resizeObserver = new ResizeObserver((entries) => {
                    var float = isFloating();
                    //console.log('onrezie', float);
                    setFloatStyle(float);
                });
                resizeObserver.observe(div_blob_viewer);
            }
        }
        if (div_file_holder == null
            || div_file_title == null
            || div_blob_viewer == null) {
            return false;
        }
        else {
            return true;
        }
    }

    function startsSrollListener() {
        window.addEventListener('scroll', function (e) {
            updateFloat();
        });
    }

    function updateFloat() {
        var pos = getElementViewTopPos(div_blob_viewer);
        var float = isFloating();
        var height = getHeaderHeight(float);
        //console.debug('pos=', pos, 'height=', height);
        if (float) {
            if (pos > height) {
                setFloat(false);
            }
        }
        else {
            if (pos < height) {
                setFloat(true);
            }
        }
    }

    function isFloating() {
        if (div_file_title == null) {
            return false;
        }
        return div_file_title.classList.contains('gitlab-md-banner-float');
    }

    function setFloat(float) {
        if (float == true) {
            div_file_title.classList.add('gitlab-md-banner-float');
        }
        else {
            div_file_title.classList.remove('gitlab-md-banner-float');
        }
        setFloatStyle(float);
    }

    function setFloatStyle(float) {
        var style = null;
        if (float == true) {
            style = getParentsStyle();
        }
        //console.debug('style', style);
        if (style != '') {
            div_file_title.style = style;
        }
        else {
            div_file_title.style = '';
        }
    }

    function getHeaderHeight(float) {
        var h = 0;
        var nav = document.querySelector('header[class*="navbar navbar-gitlab"]');
        h = h + nav.clientHeight;

        if (float == false) {
            h = h + div_file_title.clientHeight;
        }
        return h;
    }

    function getParentsStyle() {
        if (div_blob_viewer == null) {
            return '';
        }
        else {
            var style = '';
            if (div_blob_viewer) {
                var w = Math.round(parseFloat(getComputedStyle(div_blob_viewer).width));
                if (w > 0) {
                    style = 'width:' + w + 'px;'
                }
            }
            if (div_file_holder != null) {
                var b = getComputedStyle(div_file_holder, null).borderRight;
                if (b != '') {
                    style = style + 'border-right:' + b + ';';
                }
            }
            //console.debug(style);
            return style;
        }
    }

    function getElementViewTopPos(element) {
        var actualTop = element.offsetTop;
        var current = element.offsetParent;

        while (current !== null) {
            actualTop += current.offsetTop;
            current = current.offsetParent;
        }
        var elementScrollTop = 0;
        if (document.compatMode == 'BackCompat') {
            elementScrollTop = document.body.scrollTop;
        } else {
            elementScrollTop = document.documentElement.scrollTop;
        }

        return actualTop - elementScrollTop;
    }

})();