git ssb

2+

mixmix / ticktack



Commit 30cc7a8935378d1ee6fddd76239383d8da592a47

rename ThreadCard BlogCard

mix irving committed on 10/16/2017, 3:29:23 AM
Parent: 46d507d68f853a7a8459be31b98e58fa4e8430aa

Files changed

app/html/blog-card.jsadded
app/html/blog-card.mcssadded
app/html/thread-card.jsdeleted
app/html/thread-card.mcssdeleted
app/index.jschanged
app/page/blogIndex.jschanged
app/page/channel.jschanged
app/page/home.jschanged
app/page/home.mcsschanged
app/page/userShow.jschanged
app/html/blog-card.jsView
@@ -1,0 +1,131 @@
1+var nest = require('depnest')
2+var h = require('mutant/h')
3+var isString= require('lodash/isString')
4+var maxBy= require('lodash/maxBy')
5+var humanTime = require('human-time')
6+var marksum = require('markdown-summary')
7+var markdown = require('ssb-markdown')
8+var ref = require('ssb-ref')
9+var htmlEscape = require('html-escape')
10+
11+function renderEmoji (emoji, url) {
12+ if (!url) return ':' + emoji + ':'
13+ return `
14+ <img
15+ src="${htmlEscape(url)}"
16+ alt=":${htmlEscape(emoji)}:"
17+ title=":${htmlEscape(emoji)}:"
18+ class="emoji"
19+ >
20+ `
21+}
22+
23+exports.gives = nest('app.html.blogCard', true)
24+
25+exports.needs = nest({
26+ 'keys.sync.id': 'first',
27+ 'history.sync.push': 'first',
28+ 'about.obs.name': 'first',
29+ 'about.html.avatar': 'first',
30+ 'translations.sync.strings': 'first',
31+ 'unread.sync.isUnread': 'first',
32+ 'message.html.markdown': 'first',
33+ 'blob.sync.url': 'first',
34+ 'emoji.sync.url': 'first'
35+})
36+
37+exports.create = function (api) {
38+
39+ //render markdown, but don't support patchwork@2 style mentions or custom emoji right now.
40+ function render (source) {
41+ return markdown.block(source, {
42+ emoji: (emoji) => {
43+ return renderEmoji(emoji, api.emoji.sync.url(emoji))
44+ },
45+ toUrl: (id) => {
46+ if (ref.isBlob(id)) return api.blob.sync.url(id)
47+ return id
48+ },
49+ imageLink: (id) => id
50+ })
51+ }
52+
53+
54+ //render the icon for a thread.
55+ //it would be more depjecty to split this
56+ //into two methods, one in a private plugin
57+ //one in a channel plugin
58+ function threadIcon (msg) {
59+ if(msg.value.private) {
60+ const myId = api.keys.sync.id()
61+
62+ return msg.value.content.recps
63+ .map(link => isString(link) ? link : link.link)
64+ .filter(link => link !== myId)
65+ .map(api.about.html.avatar)
66+ }
67+ else if(msg.value.content.channel)
68+ return '#'+msg.value.content.channel
69+ }
70+
71+
72+ // REFACTOR: move this to a template?
73+ function buildRecipientNames (thread) {
74+ const myId = api.keys.sync.id()
75+
76+ return thread.value.content.recps
77+ .map(link => isString(link) ? link : link.link)
78+ .filter(link => link !== myId)
79+ .map(api.about.obs.name)
80+ }
81+
82+ return nest('app.html.blogCard', (thread, opts = {}) => {
83+ var strings = api.translations.sync.strings()
84+ const { subject } = api.message.html
85+
86+ if(!thread.value) return
87+ if('string' !== typeof thread.value.content.text) return
88+
89+ const lastReply = thread.replies && maxBy(thread.replies, r => r.timestamp)
90+
91+ const onClick = opts.onClick || function () { api.history.sync.push(thread) }
92+ const id = `${thread.key.replace(/[^a-z0-9]/gi, '')}` //-${JSON.stringify(opts)}`
93+ // id is only here to help morphdom morph accurately
94+
95+ var img = marksum.image(thread.value.content.text)
96+ var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(img)
97+
98+ if(m) {
99+ //Hey this works! fit an image into a specific size (see thread-card.mcss)
100+ //centered, and scaled to fit the square (works with both landscape and portrait!)
101+ //This is functional css not opinionated css, so all embedded.
102+ img = h('Thumbnail')
103+ img.style = 'background-image: url("'+api.blob.sync.url(m[1])+'"); background-position:center; background-size: cover;'
104+ }
105+ else img = ''
106+ var title = render(marksum.title(thread.value.content.text))
107+ var summary = render(marksum.summary(thread.value.content.text))
108+
109+ var className = thread.unread ? '-unread': ''
110+ return h('BlogCard', { id, className }, [
111+ h('div.context', [
112+ api.about.html.avatar(thread.value.author),
113+ ' ',
114+ api.about.obs.name(thread.value.author),
115+ ' ',
116+ humanTime(new Date(thread.value.timestamp)),
117+ ' ',
118+ thread.value.content.channel ? '#'+thread.value.content.channel : null
119+ ]),
120+ h('div.content', {'ev-click': onClick}, [
121+ img,
122+ h('Text', [
123+ h('h2', {innerHTML: title}),
124+ h('Summary', {innerHTML: summary})
125+ ])
126+ ])
127+ ])
128+ })
129+}
130+
131+
app/html/blog-card.mcssView
@@ -1,0 +1,71 @@
1+BlogCard {
2+ display: flex
3+// align-items: center
4+ flex-direction: column
5+ margin-bottom: .5rem
6+
7+ div.context {
8+ display: flex
9+ margin-right: 1rem
10+ font-weight: inherit
11+ }
12+
13+ div.content {
14+ display: flex
15+ flex-direction: row
16+ flex-grow: 1
17+
18+ cursor: pointer
19+ padding: 1rem
20+ border: 1px solid #ddd
21+ border-radius: 2px
22+
23+ transition: all .5s ease
24+
25+ :hover {
26+ background-color: #fff
27+ border: 1px solid #fff
28+ }
29+ div.text {
30+ flow-direction: column
31+ }
32+
33+ div.subject {
34+ font-size: 1.2rem
35+ margin-bottom: .3rem
36+
37+ $markdownLarge
38+ }
39+ div.reply {
40+ color: #444
41+
42+ display: flex
43+ align-items: center
44+
45+ i.fa-caret-left {
46+ margin-left: .7rem
47+ margin-right: .5rem
48+ }
49+
50+ $markdownSmall
51+ }
52+ }
53+
54+ -unread {
55+ div.content {
56+ background-color: #fff
57+
58+ div.subject {
59+ $markdownBold
60+ }
61+ }
62+ }
63+}
64+
65+Thumbnail {
66+ border-radius: 5px
67+ min-width: 100px
68+ min-height: 100px
69+ width: 100px
70+ height: 100px
71+}
app/html/thread-card.jsView
@@ -1,131 +1,0 @@
1-var nest = require('depnest')
2-var h = require('mutant/h')
3-var isString= require('lodash/isString')
4-var maxBy= require('lodash/maxBy')
5-var humanTime = require('human-time')
6-var marksum = require('markdown-summary')
7-var markdown = require('ssb-markdown')
8-var ref = require('ssb-ref')
9-var htmlEscape = require('html-escape')
10-
11-function renderEmoji (emoji, url) {
12- if (!url) return ':' + emoji + ':'
13- return `
14- <img
15- src="${htmlEscape(url)}"
16- alt=":${htmlEscape(emoji)}:"
17- title=":${htmlEscape(emoji)}:"
18- class="emoji"
19- >
20- `
21-}
22-
23-exports.gives = nest('app.html.threadCard', true)
24-
25-exports.needs = nest({
26- 'keys.sync.id': 'first',
27- 'history.sync.push': 'first',
28- 'about.obs.name': 'first',
29- 'about.html.avatar': 'first',
30- 'translations.sync.strings': 'first',
31- 'unread.sync.isUnread': 'first',
32- 'message.html.markdown': 'first',
33- 'blob.sync.url': 'first',
34- 'emoji.sync.url': 'first'
35-})
36-
37-exports.create = function (api) {
38-
39- //render markdown, but don't support patchwork@2 style mentions or custom emoji right now.
40- function render (source) {
41- return markdown.block(source, {
42- emoji: (emoji) => {
43- return renderEmoji(emoji, api.emoji.sync.url(emoji))
44- },
45- toUrl: (id) => {
46- if (ref.isBlob(id)) return api.blob.sync.url(id)
47- return id
48- },
49- imageLink: (id) => id
50- })
51- }
52-
53-
54- //render the icon for a thread.
55- //it would be more depjecty to split this
56- //into two methods, one in a private plugin
57- //one in a channel plugin
58- function threadIcon (msg) {
59- if(msg.value.private) {
60- const myId = api.keys.sync.id()
61-
62- return msg.value.content.recps
63- .map(link => isString(link) ? link : link.link)
64- .filter(link => link !== myId)
65- .map(api.about.html.avatar)
66- }
67- else if(msg.value.content.channel)
68- return '#'+msg.value.content.channel
69- }
70-
71-
72- // REFACTOR: move this to a template?
73- function buildRecipientNames (thread) {
74- const myId = api.keys.sync.id()
75-
76- return thread.value.content.recps
77- .map(link => isString(link) ? link : link.link)
78- .filter(link => link !== myId)
79- .map(api.about.obs.name)
80- }
81-
82- return nest('app.html.threadCard', (thread, opts = {}) => {
83- var strings = api.translations.sync.strings()
84- const { subject } = api.message.html
85-
86- if(!thread.value) return
87- if('string' !== typeof thread.value.content.text) return
88-
89- const lastReply = thread.replies && maxBy(thread.replies, r => r.timestamp)
90-
91- const onClick = opts.onClick || function () { api.history.sync.push(thread) }
92- const id = `${thread.key.replace(/[^a-z0-9]/gi, '')}` //-${JSON.stringify(opts)}`
93- // id is only here to help morphdom morph accurately
94-
95- var img = marksum.image(thread.value.content.text)
96- var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(img)
97-
98- if(m) {
99- //Hey this works! fit an image into a specific size (see thread-card.mcss)
100- //centered, and scaled to fit the square (works with both landscape and portrait!)
101- //This is functional css not opinionated css, so all embedded.
102- img = h('Thumbnail')
103- img.style = 'background-image: url("'+api.blob.sync.url(m[1])+'"); background-position:center; background-size: cover;'
104- }
105- else img = ''
106- var title = render(marksum.title(thread.value.content.text))
107- var summary = render(marksum.summary(thread.value.content.text))
108-
109- var className = thread.unread ? '-unread': ''
110- return h('ThreadCard', { id, className }, [
111- h('div.context', [
112- api.about.html.avatar(thread.value.author),
113- ' ',
114- api.about.obs.name(thread.value.author),
115- ' ',
116- humanTime(new Date(thread.value.timestamp)),
117- ' ',
118- thread.value.content.channel ? '#'+thread.value.content.channel : null
119- ]),
120- h('div.content', {'ev-click': onClick}, [
121- img,
122- h('Text', [
123- h('h2', {innerHTML: title}),
124- h('Summary', {innerHTML: summary})
125- ])
126- ])
127- ])
128- })
129-}
130-
131-
app/html/thread-card.mcssView
@@ -1,71 +1,0 @@
1-ThreadCard {
2- display: flex
3-// align-items: center
4- flex-direction: column
5- margin-bottom: .5rem
6-
7- div.context {
8- display: flex
9- margin-right: 1rem
10- font-weight: inherit
11- }
12-
13- div.content {
14- display: flex
15- flex-direction: row
16- flex-grow: 1
17-
18- cursor: pointer
19- padding: 1rem
20- border: 1px solid #ddd
21- border-radius: 2px
22-
23- transition: all .5s ease
24-
25- :hover {
26- background-color: #fff
27- border: 1px solid #fff
28- }
29- div.text {
30- flow-direction: column
31- }
32-
33- div.subject {
34- font-size: 1.2rem
35- margin-bottom: .3rem
36-
37- $markdownLarge
38- }
39- div.reply {
40- color: #444
41-
42- display: flex
43- align-items: center
44-
45- i.fa-caret-left {
46- margin-left: .7rem
47- margin-right: .5rem
48- }
49-
50- $markdownSmall
51- }
52- }
53-
54- -unread {
55- div.content {
56- background-color: #fff
57-
58- div.subject {
59- $markdownBold
60- }
61- }
62- }
63-}
64-
65-Thumbnail {
66- border-radius: 5px
67- min-width: 100px
68- min-height: 100px
69- width: 100px
70- height: 100px
71-}
app/index.jsView
@@ -7,9 +7,9 @@
77 context: require('./html/context'),
88 header: require('./html/header'),
99 thread: require('./html/thread'),
1010 link: require('./html/link'),
11- threadCard: require('./html/thread-card'),
11+ blogCard: require('./html/thread-card'),
1212 },
1313 page: {
1414 blogIndex: require('./page/blogIndex'),
1515 blogNew: require('./page/blogNew'),
app/page/blogIndex.jsView
@@ -9,9 +9,9 @@
99 exports.gives = nest('app.page.blogIndex')
1010
1111 exports.needs = nest({
1212 'app.html.context': 'first',
13- 'app.html.threadCard': 'first',
13+ 'app.html.blogCard': 'first',
1414 'history.sync.push': 'first',
1515 'keys.sync.id': 'first',
1616 'translations.sync.strings': 'first',
1717 'state.obs.threads': 'first',
@@ -100,9 +100,9 @@
100100 var onClick
101101 if (channel && !recps)
102102 onClick = (ev) => api.history.sync.push({ key: thread.key, page: 'blogShow' })
103103
104- return api.app.html.threadCard(thread, { onClick })
104+ return api.app.html.blogCard(thread, { onClick })
105105 })
106106 )
107107 )
108108
app/page/channel.jsView
@@ -7,9 +7,9 @@
77 exports.gives = nest('app.page.channel')
88
99 exports.needs = nest({
1010 'app.html.link': 'first',
11- 'app.html.threadCard': 'first',
11+ 'app.html.blogCard': 'first',
1212 'history.sync.push': 'first',
1313 'state.obs.channel': 'first',
1414 'translations.sync.strings': 'first',
1515 })
@@ -41,9 +41,9 @@
4141 h('div.threads', Object.keys(threads.roots)
4242 .map(id => threads.roots[id])
4343 .filter(thread => get(thread, 'value.content.channel') == channel)
4444 .sort((a, b) => latestUpdate(b) - latestUpdate(a))
45- .map(thread => api.app.html.threadCard(thread))
45+ .map(thread => api.app.html.blogCard(thread))
4646 )
4747 )
4848 return updates
4949 }
app/page/home.jsView
@@ -11,9 +11,9 @@
1111 'history.sync.push': 'first',
1212 'keys.sync.id': 'first',
1313 'translations.sync.strings': 'first',
1414 'state.obs.threads': 'first',
15- 'app.html.threadCard': 'first',
15+ 'app.html.blogCard': 'first',
1616 'unread.sync.isUnread': 'first'
1717 })
1818
1919 // function toRecpGroup(msg) {
@@ -122,9 +122,9 @@
122122 var onClick
123123 if (channel && !recps)
124124 onClick = (ev) => api.history.sync.push({ channel })
125125
126- return api.app.html.threadCard(thread, { onClick })
126+ return api.app.html.blogCard(thread, { onClick })
127127 })
128128 )
129129 )
130130
app/page/home.mcssView
@@ -8,9 +8,9 @@
88
99 div.threads {
1010 min-width: 60%
1111
12- div.ThreadCard {
12+ div.BlogCard {
1313 div.content {
1414 div.subject {
1515 display: flex
1616
@@ -30,9 +30,9 @@
3030 -channel {
3131 $homePageSection
3232
3333 div.threads {
34- div.ThreadCard {
34+ div.BlogCard {
3535 div.context {
3636 background: #fff
3737 min-width: 8rem
3838 padding: .1rem .3rem
@@ -70,7 +70,7 @@
7070 margin-bottom: .4rem
7171 }
7272
7373 div.threads {
74- div.ThreadCard {}
74+ div.BlogCard {}
7575 }
7676 }
app/page/userShow.jsView
@@ -8,9 +8,9 @@
88 exports.needs = nest({
99 'about.html.image': 'first',
1010 'about.obs.name': 'first',
1111 'app.html.link': 'first',
12- 'app.html.threadCard': 'first',
12+ 'app.html.blogCard': 'first',
1313 'contact.async.follow': 'first',
1414 'contact.async.unfollow': 'first',
1515 'contact.obs.followers': 'first',
1616 'feed.pull.private': 'first',
@@ -92,9 +92,9 @@
9292 h('div.directMessage', directMessageButton)
9393 ])
9494 : '',
9595 ]),
96- h('section.blogs', map(threads, api.app.html.threadCard))
96+ h('section.blogs', map(threads, api.app.html.blogCard))
9797 ])
9898 ])
9999 }
100100 }

Built with git-ssb-web