React初心者がRaycastの拡張機能を作ってみた

## MacのランチャーアプリRaycast
Raycastとは、macOS向けの多機能かつシンプルなランチャーアプリです。
https://www.raycast.com/

ショートカットや数文字のキーボード入力(Alias機能)により、アプリの起動やさまざまなコマンドを実行できます。Alfredをより拡張性を高くしたようなイメージです。
主な機能として以下のようなものがあり、さらにExtensionによって機能を拡張できます。
- アプリケーション起動
- ファイル検索
- クリップボード履歴管理
- 計算機機能
- ブックマーク検索
- クイックリンク作成（クエリを入れられるので、Qiita検索や乗換案内検索もつくれる）
- ターミナルコマンド実行
- スクリプト実行
- ウィンドウマネジメント
- スニペット

またほとんどの機能を無料で使えます。
効率化アプリをいろいろ入れた結果、ショートカットが増えすぎて困っている方は、置き換え検討しても良いかもしれません。

## 今回作ったもの
Raycastの魅力の一つとして、多くの拡張機能が無料で公開されていること、さらには自分でも拡張機能を作成できることがあります。
今回作ったのは、クリップボードにある文字列を変換して、またクリップボードにコピーする、テキスト変換の機能拡張です。
変換内容はドロップダウンで選べるようになっており、空行や行頭の空白削除、変わったところではOutlookの宛先からメールアドレスのみ抽出する変換などを入れてみました。今後も必要に応じて変換内容を追加していく予定です。
これまでエディタの正規表現を使ってやっていたちょっとしたテキスト変換を、もっと手軽にできるようにしようと思って作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115422/d3a76346-2b06-4a23-49c0-d89a487b5381.png)


## 拡張機能の作り方

拡張機能はReactを使った開発になります。
Raycast公式でしっかりした環境を用意されていること、テンプレートやドキュメントがしっかりしていることから、割と初心者でもとっつきやすいつくりになっています。私はReactどころか、JavaScriptもTypeScriptもほとんど使ったことがない状態でしたが、Reactの[公式チュートリアル](https://ja.react.dev/learn/tutorial-tic-tac-toe)と、Raycastの公式ドキュメントを眺めながら、なんとか自作Extensionを作成できました。

### 準備
Extentionの設定でDeveloperのCreateExtensionにチェックがついていることを確認し、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115422/cc076eda-0bb3-d07f-f294-aec0a7f63db5.png)
Raycast上で、Create Extensionと入力する（数文字入力すれば特定されます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115422/f325ae0c-bb1c-558a-647a-a1c931d35a5c.png)


テンプレートの種類やコマンド名が聞かれるので、記入
| 項目名 | 説明 | 入力値 |
|:-:|:-:|:-:|
| Organization  | Store公開時の組織名 | None  |
| Template  | 用意されているテンプレート種類  | Form  |
| Extension Name*  | 名前。Storeや設定時に表示  | お好きに  |
| Description  | 詳細情報。Storeや設定時に表示  | -  |
| Categories  | カテゴリー情報。Storeで使用  | -  |
| Location  | プロジェクトを作成するディレクトリ  | お好きな場所で  |
| Command Name*  | 名前。Raycast検索時に表示される  | 探しやすい文字列で  |
| Subtitle  | 名前の補足。Raycast検索時に表示される  | -  |
| Description  | 詳細情報。設定時に表示される  | -  |

入力後に`cmd + enter`でコードが生成されます。

ターミナルを開き、生成されたディレクトリで`npm install && npm run dev`と入力すると、テンプレートの機能拡張が動くようになっています。
run devとしていることで、ソースコードを書き換えると、自動的にビルド・インストールが実行されます。

::: note
このあたりは公式のチュートリアル https://developers.raycast.com/basics/create-your-first-extension に記述があります。
:::

### カスタム拡張機能の作成
このテンプレートをもとに、開発していきます。
コードの中身は`src/index.tsx`に記載されています。

これを書き換えてカスタムしていきます。
書き換え後のコードを掲載します。


