Commit 49bee78f3c1fec7174a11ee2c7277f5c9a7f47fc
Merge pull request #6 from ticktackim/homescreen_styling
homescreen - box model + mcss workmix irving authored on 8/11/2017, 9:15:31 AM
GitHub committed on 8/11/2017, 9:15:31 AM
Parent: f7dff5dea73ce76928756982a627d93d49cb1968
Parent: ad32665d634a45be6ccf3a7db98533610b061176
Files changed
app/html/thread.mcss | changed |
app/page/home.js | changed |
app/page/home.mcss | changed |
app/page/page.mcss | changed |
app/page/private.js | changed |
styles/mixins.js | changed |
app/html/thread.mcss | ||
---|---|---|
@@ -1,19 +1,13 @@ | ||
1 | 1 | Thread { |
2 | - background-color: #f7f7f7 | |
3 | - font-family: 'arial' | |
4 | - padding: 1rem | |
5 | - | |
6 | - max-width: 1400px | |
7 | - | |
8 | 2 | display: flex |
9 | 3 | flex-direction: column |
10 | 4 | |
11 | 5 | div.my-chunk { |
12 | 6 | $chunk |
13 | 7 | |
14 | 8 | justify-content: space-between |
15 | - | |
9 | + | |
16 | 10 | div.avatar { |
17 | 11 | visibility: hidden |
18 | 12 | } |
19 | 13 | |
@@ -21,46 +15,24 @@ | ||
21 | 15 | div.msg-row { |
22 | 16 | div.msg { |
23 | 17 | $primaryColor |
24 | 18 | |
25 | - border-bottom-left-radius: .9rem | |
26 | - border-top-left-radius: .9rem | |
19 | + (a) { color: #4d3568 } | |
20 | + $roundLeft | |
27 | 21 | } |
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 | - } | |
38 | 22 | } |
39 | 23 | } |
40 | 24 | } |
41 | 25 | |
42 | 26 | div.other-chunk { |
43 | 27 | $chunk |
44 | 28 | |
45 | 29 | div.msgs { |
46 | - | |
47 | 30 | div.msg-row { |
48 | 31 | div.msg { |
49 | 32 | border: 1.5px #ddd solid |
50 | - border-bottom-right-radius: .9rem | |
51 | - border-top-right-radius: .9rem | |
33 | + $roundRight | |
52 | 34 | } |
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 | - } | |
63 | 35 | } |
64 | 36 | } |
65 | 37 | } |
66 | 38 | } |
@@ -84,13 +56,20 @@ | ||
84 | 56 | max-width: 80% |
85 | 57 | div.msg-row { |
86 | 58 | display: flex |
87 | 59 | |
60 | + :first-child { | |
61 | + div.msg { $roundTop } | |
62 | + } | |
63 | + :last-child { | |
64 | + div.msg { $roundBottom } | |
65 | + } | |
66 | + | |
88 | 67 | div.msg { |
89 | 68 | line-height: 1.2rem |
90 | 69 | background-color: #fff |
91 | 70 | padding: 0 .7rem |
92 | - margin-bottom: .1rem | |
71 | + margin-bottom: .1rem | |
93 | 72 | border-radius: .3rem |
94 | 73 | } |
95 | 74 | div.msg-spacer { |
96 | 75 | flex-grow: grow |
app/page/home.js | ||
---|---|---|
@@ -1,6 +1,6 @@ | ||
1 | 1 | const nest = require('depnest') |
2 | -const { h } = require('mutant') | |
2 | +const { h, computed } = require('mutant') | |
3 | 3 | const {threadReduce} = require('ssb-reduce-stream') |
4 | 4 | const pull = require('pull-stream') |
5 | 5 | const isObject = require('lodash/isObject') |
6 | 6 | const isString = require('lodash/isString') |
@@ -10,20 +10,47 @@ | ||
10 | 10 | exports.gives = nest('app.page.home') |
11 | 11 | |
12 | 12 | exports.needs = nest({ |
13 | 13 | 'about.html.image': 'first', |
14 | + 'about.obs.name': 'first', | |
14 | 15 | 'app.html.nav': 'first', |
15 | 16 | 'feed.pull.public': 'first', |
16 | 17 | 'history.sync.push': 'first', |
18 | + 'keys.sync.id': 'first', | |
17 | 19 | 'message.sync.unbox': 'first', |
20 | + 'message.html.markdown': 'first' | |
18 | 21 | }) |
19 | 22 | |
20 | 23 | function firstLine (text) { |
21 | 24 | if(text.length < 80 && !~text.indexOf('\n')) return text |
22 | 25 | |
23 | - return text.split('\n')[0].substring(0, 80) | |
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 | |
24 | 41 | } |
25 | 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 | + | |
26 | 53 | exports.create = (api) => { |
27 | 54 | return nest('app.page.home', home) |
28 | 55 | |
29 | 56 | function home (location) { |
@@ -32,37 +59,65 @@ | ||
32 | 59 | var container = h('div.container', []) |
33 | 60 | |
34 | 61 | function subject (msg) { |
35 | 62 | const { subject, text } = msg.value.content |
36 | - return firstLine(subject|| text) | |
63 | + return api.message.html.markdown(firstLine(subject|| text)) | |
37 | 64 | } |
38 | 65 | |
39 | 66 | function link(location) { |
40 | 67 | return {'ev-click': () => api.history.sync.push(location)} |
41 | 68 | } |
42 | 69 | |
43 | - function item (name, thread) { | |
70 | + function item (context, thread, opts = {}) { | |
44 | 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 | + | |
45 | 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 | |
46 | 87 | |
47 | - return h('div.threadLink', link(thread), [ | |
48 | - name, | |
49 | - h('div.subject', [subject(thread)]), | |
50 | - lastReply ? h('div.reply', [subject(lastReply)]) : null | |
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 | + ]) | |
51 | 105 | ]) |
52 | 106 | } |
53 | 107 | |
54 | - function threadGroup (threads, obj, toName) { | |
108 | + function threadGroup (threads, obj, toContext, opts) { | |
55 | 109 | // threads = a state object for all the types of threads |
56 | 110 | // 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 | |
58 | - | |
59 | - var groupEl = h('div.group') | |
111 | + // toContext = fn that derives the context of the group | |
112 | + // opts = { nameRecipients } | |
113 | + | |
114 | + var groupEl = h('div.threads') | |
60 | 115 | for(var k in obj) { |
61 | 116 | var id = obj[k] |
62 | 117 | var thread = get(threads, ['roots', id]) |
63 | 118 | if(thread && thread.value) { |
64 | - var el = item(toName(k, thread), thread) | |
119 | + var el = item(toContext(k, thread), thread, opts) | |
65 | 120 | if(el) groupEl.appendChild(el) |
66 | 121 | } |
67 | 122 | } |
68 | 123 | return groupEl |
@@ -79,27 +134,48 @@ | ||
79 | 134 | }) |
80 | 135 | .filter(Boolean) |
81 | 136 | .reduce(threadReduce, null) |
82 | 137 | |
83 | - container.appendChild(threadGroup( | |
84 | - threads, | |
85 | - threads.private, | |
86 | - function (_, msg) { | |
87 | - // NB: msg passed in is actually a 'thread', but only care about root msg | |
88 | - | |
89 | - return h('div.recps', [ | |
90 | - msg.value.content.recps.map(function (link) { | |
91 | - return api.about.html.image(isString(link) ? link : link.link) | |
92 | - }) | |
93 | - ]) | |
94 | - } | |
95 | - )) | |
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() | |
96 | 146 | |
97 | - container.appendChild(threadGroup( | |
98 | - threads, | |
99 | - threads.channels, | |
100 | - ch => h('h2.title', '#'+ch) | |
101 | - )) | |
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) | |
102 | 178 | }) |
103 | 179 | ) |
104 | 180 | |
105 | 181 | return h('Page -home', [ |
app/page/home.mcss | ||
---|---|---|
@@ -1,17 +1,115 @@ | ||
1 | 1 | Page -home { |
2 | - h1 {} | |
3 | 2 | |
4 | 3 | 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 | + } | |
10 | 28 | } |
11 | 29 | } |
12 | 30 | } |
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 | + } | |
13 | 51 | } |
14 | 52 | } |
15 | 53 | } |
16 | 54 | |
55 | +$homePageSection { | |
56 | + display: flex | |
57 | + flex-direction: column | |
58 | + align-items: center | |
59 | + margin-bottom: 1.5rem | |
17 | 60 | |
61 | + h2 { | |
62 | + color: #888 | |
63 | + font-size: 1rem | |
64 | + text-align: center | |
65 | + padding-bottom: .3rem | |
66 | + width: 420px | |
67 | + margin-top: 0 | |
68 | + margin-bottom: .4rem | |
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: 1 | |
90 | + background: #fff | |
91 | + padding: 1rem | |
92 | + border: 1px solid #ddd | |
93 | + border-radius: 2px | |
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.mcss | ||
---|---|---|
@@ -2,6 +2,15 @@ | ||
2 | 2 | h1 { |
3 | 3 | margin-left: 1rem |
4 | 4 | } |
5 | 5 | |
6 | + div.Nav {} | |
7 | + | |
8 | + div.container { | |
9 | + $primaryBackground | |
10 | + padding: 1rem | |
11 | + | |
12 | + max-width: 1400px | |
13 | + | |
14 | + } | |
6 | 15 | } |
7 | 16 |
app/page/private.js | ||
---|---|---|
@@ -18,8 +18,8 @@ | ||
18 | 18 | |
19 | 19 | return h('Page -private', [ |
20 | 20 | h('h1', 'Private message'), |
21 | 21 | api.app.html.nav(), |
22 | - thread | |
22 | + h('div.container', thread) | |
23 | 23 | ]) |
24 | 24 | } |
25 | 25 | } |
styles/mixins.js | ||
---|---|---|
@@ -18,8 +18,12 @@ | ||
18 | 18 | $colorSubtle { |
19 | 19 | color: #222 |
20 | 20 | } |
21 | 21 | |
22 | +$primaryBackground { | |
23 | + background-color: #f7f7f7 | |
24 | +} | |
25 | + | |
22 | 26 | $smallAvatar { |
23 | 27 | width: 3rem |
24 | 28 | height: 3rem |
25 | 29 | border-radius: 1.5rem |
@@ -29,5 +33,45 @@ | ||
29 | 33 | width: 6rem |
30 | 34 | height: 6rem |
31 | 35 | border-radius: 3rem |
32 | 36 | } |
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 | +} | |
33 | 77 | ` |
Built with git-ssb-web