fjordllc / bootcamp

プログラマー向けEラーニングシステム
https://bootcamp.fjord.jp
MIT License
286 stars 71 forks source link

リンクをわかりやすく見せる部品(リンクカード)が欲しい #7455

Open komagata opened 8 months ago

komagata commented 8 months ago

↓こういうやつが欲しい。

ブログや日報など、Markdownエディターを使ってるところで使えるようにする。

下記のnpmを試してみてください〜(書式も下記のnpmに従います)

https://www.npmjs.com/package/@luckrya/markdown-it-link-to-card

mousu-a commented 8 months ago

@komagata お疲れ様です。 実装の方針について確認させていただきたいです🙏

luckrya/markdown-it-link-to-card リンクカードの実装に使うこちらのnpmなんですが、使われている言語がTypeScriptなためそのまま使えなさそうです。 下記のAPI(JavaScript)の方で実装するという形で良かったのでしょうか? 不安に思ったので確認させていただきたいです🙇‍♂️

Usage(TypeScript)

import MarkdownIt from "markdown-it";
import { linkToCardPlugin } from "@luckrya/markdown-it-link-to-card";
import type { LinkToCardPluginOptions } from "@luckrya/markdown-it-link-to-card";

const md = MarkdownIt({ html: true }).use<LinkToCardPluginOptions>(
  linkToCardPlugin,
  {
    // options
    size: "small",
  }
);

const rendered = md.render(`

# Home

...

### Reference

  - [github](https://github.com)
  - [bing](https://cn.bing.com/)
  - [知乎 - 发现页](https://www.zhihu.com/explore)
  - [markdown-it-link-to-card](https://github.com/luckrya/markdown-it-link-to-card)

<br />

  - [github](@:https://github.com)
  - [bing](@:https://cn.bing.com)
  - [知乎 - 发现页](@:https://www.zhihu.com/explore)
  - [markdown-it-link-to-card](@:https://github.com/luckrya/markdown-it-link-to-card)

`);

API(JavaScript)

import { generateCard } from "@luckrya/markdown-it-link-to-card";

generateCard("https://github.com", {
  linkTitle: "Github Home",
  showTitle: true,
  size: "small",
}).then(({ dom }) => {
  // card dom fragment
  console.log(dom);
});
komagata commented 8 months ago

@mousu-a 後からこのIssueを追った人にもわかるようにGitHub外でのやりとり(ミーティングやDiscordや口頭など)で決まったことがあればこちらにメモとして残すようにしてみてください〜。

mousu-a commented 8 months ago

後からこのIssueを追った人にもわかるようにGitHub外でのやりとり(ミーティングやDiscordや口頭など)で決まったことがあればこちらにメモとして残すようにしてみてください〜。

ありがとうございます🙏

以下ミーティングで決まった方針です!ログとして残します。

概要

リンクカードの実装方針に不安があり、komagataさんに相談させていただいた。

詳細

上にあるように、リンクカードの実装使うnpmがTypeScriptで書かれていたため、Usageとして例に上がっているこちらのコードではなく、 https://github.com/luckrya/markdown-it-link-to-card?tab=readme-ov-file#usage

APIとして提示されているこちらのコードを使って実装するものだと思い込んでいました。 https://github.com/luckrya/markdown-it-link-to-card?tab=readme-ov-file#api

そちらをミーティングにてkomagataさんに相談させていただいたところ、「なにか他に別の手段がありそう、探してみてほしい。というよりこの場合のAPIというのが(何に使うのか?)よくわからない」とのこと。 それとチーム開発メンバーのAntiSatoriさんから「TypeScriptは実行時にJavaScriptにコンパイルされるため、TypeScriptで書かれたコードでも問題はなさそう」とのご指摘をいただいたので、 現在は「TypeScriptで書いた(Usageにあるような)リンクカード関連のコードをJavaScriptのファイルでimportして使う」形で実装を進めています。

mousu-a commented 7 months ago

ログ

こちらご紹介いただいたnpmがTypeScriptなのですが、komagataさんより「TypeScriptを使ってしまうと、RailsエンジニアコースでもTypeScriptのカリキュラムを作る必要があり、あえて導入しないようにしているので、TypeScriptはbootcampに導入しない方向で進めていただければありがたいです〜。」とのことです。

