// ==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 = `
{{ isApplyThemeChanges }} {{ isApplyFontChanges }} {{ isForcePreBackgroundColors }} {{ isGFW }}
Apply Close Reset

Real-time preview

                    
import something from 'something'

// 获取并设置样式
const setStyle = () => {
  // 获取主题样式并添加
  const themeStyle = GM_getValue(hashString(currentTheme))

  if (themeStyle) {
    GM_addStyle(themeStyle)
  } else {
    const themeUrl = \`https://cdn.bootcss.com/highlight.js/9.15.10/styles/\${currentTheme}.min.css\`

    fetchStyleText(themeUrl).then(style => {
      GM_addStyle(style)
      GM_setValue(hashString(currentTheme), style)
    })
  }
}

export default something

                    
                
` const vm = new window.Vue({ el: vueRoot, template: vueTemplate, data () { return { current: { theme: currentTheme, font: currentFont }, styles: { pre: { maxWidth: '952px', maxHeight: '631px', overflow: 'hidden' }, code: { overflow: 'hidden' } }, fontSize: fontSize, themes: themes, fonts: fonts, isApplyThemeChanges: isApplyThemeChanges, isApplyFontChanges: isApplyFontChanges, isForcePreBackgroundColors: isForcePreBackgroundColors, isGFW: isGFW, defaultBackgroundColr: '' } }, watch: { isForcePreBackgroundColors (value) { console.log(value) if (value === 'No') { this.$refs.pre.style.backgroundColor = this.defaultBackgroundColr } else { poll({ condition: () => hasCacheValue(this.current.theme), resolve: () => { this.$refs.pre.style.backgroundColor = getCurrentThemeBackgroundColor( getCacheValue(this.current.theme) ) }, millisecond: 1000, retries: 5 }) } } }, methods: { reset () { this.fontSize = 16 this.isApplyThemeChanges = 'Yes' this.isApplyFontChanges = 'Yes' this.isForcePreBackgroundColors = 'No' this.isGFW = 'Fuck' this.current.theme = 'atom-one-dark' this.current.font = 'Fira Code' this.changeTheme(this.current.theme) this.changeFont(this.current.font) }, apply () { setCacheValue('fontSize', this.fontSize) setCacheValue('isApplyThemeChanges', this.isApplyThemeChanges) setCacheValue('isApplyFontChanges', this.isApplyFontChanges) setCacheValue( 'isForcePreBackgroundColors', this.isForcePreBackgroundColors ) setCacheValue('isGFW', this.isGFW) setCacheValue('currentTheme', this.current.theme) setCacheValue('currentFont', this.current.font) }, close () { parasitifer.hide() }, changeTheme (value) { if (this.isApplyThemeChanges === 'No') return false const doChangeTheme = (thmemName, themeStyle) => { themeStyleEle.innerText = themeStyle if (this.isForcePreBackgroundColors === 'Yes') { this.$refs.pre.style.backgroundColor = getCurrentThemeBackgroundColor( themeStyle ) // console.log(getCurrentThemeBackgroundColor(themeStyle)) } window.hljs.highlightBlock(this.$refs.code) } if (hasCacheValue(value)) { doChangeTheme(value, getCacheValue(value)) console.log(`get theme: ${value} in cache`) } else { const url = this.GFW === 'Fuck' ? `https://cdn.bootcss.com/highlight.js/9.15.10/styles/${value}.min.css` : `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/${value}.min.css` // console.log(url) fetchStyleText(url).then(style => { setCacheValue(value, style) doChangeTheme(value, style) }) } // console.log('current theme', this.current.theme) }, changeFont (value) { if (this.isApplyFontChanges === 'No') return false const doChangeFont = (fontName, fontStyle) => { fontStyleEle.innerText = fontStyle this.$refs.code.style.fontFamily = fontName } if (hasCacheValue(value)) { doChangeFont(value, getCacheValue(value)) console.log(`get font: ${value} in cache`) } else { const url = this.GFW === 'Fuck' ? `https://fonts.loli.net/css?family=${value}&display=swap` : `https://fonts.googleapis.com/css?family=${value}&display=swap` fetchStyleText(url).then(style => { console.log('font style:', style) setCacheValue(value, style) doChangeFont(value, style) }) } // console.log(value) }, changeFontSize (value) { if (this.isApplyFontChanges === 'No') return false // console.log(value) this.$refs.code.style.fontSize = `${value}px` } }, mounted () { this.defaultBackgroundColr = this.$refs.pre.style.backgroundColor this.changeTheme(this.current.theme) this.changeFont(this.current.font) } }) console.log('Setting is mounted') } // 获取 bulmaStyle, 并挂载 设置页 const bulmaStyle = getCacheValue('bulmaStyle') if (bulmaStyle) { mount(bulmaStyle) } else { const bulmaUrl = 'https://unpkg.com/buefy/dist/buefy.min.css' fetchStyleText(bulmaUrl).then(style => { mount(style) setCacheValue('bulmaStyle', style) }) } } // 注册设置页 GM_registerMenuCommand('Open Setting', openSetting, 'SH') beautify() console.log('highlight runing')