diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-07 12:44:17 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-07 12:44:17 +0900 |
| commit | 4694675635a449f6537272ddad2feb2a4724c41d (patch) | |
| tree | 8d376f782eae7cff6d09e35afe0b12349112614d /src | |
| parent | 5c56c4f74225ef9f8a5e992228cdab17eb029bea (diff) | |
events: 日付の改行と行ごとの共有ボタンを追加
「令和9年1月28日(木)」のような日付で列が広がるため、「令和〇年」の
直後で改行するようにした。各行にX・Instagram・Facebookの共有ボタンを
付け、日付・タイトル・時間と場所・申込みを文面にして共有する。
ボタンは行数が多いためHTMLには書かず、スクリプトで差し込んでいる。
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/ScheduleTable.astro | 118 | ||||
| -rw-r--r-- | src/styles/custom.css | 45 |
2 files changed, 162 insertions, 1 deletions
diff --git a/src/components/ScheduleTable.astro b/src/components/ScheduleTable.astro index dcf210d..870cd77 100644 --- a/src/components/ScheduleTable.astro +++ b/src/components/ScheduleTable.astro @@ -27,6 +27,10 @@ if (scope === 'main') { } const data: TableData = { columns: base.columns, rows: list }; + +// 「令和9年1月28日(木)」のような長い日付で列が広がるため、「令和9年」の直後で改行する。 +const dateHtml = (html: string) => + html.replace(/(令和[0-90-9]+年)(?!\s*<br)/, '$1<br />'); --- <table> @@ -37,7 +41,7 @@ const data: TableData = { columns: base.columns, rows: list }; { data.rows.map((row) => ( <tr> - <td set:html={row.date} /> + <td set:html={dateHtml(row.date)} /> <td set:html={row.title} /> <td set:html={row.place} /> <td set:html={row.note} /> @@ -47,3 +51,115 @@ const data: TableData = { columns: base.columns, rows: list }; } </tbody> </table> + +<script> + // 行ごとの共有ボタン(X・Instagram・Facebook)。 + // 行数が多いため、HTMLには書かず、このスクリプトで後から差し込む。 + // アイコンは CSS(mask-image)で描くので、ボタンには文字もSVGも入れない。 + if (!document.documentElement.dataset.scheduleShareInit) { + document.documentElement.dataset.scheduleShareInit = '1'; + + const KINDS: { kind: string; label: string }[] = [ + { kind: 'x', label: 'Xでシェア' }, + { kind: 'instagram', label: 'Instagramでシェア' }, + { kind: 'facebook', label: 'Facebookでシェア' }, + ]; + + function attachButtons() { + for (const row of document.querySelectorAll<HTMLTableRowElement>( + '.schedule-table tbody tr', + )) { + const titleCell = row.children[1]; + if (!titleCell || titleCell.querySelector('.schedule-share')) continue; + + const wrap = document.createElement('span'); + wrap.className = 'schedule-share'; + for (const { kind, label } of KINDS) { + const button = document.createElement('button'); + button.type = 'button'; + button.className = `schedule-share-btn is-${kind}`; + button.dataset.share = kind; + button.setAttribute('aria-label', label); + button.title = label; + wrap.appendChild(button); + } + titleCell.appendChild(wrap); + } + } + + const strip = (el: Element | null | undefined) => + (el?.textContent ?? '').replace(/\s+/g, ' ').trim(); + + // 共有する文面(日時+タイトル+時間と場所+申込み等) + function shareText(row: Element): string { + const cells = row.children; + const lines = [`${strip(cells[0])} ${strip(cells[1])}`]; + const place = strip(cells[2]); + const note = strip(cells[3]); + if (place) lines.push(place); + if (note) lines.push(note.length > 120 ? `${note.slice(0, 120)}…` : note); + return lines.join('\n'); + } + + function rowUrl(row: Element): string { + const base = location.origin + location.pathname; + return row.id ? `${base}#${row.id}` : base; + } + + document.addEventListener('click', async (event) => { + const target = event.target as HTMLElement | null; + const button = target?.closest<HTMLElement>('.schedule-share-btn'); + if (!button) return; + const row = button.closest('tr'); + if (!row) return; + + const text = shareText(row); + const url = rowUrl(row); + const kind = button.dataset.share; + + if (kind === 'x') { + window.open( + `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(url)}`, + '_blank', + 'noopener', + ); + return; + } + + if (kind === 'facebook') { + // Facebookのsharerは文面を指定できない(ページのOGPが使われる) + window.open( + `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`, + '_blank', + 'noopener', + ); + return; + } + + // Instagramはテキストを渡せる共有URLがないため、端末の共有メニューを使う + const share = (navigator as Navigator & { share?: (data: ShareData) => Promise<void> }) + .share; + if (share) { + try { + await share.call(navigator, { text, url }); + } catch { + // キャンセルされた場合は何もしない + } + return; + } + + try { + await navigator.clipboard.writeText(`${text}\n${url}`); + window.alert( + 'Instagram はスマートフォンの共有メニューから投稿できます(このブラウザは共有メニューに対応していません)。共有する文面をコピーしました。', + ); + } catch { + window.alert('Instagram はスマートフォンの共有メニューから投稿できます。'); + } + }); + + attachButtons(); + // アーカイブの絞り込みなどで行が増えた場合にも備える + document.addEventListener('click', () => setTimeout(attachButtons, 0)); + } +</script> diff --git a/src/styles/custom.css b/src/styles/custom.css index 843599c..4fd4829 100644 --- a/src/styles/custom.css +++ b/src/styles/custom.css @@ -297,6 +297,51 @@ body:has(.parks-controls) .sl-markdown-content p { white-space: nowrap; width: 1%; } +/* 行ごとの共有ボタン(アイコンは mask-image で描く) */ +.schedule-share { + display: inline-flex; + gap: 0.25rem; + margin-left: 0.5rem; + vertical-align: -2px; + white-space: nowrap; +} +.schedule-share-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.35rem; + height: 1.35rem; + padding: 0; + border: 1px solid var(--sl-color-gray-5); + border-radius: 4px; + background: var(--sl-color-bg); + color: var(--sl-color-gray-3); + cursor: pointer; + line-height: 1; + opacity: 0.8; +} +.schedule-share-btn::before { + content: ""; + width: 14px; + height: 14px; + background: currentColor; + -webkit-mask: var(--share-icon) center / contain no-repeat; + mask: var(--share-icon) center / contain no-repeat; +} +.schedule-share-btn:hover { + color: var(--sl-color-accent); + border-color: var(--sl-color-accent); + opacity: 1; +} +.schedule-share-btn.is-x { + --share-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M18.244%202.25h3.308l-7.227%208.26%208.502%2011.24H16.17l-5.214-6.817L4.99%2021.75H1.68l7.73-8.835L1.254%202.25H8.08l4.713%206.231%205.451-6.231Zm-1.161%2017.52h1.833L7.084%204.126H5.117l11.966%2015.644Z%27%2F%3E%3C%2Fsvg%3E"); +} +.schedule-share-btn.is-instagram { + --share-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Crect%20x%3D%273%27%20y%3D%273%27%20width%3D%2718%27%20height%3D%2718%27%20rx%3D%275%27%20fill%3D%27none%27%20stroke%3D%27black%27%20stroke-width%3D%272%27%2F%3E%3Ccircle%20cx%3D%2712%27%20cy%3D%2712%27%20r%3D%274%27%20fill%3D%27none%27%20stroke%3D%27black%27%20stroke-width%3D%272%27%2F%3E%3Ccircle%20cx%3D%2717.2%27%20cy%3D%276.8%27%20r%3D%271.2%27%2F%3E%3C%2Fsvg%3E"); +} +.schedule-share-btn.is-facebook { + --share-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M22%2012a10%2010%200%201%200-11.6%209.9v-7H7.9V12h2.5V9.8c0-2.5%201.5-3.9%203.8-3.9%201.1%200%202.2.2%202.2.2v2.4h-1.2c-1.2%200-1.6.8-1.6%201.6V12h2.7l-.4%202.9h-2.3v7A10%2010%200%200%200%2022%2012Z%27%2F%3E%3C%2Fsvg%3E"); +} /* ── 冒頭の英語・中国語・韓国語・ベトナム語の案内 ── */ .lang-intro { |
