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