git ssb

16+

Dominic / patchbay



Tree: 47f9ac92e1cabfef73b71fea878e805a70994e87

Files: 47f9ac92e1cabfef73b71fea878e805a70994e87 / message / html / compose.js

5471 bytesRaw
1const { h, when, send, resolve, Value, computed } = require('mutant')
2const nest = require('depnest')
3const mentions = require('ssb-mentions')
4const extend = require('xtend')
5const addSuggest = require('suggest-box')
6
7exports.needs = nest({
8 'about.async.suggest': 'first',
9 'blob.html.input': 'first',
10 'channel.async.suggest': 'first',
11 'emoji.sync': {
12 names: 'first',
13 url: 'first'
14 },
15 'main.sync.catchKeyboardShortcut': 'first',
16 'message.html.confirm': 'first'
17})
18
19exports.gives = nest('message.html.compose')
20
21exports.create = function (api) {
22 return nest({ 'message.html.compose': compose })
23
24 function compose ({ shrink = true, meta, prepublish, placeholder = 'Write a message' }, cb) {
25 var files = []
26 var filesById = {}
27 var channelInputFocused = Value(false)
28 var textAreaFocused = Value(false)
29 var focused = computed([channelInputFocused, textAreaFocused], (a, b) => a || b)
30 var hasContent = Value(false)
31 var getProfileSuggestions = api.about.async.suggest()
32 var getChannelSuggestions = api.channel.async.suggest()
33
34 var blurTimeout = null
35
36 var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => {
37 if (!shrink || hasContent) return true
38
39 return focused
40 })
41
42 var channelInput = h('input.channel', {
43 'ev-input': () => hasContent.set(!!channelInput.value),
44 'ev-keyup': ev => {
45 ev.target.value = ev.target.value.replace(/^#*([\w@%&])/, '#$1')
46 },
47 'ev-blur': () => {
48 clearTimeout(blurTimeout)
49 blurTimeout = setTimeout(() => channelInputFocused.set(false), 200)
50 },
51 'ev-focus': send(channelInputFocused.set, true),
52 placeholder: '#channel (optional)',
53 value: meta.channel ? `#${meta.channel}` : '',
54 disabled: !!meta.channel,
55 title: meta.channel ? 'Reply is in same channel as original message' : ''
56 })
57
58 var textArea = h('textarea', {
59 'ev-input': () => hasContent.set(!!textArea.value),
60 'ev-blur': () => {
61 clearTimeout(blurTimeout)
62 blurTimeout = setTimeout(() => textAreaFocused.set(false), 200)
63 },
64 'ev-focus': send(textAreaFocused.set, true),
65 placeholder
66 })
67 textArea.publish = publish // clunky api for the keyboard shortcut to target
68
69 var fileInput = api.blob.html.input(file => {
70 files.push(file)
71 filesById[file.link] = file
72
73 var embed = file.type.match(/^image/) ? '!' : ''
74 var spacer = embed ? '\n' : ' '
75 var insertLink = spacer + embed + '[' + file.name + ']' + '(' + file.link + ')' + spacer
76
77 var pos = textArea.selectionStart
78 textArea.value = textArea.value.slice(0, pos) + insertLink + textArea.value.slice(pos)
79
80 console.log('added:', file)
81 })
82
83 fileInput.onclick = () => hasContent.set(true)
84
85 var publishBtn = h('button', { 'ev-click': publish }, 'Publish')
86
87 var actions = h('section.actions', [
88 fileInput,
89 publishBtn
90 ])
91
92 var composer = h('Compose', {
93 classList: when(expanded, '-expanded', '-contracted')
94 }, [
95 channelInput,
96 textArea,
97 actions
98 ])
99
100 addSuggest(channelInput, (inputText, cb) => {
101 if (inputText[0] === '#') {
102 cb(null, getChannelSuggestions(inputText.slice(1)))
103 }
104 }, {cls: 'SuggestBox'})
105 channelInput.addEventListener('suggestselect', ev => {
106 channelInput.value = ev.detail.id // HACK : this over-rides the markdown value
107 })
108
109 addSuggest(textArea, (inputText, cb) => {
110 if (inputText[0] === '@') {
111 cb(null, getProfileSuggestions(inputText.slice(1)))
112 } else if (inputText[0] === '#') {
113 cb(null, getChannelSuggestions(inputText.slice(1)))
114 } else if (inputText[0] === ':') {
115 // suggest emojis
116 var word = inputText.slice(1)
117 if (word[word.length - 1] === ':') {
118 word = word.slice(0, -1)
119 }
120 // TODO: when no emoji typed, list some default ones
121 cb(null, api.emoji.sync.names().filter(function (name) {
122 return name.slice(0, word.length) === word
123 }).slice(0, 100).map(function (emoji) {
124 return {
125 image: api.emoji.sync.url(emoji),
126 title: emoji,
127 subtitle: emoji,
128 value: ':' + emoji + ':'
129 }
130 }))
131 }
132 }, {cls: 'SuggestBox'})
133
134 return composer
135
136 // scoped
137
138 function publish () {
139 publishBtn.disabled = true
140
141 meta = extend(resolve(meta), {
142 text: textArea.value,
143 channel: (channelInput.value.startsWith('#')
144 ? channelInput.value.substr(1).trim()
145 : channelInput.value.trim()
146 ) || null,
147 mentions: mentions(textArea.value).map(mention => {
148 // merge markdown-detected mention with file info
149 var file = filesById[mention.link]
150 if (file) {
151 if (file.type) mention.type = file.type
152 if (file.size) mention.size = file.size
153 }
154 return mention
155 })
156 })
157
158 try {
159 if (typeof prepublish === 'function') {
160 meta = prepublish(meta)
161 }
162 } catch (err) {
163 publishBtn.disabled = false
164 if (cb) cb(err)
165 else throw err
166 }
167
168 return api.message.html.confirm(meta, done)
169
170 function done (err, msg) {
171 publishBtn.disabled = false
172 if (err) throw err
173 else if (msg) textArea.value = ''
174 if (cb) cb(err, msg)
175 }
176 }
177 }
178}
179
180

Built with git-ssb-web