git ssb

2+

mixmix / ticktack



Tree: 453e088bb1a566f6624aca8299dbbac2fee35272

Files: 453e088bb1a566f6624aca8299dbbac2fee35272 / message / html / compose.js

5586 bytesRaw
1const nest = require('depnest')
2const { h, when, send, resolve, Value, computed, map } = require('mutant')
3const assign = require('lodash/assign')
4const ssbMentions = require('ssb-mentions')
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.async.suggest': 'first',
14 'emoji.sync.names': 'first',
15 'emoji.sync.url': 'first',
16 'message.async.publish': 'first',
17 'message.html.markdown': 'first',
18 // 'message.html.confirm': 'first'
19 'translations.sync.strings': 'first'
20})
21
22exports.create = function (api) {
23 return nest('message.html.compose', compose)
24
25 function compose (options, cb) {
26 var {
27 meta, // required
28 feedIdsInThread = [],
29 placeholder,
30 shrink = true,
31 canAttach = true, canPreview = true,
32 prepublish
33 } = options
34
35 const strings = api.translations.sync.strings()
36 const getUserSuggestions = api.about.async.suggest()
37 const getChannelSuggestions = api.channel.async.suggest()
38 const getEmojiSuggestions = api.emoji.async.suggest()
39
40 placeholder = placeholder || strings.writeMessage
41
42 var files = []
43 var filesById = {}
44 var textAreaFocused = Value(false)
45 var focused = textAreaFocused
46 var hasContent = Value(false)
47
48 var blurTimeout = null
49
50 var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => {
51 if (!shrink || hasContent) return true
52
53 return focused
54 })
55
56 var textRaw = Value('')
57 var textArea = h('textarea', {
58 'ev-input': () => textRaw.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 textRaw(text => hasContent.set(!!text))
67
68 textArea.publish = publish // TODO: fix - clunky api for the keyboard shortcut to target
69
70 var fileInput
71 if (!meta.recps) {
72 fileInput = api.blob.html.input(file => {
73 files.push(file)
74 filesById[file.link] = file
75
76 var imgPrefix = file.type.match(/^image/) ? '!' : ''
77 var spacer = imgPrefix ? '\n' : ' '
78 var insertLink = spacer + imgPrefix + '[' + file.name + ']' + '(' + file.link + ')' + spacer
79
80 var pos = textArea.selectionStart
81 var newText = textRaw().slice(0, pos) + insertLink + textRaw().slice(pos)
82 textArea.value = newText
83 textRaw.set(newText)
84
85 console.log('added:', file)
86 })
87
88 fileInput.onclick = () => hasContent.set(true)
89 }
90 // if fileInput is null, send button moves to the left side
91 // and we don't want that.
92 else { fileInput = h('input', { style: {visibility: 'hidden'} }) }
93
94 function PreviewSetup (strings) {
95 var showPreview = Value(false)
96 var previewBtn = h('Button',
97 {
98 className: when(showPreview, '-strong', '-subtle'),
99 'ev-click': () => showPreview.set(!showPreview())
100 },
101 when(showPreview, strings.blogNew.actions.edit, strings.blogNew.actions.preview)
102 )
103 return { previewBtn, showPreview }
104 }
105 var { previewBtn, showPreview } = PreviewSetup(strings)
106 var preview = computed(textRaw, text => api.message.html.markdown(text))
107
108 var publishBtn = h('Button -primary', { 'ev-click': publish }, strings.sendMessage)
109
110 var actions = h('section.actions', [
111 canAttach ? fileInput : '',
112 canPreview ? previewBtn : '',
113 publishBtn
114 ])
115
116 var composer = h('Compose', {
117 classList: when(expanded, '-expanded', '-contracted')
118 }, [
119 when(showPreview, preview, textArea),
120 actions
121 ])
122
123 addSuggest(textArea, (inputText, cb) => {
124 const char = inputText[0]
125 const wordFragment = inputText.slice(1)
126
127 if (char === '@') cb(null, getUserSuggestions(wordFragment, feedIdsInThread))
128 if (char === '#') cb(null, getChannelSuggestions(wordFragment))
129 if (char === ':') cb(null, getEmojiSuggestions(wordFragment))
130 }, {cls: 'PatchSuggest'})
131
132 return composer
133
134 // scoped
135
136 function publish () {
137 publishBtn.disabled = true
138 const text = resolve(textRaw)
139
140 const mentions = ssbMentions(text).map(mention => {
141 // merge markdown-detected mention with file info
142 var file = filesById[mention.link]
143 if (file) {
144 if (file.type) mention.type = file.type
145 if (file.size) mention.size = file.size
146 }
147 return mention
148 })
149
150 var content = assign({}, resolve(meta), {
151 text,
152 mentions
153 })
154 for (var k in content) { content[k] = resolve(content[k]) }
155
156 if (!content.channel) delete content.channel
157 if (!mentions.length) delete content.mentions
158 if (content.recps && content.recps.length === 0) delete content.recps
159
160 if (typeof prepublish === 'function') {
161 prepublish(content, function (err, content) {
162 if (err) handleErr(err)
163 else api.message.async.publish(content, done)
164 })
165 } else { api.message.async.publish(content, done) }
166
167 function done (err, msg) {
168 publishBtn.disabled = false
169 if (err) handleErr(err)
170 else if (msg) {
171 textRaw.set('')
172 textArea.value = ''
173 }
174 if (cb) cb(err, msg)
175 }
176
177 function handleErr (err) {
178 publishBtn.disabled = false
179 if (cb) cb(err)
180 else throw err
181 }
182 }
183 }
184}
185

Built with git-ssb-web