Commit 0a4073c00a2da5c212e6f7ecf45ad96cc8818d14
Merge pull request #129 from ticktackim/draft
drafts for blogNewAndre Alves Garzia authored on 4/16/2018, 3:21:47 AM
GitHub committed on 4/16/2018, 3:21:47 AM
Parent: 856796486d48c7b494769019a74b9b843471ee41
Parent: c595622493e1e6fe53de788e32b14f98db8475cc
Files changed
app/page/blogNew.js | changed |
main.js | changed |
package-lock.json | changed |
package.json | changed |
translations/en.js | changed |
app/page/blogNew.js | ||
---|---|---|
@@ -1,19 +1,25 @@ | ||
1 | 1 | const nest = require('depnest') |
2 | -const { h, Struct, Value, when } = require('mutant') | |
2 | +const { h, Struct, Value, when, resolve } = require('mutant') | |
3 | 3 | const addSuggest = require('suggest-box') |
4 | 4 | const pull = require('pull-stream') |
5 | 5 | const marksum = require('markdown-summary') |
6 | 6 | const MediumEditor = require('medium-editor').MediumEditor |
7 | 7 | const MediumToMD = require('medium-editor-markdown') |
8 | +const throttle = require('lodash/throttle') | |
8 | 9 | // const CustomHtml = require('medium-editor-custom-async') |
9 | 10 | |
11 | +const DRAFT_LOCATION = 'TicktackBlogNew' | |
12 | +// NOTE - may want to have multiple drafts in future, this location would then become variable | |
13 | + | |
10 | 14 | exports.gives = nest('app.page.blogNew') |
11 | 15 | |
12 | 16 | exports.needs = nest({ |
13 | 17 | 'app.html.sideNav': 'first', |
14 | 18 | 'blob.html.input': 'first', |
15 | 19 | 'channel.async.suggest': 'first', |
20 | + 'drafts.sync.get': 'first', | |
21 | + 'drafts.sync.set': 'first', | |
16 | 22 | 'history.sync.push': 'first', |
17 | 23 | 'message.html.compose': 'first', |
18 | 24 | 'translations.sync.strings': 'first', |
19 | 25 | 'sbot.async.addBlob': 'first' |
@@ -33,9 +39,30 @@ | ||
33 | 39 | summary: Value(), |
34 | 40 | text: Value('') |
35 | 41 | }) |
36 | 42 | |
37 | - const mediumComposer = h('Markdown.editor') | |
43 | + const title = h('h1.input', { | |
44 | + attributes: { | |
45 | + contenteditable: true, | |
46 | + 'data-placeholder': strings.blogNew.field.title | |
47 | + }, | |
48 | + 'ev-input': updateTitle, | |
49 | + className: when(meta.title, '', '-empty') | |
50 | + }) | |
51 | + function updateTitle (e) { | |
52 | + if (e.target.childElementCount) { | |
53 | + // the title h1 is contenteditable, meaning people can paste html elements in here! | |
54 | + // this is designed to strip down to heading content text | |
55 | + // - I went with contenteditable because it handles wrapping of long titles, | |
56 | + // whereas a styled input field just pushes content off the page! | |
57 | + e.target.innerHTML = e.target.innerText | |
58 | + } | |
59 | + meta.title.set(e.target.innerText) | |
60 | + } | |
61 | + | |
62 | + var mediumComposer | |
63 | + mediumComposer = h('Markdown.editor', { | |
64 | + }) | |
38 | 65 | var filesById = {} |
39 | 66 | const composer = initialiseDummyComposer({ filesById, meta, api }) |
40 | 67 | // NOTE we are bootstrapping off the message.html.compose logic |
41 | 68 | // - the mediumComposer feeds content into the composer, which the provides publishing |
@@ -46,36 +73,17 @@ | ||
46 | 73 | 'ev-input': e => meta.channel.set(e.target.value), |
47 | 74 | value: meta.channel, |
48 | 75 | placeholder: strings.channel |
49 | 76 | }) |
50 | - const updateTitle = e => { | |
51 | - if (e.target.childElementCount) { | |
52 | - // the title h1 is contenteditable, meaning people can paste html elements in here! | |
53 | - // this is designed to strip down to heading content text | |
54 | - // - I went with contenteditable because it handles wrapping of long titles, | |
55 | - // whereas a styled input field just pushes content off the page! | |
56 | - e.target.innerHTML = e.target.innerText | |
57 | - } | |
58 | - meta.title.set(e.target.innerText) | |
59 | - } | |
60 | 77 | |
61 | 78 | var page = h('Page -blogNew', [ |
62 | 79 | api.app.html.sideNav(location), |
63 | 80 | h('div.content', [ |
64 | - h('div.container', [ | |
65 | - h('div.field -title', [ | |
66 | - h('h1.input', { | |
67 | - attributes: { | |
68 | - contenteditable: true, | |
69 | - 'data-placeholder': strings.blogNew.field.title | |
70 | - }, | |
71 | - 'ev-input': updateTitle, | |
72 | - className: when(meta.title, '', '-empty') | |
73 | - }) | |
74 | - ]), | |
81 | + h('div.container', { 'ev-input': throttledSaveDraft({ composer: mediumComposer, meta, api }) }, [ | |
82 | + h('div.field -title', title), | |
75 | 83 | mediumComposer, |
76 | 84 | h('div.field -attachment', |
77 | - AddFileButton({ api, filesById, meta, textArea: mediumComposer }) | |
85 | + AddFileButton({ api, filesById, meta, composer: mediumComposer }) | |
78 | 86 | ), |
79 | 87 | h('div.field -channel', [ |
80 | 88 | h('div.label', strings.channel), |
81 | 89 | channelInput |
@@ -91,19 +99,43 @@ | ||
91 | 99 | ]) |
92 | 100 | ]) |
93 | 101 | ]) |
94 | 102 | |
95 | - initialiseMedium({ page, el: mediumComposer, meta }) | |
103 | + initialiseMedium({ api, page, el: mediumComposer, meta }) | |
96 | 104 | initialiseChannelSuggests({ input: channelInput, suggester: getChannelSuggestions, meta }) |
97 | 105 | |
106 | + loadDrafts({ composer: mediumComposer, title, meta, api }) | |
107 | + | |
98 | 108 | return page |
99 | 109 | } |
100 | 110 | } |
101 | 111 | |
102 | -function AddFileButton ({ api, filesById, meta, textArea }) { | |
103 | - // var textRaw = meta.text | |
104 | - // | |
112 | +function loadDrafts ({ composer, title, meta, api }) { | |
113 | + var draft = api.drafts.sync.get(DRAFT_LOCATION) | |
114 | + if (!draft) return | |
105 | 115 | |
116 | + if (draft.title) { | |
117 | + meta.title.set(draft.title) | |
118 | + title.innerText = draft.title | |
119 | + } | |
120 | + | |
121 | + if (draft.body) composer.innerHTML = draft.body | |
122 | +} | |
123 | + | |
124 | +function saveDraft ({ composer, meta, api }) { | |
125 | + const hasBody = composer.innerText.split('\n').join('').replace(/\s/g, '').length > 0 | |
126 | + | |
127 | + const draft = { | |
128 | + title: resolve(meta.title), | |
129 | + body: hasBody ? composer.innerHTML : null | |
130 | + } | |
131 | + api.drafts.sync.set(DRAFT_LOCATION, draft) | |
132 | +} | |
133 | +function throttledSaveDraft ({ composer, meta, api }) { | |
134 | + return throttle(() => saveDraft({ composer, meta, api }), 2000) | |
135 | +} | |
136 | + | |
137 | +function AddFileButton ({ api, filesById, meta, composer }) { | |
106 | 138 | const fileInput = api.blob.html.input(file => { |
107 | 139 | filesById[file.link] = file |
108 | 140 | |
109 | 141 | const isImage = file.type.match(/^image/) |
@@ -118,29 +150,32 @@ | ||
118 | 150 | } else { |
119 | 151 | content = h('a', { href: file.link }, file.name) |
120 | 152 | } |
121 | 153 | // TODO - insert where the mouse is yo |
122 | - var editor = MediumEditor.getEditorFromElement(textArea) | |
123 | - textArea.insertBefore( | |
154 | + var editor = MediumEditor.getEditorFromElement(composer) | |
155 | + composer.insertBefore( | |
124 | 156 | h('p', content), |
125 | 157 | editor.currentEl || null |
126 | 158 | ) |
127 | 159 | |
160 | + saveDraft({ composer, meta, api }) | |
161 | + | |
128 | 162 | console.log('added:', file) |
129 | 163 | }) |
130 | 164 | |
131 | 165 | return fileInput |
132 | 166 | } |
133 | 167 | |
134 | 168 | function initialiseDummyComposer ({ meta, api, filesById }) { |
169 | + const strings = api.translations.sync.strings() | |
170 | + | |
135 | 171 | return api.message.html.compose( |
136 | 172 | { |
137 | 173 | meta, |
138 | - // placeholder: strings.blogNew.actions.writeBlog, | |
139 | 174 | shrink: false, |
140 | 175 | canAttach: false, |
141 | 176 | canPreview: false, |
142 | - publishString: api.translations.sync.strings().publishBlog, | |
177 | + publishString: strings.publishBlog, | |
143 | 178 | filesById, |
144 | 179 | prepublish: function (content, cb) { |
145 | 180 | var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(marksum.image(content.text)) |
146 | 181 | content.thumbnail = m && m[1] |
@@ -155,9 +190,12 @@ | ||
155 | 190 | cb(null, content) |
156 | 191 | }) |
157 | 192 | } |
158 | 193 | }, |
159 | - (err, msg) => api.history.sync.push(err || { page: 'blogIndex' }) | |
194 | + (err, msg) => { | |
195 | + api.drafts.sync.remove(DRAFT_LOCATION) | |
196 | + api.history.sync.push(err || { page: 'blogIndex' }) | |
197 | + } | |
160 | 198 | ) |
161 | 199 | } |
162 | 200 | |
163 | 201 | function initialiseChannelSuggests ({ input, suggester, meta }) { |
@@ -189,10 +227,16 @@ | ||
189 | 227 | meta.channel.set(e.detail.value) |
190 | 228 | }) |
191 | 229 | } |
192 | 230 | |
193 | -function initialiseMedium ({ page, el, meta }) { | |
231 | +function initialiseMedium ({ api, page, el, meta }) { | |
232 | + const strings = api.translations.sync.strings() | |
233 | + const draft = api.drafts.sync.get(DRAFT_LOCATION) || {} | |
234 | + | |
194 | 235 | var editor = new MediumEditor(el, { |
236 | + placeholder: { | |
237 | + text: draft.body ? '' : strings.blogNew.actions.writeBlog | |
238 | + }, | |
195 | 239 | elementsContainer: page, |
196 | 240 | // autoLink: true, |
197 | 241 | buttonLabels: 'fontawesome', |
198 | 242 | imageDragging: true, |
main.js | ||
---|---|---|
@@ -33,8 +33,9 @@ | ||
33 | 33 | channel: require('./channel') |
34 | 34 | }, |
35 | 35 | { |
36 | 36 | profile: require('patch-profile'), |
37 | + drafts: require('patch-drafts'), | |
37 | 38 | history: require('patch-history'), |
38 | 39 | core: require('patchcore') |
39 | 40 | } |
40 | 41 | ) |
package-lock.json | ||
---|---|---|
The diff is too large to show. Use a local git client to view these changes. Old file size: 238763 bytes New file size: 239110 bytes |
Built with git-ssb-web