技術者寫文章,基本少不了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什麼的)。
一共上面4個核心問題處理,這個簡易版插件就完成了(雖然沒什麼卵用)。
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,主要看邏輯便可。
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。
有興趣的,能夠扒拉源碼研究下,沒準哪一個平臺你有興趣能夠作一個完整版的~