Skip to content

Instantly share code, notes, and snippets.

@odanado
Created September 22, 2025 09:49
Show Gist options
  • Select an option

  • Save odanado/7efe1f083d36373fb9ee2be6b5d1e35a to your computer and use it in GitHub Desktop.

Select an option

Save odanado/7efe1f083d36373fb9ee2be6b5d1e35a to your computer and use it in GitHub Desktop.
フロントエンドカンファレンス東京2025の Twitter 検索 URL を作る
const sessions = [];
$$(".timetable .tableRow").forEach((row) => {
const startTime = row.querySelector(".timeCell .timeText:first-child").textContent.trim();
const endTime = row.querySelector(".timeCell .timeText:last-child").textContent.trim();
const presentations = Array.from(row.querySelectorAll(".presentationCell"));
if (presentations.length === 0) return;
presentations.forEach((x) => {
const room = x.querySelector(".cellRoomType").textContent.trim();
const title = x.querySelector(".cellTitle").textContent.trim();
sessions.push({
startTime,
endTime,
room,
title,
});
});
});
console.log(sessions)
const slides = [];
$$(".BodyContent_anchorToHeadings__uGxNv > h2").forEach((x) => {
const title = x.textContent.trim();
const maybeSlideUrl = x.nextElementSibling?.querySelector("a")?.href;
if (maybeSlideUrl) {
slides.push({
title,
url: maybeSlideUrl,
});
} else {
console.warn(`No slide URL found for title: ${title}`);
}
});
import { sessions } from "./sessions.mjs";
import { slides } from "./slides.mjs";
// 各ルームで使われていたハッシュタグ
const hashTags = {
"SESSION ROOM - A": "#fec_tokyo_room_a",
"SESSION ROOM - B": "#fec_tokyo_room_b",
};
// イベント開催日(必要なら環境変数 EVENT_DATE=YYYY-MM-DD で上書き可能)
const EVENT_DATE = process.env.EVENT_DATE || "2025-09-21";
// タイムゾーンサフィックス(クエリ内の since/until で使用)
const TZ_SUFFIX = "JST";
// タイトル -> スライドURL のルックアップ
const slideByTitle = new Map(slides.map((s) => [s.title, s.url]));
// HH:MM を 2桁ゼロ埋め
const pad2 = (n) => String(n).padStart(2, "0");
// "HH:MM" を since/until 用の "YYYY-MM-DD_HH:MM:SS_TZ" 文字列に変換
function toSinceUntil(dateStr, timeStr) {
const [h, m] = timeStr.split(":").map((x) => parseInt(x, 10));
const hh = pad2(h);
const mm = pad2(m);
return `${dateStr}_${hh}:${mm}:00_${TZ_SUFFIX}`;
}
function addDaysToDate(dateStr, days) {
if (!days) return dateStr;
const [y, mo, d] = dateStr.split("-").map((x) => parseInt(x, 10));
const dt = new Date(Date.UTC(y, mo - 1, d));
dt.setUTCDate(dt.getUTCDate() + days);
const ny = dt.getUTCFullYear();
const nmo = pad2(dt.getUTCMonth() + 1);
const nd = pad2(dt.getUTCDate());
return `${ny}-${nmo}-${nd}`;
}
function addMinutesToTime(dateStr, timeStr, deltaMinutes) {
const [h, m] = timeStr.split(":").map((x) => parseInt(x, 10));
let total = h * 60 + m + deltaMinutes;
const minutesInDay = 24 * 60;
let addDays = 0;
if (total >= minutesInDay) {
addDays = Math.floor(total / minutesInDay);
total = total % minutesInDay;
} else if (total < 0) {
addDays = Math.floor((total - minutesInDay + 1) / minutesInDay); // negative wrap
total = ((total % minutesInDay) + minutesInDay) % minutesInDay;
}
const nh = Math.floor(total / 60);
const nm = total % 60;
const newDateStr = addDays ? addDaysToDate(dateStr, addDays) : dateStr;
const newTimeStr = `${pad2(nh)}:${pad2(nm)}`;
return { dateStr: newDateStr, timeStr: newTimeStr };
}
// Twitter(X) 検索URLを生成
function buildTwitterSearchUrl({ room, startTime, endTime }) {
const tag = hashTags[room] || "";
const since = toSinceUntil(EVENT_DATE, startTime);
const { dateStr: uDate, timeStr: uTime } = addMinutesToTime(
EVENT_DATE,
endTime,
3,
);
const until = toSinceUntil(uDate, uTime);
const query = [tag, `since:${since}`, `until:${until}`].filter(Boolean).join(" ");
const base = "https://x.com/search"; // 旧 twitter.com でも可
const url = `${base}?q=${encodeURIComponent(query)}&src=typed_query&f=live`;
return { url, label: tag || "Twitter検索" };
}
// Markdown の行を出力
function printMarkdownTable(rows) {
console.log(`| 時刻 | タイトル | Twitter検索 |`);
console.log(`|---|---|---|`);
rows.forEach(({ timeCell, titleCell, twitterCell }) => {
console.log(`| ${timeCell} | ${titleCell} | ${twitterCell} |`);
});
}
// メイン処理:sessions と slides を突合して表を生成
function main() {
const EXCLUDED_TITLES = new Set(["オープニング", "クロージング"]);
const rows = sessions.filter((s) => !EXCLUDED_TITLES.has(s.title)).map((s) => {
const slideUrl = slideByTitle.get(s.title);
const titleCell = slideUrl ? `[${s.title}](${slideUrl})` : s.title;
const { url: twUrl, label } = buildTwitterSearchUrl(s);
const twitterCell = `[${label}](${twUrl})`;
const timeCell = `${s.startTime} ~ ${s.endTime}`;
return { timeCell, titleCell, twitterCell };
});
printMarkdownTable(rows);
}
main();
export const sessions = [
{
startTime: "10:30",
endTime: "11:00",
room: "SESSION ROOM - A",
title: "オープニング",
},
{
startTime: "11:00",
endTime: "11:30",
room: "SESSION ROOM - A",
title: "Bon Voyage! CSS Ecosystem Meets Standards, now?",
},
{
startTime: "11:00",
endTime: "11:30",
room: "SESSION ROOM - B",
title: "CSS Linter の現在地 ― 2025年のベストプラクティスを探る",
},
{
startTime: "11:40",
endTime: "12:10",
room: "SESSION ROOM - A",
title:
"見た目は動く。でも使えない、、アクセシブルなUIの実装アンチパターン集",
},
{
startTime: "11:40",
endTime: "12:10",
room: "SESSION ROOM - B",
title: "日本語縦書きWebの現在地 2025",
},
{
startTime: "13:45",
endTime: "14:15",
room: "SESSION ROOM - A",
title: "実践AIチャットボットUI実装入門",
},
{
startTime: "13:45",
endTime: "14:15",
room: "SESSION ROOM - B",
title: "LLMとPlaywright/reg-suitを活用したjQueryリファクタリングの実際",
},
{
startTime: "14:25",
endTime: "14:55",
room: "SESSION ROOM - A",
title:
"そのJavaScript、V8が泣いてます。V8の気持ちを理解して書くパフォーマンス最適化",
},
{
startTime: "14:25",
endTime: "14:55",
room: "SESSION ROOM - B",
title: "フロントエンドパフォーマンスチューニングで Web 技術を深掘り直す",
},
{
startTime: "15:05",
endTime: "15:35",
room: "SESSION ROOM - A",
title:
"爆速でプロダクトをリリースしようと思ったらマイクロフロントエンドを選んでいた",
},
{
startTime: "15:05",
endTime: "15:35",
room: "SESSION ROOM - B",
title: "フロントエンド開発に役立つクライアントプログラム共通のノウハウ",
},
{
startTime: "15:45",
endTime: "16:15",
room: "SESSION ROOM - A",
title:
"Reduxモダナイズ 〜コードのモダン化を通して、将来のライブラリ移行に備える〜",
},
{
startTime: "15:45",
endTime: "16:15",
room: "SESSION ROOM - B",
title: '"フロントエンドの技術"を移行する技術',
},
{
startTime: "16:25",
endTime: "16:55",
room: "SESSION ROOM - A",
title: "TS - Type = JS ?",
},
{
startTime: "17:15",
endTime: "17:25",
room: "SESSION ROOM - A",
title: "愛すべき Image API - 前世紀の技を現代で",
},
{
startTime: "17:15",
endTime: "17:25",
room: "SESSION ROOM - B",
title: "Local Peer-to-Peer APIはどのように使われていくのか?",
},
{
startTime: "17:25",
endTime: "17:35",
room: "SESSION ROOM - A",
title: "IME vs Input Field Shortcuts: Enhancing Text Input Accessibility",
},
{
startTime: "17:25",
endTime: "17:35",
room: "SESSION ROOM - B",
title: "意外と知らない input[type=number] の仕様と、全角対応について",
},
{
startTime: "17:35",
endTime: "17:45",
room: "SESSION ROOM - A",
title: "メディアクエリだけではない、レスポンシブ対応の考え方と実装",
},
{
startTime: "17:35",
endTime: "17:45",
room: "SESSION ROOM - B",
title: "先進的なCSS関数で実現する動的スタイリング",
},
{
startTime: "17:45",
endTime: "17:55",
room: "SESSION ROOM - A",
title: "Playwright はどのようにクロスブラウザをサポートしているのか",
},
{
startTime: "17:45",
endTime: "17:55",
room: "SESSION ROOM - B",
title: "reading-flow プロパティから見るアクセシビリティ的懸念点",
},
{
startTime: "17:55",
endTime: "18:05",
room: "SESSION ROOM - A",
title:
"ブラウザストレージを活用した、複数アプリをまたぐ永続化とリアクティブな同期",
},
{
startTime: "17:55",
endTime: "18:05",
room: "SESSION ROOM - B",
title: "フロントエンドで実現するアクセス制御入門",
},
{
startTime: "18:05",
endTime: "18:15",
room: "SESSION ROOM - A",
title: "Web技術を最大限活用してRAW画像を現像する",
},
{
startTime: "18:05",
endTime: "18:15",
room: "SESSION ROOM - B",
title: "SSGの限界を破る、再ビルド不要なサイト",
},
{
startTime: "18:15",
endTime: "18:25",
room: "SESSION ROOM - A",
title:
"Yahoo! 知恵袋におけるFeature Flag活用 〜安全で柔軟なリリースを目指して〜",
},
{
startTime: "18:15",
endTime: "18:25",
room: "SESSION ROOM - B",
title: "コードの向こう側 × 校正ツール × チーム:言葉をつなぐ体験",
},
{
startTime: "18:45",
endTime: "19:00",
room: "SESSION ROOM - A",
title: "クロージング",
},
];
export const slides = [
{
title: "Bon Voyage! CSS Ecosystem Meets Standards, now?",
url: "https://sakupi01.github.io/slides/ja/2025_09_21_css-ecosystem-meets-standards-now/",
},
{
title: "CSS Linter の現在地 ― 2025年のベストプラクティスを探る",
url: "https://speakerdeck.com/ryo_manba/css-linter-noxian-zai-di-2025nian-nobesutopurakuteisuwotan-ru",
},
{
title: "日本語縦書きWebの現在地 2025",
url: "https://speakerdeck.com/berlysia/japanese-vertical-writing-on-the-web-the-state-of-play-in-2025-a19f0026-9473-4863-b75d-35eae77c7e01",
},
{
title: "実践AIチャットボットUI実装入門",
url: "https://speakerdeck.com/syumai/practical-ai-chat-bot-ui-implementation",
},
{
title: "LLMとPlaywright/reg-suitを活用したjQueryリファクタリングの実際",
url: "https://speakerdeck.com/kinocoboy2/reg-suitwohuo-yong-sita-jqueryrihuakutaringunoshi-ji",
},
{
title:
"そのJavaScript、V8が泣いてます。V8の気持ちを理解して書くパフォーマンス最適化",
url: "https://speakerdeck.com/riyaamemiya/sonojavascript-v8gaqi-itemasu-v8noqi-chi-tiwoli-jie-siteshu-kupahuomansuzui-shi-hua",
},
{
title: "フロントエンドパフォーマンスチューニングで Web 技術を深掘り直す",
url: "https://speakerdeck.com/progfay/pahuomansutiyuningude-web-ji-shu-woshen-jue-rizhi-su",
},
{
title:
"爆速でプロダクトをリリースしようと思ったらマイクロフロントエンドを選んでいた",
url: "https://speakerdeck.com/kakehashi/shipping-fast-with-micro-frontends",
},
{
title: "フロントエンド開発に役立つクライアントプログラム共通のノウハウ",
url: "https://speakerdeck.com/nrslib/universal-client-side-programming-best-practices-for-frontend-development",
},
{
title:
"Reduxモダナイズ 〜コードのモダン化を通して、将来のライブラリ移行に備える〜",
url: "https://speakerdeck.com/pvcresin/reduxmodanaizu-kodonomodanhua-wotong-site-jiang-lai-noraiburariyi-xing-nibei-eru",
},
{
title: '"フロントエンドの技術"を移行する技術',
url: "https://www.docswell.com/s/toshi_toma/Z44R7Y-frontend-migrations",
},
{
title: "TS - Type = JS ?",
url: "https://www.docswell.com/s/jxck/5M628L-ts-type-js",
},
{
title: "Local Peer-to-Peer APIはどのように使われていくのか?",
url: "https://speakerdeck.com/hal_spidernight/local-peer-to-peer-apihadonoyounishi-wareteikunoka",
},
{
title: "IME vs Input Field Shortcuts: Enhancing Text Input Accessibility",
url: "https://ef81sp.github.io/ime-vs-input-keyboard-shortcut/1",
},
{
title: "Playwright はどのようにクロスブラウザをサポートしているのか",
url: "https://speakerdeck.com/yotahada3/playwright-cross-browser-support",
},
{
title: "reading-flow プロパティから見るアクセシビリティ的懸念点",
url: "https://speakerdeck.com/ayamotokohei/reading-flow-puropateikarajian-ru-akusesibiriteide-xuan-nian-dian",
},
{
title:
"ブラウザストレージを活用した、複数アプリをまたぐ永続化とリアクティブな同期",
url: "https://speakerdeck.com/teppei0717/burauzasutoreziwohuo-yong-sita-fu-shu-apuriwomataguyong-sok-hua-toriakuteibunatong-qi",
},
{
title: "フロントエンドで実現するアクセス制御入門",
url: "https://speakerdeck.com/echanoknan/fc-tokyo2025-hurontoendodeshi-xian-suruakusesuzhi-yu-ru-men",
},
{
title: "Web技術を最大限活用してRAW画像を現像する",
url: "https://speakerdeck.com/ssssota/developing-raw-images-on-the-web",
},
{
title: "SSGの限界を破る、再ビルド不要なサイト",
url: "https://speakerdeck.com/reyalka/ssg-noxian-jie-wopo-ru-zai-birudobu-yao-nasaito",
},
{
title:
"Yahoo! 知恵袋におけるFeature Flag活用 〜安全で柔軟なリリースを目指して〜",
url: "https://speakerdeck.com/l1lhu1hu1/yahoo-zhi-hui-dai-niokerufeature-flaghuo-yong-an-quan-derou-ruan-naririsuwomu-zhi-site",
},
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment