git ssb

16+

Dominic / patchbay



Tree: 78dec1818c3a24d568c11eeaeaf553ac7a3e9140

Files: 78dec1818c3a24d568c11eeaeaf553ac7a3e9140 / message / html / compose.js

6289 bytesRaw
1const { h, when, send, resolve, Value, computed } = require('mutant')
2const nest = require('depnest')
3const ssbMentions = require('ssb-mentions')
4const extend = require('xtend')
5const addSuggest = require('suggest-box')
6
7exports.gives = nest('message.html.compose')
8
9exports.needs = nest({
10 'about.async.suggest': 'first',
11 'blob.html.input': 'first',
12 'channel.async.suggest': 'first',
13 'emoji.sync': {
14 names: 'first',
15 url: 'first'
16 },
17 'message.html.confirm': 'first'
18})
19
20exports.create = function (api) {
21 return nest({ 'message.html.compose': compose })
22
23 function compose ({ shrink = true, meta, prepublish, placeholder = 'Write a message' }, cb) {
24 var files = []
25 var filesById = {}
26 var channelInputFocused = Value(false)
27 var textAreaFocused = Value(false)
28 var focused = computed([channelInputFocused, textAreaFocused], (a, b) => a || b)
29 var hasContent = Value(false)
30 var getProfileSuggestions = api.about.async.suggest()
31 var getChannelSuggestions = api.channel.async.suggest()
32
33 var blurTimeout = null
34
35 var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => {
36 if (!shrink || hasContent) return true
37
38 return focused
39 })
40
41 var channelInput = h('input.channel', {
42 'ev-input': () => hasContent.set(!!channelInput.value),
43 'ev-keyup': ev => {
44 ev.target.value = ev.target.value.replace(/^#*([\w@%&])/, '#$1')
45 },
46 'ev-blur': () => {
47 clearTimeout(blurTimeout)
48 blurTimeout = setTimeout(() => channelInputFocused.set(false), 200)
49 },
50 'ev-focus': send(channelInputFocused.set, true),
51 placeholder: '#channel (optional)',
52 value: computed(meta.channel, ch => ch ? '#' + ch : null),
53 disabled: when(meta.channel, true),
54 title: when(meta.channel, 'Reply is in same channel as original message')
55 })
56
57 var textArea = h('textarea', {
58 'ev-input': () => hasContent.set(!!textArea.value),
59 'ev-blur': () => {
60 clearTimeout(blurTimeout)
61 blurTimeout = setTimeout(() => textAreaFocused.set(false), 200)
62 },
63 'ev-focus': send(textAreaFocused.set, true),
64 placeholder
65 })
66 textArea.publish = publish // TODO: fix - clunky api for the keyboard shortcut to target
67
68 var warningMessage = Value(null)
69 var warning = h('section.warning',
70 { className: when(warningMessage, '-open', '-closed') },
71 [
72 h('div.warning', warningMessage),
73 h('div.close', { 'ev-click': () => warningMessage.set(null) }, 'x')
74 ]
75 )
76 var fileInput = api.blob.html.input(file => {
77
78 const megabytes = file.size / 1024 / 1024
79 if (megabytes >= 5) {
80 const rounded = Math.floor(megabytes*100)/100
81 warningMessage.set([
82 h('i.fa.fa-exclamation-triangle'),
83 h('strong', file.name),
84 ` is ${rounded}MB - the current limit is 5MB`
85 ])
86 return
87 }
88
89 files.push(file)
90 filesById[file.link] = file
91
92 const pos = textArea.selectionStart
93 const embed = file.type.match(/^image/) ? '!' : ''
94 const spacer = embed ? '\n' : ' '
95 const insertLink = spacer + embed + '[' + file.name + ']' + '(' + file.link + ')' + spacer
96
97 textArea.value = textArea.value.slice(0, pos) + insertLink + textArea.value.slice(pos)
98
99 console.log('added:', file)
100 })
101
102 fileInput.onclick = () => hasContent.set(true)
103
104 var publishBtn = h('button', { 'ev-click': publish }, 'Publish')
105
106 var actions = h('section.actions', [
107 fileInput,
108 publishBtn
109 ])
110
111 var composer = h('Compose', {
112 classList: when(expanded, '-expanded', '-contracted')
113 }, [
114 channelInput,
115 textArea,
116 warning,
117 actions
118 ])
119
120 addSuggest(channelInput, (inputText, cb) => {
121 if (inputText[0] === '#') {
122 cb(null, getChannelSuggestions(inputText.slice(1)))
123 }
124 }, {cls: 'SuggestBox'})
125 channelInput.addEventListener('suggestselect', ev => {
126 channelInput.value = ev.detail.id // HACK : this over-rides the markdown value
127 })
128
129 addSuggest(textArea, (inputText, cb) => {
130 if (inputText[0] === '@') {
131 cb(null, getProfileSuggestions(inputText.slice(1)))
132 } else if (inputText[0] === '#') {
133 cb(null, getChannelSuggestions(inputText.slice(1)))
134 } else if (inputText[0] === ':') {
135 // suggest emojis
136 var word = inputText.slice(1)
137 if (word[word.length - 1] === ':') {
138 word = word.slice(0, -1)
139 }
140 // TODO: when no emoji typed, list some default ones
141 cb(null, api.emoji.sync.names().filter(function (name) {
142 return name.slice(0, word.length) === word
143 }).slice(0, 100).map(function (emoji) {
144 return {
145 image: api.emoji.sync.url(emoji),
146 title: emoji,
147 subtitle: emoji,
148 value: ':' + emoji + ':'
149 }
150 }))
151 }
152 }, {cls: 'SuggestBox'})
153
154 return composer
155
156 // scoped
157
158 function publish () {
159 publishBtn.disabled = true
160
161 const channel = channelInput.value.startsWith('#')
162 ? channelInput.value.substr(1).trim()
163 : channelInput.value.trim()
164 const mentions = ssbMentions(textArea.value).map(mention => {
165 // merge markdown-detected mention with file info
166 var file = filesById[mention.link]
167 if (file) {
168 if (file.type) mention.type = file.type
169 if (file.size) mention.size = file.size
170 }
171 return mention
172 })
173
174 var content = extend(resolve(meta), {
175 text: textArea.value,
176 channel,
177 mentions
178 })
179
180 if (!channel) delete content.channel
181 if (!mentions.length) delete content.mentions
182 if (content.recps && content.recps.length === 0) delete content.recps
183
184 try {
185 if (typeof prepublish === 'function') {
186 content = prepublish(content)
187 }
188 } catch (err) {
189 publishBtn.disabled = false
190 if (cb) cb(err)
191 else throw err
192 }
193
194 return api.message.html.confirm(content, done)
195
196 function done (err, msg) {
197 publishBtn.disabled = false
198 if (err) throw err
199 else if (msg) textArea.value = ''
200 if (cb) cb(err, msg)
201 }
202 }
203 }
204}
205

Built with git-ssb-web