您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fix the awful non-responsive layout on n8n workflow pages, kupo!
// ==UserScript== // @name n8n Workflow Preview Enhancer // @namespace http://tampermonkey.net/ // @version 1.9 // @description Fix the awful non-responsive layout on n8n workflow pages, kupo! // @author You // @match https://n8n.io/workflows/* // @exclude https://n8n.io/workflows/ // @exclude https://n8n.io/workflows // @grant none // @run-at document-idle // @license GPL-3.0-or-later // ==/UserScript== (function() { 'use strict'; console.log('n8n Layout Fix: Enhanced version with SPA navigation support, kupo!'); let isFixing = false; let fixAttempts = 0; const maxFixAttempts = 15; // Increased for slow iframe loading // Function to find elements in shadow DOM const findInShadowDOM = (selector) => { const elements = []; // Find all elements with shadow roots const allElements = document.querySelectorAll('*'); allElements.forEach(el => { if (el.shadowRoot) { const shadowElements = el.shadowRoot.querySelectorAll(selector); elements.push(...shadowElements); } }); return elements; }; // Enhanced function to wait for iframe content const waitForIframeContent = () => { return new Promise((resolve) => { const checkIframe = () => { const iframe = document.querySelector('#int_iframe, iframe.embedded_workflow_iframe'); if (iframe) { // Check if iframe has loaded content try { if (iframe.contentDocument || iframe.contentWindow) { console.log('Iframe detected and accessible, kupo!'); resolve(true); return; } } catch (e) { // Cross-origin iframe, but that's normal for n8n console.log('Cross-origin iframe detected (normal), kupo!'); resolve(true); return; } } // Also check for shadow DOM iframe const shadowIframe = findInShadowDOM('#int_iframe, .embedded_workflow_iframe'); if (shadowIframe.length > 0) { console.log('Shadow DOM iframe found, kupo!'); resolve(true); return; } setTimeout(checkIframe, 200); }; checkIframe(); // Fallback timeout setTimeout(() => resolve(false), 8000); }); }; // Function to apply all layout fixes const fixLayout = async () => { if (isFixing) return false; isFixing = true; console.log(`Fix attempt ${fixAttempts + 1}/${maxFixAttempts}, kupo!`); let fixesApplied = 0; // Fix 1: Change flex-direction to column const targetElement = document.querySelector('.section-content-group.flex.flex-col.gap-8.lg\\:flex-row'); if (targetElement) { targetElement.style.flexDirection = 'column !important'; targetElement.classList.remove('lg:flex-row'); console.log('Flex-direction fix applied, kupo!'); fixesApplied++; } // Fix 2: Add max-width calc(100vw - 55px) to BOTH outer wrapper variations const maxWidthSelectors = [ 'div.max-w-section-default.mx-auto.w-full', 'div.mx-auto.w-full.max-w-section-default' ]; maxWidthSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(el => { el.style.maxWidth = 'calc(100vw - 55px)'; console.log('Max-width calc(100vw - 55px) applied to: ' + selector + ', kupo!'); fixesApplied++; }); }); // Fix 3: Update width calc(100vw - 55px) to lg:w-8/12 element const targetWidthElement = document.querySelector('div.lg\\:w-8\\/12'); if (targetWidthElement) { targetWidthElement.style.width = 'calc(100vw - 55px)'; console.log('Width calc(100vw - 55px) applied to lg:w-8/12 element, kupo!'); fixesApplied++; } // Wait for iframe content before applying iframe-related fixes console.log('Waiting for iframe content to load, kupo...'); await waitForIframeContent(); // Fix 4: Regular DOM elements with min-height 100vh const regularSelectors = [ 'div.base-frame.relative.rounded-2xl.bg-white.bg-opacity-10.p-2.base-frame--default.workflow-viewer[data-v-57c68cc9][data-v-2f4878b1][data-v-6c0d4504]', 'div.base-frame-inner.overflow-hidden.rounded-xl[data-v-57c68cc9]', 'n8n-demo[data-v-2f4878b1]' ]; regularSelectors.forEach(selector => { const el = document.querySelector(selector); if (el) { el.style.minHeight = '100vh'; console.log('min-height 100vh applied to ' + selector + ', kupo!'); fixesApplied++; } }); // Fix 5: The bg-white div (try different approach) const bgWhiteElements = document.querySelectorAll('div[data-v-2f4878b1]'); bgWhiteElements.forEach(el => { if (el.classList.contains('bg-white')) { el.style.minHeight = '100vh'; console.log('min-height 100vh applied to bg-white div, kupo!'); fixesApplied++; } }); // Fix 6: Shadow DOM elements (with retry mechanism) const shadowSelectors = [ '.embedded_workflow', '.canvas-container', '#int_iframe.embedded_workflow_iframe.non_interactive' ]; shadowSelectors.forEach(selector => { const shadowElements = findInShadowDOM(selector); shadowElements.forEach(el => { el.style.minHeight = '100vh'; console.log('min-height 100vh applied to shadow DOM element: ' + selector + ', kupo!'); fixesApplied++; }); }); // Alternative approach for shadow DOM - find n8n-demo and access its shadow root const n8nDemo = document.querySelector('n8n-demo[data-v-2f4878b1]'); if (n8nDemo && n8nDemo.shadowRoot) { const shadowRoot = n8nDemo.shadowRoot; // Try to find elements in this specific shadow root const embeddedWorkflow = shadowRoot.querySelector('.embedded_workflow'); if (embeddedWorkflow) { embeddedWorkflow.style.minHeight = '100vh'; console.log('Applied 100vh to .embedded_workflow in shadow root, kupo!'); fixesApplied++; } const canvasContainer = shadowRoot.querySelector('.canvas-container'); if (canvasContainer) { canvasContainer.style.minHeight = '100vh'; console.log('Applied 100vh to .canvas-container in shadow root, kupo!'); fixesApplied++; } const iframe = shadowRoot.querySelector('#int_iframe'); if (iframe) { iframe.style.minHeight = '100vh'; console.log('Applied 100vh to iframe in shadow root, kupo!'); fixesApplied++; } } isFixing = false; return fixesApplied > 0; }; // Function to detect URL changes (for SPA navigation) const detectURLChange = () => { let currentURL = window.location.href; const checkURLChange = () => { if (window.location.href !== currentURL) { currentURL = window.location.href; console.log('URL changed to:', currentURL, 'kupo!'); // Reset fix attempts for new page fixAttempts = 0; // Wait a bit for the new content to start loading setTimeout(() => { console.log('Starting layout fixes after navigation, kupo!'); startFixingProcess(); }, 500); } }; setInterval(checkURLChange, 100); }; // Main fixing process with enhanced retry logic const startFixingProcess = async () => { // Try to fix immediately if (await fixLayout()) { console.log('Initial layout fixes applied successfully, kupo!'); } // Enhanced retry mechanism const retryInterval = setInterval(async () => { fixAttempts++; if (fixAttempts >= maxFixAttempts) { clearInterval(retryInterval); console.log('Max fix attempts reached. Layout fix monitoring complete, kupo!'); return; } await fixLayout(); }, 1000); // Check every second // Watch for dynamic content changes const observer = new MutationObserver(async (mutations) => { let shouldCheck = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // Check if any added nodes contain our target elements mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { // Element node if (node.matches && ( node.matches('n8n-demo') || node.matches('.base-frame') || node.matches('iframe') || node.querySelector && ( node.querySelector('n8n-demo') || node.querySelector('.base-frame') || node.querySelector('iframe') ) )) { shouldCheck = true; } } }); } }); if (shouldCheck && fixAttempts < maxFixAttempts) { setTimeout(async () => { console.log('DOM change detected, applying fixes, kupo!'); await fixLayout(); }, 300); } }); // Start observing with enhanced options observer.observe(document.body, { childList: true, subtree: true, attributes: false, // Don't watch attributes to reduce noise attributeOldValue: false }); }; // Start the URL change detection for SPA navigation detectURLChange(); // Start the initial fixing process startFixingProcess(); })();