Develop Plugin for Joplin Index
Take Control More Usefully かなり便利なJoplin生活。 メモ帳革命が私に起きている。
ただ若干不便なことが。 それがBlogの更新。 動画のリンクをhexo-tag-owl で変換しているのだが、タグをいちいち書いているのが面倒くさい。 そんなとき、ショートカットキーで一発変換してくれたら超便利だなと。
あと、pixabay とかに代表される無料素材サイトからそれらしい画像を検索して勝手にダウンロードして添付するようなことできないかなと。 Office製品にある画像検索機能。そうアレ。
もうひとつ。ブラウザと同じように選択テキストを右クリックしてコンテキストメニューに「Google検索」と「Google翻訳」を追加したい。
そんなニッチなプラグインがあるはずもなく、そこら辺がちょっと不便。
It doesn’t exist, just make it exist なけりゃ作るだけ。ここ を参考に作ってみようって話。
What’s required 要件としては
エディタ上で選択テキストをショートカットキーでHeo Tag Owlのフォーマットに変換する
エディタ上でショートカットキーでプロンプトを出して、検索ワードを入力、Pixabayで検索して出てきた候補を選択したら画像をダウンロードして添付する
エディタ上で選択テキストを右クリックのコンテキストメニューから「Google検索」「Google翻訳」を選択できるようにする
こんな感じだろうか。 まずは勉強も兼ねて1番目のやつからやってみる。
What is Hexo Tag Owl? そもそもこのHexo Tag Owl というのが何かを説明すると、Hexoというブログ管理ツールのプラグインで記事内に特殊タグを書くと、記事を投稿するときに自動的にHTMLタグに展開される仕組み。
タグはYouTube、ニコニコ動画などの動画、Giphyとかの静止画に対応しており、ユーザはタグの種類(youtube、niconico etc..)と、リソースのIDだけを指定すれば、自動的にHTMLタグに展開するという支援ツール。
これをJoplin上で編集しているときにURLを貼り付けて変換ボタンもしくはショートカットキーで展開してくれたら便利だなと。
たとえば
みたいなURLがあったとしたら、これを
{% owl youtube Bu1TGvs %}
って変換してほしい。
Setup My Environment 開発環境を整える。 Windows 11 Proの環境でnodejsのバージョンが18.*。
%USERPROFILE%>cd Documents %USERPROFILE%\Documents>npm i -g yo generator-joplin %USERPROFILE%\Documents>mkdir develop\joplin-plugin-hexo-support %USERPROFILE%\Documents>cd develop\joplin-plugin-hexo-support %USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >git init %USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >yo joplin
ここでエラーが出る。 エラー内容を見るとjoplinのバージョンが古いみたいな内容。
%USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >type src\manifest.json { "manifest_version" : 1 , "id" : "net.yf-19.friedbis.joplin-plugin-hexo-support" , "app_min_version" : "2.8.0" , "version" : "1.0.2" , "name" : "joplin-plugin-hexo-support" , "description" : "add some buttons of blogging support for hexo" , "author" : "friedbis" , "homepage_url" : "https://superpack.yf-19.net" , "repository_url" : "https://github.com/friedbis/joplin-plugin-hexo-support.git" , "keywords" : ["shortcut" , "toolbar" , "icon" , "menu" ], "categories" : [] } %USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >
ここのmanifest.jsonファイルの「app_min_version 」が環境に入れているJoplinのバージョンより高いから怒られているのか? なので、今の2.7.15を入れたら怒られなくなった。 これで必要なアセットが用意される。
ほんで
というファイルをいじることで実装できるようだ。 早速やってみよう。
Develop my plugin 正直全部自分で用意するのが億劫なので、違うプラグインを参考にしてみたい。 そこで見つけたのが、xardbaiz 氏のjoplin-plugin-hackmd というプラグイン。 このプラグインを選んだ理由は以下のとおり。
エディタにアイコンを追加している
ショートカットキーを追加している
設定画面に個別設定を追加している
これと同じことをしたかったので、参考に作ってみた。
index.ts import joplin from 'api' ;import { MenuItemLocation } from 'api/types' ;import { ToolbarButtonLocation } from 'api/types' ;import { settings } from "./settings" ;import { actions, DTI_SETTINGS_PREFIX , ACTIVATE_ONLY_SETTING , hostList, DEFAULTID } from "./common" ;function wrapSelectionWithStrings (selected : string |null ){ for (const host in hostList){ const objHost = hostList[host]; if (selected.search (objHost.hostname )>-1 ){ console .log ('Detected [' +objHost.name +']' ); return _wrapSelectionWithStrings (selected, objHost); } if (objHost.anotherHostname !=="" &&selected.search (objHost.anotherHostname )>-1 ){ console .log ('Detected [' +objHost.name +']' ); return _wrapSelectionWithStrings (selected, objHost); } if (selected.search (objHost.name )>-1 ){ console .log ('Detected [' +objHost.name +']' ); return _wrapSelectionWithStrings (selected, objHost); } } } function _wrapSelectionWithStrings (selected : string |null , Host : any ) { if (!selected) selected = Host .defaultURL ; const start = selected.search (/[^\s]/ ); const end = selected.search (/[^\s](?=[\s]*$)/ ); const core = selected.slice (start, end + 1 ); if (core.startsWith (Host .wrapString1 ) && core.endsWith (Host .wrapString2 )) { console .log ('TagOWL -> URL' ); const inside = core.slice (Host .wrapString1 .length , core.length - Host .wrapString2 .length ); const defaulturl = Host .defaultURL .replace (DEFAULTID , inside); console .log ('[' +defaulturl+']' ); return defaulturl; } else { console .log ('URL -> TagOWL' ); const mediaid=getMediaId (selected, Host .hostname , Host .queryString ); const tagowl=selected.slice (0 , start) + Host .wrapString1 + mediaid + Host .wrapString2 + selected.slice (end + 1 ); console .log ('[' +tagowl+']' ); return tagowl; } } function getMediaId (selected : string , hostname : string , queryString : string ){ let parse : URL ; try { console .log ('Parsing URL' ); parse = new URL (selected); }catch (err){ console .log ('Error parsing URL' ); return '**error**' ; } if (parse.hostname .search (hostname)<0 ){ console .log ('Invalid hostname' ); return '**error**' ; } if (queryString!=="" ){ console .log ('Parsing MediaID' ); const start = parse.search .search (queryString); if (start>-1 ){ const params = parse.searchParams ; const query = queryString.replace ('=' ,'' ).replace ('?' ,'' ); return params.get (query); } } console .log ('Parsing pathname' ); const pathArray = parse.pathname .split ('/' ); if (pathArray[pathArray.length -1 ]!=='' ){ if (hostname.search ('giphy' )>-1 ) return pathArray[pathArray.length -2 ]; else return pathArray[pathArray.length -1 ]; }else { return pathArray[pathArray.length -2 ]; } } joplin.plugins .register ({ onStart : async function ( ) { console .info ('Hexo Support plugin started!' ); await settings.register (); const activateOnlyIfEnabledInMarkdownSettings = await joplin.settings .value (ACTIVATE_ONLY_SETTING ); for (const actionName in actions) { const action = actions[actionName]; let activate = true ; if (activateOnlyIfEnabledInMarkdownSettings && actionName !== 'textStrikethrough' ) { activate = await joplin.settings .globalValue (action.markdownPluginSetting ); } joplin.commands .register ({ name : actionName, label : action.label , enabledCondition : 'markdownEditorPaneVisible && !richTextEditorVisible' , iconName : action.iconName , execute : async () => { const selectedText = (await joplin.commands .execute ('selectedText' ) as string ); const newText = wrapSelectionWithStrings (selectedText); await joplin.commands .execute ('replaceSelection' , newText); await joplin.commands .execute ('editor.focus' ); }, }); var toolbarIconEnabled = !(await joplin.settings .value (DTI_SETTINGS_PREFIX + actionName)); if (toolbarIconEnabled) { joplin.views .toolbarButtons .create (actionName + 'Button' , actionName, ToolbarButtonLocation .EditorToolbar ); } joplin.views .menuItems .create (actionName + 'MenuItem' , actionName, MenuItemLocation .Edit , { accelerator : action.accelerator }); } }, });
としてみた。 あとは
src/settings.ts src/common.ts
ここらへんを編集した。 ポイントとしては、common.tsにタグ変換のための情報をhostListとして保存している。 こうしておけば、あとから追加することもできる。
a part of common.ts export const hostList = { YouTube : { name : 'youtube' , wrapString1 : '{% owl youtube ' , wrapString2 : ' %}' , defaultURL : 'https://www.youtube.com/watch?v=NNNNNNNN' , hostname : 'youtube.com' , anotherHostname : 'youtu.be' , queryString : 'v=' , }, DailyMotion : { name : 'dailymotion' , wrapString1 : '{% owl dailymotion ' , wrapString2 : ' %}' , defaultURL : 'https://www.dailymotion.com/video/NNNNNNNN' , hostname : 'dailymotion.com' , anotherHostname : '' , queryString : '' , }, NicoNico : { name : 'niconico' , wrapString1 : '{% owl niconico ' , wrapString2 : ' watch %}' , defaultURL : 'https://www.nicovideo.jp/watch/NNNNNNNN' , hostname : 'nicovideo.jp' , anotherHostname : 'nico.ms' , queryString : '' , }, vimeo : { name : 'vimeo' , wrapString1 : '{% owl vimeo ' , wrapString2 : ' %}' , defaultURL : 'https://vimeo.com/NNNNNNNN' , hostname : 'vimeo.com' , anotherHostname : '' , queryString : '' , }, IMDB : { name : 'imdb' , wrapString1 : '{% owl imdb ' , wrapString2 : ' %}' , defaultURL : 'https://www.imdb.com/title/NNNNNNNN/?ref_=ext_shr_lnk' , hostname : 'imdb.com' , anotherHostname : '' , queryString : '' , }, GIPHY : { name : 'giphy' , wrapString1 : '{% owl giphy ' , wrapString2 : ' %}' , defaultURL : 'https://media.giphy.com/media/NNNNNNNN/giphy.gif' , hostname : 'giphy.com' , anotherHostname : '' , queryString : '' , }, Imgur : { name : 'imgur' , wrapString1 : '{% owl imgur ' , wrapString2 : ' %}' , defaultURL : 'https://imgur.com/gallery/NNNNNNNN' , hostname : 'imgur.com' , anotherHostname : '' , queryString : '' , }, };
最終的な状況はここ を参考にしていただければと。
Test my plugin んで、テストをしたいときは、以下のようにする。
%USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >npm run dist %USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >
これでdistフォルダにindex.jsとmanifest.jsonができるようだ。 ここの場所をプラグインの場所としてJoplinを設定するとテストができるようだ。 ただ環境を変える方がよいってことだったので、
C:\> "%USERPROFILE%\AppData\Local\Programs\Joplin\Joplin.exe" --env dev
DOS窓から上記コマンドを実行すると環境を変えられるようだ。
==現行の環境でやらない方がよいです。最悪データが削除される可能性があります。==
んで、開発用Joplinの[オプション] -> [プラグイン]を開いて、[詳細設定を表示]をクリックすると、「Development plugins」というテキストボックスがあるので、そこに今回の開発フォルダ内のdistフォルダを指定する。
%USERPROFILE%\Documents\develop\joplin-plugin-hexo-support\dist
ここで一旦開発用joplinを閉じて、さっきのjoplinの起動方法で
C:\> "%USERPROFILE%\AppData\Local\Programs\Joplin\Joplin.exe" --env dev
開発用Joplinを再度起動すると、今回のプラグインを試すことができた。 デバッグのやり方はまた今度調べてみよう。
Deploy my plugin さてテストも終わって、実際のプラグインをインストールする。 さっきテストするときのコマンドを実行すると、publishフォルダにjplファイルが出来上がる。
%USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >npm run dist > joplin-plugin-hexo-support @1.0 .0 dist > webpack --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive (node:20776 ) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead (Use `node --trace-deprecation ...` to show where the warning was created) Plugin archive has been created in %USERPROFILE%\Documents\develop\joplin-plugin-hexo-support \publish\net.yf-19 .friedbis.joplin-plugin-hexo-support .jpl %USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >dir publish 2022 /05 /03 09 :31 <DIR > .2022 /05 /03 09 :31 <DIR > ..2022 /05 /03 09 :31 11 ,776 net.yf-19 .friedbis.joplin-plugin-hexo-support .jpl2022 /05 /03 09 :31 634 net.yf-19 .friedbis.joplin-plugin-hexo-support .json 2 個のファイル 12 ,410 バイト 2 個のディレクトリ 179 ,803 ,062 ,272 バイトの空き領域 %USERPROFILE%\Documents\develop\joplin-plugin-hexo-support >
こんな感じ。 このjplファイルを実際の環境にインストールしたら使える。 なんて簡単なんだ。
今後 要件のところにも書いたけど、2つ目や3つ目も実装していくことにしよう。 Joplin面白い。
コメント: