NewselaからRoam Researchに記事をコピーするブックマークレットを作成しました
タイトルの通りですが、NewselaからRoam Researchに記事をコピーするブックマークレットを作成しました。動作イメージは以下のツイートの動画を見ていただけると分かりやすいかと思います。
使い方
詳しいことを話す前に先に使い方を書いておこうと思います。以下のコードをブックマークに登録します。私は `Newsela2Roam` という名前で使っています。これはこの記事内にあるJavaScriptのコードをminifyしたものです。
javascript:void function()%7Bconst a%3Ddocument.querySelector("%23article-title").textContent,b%3Ddocument.querySelector("h2%5Bdata-qa-selector%3Darticle_label_text%5D").textContent,c%3Ddocument.querySelector("div span%5Bdata-qa-selector%3Dword_count%5D").textContent,d%3Ddocument.querySelector("div%5Bdata-qa-selector%3Dlexile_dropdown_menu%5D div").textContent,e%3Ddocument.querySelector("article img").getAttribute("src"),f%3Ddocument.evaluate("//span%5Btext()%3D%27Published:%27%5D",document,null,XPathResult.ANY_TYPE,null),g%3Df.iterateNext(),h%3Dnew Date(g.nextElementSibling.textContent),i%3D(a%3D>%7Bconst b%3Da.getFullYear(),c%3Da.toLocaleString("en-us",%7Bmonth:"long"%7D),d%3D(a%3D>a%2B(0<a%3F%5B"th","st","nd","rd"%5D%5B3<a%26%2621>a%7C%7C3<a%2510%3F0:a%2510%5D:""))(a.getDate())%3Breturn%60%24%7Bc%7D %24%7Bd%7D, %24%7Bb%7D%60%7D)(h),j%3Ddocument.querySelectorAll("div%5Bdata-qa-selector%3Darticle_text%5D p,div%5Bdata-qa-selector%3Darticle_text%5D h2")%3Blet k%3Dlocation.href%3Btry%7Bk%3Ddocument.querySelector("link%5Brel%3D%27canonical%27%5D").getAttribute("href")%7Dcatch(a)%7Bconsole.log("canonical url is not found.")%7Dconst l%3D%5B"%23Newsela",%60Lexile:: %24%7Bd%7D%60,%60Topic:: %23%5B%5B%24%7Bb%7D%5D%5D%60,%60Published:: %5B%5B%24%7Bi%7D%5D%5D%60,%60Date to Read:: %60,%60Words:: %24%7Bc%7D%60,%60Source: %24%7Bk%7D%60,%60!%5B%5D(%24%7Be%7D)%60,"%7B%7Bword-count%7D%7D%5Cn"%5D%3Blet m%3Dl.join("%5Cn"),o%3D!1%3Bconst p%3Da%3D>%7Bconst b%3Da%3F4:2%3Breturn" ".repeat(b)%7D%3Bj.forEach(a%3D>%7Bconst b%3D"H2"%3D%3D%3Da.tagName,c%3Dnull!%3D%3Da.querySelector("img")%3Bb%26%26(o%3D!1)%3Blet d%3Db%3F%60***%24%7Ba.textContent%7D**%60:a.textContent%3Bif(c)%7Bconst b%3Da.querySelector("img").getAttribute("src")%3Bd%3D%60!%5B%5D(%24%7Bb%7D)%24%7Bd%7D%60%7Dm%2B%3D%60%24%7Bp(o)%7D %24%7Bd%7D%5Cn%60,b%26%26(o%3D!0)%7D),navigator.clipboard.writeText(m)%7D()%3B
これで準備ができました。やることは簡単です。
1. Roamで記事のタイトルでページを作成する
2. Newselaの記事ページに行って、↑のブックマークレットを起動する
3. Roamに戻って作成したページにペーストする
モチベーション
読みたいNewselaの記事を手動でRoamにコピーして、Roam上で分からない単語とかをページ作成しつつ読むというのを続けていました。しかし、手動でコピーするのは面倒だし、メタデータを入れるのもまた面倒くさかったです。そこでこの面倒くさい作業を自動化することにしました。
自動化するにはいくつか方法があると思うのですが、Roamについて詳しくないので、記事をRoam形式でコピーできるブックマークレットを作成することにしました。
作成したブックマークレット
作成したブックマークレットはこんな感じです。やっていることは難しくなく、DOMから必要な情報を `querySelector` で引っ張り出してきています。ここで重要なのは `class` を使わないということです。 `class` に頼ると見た目の変更によって変更されてしまうことが多くあるので、すぐに動かなくなってしまいます。URLはcanonicalなURLを取得しようとしていますが、ない場合は `location.href` を採用するようになっています。
最後にDOMから取得した情報をRoamの形式にしてクリップボードに詰めています。一番面倒くさかったのはDateオブジェクトをRoamの形式の日付に変換することです。これ良い方法ないのかな...
// DateオブジェクトをRoamの日付の形式に変換する
const roamfy = (d) => {
const ord = (n) => n + (n > 0 ? ['th', 'st', 'nd', 'rd'][(n > 3 && n < 21) || n % 10 > 3 ? 0 : n % 10] : '');
const year = d.getFullYear();
const month = d.toLocaleString('en-us', { month: 'long' });
const date = ord(d.getDate());
return `${month} ${date}, ${year}`
}
const title = document.querySelector("#article-title").textContent;
const topic = document.querySelector("h2[data-qa-selector=article_label_text]").textContent;
const wordCount = document.querySelector("div span[data-qa-selector=word_count]").textContent;
const lexile = document.querySelector("div[data-qa-selector=lexile_dropdown_menu] div").textContent;
const imageSrc = document.querySelector("article img").getAttribute("src");
const publishedDateDOMRes = document.evaluate("//span[text()='Published:']", document, null, XPathResult.ANY_TYPE, null);
const publishedDateDOM = publishedDateDOMRes.iterateNext();
const publishedDate = new Date(publishedDateDOM.nextElementSibling.textContent);
const published = roamfy(publishedDate);
const articleTexts = document.querySelectorAll("div[data-qa-selector=article_text] p,div[data-qa-selector=article_text] h2");
let source = location.href;
try {
source = document.querySelector("link[rel='canonical']").getAttribute("href") ;
} catch (e) {
console.log("canonical url is not found.");
}
const attributes = [
"#Newsela",
`Lexile:: ${lexile}`,
`Topic:: #[[${topic}]]`,
`Published:: [[${published}]]`,
`Date to Read:: `,
`Words:: ${wordCount}`,
`Source: ${source}`,
`![](${imageSrc})`,
"{{word-count}}\n"
];
let res = attributes.join("\n");
let inParagraph = false;
const putIndent = (inParagraph) => {
const indent = inParagraph ? 4 : 2;
return " ".repeat(indent);
}
articleTexts.forEach((n) => {
const isH2 = n.tagName === "H2"
const hasImage = n.querySelector("img") !== null;
if (isH2) {
inParagraph = false;
}
let text = isH2 ? `***${n.textContent}**` : n.textContent; // Boldの始まりのアスタリスクが2つだと1つに削られてしまうので3つにした
if (hasImage) {
const imageSrc = n.querySelector("img").getAttribute("src");
text = `![](${imageSrc})${text}`;
}
res += `${putIndent(inParagraph)} ${text}\n`;
if (isH2) {
inParagraph = true;
}
});
navigator.clipboard.writeText(res);
おわりに
今回はRoamについてあまり知らなかったので、ブックマークレットに逃げましたが、Roam周りについてよく知るともっと面白そうなことができる気がしています。結構自動化ができそうで楽しみです。
このブックマークレットについての質問や、バグ報告、機能要望をお待ちしています!ベストエフォートで対応します :pekori: