git ssb

2+

mixmix / ticktack



Tree: e4aaa311b7efe7bf2540be819c77348f258eb18d

Files: e4aaa311b7efe7bf2540be819c77348f258eb18d / message / html / compose.js

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

Built with git-ssb-web