GitHub CodeBlock Icon Enhance PowerBy NerdFont

Display more icons by setting NerdFont for GitHub's code block.

// ==UserScript==
// @name              GitHub CodeBlock Icon Enhance PowerBy NerdFont
// @name:zh-CN        GitHub 代码块图标增强(基于 NerdFont)
// @description       Display more icons by setting NerdFont for GitHub's code block.
// @description:zh-CN 通过为 GitHub 代码块设置 NerdFont 字体以显示更多图标。
// @icon              https://github.githubassets.com/favicons/favicon.png
// @author            cuiko
// @namespace         https://github.com/cuiko/codeblock-icon-enhance
// @homepage          https://github.com/cuiko/codeblock-icon-enhance
// @supportURL        https://github.com/cuiko/codeblock-icon-enhance/issues
// @require           https://unpkg.com/sweetalert2@11/dist/sweetalert2.min.js
// @resource          Swal https://unpkg.com/sweetalert2@11/dist/sweetalert2.min.css
// @version           0.1.1
// @license           GPL-3.0
// @match             *://github.com/*
// @match             *://gitlab.com/*
// @match             *://gitee.com/*
// @match             *://stackoverflow.com/*
// @match             *://stackexchange.com/*
// @match             *://reddit.com/*
// @match             *://juejin.cn/*
// @match             *://segmentfault.com/*
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_addStyle
// @grant             GM_registerMenuCommand
// @grant             GM_getResourceText
// @run-at            document-start
// ==/UserScript==

;(function () {
  "use strict"

  class Fonts {
    constructor(...fonts) {
      if (fonts.length === 1 && Array.isArray(fonts[0])) {
        this.fonts = fonts.split(",")
      } else {
        this.fonts = fonts
      }
    }

    add(...fonts) {
      this.fonts.push(...fonts)
    }

    remove(font) {
      let index = this.fonts.indexOf(font)
      if (index !== -1) {
        this.fonts.splice(index, 1)
      }
    }

    toString(separator = ",") {
      return this.fonts.join(separator)
    }
  }

  let default_settings = {
    presetFonts: new Fonts(
      "ui-monospace",
      "SFMono-Regular",
      "SF Mono",
      "Menlo",
      "Consolas",
      "Liberation Mono",
      "monospace"
    ),
    customFonts: new Fonts("CaskaydiaCove Nerd Font Propo"),
  }

  const Symbol = {
    PLUGIN_NAME: "codeblock-icon-enhance",
  }

  let kit = {
    /**
     * prompt
     * @param {Object} options
     * @param {string} options.title
     * @param {string} options.default_value
     * @param {Function} options.callback
     */
    prompt: function (options) {
      let { title, default_value, callback } = options

      if (Swal) {
        Swal.fire({
          title,
          input: "text",
          inputValue: default_value,
        }).then(({ value }) => callback(value))
      } else {
        const value = window.prompt(title, default_value)
        callback(value)
      }
    },
  }

  /**
   * add style
   * @param {string} id
   * @param {string} tag
   * @param {string} css
   */
  function addStyle(id, tag, css) {
    tag = tag || "style"
    let doc = document,
      styleDom = doc.getElementById(id)
    if (styleDom) styleDom.remove()
    let style = doc.createElement(tag)
    style.rel = "stylesheet"
    style.id = id
    tag === "style" ? (style.innerHTML = css) : (style.href = css)
    doc.getElementsByTagName("body")[0].appendChild(style)
  }

  /**
   * apply fonts setting
   * @param fonts string
   * @returns {Fonts}
   */
  function applyFonts(fonts) {
    let codeblockFonts = fonts ? new Fonts(fonts) : default_settings.customFonts

    GM_setValue(Symbol.PLUGIN_NAME, codeblockFonts.toString())
    addStyle(
      Symbol.PLUGIN_NAME,
      "style",
      ` :root :is(pre,pre *,code,code *,.cm-editor [class*='cm-'] *,.code,.code *,.blob-num,.blob-num *,.blob-code,.blob-code *,textarea,.react-line-numbers *,.react-code-lines *):not([class*='expand' i],[class*='collapse' i]) { font-family: ${default_settings.presetFonts.toString()},${codeblockFonts} !important; } `
    )

    return codeblockFonts
  }

  function main() {
    // init default settings
    addStyle("swal-pub-style", "style", GM_getResourceText("Swal"))
    let fonts = GM_getValue(Symbol.PLUGIN_NAME)
    let appliedFonts = applyFonts(fonts)

    // init menu
    GM_registerMenuCommand("Font Settings", () => {
      kit.prompt({
        title: "Enter the font(s) you want to use for the code block",
        default_value: appliedFonts.toString(),
        callback: (value) => {
          appliedFonts = applyFonts(value)
        },
      })
    })
  }
  main()
})()