git ssb

2+

mixmix / ticktack



Tree: 3ad00d6029fdc254261c83d438be1fe0dd4977bf

Files: 3ad00d6029fdc254261c83d438be1fe0dd4977bf / app / page / blogNew.js

7733 bytesRaw
1const nest = require('depnest')
2const { h, Struct, Value, when } = require('mutant')
3const addSuggest = require('suggest-box')
4const pull = require('pull-stream')
5const marksum = require('markdown-summary')
6const MediumEditor = require('medium-editor').MediumEditor
7const MediumToMD = require('medium-editor-markdown')
8// const CustomHtml = require('medium-editor-custom-async')
9
10exports.gives = nest('app.page.blogNew')
11
12exports.needs = nest({
13 'app.html.sideNav': 'first',
14 'blob.html.input': 'first',
15 'channel.async.suggest': 'first',
16 'history.sync.push': 'first',
17 'message.html.compose': 'first',
18 'translations.sync.strings': 'first',
19 'sbot.async.addBlob': 'first'
20})
21
22exports.create = (api) => {
23 return nest('app.page.blogNew', blogNew)
24
25 function blogNew (location) {
26 const strings = api.translations.sync.strings()
27 const getChannelSuggestions = api.channel.async.suggest()
28
29 const meta = Struct({
30 type: 'blog',
31 channel: Value(),
32 title: Value(),
33 summary: Value(),
34 text: Value('')
35 })
36
37 const mediumComposer = h('Markdown.editor')
38 var filesById = {}
39 const composer = initialiseDummyComposer({ filesById, meta, api })
40 // NOTE we are bootstrapping off the message.html.compose logic
41 // - the mediumComposer feeds content into the composer, which the provides publishing
42 // - this works, but should be refactorer after more thought about generalised composer
43 // componentes have been done
44
45 const channelInput = h('input', {
46 'ev-input': e => meta.channel.set(e.target.value),
47 value: meta.channel,
48 placeholder: strings.channel
49 })
50 const updateTitle = e => {
51 if (e.target.childElementCount) {
52 // catch people pasting html in here!
53 e.target.innerHTML = e.target.innerText
54 }
55 meta.title.set(e.target.innerText)
56 }
57
58 var page = h('Page -blogNew', [
59 api.app.html.sideNav(location),
60 h('div.content', [
61 h('div.container', [
62 h('div.field -title', [
63 h('h1.input', {
64 attributes: {
65 contenteditable: true,
66 'data-placeholder': strings.blogNew.field.title
67 },
68 'ev-input': updateTitle,
69 className: when(meta.title, '', '-empty')
70 })
71 ]),
72 mediumComposer,
73 h('div.field -attachment',
74 AddFileButton({ api, filesById, meta, textArea: mediumComposer })
75 ),
76 h('div.field -channel', [
77 h('div.label', strings.channel),
78 channelInput
79 ]),
80 h('div.field -summary', [
81 h('div.label', strings.blogNew.field.summary),
82 h('input', {
83 'ev-input': e => meta.summary.set(e.target.value),
84 placeholder: strings.blogNew.field.summary
85 })
86 ]),
87 composer
88 ])
89 ])
90 ])
91
92 initialiseMedium({ page, el: mediumComposer, meta })
93 initialiseChannelSuggests({ input: channelInput, suggester: getChannelSuggestions, meta })
94
95 return page
96 }
97}
98
99function AddFileButton ({ api, filesById, meta, textArea }) {
100 // var textRaw = meta.text
101 //
102
103 const fileInput = api.blob.html.input(file => {
104 filesById[file.link] = file
105
106 const isImage = file.type.match(/^image/)
107
108 var content
109
110 if (isImage) {
111 content = h('img', {
112 src: `http://localhost:8989/blobs/get/${encodeURIComponent(file.link)}`,
113 alt: file.name
114 })
115 } else {
116 content = h('a', { href: file.link }, file.name)
117 }
118 // TODO - insert where the mouse is yo
119 var editor = MediumEditor.getEditorFromElement(textArea)
120 textArea.insertBefore(
121 h('p', content),
122 editor.currentEl || null
123 )
124
125 console.log('added:', file)
126 })
127
128 return fileInput
129}
130
131function initialiseDummyComposer ({ meta, api, filesById }) {
132 return api.message.html.compose(
133 {
134 meta,
135 // placeholder: strings.blogNew.actions.writeBlog,
136 shrink: false,
137 canAttach: false,
138 canPreview: false,
139 publishString: api.translations.sync.strings().publishBlog,
140 filesById,
141 prepublish: function (content, cb) {
142 var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(marksum.image(content.text))
143 content.thumbnail = m && m[1]
144 // content.summary = marksum.summary(content.text) // TODO Need a summary which doesn't trim the start
145
146 var stream = pull.values([content.text])
147 api.sbot.async.addBlob(stream, function (err, hash) {
148 if (err) return cb(err)
149 if (!hash) throw new Error('missing hash')
150 content.blog = hash
151 delete content.text
152 cb(null, content)
153 })
154 }
155 },
156 (err, msg) => api.history.sync.push(err || { page: 'blogIndex' })
157 )
158}
159
160function initialiseChannelSuggests ({ input, suggester, meta }) {
161 addSuggest(input, (inputText, cb) => {
162 inputText = inputText.replace(/^#/, '')
163 var suggestions = suggester(inputText)
164 .map(s => {
165 s.value = s.value.replace(/^#/, '') // strip the defualt # prefix here
166 return s
167 })
168 .map(s => {
169 if (s.subtitle === 'subscribed') { s.subtitle = h('i.fa.fa-heart') } // TODO - translation-friendly subscribed
170 return s
171 })
172
173 // HACK add the input text if it's not an option already
174 if (!suggestions.some(s => s.title === inputText)) {
175 suggestions.push({
176 title: inputText,
177 subtitle: h('i.fa.fa-plus-circle'),
178 value: inputText
179 })
180 }
181
182 cb(null, suggestions)
183 }, {cls: 'PatchSuggest.-channelHorizontal'}) // WARNING hacking suggest-box cls
184
185 input.addEventListener('suggestselect', (e) => {
186 meta.channel.set(e.detail.value)
187 })
188}
189
190function initialiseMedium ({ page, el, meta }) {
191 var editor = new MediumEditor(el, {
192 elementsContainer: page,
193 // autoLink: true,
194 buttonLabels: 'fontawesome',
195 imageDragging: true,
196 toolbar: {
197 allowMultiParagraphSelection: true,
198 buttons: [
199 'bold',
200 'italic',
201 'anchor',
202 {
203 name: 'h2',
204 contentFA: '<i class="fa fa-header" />',
205 classList: ['custom-button-h2']
206 },
207 {
208 name: 'h3',
209 contentFA: '<i class="fa fa-header" />',
210 classList: ['custom-button-h3']
211 },
212 'quote'
213 ],
214 diffLeft: 0,
215 diffTop: 10,
216 firstButtonClass: 'medium-editor-button-first',
217 lastButtonClass: 'medium-editor-button-last',
218 relativeContainer: null,
219 standardizeSelectionStart: false,
220 static: false,
221 /* options which only apply when static is true */
222 align: 'center',
223 sticky: false,
224 updateOnEmptySelection: false
225 },
226 extensions: {
227 markdown: new MediumToMD(
228 {
229 toMarkdownOptions: {
230 converters: [{
231 filter: 'img',
232 replacement: (content, node) => {
233 var blob = decodeURIComponent(node.src.replace('http://localhost:8989/blobs/get/', ''))
234 return `![${node.alt}](${blob})`
235 }
236 }, {
237 filter: 'span',
238 replacement: (content, node) => content
239 }]
240 },
241 events: ['input', 'change', 'DOMNodeInserted']
242 },
243 md => meta.text.set(md)
244 )
245 }
246 })
247
248 editor.on(el, 'keyup', setCurrentEl)
249 editor.on(el, 'click', setCurrentEl)
250
251 function setCurrentEl (ev) {
252 var sel = window.getSelection()
253 var container = sel.getRangeAt(0).commonAncestorContainer
254 editor.currentEl = container.textContent === '' // NOTE this could be a brittle check
255 ? container
256 : container.parentElement
257 }
258
259 return editor
260}
261

Built with git-ssb-web