この
前回の
SNS シェア機能

記事下部に
とりあえず X と
各 SNS の
// Xconst generateXShareLink = (url: string, title: string) => `https://twitter.com/intent/tweet?url=${encodeURIComponent(`${site.url.base}${url}`)}&title=${encodeURIComponent(`${title} | ${site.title}`)}`;
// Facebookconst generateFacebookShareLink = (url: string) => `https://www.facebook.com/sharer.php?u=${encodeURIComponent(`${site.url.base}${url}`)}`;
// Hatena Bookmarkconst generateHatebuSaveLink = (url: string, title: string) => `https://b.hatena.ne.jp/add?mode=confirm&url=${encodeURIComponent(`${site.url.base}${url}`)}&title=${title} | ${site.title}`;
encodeURIComponent
を
コピーリンク
上述したように
コピーボタンを
/** * Copies a given URL to the clipboard and displays a success toast message. * * @param url - The URL to be copied. */const handleCopyLink = (url: string) => void window.navigator.clipboard.writeText(`${site.url.base}${url}`).then(() => toast.success('Link copied.'));
Clipboard API を
記事・次の 記事
前の
記事下部に
単純にnull
を
/** * Retrieves the previous and next posts based on a target post ID. * * @param targetId - The ID of the target post. * @returns An object containing the previous and next post, or null if there are none. */const getPager = (targetId: string) => { const targetPosts = [ null, ...allPosts .filter((post) => post.status === 'published') .filter((post) => post._id) .sort((a, b) => new Date(a.publishedAt).getTime() - new Date(b.publishedAt).getTime()), null, ];
const activeIndex = targetPosts.findIndex((post) => targetId === post?._id); const prev = activeIndex !== 0 ? targetPosts[activeIndex - 1] : null; const next = activeIndex !== targetPosts.length - 1 ? targetPosts[activeIndex + 1] : null;
return { prev, next };};
ここでの
本番環境に
提案
関連記事の
同じ
まだ
オライリーの
一応、
const relatedPosts = fisherYatesShuffle( allPosts.filter((post) => post.status === 'published' && post._id !== id && post.category === category), ).slice(0, 5);
/** * Performs Fisher-Yates shuffle on the given array. * * @template T - The type of elements in the array. * @param array - The array to be shuffled. * @returns The shuffled array. */const fisherYatesShuffle = <T>(array: T[]) => { const copy = [...array];
copy.forEach((_, index, arr) => { const i = arr.length - 1 - index; const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j] as T, arr[i] as T]; });
return copy;};
Fisher–Yates shuffleと
検索機能

フッターに
⌘ + K
or ^ + K
でも
React.useEffect(() => { const down = (e: KeyboardEvent) => { if ((e.key === 'k' && (e.metaKey || e.ctrlKey)) || e.key === '/') { if ( (e.target instanceof HTMLElement && e.target.isContentEditable) || e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLSelectElement ) { return; }
e.preventDefault(); setOpen((open) => !open); } };
document.addEventListener('keydown', down); return () => document.removeEventListener('keydown', down);}, []);
検索全般は
ただ、
他の 細々とした 修正
そのマイナーアップデート以外にも
リスト型に
記事一覧を
以前は
それは
小さいfont-variant-numeric: tabular-nums;
をtabular-nums
)。
デフォルトだと
また、font-feature-settings: "palt"
を
シャドーを 追加
画面上部にダークモード限定の
視覚的な
抜粋を 自動生成
投稿のOGP の
さすがに
const Post = defineDocumentType(() => ({ ... computedFields: { excerpt: { description: 'The description of the post (120 words or less)', type: 'string', resolve: async ({ body: { raw } }) => await createExcerpt(raw), }, },}));
/** * Generates an excerpt from a given raw string. * * @param raw - The raw string to generate the excerpt from. * @returns The generated excerpt. */const createExcerpt = async (raw: string) => { const maxWords = 120; const stripped = (await remark().use(strip).process(raw)).toString(); const urlWithLineBreakRegex = /^(?:\r\n|\n)(https?:\/\/\S+)(?:\r\n|\n)/gm; const whitespaceRegex = /\s+/g; const excerpt = stripped .trim() .replaceAll(urlWithLineBreakRegex, '') .replaceAll(whitespaceRegex, '') .slice(0, maxWords); return stripped.length > maxWords ? excerpt + '...' : excerpt;};
Contentlayer での
プリフェッチで 404 エラー
Atom のNext.js の
プリフェッチを
強化
コードブロックのすでに
- コード行数を
追加 (するかしないかは 選べる) - diff 機能を
追加 (他の 言語の ハイライトと 併せて 使える。 コード行数と 併用不可) - 単語の
ハイライト機能を 追加 - 行の
ハイライト機能を 追加
全部
const convertTextToWordFrequency = (text: string): Map<string, number> => { const arr = text.split(' '); const wordCountMap = new Map<string, number>();
arr.forEach((word) => wordCountMap.set(word, (wordCountMap.get(word) ?? 0) + 1));
return wordCountMap;};
export const isNoteCreatableFromMagazine = (noteText: string, magazineText: string): boolean => { let noteWordsArray = noteText.split(' '); const noteWordsArray = noteText.split(' '); const magazineWordCountMap = convertTextToWordFrequency(magazineText);
for (const word of noteWordsArray) { const magazineWordCount = magazineWordCountMap.get(word) ?? 0; if (magazineWordCount > 0) { magazineWordCountMap.set(word, magazineWordCountMap.get(word) - 1); } else { return false; } }
return true;};
ごに
さいひと
次の