背景
先看一下目前的效果:在「Markdown 筆記」原有的上傳圖片彈窗中增加了一個我們自定義的上傳按鈕,通過直接與後端 API 互動完成圖片上傳(相關 API 是「筆記」上傳時公開使用的)。
兩年前還沒開始使用 GitHub 記錄讀書筆記,那時在用有道雲筆記。我使用的是 「Markdown 筆記」,在最開始一段時間沒有上傳圖片的需求,所以用起來還可以。後來開始記錄《Head First 設計模式》的讀書筆記,並畫了每個模式的類圖,開始有了上傳圖片的需求,而官方將 「Markdown 筆記」上傳圖片的功能僅對會員開放。
窮則思變,機智的我就注意到了以前使用「筆記」時可以直接上傳圖片,並且沒有會員限制,將這個圖片的連結放到「Markdown 筆記」內也可正常使用。所以就先人工操作,每次需要上傳圖片時,先切到一個自己建立的用於上傳圖片的「筆記」,圖片上傳成功後再將連結拷貝回「Markdown 筆記」,暫時解決了上傳圖片的需求。
懶是第一生產力,做程式設計師最大的好處就是可以通過寫程式碼簡化日常網上活動的各種重複性操作。在按照前面的方式完成幾篇帶圖片的「Markdown 筆記」後,就開始感到厭煩,這一操作機械重複沒有任何價值,所以就想到很適合通過程式碼自動執行。
實現流程
由於我們需要的圖片上傳功能在「Markdown 筆記」頁面中沒有,所以不能使用操作頁面元素的方式,只能通過抓 API ,並且自己呼叫 API 來實現圖片上傳。
封裝 API
封裝 API 前我們需要抓 API ,這個很簡單,其實就是觸發一下我們所需要實現的功能,然後檢視瀏覽器傳送了哪些請求,記住這些請求並封裝一下,以便後續呼叫。
「筆記」圖片上傳操作後會發現瀏覽器傳送了三個請求:
- 第一個是獲取
transmitId
以供後續兩個請求使用 - 第二個是使用
transmitId
上傳圖片(這裡僅實現了小檔案單次上傳) - 第三個是使用
transmitId
給上傳完成的檔案新增各種資訊,並獲取圖片地址
一個簡單的圖片上傳只需要三個請求,所以我們先封裝一下,具體實現可以在 api.js 找到(其中還封裝了其他 API ,不過後續沒有使用到)
封裝上傳元件
點選完上傳後,我們需要一個元件來實現選擇圖片、上傳圖片、返回圖片地址這三個操作。我們在前面封裝的 API 已經實現了上傳圖片並返回圖片地址的功能,所以在這裡我們這個元件只需要能觸發選擇圖片邏輯即可。我們可以通過 <input type="file">
來實現選擇檔案的功能,然後我們需要對其註冊 change
事件,用於當使用者選擇完圖片後,實現後續的操作邏輯。
// 上傳檔案,觸發後,會選擇檔案,並執行上傳檔案獲取url,最後執行回撥(回撥的第一個引數是檔案,第二個引數是上傳的url)
function upload(accept, callback) {
// 1. 建立 input 節點
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. 並繫結點選事件,用於觸發 實際執行上傳
var $this = this;
$('#diy-uploader-input').on('change', function(event) {
var file = event.target.files[0];
var url = $this.doUpload(file);
// 執行回撥
callback(file, url);
});
// 3. 執行模擬點選
$('#diy-uploader-input').click();
}
封裝上傳功能
現在我們已經擁有了上傳圖片的能力,接下來就是要將這個能力新增到我們的「Markdown 筆記」中,我們需要支援兩個功能:
- 在彈出上傳的視窗中增加一個按鈕,以便我們使用自定義的上傳
- 上傳成功後將相關資訊回填到視窗中的對應欄位
當時還沒怎麼接觸過 HTML
和 JavaScript
,但這兩個功能比較簡單,程式設計的基本原理也沒有使用新的知識體系,所以很快就能寫出能完成功能的程式碼(省略中間處理各種問題的過程)。
// 初始化,當md檔案上傳彈框出來的時候,新增上傳圖片按鈕
function init() {
// 有道雲筆記用 on 繫結 DOMNodeInserted 不生效 - -|||
$('body')[0].addEventListener("DOMNodeInserted", function(e){
// 如果是 markdown 上傳圖片的節點被新增
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);
// 觸發 input 事件,更新雙向繫結的資料
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);
// 觸發 input 事件,更新雙向繫結的資料
tool.trigger(nameSelector, 'input');
}
至此我們已經實現了有道雲筆記支援「Markdown 筆記」上傳圖片的功能。可以直接將 loader.js 中的程式碼拷貝至 Tampermonkey 中即可實現非會員上傳。
小結
這一段指令碼是兩年前寫的,但是至今仍舊可以使用。雖然時隔很久,指令碼實現的具體細節早已忘記,但是當我看到我這豐富的註釋時,還是可以回想起來當時想法及每段程式碼的邏輯。寫註釋也是我一直堅持的好習慣,平時寫業務程式碼中沒有這麼詳細的註釋去解釋每一行的操作邏輯,但仍舊會在每一段相對獨立的操作開始時註明其功能等資訊。
本文首發於公眾號:滿賦諸機(點選檢視原文) 開源在 GitHub :reading-notes/tampermonkey/note-youdao