Files: 455af00799601cb57c6ea54edad5e85c516b799f / lib / depject / message / html / render / gathering.js
5923 bytesRaw
1 | const { h, computed, when, map, send } = require('mutant') |
2 | const nest = require('depnest') |
3 | const extend = require('xtend') |
4 | const moment = require('moment-timezone') |
5 | const addContextMenu = require('../../../../message/html/decorate/context-menu') |
6 | |
7 | exports.needs = nest({ |
8 | 'message.html.markdown': 'first', |
9 | 'message.html.layout': 'first', |
10 | 'message.async.publish': 'first', |
11 | 'keys.sync.id': 'first', |
12 | 'about.html.image': 'first', |
13 | 'about.obs.latestValue': 'first', |
14 | 'about.obs.socialValues': 'first', |
15 | 'about.obs.valueFrom': 'first', |
16 | 'about.obs.name': 'first', |
17 | 'contact.obs.following': 'first', |
18 | 'blob.sync.url': 'first', |
19 | 'gathering.sheet.edit': 'first' |
20 | }) |
21 | |
22 | exports.gives = nest('message.html', { |
23 | canRender: true, |
24 | render: true |
25 | }) |
26 | |
27 | exports.create = function (api) { |
28 | let following = null |
29 | |
30 | return nest('message.html', { |
31 | canRender: isRenderable, |
32 | render: function (msg, opts) { |
33 | if (!isRenderable(msg)) return |
34 | |
35 | const yourId = api.keys.sync.id() |
36 | |
37 | // passed in from sbot/public-feed/roots |
38 | const suppliedGathering = msg.gathering || {} |
39 | |
40 | // allow override of resolved about messages for preview in modules/gathering/sheet/edit.js |
41 | const about = msg.key |
42 | ? extend({ |
43 | hidden: api.about.obs.valueFrom(msg.key, 'hidden', yourId), |
44 | image: suppliedGathering.image || api.about.obs.latestValue(msg.key, 'image'), |
45 | title: suppliedGathering.title || api.about.obs.latestValue(msg.key, 'title'), |
46 | description: suppliedGathering.description || api.about.obs.latestValue(msg.key, 'description'), |
47 | location: suppliedGathering.location || api.about.obs.latestValue(msg.key, 'location'), |
48 | startDateTime: suppliedGathering.startDateTime || api.about.obs.latestValue(msg.key, 'startDateTime') |
49 | }, msg.previewAbout) |
50 | : msg.previewAbout |
51 | |
52 | const attendees = msg.key ? computed([api.about.obs.socialValues(msg.key, 'attendee')], getAttendees) : [] |
53 | const disableActions = !!msg.previewAbout |
54 | |
55 | const attending = computed([attendees, yourId], (attendees, yourId) => attendees.includes(yourId)) |
56 | |
57 | if (!following) { |
58 | following = api.contact.obs.following(yourId) |
59 | } |
60 | |
61 | const imageUrl = computed(about.image, (id) => api.blob.sync.url(id)) |
62 | const imageId = computed(about.image, (link) => (link && link.link) || link) |
63 | const content = h('GatheringCard', [ |
64 | h('div.title', [ |
65 | h('a', { |
66 | href: msg.key |
67 | }, about.title), |
68 | h('button', { |
69 | disabled: disableActions, |
70 | 'ev-click': send(api.gathering.sheet.edit, msg.key) |
71 | }, 'Edit Details') |
72 | ]), |
73 | h('div.time', computed(about.startDateTime, formatTime)), |
74 | when(about.image, h('a.image', { |
75 | href: imageId, |
76 | style: { |
77 | 'background-image': computed(imageUrl, (url) => `url(${url})`) |
78 | } |
79 | })), |
80 | h('div.attending', [ |
81 | h('div.title', ['Attendees', ' (', computed([attendees], (x) => x.length), ')']), |
82 | h('div.attendees', [ |
83 | map(attendees, (attendee) => { |
84 | return h('a.attendee', { |
85 | href: attendee, |
86 | title: nameAndFollowWarning(attendee) |
87 | }, api.about.html.image(attendee)) |
88 | }) |
89 | ]), |
90 | h('div.actions', [ |
91 | h('button -attend', { |
92 | disabled: computed([attending, disableActions], (...args) => args.some(Boolean)), |
93 | 'ev-click': send(publishAttending, msg) |
94 | }, 'Attending'), |
95 | h('button -attend', { |
96 | disabled: disableActions, |
97 | 'ev-click': send(publishNotAttending, msg) |
98 | }, 'Can\'t Attend') |
99 | ]) |
100 | ]), |
101 | h('div.location', markdown(about.location)), |
102 | when(about.description, h('div.description', markdown(about.description))) |
103 | ]) |
104 | |
105 | const editPreview = msg.previewAbout && msg.key |
106 | |
107 | const element = api.message.html.layout(msg, extend({ |
108 | content, |
109 | miniContent: editPreview ? 'Edited a gathering' : 'Added a gathering', |
110 | actions: !msg.previewAbout, |
111 | layout: 'mini' |
112 | }, opts)) |
113 | |
114 | return addContextMenu(element, { |
115 | msg |
116 | }) |
117 | } |
118 | }) |
119 | |
120 | function publishAttending (msg) { |
121 | const yourId = api.keys.sync.id() |
122 | const content = { |
123 | type: 'about', |
124 | about: msg.key, |
125 | attendee: { |
126 | link: yourId |
127 | } |
128 | } |
129 | |
130 | // what starts in private, stays in private! |
131 | if (msg.value.content.recps) { |
132 | content.recps = msg.value.content.recps |
133 | } |
134 | |
135 | // publish with confirm |
136 | api.message.async.publish(content) |
137 | } |
138 | |
139 | function publishNotAttending (msg) { |
140 | const yourId = api.keys.sync.id() |
141 | const content = { |
142 | type: 'about', |
143 | about: msg.key, |
144 | attendee: { |
145 | link: yourId, |
146 | remove: true |
147 | } |
148 | } |
149 | |
150 | // what starts in private, stays in private! |
151 | if (msg.value.content.recps) { |
152 | content.recps = msg.value.content.recps |
153 | } |
154 | |
155 | // publish with confirm |
156 | api.message.async.publish(content) |
157 | } |
158 | |
159 | function nameAndFollowWarning (id) { |
160 | const yourId = api.keys.sync.id() |
161 | return computed([api.about.obs.name(id), id, following], function nameAndFollowWarning (name, id, following) { |
162 | if (id === yourId) { |
163 | return `${name} (you)` |
164 | } else if (following.includes(id)) { |
165 | return `${name}` |
166 | } else { |
167 | return `${name} (not following)` |
168 | } |
169 | }) |
170 | } |
171 | |
172 | function markdown (obs) { |
173 | return computed(obs, (text) => { |
174 | if (typeof text === 'string') return api.message.html.markdown(text) |
175 | }) |
176 | } |
177 | } |
178 | |
179 | function formatTime (time) { |
180 | if (time && time.epoch) { |
181 | return moment(time.epoch).format('LLLL zz') |
182 | } |
183 | } |
184 | |
185 | function getAttendees (lookup) { |
186 | return Object.keys(lookup) |
187 | } |
188 | |
189 | function isRenderable (msg) { |
190 | return (msg.value.content.type === 'gathering') ? true : undefined |
191 | } |
192 |
Built with git-ssb-web