您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在微信公众号编辑器中添加HTML代码查看和编辑功能
// ==UserScript== // @name 微信公众号编辑器HTML工具 // @namespace http://tampermonkey.net/ // @version 0.0.2 // @description 在微信公众号编辑器中添加HTML代码查看和编辑功能 // @author liudonghua123 // @match https://mp.weixin.qq.com/cgi-bin/appmsg* // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/loader.min.js // @license MIT // ==/UserScript== (async function() { 'use strict'; console.info(`微信公众号编辑器HTML工具`); // 等待编辑器加载完成 async function waitForEditor() { return new Promise((resolve) => { console.log('微信公众号HTML工具: 等待编辑器加载...'); const checkEditor = setInterval(() => { if (window.__MP_Editor_JSAPI__) { clearInterval(checkEditor); console.log('微信公众号HTML工具: 编辑器已加载'); resolve(); } }, 1000); }); } async function waitForToolbar() { return new Promise((resolve) => { console.log('微信公众号HTML工具: 等待工具栏加载...'); const checkToolbar = setInterval(() => { const toolbar = document.querySelector('.edui-toolbar.edui-toolbar-primary'); if (toolbar) { clearInterval(checkToolbar); console.log('微信公众号HTML工具: 工具栏已加载'); resolve(toolbar); } }, 500); }); } // 监听工具栏变化,确保按钮始终存在 function observeToolbar(toolbar) { // 清除现有的观察器 if (window.htmlToolObserver) { window.htmlToolObserver.disconnect(); } const observer = new MutationObserver((mutations) => { // 检查按钮是否已存在 const existingButton = toolbar.querySelector('.edui-for-htmlcode'); if (!existingButton) { console.log('微信公众号HTML工具: 检测到工具栏变化,重新添加HTML按钮'); addHTMLButton(toolbar); } }); observer.observe(toolbar, { childList: true, subtree: true }); // 保存观察器引用,防止被垃圾回收 window.htmlToolObserver = observer; // 定期检查按钮是否存在(备用机制) const interval = setInterval(() => { const existingButton = toolbar.querySelector('.edui-for-htmlcode'); if (!existingButton) { console.log('微信公众号HTML工具: 定期检查发现按钮缺失,重新添加HTML按钮'); addHTMLButton(toolbar); } }, 2000); // 保存定时器引用 window.htmlToolInterval = interval; } // 页面可见性变化时检查按钮 document.addEventListener('visibilitychange', () => { if (!document.hidden) { // 页面变为可见时,延迟检查按钮 setTimeout(() => { checkAndAddButton(); }, 500); } }); // 窗口焦点变化时检查按钮 window.addEventListener('focus', () => { setTimeout(() => { checkAndAddButton(); }, 500); }); // 检查并添加按钮的通用函数 async function checkAndAddButton() { try { const toolbar = await waitForToolbar(); const existingButton = toolbar.querySelector('.edui-for-htmlcode'); if (!existingButton) { console.log('微信公众号HTML工具: 检查发现按钮缺失,重新添加HTML按钮'); addHTMLButton(toolbar); } } catch (error) { console.error('微信公众号HTML工具: 检查按钮失败', error); } } async function initHTMLTool() { try { await waitForEditor(); const toolbar = await waitForToolbar(); addHTMLButton(toolbar); observeToolbar(toolbar); // 定期重新检查工具栏(防止观察器失效) setInterval(async () => { try { const currentToolbar = await waitForToolbar(); const existingButton = currentToolbar.querySelector('.edui-for-htmlcode'); if (!existingButton) { console.log('微信公众号HTML工具: 定期重新检查发现按钮缺失,重新添加HTML按钮'); addHTMLButton(currentToolbar); // 重新绑定观察器 observeToolbar(currentToolbar); } } catch (error) { console.error('微信公众号HTML工具: 定期检查失败', error); } }, 5000); } catch (error) { console.error('微信公众号HTML工具: 初始化失败', error); createNotification('工具初始化失败: ' + error.message, 'error'); } } function addHTMLButton(toolbar) { // 检查按钮是否已存在 const existingButton = toolbar.querySelector('.edui-for-htmlcode'); if (existingButton) { console.log('微信公众号HTML工具: HTML按钮已存在,无需重复添加'); return; } // 创建HTML按钮 const htmlButton = document.createElement('div'); htmlButton.className = 'edui-box edui-button edui-default edui-for-htmlcode'; htmlButton.innerHTML = ` <div data-tooltip="HTML代码" class="js_tooltip edui-default"> <div class="edui-button-wrap edui-default"> <div class="edui-box edui-button-body edui-default"> <div class="edui-box edui-icon edui-default" style="font-size: 16px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"> <span>📄</span> </div> </div> </div> </div> `; // 添加到工具栏末尾 toolbar.appendChild(htmlButton); // 绑定点击事件 htmlButton.addEventListener('click', showHTMLDialog); // 创建一键整理图片按钮 const formatImageButton = document.createElement('div'); formatImageButton.className = 'edui-box edui-button edui-default edui-for-formatimage'; formatImageButton.innerHTML = ` <div data-tooltip="一键整理图片" class="js_tooltip edui-default"> <div class="edui-button-wrap edui-default"> <div class="edui-box edui-button-body edui-default"> <div class="edui-box edui-icon edui-default" style="font-size: 16px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"> <span>🖼️</span> </div> </div> </div> </div> `; // 添加到HTML按钮右边 toolbar.appendChild(formatImageButton); // 绑定点击事件 formatImageButton.addEventListener('click', formatImages); console.log('微信公众号HTML工具: HTML按钮和一键整理图片按钮添加成功'); } async function getEditorContent() { return new Promise((resolve, reject) => { window.__MP_Editor_JSAPI__.invoke({ apiName: 'mp_editor_get_content', sucCb: (res) => { console.log('微信公众号HTML工具: 获取内容成功', res); resolve(res.content); }, errCb: (err) => { console.error('微信公众号HTML工具: 获取内容失败', err); reject(err); } }); }); } async function setEditorContent(content) { return new Promise((resolve, reject) => { window.__MP_Editor_JSAPI__.invoke({ apiName: 'mp_editor_set_content', apiParam: { content: content }, sucCb: (res) => { console.log('微信公众号HTML工具: 设置内容成功', res); resolve(res); }, errCb: (err) => { console.error('微信公众号HTML工具: 设置内容失败', err); reject(err); } }); }); } async function loadMonaco() { return new Promise((resolve, reject) => { if (window.monaco) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/loader.min.js'; script.onload = () => { require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs' }}); require(['vs/editor/editor.main'], () => { resolve(); }, reject); }; script.onerror = reject; document.head.appendChild(script); }); } async function showHTMLDialog() { try { // 获取当前HTML内容 const content = await getEditorContent(); // 创建对话框 const dialog = document.createElement('div'); dialog.id = 'html-editor-dialog'; dialog.innerHTML = ` <div class="html-editor-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999;"></div> <div class="html-editor-container" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; height: 70%; background: white; border-radius: 8px; z-index: 10000; display: flex; flex-direction: column; box-shadow: 0 4px 20px rgba(0,0,0,0.15); border: 1px solid #e0e0e0;"> <div class="html-editor-header" style="padding: 16px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; background: #f8f8f8; border-radius: 8px 8px 0 0;"> <h3 style="margin: 0; font-weight: 500; color: #333;">HTML代码编辑器</h3> <div style="display: flex; gap: 8px;"> <button class="html-editor-maximize" style="background: none; border: none; font-size: 16px; cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;" title="最大化">□</button> <button class="html-editor-close" style="background: none; border: none; font-size: 24px; cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;">×</button> </div> </div> <div class="html-editor-body" style="flex: 1; padding: 0; overflow: hidden;"> <div id="monaco-editor" style="width: 100%; height: 100%;"></div> </div> <div class="html-editor-footer" style="padding: 16px; border-top: 1px solid #e0e0e0; display: flex; justify-content: flex-end; gap: 12px; background: #f8f8f8; border-radius: 0 0 8px 8px;"> <button id="format-btn" style="padding: 8px 16px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; font-size: 14px;">格式化</button> <button id="save-btn" style="padding: 8px 16px; background: #07c160; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">保存</button> </div> </div> `; // 添加到页面 document.body.appendChild(dialog); // 加载Monaco Editor await loadMonaco(); // 初始化Monaco Editor const editor = monaco.editor.create(document.getElementById('monaco-editor'), { value: content || '', language: 'html', theme: 'vs-light', automaticLayout: true, minimap: { enabled: true }, fontSize: 14, scrollBeyondLastLine: false, wordWrap: 'on' }); // 保存编辑器实例到对话框上,方便后续访问 dialog.editorInstance = editor; // 绑定事件 dialog.querySelector('.html-editor-close').addEventListener('click', () => { editor.dispose(); document.body.removeChild(dialog); }); dialog.querySelector('.html-editor-overlay').addEventListener('click', () => { editor.dispose(); document.body.removeChild(dialog); }); // 最大化功能 let isMaximized = false; const container = dialog.querySelector('.html-editor-container'); const maximizeBtn = dialog.querySelector('.html-editor-maximize'); maximizeBtn.addEventListener('click', () => { if (isMaximized) { // 恢复原状 container.style.width = '80%'; container.style.height = '70%'; container.style.top = '50%'; container.style.left = '50%'; container.style.transform = 'translate(-50%, -50%)'; maximizeBtn.textContent = '□'; maximizeBtn.title = '最大化'; isMaximized = false; } else { // 最大化 container.style.width = '95%'; container.style.height = '90%'; container.style.top = '5%'; container.style.left = '2.5%'; container.style.transform = 'none'; maximizeBtn.textContent = '❐'; maximizeBtn.title = '恢复'; isMaximized = true; } // 通知编辑器重新布局 setTimeout(() => editor.layout(), 100); }); // 格式化功能 dialog.querySelector('#format-btn').addEventListener('click', () => { const currentValue = editor.getValue(); // 简单的HTML格式化 let formatted = currentValue.replace(/></g, '>\n<'); editor.setValue(formatted); }); // 保存功能 dialog.querySelector('#save-btn').addEventListener('click', async () => { const html = editor.getValue(); try { await setEditorContent(html); createNotification('保存成功', 'success'); } catch (e) { console.error('微信公众号HTML工具: 保存失败', e); createNotification('保存失败: ' + e.message, 'error'); } }); } catch (error) { console.error('微信公众号HTML工具: 显示对话框失败', error); createNotification('获取内容失败: ' + error.message, 'error'); } } // 创建通知banner function createNotification(message, type = 'info') { // 移除现有的通知 const existingNotification = document.getElementById('html-editor-notification'); if (existingNotification) { existingNotification.remove(); } const notification = document.createElement('div'); notification.id = 'html-editor-notification'; notification.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); padding: 12px 24px; border-radius: 4px; color: white; font-size: 14px; z-index: 10001; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: opacity 0.3s ease-in-out; max-width: 80%; text-align: center; `; // 根据类型设置样式 switch (type) { case 'success': notification.style.backgroundColor = '#07c160'; break; case 'error': notification.style.backgroundColor = '#fa5151'; break; case 'warning': notification.style.backgroundColor = '#ffc300'; notification.style.color = '#333'; break; default: notification.style.backgroundColor = '#000000'; } notification.textContent = message; document.body.appendChild(notification); // 3秒后自动移除 setTimeout(() => { if (notification.parentNode) { notification.style.opacity = '0'; setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); } }, 3000); return notification; } // 一键整理图片功能 async function formatImages() { try { // 获取当前HTML内容 const content = await getEditorContent(); // 处理HTML内容 let formattedContent = content; // 统计修改数量 let imgModifiedCount = 0; let sectionRemovedCount = 0; // 2.1 找到在<section style="text-align: center;" nodeleaf=""> </section> 中的 带有class="rich_pages wxw-img" 没有 style的 img,添加样式 formattedContent = formattedContent.replace( /(<section style="text-align: center;" nodeleaf="">)([\s\S]*?)(<\/section>)/g, (sectionMatch, openTag, content, closeTag) => { // 处理section中的img标签 const processedContent = content.replace( /<img(?![^>]*style="[^"]*border-radius[^"]*")([^>]*class="rich_pages wxw-img"[^>]*>)/g, (imgMatch, imgTag) => { imgModifiedCount++; return `<img style="border-radius: 9px;box-shadow: rgb(210, 210, 210) 0px 0px 0.5em 0px;background-color: transparent;"${imgTag}`; } ); return openTag + processedContent + closeTag; } ); // 2.2 删除可能的<section ...><figure ...><span leaf=""><br ...></span>...</figure></section> const removeSectionRegex = /<section[^>]*?><figure[^>]*?><span leaf=""><br[^>]*?><\/span>(<figcaption[^>]*?>.*?<\/figcaption>)?<\/figure><\/section>/g; let removeSectionMatch; while ((removeSectionMatch = removeSectionRegex.exec(formattedContent)) !== null) { sectionRemovedCount++; } formattedContent = formattedContent.replace(removeSectionRegex, ''); // 设置处理后的内容 await setEditorContent(formattedContent); createNotification(`图片整理完成,修改了 ${imgModifiedCount} 处图片,删除了 ${sectionRemovedCount} 处多余内容`, 'success'); } catch (error) { console.error('微信公众号HTML工具: 图片整理失败', error); createNotification('图片整理失败: ' + error.message, 'error'); } } await initHTMLTool(); })();