B-Teck!

お仕事からゲームまで幅広く

ObsidianのVaultをPC/モバイルで同期する方法を模索した

ObsidianをPC/モバイルで使いたいとき、同期方法は大きく3つある:

  1. Obsidian Sync(公式有料サービス)
  2. iCloud/Google Drive などのクラウドストレージ
  3. Git

以前はObsidian Syncを利用していたけど、Obsidian以外でもファイルを参照したい場合などを考えて「Obsidian Git」プラグインを利用するようになった。
ただ、PC版では安定しているものの、モバイル版では後述するような問題があって方法を模索していた。

Obsidian Gitのモバイル版は不安定

Obsidian Gitプラグインはモバイルにも対応している。
しかし、公式READMEに「モバイルサポートは実験的」と明記されている。

これはプラグインの問題というより、Obsidianモバイル版がネイティブGitを使えないことに起因する。
プラグインはJavaScriptでGitを再実装した「isomorphic-git」を使っており、これが原因で制限が多い。

具体的には以下のような問題がある。

  • isomorphic-gitの制限でSSH認証が使えない
  • 大きめのリポジトリでRAM不足によりクラッシュ
  • サブモジュールが使えない
  • リベースができない
  • バッファオーバーフローで無限ループになる場合がある

最初は我慢して使っていたけど、Vaultが大きくなるにつれて不安定になってしまったので代替案を考える必要が出てきた。

GitSyncアプリを使う

そこで見つけたのがGitSyncというスタンドアロンのGit同期アプリ。
ネイティブ実装のJGitを使っているため、前述したようなisomorphic-gitの制限を受けずに安定して動作する。

特徴:

  • 任意のアプリの起動/終了に連動して自動同期
  • クイックタイルから手動同期も可能
  • スケジュール同期(例:15分ごと)もできる
  • オープンソース

注意点

PC/モバイルで同時編集すると競合してめんどくさいので避けたほうがいい。
競合した場合もGitSync上で手動解決自体は可能。

セットアップ手順

詳しくはGitSync Tutorialを参照。
以下は自分がセットアップしたときの流れ。

インストールとクローン

アプリをインストールして、GitHub OAuth認証でログイン。
新しい空のフォルダを作成してリポジトリをクローンし、ObsidianでそのフォルダをVaultとして開いた。

同期方式の選択

GitSyncには2つの同期方式がある:

A. アプリ連動方式

  • Obsidianの起動時にpull、終了時にpush
  • (Androidの場合)アクセシビリティサービスの有効化が必要

B. スケジュール方式

  • 例:15分ごとに自動同期
  • アプリの状態に関係なく定期的に同期

最初はアプリ連動方式を使っていたけど、以下の問題があった

  • 頻度が高すぎる(開く/閉じるたびに同期)
  • 意図しない差分解決が発生
    (起動時のSyncでモバイル側に存在しないファイルが削除判定されて削除されることがあった)

現在は15分に1度のスケジュール同期に落ち着いている。
頻度を下げることで誤検知が減ったことと、起動してないときにも裏でSyncされていることにより、意図しない差分の競合も避けられて安定した。

Obsidian Gitとの競合回避

GitSyncとObsidian Gitが両方動くと競合するので、モバイルではObsidian Gitを動かさないようにする必要があった。

自分の場合、PC・モバイルでObsidianの設定ファイル(.obsidian/)も同期している。
そのため、.gitignoreにObsidian Gitプラグインのディレクトリを追加することで対処した。

.obsidian/plugins/obsidian-git/

こうするとモバイル側では「プラグイン一覧には表示されるけど、ファイルが存在しないので動作しない」状態になる。PC側では普通に動く。

設定ファイルを同期していない場合は、単純にモバイル版Obsidianでプラグインを無効化すればOK。

JCOM回線でObsidianプラグインがダウンロードできなくなったので対処した

