git ssb

10+

Matt McKegg / patchwork



Tree: 0975af1e429bd7b240f035de3051d6ea2cf38c75

Files: 0975af1e429bd7b240f035de3051d6ea2cf38c75 / modules / page / html / render / search.js

3767 bytesRaw
1var { h, Value, when, computed } = require('mutant')
2var pull = require('pull-stream')
3var TextNodeSearcher = require('text-node-searcher')
4var whitespace = /\s+/
5var pullAbortable = require('pull-abortable')
6var Scroller = require('../../../../lib/scroller')
7var nest = require('depnest')
8var Proxy = require('mutant/proxy')
9
10exports.needs = nest({
11 'sbot.pull.stream': 'first',
12 'keys.sync.id': 'first',
13 'message.html.render': 'first',
14 'intl.sync.i18n': 'first'
15})
16
17exports.gives = nest('page.html.render')
18
19exports.create = function (api) {
20 const i18n = api.intl.sync.i18n
21 return nest('page.html.render', function channel (path) {
22 if (path[0] !== '?') return
23
24 var query = path.substr(1).trim()
25 var done = Value(false)
26 var loading = Proxy(true)
27 var count = Value(0)
28 var updates = Value(0)
29 var aborter = null
30
31 const searchHeader = h('div', {className: 'PageHeading'}, [
32 h('h1', [h('strong', i18n('Search Results:')), ' ', query])
33 ])
34
35 var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [
36 'Show ', h('strong', [updates]), ' ', plural(updates, i18n('update'), i18n('updates'))
37 ])
38
39 var content = Proxy()
40 var container = h('Scroller', {
41 style: { overflow: 'auto' }
42 }, [
43 h('div.wrapper', [
44 h('SearchPage', [
45 searchHeader,
46 content,
47 when(loading, h('Loading -search'), h('div', {
48 style: {
49 'padding': '60px 0',
50 'font-size': '150%'
51 }
52 }, [h('strong', i18n('Search completed.')), ' ', count, ' ', plural(count, i18n('result found'), i18n('results found'))]))
53 ])
54 ])
55 ])
56
57 var realtimeAborter = pullAbortable()
58
59 pull(
60 api.sbot.pull.stream(sbot => sbot.patchwork.linearSearch({old: false, query: query.split(whitespace)})),
61 realtimeAborter,
62 pull.drain(msg => {
63 updates.set(updates() + 1)
64 })
65 )
66
67 refresh()
68
69 return h('SplitView', {
70 hooks: [
71 RemoveHook(() => {
72 // terminate search if removed from dom
73 // this is triggered whenever a new search is started
74 realtimeAborter.abort()
75 aborter && aborter.abort()
76 })
77 ],
78 uniqueKey: 'search'
79 }, [
80 h('div.main', [
81 when(updates, updateLoader),
82 container
83 ])
84 ])
85
86 // scoped
87
88 function refresh () {
89 if (aborter) {
90 aborter.abort()
91 }
92
93 aborter = pullAbortable()
94
95 updates.set(0)
96 content.set(h('section.content'))
97
98 var scroller = Scroller(container, content(), renderMsg, err => {
99 if (err) console.log(err)
100 done.set(true)
101 })
102
103 pull(
104 api.sbot.pull.stream(sbot => sbot.search.query({query})),
105 pull.through(() => count.set(count() + 1)),
106 aborter,
107 pull.filter(msg => msg.value),
108 scroller
109 )
110
111 loading.set(computed([done, scroller.queue], (done, queue) => {
112 return !done && queue < 5
113 }))
114 }
115
116 function renderMsg (msg) {
117 var el = h('FeedEvent', api.message.html.render(msg))
118 highlight(el, createOrRegExp(query.split(whitespace)))
119 return el
120 }
121 })
122}
123
124function createOrRegExp (ary) {
125 return new RegExp(ary.map(function (e) {
126 return '\\b' + e + '\\b'
127 }).join('|'), 'i')
128}
129
130function highlight (el, query) {
131 if (el) {
132 var searcher = new TextNodeSearcher({container: el})
133 searcher.query = query
134 searcher.highlight()
135 return el
136 }
137}
138
139function RemoveHook (fn) {
140 return function (element) {
141 return fn
142 }
143}
144
145function plural (value, single, many) {
146 return computed(value, (value) => {
147 if (value === 1) {
148 return single
149 } else {
150 return many
151 }
152 })
153}
154

Built with git-ssb-web