git ssb

2+

mixmix / ticktack



Commit 26ec4bc385c250f6b9b1d0eb534ceaff0f876083

add images as attachments

mix irving committed on 4/10/2018, 10:06:11 PM
Parent: 8bc84ea53b6b557f2173afd3670d0154c1ca27aa

Files changed

app/page/blogNew.jschanged
message/html/compose.jschanged
package-lock.jsonchanged
package.jsonchanged
app/page/blogNew.jsView
@@ -10,18 +10,17 @@
1010 exports.gives = nest('app.page.blogNew')
1111
1212 exports.needs = nest({
1313 'app.html.sideNav': 'first',
14 + 'blob.html.input': 'first',
1415 'channel.async.suggest': 'first',
1516 'history.sync.push': 'first',
1617 'message.html.compose': 'first',
1718 'translations.sync.strings': 'first',
1819 'sbot.async.addBlob': 'first'
1920 })
2021
2122 exports.create = (api) => {
22- var contentHtmlObs
23-
2423 return nest('app.page.blogNew', blogNew)
2524
2625 function blogNew (location) {
2726 const strings = api.translations.sync.strings()
@@ -34,35 +33,19 @@
3433 summary: Value(),
3534 text: Value('')
3635 })
3736
38- const mediumComposer = h('div.editor', {
37 + const mediumComposer = h('div.editor.Markdown', {
3938 'ev-input': e => {
4039 }
4140 })
42- const composer = api.message.html.compose(
43- {
44- meta,
45- placeholder: strings.blogNew.actions.writeBlog,
46- shrink: false,
47- prepublish: function (content, cb) {
48- var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(marksum.image(content.text))
49- content.thumbnail = m && m[1]
50- // content.summary = marksum.summary(content.text) // Need a summary whihch doesn't trim the start
41 + var filesById = {}
42 + const composer = initialiseDummyComposer({ filesById, meta, api })
43 + // NOTE we are bootstrapping off the message.html.compose logic
44 + // - the mediumComposer feeds content into the composer, which the provides publishing
45 + // - this works, but should be refactorer after more thought about generalised composer
46 + // componentes have been done
5147
52- var stream = pull.values([content.text])
53- delete content.text
54- api.sbot.async.addBlob(stream, function (err, hash) {
55- if (err) return cb(err)
56- if (!hash) throw new Error('missing hash')
57- content.blog = hash
58- cb(null, content)
59- })
60- }
61- },
62- (err, msg) => api.history.sync.push(err || { page: 'blogIndex' })
63- )
64-
6548 const channelInput = h('input', {
6649 'ev-input': e => meta.channel.set(e.target.value),
6750 value: meta.channel,
6851 placeholder: strings.channel
@@ -90,87 +73,144 @@
9073 placeholder: strings.blogNew.field.summary
9174 })
9275 ]),
9376 mediumComposer,
94- h('Button', {
95- 'ev-click': () => {
96- var img = h('img', { src: 'http://localhost:8989/blobs/get/%264TKyoyZmjjtpPvwiSR%2BGQIgrJs8o6XmzfUDZ5p1PP30%3D.sha256' })
97- // var e = MediumEditor.getEditorFromElement(mediumComposer)
98- // e.serialize()['element-0'].value
99- mediumComposer.appendChild(img)
100- }
101- }, 'Add Image'),
77 + AddFileButton({ api, filesById, meta, textArea: mediumComposer }),
10278 composer
10379 ])
10480 ])
10581 ])
10682
107- function initialiseMedium () {
108- new MediumEditor(mediumComposer, {
109- elementsContainer: page,
110- toolbar: {
111- allowMultiParagraphSelection: true,
112- buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'],
113- diffLeft: 0,
114- diffTop: -10,
115- firstButtonClass: 'medium-editor-button-first',
116- lastButtonClass: 'medium-editor-button-last',
117- relativeContainer: null,
118- standardizeSelectionStart: false,
119- static: false,
120- /* options which only apply when static is true */
121- align: 'center',
122- sticky: false,
123- updateOnEmptySelection: false
124- },
125- extensions: {
126- markdown: new MediumToMD (
127- {
128- toMarkdownOptions: {
129- converters: [{
130- filter: 'img',
131- replacement: (content, node) => {
132- var blob = decodeURIComponent(node.src.replace('http://localhost:8989/blobs/get/', ''))
133- return `![](${blob})`
134- }
135- }]
136- },
137- events: ['input', 'change', 'DOMNodeInserted']
138- },
139- md => meta.text.set(md)
140- )
141- }
83 + initialiseMedium({ page, el: mediumComposer, meta })
84 + initialiseChannelSuggests({ input: channelInput, suggester: getChannelSuggestions, meta })
85 +
86 + return page
87 + }
88 +}
89 +
90 +function AddFileButton ({ api, filesById, meta, textArea }) {
91 + // var textRaw = meta.text
92 +
93 + const fileInput = api.blob.html.input(file => {
94 + filesById[file.link] = file
95 +
96 + const isImage = file.type.match(/^image/)
97 + const imgPrefix = isImage ? '!' : ''
98 + const spacer = isImage ? '\n' : ' '
99 + const insertLink = spacer + imgPrefix + '[' + file.name + ']' + '(' + file.link + ')' + spacer
100 +
101 + const pos = textArea.selectionStart
102 + // var newText = textRaw().slice(0, pos) + insertLink + textRaw().slice(pos)
103 + // textArea.value = newText
104 + // textRaw.set(newText)
105 +
106 + // TODO pivot on image to insert link, or image
107 + const img = h('p', [
108 + h('img', {
109 + src: `http://localhost:8989/blobs/get/${encodeURIComponent(file.link)}`,
110 + alt: file.name
142111 })
143- }
144- initialiseMedium()
112 + ])
113 + // TODO - insert where the mouse is yo
114 + textArea.appendChild(img)
145115
146- addSuggest(channelInput, (inputText, cb) => {
147- inputText = inputText.replace(/^#/, '')
148- var suggestions = getChannelSuggestions(inputText)
149- .map(s => {
150- s.value = s.value.replace(/^#/, '') // strip the defualt # prefix here
151- return s
152- })
153- .map(s => {
154- if (s.subtitle === 'subscribed') { s.subtitle = h('i.fa.fa-heart') } // TODO - translation-friendly subscribed
155- return s
156- })
116 + console.log('added:', file)
117 + })
157118
158- // HACK add the input text if it's not an option already
159- if (!suggestions.some(s => s.title === inputText)) {
160- suggestions.push({
161- title: inputText,
162- subtitle: h('i.fa.fa-plus-circle'),
163- value: inputText
119 + return fileInput
120 +}
121 +
122 +function initialiseDummyComposer ({ meta, api, filesById }) {
123 + return api.message.html.compose(
124 + {
125 + meta,
126 + // placeholder: strings.blogNew.actions.writeBlog,
127 + shrink: false,
128 + filesById,
129 + prepublish: function (content, cb) {
130 + var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(marksum.image(content.text))
131 + content.thumbnail = m && m[1]
132 + // content.summary = marksum.summary(content.text) // TODO Need a summary which doesn't trim the start
133 +
134 + var stream = pull.values([content.text])
135 + api.sbot.async.addBlob(stream, function (err, hash) {
136 + if (err) return cb(err)
137 + if (!hash) throw new Error('missing hash')
138 + content.blog = hash
139 + delete content.text
140 + cb(null, content)
164141 })
165142 }
143 + },
144 + (err, msg) => api.history.sync.push(err || { page: 'blogIndex' })
145 + )
146 +}
166147
167- cb(null, suggestions)
168- }, {cls: 'PatchSuggest.-channelHorizontal'}) // WARNING hacking suggest-box cls
148 +function initialiseChannelSuggests ({ input, suggester, meta }) {
149 + addSuggest(input, (inputText, cb) => {
150 + inputText = inputText.replace(/^#/, '')
151 + var suggestions = suggester(inputText)
152 + .map(s => {
153 + s.value = s.value.replace(/^#/, '') // strip the defualt # prefix here
154 + return s
155 + })
156 + .map(s => {
157 + if (s.subtitle === 'subscribed') { s.subtitle = h('i.fa.fa-heart') } // TODO - translation-friendly subscribed
158 + return s
159 + })
169160
170- channelInput.addEventListener('suggestselect', (e) => {
171- meta.channel.set(e.detail.value)
172- })
161 + // HACK add the input text if it's not an option already
162 + if (!suggestions.some(s => s.title === inputText)) {
163 + suggestions.push({
164 + title: inputText,
165 + subtitle: h('i.fa.fa-plus-circle'),
166 + value: inputText
167 + })
168 + }
173169
174- return page
175- }
170 + cb(null, suggestions)
171 + }, {cls: 'PatchSuggest.-channelHorizontal'}) // WARNING hacking suggest-box cls
172 +
173 + input.addEventListener('suggestselect', (e) => {
174 + meta.channel.set(e.detail.value)
175 + })
176176 }
177 +
178 +function initialiseMedium ({ page, el, meta }) {
179 + var editor = new MediumEditor(el, {
180 + elementsContainer: page,
181 + toolbar: {
182 + allowMultiParagraphSelection: true,
183 + buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'],
184 + diffLeft: 0,
185 + diffTop: -10,
186 + firstButtonClass: 'medium-editor-button-first',
187 + lastButtonClass: 'medium-editor-button-last',
188 + relativeContainer: null,
189 + standardizeSelectionStart: false,
190 + static: false,
191 + /* options which only apply when static is true */
192 + align: 'center',
193 + sticky: false,
194 + updateOnEmptySelection: false
195 + },
196 + extensions: {
197 + markdown: new MediumToMD(
198 + {
199 + toMarkdownOptions: {
200 + converters: [{
201 + filter: 'img',
202 + replacement: (content, node) => {
203 + var blob = decodeURIComponent(node.src.replace('http://localhost:8989/blobs/get/', ''))
204 + return `![${node.alt}](${blob})`
205 + }
206 + }]
207 + },
208 + events: ['input', 'change', 'DOMNodeInserted']
209 + },
210 + md => meta.text.set(md)
211 + )
212 + }
213 + })
214 +
215 + return editor
216 +}
message/html/compose.jsView
@@ -1,6 +1,6 @@
11 const nest = require('depnest')
2-const { h, when, send, resolve, Value, computed, map } = require('mutant')
2 +const { h, when, send, resolve, Value, computed } = require('mutant')
33 const assign = require('lodash/assign')
44 const isEmpty = require('lodash/isEmpty')
55 const ssbMentions = require('ssb-mentions')
66 const addSuggest = require('suggest-box')
@@ -29,8 +29,9 @@
2929 feedIdsInThread = [],
3030 placeholder,
3131 shrink = true,
3232 canAttach = true, canPreview = true,
33 + filesById = {},
3334 prepublish
3435 } = options
3536
3637 const strings = api.translations.sync.strings()
@@ -39,10 +40,8 @@
3940 const getEmojiSuggestions = api.emoji.async.suggest()
4041
4142 placeholder = placeholder || strings.writeMessage
4243
43- var files = []
44- var filesById = {}
4544 var textAreaFocused = Value(false)
4645 var focused = textAreaFocused
4746 var hasContent = Value(false)
4847
@@ -66,14 +65,13 @@
6665 placeholder
6766 })
6867 textRaw(text => hasContent.set(!!text))
6968
70- textArea.publish = publish // TODO: fix - clunky api for the keyboard shortcut to target
69 + textArea.publish = () => publish({ filesById }) // TODO: fix - clunky api for the keyboard shortcut to target
7170
7271 var fileInput
7372 if (!meta.recps) {
7473 fileInput = api.blob.html.input(file => {
75- files.push(file)
7674 filesById[file.link] = file
7775
7876 var imgPrefix = file.type.match(/^image/) ? '!' : ''
7977 var spacer = imgPrefix ? '\n' : ' '
@@ -106,9 +104,9 @@
106104 }
107105 var { previewBtn, showPreview } = PreviewSetup(strings)
108106 var preview = computed(textRaw, text => api.message.html.markdown(text))
109107
110- var publishBtn = h('Button -primary', { 'ev-click': publish }, strings.sendMessage)
108 + var publishBtn = h('Button -primary', { 'ev-click': () => publish({ filesById }) }, strings.sendMessage)
111109
112110 var actions = h('section.actions', [
113111 canAttach ? fileInput : '',
114112 canPreview ? previewBtn : '',
@@ -132,9 +130,9 @@
132130 return composer
133131
134132 // scoped
135133
136- function publish () {
134 + function publish ({ filesById }) {
137135 if (publishBtn.disabled) return
138136
139137 const text = resolve(textRaw)
140138 if (isEmpty(text)) return
package-lock.jsonView
The diff is too large to show. Use a local git client to view these changes.
Old file size: 229633 bytes
New file size: 238763 bytes
package.jsonView
@@ -34,9 +34,9 @@
3434 "insert-css": "^2.0.0",
3535 "libnested": "^1.2.1",
3636 "lodash": "^4.17.4",
3737 "markdown-summary": "^1.0.3",
38- "medium-editor": "^5.23.2",
38 + "medium-editor": "^5.23.3",
3939 "medium-editor-markdown": "^2.6.0",
4040 "micro-css": "^2.0.1",
4141 "morphdom": "^2.3.3",
4242 "mutant": "^3.21.2",

Built with git-ssb-web