git ssb

16+

Dominic / patchbay



Tree: dd67652a2a492f8668f29984959b814c9e3cfe6d

Files: dd67652a2a492f8668f29984959b814c9e3cfe6d / app / html / filter.js

7047 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 addSuggest(channelInput, (inputText, cb) => {
145 if (inputText[0] === '#') {
146 api.channel.async.suggest(inputText.slice(1), cb)
147 }
148 }, {cls: 'PatchSuggest'})
149 channelInput.addEventListener('suggestselect', ev => {
150 const channels = channelInput.value.trim()
151
152 api.settings.sync.set({ filter: { exclude: { channels: channels } } })
153
154 draw()
155 })
156
157 addSuggest(userInput, (inputText, cb) => {
158 inputText = inputText.replace(/^@/, '')
159 api.about.async.suggest(inputText, cb)
160 }, {cls: 'PatchSuggest'})
161 userInput.addEventListener('suggestselect', ev => userId.set(ev.detail.id))
162
163 function followFilter (msg) {
164 if (!filterSettings().only.peopleAndChannelsIFollow) return true
165
166 return (msg.value && Array.from(peopleIFollow()).concat(myId).includes(msg.value.author)) ||
167 (msg.value && msg.value.content && Array.from(channelsIFollow()).includes(msg.value.content.channel))
168 }
169
170 function userFilter (msg) {
171 const id = resolve(userId)
172 if (!id) return true
173
174 return msg.value.author === id
175 }
176
177 function rootFilter (msg) {
178 if (!filterSettings().only.rootMessages) return true
179
180 return !msg.value.content.root
181 }
182
183 function channelFilter (msg) {
184 var filters = filterSettings().exclude.channels
185 if (!filters) return true
186 filters = filters.split(' ').map(c => c.slice(1))
187
188 return msg.value.content && !filters.includes(msg.value.content.channel)
189 }
190
191 function messageFilter (msg) {
192 var { type } = msg.value.content
193 if (/^chess/.test(type)) {
194 type = 'chess'
195 }
196
197 if (typeof msg.value.content === 'string') {
198 type = 'private'
199 }
200
201 return get(filterSettings(), ['show', type], true)
202 }
203
204 var downScrollAborter
205
206 function filterDownThrough () {
207 return pull(
208 downScrollAborter,
209 pull.filter(msg => msg),
210 pull.filter(followFilter),
211 pull.filter(userFilter),
212 pull.filter(rootFilter),
213 pull.filter(channelFilter),
214 pull.filter(messageFilter)
215 )
216 }
217
218 var upScrollAborter
219
220 function filterUpThrough () {
221 return pull(
222 upScrollAborter,
223 pull.filter(msg => msg),
224 pull.filter(followFilter),
225 pull.filter(userFilter),
226 pull.filter(rootFilter),
227 pull.filter(channelFilter),
228 pull.filter(messageFilter)
229 )
230 }
231
232 function resetFeed ({ container, content }) {
233 if (typeof upScrollAborter === 'function') {
234 upScrollAborter.abort()
235 downScrollAborter.abort()
236 }
237 upScrollAborter = Abort()
238 downScrollAborter = Abort()
239
240 container.keyboardScroll(0)
241 content.innerHTML = ''
242 }
243
244 return {
245 filterMenu,
246 filterDownThrough,
247 filterUpThrough,
248 resetFeed
249 }
250 }
251}
252

Built with git-ssb-web