Files: c579769220cc3a79b16cc065f3554b1a450038fd / app / html / blogCard.js
4106 bytesRaw
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 marksum = require('markdown-summary') |
6 | var markdown = require('ssb-markdown') |
7 | var ref = require('ssb-ref') |
8 | var htmlEscape = require('html-escape') |
9 | |
10 | function renderEmoji (emoji, url) { |
11 | if (!url) return ':' + emoji + ':' |
12 | return ` |
13 | <img |
14 | src="${htmlEscape(url)}" |
15 | alt=":${htmlEscape(emoji)}:" |
16 | title=":${htmlEscape(emoji)}:" |
17 | class="emoji" |
18 | > |
19 | ` |
20 | } |
21 | |
22 | exports.gives = nest('app.html.blogCard', true) |
23 | |
24 | exports.needs = nest({ |
25 | 'keys.sync.id': 'first', |
26 | 'history.sync.push': 'first', |
27 | 'about.obs.color': '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 | 'message.html.channel': 'first', |
34 | 'message.html.timeago': 'first', |
35 | 'blob.sync.url': 'first', |
36 | 'emoji.sync.url': 'first' |
37 | }) |
38 | |
39 | exports.create = function (api) { |
40 | |
41 | //render markdown, but don't support patchwork@2 style mentions or custom emoji right now. |
42 | function render (source) { |
43 | return markdown.block(source, { |
44 | emoji: (emoji) => { |
45 | return renderEmoji(emoji, api.emoji.sync.url(emoji)) |
46 | }, |
47 | toUrl: (id) => { |
48 | if (ref.isBlob(id)) return api.blob.sync.url(id) |
49 | return id |
50 | }, |
51 | imageLink: (id) => id |
52 | }) |
53 | } |
54 | |
55 | |
56 | //render the icon for a blog. |
57 | //it would be more depjecty to split this |
58 | //into two methods, one in a private plugin |
59 | //one in a channel plugin |
60 | function blogIcon (msg) { |
61 | if(msg.value.private) { |
62 | const myId = api.keys.sync.id() |
63 | |
64 | return msg.value.content.recps |
65 | .map(link => isString(link) ? link : link.link) |
66 | .filter(link => link !== myId) |
67 | .map(link => api.about.html.avatar) |
68 | } |
69 | else if(msg.value.content.channel) |
70 | return '#'+msg.value.content.channel |
71 | } |
72 | |
73 | |
74 | // REFACTOR: move this to a template? |
75 | function buildRecipientNames (blog) { |
76 | const myId = api.keys.sync.id() |
77 | |
78 | return blog.value.content.recps |
79 | .map(link => isString(link) ? link : link.link) |
80 | .filter(link => link !== myId) |
81 | .map(api.about.obs.name) |
82 | } |
83 | |
84 | return nest('app.html.blogCard', (blog, opts = {}) => { |
85 | var strings = api.translations.sync.strings() |
86 | |
87 | if(!blog.value) return |
88 | if('string' !== typeof blog.value.content.text) return |
89 | |
90 | const lastReply = blog.replies && maxBy(blog.replies, r => r.timestamp) |
91 | |
92 | const goToBlog = () => api.history.sync.push(blog) |
93 | const onClick = opts.onClick || goToBlog |
94 | const id = `${blog.key.replace(/[^a-z0-9]/gi, '')}` //-${JSON.stringify(opts)}` |
95 | // id is only here to help morphdom morph accurately |
96 | |
97 | const { content, author } = blog.value |
98 | |
99 | var img = h('Thumbnail') |
100 | var m = /\!\[[^]+\]\(([^\)]+)\)/.exec(marksum.image(content.text)) |
101 | if(m) { |
102 | //Hey this works! fit an image into a specific size (see blog-card.mcss) |
103 | //centered, and scaled to fit the square (works with both landscape and portrait!) |
104 | //This is functional css not opinionated css, so all embedded. |
105 | img.style = 'background-image: url("'+api.blob.sync.url(m[1])+'"); background-position:center; background-size: cover;' |
106 | } else { |
107 | var style = { 'background-color': api.about.obs.color(blog.key) } |
108 | img = h('Thumbnail -empty', { style }, [ |
109 | h('i.fa.fa-file-text-o') |
110 | ]) |
111 | } |
112 | |
113 | const title = render(marksum.title(content.text)) |
114 | const summary = render(marksum.summary(content.text)) |
115 | |
116 | const className = blog.unread ? '-unread': '' |
117 | |
118 | return h('BlogCard', { id, className, 'ev-click': onClick }, [ |
119 | h('div.context', [ |
120 | api.about.html.avatar(author, 'tiny'), |
121 | h('div.name', api.about.obs.name(author)), |
122 | api.message.html.timeago(blog) |
123 | ]), |
124 | h('div.content', [ |
125 | img, |
126 | h('div.text', [ |
127 | h('h2', {innerHTML: title}), |
128 | content.channel |
129 | ? api.message.html.channel(blog) |
130 | : '', |
131 | h('div.summary', {innerHTML: summary}) |
132 | ]) |
133 | ]) |
134 | ]) |
135 | }) |
136 | } |
137 | |
138 | |
139 |
Built with git-ssb-web