Files: 164ee525d1634ae8922a2161019b8894c9a862f7 / app / html / thread-card.js
3058 bytesRaw
1 | var h = require('mutant/h') |
2 | var lodash = require('lodash') |
3 | var nest = require('depnest') |
4 | |
5 | exports.gives = nest('app.html.threadCard', true) |
6 | |
7 | exports.needs = nest({ |
8 | 'keys.sync.id': 'first', |
9 | 'history.sync.push': 'first', |
10 | 'about.obs.name': 'first', |
11 | 'about.html.image': 'first', |
12 | 'message.html.markdown': 'first', |
13 | 'translations.sync.strings': 'first' |
14 | }) |
15 | |
16 | function isString (s) { |
17 | return 'string' === typeof s |
18 | } |
19 | |
20 | function firstLine (text) { |
21 | if(text.length < 80 && !~text.indexOf('\n')) return text |
22 | |
23 | //get the first non-empty line |
24 | var line = text.trim().split('\n').shift().trim() |
25 | |
26 | //always break on a space, so that links are preserved. |
27 | var i = line.indexOf(' ', 80) |
28 | var sample = line.substring(0, ~i ? i : line.length) |
29 | |
30 | const ellipsis = (sample.length < line.length) ? '...' : '' |
31 | return sample + ellipsis |
32 | } |
33 | |
34 | function trimLeadingMentions (str) { |
35 | return str.replace(/^(\s*\[@[^\)]+\)\s*)*/, '') |
36 | // deletes any number of pattern " [@...) " from start of line |
37 | } |
38 | |
39 | function hasBrokenLink (str) { |
40 | return /\[[^\]]*\]\([^\)]*$/.test(str) |
41 | // matches "[name](start_of_link" |
42 | } |
43 | |
44 | |
45 | exports.create = function (api) { |
46 | |
47 | //render the icon for a thread. |
48 | //it would be more depjecty to split this |
49 | //into two methods, one in a private plugin |
50 | //one in a channel plugin |
51 | function threadIcon (msg) { |
52 | if(msg.value.private) { |
53 | const myId = api.keys.sync.id() |
54 | |
55 | return msg.value.content.recps |
56 | .map(link => isString(link) ? link : link.link) |
57 | .filter(link => link !== myId) |
58 | .map(api.about.html.image) |
59 | } |
60 | else if(msg.value.content.channel) |
61 | return '#'+msg.value.content.channel |
62 | } |
63 | |
64 | |
65 | // REFACTOR: move this to a template? |
66 | function buildRecipientNames (thread) { |
67 | const myId = api.keys.sync.id() |
68 | |
69 | return thread.value.content.recps |
70 | .map(link => isString(link) ? link : link.link) |
71 | .filter(link => link !== myId) |
72 | .map(api.about.obs.name) |
73 | } |
74 | |
75 | function link(location) { |
76 | return {'ev-click': () => api.history.sync.push(location)} |
77 | } |
78 | |
79 | function subject (msg) { |
80 | const { subject, text } = msg.value.content |
81 | if(!(subject || text)) return |
82 | return api.message.html.markdown(firstLine(subject|| text)) |
83 | } |
84 | |
85 | return {app: {html: {threadCard: function (thread, opts = {}) { |
86 | var strings = api.translations.sync.strings() |
87 | |
88 | if(!thread.value) return |
89 | if(!thread.value.content.text) return |
90 | |
91 | const subjectEl = h('div.subject', [ |
92 | opts.nameRecipients |
93 | ? h('div.recps', buildRecipientNames(thread).map(recp => h('div.recp', recp))) |
94 | : null, |
95 | subject(thread) |
96 | ]) |
97 | |
98 | const lastReply = thread.replies && |
99 | lodash.maxBy(thread.replies, function (e) { return e.timestamp }) |
100 | |
101 | var replySample = lastReply ? subject(lastReply) : null |
102 | |
103 | return h('div.thread', link(thread), [ |
104 | h('div.context', threadIcon(thread)), |
105 | h('div.content', [ |
106 | subjectEl, |
107 | replySample ? h('div.reply', [ |
108 | h('div.replySymbol', strings.replySymbol), |
109 | replySample |
110 | ]) : null |
111 | ]) |
112 | ]) |
113 | }}}} |
114 | } |
115 | |
116 | |
117 |
Built with git-ssb-web