git ssb

10+

Matt McKegg / patchwork



Tree: 85d9ae042b27a93cc430e3c2ca805da1d89bd739

Files: 85d9ae042b27a93cc430e3c2ca805da1d89bd739 / modules / message / html / compose.js

6250 bytesRaw
1var h = require('mutant/h')
2var when = require('mutant/when')
3var send = require('mutant/send')
4var resolve = require('mutant/resolve')
5var Value = require('mutant/value')
6var computed = require('mutant/computed')
7var nest = require('depnest')
8var mentions = require('ssb-mentions')
9var extend = require('xtend')
10var addSuggest = require('suggest-box')
11var emoji = require('emojilib')
12
13exports.needs = nest({
14 'blob.html.input': 'first',
15 'profile.async.suggest': 'first',
16 'channel.async.suggest': 'first',
17 'message.async.publish': 'first',
18 'emoji.sync.names': 'first',
19 'emoji.sync.url': 'first',
20 'intl.sync.i18n': 'first'
21})
22
23exports.gives = nest('message.html.compose')
24
25exports.create = function (api) {
26 const i18n = api.intl.sync.i18n
27 return nest('message.html.compose', function ({shrink = true, isPrivate, participants, meta, hooks, prepublish, placeholder = 'Write a message'}, cb) {
28 var files = []
29 var filesById = {}
30 var focused = Value(false)
31 var hasContent = Value(false)
32 var publishing = Value(false)
33 var getProfileSuggestions = api.profile.async.suggest()
34 var getChannelSuggestions = api.channel.async.suggest()
35
36 var blurTimeout = null
37
38 var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => {
39 if (!shrink || hasContent) {
40 return true
41 } else {
42 return focused
43 }
44 })
45
46 var textArea = h('textarea', {
47 'ev-input': function () {
48 hasContent.set(!!textArea.value)
49 },
50 'ev-blur': () => {
51 clearTimeout(blurTimeout)
52 blurTimeout = setTimeout(() => focused.set(false), 200)
53 },
54 'ev-focus': send(focused.set, true),
55 disabled: publishing,
56 placeholder
57 })
58
59 var warningMessage = Value(null)
60 var warning = h('section.warning',
61 { className: when(warningMessage, '-open', '-closed') },
62 [
63 h('div.warning', warningMessage),
64 h('div.close', { 'ev-click': () => warningMessage.set(null) }, 'x')
65 ]
66 )
67 var fileInput = api.blob.html.input(file => {
68 const megabytes = file.size / 1024 / 1024
69 if (megabytes >= 5) {
70 const rounded = Math.floor(megabytes * 100) / 100
71 warningMessage.set([
72 h('i.fa.fa-exclamation-triangle'),
73 h('strong', file.name),
74 ` is ${rounded}MB - the current limit is 5MB`
75 ])
76 return
77 }
78
79 files.push(file)
80 filesById[file.link] = file
81
82 var embed = file.type.indexOf('image/') === 0 ? '!' : ''
83 var pos = textArea.selectionStart
84 var before = textArea.value.slice(0, pos)
85 var after = textArea.value.slice(pos)
86
87 var spacer = embed ? '\n' : ' '
88 if (before && !before.endsWith(spacer)) before += spacer
89 if (!after.startsWith(spacer)) after = spacer + after
90
91 textArea.value = `${before}${embed}[${file.name}](${file.link})${after}`
92 console.log('added:', file)
93 })
94
95 fileInput.onclick = function () {
96 hasContent.set(true)
97 }
98
99 var publishBtn = h('button', {
100 'ev-click': publish,
101 classList: [
102 when(isPrivate, '-private')
103 ],
104 disabled: publishing
105 }, when(publishing,
106 i18n('Publishing...'),
107 when(isPrivate, i18n('Publish Privately'), i18n('Publish'))
108 ))
109
110 var actions = h('section.actions', [
111 fileInput,
112 publishBtn
113 ])
114
115 var composer = h('Compose', {
116 hooks,
117 classList: [
118 when(expanded, '-expanded', '-contracted')
119 ]
120 }, [
121 textArea,
122 warning,
123 actions
124 ])
125
126 composer.focus = function () {
127 textArea.focus()
128 }
129
130 composer.setText = function (value) {
131 textArea.value = value
132 hasContent.set(!!textArea.value)
133 }
134
135 addSuggest(textArea, (inputText, cb) => {
136 if (inputText[0] === '@') {
137 cb(null, getProfileSuggestions(inputText.slice(1), resolve(participants)))
138 } else if (inputText[0] === '#') {
139 cb(null, getChannelSuggestions(inputText.slice(1)))
140 } else if (inputText[0] === ':') {
141 // suggest emojis
142 var word = inputText.slice(1)
143 if (word[word.length - 1] === ':') {
144 word = word.slice(0, -1)
145 }
146 cb(null, suggestEmoji(word).slice(0, 100).map(function (emoji) {
147 return {
148 image: api.emoji.sync.url(emoji),
149 title: emoji,
150 subtitle: emoji,
151 value: ':' + emoji + ':'
152 }
153 }))
154 }
155 }, {cls: 'SuggestBox'})
156
157 return composer
158
159 // scoped
160
161 function publish () {
162 publishing.set(true)
163
164 var content = extend(resolve(meta), {
165 text: textArea.value,
166 mentions: mentions(textArea.value).map(mention => {
167 // merge markdown-detected mention with file info
168 var file = filesById[mention.link]
169 if (file) {
170 if (file.type) mention.type = file.type
171 if (file.size) mention.size = file.size
172 }
173 return mention
174 })
175 })
176
177 try {
178 if (typeof prepublish === 'function') {
179 content = prepublish(content)
180 }
181 } catch (err) {
182 return done(err)
183 }
184
185 return api.message.async.publish(content, done)
186
187 function done (err, msg) {
188 publishing.set(false)
189 if (err) {
190 if (cb) cb(err)
191 else {
192 showDialog({
193 type: 'error',
194 title: i18n('Error'),
195 buttons: [i18n('OK')],
196 message: i18n('An error occured while publishing your message.'),
197 detail: err.message
198 })
199 }
200 } else {
201 if (msg) textArea.value = ''
202 if (cb) cb(null, msg)
203 }
204 }
205 }
206 })
207
208 function suggestEmoji (prefix) {
209 var availableEmoji = api.emoji.sync.names()
210 return emoji.ordered.filter(key => {
211 if (!availableEmoji.includes(key)) return false
212 return key.startsWith(prefix) || key.includes('_' + prefix) || emoji.lib[key].keywords.some(word => word.startsWith(prefix) || word.startsWith(':' + prefix))
213 })
214 }
215}
216
217function showDialog (opts) {
218 var electron = require('electron')
219 electron.remote.dialog.showMessageBox(electron.remote.getCurrentWindow(), opts)
220}
221

Built with git-ssb-web