TypeScriptを使わずにnpmを使って導入するという方向で進めていきます🙏

mousu-a commented 7 months ago

@komagata

お疲れ様です! リンクカードの実装、ようやく方向性が見えてきたのですが、npmの問題点が見つかりました。それについての対処法について、お考えをお聞きしたいです。

npmの問題点

リンクカードを生成する際にCORSでブロックされる

対処法

間にRailsのコントローラーを挟む

懸念点

ザックリこんな方針で進めています。 このまま進めて良いものかと思い質問させていただきました。

気になるところなどあればお考えをお聞きしたいです! 大丈夫そうであればこのまま進めたいと思います!


こちらの日報でこれらの問題について、メンターのyuuuさんにお話を伺っていました。 共有させていただきます🙏

https://bootcamp.fjord.jp/reports/91320

komagata commented 7 months ago

@mousu-a まず、この npmを使う人ほぼ全員がCROSの問題にあたるとおもいます。 そこをみんながどうしてるのかを調べるのがよさそうです。

mousu-a commented 7 months ago

@komagata

一通り調べたつもりですが、確実にYESと言えるほど理解度が高くないのでもう少し調べてみます🙏

mousu-a commented 7 months ago

@komagata

お疲れ様です。一通り調べましたがやはりサーバーサイドを間に通してリクエストをするのが良さそうだと思うのですがいかがでしょうか。以下詳細になります。

npmのCORS対策

調べましたが、やはりこのnpmでCORS関連の記述は出てこないようです。 そもそもこのnpmのドキュメントが本家のREADME以外に全くないという感じです。(READMEにも書いておらず、使っている人も見つかりませんでした😓) コードを見てもそれらしき記述は見つかりませんでした。 どうやらnpmのコードはクライアントサイドの実装しかないようです。 それにリクエストにXHRを使っているので、クライアントサイドの方で(CORS対策について)出来ることはないという認識です。(cookieを必要としない限り)

@luckrya/markdown-it-link-to-card examples - CodeSandbox

こんなサイトがありましたが、この中では予め用意したデータを使ってリンクカードを生成していたので参考にならずじまいでした。。(外部サイトにリクエストを送っていないのでそもそも前提条件が違いました)

他のリンクカードのCORS問題はどうしてるのか?

主にこの二つがありそうです。 クライアントサイドで完結させる手段としては、間にプロキシサーバーを用意し、そこにリクエストを送りプロキシサーバーから外部サイトへリクエストを送るようにすることでCORSを避ける、という手段もあるようです。 サーバーサイドに関しては上のコメントで述べている通りです。

ただ結局は、サーバーサイドかサーバーサイドの代わりになるものを用意してそれを間に挟もう、という試みなのは変わらなさそうです。

この場合Railsのコントローラーを間に挟むのは下策でしょうか?ご意見を伺いたいです🙇‍♂️

komagata commented 7 months ago

@mousu-a まずパラメーターにURLを渡してどのページでも表示するというのはセキュリティ上危険なのでできないです。

それだったらリンクカードをrailsで表示するページをつくって、別のページからはIFRAMEで表示する方がいいとおもいます。 ただそれだとnpmを使う意味(便利さ)がないので、ほかのnpmを探してみるのはいかがでしょうか。

mousu-a commented 7 months ago

@komagata

質問が2つあります🙏ご意見を伺いたいです🙇‍♂️

まずパラメーターにURLを渡してどのページでも表示するというのはセキュリティ上危険なのでできないです。

  1. リクエストを受け取ったRailsのコントローラーで、「リンクかどうか?」を正規表現でチェックする、危ない記号である「<>, ”’, :;」これらをサニタイズする、などをしてもダメなのでしょうか?

ほかのnpmを探してみるのはいかがでしょうか。

  1. CORSの問題はnpmを変えてどうにかなるものでなく、ブラウザのJSから外部サイトへリクエストを送る時点で必ず発生してしまいます。 Node.jsで外部サイトへリクエストを送ればCORSは発生しませんが、それですとbootcampの既存のMarkdownItPlugin(ブラウザ用のJS)との互換性が保てません。。(webpackerによりブラウザ用にJSファイルがコンパイルされているため、ブラウザ用のJSファイルからNode.jsで書かれたJSファイルをimportすることができません)

