git ssb

2+

mixmix / ticktack



Tree: 35b74ae2d81cf0619ba5864d5875785f83255ef3

Files: 35b74ae2d81cf0619ba5864d5875785f83255ef3 / app / page / blogNew.js

6874 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('div.editor.Markdown', {
38 'ev-input': e => {
39 }
40 })
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
47
48 const channelInput = h('input', {
49 'ev-input': e => meta.channel.set(e.target.value),
50 value: meta.channel,
51 placeholder: strings.channel
52 })
53
54 var page = h('Page -blogNew', [
55 api.app.html.sideNav(location),
56 h('div.content', [
57 h('div.container', [
58 h('div.field -title', [
59 h('input', {
60 'ev-input': e => meta.title.set(e.target.value),
61 className: when(meta.title, '', '-empty'),
62 placeholder: strings.blogNew.field.title
63 })
64 ]),
65 mediumComposer,
66 h('div.field -attachment',
67 AddFileButton({ api, filesById, meta, textArea: mediumComposer })
68 ),
69 h('div.field -channel', [
70 h('div.label', strings.channel),
71 channelInput
72 ]),
73 h('div.field -summary', [
74 h('div.label', strings.blogNew.field.summary),
75 h('input', {
76 'ev-input': e => meta.summary.set(e.target.value),
77 placeholder: strings.blogNew.field.summary
78 })
79 ]),
80 composer
81 ])
82 ])
83 ])
84
85 initialiseMedium({ page, el: mediumComposer, meta })
86 initialiseChannelSuggests({ input: channelInput, suggester: getChannelSuggestions, meta })
87
88 return page
89 }
90}
91
92function AddFileButton ({ api, filesById, meta, textArea }) {
93 // var textRaw = meta.text
94
95 const fileInput = api.blob.html.input(file => {
96 filesById[file.link] = file
97
98 const isImage = file.type.match(/^image/)
99
100 var content
101
102 if (isImage) {
103 content = h('img', {
104 src: `http://localhost:8989/blobs/get/${encodeURIComponent(file.link)}`,
105 alt: file.name
106 })
107 } else {
108 content = h('a', { href: file.link }, file.name)
109 }
110 // TODO - insert where the mouse is yo
111 textArea.appendChild(h('p', content))
112
113 console.log('added:', file)
114 })
115
116 return fileInput
117}
118
119function initialiseDummyComposer ({ meta, api, filesById }) {
120 return api.message.html.compose(
121 {
122 meta,
123 // placeholder: strings.blogNew.actions.writeBlog,
124 shrink: false,
125 filesById,
126 prepublish: function (content, cb) {
127 var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(marksum.image(content.text))
128 content.thumbnail = m && m[1]
129 // content.summary = marksum.summary(content.text) // TODO Need a summary which doesn't trim the start
130
131 var stream = pull.values([content.text])
132 api.sbot.async.addBlob(stream, function (err, hash) {
133 if (err) return cb(err)
134 if (!hash) throw new Error('missing hash')
135 content.blog = hash
136 delete content.text
137 cb(null, content)
138 })
139 }
140 },
141 (err, msg) => api.history.sync.push(err || { page: 'blogIndex' })
142 )
143}
144
145function initialiseChannelSuggests ({ input, suggester, meta }) {
146 addSuggest(input, (inputText, cb) => {
147 inputText = inputText.replace(/^#/, '')
148 var suggestions = suggester(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 })
157
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
164 })
165 }
166
167 cb(null, suggestions)
168 }, {cls: 'PatchSuggest.-channelHorizontal'}) // WARNING hacking suggest-box cls
169
170 input.addEventListener('suggestselect', (e) => {
171 meta.channel.set(e.detail.value)
172 })
173}
174
175function initialiseMedium ({ page, el, meta }) {
176 // el.addEventListener('keyup', ev => {
177 // debugger
178 // })
179 var editor = new MediumEditor(el, {
180 elementsContainer: page,
181 // autoLink: true,
182 buttonLabels: 'fontawesome',
183 imageDragging: true,
184 toolbar: {
185 allowMultiParagraphSelection: true,
186 buttons: [
187 'bold',
188 'italic',
189 'anchor',
190 {
191 name: 'h2',
192 contentFA: '<i class="fa fa-header" />',
193 classList: ['custom-button-h2']
194 },
195 {
196 name: 'h3',
197 contentFA: '<i class="fa fa-header" />',
198 classList: ['custom-button-h3']
199 },
200 'quote'
201 ],
202 diffLeft: 0,
203 diffTop: 10,
204 firstButtonClass: 'medium-editor-button-first',
205 lastButtonClass: 'medium-editor-button-last',
206 relativeContainer: null,
207 standardizeSelectionStart: false,
208 static: false,
209 /* options which only apply when static is true */
210 align: 'center',
211 sticky: false,
212 updateOnEmptySelection: false
213 },
214 extensions: {
215 markdown: new MediumToMD(
216 {
217 toMarkdownOptions: {
218 converters: [{
219 filter: 'img',
220 replacement: (content, node) => {
221 var blob = decodeURIComponent(node.src.replace('http://localhost:8989/blobs/get/', ''))
222 return `![${node.alt}](${blob})`
223 }
224 }]
225 },
226 events: ['input', 'change', 'DOMNodeInserted']
227 },
228 md => meta.text.set(md)
229 )
230 }
231 })
232
233 return editor
234}
235

Built with git-ssb-web