git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: 84f42d561913f861b364caecc35050feadc23744

Files: 84f42d561913f861b364caecc35050feadc23744 / modules / message / html / compose.js

5296 bytesRaw
1var h = require('mutant/h')
2var when = require('mutant/when')
3var send = require('mutant/send')
4var resolve = require('mutant/resolve')
5var Value = require('mutant/value')
6var computed = require('mutant/computed')
7var nest = require('depnest')
8var mentions = require('ssb-mentions')
9var extend = require('xtend')
10var addSuggest = require('suggest-box')
11
12exports.needs = nest({
13 'blob.html.input': 'first',
14 'profile.async.suggest': 'first',
15 'channel.async.suggest': 'first',
16 'message.async.publish': 'first',
17 'emoji.sync.names': 'first',
18 'emoji.sync.url': 'first',
19 'intl.sync.i18n': 'first',
20})
21
22exports.gives = nest('message.html.compose')
23
24exports.create = function (api) {
25 const i18n = api.intl.sync.i18n
26 return nest('message.html.compose', function ({shrink = true, meta, hooks, prepublish, placeholder = 'Write a message'}, cb) {
27 var files = []
28 var filesById = {}
29 var focused = Value(false)
30 var hasContent = Value(false)
31 var publishing = Value(false)
32 var getProfileSuggestions = api.profile.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) {
39 return true
40 } else {
41 return focused
42 }
43 })
44
45 var textArea = h('textarea', {
46 'ev-input': function () {
47 hasContent.set(!!textArea.value)
48 },
49 'ev-blur': () => {
50 clearTimeout(blurTimeout)
51 blurTimeout = setTimeout(() => focused.set(false), 200)
52 },
53 'ev-focus': send(focused.set, true),
54 disabled: publishing,
55 placeholder
56 })
57
58 var warningMessage = Value(null)
59 var warning = h('section.warning',
60 { className: when(warningMessage, '-open', '-closed') },
61 [
62 h('div.warning', warningMessage),
63 h('div.close', { 'ev-click': () => warningMessage.set(null) }, 'x')
64 ]
65 )
66 var fileInput = api.blob.html.input(file => {
67 const megabytes = file.size / 1024 / 1024
68 if (megabytes >= 5) {
69 const rounded = Math.floor(megabytes*100)/100
70 warningMessage.set([
71 h('i.fa.fa-exclamation-triangle'),
72 h('strong', file.name),
73 ` is ${rounded}MB - the current limit is 5MB`
74 ])
75 return
76 }
77
78 files.push(file)
79 filesById[file.link] = file
80
81 var embed = file.type.indexOf('image/') === 0 ? '!' : ''
82 var pos = textArea.selectionStart
83 var before = textArea.value.slice(0, pos)
84 var after = textArea.value.slice(pos)
85
86 var spacer = embed ? '\n' : ' '
87 if (before && !before.endsWith(spacer)) before += spacer
88 if (!after.startsWith(spacer)) after = spacer + after
89
90 textArea.value = `${before}${embed}[${file.name}](${file.link})${after}`
91 console.log('added:', file)
92 })
93
94 fileInput.onclick = function () {
95 hasContent.set(true)
96 }
97
98 var publishBtn = h('button', {
99 'ev-click': publish,
100 disabled: publishing
101 }, when(publishing, i18n('Publishing...'), i18n('Publish')))
102
103 var actions = h('section.actions', [
104 fileInput,
105 publishBtn
106 ])
107
108 var composer = h('Compose', {
109 hooks,
110 classList: [
111 when(expanded, '-expanded', '-contracted')
112 ]
113 }, [
114 textArea,
115 warning,
116 actions
117 ])
118
119 composer.focus = function () {
120 textArea.focus()
121 }
122
123 addSuggest(textArea, (inputText, cb) => {
124 if (inputText[0] === '@') {
125 cb(null, getProfileSuggestions(inputText.slice(1)))
126 } else if (inputText[0] === '#') {
127 cb(null, getChannelSuggestions(inputText.slice(1)))
128 } else if (inputText[0] === ':') {
129 // suggest emojis
130 var word = inputText.slice(1)
131 if (word[word.length - 1] === ':') {
132 word = word.slice(0, -1)
133 }
134 // TODO: when no emoji typed, list some default ones
135 cb(null, api.emoji.sync.names().filter(function (name) {
136 return name.slice(0, word.length) === word
137 }).slice(0, 100).map(function (emoji) {
138 return {
139 image: api.emoji.sync.url(emoji),
140 title: emoji,
141 subtitle: emoji,
142 value: ':' + emoji + ':'
143 }
144 }))
145 }
146 }, {cls: 'SuggestBox'})
147
148 return composer
149
150 // scoped
151
152 function publish () {
153 publishing.set(true)
154
155 var content = extend(resolve(meta), {
156 text: textArea.value,
157 mentions: mentions(textArea.value).map(mention => {
158 // merge markdown-detected mention with file info
159 var file = filesById[mention.link]
160 if (file) {
161 if (file.type) mention.type = file.type
162 if (file.size) mention.size = file.size
163 }
164 return mention
165 })
166 })
167
168 try {
169 if (typeof prepublish === 'function') {
170 content = prepublish(content)
171 }
172 } catch (err) {
173 publishing.set(false)
174 if (cb) cb(err)
175 else throw err
176 }
177
178 return api.message.async.publish(content, done)
179
180 function done (err, msg) {
181 publishing.set(false)
182 if (err) throw err
183 else if (msg) textArea.value = ''
184 if (cb) cb(err, msg)
185 }
186 }
187 })
188}
189

Built with git-ssb-web