Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39551
1
##############################################################################
2
#
3
# CoCalc: Collaborative Calculation in the Cloud
4
#
5
# Copyright (C) 2016, Sagemath Inc.
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#
20
###############################################################################
21
22
###
23
Chat message JSON format:
24
25
sender_id : String which is the original message sender's account id
26
event : Can only be "chat" right now.
27
date : A date string
28
history : Array of "History" objects (described below)
29
editing : Object of <account id's> : <"FUTURE">
30
31
"FUTURE" Will likely contain their last edit in the future
32
33
--- History object ---
34
author_id : String which is this message version's author's account id
35
content : The raw display content of the message
36
date : Date **string** of when this edit was sent
37
38
Example object:
39
{"sender_id":"07b12853-07e5-487f-906a-d7ae04536540",
40
"event":"chat",
41
"history":[
42
{"author_id":"07b12853-07e5-487f-906a-d7ae04536540","content":"First edited!","date":"2016-07-23T23:10:15.331Z"},
43
{"author_id":"07b12853-07e5-487f-906a-d7ae04536540","content":"Initial sent message!","date":"2016-07-23T23:10:04.837Z"}
44
],
45
"date":"2016-07-23T23:10:04.837Z","editing":{"07b12853-07e5-487f-906a-d7ae04536540":"FUTURE"}}
46
---
47
48
Chat message types after immutable conversion:
49
(immutable.Map)
50
sender_id : String
51
event : String
52
date : Date Object
53
history : immutable.List of immutable.Maps
54
editing : immutable.Map
55
56
###
57
58
# standard non-CoCalc libraries
59
immutable = require('immutable')
60
{IS_MOBILE, isMobile, IS_TOUCH} = require('./feature')
61
underscore = require('underscore')
62
63
# CoCalc libraries
64
misc = require('smc-util/misc')
65
misc_page = require('./misc_page')
66
{defaults, required} = misc
67
{Markdown, TimeAgo, Tip} = require('./r_misc')
68
{webapp_client} = require('./webapp_client')
69
70
{alert_message} = require('./alerts')
71
72
# React libraries
73
{React, ReactDOM, rclass, rtypes, Actions, Store, redux} = require('./smc-react')
74
{Button, Col, Grid, FormControl, FormGroup, ListGroup, ListGroupItem, Panel, Row, ButtonGroup, Well} = require('react-bootstrap')
75
76
{User} = require('./users')
77
78
exports.redux_name = redux_name = (project_id, path) ->
79
return "editor-#{project_id}-#{path}"
80
81
82
### Message Methods ###
83
exports.newest_content = newest_content = (message) ->
84
return message.get('history').first()?.get('content') ? ''
85
86
exports.sender_is_viewer = sender_is_viewer = (account_id, message) ->
87
account_id == message.get('sender_id')
88
89
exports.message_colors = (account_id, message) ->
90
if sender_is_viewer(account_id, message)
91
return {background: '#46b1f6', color: '#fff', message_class:'smc-message-from-viewer'}
92
else
93
return {background: '#efefef', color: '#000', lighten:{color:'#888'}}
94
95
exports.render_timeago = (message, edit) ->
96
# NOTE: we make click on the timestamp edit the chat since onDoubleClick is completely
97
# ignored on mobile touch devices...
98
if IS_TOUCH and edit?
99
f = edit
100
else
101
f = undefined
102
<span
103
onClick = {f}
104
className = "pull-right small"
105
style = {maxWidth:'20%', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'}
106
>
107
<TimeAgo date={new Date(message.get('date'))} />
108
</span>
109
110
NAME_STYLE =
111
color : "#888"
112
marginBottom : '1px'
113
marginLeft : '10px'
114
right : 0
115
whiteSpace : 'nowrap'
116
overflow : 'hidden'
117
textOverflow : 'ellipsis' # see https://css-tricks.com/snippets/css/truncate-string-with-ellipsis/
118
position : 'absolute' # using the "absolute in relative" positioning trick
119
left : 0
120
top : 0
121
122
exports.show_user_name = show_user_name = (sender_name) ->
123
<div style={position:'relative', height:'1.2em', width:'100%'}>
124
<div className={"small"} style={NAME_STYLE}>
125
{sender_name}
126
</div>
127
</div>
128
129
exports.is_editing = is_editing = (message, account_id) ->
130
message.get('editing').has(account_id)
131
132
exports.blank_column = blank_column = ->
133
<Col key={2} xs={2} sm={2}></Col>
134
135
exports.render_markdown = render_markdown = (value, project_id, file_path, className) ->
136
# the marginBottom offsets that markdown wraps everything in a p tag
137
<div style={marginBottom:'-10px'}>
138
<Markdown value={value} project_id={project_id} file_path={file_path} className={className} />
139
</div>
140
141
exports.render_history_title = render_history_title = ->
142
<ListGroupItem style={borderRadius: '10px 10px 0px 0px', textAlign:'center', padding: '0px'}>
143
<span style={fontStyle: 'italic', fontWeight: 'bold'}>Message History</span>
144
</ListGroupItem>
145
146
exports.render_history_footer = render_history_footer = ->
147
<ListGroupItem style={borderRadius: '0px 0px 10px 10px', marginBottom: '3px'}>
148
</ListGroupItem>
149
150
exports.render_history = render_history = (history, user_map) ->
151
if not history?
152
return
153
historyList = history.toJS().slice(1) # convert to javascrip from immutable, and remove current version.
154
for index, objects of historyList
155
value = objects.content
156
value = misc.smiley
157
s: value
158
wrap: ['<span class="smc-editor-chat-smiley">', '</span>']
159
value = misc_page.sanitize_html_safe(value)
160
author = misc.trunc_middle(user_map.get(objects.author_id)?.get('first_name') + ' ' + user_map.get(objects.author_id)?.get('last_name'), 20)
161
if value.trim() == ''
162
text = "Message deleted "
163
else
164
text = "Last edit "
165
<Well key={index} bsSize="small" style={marginBottom:'0px'}>
166
<div style={marginBottom: '-10px', wordWrap:'break-word'}>
167
<Markdown value={value}/>
168
</div>
169
<div className="small">
170
{text}
171
<TimeAgo date={new Date(objects.date)} />
172
{' by ' + author}
173
</div>
174
</Well>
175
176
### ChatLog Methods ###
177
178
exports.get_user_name = get_user_name = (account_id, user_map) ->
179
account = user_map?.get(account_id)
180
if account?
181
account_name = account.get('first_name') + ' ' + account.get('last_name')
182
else
183
account_name = "Unknown"
184
185
### ChatRoom Methods ###
186
exports.send_chat = send_chat = (e, log_container, mesg, actions) ->
187
scroll_to_bottom(log_container, actions)
188
e.preventDefault()
189
# block sending empty messages
190
if mesg.length? and mesg.trim().length >= 1
191
actions.send_chat(mesg)
192
clear_input(actions)
193
194
exports.clear_input = clear_input = (actions) ->
195
actions.set_input('')
196
197
exports.is_at_bottom = is_at_bottom = (saved_position, offset, height) ->
198
# 20 for covering margin of bottom message
199
saved_position + offset + 20 > height
200
201
exports.scroll_to_bottom = scroll_to_bottom = (log_container, actions) ->
202
if log_container?
203
node = ReactDOM.findDOMNode(log_container)
204
node.scrollTop = node.scrollHeight
205
actions.save_scroll_state(node.scrollTop, node.scrollHeight, node.offsetHeight)
206
actions.set_use_saved_position(false)
207
208
exports.scroll_to_position = scroll_to_position = (log_container, saved_position, offset, height, use_saved_position, actions) ->
209
if log_container?
210
actions.set_use_saved_position(not is_at_bottom(saved_position, offset, height))
211
node = ReactDOM.findDOMNode(log_container)
212
if use_saved_position
213
node.scrollTop = saved_position
214
else
215
scroll_to_bottom(log_container, actions)
216
217
218