// ==UserScript== // @name Syntax highlighting // @namespace https://floatsyi.com/ // @version 0.2.1 // @description 使用 highlight.js 给代码片断添加语法高亮, 并设置更优雅的字体 // @author floatsyi // @license MIT // @require https://cdn.bootcss.com/highlight.js/9.15.10/highlight.min.js // @require https://cdn.bootcss.com/fontfaceobserver/2.1.0/fontfaceobserver.js // @require https://unpkg.com/vue@2.6.10/dist/vue.min.js // @require https://unpkg.com/buefy/dist/buefy.min.js // @match *://*/* // @grant GM_addStyle // @grant GM_deleteValue // @grant GM_setValue // @grant GM_getValue // @grant GM_listValues // @grant GM_addValueChangeListener // @grant unsafeWindow // @grant GM_registerMenuCommand // @downloadURL none // ==/UserScript== // https://www.bootcdn.cn/highlight.js/ // 当前版本号 // FIXME const currentVersion = '0.2.0' console.log('GM_listValues', GM_listValues()) // 环境探测 const envDetection = [ unsafeWindow.Prism, unsafeWindow.hljs, unsafeWindow.prettyPrint ] if (envDetection.some(item => !!item)) return false // thmem 和 font 列表 // https://highlightjs.org/static/demo/ // ;[...document.querySelectorAll('#styles > li')].map(item =>item.innerText.toLocaleLowerCase().replace(/\s/g, '-').replace(/(-)?(\d+)(-)?/g,'$2').replace(/(qtcreator)(-)(dark|light)/, '$1_$3').replace(/(kimbie)(-)(dark|light)/,'$1.$2')) const themes = [ 'default', 'a11y-dark', 'a11y-light', 'agate', 'an-old-hope', 'androidstudio', 'arduino-light', 'arta', 'ascetic', 'atelier-cave-dark', 'atelier-cave-light', 'atelier-dune-dark', 'atelier-dune-light', 'atelier-estuary-dark', 'atelier-estuary-light', 'atelier-forest-dark', 'atelier-forest-light', 'atelier-heath-dark', 'atelier-heath-light', 'atelier-lakeside-dark', 'atelier-lakeside-light', 'atelier-plateau-dark', 'atelier-plateau-light', 'atelier-savanna-dark', 'atelier-savanna-light', 'atelier-seaside-dark', 'atelier-seaside-light', 'atelier-sulphurpool-dark', 'atelier-sulphurpool-light', 'atom-one-dark-reasonable', 'atom-one-dark', 'atom-one-light', 'brown-paper', 'codepen-embed', 'color-brewer', 'darcula', 'dark', 'darkula', 'docco', 'dracula', 'far', 'foundation', 'github-gist', 'github', 'gml', 'googlecode', 'grayscale', 'gruvbox-dark', 'gruvbox-light', 'hopscotch', 'hybrid', 'idea', 'ir-black', 'isbl-editor-dark', 'isbl-editor-light', 'kimbie.dark', 'kimbie.light', 'lightfair', 'magula', 'mono-blue', 'monokai-sublime', 'monokai', 'nord', 'obsidian', 'ocean', 'paraiso-dark', 'paraiso-light', 'pojoaque', 'purebasic', 'qtcreator_dark', 'qtcreator_light', 'railscasts', 'rainbow', 'routeros', 'school-book', 'shades-of-purple', 'solarized-dark', 'solarized-light', 'sunburst', 'tomorrow-night-blue', 'tomorrow-night-bright', 'tomorrow-night-eighties', 'tomorrow-night', 'tomorrow', 'vs', 'vs2015', 'xcode', 'xt256', 'zenburn' ] // google Monospace fonts: https://fonts.google.com/?sort=date&category=Monospace // ;[...document.querySelectorAll('.fonts-module-title')].map(item => item.innerText) const fonts = [ 'Fira Code', 'B612 Mono', 'Major Mono Display', 'IBM Plex Mono', 'Nanum Gothic Coding', 'Overpass Mono', 'Space Mono', 'Roboto Mono', 'Fira Mono', 'Share Tech Mono', 'Cutive Mono', 'Source Code Pro' ] const shouldClearCacheKeys = ['currentTheme', 'currentFont', 'bulmaStyle'] const hash = '662eb72f' // fnv132('Syntax_highlighting') const hashString = str => `${str}-${hash}` const getCacheValue = key => GM_getValue(hashString(key)) const setCacheValue = (key, value) => GM_setValue(hashString(key), value) const deleteCacheValue = key => GM_deleteValue(hashString(key)) const hasCacheValue = key => !!GM_getValue(hashString(key)) // 默认字体与主题 const defaultTheme = 'atom-one-dark' const defaultFont = 'Fira Code' let currentTheme = getCacheValue('currentTheme') || defaultTheme let currentFont = getCacheValue('currentFont') || defaultFont // hashString const hashVersion = hashString('version') const clearCache = () => { for (const key of [...themes, ...fonts, ...shouldClearCacheKeys]) { deleteCacheValue(key) } } // 如果是新版本就清除缓存 GM_addValueChangeListener(hashVersion, function ( name, old_value, new_value, remote ) { if (old_value !== new_value) { clearCache() // TODO 清除之前 0.1.2 版本的废弃缓存 ;['Fira Code', 'atom-one-dark', 'bulmaStyle'].forEach(key => {GM_deleteValue(key)}) } }) // 保存当前版本号, 触发监听 GM_setValue(hashVersion, currentVersion) const fontSize = getCacheValue('fontSize') || 16 const isApplyThemeChanges = getCacheValue('isApplyThemeChanges') || 'Yes' const isApplyFontChanges = getCacheValue('isApplyFontChanges') || 'Yes' const isGFW = getCacheValue('isGFW') || 'Fuck' const isForcePreBackgroundColors = getCacheValue('isForcePreBackgroundColors') || 'Yes' const getCurrentThemeBackgroundColor = styleText => styleText.match(/background:(.*?)[;}]/)[1] // 轮询 const poll = ({ condition, resolve, reject = () => {}, millisecond = 1000, retries = 1 }) => { if (condition()) return resolve() let time = 0 const int = setInterval(() => { time++ if (condition()) { clearInterval(int) return resolve() } else if (time > retries) { clearInterval(int) return reject() } }, millisecond) const stop = () => { clearInterval(int) } return stop } const fetchStyleText = url => fetch(url, { headers: { 'Content-Type': 'text/plain' } }).then(response => { return response.text() }) // 获取并设置样式 const setStyle = () => { // 获取主题样式并添加 const themeStyle = getCacheValue(currentTheme) if (themeStyle) { GM_addStyle(themeStyle) } else { const themeUrl = this.GFW === 'Fuck' ? `https://cdn.bootcss.com/highlight.js/9.15.10/styles/${currentTheme}.min.css` : `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/${currentTheme}.min.css` fetchStyleText(themeUrl).then(style => { GM_addStyle(style) setCacheValue(currentTheme, style) }) } // 获取字体样式并添加 const fontStyle = getCacheValue(currentFont) if (fontStyle) { GM_addStyle(fontStyle) } else { const fontUrl = isGFW ? `https://fonts.loli.net/css?family=${currentFont}&display=swap` : `https://fonts.googleapis.com/css?family=${currentFont}&display=swap` fetchStyleText(fontUrl).then(style => { GM_addStyle(style) setCacheValue(currentFont, style) }) } } // 为 code 片断应用 highlightBlock 并设置字体样式 const beautify = () => { setStyle() const font = new window.FontFaceObserver(currentFont) font.load().then( () => { console.log('Font is available') for (const pre of Array.from(document.querySelectorAll('pre'))) { const code = pre.querySelector('code') if (isApplyThemeChanges) { window.hljs.highlightBlock(code) } if (isApplyFontChanges === 'Yes') { code.style.fontFamily = currentFont code.style.fontSize = `${fontSize}px` } if (isForcePreBackgroundColors === 'Yes') { pre.style.backgroundColor = getCurrentThemeBackgroundColor( getCacheValue(currentTheme) ) } } }, () => { console.log('Font is not available') } ) } // 设置页 let parasitifer = null const openSetting = () => { // 非首次调用 if (parasitifer) { parasitifer.show() return true } parasitifer = document.createElement('div') parasitifer.id = 'host-element' parasitifer.style = `position: fixed;top:0;bottom:0;z-index:9999;width:100vw;height:100vh;font-size:16px;background-color:#fff;` parasitifer.show = () => { parasitifer.style.display = 'block' } parasitifer.hide = () => { parasitifer.style.display = 'none' } const shadowRoot = parasitifer.attachShadow({ mode: 'open' }) const shadowHTML = document.createElement('HTML') const shadowContent = document.createElement('body') const shadowStyleEle = document.createElement('style') const bulmaStyleEle = document.createElement('style') const fontStyleEle = document.createElement('style') const themeStyleEle = document.createElement('style') shadowContent.id = 'vue-root' shadowStyleEle.innerText = `` shadowHTML.appendChild(shadowStyleEle) shadowHTML.appendChild(bulmaStyleEle) shadowHTML.appendChild(fontStyleEle) shadowHTML.appendChild(themeStyleEle) shadowHTML.appendChild(shadowContent) shadowRoot.appendChild(shadowHTML) document.body.appendChild(parasitifer) const mount = style => { bulmaStyleEle.innerText = style const vueRoot = document .querySelector('#host-element') .shadowRoot.querySelector('#vue-root') const vueTemplate = `