全屏菜单(Esc 退出全屏)

see https://ld246.com/article/1746079460404

// 全屏菜单(Esc退出全屏)
// see https://ld246.com/article/1746079460404
(()=>{
    // 判断是否全屏状态
    if(localStorage.getItem('isFullscreen') === 'true') {
        const comfirmHtml = `<div data-key="dialog-confirm" class="b3-dialog--fullScreen b3-dialog--open"><div class="b3-dialog" style="z-index: ${++window.siyuan.zIndex};">
<div class="b3-dialog__scrim"></div>
<div class="b3-dialog__container " style="width:520px;height:auto;
left:auto;top:auto">
  <svg class="b3-dialog__close"><use xlink:href="#iconCloseRound"></use></svg>
  <div class="resize__move b3-dialog__header" onselectstart="return false;">⚠️ 全屏确认</div>
  <div class="b3-dialog__body"><div class="b3-dialog__content">
    <div class="ft__breakword">意外退出全屏,确定要 <b>继续全屏</b> 吗?
<div class="fn__hr"></div>
<div class="ft__smaller ft__on-surface">全屏后可按 <code class="fn__code">Esc</code> 键退出全屏</div></div>
</div>
<div class="b3-dialog__action">
    <button class="b3-button b3-button--cancel" id="cancelDialogConfirmBtn">取消</button><div class="fn__space"></div>
    <button class="b3-button b3-button--outline" id="confirmDialogConfirmBtn">继续全屏</button>
</div></div>
  <div class="resize__rd"></div><div class="resize__ld"></div><div class="resize__lt"></div><div class="resize__rt"></div><div class="resize__r"></div><div class="resize__d"></div><div class="resize__t"></div><div class="resize__l"></div>
</div></div></div>`;
        document.body.insertAdjacentHTML('beforeend', comfirmHtml);
        const fullScreenDialog = document.querySelector('.b3-dialog--fullScreen');
        fullScreenDialog.addEventListener('click', (e) => {
            if(
                e.target.closest('.b3-dialog__scrim') ||
                e.target.closest('.b3-dialog__close') ||
                e.target.closest('.b3-button--cancel')
            ) {
                fullScreenDialog.remove();
                localStorage.setItem('isFullscreen', false);
            } else if(e.target.closest('.b3-button--outline')) {
                fullScreenDialog.remove();
                requestFullScreen(document.querySelector('html'));
                localStorage.setItem('isFullscreen', true);
            }
        });
        document.addEventListener('keydown', (event) => {
            const notOtherKey = !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey;
            if (event.key === 'Enter' && notOtherKey && document.querySelector('.b3-dialog--fullScreen')) {
                fullScreenDialog.remove();
                requestFullScreen(document.querySelector('html'));
                localStorage.setItem('isFullscreen', true);
            }
            else if (event.key === 'Escape' && notOtherKey && document.querySelector('.b3-dialog--fullScreen')) {
                fullScreenDialog.remove();
                localStorage.setItem('isFullscreen', false);
            }
        }, true);
    }
    // 监听主菜单
    whenElementExist('#commonMenu .b3-menu__items').then((mainMenu) => {
        let hasToggle = false;
        observeMainMenu(mainMenu, async ()=>{
            if(hasToggle) return;
            hasToggle = true;
            if(mainMenu.querySelector('button[data-id="fullScreen"]')) return;
            const sp2 = mainMenu.querySelector('button[data-id="separator_2"]');
            const fullScreenText = isFullScreen() ? '退出全屏' : '全屏';
            const fullScreenIcon = isFullScreen() ? '#iconFullscreenExit' : '#iconFullscreen';
            const btnString = `<button data-id="fullScreen" class="b3-menu__item"><svg class="b3-menu__icon " style=""><use xlink:href="${fullScreenIcon}"></use></svg><span class="b3-menu__label">${fullScreenText}</span></button>`;
            setTimeout(() => {
                sp2.insertAdjacentHTML('beforebegin', btnString);
                const button = mainMenu.querySelector('button[data-id="fullScreen"]');
                button.onclick = (event) => {
                    window.siyuan.menus.menu.remove(); // 关闭主菜单
                    if(isFullScreen()) {
                        // 退出全屏
                        exitFullscreen();
                        localStorage.setItem('isFullscreen', false);
                        setTimeout(()=>{
                            if(checkSoftFullScreen()) showMessage('检测到您可能处于软件级全屏状态,此操作可能无效!');
                        }, 300);
                    } else {
                        if(checkSoftFullScreen()) showMessage('检测到您可能处于软件级全屏状态,此操作可能无效!');
                        // 全屏
                        requestFullScreen(document.querySelector('html'));
                        localStorage.setItem('isFullscreen', true);
                    }
                };
                setTimeout(()=>hasToggle = false, 200);
            }, 0);
        });
    });
    // 监听全屏变化
    document.addEventListener('fullscreenchange', function () {
      if (isFullScreen()) {
        localStorage.setItem('isFullscreen', true);
      } else {
        localStorage.setItem('isFullscreen', false);
      }
    });
    /**
     * 监控 body 直接子元素中 #commonMenu 的添加
     * @returns {MutationObserver} 返回 MutationObserver 实例,便于后续断开监听
     */
    function observeMainMenu(selector, callback) {
        let hasToggle = false;
        // 创建一个 MutationObserver 实例
        const observer = new MutationObserver((mutationsList) => {
            // 遍历所有变化
            for (const mutation of mutationsList) {
                // 检查是否有节点被添加
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    // 遍历所有添加的节点
                    mutation.addedNodes.forEach((node) => {
                        if(node.nodeType === 1 && node.closest('[data-name="barWorkspace"]')) {
                            hasToggle = true;
                            callback();
                            setTimeout(() => {
                                hasToggle = false;
                            }, 200);
                        }
                    });
                }
            }
        });
        // 开始观察 body 的直接子元素的变化
        selector = typeof selector === 'string' ? document.querySelector(selector) : selector;
        observer.observe(selector || document.body, {
            childList: true, // 监听子节点的添加
            subtree: false, // 仅监听直接子元素,不监听子孙元素
        });
        // 返回 observer 实例,便于后续断开监听
        return observer;
    }
    function requestFullScreen(element) {
        if (element.requestFullscreen) {
            element.requestFullscreen();
        } else if (element.mozRequestFullScreen) { // Firefox
            element.mozRequestFullScreen();
        } else if (element.webkitRequestFullscreen) { // Chrome, Safari, Opera
            element.webkitRequestFullscreen();
        } else if (element.msRequestFullscreen) { // IE/Edge
            element.msRequestFullscreen();
        }
    }
    function exitFullscreen() {
      if (document.exitFullscreen) {
        document.exitFullscreen();
      } else if (document.webkitExitFullscreen) { /* Chrome, Safari, Opera */
        document.webkitExitFullscreen();
      } else if (document.mozCancelFullScreen) { /* Firefox */
        document.mozCancelFullScreen();
      } else if (document.msExitFullscreen) { /* IE/Edge Legacy */
        document.msExitFullscreen();
      }
    }
    function isFullScreen() {
      return !!document.fullscreenElement;
    
    }
    function checkSoftFullScreen() {
      const threshold = 0.99; // 容忍误差
      const widthMatch = window.innerWidth / screen.width > threshold;
      const heightMatch = window.innerHeight / screen.height > threshold;
      if (widthMatch && heightMatch) {
        return true;
      } else {
        return false;
      }
    }
    // 等待元素出现
    function whenElementExist(selector, node) {
        return new Promise(resolve => {
            const check = () => {
                const el = typeof selector==='function'?selector():(node||document).querySelector(selector);
                if (el) resolve(el); else requestAnimationFrame(check);
            };
            check();
        });
    }
    // 发送通知消息
    function showMessage(message, isError = false, delay = 7000) {
        return fetch('/api/notification/' + (isError ? 'pushErrMsg' : 'pushMsg'), {
            "method": "POST",
            "body": JSON.stringify({"msg": message, "timeout": delay})
        });
    }
})();
image.png

留下你的脚步
推荐阅读