您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
右键复制网页元素内容(类名、文本、HTML、Markdown),支持下载为 Markdown,使用 Vue + Element Plus + Turndown 实现。Markdown下载功能目前只做了掘金、CSDN的兼容(有瑕疵),其余网站没特意试过。
// ==UserScript== // @name 元素属性复制 // @namespace http://tampermonkey.net/ // @version 0.0.1 // @description 右键复制网页元素内容(类名、文本、HTML、Markdown),支持下载为 Markdown,使用 Vue + Element Plus + Turndown 实现。Markdown下载功能目前只做了掘金、CSDN的兼容(有瑕疵),其余网站没特意试过。 // @author 石小石Orz // @match *://*/* // @license MIT // @require https://unpkg.com/vue@3/dist/vue.global.js // @require https://unpkg.com/turndown/dist/turndown.js // @resource ELEMENT_JS https://cdn.jsdelivr.net/npm/element-plus // @resource elementPlusCss https://cdn.jsdelivr.net/npm/element-plus/dist/index.css // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_setClipboard // @grant GM_registerMenuCommand // @grant GM_download // @grant unsafeWindow // @noframes // ==/UserScript== (function () { 'use strict'; // 添加 Element Plus 样式 GM_addStyle(GM_getResourceText('elementPlusCss')); GM_addStyle(` .tm-hover-highlight { outline: 2px solid rgba(0, 123, 255, 0.7); background-color: rgba(0, 123, 255, 0.1) !important; border-radius: 4px; transition: all 0.2s ease; z-index: 9999; } `); // 加载 Vue 和 Element Plus window.Vue = unsafeWindow.Vue = Vue; const { createApp, ref, reactive } = Vue; const elementPlusJS = GM_getResourceText('ELEMENT_JS'); eval(elementPlusJS); // 插入挂载点 const container = document.createElement('div'); container.id = 'copy-helper-app'; document.body.appendChild(container); // Markdown 转换函数 function htmlToMarkdown(el) { const turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', }); // 处理 <pre><code> 为代码块,过滤说明文字 turndownService.addRule('code-block', { filter: 'pre', replacement: function (content) { const code = (content.match(/`{1,3}([\s\S]*?)`{1,3}/)?.[1] || content).trim(); return '\n```\n' + code + '\n```\n'; } }); // 处理 <table> turndownService.addRule('table', { filter: 'table', replacement: function (_, node) { let markdown = ''; const rows = Array.from(node.querySelectorAll('tr')); const extractText = (td) => td.textContent.trim().replace(/\|/g, '\\|'); rows.forEach((row, i) => { const cells = Array.from(row.children).map(extractText); markdown += '| ' + cells.join(' | ') + ' |\n'; if (i === 0) markdown += '| ' + cells.map(() => '---').join(' | ') + ' |\n'; }); return '\n' + markdown + '\n'; } }); return turndownService.turndown(el.innerHTML); } // Vue 应用 const App = { setup() { const visible = ref(false); const buttons = reactive([]); const pos = reactive({ top: 0, left: 0 }); const setButtonsFor = (el) => { buttons.length = 0; const className = el.className?.toString().trim(); const text = el.innerText?.trim(); const html = el.innerHTML?.trim(); const fullHtml = el.outerHTML?.trim(); if (className) buttons.push({ label: '复制类名', content: className }); if (text) buttons.push({ label: '复制文本', content: text }); if (html) buttons.push({ label: '复制网页', content: html, type: 'html' }); if (fullHtml) buttons.push({ label: '复制HTML文本', content: fullHtml, type: 'text' }); if (html) { const md = htmlToMarkdown(el); buttons.push({ label: '复制为Markdown', content: md, type: 'text' }); buttons.push({ label: '下载为Markdown', content: md, type: 'markdown' }); } }; const copy = ({ content, type }) => { if (type === 'markdown') { const title = document.title.replace(/[\\/:*?"<>|]/g, '_'); GM_download({ url: 'data:text/markdown;charset=utf-8,' + encodeURIComponent(content), name: title + '.md', saveAs: true }); ElementPlus.ElMessage.success('Markdown 已下载'); } else { GM_setClipboard(content, type || 'text'); ElementPlus.ElMessage.success('复制成功!'); } visible.value = false; deactivate(); }; const updatePosition = (x, y) => { pos.top = y; pos.left = x; }; return { visible, buttons, pos, copy, setButtonsFor, updatePosition }; }, template: ` <div v-if="visible" :style="{ position: 'absolute', top: pos.top + 'px', left: pos.left + 'px', zIndex: 99999, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '5px', backgroundColor: '#f2f2f2', borderRadius: '5px', border: '1px solid #ccc', padding: '8px' }"> <el-button v-for="btn in buttons" size="small" :type="btn.type === 'markdown' ? 'primary' : 'info'" style="min-width: 140px; margin: 0" @click="copy(btn)" > {{ btn.label }} </el-button> </div> ` }; const app = createApp(App); app.use(ElementPlus); const vm = app.mount('#copy-helper-app'); let currentElement = null; let activated = false; const isValidElement = (el) => { if (!el || el.nodeType !== 1) return false; const rect = el.getBoundingClientRect(); return !['html', 'body', 'script', 'style'].includes(el.tagName.toLowerCase()) && rect.width >= 30 && rect.height >= 15; }; const findValidTarget = (el) => { while (el && el !== document.body) { if (isValidElement(el)) return el; el = el.parentElement; } return null; }; const handleMouseMove = (e) => { if (!activated) return; let el = e.target; if (document.querySelector('#copy-helper-app')?.contains(el)) return; el = findValidTarget(el); if (!el) return; if (el !== currentElement) { currentElement?.classList.remove('tm-hover-highlight'); vm.visible = false; currentElement = el; currentElement.classList.add('tm-hover-highlight'); } }; const handleContextMenu = (e) => { if (!activated) return; let el = e.target; if (document.querySelector('#copy-helper-app')?.contains(el)) return; el = findValidTarget(el); if (!el) { vm.visible = false; return; } if (el === currentElement) { e.preventDefault(); vm.setButtonsFor(el); vm.updatePosition(e.pageX, e.pageY); vm.visible = true; } else { vm.visible = false; } }; function activate() { if (activated) return; activated = true; document.addEventListener('mousemove', handleMouseMove, true); document.addEventListener('contextmenu', handleContextMenu, true); ElementPlus.ElMessage.info('元素复制脚本已启动,右键高亮区域试试!'); } function deactivate() { if (!activated) return; activated = false; document.removeEventListener('mousemove', handleMouseMove, true); document.removeEventListener('contextmenu', handleContextMenu, true); currentElement?.classList.remove('tm-hover-highlight'); currentElement = null; vm.visible = false; } GM_registerMenuCommand('启动元素复制脚本', activate); })();