freee会計のファイルボックスのファイル詳細画面にショートカットキーを実装し入力を楽にしてみた

# これは何？
いつも経理にfreee会計を使っています。どうもfreee会計にはショートカットキーがなさすぎてマウスでの操作が多く、特に証憑から入力する経理作業に時間がかかっていました。
そこで「ショートカットキーが無ければ作ればいいじゃない！」の精神で実装してみました。

# 実装
使うのは[Shortkeys (Custom Keyboard Shortcuts)](https://chromewebstore.google.com/detail/shortkeys-custom-keyboard/logpjaacgmcbpdkdchjiaagddngobkck)というChrome拡張機能です。あるサイトにおいて任意のキーが押されたときに任意のJavascriptを走らせることができるので、これを使います。freee会計はdata-testid属性などが付けられていてとても要素を取り出しやすい✨
なお、この拡張機能のコメント欄に拡張機能が動かないという書き込みがありますが、以下を行うことで動作することを確認しています。

1. 拡張機能の設定変更後は保存する
1. 拡張機能の設定変更後は対象のサイトを再読込する

## 前の証憑へ移動する
left keyが押されたときにこのスクリプトを実行するようにします。先頭まで戻ってきたときはボタンがdisabledになるので押さないようにします。

```javascript:left key
(() => {
const button = document.querySelector('button[aria-label="前のファイルへ移動"]');
if(button.getAttribute('disabled') !=='disabled'){
button.click();
}
})();
```

## 次の証憑へ移動する
right keyが押されたときも同じ要領です。
```javascript:right key
(() => {
const button = document.querySelector('button[aria-label="次のファイルへ移動"]');
if(button.getAttribute('disabled') !=='disabled'){
button.click();
}
})();
```

## 「データ詳細 / 取引登録 / コメント」のタブを遷移させる
ここでは循環バッファを用いて現在のアクティブなタブが先頭や末尾であっても同じキー操作で次のタブへ移動できるようにしました。循環バッファとは1,2,3と要素があったときに現在3ならその次が1になり、現在が1ならその前は3になる配列のことです。
ちなみにChatGPTを用いてほとんど生成しました。
していることは

1. 循環バッファにタブを投入し
1. 現在のタブを取得し
1. 現在のタブの前/次のタブをクリックする

です。即時実行関数を使用して何回繰り返し実行しても問題が起きないようにステートレスにしました。

### この証憑の次のタブへ

```javascript:ctrl+right key
(() => {
class CircularBuffer {
    constructor(capacity = 256) {
        capacity = this._pow2(capacity);
        this.data = new Array(capacity);
        this.top = 0;
        this.bottom = 0;
        this.mask = capacity - 1;
    }

    // バッファのサイズを2の累乗に揃えるためのヘルパー関数
    _pow2(n) {
        n--;
        let p = 0;
        while (n !== 0) {
            n >>= 1;
            p = (p << 1) + 1;
        }
        return p + 1;
    }

    // 現在の要素数を取得するプロパティ
    get count() {
        let count = this.bottom - this.top;
        if (count < 0) count += this.data.length;
        return count;
    }

    // 指定したインデックスの要素を取得
    get(i) {
        return this.data[(i + this.top) & this.mask];
    }

    // 要素を末尾に追加するメソッド
    insertLast(elem) {
        if (this.count >= this.data.length - 1) {
            this._extend();
        }
        this.data[this.bottom] = elem;
        this.bottom = (this.bottom + 1) & this.mask;
    }

    // IDで検索し、次または前の要素を返す共通メソッド
    _findAdjacentById(id, direction) {
        let index = null;

        // IDが一致する要素のインデックスを見つける
        for (let i = 0; i < this.count; i++) {
            if (this.get(i) && this.get(i).id === id) {
                index = i;
                break;
            }
        }

        // 要素が見つからない場合は null を返す
        if (index === null) {
            return null;
        }

        // 次または前のインデックスの要素を返す（循環バッファなので、末尾の場合は先頭に、先頭の場合は末尾に戻る）
        const adjacentIndex = (index + direction + this.count) % this.count;
        return this.get(adjacentIndex);
    }

    // IDで検索し、次の要素を返すメソッド
    findNextById(id) {
        return this._findAdjacentById(id, 1);  // 1 は次の要素
    }

    // IDで検索し、前の要素を返すメソッド
    findPrevById(id) {
        return this._findAdjacentById(id, -1); // -1 は前の要素
    }

    // 配列を拡張するメソッド
    _extend() {
        const newData = new Array(this.data.length * 2);
        let i = 0;
        for (let j = this.top; j != this.bottom; j = (j + 1) & this.mask) {
            newData[i++] = this.data[j];
        }
        this.top = 0;
        this.bottom = i;
        this.data = newData;
        this.mask = newData.length - 1;
    }
}

const linkedList = new CircularBuffer(3);
linkedList.insertLast({id:"details-tab",value: document.querySelector('button[data-testid="details-tab"]')});
linkedList.insertLast({id:"deals-tab",value:document.querySelector('button[data-testid="deals-tab"]')});
linkedList.insertLast({id:"comments-tab",value:document.querySelector('button[data-testid="comments-tab"]')});

const selectedTabId = document.querySelector('button[aria-selected="true"][role="tab"]').getAttribute("data-testid");
linkedList.findNextById(selectedTabId).value.click();
})();
```

### この証憑の前のタブへ
上のコードの最後のコードを下記にするだけ。
```javascript:ctrl+left key
//　中略・・・

linkedList.findPrevById(selectedTabId).value.click();
})();
```

# 設定ファイル
[Shortkeys (Custom Keyboard Shortcuts)](https://chromewebstore.google.com/detail/shortkeys-custom-keyboard/logpjaacgmcbpdkdchjiaagddngobkck)は設定ファイルでこれらのスクリプトをインポートすることができます。設定ファイルはこちらに置きましたのでお使いください。
https://gist.github.com/kassyi/9f22675a67fe9d3b1e6137a946d45d77

# 課金でもっとfreee会計を便利にする
こんな感じの左手デバイスのプログラムブルキーボードがありまして。たとえば`Ctrl+left`や`Ctrl+left+k`なんてこともできます。waitもかけれるようです。これとこれらのプログラムを組み合わせたら、最強…ですね！！ぜひお手にとって見てください。

[<img width="390" src="https://m.media-amazon.com/images/I/71Em+GK8CsL._AC_SX425_.jpg">](https://amzn.to/3YIrEoC)
[[MH-Device] MH-Delta ジョイスティック・ホイール付き41キー片手ゲーミングキーボード 片手 キーパッド 左手キーボード (スタンダード - 黒)](https://amzn.to/3YIrEoC)