@@ -14,6 +14,36 @@ document.addEventListener("DOMContentLoaded", () => {
1414 /** @type {Array<any> } */
1515 let allPosts = [ ] ;
1616
17+ // -----------------------------
18+ // URL パラメータ操作ヘルパ
19+ // -----------------------------
20+ function readInitialParams ( ) {
21+ const params = new URLSearchParams ( location . search ) ;
22+ // 優先順: tag -> q
23+ const tag = params . get ( "tag" ) || "" ;
24+ const q = params . get ( "q" ) || "" ;
25+ const category = params . get ( "category" ) || "" ;
26+
27+ const initial = tag || q ;
28+ if ( searchInput && initial ) {
29+ searchInput . value = initial ;
30+ }
31+ if ( categorySelect && category ) {
32+ categorySelect . value = category ;
33+ }
34+ }
35+
36+ // 現在の UI 状態を URL に反映(ページの再読み込みはしない)
37+ function updateUrlParams ( keyword , category ) {
38+ const params = new URLSearchParams ( ) ;
39+ if ( keyword ) params . set ( "q" , keyword ) ;
40+ if ( category ) params . set ( "category" , category ) ;
41+ const newUrl =
42+ location . pathname +
43+ ( params . toString ( ) ? "?" + params . toString ( ) : "" ) ;
44+ history . replaceState ( null , "" , newUrl ) ;
45+ }
46+
1747 // -----------------------------
1848 // JSON 読み込み
1949 // -----------------------------
@@ -22,6 +52,8 @@ document.addEventListener("DOMContentLoaded", () => {
2252 const res = await fetch ( "assets/data/blogList.json" ) ;
2353 if ( ! res . ok ) throw new Error ( `HTTP ${ res . status } ` ) ;
2454 allPosts = await res . json ( ) ;
55+ // 初期パラメータを読み取ってフィルタをプリセット
56+ readInitialParams ( ) ;
2557 render ( ) ;
2658 } catch ( err ) {
2759 console . error ( "Failed to load blog list:" , err ) ;
@@ -52,7 +84,7 @@ document.addEventListener("DOMContentLoaded", () => {
5284 )
5385 return false ;
5486
55- // キーワードフィルタ
87+ // キーワードフィルタ (タイトル/説明/カテゴリ/タグ)
5688 if ( keyword ) {
5789 const tags = Array . isArray ( post . tags )
5890 ? post . tags
@@ -76,6 +108,9 @@ document.addEventListener("DOMContentLoaded", () => {
76108 return true ;
77109 } ) ;
78110
111+ // フィルタ状態を URL に反映
112+ updateUrlParams ( keyword , categoryFilter ) ;
113+
79114 listEl . innerHTML = "" ;
80115
81116 if ( ! filtered . length ) {
@@ -132,24 +167,68 @@ document.addEventListener("DOMContentLoaded", () => {
132167 }
133168
134169 // タグ
135- if ( post . tags && post . tags . length ) {
170+ const tagsArr = Array . isArray ( post . tags )
171+ ? post . tags
172+ : String ( post . tags || "" )
173+ . split ( "," )
174+ . map ( ( t ) => t . trim ( ) )
175+ . filter ( Boolean ) ;
176+
177+ if ( tagsArr . length ) {
136178 const tagRow = document . createElement ( "div" ) ;
137179 tagRow . className = "card__tags" ;
138- post . tags . forEach ( ( t ) => {
180+ tagsArr . forEach ( ( t ) => {
139181 const tag = document . createElement ( "span" ) ;
140182 tag . className = "tag" ;
141183 tag . textContent = t ;
184+
185+ // タグはクリックでそのタグでフィルタ
186+ tag . addEventListener ( "click" , ( e ) => {
187+ e . stopPropagation ( ) ; // カードクリックを阻止
188+ if ( searchInput ) searchInput . value = t ;
189+ if ( categorySelect ) categorySelect . value = "" ;
190+ render ( ) ;
191+ // URL に tag(または q)として反映(履歴は積まない)
192+ const params = new URLSearchParams ( ) ;
193+ params . set ( "q" , t ) ;
194+ const newUrl =
195+ location . pathname + "?" + params . toString ( ) ;
196+ history . replaceState ( null , "" , newUrl ) ;
197+ } ) ;
198+
199+ // キーボード対応
200+ tag . tabIndex = 0 ;
201+ tag . addEventListener ( "keydown" , ( ev ) => {
202+ if ( ev . key === "Enter" || ev . key === " " ) {
203+ ev . preventDefault ( ) ;
204+ tag . click ( ) ;
205+ }
206+ } ) ;
207+
142208 tagRow . appendChild ( tag ) ;
143209 } ) ;
144210 body . appendChild ( tagRow ) ;
145211 }
146212
147213 card . appendChild ( body ) ;
148214
149- // カード全体クリックで記事ページへ
215+ // カード全体クリックで記事ページへ遷移
150216 card . addEventListener ( "click" , ( ) => {
151217 if ( post . contentPath ) {
152- window . location . href = post . contentPath ;
218+ // 現在のフィルタ状態をクエリに付与して遷移(戻ってきたときに復元しやすくする)
219+ const params = new URLSearchParams ( ) ;
220+ const q = ( searchInput ?. value || "" ) . trim ( ) ;
221+ const category = (
222+ categorySelect ?. value || ""
223+ ) . trim ( ) ;
224+ if ( q ) params . set ( "q" , q ) ;
225+ if ( category ) params . set ( "category" , category ) ;
226+ const target =
227+ post . contentPath +
228+ ( params . toString ( )
229+ ? "?" + params . toString ( )
230+ : "" ) ;
231+ window . location . href = target ;
153232 }
154233 } ) ;
155234
@@ -158,14 +237,18 @@ document.addEventListener("DOMContentLoaded", () => {
158237 }
159238
160239 // -----------------------------
161- // イベント
240+ // イベント登録
162241 // -----------------------------
163242 if ( searchInput ) {
164- searchInput . addEventListener ( "input" , render ) ;
243+ searchInput . addEventListener ( "input" , ( ) => {
244+ // タイプ中は即時フィルタ(必要に応じてデバウンスを追加してください)
245+ render ( ) ;
246+ } ) ;
165247 }
166248 if ( categorySelect ) {
167249 categorySelect . addEventListener ( "change" , render ) ;
168250 }
169251
252+ // 初回ロード
170253 loadPosts ( ) ;
171254} ) ;
0 commit comments