@@ -32,6 +32,45 @@ ModuleRegistry.registerModules([AllCommunityModule]);
3232
3333const GRID_STATE_KEY = 'filing-grid-column-state' ;
3434
35+ function hash32 ( str : string ) : number {
36+ let h = 2166136261 >>> 0 ;
37+ for ( let i = 0 ; i < str . length ; i ++ ) {
38+ h ^= str . charCodeAt ( i ) ;
39+ h = Math . imul ( h , 16777619 ) >>> 0 ;
40+ }
41+ return h ;
42+ }
43+
44+ function mixHash ( a : number , b : number ) : number {
45+ let h = a ^ b ;
46+ h = Math . imul ( h ^ ( h >>> 16 ) , 0x45d9f3b ) >>> 0 ;
47+ h = Math . imul ( h ^ ( h >>> 16 ) , 0x45d9f3b ) >>> 0 ;
48+ return h ^ ( h >>> 16 ) ;
49+ }
50+
51+ const tagColorCache = new Map < string , string > ( ) ;
52+
53+ function tagColor ( str : string ) : string {
54+ const key = str . trim ( ) . toLowerCase ( ) ;
55+ let color = tagColorCache . get ( key ) ;
56+ if ( color ) return color ;
57+
58+ const h = mixHash ( hash32 ( key ) , hash32 ( '14' ) ) ;
59+ const hue = ( h % 360 + 360 ) % 360 ;
60+ const sat = 68 + ( ( h >> 8 ) % 22 ) ;
61+ const lig = 74 + ( ( h >> 16 ) % 10 ) ;
62+ const s = sat / 100 , l = lig / 100 ;
63+ const a = s * Math . min ( l , 1 - l ) ;
64+ const f = ( n : number ) => {
65+ const k = ( n + hue / 30 ) % 12 ;
66+ const c = l - a * Math . max ( - 1 , Math . min ( k - 3 , 9 - k , 1 ) ) ;
67+ return Math . round ( 255 * c ) . toString ( 16 ) . padStart ( 2 , '0' ) ;
68+ } ;
69+ color = `#${ f ( 0 ) } ${ f ( 8 ) } ${ f ( 4 ) } ` ;
70+ tagColorCache . set ( key , color ) ;
71+ return color ;
72+ }
73+
3574interface ColumnInfo {
3675 colId : string ;
3776 headerName : string ;
@@ -388,6 +427,22 @@ export class FilingResultsGridComponent implements OnInit {
388427 columnList = signal < ColumnInfo [ ] > ( [ ] ) ;
389428 headerContextMenu = signal < { x : number ; y : number ; colId : string ; headerName : string } | null > ( null ) ;
390429
430+ @HostListener ( 'mousedown' , [ '$event' ] )
431+ onMouseDown ( event : MouseEvent ) : void {
432+ if ( event . button !== 1 ) return ; // only middle click
433+ const row = ( event . target as HTMLElement ) . closest ( '.ag-row' ) ;
434+ if ( ! row ) return ;
435+ // Prevent browser auto-scroll and AG Grid's middle-click handling
436+ event . preventDefault ( ) ;
437+ const rowIndex = row . getAttribute ( 'row-index' ) ;
438+ if ( rowIndex == null ) return ;
439+ const rowNode = this . gridApi ?. getDisplayedRowAtIndex ( Number ( rowIndex ) ) ;
440+ const filing = rowNode ?. data as Filing | undefined ;
441+ if ( filing ?. accessionNumber ) {
442+ window . open ( `/filings/document/${ filing . accessionNumber } /1` , '_blank' ) ;
443+ }
444+ }
445+
391446 @HostListener ( 'contextmenu' , [ '$event' ] )
392447 onHostContextMenu ( event : MouseEvent ) : void {
393448 const headerCell = ( event . target as HTMLElement ) . closest ( '.ag-header-cell' ) ;
@@ -437,6 +492,7 @@ export class FilingResultsGridComponent implements OnInit {
437492
438493 /** Base column definitions — order/widths/visibility may be overridden by saved state */
439494 private readonly baseColumnDefs : ColDef < Filing > [ ] = [
495+ // --- Default visible columns (in order) ---
440496 {
441497 field : 'ticker' ,
442498 headerName : 'Ticker' ,
@@ -450,23 +506,56 @@ export class FilingResultsGridComponent implements OnInit {
450506 return span ;
451507 } ,
452508 } ,
509+ {
510+ field : 'formType' ,
511+ headerName : 'Form' ,
512+ width : 90 ,
513+ cellStyle : { color : '#61afef' , fontWeight : '500' } ,
514+ } ,
453515 {
454516 field : 'companyConformedName' ,
455517 headerName : 'Company' ,
456518 width : 220 ,
457519 flex : 1 ,
458520 } ,
459521 {
460- field : 'formType' ,
461- headerName : 'Form Type' ,
462- width : 110 ,
522+ field : 'tags' ,
523+ headerName : 'Tags' ,
524+ width : 180 ,
525+ sortable : false ,
526+ filter : false ,
463527 cellRenderer : ( params : ICellRendererParams ) => {
464- if ( ! params . value ) return '' ;
465- const span = document . createElement ( 'span' ) ;
466- span . textContent = params . value ;
467- span . style . cssText =
468- 'display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#0e639c;color:white;border-radius:2px;' ;
469- return span ;
528+ if ( ! params . value ?. length ) return '' ;
529+ const container = document . createElement ( 'div' ) ;
530+ container . style . cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:nowrap;overflow:hidden;' ;
531+ for ( const tag of params . value ) {
532+ const color = tagColor ( tag ) ;
533+ const item = document . createElement ( 'span' ) ;
534+ item . style . cssText = `display:inline-flex;align-items:center;gap:3px;white-space:nowrap;color:${ color } ;font-size:12px;` ;
535+ // Tiny tag icon (SVG)
536+ const svg = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'svg' ) ;
537+ svg . setAttribute ( 'width' , '10' ) ;
538+ svg . setAttribute ( 'height' , '10' ) ;
539+ svg . setAttribute ( 'viewBox' , '0 0 24 24' ) ;
540+ svg . setAttribute ( 'fill' , 'none' ) ;
541+ svg . setAttribute ( 'stroke' , color ) ;
542+ svg . setAttribute ( 'stroke-width' , '2.5' ) ;
543+ svg . setAttribute ( 'stroke-linecap' , 'round' ) ;
544+ svg . setAttribute ( 'stroke-linejoin' , 'round' ) ;
545+ svg . style . cssText = 'flex-shrink:0;' ;
546+ const path = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'path' ) ;
547+ path . setAttribute ( 'd' , 'M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z' ) ;
548+ svg . appendChild ( path ) ;
549+ const circle = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'circle' ) ;
550+ circle . setAttribute ( 'cx' , '7.5' ) ;
551+ circle . setAttribute ( 'cy' , '7.5' ) ;
552+ circle . setAttribute ( 'r' , '1' ) ;
553+ svg . appendChild ( circle ) ;
554+ item . appendChild ( svg ) ;
555+ item . appendChild ( document . createTextNode ( tag ) ) ;
556+ container . appendChild ( item ) ;
557+ }
558+ return container ;
470559 } ,
471560 } ,
472561 {
@@ -482,128 +571,126 @@ export class FilingResultsGridComponent implements OnInit {
482571 valueFormatter : ( params : ValueFormatterParams ) =>
483572 params . value ? new Date ( params . value ) . toLocaleDateString ( 'en-US' , { year : 'numeric' , month : 'short' , day : 'numeric' } ) : '' ,
484573 } ,
574+ {
575+ field : 'size' ,
576+ headerName : 'Size' ,
577+ width : 100 ,
578+ valueFormatter : ( params : ValueFormatterParams ) => {
579+ if ( ! params . value ) return '' ;
580+ const kb = params . value / 1024 ;
581+ if ( kb < 1024 ) return `${ kb . toFixed ( 0 ) } KB` ;
582+ return `${ ( kb / 1024 ) . toFixed ( 1 ) } MB` ;
583+ } ,
584+ } ,
585+ // --- Hidden by default ---
485586 {
486587 field : 'datePublished' ,
487588 headerName : 'Date Published' ,
488589 width : 130 ,
590+ hide : true ,
489591 valueFormatter : ( params : ValueFormatterParams ) =>
490592 params . value ? new Date ( params . value ) . toLocaleDateString ( 'en-US' , { year : 'numeric' , month : 'short' , day : 'numeric' } ) : '' ,
491593 } ,
492- {
493- field : 'tags' ,
494- headerName : 'Tags' ,
495- width : 180 ,
496- sortable : false ,
497- filter : false ,
498- cellRenderer : ( params : ICellRendererParams ) => {
499- if ( ! params . value ?. length ) return '' ;
500- const container = document . createElement ( 'div' ) ;
501- container . style . cssText = 'display:flex;align-items:center;gap:4px;flex-wrap:nowrap;overflow:hidden;' ;
502- for ( const tag of params . value ) {
503- const span = document . createElement ( 'span' ) ;
504- span . textContent = tag ;
505- span . style . cssText =
506- 'display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#333333;color:#a0a0a0;border-radius:3px;white-space:nowrap;' ;
507- container . appendChild ( span ) ;
508- }
509- return container ;
510- } ,
511- } ,
512594 {
513595 field : 'accessionNumber' ,
514596 headerName : 'Accession #' ,
515597 width : 200 ,
598+ hide : true ,
516599 cellStyle : { fontFamily : "'SF Mono', 'Fira Code', monospace" , fontSize : '12px' } ,
517600 } ,
518601 {
519602 field : 'centralIndexKey' ,
520603 headerName : 'CIK' ,
521604 width : 100 ,
605+ hide : true ,
522606 } ,
523607 {
524608 field : 'relationship' ,
525609 headerName : 'Relationship' ,
526610 width : 130 ,
527- } ,
528- {
529- field : 'size' ,
530- headerName : 'Size' ,
531- width : 100 ,
532- valueFormatter : ( params : ValueFormatterParams ) => {
533- if ( ! params . value ) return '' ;
534- const kb = params . value / 1024 ;
535- if ( kb < 1024 ) return `${ kb . toFixed ( 0 ) } KB` ;
536- return `${ ( kb / 1024 ) . toFixed ( 1 ) } MB` ;
537- } ,
611+ hide : true ,
538612 } ,
539613 {
540614 field : 'documentCount' ,
541615 headerName : 'Docs' ,
542616 width : 80 ,
617+ hide : true ,
543618 } ,
544619 {
545620 field : 'fileNumber' ,
546621 headerName : 'File #' ,
547622 width : 120 ,
623+ hide : true ,
548624 } ,
549625 {
550626 field : 'filmNumber' ,
551627 headerName : 'Film #' ,
552628 width : 120 ,
629+ hide : true ,
553630 } ,
554631 {
555632 field : 'isAmendment' ,
556633 headerName : 'Amendment' ,
557634 width : 110 ,
635+ hide : true ,
558636 valueFormatter : ( params : ValueFormatterParams ) => ( params . value ? 'Yes' : 'No' ) ,
559637 } ,
560638 {
561639 field : 'isAmended' ,
562640 headerName : 'Amended' ,
563641 width : 100 ,
642+ hide : true ,
564643 valueFormatter : ( params : ValueFormatterParams ) => ( params . value ? 'Yes' : 'No' ) ,
565644 } ,
566645 {
567646 field : 'amendedAccessionNumber' ,
568647 headerName : 'Amended Accession #' ,
569648 width : 200 ,
649+ hide : true ,
570650 } ,
571651 {
572652 field : 'amendmentAccessionNumber' ,
573653 headerName : 'Amendment Accession #' ,
574654 width : 200 ,
655+ hide : true ,
575656 } ,
576657 {
577658 field : 'sponsorCIK' ,
578659 headerName : 'Sponsor CIK' ,
579660 width : 120 ,
661+ hide : true ,
580662 } ,
581663 {
582664 field : 'fileName' ,
583665 headerName : 'File Name' ,
584666 width : 180 ,
667+ hide : true ,
585668 } ,
586669 {
587670 field : 'snowflakeId' ,
588671 headerName : 'Snowflake ID' ,
589672 width : 160 ,
673+ hide : true ,
590674 } ,
591675 {
592676 field : 'noDocument' ,
593677 headerName : 'No Document' ,
594678 width : 120 ,
679+ hide : true ,
595680 valueFormatter : ( params : ValueFormatterParams ) => ( params . value ? 'Yes' : 'No' ) ,
596681 } ,
597682 {
598683 field : 'isEmpty' ,
599684 headerName : 'Empty' ,
600685 width : 90 ,
686+ hide : true ,
601687 valueFormatter : ( params : ValueFormatterParams ) => ( params . value ? 'Yes' : 'No' ) ,
602688 } ,
603689 {
604690 field : 'id' ,
605691 headerName : 'ID' ,
606692 width : 80 ,
693+ hide : true ,
607694 } ,
608695 ] ;
609696
0 commit comments