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