git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: d2a64eaa0249b9f9b937741f6bb64eae15d044bd

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

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

Built with git-ssb-web