A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 157 lines 3.5 kB view raw
1// 2// Search worker 3// (◡ ‿ ◡ ✿) 4// 5// This worker is responsible for searching through a `Track` collection. 6 7 8import lunr from "lunr" 9 10 11const FIELDS = ["album", "artist", "title"] 12 13 14lunr.Pipeline.registerFunction( 15 removeParenthesesFromToken, 16 "Remove parentheses from token" 17) 18 19 20let index: lunr.Index 21 22 23 24// Incoming messages 25// ----------------- 26 27self.onmessage = (event: MessageEvent) => { 28 switch (event.data.action) { 29 case "PERFORM_SEARCH": 30 performSearch(event.data.data) 31 break 32 33 case "UPDATE_SEARCH_INDEX": 34 updateSearchIndex(event.data.data) 35 break 36 } 37} 38 39 40 41// Mapper 42// ------ 43 44const mapTrack = track => ({ 45 id: track.id, 46 album: track.tags.album, 47 artist: track.tags.artist, 48 title: track.tags.title, 49}) 50 51 52 53// Actions 54// ------- 55 56function performSearch(rawSearchTerm: string) { 57 let results: string[] = 58 [] 59 60 const searchTerm = rawSearchTerm 61 .replace(/-\s+/g, "-") 62 .replace(/\+\s+/g, "+") 63 .split(/ +/) 64 .reduce( 65 ([ acc, previousOperator, previousPrefix ]: [ string[], string, string ], chunk: string): [ string[], string, string ] => { 66 const operator = (a => a && a[0])( chunk.match(/^(\+|-)/) ) 67 68 let chunkWithoutOperator = chunk.replace(/^(\+|-)/, "").replace(/\*$/, "").trim() 69 let prefix = (a => a && a[1])( chunkWithoutOperator.match(/^([^:]+:)/) ) 70 let chunkWithoutPrefix = chunkWithoutOperator.replace(/^([^:]+:)/, "") 71 72 if (prefix && !FIELDS.includes(prefix.slice(0, -1))) { 73 prefix = null 74 chunkWithoutPrefix = chunkWithoutOperator.replace(":", "\\:") 75 chunkWithoutOperator = chunkWithoutPrefix 76 77 } else if (prefix && chunkWithoutPrefix.includes(":")) { 78 chunkWithoutPrefix = chunkWithoutPrefix.replace(":", "\\:") 79 chunkWithoutOperator = prefix + chunkWithoutPrefix 80 81 } 82 83 const op = operator || previousOperator 84 const pr = prefix ? "" : (operator ? "" : previousPrefix) 85 86 return chunkWithoutPrefix.trim().length > 0 87 ? [ [ ...acc 88 , op + pr + chunkWithoutOperator 89 ] 90 , op 91 , prefix || pr 92 ] 93 : [ acc, previousOperator, previousPrefix ] 94 }, 95 [ [], "+", "" ] 96 )[0] 97 .join(" ") 98 99 const searchTermWithAsteriks = 100 searchTerm 101 .split(" ") 102 .map(s => { 103 if (s.startsWith("-")) return s 104 return s + "*" 105 }) 106 .join(" ") 107 108 if (index) { 109 results = index 110 .search(searchTerm) 111 .map(s => s.ref) 112 .concat( 113 index 114 .search(searchTermWithAsteriks) 115 .map(s => s.ref) 116 ) 117 } 118 119 self.postMessage({ 120 action: "PERFORM_SEARCH", 121 data: results 122 }) 123} 124 125 126function updateSearchIndex(input: string | object[]) { 127 const tracks = (typeof input == "string") 128 ? JSON.parse(input) 129 : input 130 131 index = customLunr((builder: lunr.Builder) => { 132 FIELDS.forEach( 133 field => builder.field(field) 134 ) 135 136 ;(tracks || []) 137 .map(mapTrack) 138 .forEach(t => builder.add(t)) 139 }) 140} 141 142 143 144function customLunr(fn: (b: lunr.Builder) => void) { 145 const builder = new lunr.Builder 146 147 builder.pipeline.add(removeParenthesesFromToken, lunr.stemmer) 148 builder.searchPipeline.add(removeParenthesesFromToken, lunr.stemmer) 149 150 fn(builder) 151 return builder.build() 152} 153 154 155function removeParenthesesFromToken(token: lunr.Token): lunr.Token { 156 return token.update(s => s.replace(/\(|\)/, "")) 157}