```react
import { ActionPanel, Form, Icon, showToast, Toast, Action, Clipboard } from "@raycast/api";
import { useState, useEffect } from "react";

export default function Command() {
  //入力文字列と出力文字列を保持できる形で用意
  const [input, setInput] = useState<string>('');
  const changeInput =(newValue: string) => {
    setInput(newValue);
  };
  const [output, setOutput] = useState<string>('ここには結果が出力される');
  const changeOutput =(newValue: string) => {
    setOutput(newValue);
  };

  //初回のみ実行（クリップボードの内容をインプットフォームに読み込み）
  useEffect(() => {
    InputCopiedText(changeInput);
  }, []);

  return (
    <Form
      actions={
        <ActionPanel>
          <RegAction changeOutput={changeOutput} />
        </ActionPanel>
      }
    >
      <Form.TextArea id="orgText" title="Original Text" placeholder='変換したいテキストを入れてください' value={input} />
      <Form.Dropdown id="regNo" title="RegEx list" storeValue>
        <Form.Dropdown.Item value="0" title="Outlookからメアド抽出->カンマ連結" />
        <Form.Dropdown.Item value="1" title="空行削除1" />
        <Form.Dropdown.Item value="2" title="行頭空白削除" />
      </Form.Dropdown>
      <Form.Description title="結果出力" text={output} />

    </Form>


  );
}

function RegAction({changeOutput}) {
  async function handleSubmit(values: { orgText: string; regNo: number; }) {

    if (!values.orgText) {
      showToast({
        style: Toast.Style.Failure,
        title: "変換したいテキストを入れてください",
      });
      return;
    }

    const toast = await showToast({ style: Toast.Style.Animated, title: "変換中" });

    // ここに正規表現と置換のリストを作成する
    const regExs =[
      {
        reg:      /(.+?) <(.+?)>((; )|$)/g,
        replace:  "$2,"
      },
      {
        reg:      /^\n/gm,
        replace:  ""
      },
      {
        reg:      /^( |\t|　)+/gm,
        replace:  ""
      }
    ];

    // ここで正規表現に変換
    const i = values.regNo;
    const afterText = values.orgText.replace(regExs[i].reg, regExs[i].replace);
    changeOutput(afterText);

    //結果をクリップボードにコピーする
    await Clipboard.copy(afterText);

    //正常終了したことをステータス表示
    toast.style = Toast.Style.Success;
    toast.title = "Success";
    toast.message = "Copied text to clipboard";

  }

  return <Action.SubmitForm icon={Icon.Upload} title="RegEx Text" onSubmit={handleSubmit} />;
}

//クリップボードの内容をインプットフォームに入れる関数
async function InputCopiedText(changeInput) {
  const text = await Clipboard.readText();
  changeInput(text);
  return;
}

```

動作画面はこちら
![RegForm.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115422/c5143917-41df-15f7-0660-6d642032c91b.gif)


UIの部品はテンプレートで用意されているものをそのまま使い、そこに入出力するだけの簡単な内容です。
ただReactが初めてだったこともあり、更新タイミングのコントロールをどうするのか、propなどをどう渡せばよいかなどで結構悩みました。
書き方はかなり拙いですが、とりあえず動くものができたので満足です。

Reactを試す環境としても、CSSなどで悩まずに簡単に動作するものを作れるので、初心者の勉強としても良さそうです。

ほぼ同じものはスクリプトでもできそうな気もしますが、クリップボードの操作やメニューの操作などもRaycastが用意してくれているので、ちょっとした効率化アプリをつくる環境として重宝しそうです。

もっと精進して、さまざまなアプリの動作をほとんどraycastで行えるようになりたいなあ。

## 参考にしたページ

### 比較的シンプルなもの
https://zenn.dev/babyjob/articles/d1c95525c0353d


DevelopersIO | 自作Raycast Extensionでお手軽生産性アップ

https://dev.classmethod.jp/articles/create-my-raycast-extension/


### 解説が丁寧
https://goodpatch-tech.hatenablog.com/entry/raycast-extension-oauth2

### 今後目指したい
https://qiita.com/nabekou29/items/b97e0cf049fb3eb16064

https://zenn.dev/ryo_kawamata/articles/965ef95ad8bb0d

https://zenn.dev/potsbo/articles/f7f235ca7c7577

### 各種公式ページ
最近は公式ページの解説がとても充実していて本当に助かります。
単に詳しいだけでなく、初心者向けの解説が用意されているのがありがたいですね。

https://ja.react.dev/learn

https://developers.raycast.com/basics/create-your-first-extension



## 最後に
先人のみなさんのおかげで、やってみようと思えました。
ありがとうございます。
初心者の自分でも動くものが作れたということで、誰かの後押しになれればよいかと思い、記事を公開しました。