// ==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
}
})()