git ssb

0+

regular / ssb-cms



Tree: 43af34efb145e2705ff168ae3e6f3cc6832314ac

Files: 43af34efb145e2705ff168ae3e6f3cc6832314ac / revs-view.js

6338 bytesRaw
1require('setimmediate')
2const h = require('mutant/html-element')
3const Value = require('mutant/value')
4const MutantArray = require('mutant/array')
5const MutantMap = require('mutant/map')
6const computed = require('mutant/computed')
7const when = require('mutant/when')
8const send = require('mutant/send')
9
10const pull = require('pull-stream')
11const htime = require('human-time')
12const ssbAvatar = require('ssb-avatar')
13const memo = require('asyncmemo')
14const lru = require('hashlru')
15
16const DB = require('./db')
17const UpdateStream = require('./update-stream')
18const {isDraft, arr} = require('./util')
19
20function markHeads(entries) {
21 const o = {}
22 entries.forEach( e => {
23 e.isHead = true
24 o[e.key] = e
25 })
26 entries.forEach( e => {
27 let revBranch = e.value.content && e.value.content.revisionBranch
28 if (revBranch && o[revBranch]) o[revBranch].isHead = false
29 })
30}
31
32module.exports = function(ssb, drafts, me, blobsRoot, trusted_keys) {
33 const db = DB(ssb, drafts)
34 const updateStream = UpdateStream([])
35
36 let getAvatar = memo({cache: lru(50)}, function (id, cb) {
37 ssbAvatar(ssb, me, id, (err, about)=>{
38 if (err) return cb(err)
39 let name = about.name
40 if (!/^@/.test(name)) name = '@' + name
41 let imageUrl = about.image ? `${blobsRoot}/${about.image}` : null
42 cb(null, {name, imageUrl})
43 })
44 })
45
46 let selection = Value()
47 let root = Value()
48 let currRoot = null
49 let ready = Value(false)
50
51 function discardDraft(node) {
52 drafts.remove(node.key, err => {
53 if (err) return console.error('Unable to delete draft', node.key, err)
54 let hash = document.location.hash
55 if (!isDraft(hash.substr(1))) {
56 if (hash.indexOf(':') !== -1) {
57 document.location.hash = hash.split(':')[0]
58 }
59 selection.set('latest')
60 } else {
61 document.location.hash = ''
62 }
63 })
64 }
65
66 function html(entry) {
67
68 function _click(handler, args) {
69 return { 'ev-click': send( e => handler.apply(e, args) ) }
70 }
71
72 let feedId = isDraft(entry.key) ? me : entry.value.author
73 let authorAvatarUrl = Value(null, {defaultValue: ""})
74 let authorName = Value(null, {defaultValue: feedId.substr(0,6)})
75 getAvatar(feedId, (err, avatar) =>{
76 if (err) return console.error(err)
77 authorAvatarUrl.set(avatar.imageUrl || "")
78 authorName.set(avatar.name)
79 })
80
81 return h(`div.rev${isDraft(entry.key) ? '.draft' : ''}${entry.isHead ? '.head' : ''}`, {
82 classList: computed([selection], sel => sel === entry.key ? ['selected'] : []),
83 'ev-click': e => {
84 document.location.hash = `#${root()}:${entry.key}`
85 }
86 }, [
87 h('div.avatar', {
88 style: {
89 'background-image': computed([authorAvatarUrl], u => `url("${u}")`)
90 }
91 }, [
92 ...(trusted_keys.includes(feedId) ? [h('span.trusted')] : [])
93 ]),
94 h('span.author', authorName),
95 h('span.timestamp', htime(new Date(entry.value.timestamp))),
96 h('span.node',
97 ((entry.value.content && entry.value.content.revisionBranch) ?
98 entry.value.content.revisionBranch.substr(0,6) + ' → ' : '⤜') +
99 entry.key.substr(0,6)
100 ),
101 ...(isDraft(entry.key) ? [h('span', {title: 'draft'}, '✎')] : []),
102 h('span.buttons', [
103 ...(isDraft(entry.key) ? [h('button.discard', _click(discardDraft, [entry]), 'discard' )] : [])
104 ])
105 ])
106 }
107
108 let mutantArray = MutantArray()
109 let selectedLatest = false
110
111 function streamRevisions(id, syncCb) {
112 //console.log('streaming revisions of', id)
113 let drain
114 let entries
115 let synced = false
116 pull(
117 db.revisions(id, {
118 live: true,
119 sync: true
120 }),
121 updateStream({
122 live: true,
123 sync: true,
124 allowUntrusted: true,
125 allRevisions: true,
126 bufferUntilSync: true
127 }),
128 drain = pull.drain( kv =>{
129 if (kv.sync) {
130 synced = true
131 markHeads(entries)
132 mutantArray.set(entries)
133 return syncCb(null, entries)
134 }
135 entries = kv.revisions
136 if (synced) {
137 markHeads(entries)
138 mutantArray.set(entries)
139 if (selectedLatest) {
140 selection.set('latest')
141 }
142 }
143 }, err =>{
144 if (err) console.error('Revisions stream ends with error', err)
145 })
146 )
147 return drain.abort
148 }
149
150 let containerEl = h('revs', MutantMap(mutantArray, html))
151 let abort
152
153 selection( id => {
154 selectedLatest = false
155 if (id === 'latest') {
156 if (mutantArray.getLength() > 0) {
157 selection.set(mutantArray.get(0).key)
158 } else selection.set(null)
159 selectedLatest = true
160 }
161 console.log('rev selected', id)
162 })
163
164 root( id => {
165 if (currRoot === id) {
166 return ready.set(true)
167 }
168 //console.log('NEW rev root', id)
169 currRoot = id
170
171 if (abort) abort()
172 abort = null
173 selection.set(null)
174 ready.set(false)
175 entries = []
176 mutantArray.clear()
177 if (!id) return
178
179 abort = streamRevisions(id, err => {
180 if (err) console.error('streaming revisions failed with error', err)
181 else console.log('revisions synced')
182 ready.set(true)
183 })
184 })
185
186 containerEl.selection = selection
187 containerEl.root = root
188 containerEl.ready = ready
189
190 return containerEl
191}
192
193module.exports.css = ()=> `
194 .rev {
195 cursor: alias;
196 position: relative;
197 font-size: 11px;
198 color: #6b6969;
199 background-color: #eee;
200 margin: 1px 1px 0 1px;
201 display: flex;
202 flex-direction: column;
203 align-items: flex-start;
204 flex-wrap: wrap;
205 max-height: 32px;
206 align-content: flex-start;
207 }
208 .rev.head {
209 border-width: 2px;
210 border-style: dotted;
211 border-color: rgba(0,0,0,0.1);
212 background: #e0e0ff;
213 margin-right: -1.2em;
214 }
215 .rev.head .node::after {
216 content: "⚈";
217 margin-left: .5em;
218 }
219 .rev.selected {
220 color: #111110;
221 background: #b39254;
222 }
223 .rev .node {
224 position: absolute;
225 right: 1em;
226 top: .5em;
227 order: 3;
228 font-family: monospace;
229 font-size: 12px;
230 }
231 .rev .avatar {
232 margin: 0 8px;
233 height: 32px;
234 width: 32px;
235 border-radius: 3px;
236 background-size: cover;
237 }
238 .rev .author, .rev .timestamp {
239 width: 80px;
240 white-space: nowrap;
241 }
242 .rev .author {
243 padding-top: 3px;
244 }
245`
246

Built with git-ssb-web