LoginSignup
4
3

More than 1 year has passed since last update.

Notionを操作するシンプルなRaycast 拡張機能を作って色々学んだ

Last updated at Posted at 2023-01-10

こんにちは、Androidエンジニア志望のHalkoです。

最近、Raycastというランチャーツールを使用し始めました。
Raycastは自分で拡張機能を保存できます。
そこで、普段Notionに記録している作業記録を、Raycastから保存できるような拡張機能を作ってみました。

準備

インストールすべきもの

  • Raycast 1.26.0 またはそれ以上のバージョン
  • Node.js 16.10 or またはそれ以上のバージョン
    nvmのインストールが推奨されているようです
  • Visual Studio Codeをコードエディタとして使用します。

新しく拡張機能を作る

代替テキスト

Raycastを開いて、create Extensionと入力します

作成画面が出てくるので、必要最低限の項目を埋めます。

image.png

  • テンプレートには、Formを指定

image.png

  • Extension Name, Location, Command Nameが必須項目

Create Extensionのコマンドを実行すると、テンプレートに沿った拡張機能ファイルが作成されます。
Open Manage Extensions というコマンドが出てきます。
実行すると、VSCodeが自動的に開きま拡張機能の一覧が確認できます。
代替テキスト
EnterでVsCodeが自動的に開きますので、
ここから、テンプレートファイルを編集していきます。

ターミナルでの準備

開かれたファイルにある、index.tsxのimportに記載されている、@raycast/apiをインストールします。
また、今回はNotionのAPIも使用したいので、一緒にインストールしておきます。

ターミナルを開いて、

$ npm install @notionhq/client
$ npm install @raycast/api

また、拡張機能を実行できるようにするため

$ npm i
$ npm run dev

これで、ファイルを保存するたびに、自動でビルドして、変更された拡張機能を実行してくれるようになります。

いざ、コードの記入

今回は、

  • NotionDBから一行分のデータを得る
  • UIに表示する
  • NotionDBの行をアップデートする
  • NotionDBに新しく行を挿入する

というのが主な機能です。

出来上がったものは、こんな感じです。

アクティビティ開始
part 1.1.gif
アクティビティ終了
part 2.gif|

APIを触るのも、TypeScriptを書くのも初めてだったので、新しく学んだことだらけでした。

全てはここに書ききれないので、最終的なindex.tsxのコードを挙げておきます。

import { Action, ActionPanel,  Form ,LaunchProps} from '@raycast/api'
import { useRef} from "react";
import { getUIDataFromNotion } from './getUIDataFromNotion'; 
import { usePromise } from "@raycast/utils";
import { EntryValues } from './EntryValues';




const Demo = () => {
  const abortable = useRef<AbortController>();
  const { isLoading, data, revalidate } = usePromise(
    async () => {
    const result = await getUIDataFromNotion()
      return result;
    },[],
    {abortable}
  );
  
  console.log("called")
  return (
    <Form 
      key={"form"}
      isLoading ={isLoading}
      actions={
      <ActionPanel
      key={"actionPanel"}
      >
        <Action.SubmitForm
        key={"Action.SubmitForm"}
    
          title={data?.submitTitle}
          onSubmit={(values: EntryValues) => {
            data?.doOnSubmit(values)
          }}
        />
      </ActionPanel>
    }>
    <Form.TextArea
        key={"Form.TextArea"}
        id="contentField"
        title={data?.contentTitle}
        value = {data?.openedRowData?.comment}
      />
      <Form.Dropdown key={"Form.Dropdown"} id="tag" title="activity tag">
        {data?.tagList.map((item:string)=>(
          <Form.Dropdown.Item key={"Form.Dropdown.Item"+item} value={item} title={item}  />
        ))}
      </Form.Dropdown>
      <Form.DatePicker 

      key={"Form.DatePicker"}
      id="dateTime" 
      title={data?.dateTitle} 
      defaultValue={new Date()}
       />
    </Form>
    );
  
  

  
};

 export default function Command(props: LaunchProps<{ draftValues: EntryValues }>) {
    return Demo()
}

解説のポイントは、

  • usePromise
  • LaunchProps

です

usePromise

Notionからデータを得る、というように、
非同期処理でデータを得たい場合、返り値はPromise<欲しいクラス>となるようです。

async function getNotionData():Promise<NotionData>{
  //Notionからデータを取ってくる
  const response = await notion.databases.retrieve({database_id:databaseId})
  const notionData = getNotionDataFromResponse(response)
  return new Promise((resolve,reject)=>
      resolve(notionData)
    )
}

Promise<>の中のクラスにアクセスしたい場合、
await という処理をくっつけなければなりません。

厄介なことに、await処理は、非同期処理の中でしか呼べません。

//NotionDataにはアクセスできないので、エラーになる
const data:NotionData = getNotionData()

//awaitをつけると大丈夫
const data:NotionData = await getNotionData()

//非同期処理でないCommand()でawaitを呼ぶと、エラーになる
function Command(){
    const data:NotionData = await getNotionData()
}

最終的にRaycastから呼ばれる、Command() のなかで、NotionDataを使いたいわけですが、
このCommand()は非同期処理(async function)にすることができません。

そこで、Raycastが提供しているusePromiseユーティリティを使用すると、
Command()を非同期処理にすることなく、awaitを使えるようになります!

  const abortable = useRef<AbortController>();
  //dataにアクセスすればNotionDataにアクセスできる!
  const { isLoading, data, revalidate } = usePromise(
    async () => {
    const result = await getNotionData()
      return result;
    },[],
    {abortable}
    );

非同期処理が終わり、Promiseデータが返ってきた瞬間に、UIを更新してくれます。

LaunchProps

これは非常にシンプルな話です。
前述の通り私は、

Raycastの画面上に記入したテキストや日付を、Notionに送る機能

を作りたかったです。

そのためには、Notionにデータを送る、というコマンドが呼ばれた時、
UIに記入されたデータが必要でした。

そこで、Command()の引数に、LaunchPropsを追加しました。

  interface EntryValues {
    dateTime: Date ;
    contentField: string;
    tag: string;
  }
  export default function Command() {
    return Demo()
  }
//変更後
 export default function Command(props: LaunchProps<{ draftValues: EntryValues }>) {
    return Demo()
 }
  

EntryValuesは自分で作成したinterfaceなので、自分が作ったUIに応じて変更してください。
LaunchPropsを追加した後、
UIのデータにアクセスしたい際のコマンドを書き換えます。

 <Action.SubmitForm
        key={"Action.SubmitForm"}
          title={data?.submitTitle}
          onSubmit={
            //EntryValuesをとってくる
           (values: EntryValues) => {
            //valuesの中には、記入したデータ等が入るようになる。
            doSomethingWithEntryValues(values)
          }}
     />

これで、UIで記入したデータ等を利用できるようになります。

参考リンク

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3