git ssb

16+

Dominic / patchbay



Tree: 8069cbb074f6985dedd767cc31ba0586638550ea

Files: 8069cbb074f6985dedd767cc31ba0586638550ea / app / html / filter.js

7163 bytesRaw
1const nest = require('depnest')
2const { h, Value, when, computed, resolve } = require('mutant')
3const Abort = require('pull-abortable')
4const pull = require('pull-stream')
5const addSuggest = require('suggest-box')
6const get = require('lodash/get')
7const isEqual = require('lodash/isEqual')
8const isEmpty = require('lodash/isEmpty')
9const { isFeed } = require('ssb-ref')
10
11exports.gives = nest('app.html.filter')
12
13exports.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
23exports.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