Standard Notes 介面清理器

用於隱藏 Standard Notes 網頁介面中針對免費用戶的進階功能提示的使用者腳本。

目前為 2025-06-24 提交的版本,檢視 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Standard Notes UI Cleaner
// @name:ja      Standard Notes UI クリーナー
// @name:en      Standard Notes UI Cleaner
// @name:zh-CN   Standard Notes 界面清理器
// @name:zh-TW   Standard Notes 介面清理器
// @name:ko      Standard Notes UI 클리너
// @name:fr      Nettoyeur d'interface Standard Notes
// @name:es      Limpiador de interfaz de Standard Notes
// @name:de      Standard Notes UI-Reiniger
// @name:pt-BR   Limpador de UI do Standard Notes
// @name:ru      Очистка интерфейса Standard Notes
// @version      1.4.0
// @description         A userscript to hide premium prompts and other clutter from the Standard Notes web UI for free users.
// @description:ja      Standard NotesのWeb UIから、無料ユーザーには不要なプレミアム案内やボタンを非表示にして、すっきりとした画面に整えます。
// @description:zh-CN   一个用户脚本,用于隐藏 Standard Notes 网页界面中对免费用户无用的高级功能提示。
// @description:zh-TW   用於隱藏 Standard Notes 網頁介面中針對免費用戶的進階功能提示的使用者腳本。
// @description:ko      무료 사용자를 위해 Standard Notes 웹 UI에서 프리미엄 메시지와 기타 요소를 숨깁니다.
// @description:fr      Script utilisateur pour masquer les messages premium et autres éléments gênants dans l'interface web de Standard Notes pour les utilisateurs gratuits.
// @description:es      Script de usuario para ocultar mensajes premium y otros elementos en la interfaz web de Standard Notes para usuarios gratuitos.
// @description:de      Userscript zum Ausblenden von Premium-Hinweisen und anderen UI-Elementen für kostenlose Nutzer in Standard Notes.
// @description:pt-BR   Um userscript para ocultar avisos premium e outros elementos da interface web do Standard Notes para usuários gratuitos.
// @description:ru      Скрипт для скрытия премиум-уведомлений и лишних элементов интерфейса Standard Notes для бесплатных пользователей.
// @namespace    https://github.com/koyasi777/standardnotes-ui-cleaner
// @author       koyasi777
// @match        https://app.standardnotes.com/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// @homepageURL  https://github.com/koyasi777/standardnotes-ui-cleaner
// @supportURL   https://github.com/koyasi777/standardnotes-ui-cleaner/issues
// @icon         https://app.standardnotes.com/favicon/favicon-32x32.png
// ==/UserScript==