これらの理由から、必ずブラウザ用にコンパイルされたJSから外部サイトへリクエストを送る必要がある(そしてその場合CORSが発生する)、という認識です。 なので、ブラウザ用のJS→サーバー(Railsなど)→外部サイト の流れでリクエストを送ることは必須になるものと思っているのですがいかがでしょうか?

他のリンクカード実装例

Zennのリンクカードなどでは外部サービス(Cloud Functions)を使って外部サイトへのアクセスをしているみたいです。 Zennのカードリンクのレスポンスが遅い件

QiitaXは見つかりませんでした。。

認識が間違っているところなどあればご指摘いただけますと幸いです🙇‍♂️

komagata commented 7 months ago

@mousu-a

リクエストを受け取ったRailsのコントローラーで、「リンクかどうか?」を正規表現でチェックする、危ない記号である「<>, ”’, :;」これらをサニタイズする、などをしてもダメなのでしょうか?

はい。 同じサイト内だったらいいですが、外部のサイトをそのまま出すのは相当な理由がない限りNGです。

CORSの問題はnpmを変えてどうにかなるものでなく、ブラウザのJSから外部サイトへリクエストを送る時点で必ず発生してしまいます。 Node.jsで外部サイトへリクエストを送ればCORSは発生しませんが、それですとbootcampの既存のMarkdownItPlugin(ブラウザ用のJS)との互換性が保てません。。(webpackerによりブラウザ用にJSファイルがコンパイルされているため、ブラウザ用のJSファイルからNode.jsで書かれたJSファイルをimportすることができません)

同じような実装の似た他のnpmを使ってほしいと思っているわけではないです。

QiitaやXは見つかりませんでした。。

なるほどです。

Railsのコントローラーを挟む場合の実装方法は一例考えがありますが、勉強になるとおもうので @mousu-a さんもサイトにリンクカード的なものを追加したい場合、どのような実装がいいのか考えてみてください。

komagata commented 6 months ago

@mousu-a すみません、ちょっとややこしいですよね。 仕様を作ってみたのでこちらで進めていただければありがたいです。仕様に不明点などありましたらお気軽におっしゃっていただければと思います。

リンクカード · fjordllc/bootcamp Wiki

@machida リンクカードのHTMLや表示について問題点あればおっしゃっていただければとおもいます~。

mousu-a commented 5 months ago

@komagata うおお〜😭 すみません、お忙しい中ありがとうございます!

仕様に不明点などありましたらお気軽におっしゃっていただければと思います。

ありがとうございます!わからないところあれば質問させていただきます🙇‍♂️

mousu-a commented 5 months ago

@komagata

お疲れ様です。すみません、早速ですが質問が2つあります。 リンクカード実装の方向性について確認させていただきたいです🙏

質問1

リンクカードの実装は、テキスト中に[TITLE](URL)があれば該当箇所をリンクカードのHTMLに置換する、という形で宜しいのでしょうか?

適用前:

テキストテキスト
[TITLE](URL)
テキストテキスト

適用後:

<p>テキストテキスト</p>
<div class="link-card">
  <div class="link-card__title">
    <a href="URL" target="_blank">TITLE</a>
  </div>
  <div class="link-card__description">DESCRIPTION</div>
  <div class="link-card__favicon"><img src="FAVICON" /></div>
  <div class="link-card__site-title">
    <a href="SITE URL" target="_blank">SITE TITLE</a>
  </div>
  <div class="link-card__image"><img src="IMAGE" /></div>
</div>
<p>テキストテキスト</p>

質問2

リンクカードwikiにて、

markdownでの記法はFBCで使っているmarkdown-itのプラグインとして実装する。

とありますが、md.use(Plugin)のような形で使えるよう実装してほしい、ということでしょうか? それともマークダウンを適用する流れで一緒に適用出来るように実装してほしい、ということでしょうか? 例:

# markdown-initializer.js

    # md.use(Plugin)の場合
    …
    md.use(MarkdownItEmoji)

    # マークダウンを適用する流れで一緒に適用する場合(例として既存の実装をピックアップしました🙏)
    …
    MarkdownItTaskListsInitializer.initialize()

よろしくお願いいたします🙇‍♂️

komagata commented 5 months ago

@mousu-a

質問1

NOです。

下記の「リンクカード記法」の部分を参照してください。

