您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ctrl + q 复制页签标题;Alt + q 复制页签标题及链接,生成 markdown 格式
// ==UserScript== // @name Copy title // @name:zh-CN 便捷复制页签 title // @namespace http://tampermonkey.net/Henry // @version 1.0.3 // @description use Ctrl + q copy title; Alt + q copy title and url, create markdown // @description:zh-CN Ctrl + q 复制页签标题;Alt + q 复制页签标题及链接,生成 markdown 格式 // @author Henry // @icon https://tsz.netlify.app/img/favicon.png // @match http*://*/* // @license MIT // ==/UserScript== ;(function () { 'use strict' let messageComponent = null document.addEventListener('keydown', listener, false) function listener(event) { const { keyCode, ctrlKey, altKey } = event if (keyCode === 81 && ctrlKey) { event.preventDefault() event.stopPropagation() copyTextToClipboard(document.title) return false } if (keyCode === 81 && altKey) { event.preventDefault() event.stopPropagation() copyTextToClipboard(`[${document.title}](${location.href})`) return false } } function copyTextToClipboard(text) { if (!navigator.clipboard) { fallbackCopyTextToClipboard(text) return } navigator.clipboard.writeText(text).then( function () { wrapperMsg(`Copying: ${text}`, window.MessageType.SUCCESS) }, function (err) { console.log('copyTextToClipboard ~ err:', err) wrapperMsg('Oops, unable to copy', window.MessageType.ERROR) } ) } function wrapperMsg(content, type) { // 初始化绑定 if (!messageComponent) { messageComponent = new window.MessageControl() } // 调用 messageComponent.message({ content, type }) } function fallbackCopyTextToClipboard(text) { let textArea = document.createElement('textarea') textArea.value = text // Avoid scrolling to bottom textArea.style.top = '0' textArea.style.left = '0' textArea.style.position = 'fixed' document.body.appendChild(textArea) textArea.focus() textArea.select() try { var successful = document.execCommand('copy') var msg = successful ? text : '' wrapperMsg(`Copying: ${msg}`, window.MessageType.SUCCESS) } catch (err) { console.log('fallbackCopyTextToClipboard ~ err:', err) wrapperMsg('Oops, unable to copy', window.MessageType.ERROR) } document.body.removeChild(textArea) } })() ;(function () { // 消息类型 const MessageType = { MESSAGE: 'message', // 普通 SUCCESS: 'success', // 成功 ERROR: 'error', // 错误 WARNING: 'warning' // 警告 } // 状态对应的主色 const MessageTypeColor = { MESSAGE: '#909399', SUCCESS: '#67c23a', ERROR: '#f56c6c', WARNING: '#e6a23c' } // 创建DOM const createDom = ({ isId = false, name = '', tag = 'div' }) => { if (!tag) { return null } const ele = document.createElement(tag) if (name) { if (isId) { ele.id = name } else { ele.className = name } } return ele } // 获取类型对应的背景色 const getTypeBGColor = type => { let bgColor = '' switch (type) { case MessageType.SUCCESS: bgColor = 'background-color: #f0f9eb' break case MessageType.ERROR: bgColor = 'background-color: #f0f9eb' break case MessageType.WARNING: bgColor = 'background-color: #f0f9eb' break default: bgColor = 'background-color: #edf2fc' break } return bgColor } // 获取类型对应的背景色、文字颜色 const getTypeDomCss = type => { let cssStr = '' let commonCss = '' switch (type) { case MessageType.SUCCESS: cssStr = commonCss + `${getTypeBGColor(type)};color: ${MessageTypeColor.SUCCESS};` break case MessageType.ERROR: cssStr = commonCss + `${getTypeBGColor(type)};color: ${MessageTypeColor.ERROR};` break case MessageType.WARNING: cssStr = commonCss + `${getTypeBGColor(type)};color: ${MessageTypeColor.WARNING};` break default: cssStr = commonCss + `${getTypeBGColor(type)};color: ${MessageTypeColor.MESSAGE};` break } return cssStr } const createMessage = ( { type, content, duration, delay, againBtn, minWidth, maxWidth }, mainContainer ) => { if (!mainContainer) { console.error('主容器不存在,查看调用流程,确保doucument.body已生成!') return } /**随机的key */ const randomKey = Math.floor(Math.random() * (99999 - 10002)) + 10002 /**属性配置 */ const config = { isRemove: false, // 是否被移除了 type: type || MessageType.MESSAGE, // 类型 message success error warning content: content || '', // 提示内容 duration: duration || 3000, // 显示时间 delay: delay || 0, // 弹出延迟 timeout: null, // 计时器事件 againBtn: againBtn || false // 是否需要显示 不再提示 按钮 } // #region 生成DOM、样式、关系 const messageContainer = createDom({ name: `message-${randomKey}`, tag: 'div' }) messageContainer.style = ` min-width: ${minWidth}px; max-width:${maxWidth}px; padding: 12px 12px; margin-top: -20px; border-radius: 4px; box-shadow: -5px 5px 12px 0 rgba(204, 204, 204, 0.8); ${getTypeBGColor(config.type)}; animation: all cubic-bezier(0.18, 0.89, 0.32, 1.28) 0.4s; transition: all .4s; pointer-events: auto; overflow:hidden; ` /**内容区域 */ const messageTypeDom = createDom({ tag: 'div' }) messageTypeDom.style = getTypeDomCss(config.type) /**文本内容 */ const messageTypeText = createDom({ tag: 'span' }) messageTypeText.style = 'font-size: 14px;line-height: 20px;' messageTypeText.innerHTML = config.content /**建立html树关系 */ messageTypeDom.appendChild(messageTypeText) messageContainer.appendChild(messageTypeDom) /**不再提示的按钮 */ if (config.againBtn) { const messageAgainDiv = createDom({ name: 'message-again-btn', tag: 'div' }) messageAgainDiv.style = `margin-top: 5px;text-align: right;` const messageAgainBtnText = createDom({ name: 'message-again-text', tag: 'span' }) messageAgainBtnText.innerHTML = '不再提示' messageAgainBtnText.style = ` font-size: 12px; color: rgb(204, 201, 201); border-bottom: 1px solid rgb(204, 201, 201); cursor: pointer; ` // 鼠标移入 messageAgainBtnText.onmouseover = () => { messageAgainBtnText.style.color = '#fdb906' messageAgainBtnText.style.borderBottom = '1px solid #fdb906' } // 鼠标移出 messageAgainBtnText.onmouseout = () => { messageAgainBtnText.style.color = 'rgb(204, 201, 201)' messageAgainBtnText.style.borderBottom = '1px solid rgb(204, 201, 201)' } messageAgainDiv.appendChild(messageAgainBtnText) messageContainer.appendChild(messageAgainDiv) config.elsAgainBtn = messageAgainBtnText } mainContainer.appendChild(messageContainer) /**绑定DOM、销毁事件,以便进行控制内容与状态 */ config.els = messageContainer config.destory = destory.bind(this) function destory(mainContainer, isClick) { if (!config.els || !mainContainer || config.isRemove) { // 不存在,或已经移除,则不再继续 return } config.els.style.marginTop = '-20px' // 为了过渡效果 config.els.style.opacity = '0' // 为了过渡效果 config.isRemove = true if (isClick) { mainContainer.removeChild(messageContainer) _resetMianPosition(mainContainer) free() } else { setTimeout(() => { mainContainer.removeChild(messageContainer) _resetMianPosition(mainContainer) free() }, 400) } } // 销毁重置绑定 function free() { config.els = null config.elsAgainBtn = null config.destory = null } return config } function _toBindEvents(domConfig, _self) { if (!domConfig) { return } // 不再提示按钮的事件绑定 if (domConfig.againBtn && domConfig.elsAgainBtn) { // 鼠标点击:将内容记录下来,下次就不显示同内容的弹框 domConfig.elsAgainBtn.onclick = () => { clearTimeout(domConfig.timeout) let sessionJson = sessionStorage.getItem('MESSAGE_DONT_REMIND_AGAIN') let tempArr = sessionJson ? JSON.parse(sessionJson) : [] let dontRemindAgainList = Array.isArray(tempArr) ? tempArr : [] dontRemindAgainList.push(domConfig.content) sessionStorage.setItem(_self.sessionStorageName, JSON.stringify(dontRemindAgainList)) domConfig.destory(_self.mainContainer, true) } } // 鼠标移入:对销毁计时器进行销毁 domConfig.els.onmouseover = () => { clearTimeout(domConfig.timeout) } // 鼠标移出: 一秒后销毁当前message domConfig.els.onmouseout = () => { domConfig.timeout = setTimeout(() => { domConfig.destory(_self.mainContainer) clearTimeout(domConfig.timeout) }, 1000) } // 延时隐藏 domConfig.timeout = setTimeout(() => { domConfig.destory(_self.mainContainer) clearTimeout(domConfig.timeout) }, domConfig.duration) } function _resetMianPosition(mainContainer) { if (!mainContainer) { return } mainContainer.style.left = `calc(50vw - ${mainContainer.scrollWidth / 2}px)` } class MessageControl { constructor() { this.minWidth = 380 // 内容显示宽度:最小值 this.maxWidth = 800 // 内容显示宽度:最大值 this.top = 45 // 整体的最顶部距离 this.zIndex = 999 // 层级 this.mainContainerIdName = 'selfDefine-message-box' // 主体DOM的id名 this.sessionStorageName = 'MESSAGE_DONT_REMIND_AGAIN' // 存储session信息的key /**生成主体DOM、样式容器 */ let mainDom = document.getElementById(this.mainContainerIdName) if (mainDom) { document.body.removeChild(mainDom) } this.mainContainer = createDom({ isId: true, name: this.mainContainerIdName, tag: 'div' }) this.mainContainer.style = ` pointer-events:none; position:fixed; top:${this.top}px; left:calc(50vw - ${this.minWidth / 2}px); z-index:${this.zIndex}; display: flex; flex-direction: column; align-items:center; ` document.body.appendChild(this.mainContainer) } /** * 消息提示 * @param {String} type 类型 | 必传 | 可选值:message success error warning * @param {String} content 内容 | 必传 | '' * @param {Number} duration 显示时间 | 非必传 | 默认3000毫秒 * @param {Number} delay 出现的延时 | 非必传 | 默认0 * @param {Boolean} againBtn 是否显示 不再提示 按钮 | 非必传 | 默认false */ message(config = {}) { // 不再提示(相同文字内容)的存储与判断逻辑待优化 let sessionJson = sessionStorage.getItem(this.sessionStorageName) let dontRemindAgainList = sessionJson ? JSON.parse(sessionJson) : null // 需要显示不再提示按钮,且内容有效,且不再提示的记录数组中包含本次内容,则不提示 if ( config.againBtn && config.content && dontRemindAgainList && Array.isArray(dontRemindAgainList) && dontRemindAgainList.includes(config.content) ) { return } const domConfig = createMessage( { type: config.type, content: config.content, duration: config.duration, delay: config.delay, againBtn: config.againBtn, minWidth: this.minWidth, maxWidth: this.maxWidth }, this.mainContainer ) this.mainContainer.appendChild(domConfig.els) domConfig.els.style.marginTop = '20px' // 为了过渡效果 _resetMianPosition(this.mainContainer) _toBindEvents(domConfig, this) } beforeDestory() { if (this.mainContainer && this.mainContainer.remove) { this.mainContainer.remove() } else { document.body.removeChild(this.mainContainer) } this.mainContainer = null } } if (!window.MessageType) { window.MessageType = MessageType } if (!window.MessageControl) { window.MessageControl = MessageControl } })()