git ssb

10+

Matt McKegg / patchwork



Commit a289f670e3c164407deae3baa673d2f9d1ab03ee

tidy up meta-summary files and code

Matt McKegg committed on 7/6/2018, 7:14:33 AM
Parent: 907e87521ce87db66a62d443aa9c0d5bc4e4815e

Files changed

modules/feed/html/rollup.jschanged
modules/feed/html/meta-summary.jsadded
styles/dark/follow-graph.mcssdeleted
styles/dark/feed-meta-summary.mcssadded
modules/feed/html/rollup.jsView
@@ -6,9 +6,8 @@
66 var nextStepper = require('../../../lib/next-stepper')
77 var extend = require('xtend')
88 var paramap = require('pull-paramap')
99 var Group = require('pull-group')
10-var ref = require('ssb-ref')
1110
1211 var bumpMessages = {
1312 'vote': 'liked this message',
1413 'post': 'replied to this message',
@@ -40,9 +39,10 @@
4039 'sbot.async.get': 'first',
4140 'keys.sync.id': 'first',
4241 'intl.sync.i18n': 'first',
4342 'intl.sync.i18n_n': 'first',
44- 'message.html.missing': 'first'
43+ 'message.html.missing': 'first',
44+ 'feed.html.metaSummary': 'first'
4545 })
4646
4747 exports.gives = nest({
4848 'feed.html.rollup': true
@@ -215,50 +215,11 @@
215215 )
216216 })
217217 }
218218
219- function renderGroup (item, opts) {
220- var expanded = Value(false)
221- var actions = getContactActions(item.msgs)
222- var counts = getContactActionCounts(actions)
223- var reduced = reduceActions(counts)
224-
225- var contentSummary = h('FollowGraph', [
226- reduced.map(action => actionSummary(action, id => {
227- if (id.startsWith('#')) {
228- return h('a -channel', {href: id}, `#${ref.normalizeChannel(id)}`)
229- } else {
230- return h('a', {href: id}, api.about.html.image(id))
231- }
232- }, i18n))
233- ])
234-
235- return h('FeedEvent -group', {
236- classList: [ when(expanded, '-expanded') ]
237- }, [
238- contentSummary,
239- when(expanded, h('div.items', item.msgs.map(msg => renderItem(msg, opts)))),
240- h('a.expand', {
241- 'tab-index': 0,
242- 'ev-click': {handleEvent: toggleValue, value: expanded}
243- }, [
244- when(expanded,
245- [i18n('Hide details')],
246- [i18n('Show details') + ' (', item.msgs.length, ')']
247- )
248- ])
249- ])
250- }
251-
252219 function renderItem (item, opts) {
253220 if (item.group) {
254- // if (item.msgs.length === 1) {
255- // // fallback to standard item rendering if group only contains 1 item
256- // item = item.msgs[0]
257- // } else {
258- // return renderGroup(item, opts)
259- // }
260- return renderGroup(item, opts)
221+ return api.feed.html.metaSummary(item, renderItem, opts)
261222 }
262223 var partial = opts && opts.partial
263224 var meta = null
264225 var previousId = item.key
@@ -483,206 +444,20 @@
483444 return array
484445 }
485446 }
486447
487-function actionSummary (item, avatarFormatter, i18n) {
488- var actionName = null
489- var from = []
490- var to = []
491- if (item.action === 'followedBy' || item.action === 'subscribedBy') {
492- actionName = 'followed'
493- from = item.targets
494- to = [item.id]
495- } else if (item.action === 'unfollowedBy' || item.action === 'unsubscribedBy') {
496- actionName = 'unfollowed'
497- from = item.targets
498- to = [item.id]
499- } else if (item.action === 'following' || item.action === 'subscribing') {
500- actionName = 'followed'
501- from = [item.id]
502- to = item.targets
503- } else if (item.action === 'unfollowing' || item.action === 'unsubscribing') {
504- actionName = 'unfollowed'
505- from = [item.id]
506- to = item.targets
507- } else if (item.action === 'blockedBy') {
508- actionName = 'blocked'
509- from = item.targets
510- to = [item.id]
511- } else if (item.action === 'unblockedBy') {
512- actionName = 'unblocked'
513- from = item.targets
514- to = [item.id]
515- } else if (item.action === 'blocking') {
516- actionName = 'blocked'
517- from = [item.id]
518- to = item.targets
519- } else if (item.action === 'unblocking') {
520- actionName = 'unblocked'
521- from = [item.id]
522- to = item.targets
523- } else if (item.action === 'identifying') {
524- actionName = 'identify'
525- from = [item.id]
526- to = item.targets
527- } else if (item.action === 'identifiedBy') {
528- actionName = 'identify'
529- from = item.targets
530- to = [item.id]
531- }
532-
533- return h('div -' + actionName, [h('div -left', from.slice(0, 10).map(avatarFormatter)), h('span.action'), h('div -right', to.slice(0, 10).map(avatarFormatter))])
534-}
535-
536-function getContactActions (msgs) {
537- var actions = {}
538-
539- // reduce down actions for each contact change (de-duplicate, remove redundency)
540- // e.g. if a person follows someone then unfollows, ignore both actions
541- msgs.forEach(msg => {
542- var content = msg.value.content
543- let from = msg.value.author
544- if (content.type === 'contact') {
545- if (ref.isFeed(content.contact)) {
546- let to = content.contact
547- let key = `${from}:${to}`
548- if (content.following === true) {
549- if (actions[key] === 'unfollow') {
550- delete actions[key]
551- } else {
552- actions[key] = 'follow'
553- }
554- } else if (content.blocking === true) {
555- if (actions[key] === 'unblock') {
556- delete actions[key]
557- } else {
558- actions[key] = 'block'
559- }
560- } else if (content.blocking === false) {
561- if (actions[key] === 'block') {
562- delete actions[key]
563- } else {
564- actions[key] = 'unblock'
565- }
566- } else if (content.following === false) {
567- if (actions[key] === 'follow') {
568- delete actions[key]
569- } else {
570- actions[key] = 'unfollow'
571- }
572- }
573- }
574- } else if (content.type === 'channel') {
575- if (typeof content.channel === 'string') { // TODO: better channel check
576- let to = '#' + content.channel
577- let key = `${from}:${to}`
578- if (content.subscribed === true) {
579- if (actions[key] === 'unfollow') {
580- delete actions[key]
581- } else {
582- actions[key] = 'subscribe'
583- }
584- } else if (content.subscribed === false) {
585- if (actions[key] === 'follow') {
586- delete actions[key]
587- } else {
588- actions[key] = 'unsubscribe'
589- }
590- }
591- }
592- } else if (content.type === 'about') {
593- if (ref.isFeed(content.about)) {
594- let to = content.about
595- let key = `${from}:${to}`
596- actions[key] = 'identify'
597- }
598- }
599- })
600-
601- return actions
602-}
603-
604-function getContactActionCounts (actions) {
605- var actionCounts = {}
606-
607- // get actions performed on and by profiles
608- // collect who did what and has what done on them
609- for (var key in actions) {
610- var action = actions[key]
611- var {from, to} = splitKey(key)
612- actionCounts[from] = actionCounts[from] || ContactActionTracker()
613- actionCounts[to] = actionCounts[to] || ContactActionTracker()
614-
615- if (action === 'follow') {
616- actionCounts[from].following.push(to)
617- actionCounts[to].followedBy.push(from)
618- } else if (action === 'unfollow') {
619- actionCounts[from].unfollowing.push(to)
620- actionCounts[to].unfollowedBy.push(from)
621- } else if (action === 'subscribe') {
622- actionCounts[from].subscribing.push(to)
623- actionCounts[to].subscribedBy.push(from)
624- } else if (action === 'unsubscribe') {
625- actionCounts[from].unsubscribing.push(to)
626- actionCounts[to].unsubscribedBy.push(from)
627- } else if (action === 'block') {
628- actionCounts[from].blocking.push(to)
629- actionCounts[to].blockedBy.push(from)
630- } else if (action === 'unblock') {
631- actionCounts[from].unblocking.push(to)
632- actionCounts[to].unblockedBy.push(from)
633- } else if (action === 'identify') {
634- actionCounts[from].identifying.push(to)
635- actionCounts[to].identifiedBy.push(from)
636- }
637- }
638-
639- return actionCounts
640-}
641-
642-function reduceActions (actionCounts) {
643- var actions = []
644-
645- for (let key in actionCounts) {
646- var value = actionCounts[key]
647- for (let action in value) {
648- actions.push({id: key, action, targets: value[action]})
649- }
650- }
651-
652- // sort desc by most targets per action
653- actions.sort((a, b) => b.targets.length - a.targets.length)
654-
655- // remove duplicate actions, and return!
656- var used = new Set()
657- return actions.filter(action => {
658- let targets = action.targets.filter(target => {
659- return !used.has(`${action.id}:${target}`) && !used.has(`${target}:${action.id}`)
660- })
661- action.targets = targets
662-
663- targets.forEach(target => {
664- used.add(`${action.id}:${target}`)
665- used.add(`${target}:${action.id}`)
666- })
667-
668- // only return if has targets
669- return !!targets.length
670- })
671-}
672-
673448 function GroupSimilar (windowSize, ungroupFilter) {
674449 return pull(
675450 Group(windowSize),
676451 pull.map(function (msgs) {
677452 var result = []
678453 var groups = {}
679454
680- var typeGroups = ['about', 'channel', 'contact']
455+ var metaSummaryTypes = ['about', 'channel', 'contact']
681456
682457 msgs.forEach(msg => {
683- var type = 'group' // msg.value.content.type
684- if (typeGroups.includes(msg.value.content.type) && !hasReply(msg) && !ungroupFilter(msg)) {
458+ var type = 'metaSummary' // msg.value.content.type
459+ if (metaSummaryTypes.includes(msg.value.content.type) && !hasReply(msg) && !ungroupFilter(msg)) {
685460 if (!groups[type]) {
686461 groups[type] = {group: type, msgs: []}
687462 result.push(groups[type])
688463 }
@@ -700,35 +475,4 @@
700475
701476 function hasReply (msg) {
702477 return msg.replies && msg.replies.some(msg => msg.value.content.type === 'post')
703478 }
704-
705-function ContactActionTracker () {
706- return {
707- followedBy: [],
708- following: [],
709- unfollowedBy: [],
710- unfollowing: [],
711- blockedBy: [],
712- blocking: [],
713- unblockedBy: [],
714- unblocking: [],
715- subscribedBy: [],
716- subscribing: [],
717- unsubscribedBy: [],
718- unsubscribing: [],
719- identifiedBy: [],
720- identifying: []
721- }
722-}
723-
724-function toggleValue () {
725- this.value.set(!this.value())
726-}
727-
728-function splitKey (key) {
729- var mid = key.indexOf(':')
730- return {
731- from: key.slice(0, mid),
732- to: key.slice(mid + 1)
733- }
734-}
modules/feed/html/meta-summary.jsView
@@ -1,0 +1,196 @@
1+var nest = require('depnest')
2+var ref = require('ssb-ref')
3+var { when, h, Value } = require('mutant')
4+
5+exports.needs = nest({
6+ 'intl.sync.i18n': 'first',
7+ 'about.html.image': 'first'
8+})
9+
10+exports.gives = nest({
11+ 'feed.html.metaSummary': true
12+})
13+
14+exports.create = function (api) {
15+ const i18n = api.intl.sync.i18n
16+ return nest('feed.html', {metaSummary})
17+
18+ function metaSummary (group, renderItem, opts) {
19+ var expanded = Value(false)
20+ var actions = getActions(group.msgs)
21+ var counts = getActionCounts(actions)
22+ var reduced = reduceActions(counts)
23+
24+ var contentSummary = h('FeedMetaSummary', [
25+ reduced.map(item => {
26+ return h('div -' + item.action, [
27+ h('div -left', item.from.slice(0, 10).map(avatarFormatter)),
28+ h('span.action'),
29+ h('div -right', item.to.slice(0, 10).map(avatarFormatter))
30+ ])
31+ })
32+ ])
33+
34+ return h('FeedEvent -group', {
35+ classList: [ when(expanded, '-expanded') ]
36+ }, [
37+ contentSummary,
38+ when(expanded, h('div.items', group.msgs.map(msg => renderItem(msg, opts)))),
39+ h('a.expand', {
40+ 'tab-index': 0,
41+ 'ev-click': {handleEvent: toggleValue, value: expanded}
42+ }, [
43+ when(expanded,
44+ [i18n('Hide details')],
45+ [i18n('Show details') + ' (', group.msgs.length, ')']
46+ )
47+ ])
48+ ])
49+ }
50+
51+ function avatarFormatter (id) {
52+ if (id.startsWith('#')) {
53+ return h('a -channel', {href: id}, `#${ref.normalizeChannel(id)}`)
54+ } else {
55+ return h('a', {href: id}, api.about.html.image(id))
56+ }
57+ }
58+}
59+
60+function getActions (msgs) {
61+ var actions = {}
62+
63+ // reduce down actions for each contact change (de-duplicate, remove redundency)
64+ // e.g. if a person follows someone then unfollows, ignore both actions
65+ msgs.forEach(msg => {
66+ var content = msg.value.content
67+ let from = msg.value.author
68+ if (content.type === 'contact') {
69+ if (ref.isFeed(content.contact)) {
70+ let to = content.contact
71+ let key = `${from}:${to}`
72+ if (content.following === true) {
73+ if (actions[key] === 'unfollowed') {
74+ delete actions[key]
75+ } else {
76+ actions[key] = 'followed'
77+ }
78+ } else if (content.blocking === true) {
79+ if (actions[key] === 'unblocked') {
80+ delete actions[key]
81+ } else {
82+ actions[key] = 'blocked'
83+ }
84+ } else if (content.blocking === false) {
85+ if (actions[key] === 'blocked') {
86+ delete actions[key]
87+ } else {
88+ actions[key] = 'unblocked'
89+ }
90+ } else if (content.following === false) {
91+ if (actions[key] === 'followed') {
92+ delete actions[key]
93+ } else {
94+ actions[key] = 'unfollowed'
95+ }
96+ }
97+ }
98+ } else if (content.type === 'channel') {
99+ if (typeof content.channel === 'string') { // TODO: better channel check
100+ let to = '#' + content.channel
101+ let key = `${from}:${to}`
102+ if (content.subscribed === true) {
103+ if (actions[key] === 'unsubscribed') {
104+ delete actions[key]
105+ } else {
106+ actions[key] = 'subscribed'
107+ }
108+ } else if (content.subscribed === false) {
109+ if (actions[key] === 'subscribed') {
110+ delete actions[key]
111+ } else {
112+ actions[key] = 'unsubscribed'
113+ }
114+ }
115+ }
116+ } else if (content.type === 'about') {
117+ if (ref.isFeed(content.about)) {
118+ let to = content.about
119+ let key = `${from}:${to}`
120+ actions[key] = 'identified'
121+ }
122+ }
123+ })
124+
125+ return actions
126+}
127+
128+function getActionCounts (actions) {
129+ var actionCounts = {}
130+
131+ // get actions performed on and by profiles
132+ // collect who did what and has what done on them
133+ for (var key in actions) {
134+ var action = actions[key]
135+ var {from, to} = splitKey(key)
136+ actionCounts[from] = actionCounts[from] || {from: {}, to: {}}
137+ actionCounts[to] = actionCounts[to] || {from: {}, to: {}}
138+
139+ actionCounts[from].from[action] = actionCounts[from].from[action] || []
140+ actionCounts[to].to[action] = actionCounts[to].to[action] || []
141+
142+ actionCounts[from].from[action].push(to)
143+ actionCounts[to].to[action].push(from)
144+ }
145+
146+ return actionCounts
147+}
148+
149+function reduceActions (actionCounts) {
150+ var actions = []
151+
152+ for (let key in actionCounts) {
153+ var value = actionCounts[key]
154+ for (let action in value.from) {
155+ actions.push({from: [key], action, to: value.from[action], rank: value.from[action].length})
156+ }
157+ for (let action in value.to) {
158+ actions.push({from: value.to[action], action, to: [key], rank: value.to[action].length})
159+ }
160+ }
161+
162+ // sort desc by most targets per action
163+ actions.sort((a, b) => Math.max(b.rank - a.rank))
164+
165+ // remove duplicate actions, and return!
166+ var used = new Set()
167+ return actions.filter(action => {
168+ // only add a particular action once!
169+ if (action.from.length > action.to.length) {
170+ action.from = action.from.filter(from => action.to.some(to => !used.has(`${from}:${to}`)))
171+ } else {
172+ action.to = action.to.filter(to => action.from.some(from => !used.has(`${from}:${to}`)))
173+ }
174+
175+ action.from.forEach(from => {
176+ action.to.forEach(to => {
177+ used.add(`${from}:${to}`)
178+ })
179+ })
180+
181+ // // only return if has targets
182+ return action.from.length && action.to.length
183+ })
184+}
185+
186+function toggleValue () {
187+ this.value.set(!this.value())
188+}
189+
190+function splitKey (key) {
191+ var mid = key.indexOf(':')
192+ return {
193+ from: key.slice(0, mid),
194+ to: key.slice(mid + 1)
195+ }
196+}
styles/dark/follow-graph.mcssView
@@ -1,128 +1,0 @@
1-FollowGraph {
2- position: relative
3- display: flex
4- flex-wrap: wrap
5- justify-content: center
6- margin: 10px
7-
8-
9- div {
10- display: flex
11- align-items: center;
12- border-radius: 3px
13- padding: 5px
14- background: #353535
15- margin: 5px
16- white-space: nowrap
17- overflow: hidden
18-
19- div {
20- display: flex;
21- align-items: center;
22- flex-wrap: wrap;
23-
24- -left {
25- background: #111;
26- padding: 5px;
27- margin: -5px;
28- border-radius: 3px
29- }
30-
31- a {
32- vertical-align: middle
33- display: inline-block
34- img {
35- display: block
36- width: 45px;
37- border-radius: 3px;
38- }
39-
40- -channel {
41- font-size: 20px;
42- color: #91c1ec;
43- border-bottom: 1px dotted #91c1ec;
44- margin: 5px;
45- }
46- }
47-
48- a + a {
49- margin-left: 3px
50- }
51- }
52-
53- -blocked {
54- background-color: #382c01
55- span {
56- :after {
57- content: '⚠️'
58- color: #ffa603
59- }
60- }
61- div {
62- -right {
63- opacity: 0.7
64- :hover {
65- opacity: 1
66- }
67- }
68- }
69- }
70-
71- -unblocked {
72- background-color: #39434c
73- span { :after {
74- content: '?'
75- }}
76- }
77-
78- -followed {
79- span { :after {
80- content: '✓'
81- }}
82- }
83-
84- -unfollowed {
85- background-color: #39434c
86- span { :after {
87- content: '⊗'
88- }}
89- }
90-
91- -identify {
92- background-color: #afafaf
93- span { :after {
94- content: '👤'
95- }}
96- }
97-
98-
99- span {
100- vertical-align: middle;
101- position: relative;
102- display: inline-flex;
103- align-items: center;
104- background: svg(arrow) no-repeat right
105- background-size: cover;
106- height: 45px;
107- margin-right: 3px;
108- padding-left: 8px;
109- padding-right: 15px;
110-
111- :after {
112- color: white;
113- font-size: 15px;
114- vertical-align: middle;
115- display: inline-block;
116- }
117-
118- @svg arrow {
119- width: 64px
120- height: 64px
121- path {
122- fill: #111
123- }
124- content: "<path d='M40 54 L64 32 L40 10 L28 10 L16 0 L0 0 L0 64 L16 64 L28 54 Z' />"
125- }
126- }
127- }
128-}
styles/dark/feed-meta-summary.mcssView
@@ -1,0 +1,136 @@
1+FeedMetaSummary {
2+ position: relative
3+ display: flex
4+ flex-wrap: wrap
5+ justify-content: center
6+ margin: 10px
7+
8+ div {
9+ display: flex
10+ align-items: center;
11+ border-radius: 3px
12+ padding: 5px
13+ background: #353535
14+ margin: 5px
15+ white-space: nowrap
16+ overflow: hidden
17+
18+ div {
19+ display: flex;
20+ align-items: center;
21+ flex-wrap: wrap;
22+
23+ -left {
24+ background: #111;
25+ padding: 5px;
26+ margin: -5px;
27+ border-radius: 3px
28+ }
29+
30+ a {
31+ vertical-align: middle
32+ display: inline-block
33+ img {
34+ display: block
35+ width: 45px;
36+ min-height: 30px
37+ border-radius: 3px;
38+ }
39+
40+ -channel {
41+ font-size: 20px;
42+ color: #91c1ec;
43+ border-bottom: 1px dotted #91c1ec;
44+ margin: 5px;
45+ }
46+ }
47+
48+ a + a {
49+ margin-left: 3px
50+ }
51+ }
52+
53+ -blocked {
54+ background-color: #382c01
55+ span {
56+ :after {
57+ content: '⚠️'
58+ color: #ffa603
59+ }
60+ }
61+ div {
62+ -right {
63+ opacity: 0.7
64+ :hover {
65+ opacity: 1
66+ }
67+ }
68+ }
69+ }
70+
71+ -unblocked {
72+ background-color: #39434c
73+ span { :after {
74+ content: '?'
75+ }}
76+ }
77+
78+ -followed, -subscribed {
79+ span { :after {
80+ content: '✓'
81+ }}
82+ }
83+
84+ -unfollowed, -unsubscribed {
85+ background-color: #39434c
86+ span { :after {
87+ content: '⊗'
88+ }}
89+ div {
90+ -right {
91+ opacity: 0.7
92+ :hover {
93+ opacity: 1
94+ }
95+ }
96+ }
97+ }
98+
99+ -identified {
100+ background-color: #afafaf
101+ span { :after {
102+ content: '👤'
103+ }}
104+ }
105+
106+
107+ span {
108+ vertical-align: middle;
109+ position: relative;
110+ display: inline-flex;
111+ align-items: center;
112+ background: svg(arrow) no-repeat right
113+ background-size: cover;
114+ height: 45px;
115+ margin-right: 3px;
116+ padding-left: 8px;
117+ padding-right: 15px;
118+
119+ :after {
120+ color: white;
121+ font-size: 15px;
122+ vertical-align: middle;
123+ display: inline-block;
124+ }
125+
126+ @svg arrow {
127+ width: 64px
128+ height: 64px
129+ path {
130+ fill: #111
131+ }
132+ content: "<path d='M40 54 L64 32 L40 10 L28 10 L16 0 L0 0 L0 64 L16 64 L28 54 Z' />"
133+ }
134+ }
135+ }
136+}

Built with git-ssb-web