git ssb

16+

Dominic / patchbay



Commit c948308a09c8225c1c36b7910e2f7c3e8522127e

rework like / quote / reply + message layout, install scuttle-thread

mixmix committed on 11/23/2018, 11:15:33 AM
Parent: dd6a279d35c61c3c1a36d453b6e27af326a3fc67

Files changed

app/html/scroller.mcsschanged
app/page/notifications.jschanged
app/page/private.jschanged
app/page/public.jschanged
app/page/thread.jschanged
app/sync/goTo.jschanged
message/html/layout/default.jschanged
message/html/layout/default.mcsschanged
message/html/like.jschanged
message/html/meta/raw.jschanged
message/html/action/likes.jsdeleted
message/html/action/quote.jsdeleted
message/html/action/reply.jsdeleted
message/html/like.mcssadded
message/html/quote.jsadded
message/html/quote.mcssadded
message/html/reply.jsadded
message/html/reply.mcssadded
package-lock.jsonchanged
package.jsonchanged
app/html/scroller.mcssView
@@ -9,9 +9,9 @@
99
1010 display: grid
1111 justify-content: stretch
1212 align-content: start
13- grid-template-columns: auto minmax(740px, 900px) auto
13 + grid-template-columns: auto minmax(740px, 1100px) auto
1414
1515 section {
1616 grid-column: 2 / 3
1717
app/page/notifications.jsView
@@ -41,23 +41,27 @@
4141
4242 pull(
4343 pullMentions({ old: false, live: true }),
4444 filterDownThrough(),
45- Scroller(container, content, api.message.html.render, true, false)
45 + Scroller(container, content, render, true, false)
4646 )
4747
4848 pull(
4949 pullMentions({ reverse: true, live: false }),
5050 filterUpThrough(),
51- Scroller(container, content, api.message.html.render, false, false)
51 + Scroller(container, content, render, false, false)
5252 )
5353 }
5454 draw()
5555
5656 container.title = '/notifications'
5757 return container
5858 }
5959
60 + function render (msg) {
61 + return api.message.html.render(msg, { showTitle: true })
62 + }
63 +
6064 // NOTE - currently this stream is know to pick up:
6165 // - post mentions (public)
6266 // - patchwork replies (public)
6367 // - scry (public, private)
app/page/private.jsView
@@ -57,23 +57,27 @@
5757
5858 pull(
5959 pullPrivate({ old: false, live: true }),
6060 filterDownThrough(),
61- Scroller(container, content, api.message.html.render, true, false)
61 + Scroller(container, content, render, true, false)
6262 )
6363
6464 pull(
6565 pullPrivate({ reverse: true }),
6666 filterUpThrough(),
67- Scroller(container, content, api.message.html.render, false, false)
67 + Scroller(container, content, render, false, false)
6868 )
6969 }
7070 draw()
7171
7272 container.title = '/private'
7373 return container
7474 }
7575
76 + function render (msg) {
77 + return api.message.html.render(msg, { showTitle: true })
78 + }
79 +
7680 function pullPrivate (opts) {
7781 const query = [{
7882 $filter: {
7983 timestamp: { $gt: 0 },
app/page/public.jsView
@@ -44,11 +44,11 @@
4444 const createStream = (opts) => api.sbot.pull.stream(server => {
4545 const _opts = merge({}, opts, {
4646 query: [{
4747 $filter: {
48- timestamp: {$gt: 0, $lt: undefined},
48 + timestamp: { $gt: 0, $lt: undefined },
4949 value: {
50- content: { recps: {$not: true} }
50 + content: { recps: { $not: true } }
5151 }
5252 }
5353 }],
5454 limit: 100
@@ -62,20 +62,20 @@
6262 resetFeed({ container, content })
6363
6464 const render = (msg) => {
6565 // if (msg.value.content.type === 'about') debugger
66- return api.message.html.render(msg)
66 + return api.message.html.render(msg, { showTitle: true })
6767 }
6868
6969 // TODO - change to use ssb-query, streamed by publish time
7070 pull(
71- createStream({old: false, live: true}),
71 + createStream({ old: false, live: true }),
7272 filterUpThrough(),
7373 Scroller(container, content, render, true, false)
7474 )
7575
7676 pull(
77- createStream({reverse: true, live: false}),
77 + createStream({ reverse: true, live: false }),
7878 filterDownThrough(),
7979 Scroller(container, content, render, false, false)
8080 )
8181 }
app/page/thread.jsView
@@ -81,11 +81,11 @@
8181 function locateKey () {
8282 // wait till we're on the right page
8383 if (tabs.currentPage().id !== locationId) return setTimeout(locateKey, 200)
8484
85- if (!tabs.currentPage().scroll) return setTimeout(locateKey, 200)
85 + if (!tabs.currentPage().keyboardScroll) return setTimeout(locateKey, 200)
8686
87- tabs.currentPage().scroll('first')
87 + tabs.currentPage().keyboardScroll('first')
8888 const msg = tabs.currentPage().querySelector(`[data-id='${id}']`)
8989 if (!msg) return setTimeout(locateKey, 200)
9090
9191 ;(msg.scrollIntoViewIfNeeded || msg.scrollIntoView).call(msg)
app/sync/goTo.jsView
@@ -42,10 +42,10 @@
4242 api.history.sync.push(loc)
4343
4444 if (loc.action === 'quote' && page.firstChild && page.firstChild.addQuote) {
4545 page.firstChild.addQuote(loc.value)
46- tabs.currentPage().scroll('last')
47- } else if (loc.action === 'reply') { tabs.currentPage().scroll('last') }
46 + tabs.currentPage().keyboardScroll('last')
47 + } else if (loc.action === 'reply') { tabs.currentPage().keyboardScroll('last') }
4848
4949 return true
5050 }
5151
message/html/layout/default.jsView
@@ -1,15 +1,17 @@
11 const nest = require('depnest')
22 const { h, Value } = require('mutant')
3-const { isMsg } = require('ssb-ref')
3 +// const { isMsg } = require('ssb-ref')
44
55 exports.needs = nest({
66 'about.html.avatar': 'first',
77 'keys.sync.id': 'first',
8- 'message.html.action': 'map',
98 'message.html.author': 'first',
109 'message.html.backlinks': 'first',
10 + 'message.html.like': 'first',
1111 'message.html.meta': 'map',
12 + 'message.html.quote': 'first',
13 + 'message.html.reply': 'first',
1214 'message.html.timestamp': 'first',
1315 'sbot.async.run': 'first'
1416 })
1517
@@ -18,32 +20,40 @@
1820 exports.create = (api) => {
1921 return nest('message.html.layout', messageLayout)
2022
2123 function messageLayout (msg, opts = {}) {
22- const { layout, showUnread = true } = opts
23- if (!(layout === undefined || layout === 'default')) return
24 + if (!(opts.layout === undefined || opts.layout === 'default')) return
25 + const { showUnread = true, showTitle } = opts
2426
25- var { author, timestamp, meta, action, backlinks } = api.message.html
26- if (!isMsg(msg.key)) action = () => {}
27 + var { author, timestamp, like, meta, backlinks, quote, reply } = api.message.html
2728
2829 var rawMessage = Value(null)
2930
3031 var el = h('Message -default',
3132 { attributes: { tabindex: '0' } }, // needed to be able to navigate and show focus()
3233 [
33- h('section.avatar', {}, api.about.html.avatar(msg.value.author)),
34- h('section.top', [
34 + h('section.left', [
35 + h('div.avatar', {}, api.about.html.avatar(msg.value.author)),
3536 h('div.author', {}, author(msg)),
36- h('div.title', {}, opts.title),
37- h('div.meta', {}, meta(msg, { rawMessage }))
37 + h('div.timestamp', {}, timestamp(msg))
3838 ]),
39- h('section.content', {}, opts.content),
40- h('section.raw-content', rawMessage),
41- h('section.bottom', [
42- h('div.timestamp', {}, timestamp(msg)),
43- h('div.actions', {}, action(msg))
39 +
40 + h('section.body', [
41 + showTitle ? h('div.title', {}, opts.title) : null,
42 + h('div.content', {}, opts.content),
43 + h('footer.backlinks', {}, backlinks(msg)),
44 + h('div.raw-content', rawMessage)
4445 ]),
45- h('footer.backlinks', {}, backlinks(msg))
46 +
47 + h('section.right', [
48 + h('div.meta', {}, meta(msg, { rawMessage })),
49 + // isMsg(msg.key) ? // don't show actions if no msg.key
50 + h('div.actions', [
51 + like(msg),
52 + quote(msg),
53 + reply(msg)
54 + ])
55 + ])
4656 ]
4757 )
4858
4959 // UnreadFeature (search codebase for this if extracting)
message/html/layout/default.mcssView
@@ -1,132 +1,132 @@
11 Message -default {
22 padding: 1rem .5rem
33
44 display: grid
5- grid-template-columns: 5rem auto
6- grid-row-gap: .5rem
5 + grid-template-columns: 6rem 1fr auto
6 + grid-gap: 1.5rem
77
8- section.avatar {
9- grid-column: 1 / 2
10- grid-row: 1 / span 4
11- a img {
8 + section.left {
9 + grid-column: 1
10 +
11 + display: grid
12 + align-content: start
13 + justify-items: end
14 +
15 + div.avatar {
16 + grid-row: 1
17 + a img { }
1218 }
13- }
1419
15- section.top {
16- grid-column: 2 / 3
17- grid-row: 1 / span 1
18-
19- display: flex
20- align-items: baseline
21-
2220 div.author {
21 + grid-row: 2
2322 font-weight: 600
24- margin-right: .5rem
23 + text-align: right
2524 (a) { color: #222 }
2625 }
2726
27 + div.timestamp {
28 + grid-row: 3
29 + a {
30 + font-size: .9rem
31 + $textSubtle
32 + }
33 + }
34 + }
35 +
36 + section.body {
37 + grid-column: 2
38 +
39 + display: grid
40 + align-content: start
41 +
2842 div.title {
29- flex-grow: 1
43 + grid-row: 1
3044 font-size: .9rem
3145 $textSubtle
3246 (a) { $textSubtle }
3347 }
3448
35- div.meta {
36- display: flex
37- justify-content: flex-end
38- align-items: baseline
39-
40- i, div, a {
41- margin-left: .5rem
49 + div.content {
50 + grid-row: 2
51 + (img) {
52 + max-width: 100%
4253 }
54 + (video) {
55 + max-width: 100%
56 + }
4357 }
44- }
4558
46- section.content {
47- grid-column: 2 / 3
48- grid-row: 2 / span 1
59 + footer.backlinks {
60 + grid-row: 3
61 + }
4962
50- (img) {
51- max-width: 100%
63 + div.raw-content {
64 + grid-row: 4
65 + pre {
66 + border: 1px gainsboro solid
67 + padding: .8rem
68 + background-color: #f5f5f5
69 + color: #c121dc
70 + padding: .3rem
71 + white-space: pre-wrap
72 + word-wrap: break-word
73 +
74 + span {
75 + font-weight: 600
76 + }
77 + a {
78 + word-break: break-all
79 + }
80 + }
5281 }
53- (video) {
54- max-width: 100%
55- }
82 +
5683 }
5784
58- section.bottom {
59- grid-column: 2 / 3
60- grid-row: 4 / span 1
85 + section.right {
86 + grid-column: 3
6187
62- display: flex
63- align-items: center
88 + display: grid
89 + justify-items: end
90 + align-content: space-between
6491
65- div.timestamp {
66- flex-grow: 1
92 + div.meta {
93 + grid-row: 1
6794
68- a {
69- font-size: .9rem
70- $textSubtle
71- }
95 + display: grid
96 + justify-items: end
97 + grid-gap: .5rem
98 +
99 + i, div, a { }
100 +
101 + a.channel { grid-row: 1 }
72102 }
73103
74104 div.actions {
105 + grid-row: 2
106 + justify-self: end
107 +
75108 display: grid
76109 grid-auto-flow: column
110 + grid-gap: .8rem
77111
78112 font-size: .9rem
79- a {
80- margin-left: .5em
81- }
82113
83- a.likes { grid-column: 1 }
84- a.like, a.unlike { grid-column: 2 }
85- a.unlike {
86- $textSubtle
87- }
114 + div.MessageLike { }
115 + i.MessageQuote { margin-top: 3px }
116 + i.MessageReply { margin-top: 1px }
88117 }
89118 }
90119
91- section.raw-content {
92- grid-column: 2 / 3
93- grid-row: 3 / span 1
94-
95- pre {
96- border: 1px gainsboro solid
97- padding: .8rem
98- background-color: #f5f5f5
99- color: #c121dc
100- padding: .3rem
101- white-space: pre-wrap
102- word-wrap: break-word
103-
104- span {
105- font-weight: 600
106- }
107- a {
108- word-break: break-all
109- }
110- }
111- }
112-
113- footer.backlinks {
114- grid-row: 5 / span 1
115-
116- grid-column: 2 / 3
117- flex-basis: 100%
118- }
119-
120120 /* UnreadFeature (search codebase for this if extracting) */
121121
122122 /* initially read */
123123 -read {
124124 }
125125
126126 /* initially unread */
127127 -unread {
128- section.top {
128 + section.right {
129129 div.meta {
130130 i.unread {
131131 display: initial
132132 transition: color 1s ease-in
@@ -138,9 +138,9 @@
138138
139139 /* initially unread, but subsequently marked read */
140140 -unread {
141141 -read {
142- section.top {
142 + section.right {
143143 div.meta {
144144 i.unread {
145145 color: #bbb
146146 }
message/html/like.jsView
@@ -1,50 +1,44 @@
1-var { h, computed, when } = require('mutant')
1 +var { h, computed } = require('mutant')
22 var nest = require('depnest')
3 +const Scuttle = require('scuttle-thread')
34
45 exports.needs = nest({
56 'keys.sync.id': 'first',
67 'message.obs.likes': 'first',
7- 'sbot.async.publish': 'first'
8 + 'sbot.obs.connection': 'first'
89 })
910
1011 exports.gives = nest('message.html.like')
1112
1213 exports.create = (api) => {
13- return nest('message.html.like', function like (msg) {
14- var id = api.keys.sync.id()
15- var liked = computed([api.message.obs.likes(msg.key), id], doesLike)
16- return when(liked,
17- h('a.unlike', {
18- href: '#',
19- 'ev-click': () => publishLike(msg, false)
20- }, 'Unlike'),
21- h('a.like', {
22- href: '#',
23- 'ev-click': () => publishLike(msg, true)
24- }, 'Like')
25- )
26- })
14 + return nest('message.html.like', like)
2715
28- function publishLike (msg, status = true) {
29- var like = status ? {
30- type: 'vote',
31- channel: msg.value.content.channel,
32- vote: { link: msg.key, value: 1, expression: 'Like' }
33- } : {
34- type: 'vote',
35- channel: msg.value.content.channel,
36- vote: { link: msg.key, value: 0, expression: 'Unlike' }
37- }
38- if (msg.value.content.recps) {
39- like.recps = msg.value.content.recps.map(function (e) {
40- return e && typeof e !== 'string' ? e.link : e
41- })
42- like.private = true
43- }
44- api.sbot.async.publish(like)
16 + function like (msg) {
17 + const id = api.keys.sync.id()
18 +
19 + // TODO make this full-async :
20 + // - get whether i like this currently
21 + // - only update after I click like/ unlike
22 +
23 + return computed(api.message.obs.likes(msg.key), likes => {
24 + const iLike = likes.includes(id)
25 +
26 + return h('MessageLike',
27 + {
28 + className: iLike ? '-liked' : '',
29 + 'ev-click': () => publishLike(msg, !iLike)
30 + },
31 + [
32 + h('span.count', likes.length ? likes.length : ''),
33 + h('i.fa', { className: iLike ? 'fa-heart' : 'fa-heart-o' })
34 + ]
35 + )
36 + })
4537 }
46-}
4738
48-function doesLike (likes, userId) {
49- return likes.includes(userId)
39 + function publishLike (msg, value = true) {
40 + const scuttle = Scuttle(api.sbot.obs.connection)
41 +
42 + scuttle.like(msg, { value }, console.log)
43 + }
5044 }
message/html/meta/raw.jsView
@@ -54,8 +54,8 @@
5454 var refRegex = /((?:@|%|&)[A-Za-z0-9/+]{43}=\.[\w\d]+)/g
5555
5656 var arr = text.split(refRegex)
5757 for (var i = 1; i < arr.length; i += 2) {
58- arr[i] = h('a', {href: arr[i]}, arr[i])
58 + arr[i] = h('a', { href: arr[i] }, arr[i])
5959 }
6060 return arr
6161 }
message/html/action/likes.jsView
@@ -1,32 +1,0 @@
1-const nest = require('depnest')
2-const { h, computed, map } = require('mutant')
3-const ref = require('ssb-ref')
4-
5-exports.gives = nest('message.html.action')
6-
7-exports.needs = nest({
8- 'about.obs.name': 'first',
9- 'message.obs.likes': 'first'
10-})
11-
12-exports.create = (api) => {
13- return nest('message.html.action', likes)
14-
15- function likes (msg) {
16- if (!ref.isMsgId(msg.key)) return
17-
18- const symbol = '\u2713' // tick 🗸
19-
20- var likes = api.message.obs.likes(msg.key)
21-
22- var body = computed(likes, likes => likes.length > 4
23- ? likes.length + ' ' + symbol
24- : Array(likes.length).fill(symbol).join('')
25- )
26-
27- var names = map(likes, id => api.about.obs.name(id))
28- var title = computed(names, names => names.map(n => '@' + n).join('\n'))
29-
30- return h('a.likes', { title }, body)
31- }
32-}
message/html/action/quote.jsView
@@ -1,17 +1,0 @@
1-var h = require('mutant/h')
2-var nest = require('depnest')
3-
4-exports.needs = nest({
5- 'app.sync.goTo': 'first'
6-})
7-
8-exports.gives = nest('message.html.action')
9-
10-exports.create = (api) => {
11- return nest('message.html.action', function quote (msg) {
12- return h('a', {
13- href: '#',
14- 'ev-click': (ev) => { ev.preventDefault(); api.app.sync.goTo({ action: 'quote', key: msg.key, value: msg.value }) }
15- }, 'Quote')
16- })
17-}
message/html/action/reply.jsView
@@ -1,17 +1,0 @@
1-var h = require('mutant/h')
2-var nest = require('depnest')
3-
4-exports.needs = nest({
5- 'app.sync.goTo': 'first'
6-})
7-
8-exports.gives = nest('message.html.action')
9-
10-exports.create = (api) => {
11- return nest('message.html.action', function betterReply (msg) {
12- return h('a', {
13- href: '#',
14- 'ev-click': (ev) => { ev.preventDefault(); api.app.sync.goTo({ action: 'reply', key: msg.key, value: msg.value }) }
15- }, 'Reply')
16- })
17-}
message/html/like.mcssView
@@ -1,0 +1,22 @@
1 +MessageLike {
2 + color: gray
3 + cursor: pointer
4 +
5 + display: grid
6 + grid-template-columns: auto auto
7 + align-items: center
8 + grid-gap: .2rem
9 +
10 + span.count {}
11 + i.fa {}
12 +
13 + -liked {
14 + i.fa { color: red }
15 + }
16 +
17 + :hover {
18 + i.fa {
19 + color: red
20 + }
21 + }
22 +}
message/html/quote.jsView
@@ -1,0 +1,22 @@
1 +var h = require('mutant/h')
2 +var nest = require('depnest')
3 +
4 +exports.gives = nest('message.html.quote')
5 +
6 +exports.needs = nest({
7 + 'app.sync.goTo': 'first'
8 +})
9 +
10 +exports.create = (api) => {
11 + return nest('message.html.quote', function quote (msg) {
12 + return h('i.MessageQuote.fa.fa-quote-right',
13 + {
14 + 'ev-click': (ev) => {
15 + ev.preventDefault()
16 + api.app.sync.goTo({ action: 'quote', key: msg.key, value: msg.value })
17 + },
18 + title: 'quote'
19 + }
20 + )
21 + })
22 +}
message/html/quote.mcssView
@@ -1,0 +1,6 @@
1 +MessageQuote {
2 + color: #c9c9c9
3 +
4 + cursor: pointer
5 + :hover { color: gray }
6 +}
message/html/reply.jsView
@@ -1,0 +1,22 @@
1 +var h = require('mutant/h')
2 +var nest = require('depnest')
3 +
4 +exports.gives = nest('message.html.reply')
5 +
6 +exports.needs = nest({
7 + 'app.sync.goTo': 'first'
8 +})
9 +
10 +exports.create = (api) => {
11 + return nest('message.html.reply', function betterReply (msg) {
12 + return h('i.MessageReply.fa.fa-comment',
13 + {
14 + title: 'reply',
15 + 'ev-click': (ev) => {
16 + ev.preventDefault()
17 + api.app.sync.goTo({ action: 'reply', key: msg.key, value: msg.value })
18 + }
19 + }
20 + )
21 + })
22 +}
message/html/reply.mcssView
@@ -1,0 +1,6 @@
1 +MessageReply {
2 + color: #c9c9c9
3 +
4 + cursor: pointer
5 + :hover { color: gray }
6 +}
package-lock.jsonView
The diff is too large to show. Use a local git client to view these changes.
Old file size: 381120 bytes
New file size: 382012 bytes
package.jsonView
@@ -82,8 +82,9 @@
8282 "pull-stream": "^3.6.9",
8383 "read-directory": "^3.0.1",
8484 "require-style": "^1.0.1",
8585 "scuttle-blog": "^1.0.1",
86 + "scuttle-thread": "^1.0.1",
8687 "scuttlebot": "^13.0.2",
8788 "setimmediate": "^1.0.5",
8889 "ssb-about": "^2.0.0",
8990 "ssb-backlinks": "^0.7.3",

Built with git-ssb-web