https://github.com/fjordllc/bootcamp/wiki/%E3%83%AA%E3%83%B3%E3%82%AF%E3%82%AB%E3%83%BC%E3%83%89#%E3%83%AA%E3%83%B3%E3%82%AF%E3%82%AB%E3%83%BC%E3%83%89%E8%A1%A8%E7%A4%BA

質問2

markdown-itのプラグインとは何なのか、markdown-itの公式サイトなどを参考に調べてみてください。

mousu-a commented 5 months ago

ミーティングを経てのログ

mousu-a commented 4 months ago

@komagata お疲れ様です! すみません、実装の仕方について確認させていただきたいです。 リンクカードの実装についてですが、このような認識で合っていますでしょうか?

リンクカード:考えている実装

// markdown-initializer.js
import MarkdownItLinkcard from 'markdown-it-linkcard'
~
md.use(MarkdownItLinkcard)
~

// markdown-it-linkcard.js
export default  (md) => {
    ~リンクカードの実装~
}

bootcamp内での既存の実装例

// markdown-initializer.js
import MarkDownItContainerDetails from 'markdown-it-container-details'
~
md.use(MarkDownItContainerDetails)
~

// markdown-it-container-details.js
export default (md) => {
  ~プラグインの実装~
}
komagata commented 4 months ago

@mousu-a linkはcontainer(block要素)じゃなくてinline要素なので違うと思います。

import MarkdownItLinkcard from 'markdown-it-linkcard'

これは下記がいいとおもいます。

import MarkdownItLinkCard from 'markdown-it-link-card'

別の点で、これも、

import MarkdownItContainerDetails from 'markdown-it-container-details'

こちらの間違いかなとおもいます。

mousu-a commented 4 months ago

@komagata 質問させていただきたいです。 リンクカードの生成条件についての確認になります。

①1行に@[TITLE](URL)のみ存在する場合(記法の前後に文字が存在しない)

aaa
@[TITLE](URL)
aaa

@[TITLE](URL)の前後に文字がある場合(文の中に記法が存在する場合)

aaa @[TITLE](URL) aaa

リンクカード生成にあたり、この2つが状況として考えられると思います。

自分の認識としては、 ①の場合のみリンクカードを生成する。 ②の場合は"表示できない場合"として扱い、普通のリンクとして表示する。 という風に考えているのですがいかがでしょうか。

komagataさんのご意見を伺いたいです🙇‍♂️

捕捉情報

ZENNでは1行にURLのみ存在する場合(①の場合)のみリンクカードを生成するという形になっています。

mousu-a commented 4 months ago

ログ 上記質問に対するkomagataさんの回答

①1行に@[title](url)のみ存在する場合 ②@[title](url)の前後に文字がある場合 どちらの場合でもリンクカードを作成するように実装する

mousu-a commented 3 months ago

MTGログ 下記質問に対するkomagataさんの回答

質問1

リンクカードWikiの「表示できない場合」とは具体的に何を指すのか?

回答

捕捉

上記のいずれの状況もユーザーにとってハイコンテキストなため、ユーザーからは見えないのが望ましい。


質問2

マークダウン記法がネストしている場合はリンクカードは生成されるのか?

:::details ネタバレ注意! @TITLE :::

回答

マークダウン記法がネストしている状態でもリンクカードは生成する。

mousu-a commented 3 months ago

@komagata

お疲れ様です。 質問があります!2つあります。 よろしくお願いします🙏

1つ目

メタデータAPIのこちらのレスポンスですが、これらのkeyはogpのproperty値を参考にしているのでしょうか?

{
  site_title: "Moeny Forward Developers Blog",
  site_url: "https://xxx.com",
  favicon: "https://xxx.com/favicon.ico",
  url: "https://xxx.com/xxxx",
  title: "ChatGPTのAPIがオープンになったので(略)",
  description: "xxxxxxx",
  image: "https://xxx.com/xxx.png"
}

補足情報

OGP

<meta property="og:url" content=" ページの URL" />
<meta property="og:type" content=" ページの種類" />
<meta property="og:title" content=" ページの タイトル" />
<meta property="og:description" content=" ページの説明文" />
<meta property="og:site_name" content="サイト名" />
<meta property="og:image" content=" サムネイル画像の URL" />

2つ目

