Commit 5cfee7fcb845036b63555b7546206364a1700b7a
compose: suggest mentions and emoji
Matt McKegg committed on 2/19/2017, 8:46:12 AMParent: 18d5d5c12c41651882d96e8d597c169e95dc4e73
Files changed
modules/message/html/compose.js | changed |
modules/profile/async/suggest.js | added |
package.json | changed |
styles/suggest-box.mcss | added |
modules/message/html/compose.js | ||
---|---|---|
@@ -6,12 +6,16 @@ | ||
6 | 6 … | var computed = require('mutant/computed') |
7 | 7 … | var nest = require('depnest') |
8 | 8 … | var mentions = require('ssb-mentions') |
9 | 9 … | var extend = require('xtend') |
10 … | +var addSuggest = require('suggest-box') | |
10 | 11 … | |
11 | 12 … | exports.needs = nest({ |
12 | 13 … | 'blob.html.input': 'first', |
13 | - 'message.async.publish': 'first' | |
14 … | + 'profile.async.suggest': 'first', | |
15 … | + 'message.async.publish': 'first', | |
16 … | + 'emoji.sync.names': 'first', | |
17 … | + 'emoji.sync.url': 'first' | |
14 | 18 … | }) |
15 | 19 … | |
16 | 20 … | exports.gives = nest('message.html.compose') |
17 | 21 … | |
@@ -20,8 +24,9 @@ | ||
20 | 24 … | var files = [] |
21 | 25 … | var filesById = {} |
22 | 26 … | var focused = Value(false) |
23 | 27 … | var hasContent = Value(false) |
28 … | + var getProfileSuggestions = api.profile.async.suggest() | |
24 | 29 … | var blurTimeout = null |
25 | 30 … | |
26 | 31 … | var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => { |
27 | 32 … | if (!shrink || hasContent) { |
@@ -72,8 +77,31 @@ | ||
72 | 77 … | textArea, |
73 | 78 … | actions |
74 | 79 … | ]) |
75 | 80 … | |
81 … | + addSuggest(textArea, (inputText, cb) => { | |
82 … | + if (inputText[0] === '@') { | |
83 … | + cb(null, getProfileSuggestions(inputText.slice(1))) | |
84 … | + } else if (inputText[0] === ':') { | |
85 … | + // suggest emojis | |
86 … | + var word = inputText.slice(1) | |
87 … | + if (word[word.length - 1] === ':') { | |
88 … | + word = word.slice(0, -1) | |
89 … | + } | |
90 … | + // TODO: when no emoji typed, list some default ones | |
91 … | + cb(null, api.emoji.sync.names().filter(function (name) { | |
92 … | + return name.slice(0, word.length) === word | |
93 … | + }).slice(0, 100).map(function (emoji) { | |
94 … | + return { | |
95 … | + image: api.emoji.sync.url(emoji), | |
96 … | + title: emoji, | |
97 … | + subtitle: emoji, | |
98 … | + value: ':' + emoji + ':' | |
99 … | + } | |
100 … | + })) | |
101 … | + } | |
102 … | + }, {cls: 'SuggestBox'}) | |
103 … | + | |
76 | 104 … | return composer |
77 | 105 … | |
78 | 106 … | // scoped |
79 | 107 … |
modules/profile/async/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 … | + 'profile.obs.recentlyUpdated': 'first', | |
6 … | + 'contact.obs.following': 'first', | |
7 … | + 'about.obs.name': 'first', | |
8 … | + 'about.obs.imageUrl': 'first', | |
9 … | + 'keys.sync.id': 'first' | |
10 … | +}) | |
11 … | + | |
12 … | +exports.gives = nest('profile.async.suggest') | |
13 … | + | |
14 … | +exports.create = function (api) { | |
15 … | + var suggestions = null | |
16 … | + var recentSuggestions = null | |
17 … | + | |
18 … | + return nest('profile.async.suggest', function () { | |
19 … | + loadSuggestions() | |
20 … | + return function (word) { | |
21 … | + if (!word) { | |
22 … | + return recentSuggestions() | |
23 … | + } else { | |
24 … | + return suggestions().filter((item) => { | |
25 … | + return item.title.toLowerCase().startsWith(word.toLowerCase()) | |
26 … | + }) | |
27 … | + } | |
28 … | + } | |
29 … | + }) | |
30 … | + | |
31 … | + function loadSuggestions () { | |
32 … | + if (!suggestions) { | |
33 … | + var id = api.keys.sync.id() | |
34 … | + var following = api.contact.obs.following(id) | |
35 … | + var recentlyUpdated = api.profile.obs.recentlyUpdated() | |
36 … | + var contacts = computed([following, recentlyUpdated], function (a, b) { | |
37 … | + var result = Array.from(a) | |
38 … | + b.forEach((item, i) => { | |
39 … | + if (!result.includes(item)) { | |
40 … | + result.push(item) | |
41 … | + } | |
42 … | + }) | |
43 … | + return result | |
44 … | + }) | |
45 … | + | |
46 … | + recentSuggestions = map(computed(recentlyUpdated, (items) => Array.from(items).slice(0, 10)), suggestion, {idle: true}) | |
47 … | + suggestions = map(contacts, suggestion, {idle: true}) | |
48 … | + watch(recentSuggestions) | |
49 … | + watch(suggestions) | |
50 … | + } | |
51 … | + } | |
52 … | + | |
53 … | + function suggestion (id) { | |
54 … | + var name = api.about.obs.name(id) | |
55 … | + return Struct({ | |
56 … | + title: name, | |
57 … | + subtitle: id.substring(0, 10), | |
58 … | + value: computed([name, id], mention), | |
59 … | + image: api.about.obs.imageUrl(id), | |
60 … | + showBoth: true | |
61 … | + }) | |
62 … | + } | |
63 … | +} | |
64 … | + | |
65 … | +function mention (name, id) { | |
66 … | + return `[@${name}](${id})` | |
67 … | +} |
package.json | ||
---|---|---|
@@ -27,9 +27,9 @@ | ||
27 | 27 … | "is-visible": "^2.1.1", |
28 | 28 … | "level": "~1.4.0", |
29 | 29 … | "level-memview": "0.0.0", |
30 | 30 … | "micro-css": "^1.0.0", |
31 | - "mutant": "^3.15.0", | |
31 … | + "mutant": "^3.15.1", | |
32 | 32 … | "mutant-pull-reduce": "^1.0.1", |
33 | 33 … | "non-private-ip": "^1.4.1", |
34 | 34 … | "on-change-network": "0.0.2", |
35 | 35 … | "on-wakeup": "^1.0.1", |
@@ -55,9 +55,10 @@ | ||
55 | 55 … | "ssb-msgs": "^5.2.0", |
56 | 56 … | "ssb-query": "~0.1.1", |
57 | 57 … | "ssb-ref": "~2.6.2", |
58 | 58 … | "ssb-sort": "^1.0.0", |
59 | - "statistics": "^3.3.0" | |
59 … | + "statistics": "^3.3.0", | |
60 … | + "suggest-box": "^2.2.3" | |
60 | 61 … | }, |
61 | 62 … | "devDependencies": { |
62 | 63 … | "electron": "~1.4.4" |
63 | 64 … | } |
styles/suggest-box.mcss | ||
---|---|---|
@@ -1,0 +1,48 @@ | ||
1 … | +SuggestBox { | |
2 … | + width: max-content; | |
3 … | + max-height: 50vh; | |
4 … | + overflow-y: scroll; | |
5 … | + background-color: #fff; | |
6 … | + border: 1px gainsboro solid; | |
7 … | + | |
8 … | + padding: .2rem .5rem; | |
9 … | + margin-top: .35rem; | |
10 … | + | |
11 … | + ul { | |
12 … | + list-style-type: none; | |
13 … | + padding: 0; | |
14 … | + | |
15 … | + li { | |
16 … | + display: flex; | |
17 … | + align-items: center; | |
18 … | + | |
19 … | + padding-right: .2rem; | |
20 … | + margin-bottom: .2rem; | |
21 … | + | |
22 … | + img { | |
23 … | + height: 36px; | |
24 … | + width: 36px; | |
25 … | + padding: .2rem; | |
26 … | + } | |
27 … | + | |
28 … | + strong { | |
29 … | + flex-grow: 1; | |
30 … | + margin-left: .5rem; | |
31 … | + font-weight: 300; | |
32 … | + } | |
33 … | + | |
34 … | + small { | |
35 … | + font-family: monospace; | |
36 … | + margin-left: .5rem; | |
37 … | + padding-right: .2rem; | |
38 … | + font-size: 1rem; | |
39 … | + } | |
40 … | + } | |
41 … | + | |
42 … | + li.selected { | |
43 … | + color: #fff; | |
44 … | + background: #0caaf9; | |
45 … | + } | |
46 … | + } | |
47 … | + | |
48 … | +} |
Built with git-ssb-web