git ssb

2+

mixmix / ticktack



Tree: d1c4f8d0850bbfbb59186b4b79726d7de9011998

Files: d1c4f8d0850bbfbb59186b4b79726d7de9011998 / app / page / home.js

6112 bytesRaw
1const nest = require('depnest')
2const { h, computed } = require('mutant')
3const {threadReduce} = require('ssb-reduce-stream')
4const pull = require('pull-stream')
5const isObject = require('lodash/isObject')
6const isString = require('lodash/isString')
7const last = require('lodash/last')
8const get = require('lodash/get')
9const More = require('hypermore')
10exports.gives = nest('app.page.home')
11const morphdom = require('morphdom')
12const Next = require('pull-next')
13
14exports.needs = nest({
15 'about.html.image': 'first',
16 'about.obs.name': 'first',
17 'app.html.nav': 'first',
18 'sbot.pull.log': 'first',
19 'history.sync.push': 'first',
20 'keys.sync.id': 'first',
21 'message.sync.unbox': 'first',
22 'message.html.markdown': 'first'
23})
24
25var strings = {
26 showMore: "Show More",
27 channels: "Channels",
28 directMessages: "Direct Messages",
29 replySymbol: "> "
30}
31
32function firstLine (text) {
33 if(text.length < 80 && !~text.indexOf('\n')) return text
34
35 var line = ''
36 var lineNumber = 0
37 while (line.length === 0) {
38 const rawLine = text.split('\n')[lineNumber]
39 line = trimLeadingMentions(rawLine)
40
41 lineNumber++
42 }
43
44 var sample = line.substring(0, 80)
45 if (hasBrokenLink(sample))
46 sample = sample + line.substring(81).match(/[^\)]*\)/)[0]
47
48 return sample
49
50 function trimLeadingMentions (str) {
51 return str.replace(/^(\s*\[@[^\)]+\)\s*)*/, '')
52 // deletes any number of pattern " [@...) " from start of line
53 }
54
55 function hasBrokenLink (str) {
56 return /\[[^\]]*\]\([^\)]*$/.test(str)
57 // matches "[name](start_of_link"
58 }
59}
60
61exports.create = (api) => {
62 return nest('app.page.home', function (location) {
63 // location here can expected to be: { page: 'home' }
64
65 var container = h('div.container', [])
66
67 function subject (msg) {
68 const { subject, text } = msg.value.content
69 return api.message.html.markdown(firstLine(subject|| text))
70 }
71
72 function link(location) {
73 return {'ev-click': () => api.history.sync.push(location)}
74 }
75
76 function item (context, thread, opts = {}) {
77 if(!thread.value) return
78
79 const subjectEl = h('div.subject', [
80 opts.nameRecipients
81 ? h('div.recps', buildRecipientNames(thread).map(recp => h('div.recp', recp)))
82 : null,
83 subject(thread)
84 ])
85
86 const lastReply = thread.replies && last(thread.replies)
87 const replyEl = lastReply
88 ? h('div.reply', [
89 h('div.replySymbol', strings.replySymbol),
90 subject(lastReply)
91 ])
92 : null
93
94
95 // REFACTOR: move this to a template?
96 function buildRecipientNames (thread) {
97 const myId = api.keys.sync.id()
98
99 return thread.value.content.recps
100 .map(link => isString(link) ? link : link.link)
101 .filter(link => link !== myId)
102 .map(api.about.obs.name)
103 }
104
105 return h('div.thread', link(thread), [
106 h('div.context', context),
107 h('div.content', [
108 subjectEl,
109 replyEl
110 ])
111 ])
112 }
113
114 function threadGroup (threads, obj, toContext, opts) {
115 // threads = a state object for all the types of threads
116 // obj = a map of keys to root ids, where key (channel | group | concatenated list of pubkeys)
117 // toContext = fn that derives the context of the group
118 // opts = { nameRecipients }
119
120 var groupEl = h('div.threads')
121 for(var k in obj) {
122 var id = obj[k]
123 var thread = get(threads, ['roots', id])
124 if(thread && thread.value) {
125 var el = item(toContext(k, thread), thread, opts)
126 if(el) groupEl.appendChild(el)
127 }
128 }
129 return groupEl
130 }
131
132 var initial
133 try { initial = JSON.parse(localStorage.threadsState) }
134 catch (_) { }
135 var lastTimestamp = initial ? initial.last : Date.now()
136
137 var timer
138 function update (threadsState) {
139 clearTimeout(timer)
140 setTimeout(function () {
141 threadsState.last = lastTimestamp
142 localStorage.threadsState = JSON.stringify(threadsState)
143 }, 1000)
144 }
145
146 var threadsObs = More(
147 threadReduce,
148 pull(
149 Next(function () {
150 return api.sbot.pull.log({reverse: true, limit: 500, lte: lastTimestamp})
151 }),
152 pull.map(function (data) {
153 lastTimestamp = data.timestamp
154 if(isObject(data.value.content)) return data
155 return api.message.sync.unbox(data)
156 }),
157 pull.filter(Boolean)
158 ),
159 function render (threads) {
160 update(threads)
161 morphdom(container,
162 h('div.container', [
163 //private section
164 h('section.updates -directMessage', [
165 h('h2', strings.directMessages),
166 threadGroup(
167 threads,
168 threads.private,
169 function (_, msg) {
170 // NB: msg passed in is actually a 'thread', but only care about root msg
171 const myId = api.keys.sync.id()
172
173 return msg.value.content.recps
174 .map(link => isString(link) ? link : link.link)
175 .filter(link => link !== myId)
176 .map(api.about.html.image)
177 },
178 { nameRecipients: true }
179 )
180 ]),
181 //channels section
182 h('section.updates -channel', [
183 h('h2', strings.channels),
184 threadGroup(
185 threads,
186 threads.channels,
187 ch => '#'+ch
188 )
189 ]),
190 //group section
191 h('section.updates -group', [
192 h('h2', 'Groups'),
193 'TODO: complete + enable when groups are live'
194 // threadGroup(
195 // threads,
196 // threads.groups,
197 // toName ...
198 // )
199 ])
200 ])
201 )
202 return container
203 },
204 initial
205 )
206
207 return h('Page -home', [
208 h('h1', 'Home'),
209 api.app.html.nav(),
210 threadsObs,
211 h('button', {'ev-click': threadsObs.more}, [strings.showMore])
212 ])
213 })
214}
215
216

Built with git-ssb-web