Greasy Fork

Greasy Fork is available in English.

Syntax highlighting

使用 highlight.js 给代码片断添加语法高亮, 并设置更优雅的字体.查看codepen原型快速了解: https://codepen.io/FloatingShuYin/pen/GRRjmOE?editors=0010

当前为 2019-10-20 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Syntax highlighting
// @namespace    https://floatsyi.com/
// @version      0.2.4
// @description  使用 highlight.js 给代码片断添加语法高亮, 并设置更优雅的字体.查看codepen原型快速了解: https://codepen.io/FloatingShuYin/pen/GRRjmOE?editors=0010
// @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/[email protected]/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
// ==/UserScript==
//  https://www.bootcdn.cn/highlight.js/
// 当前版本号
// FIXME
const currentVersion = '0.2.4'
// debug log
const isDev = false
const log = (...any) => {if (isDev) { console.log(...any) }}
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)})
    // TODO 清除 0.2.2 版本的废弃缓存
    deleteCacheValue('isForcePreBackgroundColors')
  }
})

// 保存当前版本号, 触发监听
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 isForcePreBackground =
  getCacheValue('isForcePreBackground') || 'Yes'

const getCurrentThemeBackground = 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(
    () => {
      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 (isForcePreBackground === 'Yes') {
          pre.style.background = getCurrentThemeBackground(
            getCacheValue(currentTheme)
          )
        }
      }
    },
    () => {
      log('Font is not available')
    }
  )
}