もしそうであれば、差し支えなければsite_titlesite_nameに変更させていただきたいのですがどうでしょうか。

補足情報

というのも、レスポンスのkeyとogpのproperty値がほとんど一致しているためそれに即した実装を考えているのですがsite_titleだけ違うためちょっと引っかかってしまうな、というところで質問いたしました。

まだ未完成ではありますが、このような実装を考えています。

# リクエスト
uri = Addressable::URI.parse(url).normalize
res = Net::HTTP.get_response(uri)

# nokogiriでdoc化
doc = Nokogiri::HTML(res.body)
# OGPのmetatagを抽出
og_meta_tags = doc.css('meta[property^="og:"]')

# レスポンスを生成
js_response = { site_title: '', site_url: '', favicon: '', url: '', title: '', description: '', image: '' }
og_meta_tags.each do |meta|
  js_response.each do |key, _value|
    # レスポンスのkeyと、metadataのpropertyが一致したら contentの値をvalueにセット
    js_response[key] = meta['content'] if meta['property'] == "og:#{key}"
  end
end
komagata commented 2 months ago

@mousu-a 厳密な仕様って感じではないので、やりやすいように変えてしまって大丈夫です〜

mousu-a commented 2 months ago

メモ

TweetのURLだけ特別仕様として実装する

mousu-a commented 2 months ago

@machida お疲れ様です!先日のMTGでお話していたTweetのリンクカード(埋め込みTweet)のデザインについてお聞きしたいです。 お手すきの際にご確認をお願いいたします🙇‍♂️

質問:Tweetのリンクカード(埋め込みTweet)だけ特別仕様とするかどうか?

現状Tweetのリンクカードだけ、このようにTwitterのoEmbedAPIを使用しデザインが特別仕様となっています。 Tweetのリンクカードも他のリンクカードとデザインを統一する形にした方がいいでしょうか?

説明:Tweetのリンクカード(埋め込みTweet)の実装方法

oEmbedAPIを通してTweetURLにリクエストを送ることで、このようなデータを取得することができます。

懸念点

Tweetのリンクカードも他のリンクカードとデザインを統一する場合、リンクカードのmetadataが必要となります。

{
  site_name: "Moeny Forward Developers Blog",
  site_url: "https://xxx.com",
  favicon: "https://xxx.com/favicon.ico",
  url: "https://xxx.com/xxxx",
  title: "ChatGPTのAPIがオープンになったので(略)",
  description: "xxxxxxx",
  image: "https://xxx.com/xxx.png"
}

前述したAPIを介したリクエストでは取得出来るmetadataが少なく、リンクカードとして成立させにくい、という問題があります。 具体的にはfavicon, site_name, title, imageが足りていません。ただ他のもので代替することはできそうです。

ZENNでの実装

ZENNではoEmbedAPIを使いTweetのリンクカード(埋め込みTweet)を特別仕様としています。

Pasted Graphic

以上のことを踏まえて、Tweetのリンクカード(埋め込みTweet)も他のリンクカードとデザインを統一させるかどうかをお聞きしたいです🙏

machida commented 1 month ago

@mousu-a

Twitterのデザインは別にし、TwitterはTwitterに適した見た目にしたいと思います。もし、Twitterは何もしなかったら勝手にデザインが入るようになっているのであれば、それを確認したいです。

リンクカードのデザインは僕の方でやります。

確認、デザインがしやすいように、リンクカード、Twitterを入れた投稿をfixtureに入れておいてください。それをcommitしたら、メンションをお願いします。実際に手元でコードを見て確認します。

よろしくお願いします🙏

mousu-a commented 1 month ago

@machida まだテストやリファクタリングは出来ていないのですが、macihdaさんの確認のために一度commit、プルリクを作ってお見せした方がいいでしょうか? (実装に変更があれば手戻りが発生してしまうのかなと思った次第です)

machida commented 1 month ago

@mousu-a キリのいいところで大丈夫ですー どこをキリのいいところとするかで迷ったら@komagataさんと相談してください🙏

mousu-a commented 1 month ago

@komagata

お疲れ様です。 1つ質問をさせていただきたいです。

質問

リンクカード記法の[TITLE](URL)の値、

@[TITLE](URL)

metadata(レスポンス)のtitleurlの値

