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