git ssb

16+

Dominic / patchbay



Tree: c24aab4d406daa99a57712bf37fc9f5d7c2971ff

Files: c24aab4d406daa99a57712bf37fc9f5d7c2971ff / message / html / compose.js

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

Built with git-ssb-web