git ssb

2+

mixmix / ticktack



Tree: 0107e46b337f0846775565435aaf8a90f95b1955

Files: 0107e46b337f0846775565435aaf8a90f95b1955 / message / html / compose.js

6292 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.sync.names': 'first',
14 'emoji.sync.url': 'first',
15 'message.async.publish': 'first',
16 // 'message.html.confirm': 'first'
17 'translations.sync.strings': '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 const strings = api.translations.sync.strings()
25
26 var files = []
27 var filesById = {}
28 var channelInputFocused = Value(false)
29 var textAreaFocused = Value(false)
30 var focused = computed([channelInputFocused, textAreaFocused], (a, b) => a || b)
31 var hasContent = Value(false)
32 var getProfileSuggestions = api.about.async.suggest()
33 // var getChannelSuggestions = api.channel.async.suggest()
34
35 var blurTimeout = null
36
37 var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => {
38 if (!shrink || hasContent) return true
39
40 return focused
41 })
42
43 // var channelInput = h('input.channel', {
44 // 'ev-input': () => hasContent.set(!!channelInput.value),
45 // 'ev-keyup': ev => {
46 // ev.target.value = ev.target.value.replace(/^#*([\w@%&])/, '#$1')
47 // },
48 // 'ev-blur': () => {
49 // clearTimeout(blurTimeout)
50 // blurTimeout = setTimeout(() => channelInputFocused.set(false), 200)
51 // },
52 // 'ev-focus': send(channelInputFocused.set, true),
53 // placeholder: '#channel (optional)',
54 // value: computed(meta.channel, ch => ch ? '#' + ch : null),
55 // disabled: when(meta.channel, true),
56 // title: when(meta.channel, 'Reply is in same channel as original message')
57 // })
58
59 var textArea = h('textarea', {
60 'ev-input': () => hasContent.set(!!textArea.value),
61 'ev-blur': () => {
62 clearTimeout(blurTimeout)
63 blurTimeout = setTimeout(() => textAreaFocused.set(false), 200)
64 },
65 'ev-focus': send(textAreaFocused.set, true),
66 placeholder
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 embed = file.type.match(/^image/) ? '!' : ''
77 var spacer = embed ? '\n' : ' '
78 var insertLink = spacer + embed + '[' + file.name + ']' + '(' + file.link + ')' + spacer
79
80 var pos = textArea.selectionStart
81 textArea.value = textArea.value.slice(0, pos) + insertLink + textArea.value.slice(pos)
82
83 console.log('added:', file)
84 })
85
86 fileInput.onclick = () => hasContent.set(true)
87 }
88 // if fileInput is null, send button moves to the left side
89 // and we don't want that.
90 else
91 fileInput = h('span')
92
93 var publishBtn = h('Button -primary', { 'ev-click': publish }, strings.sendMessage)
94
95 var actions = h('section.actions', [
96 fileInput,
97 publishBtn
98 ])
99
100 var composer = h('Compose', {
101 classList: when(expanded, '-expanded', '-contracted')
102 }, [
103 // channelInput,
104 textArea,
105 actions
106 ])
107
108 // addSuggest(channelInput, (inputText, cb) => {
109 // if (inputText[0] === '#') {
110 // cb(null, getChannelSuggestions(inputText.slice(1)))
111 // }
112 // }, {cls: 'SuggestBox'})
113 // channelInput.addEventListener('suggestselect', ev => {
114 // channelInput.value = ev.detail.id // HACK : this over-rides the markdown value
115 // })
116
117 addSuggest(textArea, (inputText, cb) => {
118 if (inputText[0] === '@') {
119 cb(null, getProfileSuggestions(inputText.slice(1)))
120 // } else if (inputText[0] === '#') {
121 // cb(null, getChannelSuggestions(inputText.slice(1)))
122 } else if (inputText[0] === ':') {
123 // suggest emojis
124 var word = inputText.slice(1)
125 if (word[word.length - 1] === ':') {
126 word = word.slice(0, -1)
127 }
128 // TODO: when no emoji typed, list some default ones
129 cb(null, api.emoji.sync.names().filter(function (name) {
130 return name.slice(0, word.length) === word
131 }).slice(0, 100).map(function (emoji) {
132 return {
133 image: api.emoji.sync.url(emoji),
134 title: emoji,
135 subtitle: emoji,
136 value: ':' + emoji + ':'
137 }
138 }))
139 }
140 }, {cls: 'SuggestBox'})
141
142 return composer
143
144 // scoped
145
146 function publish () {
147 publishBtn.disabled = true
148
149 // const channel = channelInput.value.startsWith('#')
150 // ? channelInput.value.substr(1).trim()
151 // : channelInput.value.trim()
152 const mentions = ssbMentions(textArea.value).map(mention => {
153 // merge markdown-detected mention with file info
154 var file = filesById[mention.link]
155 if (file) {
156 if (file.type) mention.type = file.type
157 if (file.size) mention.size = file.size
158 }
159 return mention
160 })
161
162 var content = assign({}, resolve(meta), {
163 text: textArea.value,
164 // channel,
165 mentions
166 })
167
168 // if (!channel) delete content.channel
169 if (!content.channel) delete content.channel
170 if (!mentions.length) delete content.mentions
171 if (content.recps && content.recps.length === 0) delete content.recps
172
173 try {
174 if (typeof prepublish === 'function') {
175 content = prepublish(content)
176 }
177 } catch (err) {
178 publishBtn.disabled = false
179 handleErr(err)
180 }
181
182 return api.message.async.publish(content, done)
183 // return api.message.html.confirm(content, done)
184
185 function done (err, msg) {
186 publishBtn.disabled = false
187 if (err) handleErr(err)
188 else if (msg) textArea.value = ''
189 if (cb) cb(err, msg)
190 }
191
192 function handleErr (err) {
193 if (cb) cb(err)
194 else throw err
195 }
196 }
197 }
198}
199
200
201
202
203

Built with git-ssb-web