使用Chrome插件來補充一些寫做網站沒有Markdown的坑

場景

技術者寫文章,基本少不了Markdown了,可是不少自媒體平臺(大而全那種),每每都是坑爹的富文本編輯器(還不少是魔改UEditor,人家官方三年沒更新了喂)。css

相似這種:html

這是很麻煩的一件事,尤爲是那些沒有代碼塊的編輯器,沒錯,說的就是你,頭條!這種坑爹玩意兒,就得讓程序員手動粘貼代碼過來,而後遇到排版不友好的,呵呵,對,說的仍是你,頭條!
因而吧,我就想着,奶奶個熊,沒有我就本身寫個插件來搞吧。git

事實上,我本身的網站上有本身依賴marked作的一套編輯器,還挺好用,可是因爲圖牀問題,仍是得每次把富文本粘貼到頭條後,刪除圖片,從新上傳,沒辦法,窮是本命。
咳咳,最後作出來了,可是發現,沒卵用……喵的,Markdown有代碼塊,人家富文本仍是不支持啊……總之寫出來分享下方案與思路。

框架

manifest.json 配置程序員

{
  "name": "今日頭條協做輔助工具",
  "version": "1.0.0",
  "description": "今日頭條網頁版協做缺失工具的補充。",
  "permissions": [
    "activeTab",
    "declarativeContent"
  ],
  "content_scripts": [
    {
      "matches": [
        "https://mp.toutiao.com/*"
      ],
      "js": [
        "js/util.js",
        "libs/turndown.js",
        "js/content/index.js"
      ],
      "css": [],
      "run_at": "document_start"
    }
  ],
  "browser_action": {
    "default_popup": "popup.html",
    "default_title": "這裏能夠補充頭條網頁版本的不足哦。",
    "default_icon": {
      "16": "img/logo_16.png",
      "32": "img/logo_32.png",
      "48": "img/logo_48.png",
      "128": "img/logo_128.png"
    }
  },
  "homepage_url": "https://www.kvker.com/",
  "icons": {
    "16": "img/logo_16.png",
    "32": "img/logo_32.png",
    "48": "img/logo_48.png",
    "128": "img/logo_128.png"
  },
  "manifest_version": 2
}

這裏主要是看下content_scripts,這個說是scripts,你也能夠看到,是能夠塞一些css進去的,不過這裏就看js。
util.js主要提供一個編輯時候使用的函數,做用是避免每次編輯觸發input都轉義Markdown2HTML,也就是debounce消抖了。github

核心以下(附帶throttle節流):chrome

let doLastTimeout
let doLastOperates = []

let timeout = 500

let kvkerUtil = {
  /**
   * 異步執行的多個操做,只執行最後一個操做,好比輸入內容檢索
   * @param {function} operate 傳入的操做
   * @param {number} idx (可選)執行特性索引號的操做,通常不會用到
   */
  doAsyncLast(operate, time = 500, idx) {
    if (typeof operate !== 'function') {
      throw '執行doLast函數報錯:須要傳入函數!'
    }
    clearTimeout(doLastTimeout)
    doLastTimeout = setTimeout(() => {
      let lastOperate = doLastOperates[doLastOperates.length - 1]
      lastOperate()
      doLastOperates = []
      clearTimeout(doLastTimeout)
      doLastTimeout = null
    }, time)
    doLastOperates.push(operate)
  },
  /**
   * 某瞬間同步執行的多個操做,只執行最後一個操做,好比同時多個網絡請求返回而後提示消息
   * @param {function} operate 傳入的操做
   * @param {number} idx (可選)執行特性索引號的操做,通常不會用到
   */
  doSyncLast(operate, time = 500, idx) {
    if (typeof operate !== 'function') {
      throw '執行doLast函數報錯:須要傳入函數!'
    }
    if (!doLastTimeout) {
      doLastTimeout = setTimeout(() => {
        let lastOperate = doLastOperates[doLastOperates.length - 1]
        lastOperate()
        doLastOperates = []
        clearTimeout(doLastTimeout)
        doLastTimeout = null
      }, time)
    }
    doLastOperates.push(operate)
  },
}

