git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: a61adf51b6aeb00c6018ce2fb80cba9383b52625

Files: a61adf51b6aeb00c6018ce2fb80cba9383b52625 / modules / page / html / render / search.js

6331 bytesRaw
1const { h, Struct, Value, when, computed } = require('mutant')
2const pull = require('pull-stream')
3const Scroller = require('pull-scroll')
4const TextNodeSearcher = require('text-node-searcher')
5const whitespace = /\s+/
6const pullAbortable = require('pull-abortable')
7var nest = require('depnest')
8var Next = require('pull-next')
9var defer = require('pull-defer')
10
11exports.needs = nest({
12 'sbot.pull.search': 'first',
13 'sbot.pull.log': 'first',
14 'keys.sync.id': 'first',
15 'message.html.render': 'first'
16})
17
18exports.gives = nest('page.html.render')
19
20exports.create = function (api) {
21 return nest('page.html.render', function channel (path) {
22 if (path[0] !== '?') return
23
24 var queryStr = path.substr(1).trim()
25 var query = queryStr.split(whitespace)
26 var matchesQuery = searchFilter(query)
27
28 const search = Struct({
29 isLinear: Value(),
30 linear: Struct({
31 checked: Value(0)
32 }),
33 fulltext: Struct({
34 isDone: Value(false)
35 }),
36 matches: Value(0)
37 })
38
39 const hasNoFulltextMatches = computed([
40 search.fulltext.isDone, search.matches
41 ], (done, matches) => {
42 return done && matches === 0
43 })
44
45 const searchHeader = h('Search', [
46 h('div', {className: 'PageHeading'}, [
47 h('h1', [h('strong', 'Search Results:'), ' ', query.join(' ')]),
48 when(search.isLinear,
49 h('div.meta', ['Searched: ', search.linear.checked]),
50 h('section.details', when(hasNoFulltextMatches, h('div.matches', 'No matches')))
51 )
52 ]),
53 when(search.matches, null, h('Loading -large'))
54 ])
55
56 var content = h('section.content')
57 var container = h('Scroller', {
58 style: { overflow: 'auto' }
59 }, [
60 h('div.wrapper', [
61 searchHeader,
62 content
63 ])
64 ])
65
66 var realtimeAborter = pullAbortable()
67
68 pull(
69 api.sbot.pull.log({old: false}),
70 pull.filter(matchesQuery),
71 realtimeAborter,
72 Scroller(container, content, renderMsg, true, false)
73 )
74
75 // pull(
76 // nextStepper(api.sbot.pull.search, {query: queryStr, reverse: true, limit: 500, live: false}),
77 // fallback((err) => {
78 // if (err === true) {
79 // search.fulltext.isDone.set(true)
80 // } else if (/^no source/.test(err.message)) {
81 // search.isLinear.set(true)
82 // return pull(
83 // nextStepper(api.sbot.pull.log, {reverse: true, limit: 500, live: false}),
84 // pull.through((msg) => search.linear.checked.set(search.linear.checked() + 1)),
85 // pull.filter(matchesQuery)
86 // )
87 // }
88 // }),
89 // pull.through(() => search.matches.set(search.matches() + 1)),
90 // Scroller(container, content, renderMsg, false, false)
91 // )
92
93 // disable full text for now
94 var aborter = pullAbortable()
95 search.isLinear.set(true)
96 pull(
97 nextStepper(api.sbot.pull.log, {reverse: true, limit: 1000, live: false}),
98 pull.through((msg) => search.linear.checked.set(search.linear.checked() + 1)),
99 pull.filter(matchesQuery),
100 pull.through(() => search.matches.set(search.matches() + 1)),
101 aborter,
102 Scroller(container, content, renderMsg, false, false)
103 )
104
105 return h('SplitView', {
106 hooks: [
107 RemoveHook(() => {
108 // terminate search if removed from dom
109 // this is triggered whenever a new search is started
110 realtimeAborter.abort()
111 aborter.abort()
112 })
113 ],
114 uniqueKey: 'search'
115 }, [
116 h('div.main', container)
117 ])
118
119 // scoped
120
121 function renderMsg (msg) {
122 var el = h('div.result', api.message.html.render(msg))
123 highlight(el, createOrRegExp(query))
124 return el
125 }
126 })
127}
128
129function andSearch (terms, inputs) {
130 for (var i = 0; i < terms.length; i++) {
131 var match = false
132 for (var j = 0; j < inputs.length; j++) {
133 if (terms[i].test(inputs[j])) match = true
134 }
135 // if a term was not matched by anything, filter this one
136 if (!match) return false
137 }
138 return true
139}
140
141function searchFilter (terms) {
142 return function (msg) {
143 var c = msg && msg.value && msg.value.content
144 return c && (
145 msg.key === terms[0] || andSearch(terms.map(function (term) {
146 return new RegExp('\\b' + term + '\\b', 'i')
147 }), [c.text, c.name, c.title])
148 )
149 }
150}
151
152function createOrRegExp (ary) {
153 return new RegExp(ary.map(function (e) {
154 return '\\b' + e + '\\b'
155 }).join('|'), 'i')
156}
157
158function highlight (el, query) {
159 if (el) {
160 var searcher = new TextNodeSearcher({container: el})
161 searcher.query = query
162 searcher.highlight()
163 return el
164 }
165}
166
167function fallback (reader) {
168 var fallbackRead
169 return function (read) {
170 return function (abort, cb) {
171 read(abort, function next (end, data) {
172 if (end && reader && (fallbackRead = reader(end))) {
173 reader = null
174 read = fallbackRead
175 read(abort, next)
176 } else {
177 cb(end, data)
178 }
179 })
180 }
181 }
182}
183
184function nextStepper (createStream, opts, property, range) {
185 range = range || (opts.reverse ? 'lt' : 'gt')
186 property = property || 'timestamp'
187
188 var last = null
189 var count = -1
190
191 return Next(function () {
192 if (last) {
193 if (count === 0) return
194 var value = opts[range] = get(last, property)
195 if (value == null) return
196 last = null
197 }
198 var result = defer.source()
199
200 window.requestIdleCallback(() => {
201 result.resolve(pull(
202 createStream(clone(opts)),
203 pull.through(function (msg) {
204 count++
205 if (!msg.sync) {
206 last = msg
207 }
208 }, function (err) {
209 // retry on errors...
210 if (err) {
211 count = -1
212 return count
213 }
214 // end stream if there were no results
215 if (last == null) last = {}
216 })
217 ))
218 })
219
220 return result
221 })
222}
223
224function get (obj, path) {
225 if (!obj) return undefined
226 if (typeof path === 'string') return obj[path]
227 if (Array.isArray(path)) {
228 for (var i = 0; obj && i < path.length; i++) {
229 obj = obj[path[i]]
230 }
231 return obj
232 }
233}
234
235function clone (obj) {
236 var _obj = {}
237 for (var k in obj) _obj[k] = obj[k]
238 return _obj
239}
240
241function RemoveHook (fn) {
242 return function (element) {
243 return fn
244 }
245}
246

Built with git-ssb-web