modules/page/html/render/search.jsView |
---|
1 | | -const { h, Struct, Value, when, computed } = require('mutant') |
2 | | -const pull = require('pull-stream') |
3 | | -const Scroller = require('../../../../lib/scroller') |
4 | | -const TextNodeSearcher = require('text-node-searcher') |
5 | | -const whitespace = /\s+/ |
6 | | -const pullAbortable = require('pull-abortable') |
| 1 | +var { h, Value, when, computed } = require('mutant') |
| 2 | +var pull = require('pull-stream') |
| 3 | +var TextNodeSearcher = require('text-node-searcher') |
| 4 | +var whitespace = /\s+/ |
| 5 | +var pullAbortable = require('pull-abortable') |
| 6 | +var Scroller = require('../../../../lib/scroller') |
| 7 | +var nextStepper = require('../../../../lib/next-stepper') |
7 | 8 | var nest = require('depnest') |
8 | | -var Next = require('pull-next') |
9 | | -var defer = require('pull-defer') |
10 | | -var pullCat = require('pull-cat') |
| 9 | +var Proxy = require('mutant/proxy') |
11 | 10 | |
12 | 11 | exports.needs = nest({ |
13 | | - 'sbot.pull.log': 'first', |
| 12 | + 'sbot.pull.stream': 'first', |
14 | 13 | 'keys.sync.id': 'first', |
15 | 14 | 'message.html.render': 'first' |
16 | 15 | }) |
17 | 16 | |
22 | 21 | if (path[0] !== '?') return |
23 | 22 | |
24 | 23 | var queryStr = path.substr(1).trim() |
25 | 24 | var query = queryStr.split(whitespace) |
26 | | - var matchesQuery = searchFilter(query) |
| 25 | + var done = Value(false) |
| 26 | + var loading = Proxy(true) |
| 27 | + var count = Value(0) |
| 28 | + var updates = Value(0) |
| 29 | + var aborter = null |
27 | 30 | |
28 | | - const search = Struct({ |
29 | | - isLinear: Value(), |
30 | | - linear: Struct({ |
31 | | - checked: Value(0), |
32 | | - isDone: Value(false) |
33 | | - }), |
34 | | - fulltext: Struct({ |
35 | | - isDone: Value(false) |
36 | | - }), |
37 | | - matches: Value(0) |
38 | | - }) |
39 | | - |
40 | | - const hasNoFulltextMatches = computed([ |
41 | | - search.fulltext.isDone, search.matches |
42 | | - ], (done, matches) => { |
43 | | - return done && matches === 0 |
44 | | - }) |
45 | | - |
46 | | - const searchHeader = h('Search', [ |
47 | | - h('div', {className: 'PageHeading'}, [ |
48 | | - h('h1', [h('strong', 'Search Results:'), ' ', query.join(' ')]), |
49 | | - when(search.isLinear, |
50 | | - h('div.meta', ['Searched: ', search.linear.checked]), |
51 | | - h('section.details', when(hasNoFulltextMatches, h('div.matches', 'No matches'))) |
52 | | - ) |
53 | | - ]) |
| 31 | + const searchHeader = h('div', {className: 'PageHeading'}, [ |
| 32 | + h('h1', [h('strong', 'Search Results:'), ' ', query.join(' ')]) |
54 | 33 | ]) |
55 | 34 | |
56 | | - var content = h('section.content') |
| 35 | + var content = Proxy() |
57 | 36 | var container = h('Scroller', { |
58 | 37 | style: { overflow: 'auto' } |
59 | 38 | }, [ |
60 | 39 | h('div.wrapper', [ |
61 | | - searchHeader, |
62 | | - content, |
63 | | - when(search.linear.isDone, null, h('Loading -search')) |
| 40 | + h('SearchPage', [ |
| 41 | + searchHeader, |
| 42 | + content, |
| 43 | + when(loading, h('Loading -search'), h('div', { |
| 44 | + style: { |
| 45 | + 'padding': '60px 0', |
| 46 | + 'font-size': '150%' |
| 47 | + } |
| 48 | + }, [h('strong', 'Search completed.'), ' ', count, ' ', plural(count, 'result', 'results'), ' found'])) |
| 49 | + ]) |
64 | 50 | ]) |
65 | 51 | ]) |
66 | 52 | |
67 | 53 | var realtimeAborter = pullAbortable() |
68 | 54 | |
69 | 55 | pull( |
70 | | - api.sbot.pull.log({old: false}), |
71 | | - pull.filter(matchesQuery), |
| 56 | + api.sbot.pull.stream(sbot => sbot.patchwork.linearSearch({old: false, query})), |
72 | 57 | realtimeAborter, |
73 | | - Scroller(container, content, renderMsg) |
| 58 | + pull.drain(msg => { |
| 59 | + updates.set(updates() + 1) |
| 60 | + }) |
74 | 61 | ) |
75 | 62 | |
76 | | - |
77 | | - |
78 | | - |
79 | | - |
80 | | - |
81 | | - |
82 | | - |
83 | | - |
84 | | - |
85 | | - |
86 | | - |
87 | | - |
88 | | - |
89 | | - |
90 | | - |
91 | | - |
92 | | - |
| 63 | + refresh() |
93 | 64 | |
94 | | - |
95 | | - var aborter = pullAbortable() |
96 | | - search.isLinear.set(true) |
97 | | - pull( |
98 | | - nextStepper(api.sbot.pull.log, {reverse: true, limit: 500, live: false, delay: 100}), |
99 | | - pull.through((msg) => search.linear.checked.set(search.linear.checked() + 1)), |
100 | | - pull.filter(matchesQuery), |
101 | | - pull.through(() => search.matches.set(search.matches() + 1)), |
102 | | - aborter, |
103 | | - Scroller(container, content, renderMsg, false, false, err => { |
104 | | - if (err) console.log(err) |
105 | | - search.linear.isDone.set(true) |
106 | | - }) |
107 | | - ) |
108 | | - |
109 | 65 | return h('SplitView', { |
110 | 66 | hooks: [ |
111 | 67 | RemoveHook(() => { |
112 | 68 | |
113 | 69 | |
114 | 70 | realtimeAborter.abort() |
115 | | - aborter.abort() |
| 71 | + aborter && aborter.abort() |
116 | 72 | }) |
117 | 73 | ], |
118 | 74 | uniqueKey: 'search' |
119 | 75 | }, [ |
121 | 77 | ]) |
122 | 78 | |
123 | 79 | |
124 | 80 | |
| 81 | + function refresh () { |
| 82 | + if (aborter) { |
| 83 | + aborter.abort() |
| 84 | + } |
| 85 | + |
| 86 | + aborter = pullAbortable() |
| 87 | + |
| 88 | + updates.set(0) |
| 89 | + content.set(h('section.content')) |
| 90 | + |
| 91 | + var scroller = Scroller(container, content(), renderMsg, err => { |
| 92 | + if (err) console.log(err) |
| 93 | + done.set(true) |
| 94 | + }) |
| 95 | + |
| 96 | + pull( |
| 97 | + api.sbot.pull.stream(sbot => nextStepper(getStream, { |
| 98 | + reverse: true, |
| 99 | + limit: 5, |
| 100 | + query |
| 101 | + })), |
| 102 | + pull.through(() => count.set(count() + 1)), |
| 103 | + aborter, |
| 104 | + pull.filter(msg => msg.value), |
| 105 | + scroller |
| 106 | + ) |
| 107 | + |
| 108 | + loading.set(computed([done, scroller.queue], (done, queue) => { |
| 109 | + return !done && queue < 5 |
| 110 | + })) |
| 111 | + } |
| 112 | + |
| 113 | + function getStream (opts) { |
| 114 | + if (opts.lt != null && !opts.lt.marker) { |
| 115 | + |
| 116 | + return pull.empty() |
| 117 | + } else { |
| 118 | + return api.sbot.pull.stream(sbot => sbot.patchwork.linearSearch(opts)) |
| 119 | + } |
| 120 | + } |
| 121 | + |
125 | 122 | function renderMsg (msg) { |
126 | | - var el = h('div.result', api.message.html.render(msg)) |
| 123 | + var el = h('FeedEvent', api.message.html.render(msg)) |
127 | 124 | highlight(el, createOrRegExp(query)) |
128 | 125 | return el |
129 | 126 | } |
130 | 127 | }) |
131 | 128 | } |
132 | 129 | |
133 | | -function andSearch (terms, inputs) { |
134 | | - for (var i = 0; i < terms.length; i++) { |
135 | | - var match = false |
136 | | - for (var j = 0; j < inputs.length; j++) { |
137 | | - if (terms[i].test(inputs[j])) match = true |
138 | | - } |
139 | | - |
140 | | - if (!match) return false |
141 | | - } |
142 | | - return true |
143 | | -} |
144 | | - |
145 | | -function searchFilter (terms) { |
146 | | - return function (msg) { |
147 | | - var c = msg && msg.value && msg.value.content |
148 | | - return c && ( |
149 | | - msg.key === terms[0] || andSearch(terms.map(function (term) { |
150 | | - return new RegExp('\\b' + term + '\\b', 'i') |
151 | | - }), [c.text, c.name, c.title]) |
152 | | - ) |
153 | | - } |
154 | | -} |
155 | | - |
156 | 130 | function createOrRegExp (ary) { |
157 | 131 | return new RegExp(ary.map(function (e) { |
158 | 132 | return '\\b' + e + '\\b' |
159 | 133 | }).join('|'), 'i') |
167 | 141 | return el |
168 | 142 | } |
169 | 143 | } |
170 | 144 | |
171 | | -function fallback (reader) { |
172 | | - var fallbackRead |
173 | | - return function (read) { |
174 | | - return function (abort, cb) { |
175 | | - read(abort, function next (end, data) { |
176 | | - if (end && reader && (fallbackRead = reader(end))) { |
177 | | - reader = null |
178 | | - read = fallbackRead |
179 | | - read(abort, next) |
180 | | - } else { |
181 | | - cb(end, data) |
182 | | - } |
183 | | - }) |
184 | | - } |
185 | | - } |
186 | | -} |
187 | | - |
188 | | -function nextStepper (createStream, opts, property, range) { |
189 | | - range = range || (opts.reverse ? 'lt' : 'gt') |
190 | | - property = property || 'timestamp' |
191 | | - |
192 | | - var last = null |
193 | | - var count = -1 |
194 | | - |
195 | | - return Next(function () { |
196 | | - if (last) { |
197 | | - if (count === 0) return |
198 | | - var value = opts[range] = get(last, property) |
199 | | - if (value == null) return |
200 | | - last = null |
201 | | - } |
202 | | - var result = defer.source() |
203 | | - |
204 | | - window.requestIdleCallback(() => { |
205 | | - result.resolve(pull( |
206 | | - createStream(clone(opts)), |
207 | | - pull.through(function (msg) { |
208 | | - count++ |
209 | | - if (!msg.sync) { |
210 | | - last = msg |
211 | | - } |
212 | | - }, function (err) { |
213 | | - |
214 | | - if (err) { |
215 | | - count = -1 |
216 | | - return count |
217 | | - } |
218 | | - |
219 | | - if (last == null) last = {} |
220 | | - }) |
221 | | - )) |
222 | | - }) |
223 | | - |
224 | | - return pauseAfter(result, opts.delay) |
225 | | - }) |
226 | | -} |
227 | | - |
228 | | -function get (obj, path) { |
229 | | - if (!obj) return undefined |
230 | | - if (typeof path === 'string') return obj[path] |
231 | | - if (Array.isArray(path)) { |
232 | | - for (var i = 0; obj && i < path.length; i++) { |
233 | | - obj = obj[path[i]] |
234 | | - } |
235 | | - return obj |
236 | | - } |
237 | | -} |
238 | | - |
239 | | -function clone (obj) { |
240 | | - var _obj = {} |
241 | | - for (var k in obj) _obj[k] = obj[k] |
242 | | - return _obj |
243 | | -} |
244 | | - |
245 | 145 | function RemoveHook (fn) { |
246 | 146 | return function (element) { |
247 | 147 | return fn |
248 | 148 | } |
249 | 149 | } |
250 | 150 | |
251 | | -function pauseAfter (stream, delay) { |
252 | | - if (!delay) { |
253 | | - return stream |
254 | | - } else { |
255 | | - return pullCat([ |
256 | | - stream, |
257 | | - wait(delay) |
258 | | - ]) |
259 | | - } |
260 | | -} |
261 | | - |
262 | | -function wait (delay) { |
263 | | - return function (abort, cb) { |
264 | | - if (abort) { |
265 | | - cb(true) |
| 151 | +function plural (value, single, many) { |
| 152 | + return computed(value, (value) => { |
| 153 | + if (value === 1) { |
| 154 | + return single |
266 | 155 | } else { |
267 | | - setTimeout(() => { |
268 | | - cb(true) |
269 | | - }, delay) |
| 156 | + return many |
270 | 157 | } |
271 | | - } |
| 158 | + }) |
272 | 159 | } |