Files: 8069cbb074f6985dedd767cc31ba0586638550ea / app / html / filter.js
7163 bytesRaw
1 | const nest = require('depnest') |
2 | const { h, Value, when, computed, resolve } = require('mutant') |
3 | const Abort = require('pull-abortable') |
4 | const pull = require('pull-stream') |
5 | const addSuggest = require('suggest-box') |
6 | const get = require('lodash/get') |
7 | const isEqual = require('lodash/isEqual') |
8 | const isEmpty = require('lodash/isEmpty') |
9 | const { isFeed } = require('ssb-ref') |
10 | |
11 | exports.gives = nest('app.html.filter') |
12 | |
13 | exports.needs = nest({ |
14 | 'about.async.suggest': 'first', |
15 | 'channel.async.suggest': 'first', |
16 | 'contact.obs.following': 'first', |
17 | 'channel.obs.subscribed': 'first', |
18 | 'keys.sync.id': 'first', |
19 | 'settings.obs.get': 'first', |
20 | 'settings.sync.set': 'first' |
21 | }) |
22 | |
23 | exports.create = function (api) { |
24 | return nest({ |
25 | 'app.html.filter': Filter |
26 | }) |
27 | |
28 | function Filter (draw) { |
29 | const showFilters = Value(false) |
30 | |
31 | const myId = api.keys.sync.id() |
32 | const peopleIFollow = api.contact.obs.following(myId) |
33 | const channelsIFollow = api.channel.obs.subscribed(myId) |
34 | |
35 | const filterSettings = api.settings.obs.get('filter', {exclude: {}}) |
36 | |
37 | const channelInput = h('input', { |
38 | value: filterSettings().exclude.channels, |
39 | 'ev-keyup': (ev) => { |
40 | var text = ev.target.value |
41 | if (text.length === 0 || ev.which === 13) { |
42 | api.settings.sync.set({ |
43 | filter: { |
44 | exclude: { |
45 | channels: text |
46 | } |
47 | } |
48 | }) |
49 | draw() |
50 | } |
51 | } |
52 | }) |
53 | |
54 | var userId = Value('') |
55 | userId(id => { |
56 | userInput.value = id |
57 | draw() |
58 | }) |
59 | const userInput = h('input', { |
60 | 'ev-input': (ev) => { |
61 | const { value } = ev.target |
62 | if (value === resolve(userId)) return // stops infinite loop |
63 | |
64 | if (isFeed(value)) userId.set(value) |
65 | else if (isEmpty(value)) userId.set('') |
66 | } |
67 | }) |
68 | |
69 | const isFiltered = computed(filterSettings, (filterSettings) => { |
70 | const _settings = Object.assign({}, filterSettings) |
71 | delete _settings.defaults |
72 | |
73 | return !isEqual(_settings, filterSettings.defaults) |
74 | }) |
75 | |
76 | const filterMenu = h('Filter', [ |
77 | when(isFiltered, h('i.custom')), |
78 | h('i.fa.fa-filter', { |
79 | classList: when(showFilters, '-active'), |
80 | 'ev-click': () => showFilters.set(!showFilters()) |
81 | }), |
82 | h('i.fa.fa-angle-up', { 'ev-click': draw }), |
83 | h('div.options', { className: when(showFilters, '', '-hidden') }, [ |
84 | h('header', [ |
85 | 'Filter', |
86 | h('i.fa.fa-filter') |
87 | ]), |
88 | h('section', [ |
89 | h('div.users', [ |
90 | toggle({ type: 'peopleAndChannelsIFollow', filterGroup: 'only', label: 'Only people & channels I follow' }), |
91 | h('div.user-filter', [ |
92 | h('label', 'Only this user (temporary filter):'), |
93 | userInput |
94 | ]) |
95 | ]), |
96 | h('div.channels', [ |
97 | h('label', 'Exclude channels'), |
98 | channelInput |
99 | ]), |
100 | h('div.message-types', [ |
101 | h('header', 'Show messages'), |
102 | toggle({ type: 'post' }), |
103 | toggle({ type: 'like' }), |
104 | toggle({ type: 'about' }), |
105 | toggle({ type: 'contact' }), |
106 | toggle({ type: 'channel' }), |
107 | toggle({ type: 'pub' }), |
108 | toggle({ type: 'private' }), |
109 | toggle({ type: 'chess' }) |
110 | ]), |
111 | h('div.root-messages', [ |
112 | toggle({ type: 'rootMessages', filterGroup: 'only', label: 'Root messages only' }) |
113 | ]) |
114 | ]) |
115 | ]) |
116 | ]) |
117 | |
118 | function toggle ({ type, filterGroup, label }) { |
119 | label = label || type |
120 | filterGroup = filterGroup || 'show' |
121 | |
122 | const state = computed(filterSettings, settings => get(settings, [filterGroup, type])) |
123 | const handleClick = () => { |
124 | const currentState = state() |
125 | |
126 | // TODO use some lodash tool ? |
127 | api.settings.sync.set({ |
128 | filter: { |
129 | [filterGroup]: { |
130 | [type]: !currentState |
131 | } |
132 | } |
133 | }) |
134 | |
135 | draw() |
136 | } |
137 | |
138 | return h('FilterToggle', { 'ev-click': handleClick }, [ |
139 | h('label', label), |
140 | h('i', { classList: when(state, 'fa fa-check-square-o', 'fa fa-square-o') }) |
141 | ]) |
142 | } |
143 | |
144 | const getChannelSuggestions = api.channel.async.suggest() |
145 | addSuggest(channelInput, (inputText, cb) => { |
146 | if (inputText[0] === '#') { |
147 | cb(null, getChannelSuggestions(inputText.slice(1))) |
148 | } |
149 | }, {cls: 'PatchSuggest'}) |
150 | channelInput.addEventListener('suggestselect', ev => { |
151 | const channels = channelInput.value.trim() |
152 | |
153 | api.settings.sync.set({ filter: { exclude: { channels: channels } } }) |
154 | |
155 | draw() |
156 | }) |
157 | |
158 | const getAboutSuggestions = api.about.async.suggest() |
159 | addSuggest(userInput, (inputText, cb) => { |
160 | inputText = inputText.replace(/^@/, '') |
161 | cb(null, getAboutSuggestions(inputText)) |
162 | }, {cls: 'PatchSuggest'}) |
163 | userInput.addEventListener('suggestselect', ev => userId.set(ev.detail.id)) |
164 | |
165 | function followFilter (msg) { |
166 | if (!filterSettings().only.peopleAndChannelsIFollow) return true |
167 | |
168 | return (msg.value && Array.from(peopleIFollow()).concat(myId).includes(msg.value.author)) || |
169 | (msg.value && msg.value.content && Array.from(channelsIFollow()).includes(msg.value.content.channel)) |
170 | } |
171 | |
172 | function userFilter (msg) { |
173 | const id = resolve(userId) |
174 | if (!id) return true |
175 | |
176 | return msg.value.author === id |
177 | } |
178 | |
179 | function rootFilter (msg) { |
180 | if (!filterSettings().only.rootMessages) return true |
181 | |
182 | return !msg.value.content.root |
183 | } |
184 | |
185 | function channelFilter (msg) { |
186 | var filters = filterSettings().exclude.channels |
187 | if (!filters) return true |
188 | filters = filters.split(' ').map(c => c.slice(1)) |
189 | |
190 | return msg.value.content && !filters.includes(msg.value.content.channel) |
191 | } |
192 | |
193 | function messageFilter (msg) { |
194 | var { type } = msg.value.content |
195 | if (/^chess/.test(type)) { |
196 | type = 'chess' |
197 | } |
198 | |
199 | if (typeof msg.value.content === 'string') { |
200 | type = 'private' |
201 | } |
202 | |
203 | return get(filterSettings(), ['show', type], true) |
204 | } |
205 | |
206 | var downScrollAborter |
207 | |
208 | function filterDownThrough () { |
209 | return pull( |
210 | downScrollAborter, |
211 | pull.filter(msg => msg), |
212 | pull.filter(followFilter), |
213 | pull.filter(userFilter), |
214 | pull.filter(rootFilter), |
215 | pull.filter(channelFilter), |
216 | pull.filter(messageFilter) |
217 | ) |
218 | } |
219 | |
220 | var upScrollAborter |
221 | |
222 | function filterUpThrough () { |
223 | return pull( |
224 | upScrollAborter, |
225 | pull.filter(msg => msg), |
226 | pull.filter(followFilter), |
227 | pull.filter(userFilter), |
228 | pull.filter(rootFilter), |
229 | pull.filter(channelFilter), |
230 | pull.filter(messageFilter) |
231 | ) |
232 | } |
233 | |
234 | function resetFeed ({ container, content }) { |
235 | if (typeof upScrollAborter === 'function') { |
236 | upScrollAborter.abort() |
237 | downScrollAborter.abort() |
238 | } |
239 | upScrollAborter = Abort() |
240 | downScrollAborter = Abort() |
241 | |
242 | container.scroll(0) |
243 | content.innerHTML = '' |
244 | } |
245 | |
246 | return { |
247 | filterMenu, |
248 | filterDownThrough, |
249 | filterUpThrough, |
250 | resetFeed |
251 | } |
252 | } |
253 | } |
254 |
Built with git-ssb-web