// ==UserScript== // @name uutool.cn在线SVG预览功能增强 // @namespace https://scriptcat.org/zh-CN/script-show-page/3089 // @version 0.4 // @description 增强SVG预览界面样式及交互功能 // @author beibeibeibei // @license MIT // @match https://uutool.cn/svg-preview/ // @grant none // ==/UserScript== (function() { 'use strict'; /** * Minified by jsDelivr using Terser v5.37.0. * Original file: /npm/xml-formatter@3.6.5/dist/browser/xml-formatter-singleton.js * * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files */ !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).xmlFormatter=t()}}((function(){return function t(e,n,o){function r(c,u){if(!n[c]){if(!e[c]){var l="function"==typeof require&&require;if(!u&&l)return l(c,!0);if(i)return i(c,!0);var f=new Error("Cannot find module '"+c+"'");throw f.code="MODULE_NOT_FOUND",f}var a=n[c]={exports:{}};e[c][0].call(a.exports,(function(t){return r(e[c][1][t]||t)}),a,a.exports,t,e,n,o)}return n[c].exports}for(var i="function"==typeof require&&require,c=0;c");if(t>-1){var e=t+3,n={type:"CDATA",content:a.xml.substring(0,e)};return a.xml=a.xml.slice(e),{excluded:!1===a.options.filter(n),node:n}}}}()}function d(){return g(/\s*/),h(!0)||m()||function(){var t=g(/^]*>/)||g(/^]*>/)||g(/^/)||g(/^/);if(t){var e={type:"DocumentType",content:t[0]};return{excluded:!1===a.options.filter(e),node:e}}}()||y(!1)}function y(t){var e=g(t?/^<\?(xml)\s*/:/^<\?([\w-:.]+)\s*/);if(e){for(var n={name:e[1],type:"ProcessingInstruction",attributes:{}};!b()&&!x("?>");){var o=v();if(!o)return;n.attributes[o.name]=o.value}return g(/\?>/),{excluded:!t&&!1===a.options.filter(n),node:n}}}function h(t){var e=g(/^<([^?!\s]+)\s*/);if(e){for(var n={type:"Element",name:e[1],attributes:{},children:[]},o=!t&&!1===a.options.filter(n);!(b()||x(">")||x("?>")||x("/>"));){var r=v();if(!r)return;n.attributes[r.name]=r.value}if(g(/^\s*\/>/))return n.children=null,{excluded:o,node:n};g(/\??>/);for(var i=p();i;)i.excluded||n.children.push(i.node),i=p();if(a.options.strictMode){var c="");if(!a.xml.startsWith(c))throw new s("Failed to parse XML",'Closing tag not matching "'.concat(c,'"'));a.xml=a.xml.slice(c.length)}else g(/^<\/[\w-:.\u00C0-\u00FF]+\s*>/);return{excluded:o,node:n}}}function m(){var t=g(/^/);if(t){var e={type:"Comment",content:t[0]};return{excluded:!1===a.options.filter(e),node:e}}}function v(){var t,e=g(/([^=]+)\s*=\s*("[^"]*"|'[^']*'|[^>\s]+)\s*/);if(e)return{name:e[1].trim(),value:(t=e[2].trim(),t.replace(/^['"]|['"]$/g,""))}}function g(t){var e=a.xml.match(t);if(e)return a.xml=a.xml.slice(e[0].length),e}function b(){return 0===a.xml.length}function x(t){return 0===a.xml.indexOf(t)}function w(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t=t.trim();var n=e.filter||function(){return!0};return a={xml:t,options:Object.assign(Object.assign({},e),{filter:n,strictMode:!0===e.strictMode})},function(){for(var t,e=y(!0),n=[],o=d();o;){if("Element"===o.node.type){if(t)throw new Error("Found multiple root nodes");t=o.node}o.excluded||n.push(o.node),o=d()}if(!t)throw new s("Failed to parse XML","Root Element not found");if(0!==a.xml.length)throw new s("Failed to parse XML","Not Well-Formed XML");return{declaration:e?e.node:null,root:t,children:n}}()}n.ParsingError=s,void 0!==e&&"object"===(void 0===n?"undefined":o(n))&&(e.exports=w),n.default=w},{}],"xml-formatter":[function(t,e,n){"use strict";function o(t){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o(t)}var r=function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(n,"__esModule",{value:!0});var i=r(t("xml-parser-xo"));function c(t){var e;if(t.options.indentation||t.options.lineSeparator)for(t.content+=t.options.lineSeparator,e=0;e0&&(!n&&e.content.length>0&&c(e),u(e,t))}(t.content,e,n);else if("Element"===t.type)!function(t,e,n){e.path.push(t.name),!n&&e.content.length>0&&c(e);if(u(e,"<"+t.name),f(e,t.attributes),null===t.children||e.options.forceSelfClosingEmptyTag&&0===t.children.length){var o=e.options.whiteSpaceAtEndOfSelfclosingTag?" />":"/>";u(e,o)}else if(0===t.children.length)u(e,">");else{var r=t.children;u(e,">"),e.level++;var i="preserve"===t.attributes["xml:space"]||n,a=!1;if(!i&&e.options.ignoredPaths&&(y=e.path,h=e.options.ignoredPaths,m="/"+y.join("/"),v=y[y.length-1],a=h.includes(v)||h.includes(m),i=a),!i&&e.options.collapseContent){var s=!1,p=!1,d=!1;r.forEach((function(t,e){"Text"===t.type?(t.content.includes("\n")?(p=!0,t.content=t.content.trim()):0!==e&&e!==r.length-1||n||0===t.content.trim().length&&(t.content=""),(t.content.trim().length>0||1===r.length)&&(s=!0)):"CDATA"===t.type?s=!0:d=!0})),!s||d&&p||(i=!0)}r.forEach((function(t){l(t,e,n||i)})),e.level--,n||i||c(e),a&&function(t){var e;for(t.content=t.content.replace(/ +$/,""),e=0;e")}var y,h,m,v;e.path.pop()}(t,e,n);else{if("ProcessingInstruction"!==t.type)throw new Error("Unknown node type: "+t.type);a(t,e)}}function f(t,e){Object.keys(e).forEach((function(n){var o=e[n].replace(/"/g,""");u(t," "+n+'="'+o+'"')}))}function a(t,e){e.content.length>0&&c(e),u(e,"")}function s(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e.indentation="indentation"in e?e.indentation:" ",e.collapseContent=!0===e.collapseContent,e.lineSeparator="lineSeparator"in e?e.lineSeparator:"\r\n",e.whiteSpaceAtEndOfSelfclosingTag=!0===e.whiteSpaceAtEndOfSelfclosingTag,e.throwOnFailure=!1!==e.throwOnFailure;try{var n=(0,i.default)(t,{filter:e.filter,strictMode:e.strictMode}),o={content:"",level:0,options:e,path:[]};return n.declaration&&a(n.declaration,o),n.children.forEach((function(t){l(t,o,!1)})),e.lineSeparator?o.content.replace(/\r\n/g,"\n").replace(/\n/g,e.lineSeparator):o.content}catch(n){if(e.throwOnFailure)throw n;return t}}s.minify=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return s(t,Object.assign(Object.assign({},e),{indentation:"",lineSeparator:""}))},void 0!==e&&"object"===(void 0===n?"undefined":o(n))&&(e.exports=s),n.default=s},{"xml-parser-xo":1}]},{},[])("xml-formatter")})); //# sourceMappingURL=/sm/c239a0c35331393cae23da4ce5219c2cbca9a87db0ac4305dd2a6b4f86b918da.map const xmlFormatter = window.xmlFormatter; // 节流函数 function throttle(func, limit = 300) { let lastFunc; let lastRan; return function(...args) { const context = this; if (!lastRan) { func.apply(context, args); lastRan = Date.now(); } else { clearTimeout(lastFunc); lastFunc = setTimeout(function() { if ((Date.now() - lastRan) >= limit) { func.apply(context, args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; } // 样式注入 function injectCustomStyles() { const style = document.createElement('style'); style.classList.add("beibeibeibei"); document.head.appendChild(style); // 添加样式规则 const css = ` /*背景颜色*/ body { background: black; } /*让左侧导航展开收起按钮更明显*/ .left-nav .left-nav-collapse, .left-nav .left-nav-open { filter: drop-shadow(1px 0px 0px black); } /*左侧导航暗色*/ .left-nav { background: #090805 !important; } .left-nav span, .left-nav li a { color: #CCC !important; } /*左侧导航收起*/ body.left-nav-collapse .left-nav { background: none !important; width: 20px; } /*左侧导航按钮悬浮效果*/ .left-nav li:hover { background: #12367d !important; } /*滚动条*/ ::-webkit-scrollbar-thumb { background-color: #ffffff66 !important; } /*工具介绍*/ .tool-intro { background: black !important; } /*footer*/ footer { background: black !important; } /*搜索框*/ #searchInput { background: black !important; border: 1px solid white; color: white; } /*title*/ .tool-meta { background: black !important; padding: 2px 18px; } /*svg代码文字背景*/ textarea { background: black !important; color: white; transform-origin: 0 0; } /*textarea隐藏*/ textarea.hideTextarea { position: absolute; z-index: -1; scale: 0.1; transform-origin: 0 0; } /*svg*/ svg { display: block; } /*滑块按钮*/ input[type=range] { -webkit-appearance: none; border-radius: 1em; } input[type=range]:focus { outline: 1px solid gray; } input[type=range]::-webkit-slider-runnable-track { height: 1em; background: #d9d9d9; border-radius: 1em; } input[type=range]::-webkit-slider-thumb { height: 1em; width: 1em; border-radius: 50%; background: #8c8c8c; cursor: pointer; -webkit-appearance: none; } /*设置背景输入框*/ input.beibeibeibei-background-input:focus { outline: none; border-color: black !important; } /*示例按钮hover*/ button.beibeibeibei-exampleBtn:hover { background: #409EFF; color: white; } /*隐藏按钮hover*/ button.beibeibeibei-hideBtn:hover { background: #409EFF; color: white; } /*格式化按钮样式*/ button.beibeibeibei-formatBtn:hover { background-color: #409EFF; color: white; } /*重置按钮样式*/ button.beibeibeibei-resetBtn:hover { background-color: #409EFF; color: white; } /*隐藏原页面的清空按钮*/ .btn-reset { display: none; } /*按钮按下效果*/ .btn-box > button:active { transform: translateY(1px); } /*按钮按下效果*//*right-clicked*/ .btn-box > button.right-clicked { transform: translateY(1px); } `.replace(/\n/g, ' '); // 去掉换行符; style.innerText = css; } /*添加滑块控制展示区域大小*/ function createZoomController() { // 选择目标SVG元素 function getSvgElements() { const svgElement = document.querySelectorAll('svg'); return svgElement; } // 创建滑块元素 const slider = document.createElement('input'); slider.classList.add("beibeibeibei-slider"); slider.type = 'range'; slider.min = 1; // 最小宽度百分比 slider.max = 100; // 最大宽度百分比 slider.value = '100'; slider.title = "svg宽度百分比滑块"; slider.style.width = '300px'; slider.style.display = "block"; // label const label = document.createElement('div'); label.textContent = "svg宽度百分比滑块"; label.style.fontSize = "12px"; // 将滑块和标签添加到页面 const container = document.createElement('div'); container.appendChild(slider); container.appendChild(label); container.style.display = 'inline-block'; container.style.verticalAlign = "middle"; container.style.border = '1px solid #d3d4d6'; container.style.borderRadius = "4px"; container.style.padding = "2px"; container.style.marginRight = "10px"; container.style.transform = 'translateY(-1px)'; document.querySelector(".btn-box").appendChild(container); // 更新SVG宽度函数 function updateSvgWidth(value) { let svgs = getSvgElements(); svgs.forEach((svg) => { svg.style.width = value + '%'; }); } // 监听滑块输入事件 slider.addEventListener('input', function(e) { updateSvgWidth(e.target.value); }); // 修改背景输入框 const background_input = document.createElement('input'); background_input.placeholder="双击输入框"; background_input.classList.add("beibeibeibei-background-input"); background_input.title = '设置svg背景, 试试双击输入框自动输入: \'linear-gradient(#ffe,#adf)\''; background_input.style.borderRadius = '4px'; background_input.style.border = '1px solid #DCDFE6'; background_input.style.marginRight = '10px'; background_input.style.padding = '11px 20px'; background_input.style.fontSize = '14px'; document.querySelector(".btn-box").appendChild(background_input); function handleBackgroundInput(e) { document.querySelector("#svgBox").style.background = e.target.value; } // 事件监听器,节流 background_input.addEventListener('input', throttle(handleBackgroundInput)); background_input.addEventListener('dblclick', (e) => { e.target.value += "linear-gradient(#ffe,#adf)"; // 主动触发 input 事件 const event = new Event('input', {'bubbles': true,'cancelable': true}); e.target.dispatchEvent(event); }); /*示例按钮*/ const exampleBtn = document.createElement('button'); exampleBtn.classList.add("beibeibeibei-exampleBtn"); exampleBtn.textContent = '生成svg文本'; exampleBtn.addEventListener('click', (e) => { document.querySelector('textarea').value += `\n` + ` \n` + ` \n` + `\n` + ` \n` + ` \n` + `\n` + ` \n` + ` \n` + `\n` + ` \n` + ` \n` + `\n` + ` \n` + ` \n` + `\n` + ` \n` + ` \n` + ` 示例SVG图形\n` + ` \n` + `\n` + `\n`; const event = new Event('input', {'bubbles': true,'cancelable': true}); document.querySelector('textarea').dispatchEvent(event); }); document.querySelector(".btn-box").appendChild(exampleBtn); /*隐藏textarea按钮*/ const hideBtn = document.createElement('button'); hideBtn.classList.add("beibeibeibei-hideBtn"); hideBtn.textContent = '隐藏文本框'; hideBtn.addEventListener('click', (e) => { document.querySelector('textarea').classList.toggle("hideTextarea"); }); document.querySelector(".btn-box").appendChild(hideBtn); // 添加格式化按钮 const formatBtn = document.createElement('button'); formatBtn.classList.add("beibeibeibei-formatBtn"); formatBtn.textContent = '格式化'; formatBtn.title = "按左键缩进2个空格,按右键缩进4个空格"; formatBtn.addEventListener('click', (e) => { let indentation = '\u0020'.repeat(2); formatBtnEvent(indentation); }); formatBtn.addEventListener('contextmenu',(e) => { e.preventDefault(); let indentation = '\u0020'.repeat(4); formatBtnEvent(indentation); e.target.classList.add('right-clicked'); setTimeout(() => { e.target.classList.remove('right-clicked'); }, 100); }, { passive: false }); const formatBtnEvent = (indentation) => { const svgCode = document.querySelector('textarea').value; const formattedSvg = xmlFormatter(svgCode, { indentation: indentation, lineSeparator: "\n", whiteSpaceAtEndOfSelfclosingTag: true, throwOnFailure: false }); document.querySelector('textarea').value = formattedSvg; document.querySelector('textarea').dispatchEvent(new Event('input', { 'bubbles': true, 'cancelable': true })); }; document.querySelector(".btn-box").appendChild(formatBtn); // 添加清空按钮 const resetBtn = document.createElement('button'); resetBtn.classList.add("beibeibeibei-resetBtn"); resetBtn.textContent = '重置!'; resetBtn.addEventListener('click', (e) => { // 获取textarea元素,设置其值为空字符串 document.querySelector('textarea').value = ''; // 调用dispatchEvent方法,触发输入事件 document.querySelector('textarea').dispatchEvent(new Event('input', {'bubbles': true,'cancelable': true})); // 清空结果区 document.querySelector("#svgBox").innerHTML = ''; }); document.querySelector(".btn-box").appendChild(resetBtn); } // 监测SVG元素变化 function observeSvgChanges() { // 创建MutationObserver实例 const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // 遍历每个变化 mutation.addedNodes.forEach(function(node) { // 检查添加的元素是否是svg if (node.tagName === 'svg') { // 获取滑块当前的值 const slider = document.querySelector('.beibeibeibei-slider'); const value = slider ? slider.value : '100'; // 应用宽度百分比 if (value) { node.style.width = value + '%'; } } }); }); }); // 开始观察SVG预览区域 const svgPreviewArea = document.querySelector('#svgBox'); if (svgPreviewArea) { observer.observe(svgPreviewArea, { childList: true, // 监视子元素的添加或移除 subtree: true, // 监视所有后代节点 attributes: false }); } } // 初始化函数 function init() { if (!document.querySelector("style.beibeibeibei")) { injectCustomStyles(); } // 创建缩放控制器 createZoomController(); // 监测SVG元素变化 observeSvgChanges(); } // DOM加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();