// 设置页
let parasitifer = null
const openSetting = () => {
  // 非首次调用
  if (parasitifer) {
    parasitifer.show()
    return true
  }

  parasitifer = document.createElement('div') // 此 DOM 节点将用作 shadowDOM 的载体被插入宿主的 DOM 节点中.
  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' })
  // 此节点将成为 shadowDOM 的直接子元素, 包裹一切, 所以用 HTML 元素很合适.
  // 不仅仅是语义上的合适, 大多数的 UI 库都需要一个结构完整的 DOM 树用来做自适应布局.
  const shadowContent = document.createElement('HTML')
  const shadowStyleEle = document.createElement('style')
  const bulmaStyleEle = document.createElement('style')
  const fontStyleEle = document.createElement('style')
  const themeStyleEle = document.createElement('style')
  const vueContainer = document.createElement('div') // 这个 DOM 节点不会显示在 DOM 树中, 而是作为 vue 的挂载点,同来渲染 vue 的模板.
  vueContainer.id = 'vue-root'
  // shadow DOM 的样式作用域隔离是非常实用的特性, 完全不受宿主环境影响的样式, 轻盈的开始
  shadowStyleEle.innerText = ``
  shadowContent.appendChild(shadowStyleEle)
  shadowContent.appendChild(bulmaStyleEle)
  shadowContent.appendChild(fontStyleEle)
  shadowContent.appendChild(themeStyleEle)
  shadowContent.appendChild(vueContainer)
  shadowRoot.appendChild(shadowContent)
  document.body.appendChild(parasitifer)

  const mount = style => {
    bulmaStyleEle.innerText = style

    const vueRoot = document
      .querySelector('#host-element')
      .shadowRoot.querySelector('#vue-root')
    // 这里使用 body 元素 作为父节点, 结合上面创造的 HTML 元素是为了给 UI 组件一个完整的上下文环境, 就像在一个新的 HTML 页面中一样.
    const vueTemplate = `<body style="height: 100vh">
    <div id="app-vue" class="container">
        <div class="columns is-vcentered is-centered">
            <div class="column is-3">
                <section>
                    <b-field label="Themes">
                        <b-select placeholder="Select a name" @input="changeTheme" v-model="current.theme" rounded>
                            <option v-for="(item, index) in themes" :value="item" :key="index">
                                {{item}}
                            </option>
                        </b-select>
                    </b-field>
                    <b-field label="Monospace Fonts">
                        <b-select placeholder="Select a name" @input="changeFont" v-model="current.font" rounded>
                            <option v-for="(item, index) in fonts" :value="item" :key="index">
                                {{item}}
                            </option>
                        </b-select>
                    </b-field>
                    <b-field label="Font Size">
                        <b-slider v-model="fontSize" @input="changeFontSize"></b-slider>
                    </b-field>
                    <b-field label="Apply theme changes">
                        <b-switch v-model="isApplyThemeChanges" true-value="Yes" false-value="No">
                            {{ isApplyThemeChanges }}
                        </b-switch>
                    </b-field>
                    <b-field label="Apply font changes">
                        <b-switch v-model="isApplyFontChanges" true-value="Yes" false-value="No">
                            {{ isApplyFontChanges }}
                        </b-switch>
                    </b-field>
                    <b-field label="Force theme background colors">
                        <b-switch v-model="isForcePreBackground" true-value="Yes" false-value="No">
                            {{ isForcePreBackground }}
                        </b-switch>
                    </b-field>
                    <b-field label="铁幕重重困青年">
                        <b-switch v-model="isGFW" true-value="Fuck" false-value="No thank you">
                            {{ isGFW }}
                        </b-switch>
                    </b-field>
                </section>
                <section style="margin-top:30px;">
                    <b-button type="is-primary" rounded @click="apply">Apply</b-button>
                    <b-button type="is-warning" rounded @click="close">Close</b-button>
                    <b-button type="is-danger" rounded @click="reset">Reset</b-button>
                </section>
            </div>
            <div class="column">
                <section style="overflow:hidden;">
                    <h1 class="has-text-centered">Real-time preview</h1>
                    <pre class="has-text-left" :style="styles.pre" ref="pre">
                    <code :style="styles.code" ref="code">
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

                    </code>
                </pre>
                </section>
            </div>
        </div>
    </div>
</body>
`
    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}px`,
              fontFamily: currentFont
            }
          },
          fontSize: fontSize,
          themes: themes,
          fonts: fonts,
          isApplyThemeChanges: isApplyThemeChanges,
          isApplyFontChanges: isApplyFontChanges,
          isForcePreBackground: isForcePreBackground,
          isGFW: isGFW,
          defaultBackground: ''
        }
      },
      watch: {
        isForcePreBackground (value) {
          log(value)
          if (value === 'No') {
            this.$refs.pre.style.background = this.defaultBackground
          } else {
            poll({
              condition: () => hasCacheValue(this.current.theme),
              resolve: () => {
                this.$refs.pre.style.background = getCurrentThemeBackground(
                  getCacheValue(this.current.theme)
                )
              },
              millisecond: 1000,
              retries: 5
            })
          }
        }
      },
      methods: {
        reset () {
          this.fontSize = 16
          this.isApplyThemeChanges = 'Yes'
          this.isApplyFontChanges = 'Yes'
          this.isForcePreBackground = '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(
            'isForcePreBackground',
            this.isForcePreBackground
          )
          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.isForcePreBackground === 'Yes') {
              this.$refs.pre.style.background = getCurrentThemeBackground(
                themeStyle
              )
              log(getCurrentThemeBackground(themeStyle))
            }

            window.hljs.highlightBlock(this.$refs.code)
          }

          if (hasCacheValue(value)) {
            doChangeTheme(value, getCacheValue(value))
            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`
            log(url)
            fetchStyleText(url).then(style => {
              setCacheValue(value, style)
              doChangeTheme(value, style)
            })
          }
          log('current theme', this.current.theme)
        },
        changeFont (value) {
          if (this.isApplyFontChanges === 'No') return false

          const doChangeFont = (fontName, fontStyle) => {
            fontStyleEle.innerText = fontStyle
            this.styles.code.fontFamily = fontName
          }

          if (hasCacheValue(value)) {
            doChangeFont(value, getCacheValue(value))
            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 => {
              log('font style:', style)
              setCacheValue(value, style)
              doChangeFont(value, style)
            })
          }
          log(value)
        },
        changeFontSize (value) {
          if (this.isApplyFontChanges === 'No') return false
          log(value)
          this.styles.code.fontSize = `${value}px`
        }
      },
      mounted () {
        this.defaultBackground = this.$refs.pre.style.background
        this.changeTheme(this.current.theme)
        this.changeFont(this.current.font)
      }
    })
    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()
log('highlight runing')