そういえば、書く場所へは共有できなかった。Qiita・Zenn・note・Medium・アメブロへの共有ボタンを足しました
作成: / 公開: / 内容更新:
執筆:tamito0201 / 掲載・運営:プロマリ
記事の下の共有欄に、X や LINE はあるのに、読んだ人が自分の記事で紹介する Qiita・Zenn・note・Medium・アメブロへの入口がありませんでした。note は公式の共有入口へ、入口の無い4つは「題名と URL をコピーして投稿画面を開く」新しい動きで足しました。4.2.0 では、貼った先でそのまま記事のカードになるよう、共有先ごとの書き方をそろ…
note は、カードを1枚差し込むだけで足りた
前のページで、5つのうち note にだけ、公式の共有の入口があると分かりました。入口があるなら、話は簡単です。このプラグインは、X やはてなブックマークと同じように、「入口の URL に、記事の情報を決まった名前で付けて開く」という動きをすでに持っているからです。この動きを、プラグインの中では open と呼んでいます。note は、この open の共有先として足せます。
では、足すために何をしたのか。実は、新しいファイルを1つ置いただけです。ほかのコードは、1行も開いていません。
このプラグインでは、共有先を1つにつき1つのTOMLファイルで宣言します。destinations というフォルダに、copy.toml・email.toml・facebook.toml……と、共有先の名前のファイルが並んでいて、それぞれに「どの入口へ、どんな名前で、何を送るか」が書いてあります。カードの整理箱に、1社につき1枚ずつカードを立てているようなものです。note のために足したのは、そこへ差し込む1枚のカードでした。
# note's "Write on note" screen, its official share entry point (signed-out users return here after login).
# Icon: simple-icons 16.33.0 (note.svg), CC0 1.0. The logo remains a trademark of its owner.
key = 'note'
label = 'note'
brand_color = '#000000'
action = 'open'
endpoint = 'https://note.com/intent/post'
[params]
url = 'url'
hashtags = 'hashtagsCsv'上から読んでいきます。最初の2行はコメントで、この入口が何なのかと、アイコンをどこから借りたのかを書いています。key は共有先の識別子で、ファイル名と同じ note です。label はボタンに出す名前、brand_color はボタンの色です。action = ‘open’ が「入口の URL を開く」動きを選ぶ行で、endpoint がその入口です。最後の [params] には、入口の URL に付ける項目の名前と、その中身を書きます。url という名前で記事の URL を、hashtags という名前でハッシュタグをカンマ区切りで付ける、という意味です。
このように、共有先を「データのカード」として持つ作りは、以前から決めていたものです。本編の連載では、この判断をADRという形の文書(ADR-0002)に残しています。そこに書いた目的の1つが、共有先を1つ足すとき、1ファイル足すだけで済む形を保つことでした。今回の note は、その目的どおりに足せた、いちばん素直な例です。連載2.2「OCP――共有先を一つ足したときの差分を追う」で追いかけた「足しても触らないコード」を、今回は本物の共有先で確かめたことになります。
ちなみに、カードを差し込んだあと、配布物を作る生成の手順(pnpm run build)を1回実行しています。カードを読み込んで、ブラウザ側のプログラムが使う一覧(generated/catalog.ts)と、配布する JavaScript を作り直すためです。この一覧や配布物は手で書き換えない決まりで、カードだけが正本です。
カードの何が、配布の前に確かめられるのか
カードを1枚差し込むだけ、と書くと、手軽すぎて不安に感じるかもしれません。書き間違えたらどうなるのか、と。実は、カードは配布物になる前に、必ず生成器(tools/config.py)の検査を通ります。note のカードも、差し込んだあとの build で検査を受けました。検査の中身は、どの共有先のカードにも共通の、次の項目です。
| 項目 | 確かめること | 止まったときの文の例 |
|---|---|---|
| 書式 | TOML として読めるか | TOML として読めません |
| 項目の種類 | 決まった項目だけがあり、型が合っているか | (知らない項目の名前を示して止まる) |
| key | 英小文字と数字だけで、ファイル名と同じか | key とファイル名をそろえてください |
| label | 空でないか | label を空にしないでください |
| brand_color | #rrggbb の形か | brand_color は #rrggbb で書いてください |
| icon | <svg で始まり、色を currentColor で受け取るか | icon は <svg …> で始まり fill=”currentColor” を使ってください |
| action | open・copy・native・compose のどれかか | action は … のどれかにしてください |
| params | 送る項目の中身が、決まった種類(url・title・text など)か | params は「SNS 側の項目名 = …」の形にしてください |
たとえば、note のカードの key をうっかり Note と大文字で書いたら、「key は英小文字と数字だけにしてください」と止まります。ファイル名を notes.toml にしてしまったら、「key とファイル名をそろえてください」と止まります。同じ名前の共有先が2つある、という状態も、この「ファイル名と key をそろえる」検査のおかげで作れません。フォルダの中に同じ名前のファイルは2つ置けないからです。
icon の検査で「色を currentColor で受け取るか」を見ているのは、アイコンの色を、ボタンの色の設定から変えられるようにするためです。アイコンの中に色を直接書いてしまうと、灰色のボタンの中に、そのサービスの色のロゴだけが浮いてしまいます。細かい決まりですが、1枚ずつ目で確かめるより、生成器にまとめて確かめてもらうほうが確実です。
共有 URL は、宛名の組み合わせ
カードに書いた情報から、実際にどんな URL ができるのかを見てみましょう。note のボタンを押すと、ブラウザの中で次のような URL が組み立てられます。
URL は、封筒の宛名のように、決まった順に3つの部分を並べてできています。最初が宛先、つまりカードの endpoint に書いた入口(https://note.com/intent/post)です。次が ?url= に続く記事の URL で、このとき記事の URL には、どこから来た訪問かを計測するためのUTMが付きます。utm_source には共有先の名前(note)が入ります。最後が &hashtags= に続くハッシュタグで、promari.jp の設定では Promari です。
組み立てているのは、プラグインの中の ShareDestination というクラスの shareUrl という関数、ただ1か所です。
shareUrl(request: ShareRequest): string {
if (!this.#endpoint) return request.url;
const pairs = Object.entries(this.#params)
.map(([name, field]) => [name, FIELD[field](request)] as const)
.filter(([, value]) => value !== '')
.map(([name, value]) => `${UriEncoder.encode(name)}=${UriEncoder.encode(value)}`);
return this.#endpoint + (pairs.length ? `?${pairs.join('&')}` : '');
}上から読んでいきます。2行目は、入口の無い共有先(URL のコピーなど)のための道で、記事の URL をそのまま返します。3行目で、カードの [params] に書いた項目を1つずつ取り出し、4行目で、その項目の中身(記事の URL やハッシュタグ)を読み出します。5行目で、中身が空の項目は捨てます。ハッシュタグを設定していないサイトなら、hashtags= は付きません。6行目で、名前と中身の両方をエンコードしてから「名前=中身」の形にし、最後に入口の後ろへ ? と & でつなぎます。
ここで大事なのは、共有先ごとの違いは、すべてカードの中の「データ」にあり、この関数は共有先の名前を1つも知らないことです。note を足しても、この関数は変わりません。X にはテキストと via を、LINE にはテキストを、note にはハッシュタグを、というふうに、送る項目の違いはカードの [params] が受け持ちます。同じ関数が、どのカードを渡されても同じやり方で URL を組み立てるので、共有先を足すたびにこの関数を直す必要がないのです。
エンコードを最後の1か所でまとめて行っているのにも理由があります。記事の URL の中には & や = のような記号が含まれることがあり、そのまま並べると、どこまでが url の中身で、どこからが次の項目なのか、受け取る側が区別できなくなります。日本語の題名なども、そのままでは URL に入れられません。そこで、組み立ての直前に、名前も中身もまとめて安全な形に置き換えています。組み立てる場所を1つにすれば、エンコードのし忘れや、二重にエンコードしてしまう間違いも1か所で防げます。
記事の URL に付く、見えない名札
note の共有 URL の中に入る記事の URL には、UTMという見えない名札が付くと書きました。この名札の中身も見ておきましょう。promari.jp の設定では、共有した URL に次の3つの項目を付けています。
| 項目 | 設定の値 | note のボタンで付く値 | 意味 |
|---|---|---|---|
| utm_source | {destination} | note | どの共有先から来たか |
| utm_medium | social | social | どんな種類の経路から来たか |
| utm_campaign | share | share | 何の取り組みから来たか |
設定の utm_source に書いた {destination} は、共有先の名前に置き換わる印です。note のボタンなら note、Qiita のボタンなら qiita が入ります。こうしておくと、note で紹介された記事から誰かが読みに来たとき、計測の画面で「note から来た訪問」として数えられます。共有ボタンが押された回数だけでなく、共有された先から読者が戻ってきた回数まで、共有先ごとに追いかけられるわけです。
この名札は、3ページ目で見る compose でコピーする URL にも、同じように付きます。compose の4つは入口が無いので、URL の組み立てには関わりませんが、「コピーする URL」はほかの共有先と同じ規則で作ります。共有先によって名札の付け方が違うと、あとで数字を比べられなくなるからです。
ここで、note を compose ではなく open で足した判断についても、書いておきます。公式の入口がある note は、compose にそろえず open で足しました。5つを同じ動きにそろえるより、それぞれのサービスが用意した一番よい道を使うほうが、読者にとって確かだと考えたからです。
ログインしていないと、改札を1回通ってから戻ってくる
note の入口には、もう1つ確かめておきたいことがありました。読者が note にログインしていないとき、ボタンを押すとどうなるかです。
共有ボタンを押す人の中には、そのブラウザで note にログインしていない人もいます。もしログインしていない人がボタンを押して、エラーの画面が出たり、note のトップページに飛ばされて記事の URL が失われたりしたら、せっかくの入口が台無しです。そこで、ログインしていない状態で入口を開き、何が返ってくるかを確かめました。
入口の URL(記事の URL 付き)を開くと、note のサーバーは、その場で投稿画面を返すのではなく、302 転送でログイン画面へ案内しました。案内先の URL を見ると、redirectPath という項目に、最初に開いた入口の URL が、記事の URL ごと入っています。つまり note は、「ログインが済んだら、ここへ戻ってきてね」という行き先を覚えたまま、ログイン画面へ回してくれるのです。駅の改札で、切符を忘れた人がいったん窓口へ回り、そこから元のホームへ戻ってくるようなものです。
| 順番 | 起きること | URL |
|---|---|---|
| 1 | 共有ボタンを押す | note.com/intent/post?url=(記事の URL) |
| 2 | note がログイン画面へ転送する(302) | note.com/login?redirectPath=(1の URL) |
| 3 | 読者がログインする | (本人が入力) |
| 4 | note が1の画面へ戻す | 「noteで書く」の画面 |
ここで大事なのは、この遠回りのために、こちらのコードは何も足していないことです。行き先を覚えて戻すのは、入口を用意した note の仕事です。公式の入口を使う良さは、こうした「ログインしていない人」「スマホのアプリを入れている人」といった、共有の周りのいろいろな場合を、サービスの側が面倒を見てくれるところにあります。
なお、1から2への転送までは、ブラウザの外から確かめた事実です。3と4、つまりログインしたあとに本当に「noteで書く」の画面へ戻るかは、note の仕様に任せている部分で、この記事の中で自動では確かめていません。入口が公式のものなので、そう動くことを note が約束している、という位置付けです。
こうして note は、カード1枚で片付きました。次のページでは、いよいよ入口の無い4つです。カードを1枚差し込むだけでは、どうにもならない相手でした。
考えてみる:note の入口にも、題名を渡せたらよかったのでは?
note の公式の入口が受け取るのは、記事の URL とハッシュタグです。題名を渡す項目は用意されていません。カードの [params] に title = ‘title’ と書き足すことはできますが、note がその項目を読まなければ、URL が長くなるだけで何も起きません。受け取られない項目を送るのは、動いているように見えて何も起きない、いちばん気づきにくい失敗です。そのため、公式の説明にある項目だけを送っています。入口に渡せない情報は、入口の先で読者が書き足す、という役割の分け方です。









このページ(2ページ目)の感想・質問・設計へのコメント