git ssb

2+

mixmix / ticktack



Tree: 26ec4bc385c250f6b9b1d0eb534ceaff0f876083

Files: 26ec4bc385c250f6b9b1d0eb534ceaff0f876083 / app / page / blogNew.js

6698 bytesRaw
1const nest = require('depnest')
2const { h, Struct, Value } = 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 -channel', [
59 h('div.label', strings.channel),
60 channelInput
61 ]),
62 h('div.field -title', [
63 h('div.label', strings.blogNew.field.title),
64 h('input', {
65 'ev-input': e => meta.title.set(e.target.value),
66 placeholder: strings.blogNew.field.title
67 })
68 ]),
69 h('div.field -summary', [
70 h('div.label', strings.blogNew.field.summary),
71 h('input', {
72 'ev-input': e => meta.summary.set(e.target.value),
73 placeholder: strings.blogNew.field.summary
74 })
75 ]),
76 mediumComposer,
77 AddFileButton({ api, filesById, meta, textArea: mediumComposer }),
78 composer
79 ])
80 ])
81 ])
82
83 initialiseMedium({ page, el: mediumComposer, meta })
84 initialiseChannelSuggests({ input: channelInput, suggester: getChannelSuggestions, meta })
85
86 return page
87 }
88}
89
90function 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
111 })
112 ])
113 // TODO - insert where the mouse is yo
114 textArea.appendChild(img)
115
116 console.log('added:', file)
117 })
118
119 return fileInput
120}
121
122function 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)
141 })
142 }
143 },
144 (err, msg) => api.history.sync.push(err || { page: 'blogIndex' })
145 )
146}
147
148function 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 })
160
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 }
169
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 })
176}
177
178function 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}
217

Built with git-ssb-web