背景
先看一下目前的效果:在「Markdown 筆記」原有的上傳圖片彈窗中增加了一個我們自定義的上傳按鈕,通過直接與后端 API 交互完成圖片上傳(相關(guān) API 是「筆記」上傳時公開使用的)嗤堰。
兩年前還沒開始使用 GitHub 記錄讀書筆記戴质,那時在用有道云筆記。我使用的是 「Markdown 筆記」踢匣,在最開始一段時間沒有上傳圖片的需求告匠,所以用起來還可以。后來開始記錄《Head First 設(shè)計模式》的讀書筆記离唬,并畫了每個模式的類圖后专,開始有了上傳圖片的需求,而官方將 「Markdown 筆記」上傳圖片的功能僅對會員開放输莺。
窮則思變行贪,機智的我就注意到了以前使用「筆記」時可以直接上傳圖片,并且沒有會員限制模闲,將這個圖片的鏈接放到「Markdown 筆記」內(nèi)也可正常使用建瘫。所以就先人工操作,每次需要上傳圖片時尸折,先切到一個自己建立的用于上傳圖片的「筆記」啰脚,圖片上傳成功后再將鏈接拷貝回「Markdown 筆記」,暫時解決了上傳圖片的需求实夹。
懶是第一生產(chǎn)力橄浓,做程序員最大的好處就是可以通過寫代碼簡化日常網(wǎng)上活動的各種重復(fù)性操作。在按照前面的方式完成幾篇帶圖片的「Markdown 筆記」后亮航,就開始感到厭煩荸实,這一操作機械重復(fù)沒有任何價值,所以就想到很適合通過代碼自動執(zhí)行缴淋。
實現(xiàn)流程
由于我們需要的圖片上傳功能在「Markdown 筆記」頁面中沒有准给,所以不能使用操作頁面元素的方式泄朴,只能通過抓 API ,并且自己調(diào)用 API 來實現(xiàn)圖片上傳露氮。
封裝 API
封裝 API 前我們需要抓 API 祖灰,這個很簡單,其實就是觸發(fā)一下我們所需要實現(xiàn)的功能畔规,然后查看瀏覽器發(fā)送了哪些請求局扶,記住這些請求并封裝一下,以便后續(xù)調(diào)用叁扫。
「筆記」圖片上傳操作后會發(fā)現(xiàn)瀏覽器發(fā)送了三個請求:
- 第一個是獲取
transmitId
以供后續(xù)兩個請求使用 - 第二個是使用
transmitId
上傳圖片(這里僅實現(xiàn)了小文件單次上傳) - 第三個是使用
transmitId
給上傳完成的文件添加各種信息三妈,并獲取圖片地址
一個簡單的圖片上傳只需要三個請求,所以我們先封裝一下莫绣,具體實現(xiàn)可以在 api.js 找到(其中還封裝了其他 API 沈跨,不過后續(xù)沒有使用到)
封裝上傳組件
點擊完上傳后,我們需要一個組件來實現(xiàn)選擇圖片兔综、上傳圖片、返回圖片地址這三個操作狞玛。我們在前面封裝的 API 已經(jīng)實現(xiàn)了上傳圖片并返回圖片地址的功能软驰,所以在這里我們這個組件只需要能觸發(fā)選擇圖片邏輯即可。我們可以通過 <input type="file">
來實現(xiàn)選擇文件的功能心肪,然后我們需要對其注冊 change
事件锭亏,用于當(dāng)用戶選擇完圖片后,實現(xiàn)后續(xù)的操作邏輯硬鞍。
// 上傳文件慧瘤,觸發(fā)后,會選擇文件固该,并執(zhí)行上傳文件獲取url锅减,最后執(zhí)行回調(diào)(回調(diào)的第一個參數(shù)是文件,第二個參數(shù)是上傳的url)
function upload(accept, callback) {
// 1. 創(chuàng)建 input 節(jié)點
if($('#diy-uploader-input').length == 0) {
$('body').append('<input id="diy-uploader-input" type="file" style="position: absolute; top: -1000px; left: -1000px;" accept="' + accept + '">');
}
// 2. 并綁定點擊事件伐坏,用于觸發(fā) 實際執(zhí)行上傳
var $this = this;
$('#diy-uploader-input').on('change', function(event) {
var file = event.target.files[0];
var url = $this.doUpload(file);
// 執(zhí)行回調(diào)
callback(file, url);
});
// 3. 執(zhí)行模擬點擊
$('#diy-uploader-input').click();
}
封裝上傳功能
現(xiàn)在我們已經(jīng)擁有了上傳圖片的能力怔匣,接下來就是要將這個能力添加到我們的「Markdown 筆記」中,我們需要支持兩個功能:
- 在彈出上傳的窗口中增加一個按鈕桦沉,以便我們使用自定義的上傳
- 上傳成功后將相關(guān)信息回填到窗口中的對應(yīng)字段
當(dāng)時還沒怎么接觸過 HTML
和 JavaScript
每瞒,但這兩個功能比較簡單,編程的基本原理也沒有使用新的知識體系纯露,所以很快就能寫出能完成功能的代碼(省略中間處理各種問題的過程)剿骨。
// 初始化,當(dāng)md文件上傳彈框出來的時候埠褪,添加上傳圖片按鈕
function init() {
// 有道云筆記用 on 綁定 DOMNodeInserted 不生效 - -|||
$('body')[0].addEventListener("DOMNodeInserted", function(e){
// 如果是 markdown 上傳圖片的節(jié)點被添加
if(e.target.nodeName.toLowerCase()== 'markdown-upload-image') {
var divButtonBarSelector = 'body > dialog-overlay > div > div > div.widget-dialog-body > markdown-upload-image > div > div.button-bar';
// 如果 底部按鈕欄已出來浓利,并且沒添加過 上傳按鈕挤庇,則添加 上傳按鈕
if($(divButtonBarSelector).length == 1 && $('#diy-uploader-button').length == 0) {
// 添加按鈕
var uploaderButton = '<div id="diy-uploader-button" class="loadbtn local-img" style="margin-right:15px;height:34px">上傳圖片</div>';
$('body > dialog-overlay > div > div > div.widget-dialog-body > markdown-upload-image > div > div.button-bar').prepend(uploaderButton);
// 給按鈕添加事件
$('#diy-uploader-button').on('click', function() {
component.uploader.upload('image/*', feature.mdImageUploader.backfillPage);
});
}
}
}, false);
}
// 回填頁面
function backfillPage(file, url) {
// 填入url
var urlSelector = 'body > dialog-overlay > div > div > div.widget-dialog-body > markdown-upload-image > div > div:nth-child(2) > div.edit-container > input';
$(urlSelector).val(url);
// 觸發(fā) input 事件,更新雙向綁定的數(shù)據(jù)
tool.trigger(urlSelector, 'input');
// 填入文件名
var nameSelector = 'body > dialog-overlay > div > div > div.widget-dialog-body > markdown-upload-image > div > div:nth-child(3) > div.edit-container > input';
var name = file.name.substring(0, file.name.lastIndexOf('.'));
$(nameSelector).val(name);
// 觸發(fā) input 事件荞膘,更新雙向綁定的數(shù)據(jù)
tool.trigger(nameSelector, 'input');
}
至此我們已經(jīng)實現(xiàn)了有道云筆記支持「Markdown 筆記」上傳圖片的功能罚随。可以直接將 loader.js 中的代碼拷貝至 Tampermonkey 中即可實現(xiàn)非會員上傳羽资。
小結(jié)
這一段腳本是兩年前寫的淘菩,但是至今仍舊可以使用。雖然時隔很久屠升,腳本實現(xiàn)的具體細節(jié)早已忘記潮改,但是當(dāng)我看到我這豐富的注釋時,還是可以回想起來當(dāng)時想法及每段代碼的邏輯腹暖。寫注釋也是我一直堅持的好習(xí)慣舌界,平時寫業(yè)務(wù)代碼中沒有這么詳細的注釋去解釋每一行的操作邏輯芙扎,但仍舊會在每一段相對獨立的操作開始時注明其功能等信息。