而後是turndown.js,這個是marked.js的反向。marked是把Markdown2HTML,那麼turndown就是把HTML2Markdown了。這種東西固然是輪子了,安全好用(npm)。npm

至於content/index.js,就是核心頁面插入的js(不是注入inject,這倆有差,這裏不細說),就是document有了就運行的函數,通常都是document_start。這個等下結合插件的js說。json

這個文件最後就是看popup.html,這個文件名隨意區,做用是點擊插件顯示的那個小窗戶,拿FeHelper看就是這樣的:api

看下內容:安全

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Popup</title>
  <style>
    * {
      box-sizing: border-box !important;
    }
    body {
      margin: 0;
      padding: 4px;
    }
    #textarea {
      resize: none;
      outline: none;
      padding: 8px;
    }
  </style>
</head>

<body>
  <h1>頭條Markdown編輯器</h1>
  <textarea id="textarea" cols="80" rows="30"></textarea>

  <script src="libs/marked.js"></script>
  <script src="js/popup.js"></script>
</body>

</html>

常規內容,長這樣:

就一個輸入框和header,沒了,監聽這個輸入框變化。

而後引入js,marked.js就不用說了,popup.js就是這個頁面核心js了,下面細說。

到這裏,功能頁面與資源齊全了(不算icon什麼的)。


邏輯

  1. 插件的頁面輸入內容要同步到網頁的輸入框裏面,並且因爲網頁的輸入框是富文本,因此得是Markdown2HTML化以後的HTML字符;
  2. 網頁啓動時候,因爲content/index.js加載早於富文本生成,因此想辦法獲取到富文本的標籤;
  3. 網頁啓動時候,若是有草稿,得把草稿內容HTML2Markdown給插件輸入框;
  4. 基於3,得提示用戶在傳HTML2Markdown以前,打開popup頁面(插件頁面),否則傳給鬼了(插件頁面打開關閉都是從新運行頁面)。

一共上面4個核心問題處理,這個簡易版插件就完成了(雖然沒什麼卵用)。

問題1

popup.js

let editor = document.querySelector('#textarea')

// 監聽輸入,並傳給content/index.js,並接收回調備用
editor.addEventListener('input', e => {
  sendMessageToContentScript({ cmd: 'test', value: marked(e.target.value) }, function(response) {
    console.log('來自content的回覆:' + response)
  })
})

// 發送消息給content/index.js
function sendMessageToContentScript(message, callback) {
  chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
    chrome.tabs.sendMessage(tabs[0].id, message, function(response) {
      if(callback) callback(response)
    })
  })
}

// 監聽頁面生成的草稿……
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  editor.value = request.value
  sendResponse('我是popup,我已收到你的消息:' + JSON.stringify(request))
})

具體都是chrome插件的api,主要看邏輯便可。

問題2,3,4

content/index.js

let sourceEditor
// 每秒一次檢查是否加載好編輯器
let interval = setInterval(() => {
  if(sourceEditor) {
    // 這裏使用alert提示而且阻斷運行,給用戶時間打開插件……我是否是很機智
    alert('插件裝載完畢,請打開插件,再關閉彈窗')
    clearInterval(interval)
    // 發送草稿給popup
    sendInitialContent({cmd: 'initialData', value: new TurndownService().turndown(sourceEditor.innerHTML)})
  } else {
    sourceEditor = document.querySelector('.ql-editor')
  }
}, 1000)

function sendInitialContent(message) {
  chrome.runtime.sendMessage(message, function(response) {
    console.log('收到來自後臺的回覆:' + response)
  })
}

// 監聽popup來的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if(request.cmd === 'test') {
    console.log(request.value)
    kvkerUtil.doAsyncLast(() => sourceEditor.innerHTML = request.value)
  }
  sendResponse('我收到了你的消息!')
})

沒錯,靈魂是哪一個alert,YES!


效果

bug是有的,由於我也沒去優化,反正也沒用。並且頭條這富文本標籤挺奇葩的,得去魔改下marked.js才行。

主要是分享下邏輯,以及熟悉下chrome的api。

有興趣的,能夠扒拉源碼研究下,沒準哪一個平臺你有興趣能夠作一個完整版的~


資源

頭條插件v0.0.1源碼

相關文章
相關標籤/搜索