Files: 1a1dfa987b784a002e626f97e18aeaf2164d1299 / modules / page / html / render / search.js
4111 bytesRaw
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') |
8 | var nest = require('depnest') |
9 | var Proxy = require('mutant/proxy') |
10 | |
11 | exports.needs = nest({ |
12 | 'sbot.pull.stream': 'first', |
13 | 'keys.sync.id': 'first', |
14 | 'message.html.render': 'first' |
15 | }) |
16 | |
17 | exports.gives = nest('page.html.render') |
18 | |
19 | exports.create = function (api) { |
20 | return nest('page.html.render', function channel (path) { |
21 | if (path[0] !== '?') return |
22 | |
23 | var queryStr = path.substr(1).trim() |
24 | var query = queryStr.split(whitespace) |
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', 'Search Results:'), ' ', query.join(' ')]) |
33 | ]) |
34 | |
35 | var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [ |
36 | 'Show ', h('strong', [updates]), ' ', plural(updates, 'update', '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', 'Search completed.'), ' ', count, ' ', plural(count, 'result', '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})), |
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 => nextStepper(getStream, { |
105 | reverse: true, |
106 | limit: 5, |
107 | query |
108 | })), |
109 | pull.through(() => count.set(count() + 1)), |
110 | aborter, |
111 | pull.filter(msg => msg.value), |
112 | scroller |
113 | ) |
114 | |
115 | loading.set(computed([done, scroller.queue], (done, queue) => { |
116 | return !done && queue < 5 |
117 | })) |
118 | } |
119 | |
120 | function getStream (opts) { |
121 | if (opts.lt != null && !opts.lt.marker) { |
122 | // if an lt has been specified that is not a marker, assume stream is finished |
123 | return pull.empty() |
124 | } else { |
125 | return api.sbot.pull.stream(sbot => sbot.patchwork.linearSearch(opts)) |
126 | } |
127 | } |
128 | |
129 | function renderMsg (msg) { |
130 | var el = h('FeedEvent', api.message.html.render(msg)) |
131 | highlight(el, createOrRegExp(query)) |
132 | return el |
133 | } |
134 | }) |
135 | } |
136 | |
137 | function createOrRegExp (ary) { |
138 | return new RegExp(ary.map(function (e) { |
139 | return '\\b' + e + '\\b' |
140 | }).join('|'), 'i') |
141 | } |
142 | |
143 | function highlight (el, query) { |
144 | if (el) { |
145 | var searcher = new TextNodeSearcher({container: el}) |
146 | searcher.query = query |
147 | searcher.highlight() |
148 | return el |
149 | } |
150 | } |
151 | |
152 | function RemoveHook (fn) { |
153 | return function (element) { |
154 | return fn |
155 | } |
156 | } |
157 | |
158 | function plural (value, single, many) { |
159 | return computed(value, (value) => { |
160 | if (value === 1) { |
161 | return single |
162 | } else { |
163 | return many |
164 | } |
165 | }) |
166 | } |
167 |
Built with git-ssb-web