Commit 26d9fa8ec162c42e2fa61b44c63952e0f38b38c3
suggest on search box, also suggest channels
Matt McKegg committed on 2/19/2017, 9:47:23 AMParent: 5cfee7fcb845036b63555b7546206364a1700b7a
Files changed
main-window.js | ||
---|---|---|
@@ -10,8 +10,9 @@ | ||
10 | 10 | var MutantMap = require('mutant/map') |
11 | 11 | var Url = require('url') |
12 | 12 | var insertCss = require('insert-css') |
13 | 13 | var nest = require('depnest') |
14 | +var addSuggest = require('suggest-box') | |
14 | 15 | |
15 | 16 | module.exports = function (config) { |
16 | 17 | var sockets = combine( |
17 | 18 | overrideConfig(config), |
@@ -23,18 +24,26 @@ | ||
23 | 24 | |
24 | 25 | var api = entry(sockets, nest({ |
25 | 26 | 'page.html.render': 'first', |
26 | 27 | 'keys.sync.id': 'first', |
27 | - 'blob.sync.url': 'first' | |
28 | + 'blob.sync.url': 'first', | |
29 | + 'profile.async.suggest': 'first', | |
30 | + 'channel.async.suggest': 'first' | |
28 | 31 | })) |
29 | 32 | |
30 | 33 | var renderPage = api.page.html.render |
31 | 34 | var id = api.keys.sync.id() |
35 | + var getProfileSuggestions = api.profile.async.suggest() | |
36 | + var getChannelSuggestions = api.channel.async.suggest() | |
32 | 37 | |
33 | 38 | var searchTimer = null |
34 | 39 | var searchBox = h('input.search', { |
35 | 40 | type: 'search', |
36 | - placeholder: 'word, @key, #channel' | |
41 | + placeholder: 'word, @key, #channel', | |
42 | + 'ev-suggestselect': (ev) => { | |
43 | + setView(ev.detail.id) | |
44 | + searchBox.value = ev.detail.id | |
45 | + } | |
37 | 46 | }) |
38 | 47 | |
39 | 48 | searchBox.oninput = function () { |
40 | 49 | clearTimeout(searchTimer) |
@@ -83,9 +92,9 @@ | ||
83 | 92 | })) |
84 | 93 | |
85 | 94 | insertCss(require('./styles')) |
86 | 95 | |
87 | - return h(`MainWindow -${process.platform}`, { | |
96 | + var container = h(`MainWindow -${process.platform}`, { | |
88 | 97 | events: { |
89 | 98 | click: catchLinks |
90 | 99 | } |
91 | 100 | }, [ |
@@ -113,8 +122,18 @@ | ||
113 | 122 | ]), |
114 | 123 | mainElement |
115 | 124 | ]) |
116 | 125 | |
126 | + addSuggest(searchBox, (inputText, cb) => { | |
127 | + if (inputText[0] === '@') { | |
128 | + cb(null, getProfileSuggestions(inputText.slice(1)), {idOnly: true}) | |
129 | + } else if (inputText[0] === '#') { | |
130 | + cb(null, getChannelSuggestions(inputText.slice(1))) | |
131 | + } | |
132 | + }, {cls: 'SuggestBox'}) | |
133 | + | |
134 | + return container | |
135 | + | |
117 | 136 | // scoped |
118 | 137 | |
119 | 138 | function catchLinks (ev) { |
120 | 139 | if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.defaultPrevented) { |
@@ -210,11 +229,17 @@ | ||
210 | 229 | |
211 | 230 | function doSearch () { |
212 | 231 | var value = searchBox.value.trim() |
213 | 232 | if (value.startsWith('/') || value.startsWith('?') || value.startsWith('@') || value.startsWith('#') || value.startsWith('%')) { |
214 | - setView(value) | |
233 | + if (value.startsWith('@') && value.length < 30) { | |
234 | + return // probably not a key | |
235 | + } else if (value.length > 2) { | |
236 | + setView(value) | |
237 | + } | |
215 | 238 | } else if (value.trim()) { |
216 | - setView(`?${value.trim()}`) | |
239 | + if (value.length > 2) { | |
240 | + setView(`?${value.trim()}`) | |
241 | + } | |
217 | 242 | } else { |
218 | 243 | setView('/public') |
219 | 244 | } |
220 | 245 | } |
modules/channel/obs/recent.js | ||
---|---|---|
@@ -9,9 +9,9 @@ | ||
9 | 9 | exports.create = function (api) { |
10 | 10 | var channelsLookup = Dict() |
11 | 11 | |
12 | 12 | var recentChannels = computed(channelsLookup, (lookup) => { |
13 | - var values = Object.keys(lookup).map(x => lookup[x]).sort((a, b) => b.updatedAt - a.updatedAt) | |
13 | + var values = Object.keys(lookup).map(x => lookup[x]).sort((a, b) => b.updatedAt - a.updatedAt).map(x => x.id) | |
14 | 14 | return values |
15 | 15 | }, {nextTick: true}) |
16 | 16 | |
17 | 17 | return nest({ |
@@ -19,9 +19,9 @@ | ||
19 | 19 | if (msg.key && msg.value && msg.value.content) { |
20 | 20 | var c = msg.value.content |
21 | 21 | if (c.type === 'post' && typeof c.channel === 'string') { |
22 | 22 | var name = c.channel.trim() |
23 | - if (name) { | |
23 | + if (name && name.length < 30) { | |
24 | 24 | var channel = channelsLookup.get(name) |
25 | 25 | if (!channel) { |
26 | 26 | channel = Struct({ |
27 | 27 | id: name, |
modules/channel/obs/suggest.js | ||
---|---|---|
@@ -1,0 +1,67 @@ | ||
1 | +var nest = require('depnest') | |
2 | +var {Struct, map, computed, watch} = require('mutant') | |
3 | + | |
4 | +exports.needs = nest({ | |
5 | + 'channel.obs.recent': 'first', | |
6 | + 'channel.obs.subscribed': 'first', | |
7 | + 'keys.sync.id': 'first' | |
8 | +}) | |
9 | + | |
10 | +exports.gives = nest('channel.async.suggest') | |
11 | + | |
12 | +exports.create = function (api) { | |
13 | + var suggestions = null | |
14 | + var subscribed = null | |
15 | + | |
16 | + return nest('channel.async.suggest', function () { | |
17 | + loadSuggestions() | |
18 | + return function (word) { | |
19 | + if (!word) { | |
20 | + return suggestions().slice(0, 100) | |
21 | + } else { | |
22 | + return suggestions().filter((item) => { | |
23 | + return item.title.toLowerCase().startsWith(word.toLowerCase()) | |
24 | + }) | |
25 | + } | |
26 | + } | |
27 | + }) | |
28 | + | |
29 | + function loadSuggestions () { | |
30 | + if (!suggestions) { | |
31 | + var id = api.keys.sync.id() | |
32 | + subscribed = api.channel.obs.subscribed(id) | |
33 | + var recentlyUpdated = api.channel.obs.recent() | |
34 | + var contacts = computed([subscribed, recentlyUpdated], function (a, b) { | |
35 | + var result = Array.from(a) | |
36 | + b.forEach((item, i) => { | |
37 | + if (!result.includes(item)) { | |
38 | + result.push(item) | |
39 | + } | |
40 | + }) | |
41 | + return result | |
42 | + }) | |
43 | + | |
44 | + suggestions = map(contacts, suggestion, {idle: true}) | |
45 | + watch(suggestions) | |
46 | + } | |
47 | + } | |
48 | + | |
49 | + function suggestion (id) { | |
50 | + return Struct({ | |
51 | + title: id, | |
52 | + id: `#${id}`, | |
53 | + subtitle: computed([id, subscribed], subscribedCaption), | |
54 | + value: computed([id], mention) | |
55 | + }) | |
56 | + } | |
57 | +} | |
58 | + | |
59 | +function subscribedCaption (id, subscribed) { | |
60 | + if (subscribed.has(id)) { | |
61 | + return 'subscribed' | |
62 | + } | |
63 | +} | |
64 | + | |
65 | +function mention (id) { | |
66 | + return `[#${id}](#${id})` | |
67 | +} |
modules/message/html/compose.js | ||
---|---|---|
@@ -11,8 +11,9 @@ | ||
11 | 11 | |
12 | 12 | exports.needs = nest({ |
13 | 13 | 'blob.html.input': 'first', |
14 | 14 | 'profile.async.suggest': 'first', |
15 | + 'channel.async.suggest': 'first', | |
15 | 16 | 'message.async.publish': 'first', |
16 | 17 | 'emoji.sync.names': 'first', |
17 | 18 | 'emoji.sync.url': 'first' |
18 | 19 | }) |
@@ -25,8 +26,10 @@ | ||
25 | 26 | var filesById = {} |
26 | 27 | var focused = Value(false) |
27 | 28 | var hasContent = Value(false) |
28 | 29 | var getProfileSuggestions = api.profile.async.suggest() |
30 | + var getChannelSuggestions = api.channel.async.suggest() | |
31 | + | |
29 | 32 | var blurTimeout = null |
30 | 33 | |
31 | 34 | var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => { |
32 | 35 | if (!shrink || hasContent) { |
@@ -80,8 +83,10 @@ | ||
80 | 83 | |
81 | 84 | addSuggest(textArea, (inputText, cb) => { |
82 | 85 | if (inputText[0] === '@') { |
83 | 86 | cb(null, getProfileSuggestions(inputText.slice(1))) |
87 | + } else if (inputText[0] === '#') { | |
88 | + cb(null, getChannelSuggestions(inputText.slice(1))) | |
84 | 89 | } else if (inputText[0] === ':') { |
85 | 90 | // suggest emojis |
86 | 91 | var word = inputText.slice(1) |
87 | 92 | if (word[word.length - 1] === ':') { |
modules/page/html/render/public.js | ||
---|---|---|
@@ -120,22 +120,22 @@ | ||
120 | 120 | classList: 'ChannelList', |
121 | 121 | hidden: loading |
122 | 122 | }, [ |
123 | 123 | map(channels, (channel) => { |
124 | - var subscribed = subscribedChannels.has(channel.id) | |
124 | + var subscribed = subscribedChannels.has(channel) | |
125 | 125 | return h('a.channel', { |
126 | - href: `#${channel.id}`, | |
126 | + href: `#${channel}`, | |
127 | 127 | classList: [ |
128 | 128 | when(subscribed, '-subscribed') |
129 | 129 | ] |
130 | 130 | }, [ |
131 | - h('span.name', '#' + channel.id), | |
131 | + h('span.name', '#' + channel), | |
132 | 132 | when(subscribed, |
133 | 133 | h('a.-unsubscribe', { |
134 | - 'ev-click': send(unsubscribe, channel.id) | |
134 | + 'ev-click': send(unsubscribe, channel) | |
135 | 135 | }, 'Unsubscribe'), |
136 | 136 | h('a.-subscribe', { |
137 | - 'ev-click': send(subscribe, channel.id) | |
137 | + 'ev-click': send(subscribe, channel) | |
138 | 138 | }, 'Subscribe') |
139 | 139 | ) |
140 | 140 | ]) |
141 | 141 | }, {maxTime: 5}) |
Built with git-ssb-web