git ssb

2+

mixmix / ticktack



Tree: f6a16e3fc5c25cadd2445d141a36a91d964a1813

Files: f6a16e3fc5c25cadd2445d141a36a91d964a1813 / message / html / compose.js

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

Built with git-ssb-web