Astroでトップページを最新記事にしたい
トップページは常に最新記事にしたい、つまりビルドするごとに内容が変わる(かも)という状態にしたい。 WordPressなんかでトップページを「最新の投稿」にする、みたいな設定がある思うんですが、ああいうイメージです。
Routing -> Rest parameters -> undefined でできる
結論からすると、[...slug].astro
で、path
にundefined
を指定するとトップレベルのindex.html
になりました。痒いところに手が届いてて助かる……。
ドキュメント:Routing 🚀 Astro Documentation
index.astroに書けば…?
pages/index.astro
に最新記事を取得して出力するコードを書いてももちろんできるんですが、個別記事を出力するファイル(つまりpages/posts/[slug].astro
みたいなやつ)とほぼ同じことを書くことになるので、1つのファイルで済ませたいな、というのが前提でした。
もうちょっと具体的に
pages/[...slug].astro
を用意するgetStaticPaths()
関数に、記事を取得してきて配列を返すコードを書く- 配列の最後に、最新記事をコピーして
params.path
をundefined
に変更した要素を入れておく
こんな感じ
const paths = posts.map(post => { //...なんか記事データの処理 return { params: {path: `posts/${post.id}`}, props: { // } } }) const lastItem = Object.assign({params: {}, props: {}}, paths[paths.length - 1]) lastItem.params = {path: undefined} paths.push(lastItem) return paths
こうすると、path
がundefined
な要素は親階層のパスになるので、/dist/index.html
に書き出される。
JavaScriptでクリップボードにコピー(2022年版)
※ 2017年に書いた記事が、2022年現在では非推奨になっているAPIを使っているので、改めて。
変わったところ
document.execCommand('copy')
が仕様からなくなり、非推奨になりました(MDN)。今のところどのブラウザでも動作しますが、今後も動く保証はありません(とはいえ後方互換のために当面は動くと思いますが)。代わりにClipboard APIという仕様が整備されているので、新たに実装する場合はこちらを使うべきです。
HTMLとJavaScriptのコード
<p class="copyTarget">コピーしてもらいたいテキスト</p> <button class="copyBtn">クリップボードにコピーする</button>
function copyText () { const $target = document.querySelector('.copyTarget'); if (!$target) { return false; } const range = document.createRange(); range.selectNode($target); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); // document.execCommand('copy'); // ←非推奨に。 navigator.clipboard.writeText($target.innerText); return false; } document.querySelector('.copyBtn').addEventListener('click', copyText, false);
テキストエリア内のコンテンツをコピーする場合
<textarea class="textarea">テキストエリアのテキスト</textarea> <button class="copyBtn">クリップボードにコピーする</button>
function copyText () { const $target = document.querySelector('.textarea'); if (!$target) { return false; } $target.select(); navigator.clipboard.writeText($target.value); return false; } document.querySelector('.copyBtn').addEventListener('click', copyText, false);
クリップボードからコピーする
<textarea class="textarea">テキストエリアのテキスト</textarea> <button class="copyBtn">クリップボードからコピーする</button>
function copyText () { const $target = document.querySelector('.textarea'); if (!$target) { return false; } navigator.clipboard.readText().then(clipText => { $target.value = clipText; }); return false; } document.querySelector('.copyBtn').addEventListener('click', copyText, false);
ユーザーの許可を求める表示が出ますが、許可するとクリップボードにあるテキストがテキストエリアの内容に置き換わります。
ElectronでPuppeteerを使う場合の複数プラットフォーム対応
Electronで作っているデスクトップアプリ内で、Puppeteerを使った時のメモです。
結果的には、各プラットフォーム用のChromiumを手動でダウンロードして追加し、Puppeteerに対してChromiumのパスを明示的に指定する必要がありました。
npm install puppeteer
して開発しているうちは問題なかったのですが、開発環境と異なるプラットフォーム用にElectronをビルドして、単体のアプリケーションとして実行するとうまくいきませんでした。
Chromium revision is not downloaded. Run "npm install" or "yarn install" at Launcher.launch
というようなエラーが出ます。Chromiumが入ってないよ、とのこと。npm installしろ、と言われても、ビルドしてしまっているのでできません。
ビルドするプラットフォーム向けのChromiumを手動で追加する
Electronをビルドし、macOS版のアプリケーションのパッケージ内容を確認すると、/Contents/Resources/app.asar.unpacked/node_modules/puppeteer
以下に、.local-chromium
というディレクトリがあり、さらにその中にmac-722234
というディレクトリがありました。(6ケタの数字はバージョンのようなので、環境により異なると思います。)この中にChromium本体が入っています。
私の場合はMacで開発していて、Windows用にもビルドしましたが、win-unpacked/resources/app.asar.unpacked/node_modules/puppeteer/.local-chromium
の中にはmac-722234
ディレクトリしかありません。そこで、ここにwin64-722234
というディレクトリを作成し、Windows用のChromiumをダウンロードしてきて入れました。ディレクトリ構造としては、win64-722234/chrome-win
の中にchrome.exe
などのファイルが入っている状態です。
Chromiumのダウンロードはこちらからできます:https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html
続いて、Puppeteerの起動時にこのChromiumを見つけられるようパスを指定します。GitHubにそのままのissueがありましたので参考にしました:Run from Electron · Issue #2134 · puppeteer/puppeteer · GitHub
これでMacでもWindowsでも問題なく、単体のアプリケーション上でPuppeteerが実行できるようになりました。
また、エラーの原因を調べるにあたっては、electron-logというnpmパッケージが便利でした。こちらが詳しいです:
[Electron] ログをファイルに記録する - electron-log
最後に蛇足ながら、
Web上のExcelファイルからデータを読み取る(Node.js)
備忘録。
const request = require('request'); const Excel = require('exceljs'); const getBuffer = (url) => { return new Promise((resolve, reject) => { request(url, {encoding: null}, (error, response, body) => { if (error !== null) { reject(null); } const buffer = new Buffer.from(body); resolve(buffer); }); }); } const buffer = await getBuffer('https://...'); if (buffer === null) { console.log('Couldn\'t get the file'); return; } const excel = new Excel.Workbook(); await excel.xlsx.load(buffer); const sheet = excel.worksheets[0]; const row = sheet.getRow(1); const cellValue = row.getCell('C').value; //...
exceljsというnpmを使った。ライセンスはMIT。
HTTP(S)リクエストのところで、{encoding: null}
を入れておかないとExcelファイルとして読み込めずにエラーになる。
excel.xlsx.load()
のところでawaitしてるので、asyncな関数内で使ってください。
CSS: overscroll-behavior
端までスクロールした時の振る舞いを、CSSで指定できる、というものです。
仕様:CSS Overscroll Behavior Module Level 1
対応状況:Can I use... Support tables for HTML5, CSS3, etc
MDNが分かりやすいです:
overscroll-behavior - CSS: カスケーディングスタイルシート | MDN
仕様としてはまだ草案段階で、Safariは対応していません。
ですが、「適切な場面で指定しておけばWebページが使いやすくなるが、なくても問題が起こるわけではない」タイプの機能なので、いわゆる"プログレッシブエンハンスメント"なアプローチとして採用するのは有効だと思います。
スクロールバーが入れ子になった状態で使う
MDNでは、縦に長いWebページ内に、小さなチャットウィンドウが乗っかっている例を取り上げています。ほとんどのブラウザでは、チャットを最後までスクロールしたら背後のメインコンテンツがスクロールし始めますが、overscroll-behaviorにnone
を指定すると、これを止める(スクロールを伝播させない)ことができます。
デモ:
See the Pen
vYXKWJg by Yu Watanabe (@yuw27b)
on CodePen.
「overscroll-behavior: none」のボタンを押してから、水色のボックス内をスクロールしてみてください。端まで行っても後ろのメインコンテンツはスクロールしないはずです。
「none」は、ブラウザ既定の振る舞いも止まる
overscroll-behaviorにnone
を指定すると、上記のように外側の要素へのスクロール伝播が止まりますが、同時にスクロールが端に達した時のブラウザ既定の振る舞いが全て止まっています。例えばChromeでは上端までスクロールすると、跳ね返るようなアニメーションをしますが、body要素に{overscroll-behavior: none}
を指定すると、これがなくなって、ピタッと止まります(この感覚久しぶりです)。
ブラウザ既定の振る舞いはさせたいけど、スクロール伝播は止めたい、という場合はoverscroll-behavior: contain
を指定すればOKです。
スマートフォンでは、「上端までスクロールしてさらに引っ張る」と、ページの再読み込みが行われますが、AndroidのChromeではこの振る舞いも止まるようです(未検証)。iOSのChromeは未対応のようで、いつも通り再読み込みが行われてしまいました…。
x,y 個別指定もできます
「overscroll-behavior-x」「overscroll-behavior-y」というプロパティもあります。
「背後のコンテンツまでスクロールしないほうがいいな」という状況は時々あるので、そういう時は「overscroll-behavior」が有効に使えそうです。ですが、ユーザーが期待している「いつもの動作」を止めることもできてしまうので、あまり濫用しないほうが良さそうとも思いました。