ObsidianのVaultを整備していたところ、なぜかCommunity Pluginのインストールや更新などが全て失敗してしまう状態になった。
UI上はタイムアウト表示になるだけで特に原因も思い当たらない。
最初は「配信サーバーの一時的な問題かな?」と思っていたけど、数時間経っても改善する見込みがないので調査した。

原因調査

Webへのアクセスなど、その他インターネットを経由する機能は動作していた。
回線を疑って別回線で試したところ正常に通信できたので、JCOMから貸与されているルーターが原因だと判断した。

Obsidian、JCOMというワードで検索すると以下の記事が引っかかった。
note.com

また、類似事例として以下のような投稿も確認できた。

ざっくり要約すると以下の感じ:

  • JCOMにはGuard機能が存在する
  • GitHub関連のドメインをブロックすることがある
  • 「メッシュWi-Fi」アプリで設定を解除することができる

ちなみに、我が家ではメッシュWi-Fiは契約した覚えがないので、なぜGuard機能が適用されてるのかはよくわからない。

解決方法

1. メッシュWi-Fiアプリのインストール

以下のページからアプリをインストールする。
https://cs.myjcom.jp/knowledgeDetail?an=002000390

2. ブロックされているURLの確認と許可

アプリにパーソナルIDでログイン後、以下の手順でブロックを解除する。
(Androidでしか確認していないので、iPhoneだと細部で違いがあるかも)

  1. ホーム画面から「Guard」のメニューを探す
  2. 「オンラインプロテクション」をタップ
  3. 画面の下部のイベントから解除したいURLを選択する
  4. 「︙」メニューから許可リストに追加する
  5. 「Guardイベント」画面の「︙」から「許可リスト」を表示し、許可したURLが追加されていることを確認

解除したいURLが 手順3 の履歴 にない場合は、手順5 の画面から直接許可リストに追加することも可能。

3. 今回許可したURL

今回は githubusercontent.com がブロックされていた。
これは、GitHubにアップロードされたファイルを匿名化したリンクに用いられるURLで、Obsidianのプラグインもここから配信されている。 docs.github.com

このURLを許可リストに追加後、再度Community Pluginのダウンロードを試したところ、問題なく動作した。

はてなブログのコードブロックをコピーできるようにする

ブログにコードブロックを置いたときに、コピーボタンがほしいとずっと思ってた。
N番煎じすぎるけど一応メモしておく。

コード

CSS

管理画面 > デザイン > デザインCSSに以下を追記する。

:root {
  --btn-bg: #2d2d2d;
  --btn-bg-hover: #3d3d3d;
  --btn-bg-copied: #4a4a4a;
}

pre.code {
  position: relative;
}

.copy-button {
  position: absolute;
  top: 4px;
  right: 4px;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background: var(--btn-bg);
  color: #fff;
  font-size: 14px;
  font-weight: bold;
  cursor: pointer;
  opacity: 0;
  transition: all 0.3s;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

pre.code:hover .copy-button {
  opacity: 1;
}

.copy-button:hover {
  background: var(--btn-bg-hover);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
}

.copy-button:active {
  transform: translateY(2px);
  box-shadow: none;
}

.copy-button.copied {
  background: var(--btn-bg-copied);
}

JavaScript

管理画面 > デザイン > フッタ のHTMLに以下を貼り付ける

<script>
  document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('pre.code').forEach(codeBlock => {
      const button = document.createElement('button');
      button.className = 'copy-button';
      button.textContent = 'Copy';

      button.addEventListener('click', async () => {
        const code = codeBlock.querySelector('code')?.textContent || codeBlock.textContent;
        const text = code.replace(/(Copy|✓ Copied)$/, '').trim();

        await navigator.clipboard.writeText(text);

        button.textContent = '✓ Copied';
        button.classList.add('copied');

        setTimeout(() => {
          button.textContent = 'Copy';
          button.classList.remove('copied');
        }, 2000);
      });

      codeBlock.appendChild(button);
    });
  });
</script>