git ssb

16+

Dominic / patchbay



Tree: 81f1fc4beaf000077eb2a907b7f3cee091bce3ba

Files: 81f1fc4beaf000077eb2a907b7f3cee091bce3ba / message / html / compose.js

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

Built with git-ssb-web