{
  site_name: "Moeny Forward Developers Blog",
  site_url: "https://xxx.com",
  favicon: "https://xxx.com/favicon.ico",
  url: "https://xxx.com/xxxx",
  title: "ChatGPTのAPIがオープンになったので(略)",
  description: "xxxxxxx",
  image: "https://xxx.com/xxx.png"
}

この2つは同じものと考えてもいいのでしょうか?

補足

同じものなのであれば、今回実装するAPIコントローラーのレスポンスは上記wikiの通り用意し、今回は(リンクカードの実装では)レスポンスのtitleurlを使わずにリンクカード記法のTITLEURLを使う、という風な実装を考えています。

というのも、存在しないサイトのリンクカードを生成する時、リンクカード記法のTITLEURLのみをmetadataとして用いてリンクカードを生成したいからです。 (存在しないサイトのリンクカードを生成する場合、空のレスポンスが返るため)

mousu-a commented 1 month ago

📝 リンクカードの仕様を@[TITLE](URL) → @[card](URL)という記法に変更。リンクカードのtitleはmetadataAPIのレスポンスのtitleを使う。

mousu-a commented 1 week ago

@komagata すみません、こちらで質問させていただいたことと同じなのですが、具体的な内容がわからないメモの残し方をしてしまい、リンクカードの具体的なイメージについてもう一度お聞きしたいです🙇

質問

@[card](url)の前後に文字がある場合でもリンクカードを生成するという仕様になっていますが、具体的なイメージとしてはどちらでしたでしょうか?

例1

aaa リンクカード記法 bbb
↓
aaa
リンクカード
bbb

例2

aaa リンクカード記法 bbb
↓
リンクカード

補足説明

Markdown-it のプラグインとして実装する際に、inlineruleとして実装すると例1のようになります。 blockruleとして実装すると例2のようになります。

個人的には例1(inlineruleとしてプラグインを実装する)が正しい形なのかなという認識です。


お手間をとらせてしまい申し訳ありません🙇 よろしくお願いいたします。

machida commented 1 week ago

@mousu-a @komagata

仕様を変更させてください。

inlineで @[card](URL) がある場合、つまり、@[card](URL) この前後に文字がある場合はリンクカードにする、という仕様は無しにしてください。

理由は、inline の場合、p や li の中にリンクカードが生成されるのは、マークアップ的にinvaridなHTMLになるからです。

あああ @[card](URL) あああ

の場合は、

<p>あああ</p>
[リンクカード]
<p>あああ</p>

という表示になるなら HTML は validにはなるので、そこまでしてあげたらユーザーには優しいですが、そもそも変換前の Markdown のコードの時点で invalid な HTML であるため、このように変更してあげる気遣いは不要です。

あああ @[card](URL) あああ

このコードの場合、現在のコード(machidaのPR)では以下のように出力されます。

<p>あああ @<a href="URL">card</a> あああ</p>

このままでいいと思います。 Zennもこのようになります。


あああ
@[card](URL)
あああ

このコードの場合は、

<p>あああ
<br>
[リンクカード]
<br>
あああ</p>

というHTMLに変換されるのも、invalidなHTMLなのでNGです。

<p>あああ</p>
[リンクカード]
<p>あああ</p>

このように変換されると、 HTML は valid にはなるので、そこまでしてあげたらユーザーには優しいですが、そもそも変換前の Markdown のコードの時点で invalid な HTML であるため、このように変更してあげなくてもいいとは思います。

ただ、Zennではこのように変換されるようにはなっていました(FBCがそこまでやってあげなくてもいいとは思いますが、簡単にできるならやってあげたい)。

現在のコード(machidaのPR)では、

<p>あああ
<br>
@<a href="URL">card</a>
<br>
あああ</p>

このように表示されます。


少し別の話になりますが、 現在の mousu さんのコードは、

あああ

@[card](URL)

あああ

の場合、

<p>あああ</p>
<p>[リンクカード]</p>
<p>あああ</p>

このように出力されます。 p の中に[リンクカード]があるので、これも invalid な HTML です。

僕の PR で[リンクカード]を囲うpを削除するコードを含めています。 以下のように出力されます。

<p>あああ</p>
[リンクカード]
<p>あああ</p>

機能を修正する場合は、差分が出てマージが難しくなるので https://github.com/fjordllc/bootcamp/pull/8143 こちらをマージした上で作業をお願いします。