@@ -10,6 +10,7 @@ import iconStar from "@ktibow/iconset-ion/star-outline";
1010import iconStarFilled from "@ktibow/iconset-ion/star" ;
1111import iconSearch from "@ktibow/iconset-ion/search" ;
1212import iconForwards from "@ktibow/iconset-ion/arrow-forward" ;
13+ import iconTrendingUp from "@ktibow/iconset-ion/trending-up" ;
1314import { Icon } from "./Icon" ;
1415import { OmnibarButton } from "./OmnibarButton" ;
1516import { createMenuCustom , setContextMenu } from "./Menu" ;
@@ -19,6 +20,7 @@ import { SiteInformationPopup } from "./SiteInformationPopup";
1920import { emToPx , splitUrl } from "../utils" ;
2021import { fetchSuggestions , type OmniboxResult } from "./suggestions" ;
2122import { BookmarkPopup } from "./BookmarkPopup" ;
23+ import { bare } from "../IsolatedFrame" ;
2224
2325export const focusOmnibox = createDelegate < void > ( ) ;
2426
@@ -30,7 +32,49 @@ export function trimUrl(v: URL) {
3032 v . search
3133 ) ;
3234}
35+ export type TrendingQuery = {
36+ title : string ;
37+ traffic ?: string ;
38+ url ?: string ;
39+ } ;
40+
41+ let trendingCached : TrendingQuery [ ] | null = null ;
42+ export async function fetchGoogleTrending ( geo = "US" ) : Promise < void > {
43+ try {
44+ if ( trendingCached ) return ;
45+
46+ const res = await bare . fetch (
47+ "https://trends.google.com/_/TrendsUi/data/batchexecute" ,
48+ {
49+ method : "POST" ,
50+ body : `f.req=[[["i0OFE","[null, null, \\"${ geo } \\", 0, null, 48]"]]]` ,
51+ headers : {
52+ "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" ,
53+ Referer : "https://trends.google.com/trends/explore" ,
54+ } ,
55+ }
56+ ) ;
57+ if ( ! res . ok ) return ;
58+
59+ const text = await res . text ( ) ;
60+ const json = JSON . parse ( text . slice ( 5 ) ) ;
61+ const data = JSON . parse ( json [ 0 ] [ 2 ] ) ;
62+ const results : TrendingQuery [ ] = [ ] ;
63+ for ( const item of data [ 1 ] ) {
64+ results . push ( {
65+ title : item [ 0 ] ,
66+ traffic : item [ 1 ] ,
67+ url : item [ 2 ]
68+ ? `https://www.google.com/search?q=${ encodeURIComponent ( item [ 0 ] ) } `
69+ : undefined ,
70+ } ) ;
71+ }
3372
73+ trendingCached = results ;
74+ } catch ( err ) {
75+ console . error ( "fetchGoogleTrending failed" , err ) ;
76+ }
77+ }
3478export const UrlInput : Component <
3579 {
3680 tabUrl : URL ;
@@ -44,11 +88,13 @@ export const UrlInput: Component<
4488 input : HTMLInputElement ;
4589 focusindex : number ;
4690 overflowItems : OmniboxResult [ ] ;
91+ trending : TrendingQuery [ ] ;
4792 }
4893> = function ( cx ) {
4994 this . focusindex = 0 ;
5095 this . overflowItems = [ ] ;
5196 this . value = "" ;
97+ this . trending = [ ] ;
5298
5399 cx . mount = ( ) => {
54100 setContextMenu ( cx . root , [
@@ -59,6 +105,11 @@ export const UrlInput: Component<
59105 } ,
60106 } ,
61107 ] ) ;
108+
109+ fetchGoogleTrending ( ) ;
110+ setTimeout ( ( ) => {
111+ fetchGoogleTrending ( ) ;
112+ } , 1000 ) ;
62113 } ;
63114
64115 focusOmnibox . listen ( ( ) => {
@@ -112,6 +163,13 @@ export const UrlInput: Component<
112163 this . input . select ( ) ;
113164 this . justselected = true ;
114165 this . input . scrollLeft = 0 ;
166+
167+ fetchGoogleTrending ( ) . then ( ( ) => {
168+ // pick a random 3 from the cache
169+ this . trending = trendingCached !
170+ . sort ( ( ) => 0.5 - Math . random ( ) )
171+ . slice ( 0 , 3 ) ;
172+ } ) ;
115173 } ;
116174
117175 const doSearch = ( ) => {
@@ -199,6 +257,36 @@ export const UrlInput: Component<
199257 </ div >
200258 </ div >
201259 ) ) }
260+ < div class = "spacertext" > Trending Searches</ div >
261+ { use ( this . trending ) . mapEach ( ( item ) => (
262+ < div
263+ class = "overflowitem"
264+ on :click = { ( ) => {
265+ if ( item . url ) {
266+ this . active = false ;
267+ this . input . blur ( ) ;
268+ browser . activetab . pushNavigate ( new URL ( item . url ) ) ;
269+ } else {
270+ this . value = item . title ;
271+ this . focusindex = 0 ;
272+ }
273+ } }
274+ class :focused = { false }
275+ title = { item . url || item . title }
276+ >
277+ < div class = "result-icon" >
278+ < Icon icon = { iconTrendingUp } > </ Icon >
279+ </ div >
280+ < div class = "result-content" >
281+ < span class = "description" >
282+ { renderResultHighlight ( item . title , this . input . value ) }
283+ </ span >
284+ { item . traffic && (
285+ < span class = "url" > { item . traffic } searches today</ span >
286+ ) }
287+ </ div >
288+ </ div >
289+ ) ) }
202290 </ div >
203291 < div class = "realbar" >
204292 < div class = "lefticon" >
@@ -245,17 +333,19 @@ export const UrlInput: Component<
245333 on :keydown = { ( e : KeyboardEvent ) => {
246334 if ( e . key === "ArrowDown" ) {
247335 e . preventDefault ( ) ;
248- this . focusindex ++ ;
249- if ( this . focusindex > this . overflowItems . length ) {
250- this . focusindex = 0 ;
336+ let idx = this . focusindex + 1 ;
337+ if ( idx > this . overflowItems . length ) {
338+ idx = 0 ;
251339 }
340+ this . focusindex = idx ;
252341 }
253342 if ( e . key === "ArrowUp" ) {
254343 e . preventDefault ( ) ;
255- this . focusindex -- ;
256- if ( this . focusindex < 0 ) {
257- this . focusindex = this . overflowItems . length ;
344+ let idx = this . focusindex - 1 ;
345+ if ( idx < 0 ) {
346+ idx = this . overflowItems . length ;
258347 }
348+ this . focusindex = idx ;
259349 }
260350 if ( e . key === "Enter" ) {
261351 e . preventDefault ( ) ;
@@ -437,6 +527,16 @@ UrlInput.style = css`
437527
438528 border-bottom : 1px solid var (--fg5 );
439529 }
530+
531+ .spacertext {
532+ display : block;
533+ height : 2em ;
534+ line-height : 2.5em ;
535+ padding-left : 1.5em ;
536+ color : var (--fg3 );
537+ font-size : 0.9em ;
538+ }
539+
440540 .overflowitem {
441541 display : flex;
442542 align-items : center;
0 commit comments