git ssb

2+

mixmix / ticktack



Commit d1c4f8d0850bbfbb59186b4b79726d7de9011998

merge

Dominic Tarr committed on 8/11/2017, 8:29:50 AM
Parent: 5eb78cc1badb9c34b623487a4ff5fe39b1d0ca87
Parent: db1b91bf9f68e6db25b325fd506058608428302e

Files changed

app/html/thread.mcsschanged
app/page/home.jschanged
app/page/home.mcsschanged
app/page/page.mcsschanged
app/page/private.jschanged
styles/mixins.jschanged
app/html/thread.mcssView
@@ -1,19 +1,13 @@
11 Thread {
2- background-color: #f7f7f7
3- font-family: 'arial'
4- padding: 1rem
5-
6- max-width: 1400px
7-
82 display: flex
93 flex-direction: column
104
115 div.my-chunk {
126 $chunk
137
148 justify-content: space-between
15-
9+
1610 div.avatar {
1711 visibility: hidden
1812 }
1913
@@ -21,46 +15,24 @@
2115 div.msg-row {
2216 div.msg {
2317 $primaryColor
2418
25- border-bottom-left-radius: .9rem
26- border-top-left-radius: .9rem
19+ (a) { color: #4d3568 }
20+ $roundLeft
2721 }
28- :first-child {
29- div.msg {
30- border-top-right-radius: .9rem
31- }
32- }
33- :last-child {
34- div.msg {
35- border-bottom-right-radius: .9rem
36- }
37- }
3822 }
3923 }
4024 }
4125
4226 div.other-chunk {
4327 $chunk
4428
4529 div.msgs {
46-
4730 div.msg-row {
4831 div.msg {
4932 border: 1.5px #ddd solid
50- border-bottom-right-radius: .9rem
51- border-top-right-radius: .9rem
33+ $roundRight
5234 }
53- :first-child {
54- div.msg {
55- border-top-left-radius: .9rem
56- }
57- }
58- :last-child {
59- div.msg {
60- border-bottom-left-radius: .9rem
61- }
62- }
6335 }
6436 }
6537 }
6638 }
@@ -84,13 +56,20 @@
8456 max-width: 80%
8557 div.msg-row {
8658 display: flex
8759
60+ :first-child {
61+ div.msg { $roundTop }
62+ }
63+ :last-child {
64+ div.msg { $roundBottom }
65+ }
66+
8867 div.msg {
8968 line-height: 1.2rem
9069 background-color: #fff
9170 padding: 0 .7rem
92- margin-bottom: .1rem
71+ margin-bottom: .1rem
9372 border-radius: .3rem
9473 }
9574 div.msg-spacer {
9675 flex-grow: grow
app/page/home.jsView
@@ -1,6 +1,6 @@
11 const nest = require('depnest')
2-const { h } = require('mutant')
2+const { h, computed } = require('mutant')
33 const {threadReduce} = require('ssb-reduce-stream')
44 const pull = require('pull-stream')
55 const isObject = require('lodash/isObject')
66 const isString = require('lodash/isString')
@@ -12,18 +12,51 @@
1212 const Next = require('pull-next')
1313
1414 exports.needs = nest({
1515 'about.html.image': 'first',
16+ 'about.obs.name': 'first',
1617 'app.html.nav': 'first',
1718 'sbot.pull.log': 'first',
1819 'history.sync.push': 'first',
20+ 'keys.sync.id': 'first',
1921 'message.sync.unbox': 'first',
22+ 'message.html.markdown': 'first'
2023 })
2124
25+var strings = {
26+ showMore: "Show More",
27+ channels: "Channels",
28+ directMessages: "Direct Messages",
29+ replySymbol: "> "
30+}
31+
2232 function firstLine (text) {
2333 if(text.length < 80 && !~text.indexOf('\n')) return text
2434
25- return text.split('\n')[0].substring(0, 80)
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+ }
2659 }
2760
2861 exports.create = (api) => {
2962 return nest('app.page.home', function (location) {
@@ -32,37 +65,65 @@
3265 var container = h('div.container', [])
3366
3467 function subject (msg) {
3568 const { subject, text } = msg.value.content
36- return firstLine(subject|| text)
69+ return api.message.html.markdown(firstLine(subject|| text))
3770 }
3871
3972 function link(location) {
4073 return {'ev-click': () => api.history.sync.push(location)}
4174 }
4275
43- function item (name, thread) {
76+ function item (context, thread, opts = {}) {
4477 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+
4586 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
4693
47- return h('div.threadLink', link(thread), [
48- name,
49- h('div.subject', [subject(thread)]),
50- lastReply ? h('div.reply', [subject(lastReply)]) : null
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+ ])
51111 ])
52112 }
53113
54- function threadGroup (threads, obj, toName) {
114+ function threadGroup (threads, obj, toContext, opts) {
55115 // threads = a state object for all the types of threads
56- // obj = a map of keys to root ids, where key ∈ (channel | group | concatenated list of pubkeys)
57- // toName = fn that derives a name from a particular thread
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 }
58119
59- var groupEl = h('div.group')
120+ var groupEl = h('div.threads')
60121 for(var k in obj) {
61122 var id = obj[k]
62123 var thread = get(threads, ['roots', id])
63124 if(thread && thread.value) {
64- var el = item(toName(k, thread), thread)
125+ var el = item(toContext(k, thread), thread, opts)
65126 if(el) groupEl.appendChild(el)
66127 }
67128 }
68129 return groupEl
@@ -92,43 +153,51 @@
92153 lastTimestamp = data.timestamp
93154 if(isObject(data.value.content)) return data
94155 return api.message.sync.unbox(data)
95156 }),
96- pull.filter(Boolean),
97- function (read) {
98- return function (abort, cb) {
99- read(abort, function (err, data) {
100- try {
101- cb(err, data)
102- } catch (err) {
103- console.error(err)
104- read(err, function () {})
105- }
106- })
107- }
108- }
157+ pull.filter(Boolean)
109158 ),
110- function render (threadsState) {
111- update(threadsState)
159+ function render (threads) {
160+ update(threads)
112161 morphdom(container,
113162 h('div.container', [
114- threadGroup(
115- threadsState,
116- threadsState.private,
117- function (_, msg) {
118- // NB: msg passed in is actually a 'thread', but only care about root msg
119- return h('div.recps', [
120- msg.value.content.recps.map(function (link) {
121- return api.about.html.image(isString(link) ? link : link.link)
122- })
123- ])
124- }
125- ),
126- threadGroup(
127- threadsState,
128- threadsState.channels,
129- ch => h('h2.title', '#'+ch)
130- )
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+ ])
131200 ])
132201 )
133202 return container
134203 },
@@ -138,9 +207,9 @@
138207 return h('Page -home', [
139208 h('h1', 'Home'),
140209 api.app.html.nav(),
141210 threadsObs,
142- h('button', {'ev-click': threadsObs.more}, ['Show More'])
211+ h('button', {'ev-click': threadsObs.more}, [strings.showMore])
143212 ])
144213 })
145214 }
146215
app/page/home.mcssView
@@ -1,17 +1,115 @@
11 Page -home {
2- h1 {}
32
43 div.container {
5- div.group {
6- div.threadLink {
7- div.recps {
8- img {
9- $smallAvatar
4+ $primaryBackground
5+
6+ section.updates {
7+
8+ -directMessage {
9+ $homePageSection
10+
11+ div.threads {
12+ div.thread {
13+ div.content {
14+ div.subject {
15+ display: flex
16+
17+ div.recps {
18+ display: flex
19+ font-weight: 600
20+ margin-right: .4rem
21+
22+ div.recp {
23+ margin-right: .4rem
24+ }
25+ }
26+ }
27+ }
1028 }
1129 }
1230 }
31+
32+ -channel {
33+ $homePageSection
34+
35+ div.threads {
36+ div.thread {
37+ div.context {
38+ background: #fff
39+ min-width: 8rem
40+ padding: .1rem .3rem
41+ border: 1px solid #ddd
42+ border-radius: 2px
43+ }
44+ }
45+ }
46+ }
47+
48+ -group {
49+ $homePageSection
50+ }
1351 }
1452 }
1553 }
1654
55+$homePageSection {
56+ display: flex
57+ flex-direction: column
58+ align-items: center
59+ margin-bottom: 1.5rem
1760
61+ h2 {
62+ color: #888
63+ font-size: 1rem
64+ text-align: center
65+ padding-bottom: .3rem
66+ width: 420px
67+ border-bottom: 1px solid #aaa
68+ margin-top: 0
69+ }
70+
71+ div.threads {
72+ div.thread {
73+ display: flex
74+ align-items: center
75+
76+ margin-bottom: .5rem
77+
78+ div.context {
79+ display: flex
80+ margin-right: 1rem
81+
82+ img {
83+ $smallAvatar
84+ margin-right: .5rem
85+ }
86+ }
87+
88+ div.content {
89+ flex-grow: .8
90+ background: #fff
91+ padding: 1rem
92+ border: 1px solid #ddd
93+ border-radius: 1rem
94+
95+ div.subject {
96+ font-size: 1.2rem
97+ margin-bottom: .3rem
98+
99+ $largeMarkdown
100+ }
101+ div.reply {
102+ display: flex
103+ color: #444
104+
105+ div.replySymbol {
106+ margin-left: .7rem
107+ margin-right: .3rem
108+ }
109+
110+ $smallMarkdown
111+ }
112+ }
113+ }
114+ }
115+}
app/page/page.mcssView
@@ -2,6 +2,15 @@
22 h1 {
33 margin-left: 1rem
44 }
55
6+ div.Nav {}
7+
8+ div.container {
9+ $primaryBackground
10+ padding: 1rem
11+
12+ max-width: 1400px
13+
14+ }
615 }
716
app/page/private.jsView
@@ -18,8 +18,8 @@
1818
1919 return h('Page -private', [
2020 h('h1', 'Private message'),
2121 api.app.html.nav(),
22- thread
22+ h('div.container', thread)
2323 ])
2424 }
2525 }
styles/mixins.jsView
@@ -18,8 +18,12 @@
1818 $colorSubtle {
1919 color: #222
2020 }
2121
22+$primaryBackground {
23+ background-color: #f7f7f7
24+}
25+
2226 $smallAvatar {
2327 width: 3rem
2428 height: 3rem
2529 border-radius: 1.5rem
@@ -29,5 +33,45 @@
2933 width: 6rem
3034 height: 6rem
3135 border-radius: 3rem
3236 }
37+
38+$smallMarkdown {
39+ div.Markdown {
40+ h1, h2, h3, h4, h5, h6, p {
41+ font-size: 1rem
42+ font-weight: 300
43+ margin: 0
44+ }
45+ }
46+}
47+
48+$largeMarkdown {
49+ div.Markdown {
50+ h1, h2, h3, h4, h5, h6, p {
51+ font-size: 1.2rem
52+ font-weight: 300
53+ margin: 0
54+ }
55+ }
56+}
57+
58+$roundLeft {
59+ border-top-left-radius: .9rem
60+ border-bottom-left-radius: .9rem
61+}
62+
63+$roundRight {
64+ border-top-right-radius: .9rem
65+ border-bottom-right-radius: .9rem
66+}
67+
68+$roundTop {
69+ border-top-left-radius: .9rem
70+ border-top-right-radius: .9rem
71+}
72+
73+$roundBottom {
74+ border-bottom-left-radius: .9rem
75+ border-bottom-right-radius: .9rem
76+}
3377 `

Built with git-ssb-web