基礎ファイルの作成
とはいっても、拡張機能を作ろうと思ったことがないので、作り方自体知りません。
色々と調べながら、試し試しやっていきましょう。
どうやらnpmで構成ファイルを作るのが簡単なようです。
とりあえずgenerator-codeをインストールします。
npm install -g yo generator-code
これをインストールすると、VS拡張の雛型を作ることができます。
雛型の生成にはこれを実行します。
yo code
え、誰?????
なんかよく知らない人が突然出てきました。
おじ
……で……たい?
ん??????
おじ
どんな言語で作りたい???
笹錆
突然怪しい人が真面目な質問してきた。
とりあえずこのおじさんに従っていけば雛型は作れそうです。
おじさんからの質問は次の通りです。
これに答え終わると、いっぱいファイルが生成されます。
大変そうな作業に思われますが、この中でいじるのはextention.tsとpackage.jsだけでいいです。
実際の機能に関わるのがextention、その機能の呼び出し方に関わるのがpackage.jsです。
機能の実装
先ほど作ったJavascriptと同じ処理をextentionに書いていきましょう。
言語が違いますが、TypescriptはJavascript亜種みたいなものなので、結構簡単に移し替えられます。
// Markdownのコンバート
function convert(code: string, dictionary: Record<string, string>) {
// Javascriptで書いた処理
}
// コンバートと表示
function convertAndDisplay(mark: vscode.TextEditor) {
var convertedHtml = "";
var markdown = mark.document.getText();
const pages = markdown.split("--p");
for (var k = 0; k < pages.length; k++) {
const paragraphs = pages[k].split("---"); // 「---」で文章を区切る
for (var i = 0; i < paragraphs.length; i++) {
var paragraph = paragraphs[i].trim();
if (i === 0) {
convertedHtml +=
'<div class="toppage"><div class="window">' +
convert(paragraph, dictional) +
'</div></div><div class="headline">';
} else if (i === 1) {
if (i === paragraphs.length - 1) {
convertedHtml +=
'<div class="top-window window-end">' +
convert(paragraph, dictional) +
"</div>";
} else {
convertedHtml +=
'<div class="top-window">' +
convert(paragraph, dictional) +
"</div>";
}
} else if (i === paragraphs.length - 1) {
convertedHtml +=
'<div class="window window-end">' +
convert(paragraph, dictional) +
"</div>";
} else {
convertedHtml +=
'<div class="window">' + convert(paragraph, dictional) + "</div>";
}
}
convertedHtml += "</div>";
}
return convertedHtml;
}
詳しいことは公式のリファンスを読んで欲しいのですが、現在表示しているファイルに書かれている文章をHTMLに起こす処理を1秒ごとに行っています。
なので、あとはHTMLへの返還を行なう関数と、ダウンロードを行う関数を作ります。
こんな感じで書くと、sabilog.marks、sabilog.convertのコマンドでそれぞれの処理を行なうことができます。
marksの方はMarkdownからHTMLに変換して表示、convertは変換したものをダウンロードします。
// エディタの表示
function getWebviewContent(marks: string) {
return `<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
${marks}
</body>
</html>`;
}
export function activate(context: vscode.ExtensionContext) {
// marksコマンドの処理
let disposable = vscode.commands.registerCommand("sabilog.marks", () => {
const textdox = vscode.window.activeTextEditor;
const panel = vscode.window.createWebviewPanel(
"markdown editor",
"MARKS EDITOR",
vscode.ViewColumn.One,
{}
);
const updateWebview = () => {
if (textdox !== undefined) {
panel.webview.html = getWebviewContent(convertAndDisplay(textdox));
}
};
setInterval(updateWebview, 1000); // 重くなるので注意
});
// convertコマンドの処理
context.subscriptions.push(
vscode.commands.registerCommand("sabilog.convert", () => {
(async () => {
// ファイルを読み込む
const editor = vscode.window.activeTextEditor;
var filepath: string = editor?.document?.fileName || "";
filepath = filepath.replace(/\.md$/, ".html");
// テキストをUint8Arrayに変換
var panel: string = "";
if (editor !== undefined) {
panel = convertAndDisplay(editor);
}
vscode.window.showInformationMessage(`${filepath}`);
const blob: Uint8Array = Buffer.from(`${panel}`);
// ファイルへ書き込む
await vscode.workspace.fs.writeFile(vscode.Uri.file(filepath), blob);
})();
vscode.window.showInformationMessage("exe convert!");
})
);
context.subscriptions.push(disposable);
}
export function deactivate() {}
すると、こんな感じで表示されます。
見やすいし使いやすい!!!!
やっぱり慣れてるツールだと書きやすいですね。
あと移行自体が簡単で、TypeScriptの強みを感じますね。
ですが、いちいちコマンドパレットを開いて編集するのも面倒ですね。
そこで、ダウンロードはボタンで行えるようにもします。
export function activate(context: vscode.ExtensionContext) {
// ダウンロードボタンを追加
const downloadbutton = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
-20
);
downloadbutton.text = "Download";
downloadbutton.tooltip = "Convert this markdown to HTML";
downloadbutton.command = "sabilog.convert";
// ボタンの表示
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (editor && editor.document.languageId === "markdown") {
downloadbutton.show();
} else {
downloadbutton.hide();
}
})
);
}
これでステータスバーにダウンロードボタンが追加されます。
他にも色々表示されていますが、これらは僕が個人的に入れていた拡張機能です。
確かMarkdownを表示するやつだった気がします。
もしかして、その拡張機能のCSSをちょっと変えたらもっと楽にできたのでは?
あれ???
Notion APIの使用
嫌なことに気付きそうになりましたが、とりあえず作業を進めます。
現状ではHTMLで作っていたエディタをVScodeに移せましたが、Notionを利用してもっと簡単に記述したいですよね。
というわけで、Notion APIを使ってMarkdownファイルを得られるようにしましょう。
APIを使うためには、ワークスペースIDとトークンが必要になります。
公式のリファレンスを見ると、なんか、こう上手いことやってあげることでページ内の要素を引っ張り出せるみたいです。
実際にインテグレーションを作るとこんな感じです。
ここに書かれているのがトークンです。
ついでに書いてた記事をまとめたページとインテグレーションをつなげます。
このページのurlに書かれた32文字がデータベースIDです。
というわけで、実際に使ってみました。
import { Client } from "@notionhq/client";
const notionToken = "secret_〇〇";
const notionDatabaseID = "△△";
const notion = new Client({
auth: notionToken,
});
const pageIdDictionary = {};
export const getDatabase = async (databaseId: string) => {
const response = await notion.databases.query({
database_id: databaseId || "",
});
return response;
};
// セレクトメニュー用
export const getPage = async (pageId: string) => {
const response = await notion.pages.retrieve({
page_id: pageId,
});
return response;
};
// 選択肢の名前の取得
export const getPageTitle = async (databaseId: string) => {
var selectIDs = [];
const database = await getDatabase(databaseId);
for (var i = 0; i < database.results.length; i++) {
const pagetitle = database.results[i].properties.名前.title[0].text.content;
pageIdDictionary[pagetitle] = database.results[i].id.trim();
selectIDs.push(pagetitle);
}
return selectIDs;
};
これらの関数は、トークンとデータベースIDからリストにあるページのタイトルを読み取ってくれるやつです。
ここに、読み取ったタイトルを実際に並べてメニューみたいにする処理を書き加えましょう。
export function activate(context: vscode.ExtensionContext) {
// セレクトボタンを追加
const selectButton = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
-10
);
selectButton.text = "$(list-selection)Notion";
selectButton.tooltip = "Download by Notion";
selectButton.command = "sabilog.selectmenu";
// ボタンの表示
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (editor && editor.document.languageId === "markdown") {
selectButton.show();
} else {
selectButton.hide();
}
})
);
// セレクトメニューのコマンド
context.subscriptions.push(
vscode.commands.registerCommand("sabilog.selectmenu", async () => {
// 選択肢の名前の取得
var selectoption = getPageTitle(notionDatabaseID);
// 選択肢を表示
const selectedOption = await vscode.window.showQuickPick(selectoption, {
placeHolder: "Select an article title",
});
if (selectedOption) {
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
const path = require("path");
const selecttitle: string = selectedOption;
const selectpageId = pageIdDictionary[selectedOption];
const file_name = await vscode.window.showInputBox({
title: "What name do you want to save it with?",
value: `${path.basename(activeEditor.document.fileName)}`,
});
if (file_name) {
convertMarkdown(selectpageId, selecttitle, file_name);
}
}
}
})
);
}
これで右下にNotionボタンができ、それを押すとリストに載っていた記事が全て表示されます。
ページのリストを選択することで、文章をダウンロードできるようになっています。
ダウンロードはページ内部を取得し、読み取ったブロックのrich_textを読み取ることで行なっています。
これでスマホで編集しても、VScode上で読み込むことが可能になりました。
笹錆
満足したし、これで完成にするか。
というわけで、VScodeの拡張機能で記事を書くためのあれこれが一元化されました。
なんならこの後も、ダウンロード処理が行われたときにアーカイブとかも自動で更新する機能をつけたり、VScodeからNotionに文章を送信できるようにしましたが、とりあえず記事で解説する部分はここまでとさせてください。
というかほとんど解説していないので、この記事を読んで拡張機能まで作れた人はなんでもできると思います。
ですが、エディタを作ってはい終わり、というわけにはいきません。
元々の目的はこれを使ってクソ記事を量産すること。
早速量産していきましょう。