Files: ac4142da16c5834e703d5723d94c3b3035a34ae0 / modules / page / html / render / search.js
4256 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 | |
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 | 'sbot.pull.backlinks': 'first' |
17 | }) |
18 | |
19 | exports.gives = nest('page.html.render') |
20 | |
21 | exports.create = function (api) { |
22 | const i18n = api.intl.sync.i18n |
23 | return nest('page.html.render', function channel (path) { |
24 | if (path[0] !== '?') return |
25 | |
26 | var query = path.substr(1).trim() |
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]) |
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 | getStream(query, true), |
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 | getStream(query, false), |
107 | pull.through(() => count.set(count() + 1)), |
108 | aborter, |
109 | pull.filter(msg => msg.value), |
110 | scroller |
111 | ) |
112 | |
113 | loading.set(computed([done, scroller.queue], (done, queue) => { |
114 | return !done |
115 | })) |
116 | } |
117 | |
118 | function renderMsg (msg) { |
119 | var el = h('FeedEvent', api.message.html.render(msg)) |
120 | highlight(el, createOrRegExp(query.split(whitespace))) |
121 | return el |
122 | } |
123 | }) |
124 | |
125 | function getStream (query, realtime = false) { |
126 | if (ref.isLink(query) || query.startsWith('#')) { |
127 | return api.sbot.pull.backlinks({ |
128 | query: [ {$filter: { dest: query }} ], |
129 | reverse: true, |
130 | old: !realtime, |
131 | index: 'DTA' // use asserted timestamps |
132 | }) |
133 | } else { |
134 | if (realtime) { |
135 | return api.sbot.pull.stream(sbot => sbot.patchwork.linearSearch({old: false, query: query.split(whitespace)})) |
136 | } else { |
137 | return api.sbot.pull.stream(sbot => sbot.search.query({query})) |
138 | } |
139 | } |
140 | } |
141 | } |
142 | |
143 | function createOrRegExp (ary) { |
144 | return new RegExp(ary.map(function (e) { |
145 | return '\\b' + e + '\\b' |
146 | }).join('|'), 'i') |
147 | } |
148 | |
149 | function highlight (el, query) { |
150 | if (el) { |
151 | var searcher = new TextNodeSearcher({container: el}) |
152 | searcher.query = query |
153 | searcher.highlight() |
154 | return el |
155 | } |
156 | } |
157 | |
158 | function RemoveHook (fn) { |
159 | return function (element) { |
160 | return fn |
161 | } |
162 | } |
163 | |
164 | function plural (value, single, many) { |
165 | return computed(value, (value) => { |
166 | if (value === 1) { |
167 | return single |
168 | } else { |
169 | return many |
170 | } |
171 | }) |
172 | } |
173 |
Built with git-ssb-web