(function() {
    'use strict';

    /**
     * Finds and hides the targeted UI elements based on user's request.
     * It uses specific, language-independent selectors and element properties to avoid accidentally hiding wrong elements.
     * Instead of removing elements, it sets their display style to 'none', which is safer for single-page applications.
     */
    function cleanupUI() {

        // --- 1. Hide "Unlock features" buttons ---
        // This targets the button based on its English text, as it's often a fallback or primary language in the code.
        try {
            const unlockButtons = Array.from(document.querySelectorAll('button'));
            unlockButtons.forEach(button => {
                if (button.textContent === 'Unlock features') {
                    const parentContainer = button.parentElement;
                    if (parentContainer && parentContainer.style.display !== 'none') {
                        parentContainer.style.display = 'none';
                    }
                }
            });
        } catch (e) {
            console.error("SN Cleaner: Failed to hide 'Unlock features' button.", e);
        }


        // --- 2. Hide "Folders" label and "Files" view ---
        // This is now language-independent.
        try {
            // Hide the "Folders" label by identifying the header that contains both a "Tags" span and a "Folders" label.
            document.querySelectorAll('.section-title-bar-header .title').forEach(titleDiv => {
                const boldSpan = titleDiv.querySelector('span.font-bold');
                const label = titleDiv.querySelector('label');
                // This structure (a bold span next to a label in the title) is unique to the Tags/Folders section.
                if (boldSpan && label && label.style.display !== 'none') {
                    label.style.display = 'none';
                }
            });

            // Hide the "Files" view link using its reliable unique ID.
            const filesView = document.querySelector('#react-tag-files');
            if (filesView) {
                const buttonContainer = filesView.closest('button');
                if (buttonContainer && buttonContainer.style.display !== 'none') {
                    buttonContainer.style.display = 'none';
                }
            }
        } catch (e) {
             console.error("SN Cleaner: Failed to hide 'Folder/Files' elements.", e);
        }


        // --- 3. Hide "Change note type" button in the editor header ---
        // This uses the aria-label, which is less likely to change than visible text.
        try {
            const changeTypeButton = document.querySelector('button[aria-label="Change note type (Ctrl+Shift+/)"]');
            if (changeTypeButton) {
                const parentDiv = changeTypeButton.parentElement;
                if (parentDiv && parentDiv.style.display !== 'none') {
                    parentDiv.style.display = 'none';
                }
            }
        } catch (e) {
            console.error("SN Cleaner: Failed to hide 'Change note type' button.", e);
        }

        // --- 4. Hide "Notes" button in the sort/filter popover menu ---
        // This is now handled by the more robust logic in section 5.
        // The old logic is kept here but commented out for reference.
        /*
        try {
            // This selector is complex to ensure we only target the correct button in the popover.
            const allDivs = document.querySelectorAll('div');
            allDivs.forEach(div => {
                if (div.textContent === 'Notes' && div.children.length === 0) {
                    const parentFlexContainer = div.parentElement;
                    if (parentFlexContainer && parentFlexContainer.querySelector('svg') && parentFlexContainer.classList.contains('flex')) {
                        const button = parentFlexContainer.closest('button');
                        if (button && button.parentElement && button.parentElement.classList.contains('justify-between')) {
                            if (button.style.display !== 'none') {
                                button.style.display = 'none';
                            }
                        }
                    }
                }
            });
        } catch (e) {
            console.error("SN Cleaner: Failed to hide 'Notes' popover button.", e);
        }
        */

        // --- 5. In popovers, keep "Global" button and hide other preference targets (like "Notes") ---
        // This is now language-independent.
        try {
            // Find the "Global" button, which is assumed to have consistent text.
            const globalButton = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.trim() === 'Global');

            if (globalButton) {
                // Find the container holding both "Global" and "Notes" buttons.
                const buttonContainer = globalButton.closest('.flex.justify-between');
                if (buttonContainer) {
                    // Hide all buttons in this container except for the "Global" one.
                    buttonContainer.querySelectorAll('button').forEach(button => {
                        if (button.textContent.trim() !== 'Global' && button.style.display !== 'none') {
                            button.style.display = 'none';
                        }
                    });
                }
            }
        } catch (e) {
            console.error("SN Cleaner: Failed to handle 'Global' settings buttons.", e);
        }

        // --- 6. Hide "Create smart view" button ("スマートビューを作成") ---
        // This targets the "+" button in the "Views" section header using its SVG path data.
        try {
            const plusIconPath = 'M13.6 8a.8.8 0 0 1-.8.8h-4v4a.8.8 0 1 1-1.6 0v-4h-4a.8.8 0 1 1 0-1.6h4v-4a.8.8 0 0 1 1.6 0v4h4a.8.8 0 0 1 .8.8Z';
            const plusIconSelector = `path[d="${plusIconPath}"]`;

            document.querySelectorAll('.section-title-bar-header').forEach(header => {
                const plusIcon = header.querySelector(plusIconSelector);
                if (plusIcon) {
                    const buttonToHide = plusIcon.closest('button');
                    const titleElement = header.querySelector('.title .font-bold');
                    if (buttonToHide && titleElement && buttonToHide.style.display !== 'none') {
                        // Additional check to make sure we're not hiding the "New Tag" button by mistake.
                        if (!buttonToHide.getAttribute('aria-label')?.includes('tag')) {
                           buttonToHide.style.display = 'none';
                        }
                    }
                }
            });
        } catch (e) {
            console.error("SN Cleaner: Failed to hide 'Create smart view' button.", e);
        }
    }

    // A MutationObserver re-runs the cleanup function whenever the page content changes.
    const observer = new MutationObserver(() => {
        cleanupUI();
    });

    // Start observing the entire document body for structural changes.
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Run once on initial load.
    cleanupUI();

})();