ScrapboxのUserScriptテンプレートまとめ

# はじめに
[Scrapbox](https://scrapbox.io)のUserScript（JavaScriptをカスタマイズする機能）について、先人のコードからテンプレートを抜き出してまとめてみました。
私みたいにUserScriptを自分で触ってみたい！ という人の敷居を低くするため、あまり小難しくならないよう（自分自身理解が追いつかないので）、jQueryによる平易なコードを心掛けています。

## 編集履歴
2020/3/15 番外テンプレートの変数宣言「var」部分を変更

# 準備
UserScriptの利用方法については公式でまとめられていますので、そちらを参考にされてください。
[UserScript](https://scrapbox.io/help-jp/UserScript)

#### つまずいたところ
**自分のページ**
>[自分のページ]に`code:script.js`という[コードブロック記法]を書くと
> その中身がプロジェクトのロード時に実行されます

と書いてあるのですが、自分のページを作っていなくて、しばらくハマりました。
公式の説明→[自分のページ](https://scrapbox.io/help-jp/%E8%87%AA%E5%88%86%E3%81%AE%E3%83%9A%E3%83%BC%E3%82%B8)（Scrapboxのリンクをたどれば行きつきますが・・・）

ちなみに複数プロジェクトを作っても、「Username」（自分のページのタイトル）は、同じでなければならないようです。（2019/01/25現在）
プロジェクト単位で自分のページのタイトルを変えようとしたら、他のプロジェクトでUserScriptが効かなくなって焦りました。

**フレームワーク**
つまずいたところというよりは、勘違いで時間を使ったところです。
フレームワークなしにJavaScript書くのはつらいと思ってUserScriptにフレームワークを適用する方法を探したのですが、**Scrapboxは元々jQueryを読み込んでました。**（デベロッパーツールでheadの中を見たら確認できます）
なのでjQueryの記法は、初めからUserScript内で利用することができます。

ちゃんと動作確認していませんが、別のフレームワーク(CDNに限りますが・・・)を読み込みたい場合は以下のサイトが参考になると思います。
[JavaScriptのファイル内でjQueryのCDNを読み込む](https://web-guided.com/post-360)

今回は元々読み込まれているjQueryを活用します！！

# テンプレート

### テンプレート（１） : PopupMenuにbuttonを追加する。
こちらは、Qiitaの記事[Scrapboxで遊ぶ](https://qiita.com/progfay/items/5a81e130fc36291990d4)でも紹介されている内容です。

```script.js
 scrapbox.PopupMenu.addButton({
   title: 'タイトル',
   onClick: 選択したテキストを引数として変換したテキストを返す関数
 })
```

#### 例
Scrapboxに記入した文字を赤文字にする`PopupMenu`を作ってみます。
まず文字色を設定する[UserCss](https://scrapbox.io/help-jp/UserCSS)を用意します。（UserCssについては、今回は題材ではないのでリンクを参照してください）
文字色を変更する設定は以下のリンクのように[拡張記法](https://scrapbox.io/scrasobox/%E6%8B%A1%E5%BC%B5%E8%A8%98%E6%B3%95%E3%81%8C%E3%81%8D%E3%81%9F%EF%BC%81)で記述できるように設定します。
[文字色](https://scrapbox.io/forum-jp/%E6%96%87%E5%AD%97%E8%89%B2)

```style.css
 .deco-\!{
     color: #f00;
   }
```

上記のコードを自分のページの`style.css`に書いて再読み込みを行えば,
`[! テキスト]`と書いて「テキスト」を赤文字にできます。
この`[! ]`の記述の手間を（１）のテンプレートを使って、`PopupMenu`のボタンにするとこんな感じになります。

```script.js
  scrapbox.PopupMenu.addButton({
   	title: 'red',
   	onClick: text => {
      	return '[! ' + text + ']';
      }
    })
```

<img width="450" height="200" alt="画面収録 2019-01-22 0.30.34.mov.gif" src="https://qiita-image-store.s3.amazonaws.com/0/277737/ce2bced6-4e89-3544-bf1b-fabf23963d7d.gif">

####ちょっとだけ補足
そもそも`scrapbox.PopupMenu.addButton`ってどこから出てきたんだよ！って話ですが、どうやらScrapboxのサイトが読み込んでいる`index.js`に用意されている関数のようです（デベロッパーツールで該当ファイルを探して`addButton`などで検索すると引っかかります）

`index.js`を理解するのはちょっとつらい・・時間がかかるので、Scrapboxさんが用意してくださってるんだってことで、ありがたく使わせてもらいましょう。
ただ、個人的にも根拠がどこにあるか気になったので補足しときます。

### テンプレート（２） : PageMenuにItemを追加する。
<img width="1096" alt="スクリーンショット 2019-01-22 1.04.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/277737/310e355e-edfd-9558-c6e5-7f0ef958afaa.png">

デフォルトの`PageMenu`は上記の赤丸で囲ったアイコン、
`Item`はそのアイコンを押したときのMenuの要素を意味しています。
テンプレートは次の通りです。

```script.js
  scrapbox.PageMenu.addItem({
    title: 'タイトル',
    //image: 'GyazoのURL（省略可能）'
    onClick: クリック後の動作を記述
  })
```

#### 例
赤文字を大きくするメニュー項目を作成します。
style属性をjQueryでどうこう扱うのは煩雑になるので、さっきと同じ要領で`style.css`で文字を大きくする以下のclassセレクタを用意しておきます。

```style.css
  .large-font {
  	font-size: xx-large;
  }
```

UserScriptはこんな感じ。

```script.js
 	scrapbox.PageMenu.addItem({
        title: () => '赤文字を大きくします',
        onClick: () =>(function(){
            //セレクタで指定した要素の存在チェック
            if($('span[class="deco-\!"]')[0]){
            		$('span[class="deco-\!"]').addClass('large-font');
            }else{
            		alert("カード上に赤文字がないか、もう大きな文字になっています");
            }
        })()
      })

```

<img width="450" height="200" alt="画面収録 2019-01-22 19.52.01.mov.gif" src="https://qiita-image-store.s3.amazonaws.com/0/277737/1c3bff99-797c-f8bc-3629-b1e4851ecd8e.gif">

`$('span[class="deco-\!"]')`でテンプレート（１）の`style.css`で設定した拡張記法を指定しています。
ちなみにテンプレート（１）はテキストを返すとその変更が保存されるように作られているようですが、この`PageMenu`の場合は特別なことをしない限り再読み込みを行うと、効果は消えます。（普通のjsの挙動と一緒です）

### テンプレート（３）　:　PageMenuにMenuを追加する。
デフォルトの`PageMenu`ではなく、別のMenuを作りたい（別のアイコンをつけたい）場合。

```script.js
 scrapbox.PageMenu.addMenu({
 	title: 'タイトル',
 	// rawを付与して画像にリダイレクトする。
 	image: 'GyazoのURL/raw'
 })
 
 //テンプレート②の記法と組み合わせる
 scrapbox.PageMenu('addMenuでtitleに記載した文字列').addItem({
     title: 'タイトル',
     //image: 'GyazoのURL（省略可能）'
     onClick: クリック後の動作を記述
   })
```

#### 例
テンプレート（２）の例で紹介した、赤文字を大きくするUserScriptを上記のテンプレートで、新たなMenuアイコンで表示させてみます。

```script.js
  	scrapbox.PageMenu.addMenu({
        title: 'large',
        // 好きな画像
        image: 'https://gyazo.com/4cほにゃらら・・・/raw'
     })
  	
  	scrapbox.PageMenu(`large`).addItem({
        title: () => '赤文字を大きくします',
        onClick: () =>(function(){
            //セレクタで指定した要素の存在チェック
            if($('span[class="deco-\!"]')[0]){
           		$('span[class="deco-\!"]').addClass('large-font');
           	}else{
           		alert("カード上に赤文字がないか、もう大きな文字になっています");
          	}
         })()
      })

```

<img width="450" height="200" alt="画面収録 2019-01-22 20.44.15.mov.gif" src="https://qiita-image-store.s3.amazonaws.com/0/277737/2a72a3e7-3769-125a-34df-cb8cf7ff0ba3.gif">

####画像
テンプレートにチラっと書いていますが、Scrapboxで画像を共有する場合は、GyazoのURLを貼ることを前提としています。（URLを貼るだけで反映される）
公式の画像に関する説明→[画像をアップロードする](https://scrapbox.io/help-jp/%E7%94%BB%E5%83%8F%E3%82%92%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B)

テンプレート（３）では`/raw`をURLにくっつけて、画像にリダイレクトさせます。`/raw`付けてないと、私の確認した限りではうまく表示されませんでした。

####活用方法
今回の例は、実用的ではないと思うので、テンプレート②③の活用方法のリンクをいくつか貼っておきます。
[テンプレートを使ってページを作成（UserScript版）](https://scrapbox.io/scrasobox/%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E6%88%90%EF%BC%88UserScript%E7%89%88%EF%BC%89)
[Tweet_Menu](https://scrapbox.io/shokai/Tweet_Menu)
[ページの見出しを作るUserScript](https://scrapbox.io/scrasobox/%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E6%88%90%EF%BC%88UserScript%E7%89%88%EF%BC%89)

# テンプレート番外編
ここからは`scrapbox`で始まる備え付けのものではありません。
`PopupMenu`や`PageMenu`以外にもただのボタン、、、というものが欲しくて調べた結果になります。

### 番外テンプレート（１） : アイコンをボタンにする
[アイコン記法](https://scrapbox.io/help-jp/%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E8%A8%98%E6%B3%95)→`[カードのタイトル.icon]`というものがScrapboxにあるのですが、そのアイコンをボタンにします。
実は、「Scrapbox ボタン」で検索するとテンプレートにしなくても素晴らしすぎるChrome拡張機能（非公式）が出てきます。

素晴らしすぎるプラグイン「ScrapScripts」の説明
[daiiz製のChrome拡張機能](https://scrapbox.io/customize/daiiz%E8%A3%BD%E3%81%AEChrome%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD)

プラグインを利用したアイコンボタンの使い方の説明
[Scrapboxのアイコンをボタンとして使う](https://scrapbox.io/customize/Scrapbox%E3%81%AE%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%82%92%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E4%BD%BF%E3%81%86)

ただ、今回は「UserScriptを自分で触る」ことを目的としているので、
公開されているソース→https://github.com/daiz713/ScrapScripts
を、使い勝手の良さを全てないがしろにして、簡単なテンプレートとして考えてみます。

**アイコンをボタンにするテンプレート**

```script.js
  	const $appRoot = $('#app-container')
  	$appRoot.on('click','img[class="icon"]',e =>{
  		const $icon = $(e.target).closest('img[class="icon"]');
  		const iconName = $icon.attr('title');
  		if(iconName === 'アイコンの画像が貼り付けられたページの名前'){
  			〜〜処理の内容〜〜
  			// 親要素のaタグを無効化
  			return false;
  		};
  	})
```

#### 説明（というより長い考察なので読み飛ばしてもらっても大丈夫です）
まず`$('#app-container')`ですが、このセレクタが指定している要素は、body要素に次いで一番外側にあるdiv要素になります。つまり、Scrapboxの中でも一番rootの位置を指します。

セレクタでいうと`img[class="icon"]`という指定もありますが、これはアイコン記法で書いて表示された画像を指しています。（逆にいうと、アイコン記法で書いて表示された画像には、class属性に必ずiconという値が付与されるということになります）

ここで、なぜ
**`$('img[class="icon"]').on('click', e => ~~)`**
と書かないのか？（そう書かないとうまくいかないのか？）という疑問が出てきます。

デベロッパーツール等で色々見た結果。
**`Scrapboxはカードをクリックする度に要素を書き換える`**という特徴があるから、みたいです。

つまり、どの画面にも存在している`$('#app-container')`を指定して、アイコン記法が存在するカードをクリックしたとき追加された`img[class="icon"]`にクリックイベントを適用する書き方が、上記のテンプレートになるのです。
[jQuery 便利なonを使おう（on click)](https://qiita.com/shizuma/items/d561f37f864c3ebb5096)
のonの有用性で述べられている`追加要素へのイベント`を利用した訳ですね。
（この理屈で試してみると、`$('#app-container')`でなく`$('body')`でも動作しましたが`$('#app-container')`の方がセレクタの優先度が高く特別に用意されているので、そっちでテンプレートは書きます）


あとの、`$(e.target).closest('img[class="icon"]')`は、同じカード内の別のアイコンを動作させないために必要です。
`$icon.attr('title')`で取得できるのはiconの画像をセットしているカードタイトルになります。

#### 例
「Hello World!」のアラートを出すアイコンを作ってみます。
アイコン記法のための画像を用意。（カードのタイトルは`hello-world`）
<img width="1080" alt="スクリーンショット 2019-01-24 20.48.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/277737/897ef5cb-b9de-b879-c241-d7a4854801a4.png">
UserScriptはこんな感じ。

```script.js
    const $appRoot = $('#app-container')
    $appRoot.on('click','img[class="icon"]',e =>{
     	const $icon = $(e.target).closest('img[class="icon"]');
     	const iconName = $icon.attr('title');
     	if(iconName === 'hello-world'){
     		alert('Hello World!');
     		// 親要素のaタグを無効化
     		return false;
     	};
     })
```

<img width="450" height="250" alt="画面収録 2019-01-24 20.59.50.mov.gif" src="https://qiita-image-store.s3.amazonaws.com/0/277737/743cd05d-6949-3a77-7376-a679f4c89a12.gif">

参考にしたプラグインの使い方でも同じ処理が説明されています。
[Scrapboxのアイコンをボタンとして使う](https://scrapbox.io/customize/Scrapbox%E3%81%AE%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%82%92%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E4%BD%BF%E3%81%86)
比べて分かると思いますが、プラグインの方がソース管理できて使い勝手もいいです。
よっぽど自分で書くことにこだわらない限りはこちらを使った方がいいと思います。

### 番外テンプレート（２） : Gyazoの画像をボタンにする
番外テンプレート（１）は、アイコンを作成するために画像専用のページを作る必要がありました。画像だけのページが増えるのがちょっと嫌だなっと思いまして、番外テンプレート（１）を改変してGyazoの画像をボタンにするテンプレートを考えてみました。

**Gyazoの画像をボタンにするテンプレート**

```script.js
    const $appRoot = $('#app-container');
 	// Gyazoのurl : https://gyazo.com/[この部分を記載]
 	const GYAZOID = '5fcほにゃらら・・・';
 	$appRoot.on('click','img[class="image"]',e =>{
     	const $gyimage = $(e.target).closest('img[class="image"]');
     	const gyazoID = $gyimage.attr('src').split('/')[3];
     	if(gyazoID === GYAZOID){
     		〜〜処理の内容〜〜
     		// 親要素のaタグ(Gyazoへの遷移)を無効
     		return false;
     	};
     })
```
勝手に`GYAZOID`と名付けているのは、`https://gyazo.com/`に続く部分です。
デベロッパーツールでGyazo周辺を眺めて、番外テンプレート（１）との違いを反映させた形になります。

#### 例
赤文字を空欄にして、暗記問題を出題するUserScriptを作ってみます。

```script.js
 const $appRoot = $('#app-container');
 //好きなGyazoの画像
 const GYAZOID_QUESTION = 'b79ほにゃらら・・・';
 const GYAZOID_ANSWER = '1faほにゃらら・・・';

     $appRoot.on('click','img[class="image"]',e =>{
       	const $gyimage = $(e.target).closest('img[class="image"]');
       	const gyazoID = $gyimage.attr('src').split('/')[3];
       	if(gyazoID === GYAZOID_QUESTION){
       		// セレクタで指定した要素の存在チェック
       		if($('span[class="deco-\!"]')[0]){
       			$('span[class="deco-\!"]').css('visibility','hidden')
       		}else{
       			alert('赤文字がないので、問題を作れませんでした。')
       		}
            // 親要素のaタグ(Gyazoへの遷移)を無効
       		return false;
       	};
       	
       	if(gyazoID === GYAZOID_ANSWER){
       		// セレクタで指定した要素の存在チェック
       		if($('span[class="deco-\!"]')[0]){
       			$('span[class="deco-\!"]').css('visibility','visible')
       		}else{
       			alert('答え合わせを行う要素はありませんでした。')
       		}
            // 親要素のaタグ(Gyazoへの遷移)を無効
       		return false;
       	};
      })
```
（いきなり上記の例を試したい場合は、テンプレート（１）で載せた`style.css`も記述してください）
結果はこんな感じ。
<img width="500" height="280" alt="画面収録 2019-01-24 21.42.17.mov.gif" src="https://qiita-image-store.s3.amazonaws.com/0/277737/0a507f4c-0a8c-9db9-0b7e-3d90f6a157cd.gif">

Gyazoの画像サイズは、以下リンクの`style.css`で変更可能です。
[画像の表示サイズを[**　]で設定できるようにする](https://scrapbox.io/customize/%E7%94%BB%E5%83%8F%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%5B**%E3%80%80%5D%E3%81%A7%E8%A8%AD%E5%AE%9A%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B)

#### 番外テンプレート（２）の欠点
<img width="361" alt="スクリーンショット 2019-01-25 0.53.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/277737/e6e55311-24b2-c356-6dd7-07ac9080db0e.png">

番外テンプレート（２）では、カードに最初に貼ったGyazoの画像が、あたかもそのカードの主題であるかのように表示されてしまいます。何度も使いたい処理の場合は同じ画像が羅列されてしまうので避けた方がいいでしょう。
限られた処理なら番外テンプレート（２）もありかな？　とは個人的に思っています。（公式としては、画像は画像でカードを作る。という思想がありそうです）
# まとめ
UserScriptを触ってみて、Scrapboxに特化した関数やらセレクタがあって、まあまあクセがあるな・・・というのが正直な感想です。
まとめにしては、先人のコードやデベロッパーツールを使った考察が多くなってしまいましたが、使い方は説明してもコードの中身を説明している情報は少なかったので、こういう記事もあっていいかと開き直っています。

Scrapbox毎日のように使って、めちゃくちゃ好きなので、このまとめが新たなカスタマイズ作成の出発点にでもなってもらえたら嬉しいです！

# 主に参考にしたScrapbox
[Scrapboxヘルプ](https://scrapbox.io/help-jp/)
[Scrapbox研究会](https://scrapbox.io/scrapboxlab/)
[橋本商会](https://scrapbox.io/shokai/)
[daiiz](https://scrapbox.io/daiiz/)