55 */
66
77import { findCollisions } from "./lib/collisions.js" ;
8+ import { tokenizeJson } from "./lib/highlight.js" ;
89import {
910 emptyFormState ,
1011 fromEventJson ,
@@ -228,7 +229,6 @@ async function startEditor(repo: string): Promise<void> {
228229 }
229230 badge . textContent = draftValid ? "✓ Ready" : "Incomplete" ;
230231 badge . className = draftValid ? "ok" : "invalid" ;
231- propose . textContent = isNew ? "Add event" : "Propose change" ;
232232 editDirect . disabled = ! isNew && editSlug === null ;
233233 editDirect . title =
234234 ! isNew && editSlug === null
@@ -296,7 +296,48 @@ async function startEditor(repo: string): Promise<void> {
296296 } ) ;
297297
298298 // --- event listing: contents API first, Pages feed.json fallback -------
299- const select = el < HTMLSelectElement > ( "event-select" ) ;
299+ // Rendered as a filter-as-you-type combobox over the loaded events.
300+ const combo = el < HTMLDivElement > ( "event-combo" ) ;
301+ const comboInput = el < HTMLInputElement > ( "event-combo-input" ) ;
302+ const comboList = el < HTMLUListElement > ( "event-combo-list" ) ;
303+
304+ function eventLabel ( event : OteEvent ) : string {
305+ const day = ( event . startDate ?? "????" ) . split ( "T" ) [ 0 ] ;
306+ return `${ day } — ${ event . name ?? event . id } ` ;
307+ }
308+
309+ function renderComboList ( query : string ) : void {
310+ comboList . textContent = "" ;
311+ const q = query . trim ( ) . toLowerCase ( ) ;
312+ const hits = listed
313+ . map ( ( entry , index ) => ( { entry, index } ) )
314+ . filter ( ( { entry } ) => eventLabel ( entry . event ) . toLowerCase ( ) . includes ( q ) )
315+ . slice ( 0 , 8 ) ;
316+ comboList . hidden = hits . length === 0 ;
317+ for ( const { entry, index } of hits ) {
318+ const li = document . createElement ( "li" ) ;
319+ const button = document . createElement ( "button" ) ;
320+ button . type = "button" ;
321+ button . textContent = eventLabel ( entry . event ) ;
322+ // mousedown, not click: it must win over the input's blur
323+ button . addEventListener ( "mousedown" , ( e ) => {
324+ e . preventDefault ( ) ;
325+ comboInput . value = eventLabel ( entry . event ) ;
326+ comboList . hidden = true ;
327+ pickEvent ( index ) ;
328+ } ) ;
329+ li . append ( button ) ;
330+ comboList . append ( li ) ;
331+ }
332+ }
333+
334+ comboInput . addEventListener ( "focus" , ( ) => renderComboList ( "" ) ) ;
335+ comboInput . addEventListener ( "input" , ( ) =>
336+ renderComboList ( comboInput . value ) ,
337+ ) ;
338+ comboInput . addEventListener ( "blur" , ( ) => {
339+ setTimeout ( ( ) => ( comboList . hidden = true ) , 150 ) ;
340+ } ) ;
300341
301342 async function loadEvents ( ) : Promise < void > {
302343 const listing = parseContentsListing ( await fetchJson ( contentsApiUrl ( repo ) ) ) ;
@@ -319,18 +360,11 @@ async function startEditor(repo: string): Promise<void> {
319360 ) ;
320361 }
321362 }
322- select . textContent = "" ;
323- const placeholder = document . createElement ( "option" ) ;
324- placeholder . value = "" ;
325- placeholder . textContent =
326- listed . length > 0 ? "Choose an event…" : "(no events found)" ;
327- select . append ( placeholder ) ;
328- listed . forEach ( ( { event } , index ) => {
329- const option = document . createElement ( "option" ) ;
330- option . value = String ( index ) ;
331- option . textContent = `${ event . startDate ?? "????" } — ${ event . name ?? event . id } ` ;
332- select . append ( option ) ;
333- } ) ;
363+ comboInput . placeholder =
364+ listed . length > 0
365+ ? "Type to filter, or pick an event…"
366+ : "No events found in this repository" ;
367+ comboInput . disabled = listed . length === 0 ;
334368 refresh ( ) ; // collision checks were waiting for the listing
335369 }
336370
@@ -342,7 +376,7 @@ async function startEditor(repo: string): Promise<void> {
342376 ) ) {
343377 radio . addEventListener ( "input" , ( ) => {
344378 isNew = radio . value === "new" ;
345- select . hidden = isNew ;
379+ combo . hidden = isNew ;
346380 touched = new Set ( ) ;
347381 submitAttempted = false ;
348382 if ( isNew ) {
@@ -357,8 +391,8 @@ async function startEditor(repo: string): Promise<void> {
357391 } ) ;
358392 }
359393
360- select . addEventListener ( "input" , ( ) => {
361- const chosen = listed [ Number ( select . value ) ] ;
394+ function pickEvent ( index : number ) : void {
395+ const chosen = listed [ index ] ;
362396 if ( ! chosen ) return ;
363397 editSlug = chosen . slug ;
364398 state = fromEventJson ( chosen . event , chosen . slug ?? "" ) ;
@@ -367,7 +401,7 @@ async function startEditor(repo: string): Promise<void> {
367401 touched = new Set ( ) ;
368402 submitAttempted = false ;
369403 render ( extraFieldsFor ( chosen . event , profile ) ) ;
370- } ) ;
404+ }
371405
372406 // --- outputs --------------------------------------------------------------
373407 const fallback = el < HTMLElement > ( "fallback" ) ;
@@ -390,44 +424,21 @@ async function startEditor(repo: string): Promise<void> {
390424
391425 // --- review step ----------------------------------------------------------
392426 const review = el < HTMLDialogElement > ( "review" ) ;
393- const reviewSummary = el < HTMLDListElement > ( "review-summary" ) ;
394427 const reviewJson = el < HTMLPreElement > ( "review-json" ) ;
395428
396- function summaryRow ( term : string , value : string ) : void {
397- if ( ! value ) return ;
398- const dt = document . createElement ( "dt" ) ;
399- dt . textContent = term ;
400- const dd = document . createElement ( "dd" ) ;
401- dd . textContent = value ;
402- reviewSummary . append ( dt , dd ) ;
403- }
404-
405429 function openReview ( ) : void {
406- const event = toEventJson ( state ) ;
407- reviewSummary . textContent = "" ;
408- summaryRow ( "Event" , state . name ) ;
409- summaryRow (
410- "When" ,
411- [
412- state . startDate ,
413- ! state . allDay && state . startTime ,
414- ( state . endDate || state . endTime ) && "→" ,
415- state . endDate ,
416- ! state . allDay && state . endTime ,
417- `(${ state . timezone } )` ,
418- ]
419- . filter ( Boolean )
420- . join ( " " ) ,
421- ) ;
422- summaryRow (
423- "Where" ,
424- [ state . venue , state . onlineUrl ] . filter ( Boolean ) . join ( " · " ) ,
425- ) ;
426- summaryRow (
427- "File" ,
428- `events/${ state . slug } .json ${ isNew ? "(new)" : "(update)" } ` ,
429- ) ;
430- reviewJson . textContent = JSON . stringify ( event , null , 2 ) ;
430+ const json = JSON . stringify ( toEventJson ( state ) , null , 2 ) ;
431+ reviewJson . textContent = "" ;
432+ for ( const token of tokenizeJson ( json ) ) {
433+ if ( token . type === "plain" ) {
434+ reviewJson . append ( token . text ) ;
435+ } else {
436+ const span = document . createElement ( "span" ) ;
437+ span . className = `j-${ token . type } ` ;
438+ span . textContent = token . text ;
439+ reviewJson . append ( span ) ;
440+ }
441+ }
431442 review . showModal ( ) ;
432443 }
433444
0 commit comments