現状
umiJSのcommentにリンクを挿入する
renderItem={}内のcommentタグがチャットメッセージコンポーネントに該当する。
その中のcontentプロパティにメッセージが渡されている。
問題点
itemを受け取ってtextを表示している。
問題はここが全てstring型で渡されているので、URLを添付してもリンクが繋がらない。
// 修正前
<List
/* 省略 */
renderItem={item => {
return (
<li>
<Comment
author={item.username}
content={item.text} // <== ここ
/>
</li>
)
}}
/>
実装方針
入力された文字列からURLを検索し変換してからcontentに渡す。
正規表現を用いてURLライクな文字列を抽出し、aタグに置換する。
const link = (str) => {
const regexp_url = /https?:\/\/[a-zA-Z0-9.\-_@:/~?%&;=+#',()*!]+/g;
const regexp_makeLink = (url) => {
return '<a href="' + url + '">' + url + '</a>';
}
return str.replace(regexp_url, regexp_makeLink);
}
strを受け取ってreplace(検索文字, 置換後)メソッドで置換する。
regexp_urlを正規表現で定義して、https://~~~という文字列を検索する。
末尾の/gはテキストないの全てのurlを検索するもので、urlがヒットするたびにregexp_makeLinkメソッドが実行される。
replaceメソッドは第一引数が席表現の場合、第二引数の関数の引数は(文字列, n番目の()で括られた文字列)を受け取る。
regexp_makeLinkは受け取ったregexp_urlをaタグで囲う関数。
そしてautoLink関数で処理したitem.textをCommentのcontentプロパティに渡す。
// 修正前
<List
/* 省略 */
renderItem={item => {
return (
<li>
<Comment
author={item.username}
content={link(item.text)} // <== ここ
/>
</li>
)
}}
/>
この時点で予期しない挙動に気づく...
<a href="' + url + '">' + url + '</a>がそのまま文字列として表示されてしまう。
現状は全て文字列として渡されてしまっているものをJSX.Elementとして渡さないといけない。
セキュリティ上あまり良くないような気がするが、InnerHTMLで埋め込む方針をとる。
Reactの場合、dangerouslySetInnerHTMLという書き方になる。
const link = (str: string): JSX.Element => {
const regexp_url = /https?:\/\/[a-zA-Z0-9.\-_@:/~?%&;=+#',()*!]+/g;
const regexp_makeLink = (url: string): string => {
return `<a href=${url} target="_blank" rel="noopener noreferrer">${url}</a>`;
}
const replacedString = str.replace(regexp_url, regexp_makeLink);
return (
<p dangerouslySetInnerHTML={{__html: replacedString}}></p>
)
};
__htmlプロパティに先ほど変換したURLを含む全ての文字列を渡すことで、pタグのなかにaタグを含む文字列を入れる。
ちなみに新規タブで開きたかったのでtarget="_blank"を追記。
加えて、「開かれた新規タブ側で、リンク元タブの URL を操作できてしまう」という虚弱性対策として rel="noopener noreferrerを加える。