git ssb

16+

Dominic / patchbay



Tree: 7041fd47bd18aed28bf562a96b236b93c482d4c2

Files: 7041fd47bd18aed28bf562a96b236b93c482d4c2 / modules_extra / search.js

3997 bytesRaw
1const h = require('../h')
2const fs = require('fs')
3const { Struct, Value, when, computed } = require('mutant')
4const u = require('../util')
5const pull = require('pull-stream')
6const Scroller = require('pull-scroll')
7const TextNodeSearcher = require('text-node-searcher')
8
9exports.needs = {
10 build_scroller: 'first',
11 message_render: 'first',
12 sbot_log: 'first',
13 sbot_fulltext_search: 'first'
14}
15
16exports.gives = {
17 screen_view: true,
18 mcss: true
19}
20
21var whitespace = /\s+/
22
23function andSearch(terms, inputs) {
24 for(var i = 0; i < terms.length; i++) {
25 var match = false
26 for(var j = 0; j < inputs.length; j++) {
27 if(terms[i].test(inputs[j])) match = true
28 }
29 //if a term was not matched by anything, filter this one
30 if(!match) return false
31 }
32 return true
33}
34
35function searchFilter(terms) {
36 return function (msg) {
37 var c = msg && msg.value && msg.value.content
38 return c && (
39 msg.key == terms[0] ||
40 andSearch(terms.map(function (term) {
41 return new RegExp('\\b'+u.escapeRegExp(term)+'\\b', 'i')
42 }), [c.text, c.name, c.title])
43 )
44 }
45}
46
47function createOrRegExp(ary) {
48 return new RegExp(ary.map(function (e) {
49 return '\\b'+u.escapeRegExp(e)+'\\b'
50 }).join('|'), 'i')
51}
52
53function highlight(el, query) {
54 var searcher = new TextNodeSearcher({container: el})
55 searcher.query = query
56 searcher.highlight()
57 return el
58}
59
60function fallback(reader) {
61 var fallbackRead
62 return function (read) {
63 return function (abort, cb) {
64 read(abort, function next(end, data) {
65 if (end && reader && (fallbackRead = reader(end))) {
66 reader = null
67 read = fallbackRead
68 read(abort, next)
69 } else {
70 cb(end, data)
71 }
72 })
73 }
74 }
75}
76
77exports.create = function (api) {
78
79 return {
80 screen_view,
81 mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
82 }
83
84 function screen_view (path) {
85 if (path[0] !== '?') return
86
87 var queryStr = path.substr(1).trim()
88 var query = queryStr.split(whitespace)
89 var matchesQuery = searchFilter(query)
90
91 const search = Struct({
92 isLinear: Value(false),
93 linear: Struct({
94 checked: Value(0)
95 }),
96 fulltext: Struct({
97 isDone: Value(false)
98 }),
99 matches: Value(0)
100 })
101 const hasNoFulltextMatches = computed([search.fulltext.isDone, search.matches],
102 (done, matches) => done && matches === 0)
103
104
105 const searchHeader = h('Search', [
106 h('header', h('h1', query.join(' '))),
107 when(search.isLinear,
108 h('section.details', [
109 h('div.searched', ['Searched: ', search.linear.checked]),
110 h('div.matches', [search.matches, ' matches'])
111 ]),
112 h('section.details', [
113 h('div.searched'),
114 when(hasNoFulltextMatches, h('div.matches', 'No matches'))
115 ])
116 )
117 ])
118 var { container, content } = api.build_scroller({ prepend: searchHeader })
119 container.id = path // helps tabs find this tab
120
121 function renderMsg(msg) {
122 var el = api.message_render(msg)
123 highlight(el, createOrRegExp(query))
124 return el
125 }
126
127 pull(
128 api.sbot_log({old: false}),
129 pull.filter(matchesQuery),
130 Scroller(container, content, renderMsg, true, false)
131 )
132
133 pull(
134 u.next(api.sbot_fulltext_search, {query: queryStr, reverse: true, limit: 500, live: false}),
135 fallback((err) => {
136 if (err === true) {
137 search.fulltext.isDone.set(true)
138 } else if (/^no source/.test(err.message)) {
139 search.isLinear.set(true)
140 return pull(
141 u.next(api.sbot_log, {reverse: true, limit: 500, live: false}),
142 pull.through(() => search.linear.checked.set(search.linear.checked()+1)),
143 pull.filter(matchesQuery)
144 )
145 }
146 }),
147 pull.through(() => search.matches.set(search.matches()+1)),
148 Scroller(container, content, renderMsg, false, false)
149 )
150
151 return container
152 }
153}
154
155

Built with git-ssb-web