2011-08-11 16:26:41 +02:00
/ * *
* Copyright 2009 Google Inc . , 2011 Peter 'Pita' Martischka ( Primary Technology Ltd )
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS-IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2020-11-23 19:24:19 +01:00
const padutils = require ( './pad_utils' ) . padutils ;
const padcookie = require ( './pad_cookie' ) . padcookie ;
const Tinycon = require ( 'tinycon/tinycon' ) ;
const hooks = require ( './pluginfw/hooks' ) ;
const padeditor = require ( './pad_editor' ) . padeditor ;
var chat = ( function ( ) {
let isStuck = false ;
let userAndChat = false ;
const gotInitialMessages = false ;
const historyPointer = 0 ;
let chatMentions = 0 ;
2011-07-14 17:15:38 +02:00
var self = {
2020-11-23 19:24:19 +01:00
show ( ) {
$ ( '#chaticon' ) . removeClass ( 'visible' ) ;
$ ( '#chatbox' ) . addClass ( 'visible' ) ;
2020-04-18 11:19:50 +02:00
self . scrollDown ( true ) ;
2011-11-27 21:39:47 +01:00
chatMentions = 0 ;
2012-07-12 20:18:33 +02:00
Tinycon . setBubble ( 0 ) ;
2020-11-23 19:24:19 +01:00
$ ( '.chat-gritter-msg' ) . each ( function ( ) {
2020-04-18 11:19:50 +02:00
$ . gritter . remove ( this . id ) ;
} ) ;
2011-07-14 17:15:38 +02:00
} ,
2020-11-23 19:24:19 +01:00
focus ( ) {
setTimeout ( ( ) => {
$ ( '#chatinput' ) . focus ( ) ;
} , 100 ) ;
2015-03-26 17:44:22 +01:00
} ,
2020-11-23 19:24:19 +01:00
stickToScreen ( fromInitialCall ) { // Make chat stick to right hand side of screen
if ( pad . settings . hideChat ) {
2020-05-28 16:18:13 +02:00
return ;
}
2012-01-25 20:03:25 +01:00
chat . show ( ) ;
2020-04-02 14:36:49 +02:00
isStuck = ( ! isStuck || fromInitialCall ) ;
2020-04-08 18:14:20 +02:00
$ ( '#chatbox' ) . hide ( ) ;
// Add timeout to disable the chatbox animations
2020-11-23 19:24:19 +01:00
setTimeout ( ( ) => {
$ ( '#chatbox, .sticky-container' ) . toggleClass ( 'stickyChat' , isStuck ) ;
2020-04-08 18:14:20 +02:00
$ ( '#chatbox' ) . css ( 'display' , 'flex' ) ;
} , 0 ) ;
2020-04-02 14:36:49 +02:00
2020-11-23 19:24:19 +01:00
padcookie . setPref ( 'chatAlwaysVisible' , isStuck ) ;
2020-04-02 14:36:49 +02:00
$ ( '#options-stickychat' ) . prop ( 'checked' , isStuck ) ;
2012-01-26 17:22:44 +01:00
} ,
2020-11-23 19:24:19 +01:00
chatAndUsers ( fromInitialCall ) {
const toEnable = $ ( '#options-chatandusers' ) . is ( ':checked' ) ;
if ( toEnable || ! userAndChat || fromInitialCall ) {
2015-01-19 02:45:49 +01:00
chat . stickToScreen ( true ) ;
2020-11-23 19:24:19 +01:00
$ ( '#options-stickychat' ) . prop ( 'checked' , true ) ;
$ ( '#options-chatandusers' ) . prop ( 'checked' , true ) ;
$ ( '#options-stickychat' ) . prop ( 'disabled' , 'disabled' ) ;
2015-02-09 20:11:35 +01:00
userAndChat = true ;
2020-11-23 19:24:19 +01:00
} else {
$ ( '#options-stickychat' ) . prop ( 'disabled' , false ) ;
2020-04-02 14:36:49 +02:00
userAndChat = false ;
2015-01-19 02:45:49 +01:00
}
2020-11-23 19:24:19 +01:00
padcookie . setPref ( 'chatAndUsers' , userAndChat ) ;
$ ( '#users, .sticky-container' ) . toggleClass ( 'chatAndUsers popup-show stickyUsers' , userAndChat ) ;
$ ( '#chatbox' ) . toggleClass ( 'chatAndUsersChat' , userAndChat ) ;
2015-01-19 02:45:49 +01:00
} ,
2020-11-23 19:24:19 +01:00
hide ( ) {
2019-04-16 00:34:29 +02:00
// decide on hide logic based on chat window being maximized or not
2013-12-15 11:02:43 +01:00
if ( $ ( '#options-stickychat' ) . prop ( 'checked' ) ) {
chat . stickToScreen ( ) ;
$ ( '#options-stickychat' ) . prop ( 'checked' , false ) ;
2020-11-23 19:24:19 +01:00
} else {
$ ( '#chatcounter' ) . text ( '0' ) ;
$ ( '#chaticon' ) . addClass ( 'visible' ) ;
$ ( '#chatbox' ) . removeClass ( 'visible' ) ;
2013-12-15 11:02:43 +01:00
}
2011-07-14 17:15:38 +02:00
} ,
2020-11-23 19:24:19 +01:00
scrollDown ( force ) {
2020-04-18 11:19:50 +02:00
if ( $ ( '#chatbox' ) . hasClass ( 'visible' ) ) {
if ( force || ! self . lastMessage || ! self . lastMessage . position ( ) || self . lastMessage . position ( ) . top < ( $ ( '#chattext' ) . outerHeight ( ) + 20 ) ) {
2015-02-08 00:23:33 +01:00
// if we use a slow animate here we can have a race condition when a users focus can not be moved away
// from the last message recieved.
2020-11-23 19:24:19 +01:00
$ ( '#chattext' ) . animate ( { scrollTop : $ ( '#chattext' ) [ 0 ] . scrollHeight } , { duration : 400 , queue : false } ) ;
2012-07-11 22:30:03 +02:00
self . lastMessage = $ ( '#chattext > p' ) . eq ( - 1 ) ;
2012-07-11 18:42:59 +02:00
}
}
2019-04-16 00:34:29 +02:00
} ,
2020-11-23 19:24:19 +01:00
send ( ) {
const text = $ ( '#chatinput' ) . val ( ) ;
if ( text . replace ( /\s+/ , '' ) . length == 0 ) return ;
this . _pad . collabClient . sendMessage ( { type : 'CHAT_MESSAGE' , text } ) ;
$ ( '#chatinput' ) . val ( '' ) ;
2011-07-14 17:15:38 +02:00
} ,
2020-11-23 19:24:19 +01:00
addMessage ( msg , increment , isHistoryAdd ) {
// correct the time
2012-01-16 06:05:19 +01:00
msg . time += this . _pad . clientTimeOffset ;
2019-04-16 00:34:29 +02:00
2020-11-23 19:24:19 +01:00
// create the time string
let minutes = ` ${ new Date ( msg . time ) . getMinutes ( ) } ` ;
let hours = ` ${ new Date ( msg . time ) . getHours ( ) } ` ;
if ( minutes . length == 1 ) minutes = ` 0 ${ minutes } ` ;
if ( hours . length == 1 ) hours = ` 0 ${ hours } ` ;
const timeStr = ` ${ hours } : ${ minutes } ` ;
2019-04-16 00:34:29 +02:00
2020-11-23 19:24:19 +01:00
// create the authorclass
2020-03-29 20:41:28 +02:00
if ( ! msg . userId ) {
/ *
* If , for a bug or a database corruption , the message coming from the
* server does not contain the userId field ( see for example # 3731 ) ,
* let ' s be defensive and replace it with "unknown" .
* /
2020-11-23 19:24:19 +01:00
msg . userId = 'unknown' ;
2020-03-29 20:41:28 +02:00
console . warn ( 'The "userId" field of a chat message coming from the server was not present. Replacing with "unknown". This may be a bug or a database corruption.' ) ;
}
2020-11-23 19:24:19 +01:00
const authorClass = ` author- ${ msg . userId . replace ( /[^a-y0-9]/g , ( c ) => {
if ( c == '.' ) return '-' ;
return ` z ${ c . charCodeAt ( 0 ) } z ` ;
} ) } ` ;
2011-07-14 17:15:38 +02:00
2020-11-23 19:24:19 +01:00
const text = padutils . escapeHtmlWithClickableLinks ( msg . text , '_blank' ) ;
2011-11-27 17:35:20 +01:00
2020-11-23 19:24:19 +01:00
const authorName = msg . userName == null ? _ ( 'pad.userlist.unnamed' ) : padutils . escapeHtml ( msg . userName ) ;
2013-03-19 20:21:27 +01:00
// the hook args
2020-11-23 19:24:19 +01:00
const ctx = {
authorName ,
author : msg . userId ,
text ,
sticky : false ,
timestamp : msg . time ,
timeStr ,
duration : 4000 ,
} ;
2011-11-27 17:35:20 +01:00
2013-03-19 20:21:27 +01:00
// is the users focus already in the chatbox?
2020-11-23 19:24:19 +01:00
const alreadyFocused = $ ( '#chatinput' ) . is ( ':focus' ) ;
2013-01-29 02:55:36 +01:00
2013-03-19 20:21:27 +01:00
// does the user already have the chatbox open?
2020-11-23 19:24:19 +01:00
const chatOpen = $ ( '#chatbox' ) . hasClass ( 'visible' ) ;
2013-01-29 02:55:36 +01:00
2013-03-19 20:21:27 +01:00
// does this message contain this user's name? (is the curretn user mentioned?)
2020-11-23 19:24:19 +01:00
const myName = $ ( '#myusernameedit' ) . val ( ) ;
const wasMentioned = ( text . toLowerCase ( ) . indexOf ( myName . toLowerCase ( ) ) !== - 1 && myName != 'undefined' ) ;
2013-03-19 20:21:27 +01:00
2020-11-23 19:24:19 +01:00
if ( wasMentioned && ! alreadyFocused && ! isHistoryAdd && ! chatOpen ) { // If the user was mentioned, make the message sticky
2013-03-19 20:21:27 +01:00
chatMentions ++ ;
Tinycon . setBubble ( chatMentions ) ;
ctx . sticky = true ;
}
// Call chat message hook
2020-11-23 19:24:19 +01:00
hooks . aCallAll ( 'chatNewMessage' , ctx , ( ) => {
const html = ` <p data-authorId=' ${ msg . userId } ' class=' ${ authorClass } '><b> ${ authorName } :</b><span class='time ${ authorClass } '> ${ ctx . timeStr } </span> ${ ctx . text } </p> ` ;
if ( isHistoryAdd ) $ ( html ) . insertAfter ( '#chatloadmessagesbutton' ) ;
else $ ( '#chattext' ) . append ( html ) ;
2013-03-19 20:21:27 +01:00
2020-11-23 19:24:19 +01:00
// should we increment the counter??
if ( increment && ! isHistoryAdd ) {
2013-03-19 20:21:27 +01:00
// Update the counter of unread messages
2020-11-23 19:24:19 +01:00
let count = Number ( $ ( '#chatcounter' ) . text ( ) ) ;
2013-03-19 20:21:27 +01:00
count ++ ;
2020-11-23 19:24:19 +01:00
$ ( '#chatcounter' ) . text ( count ) ;
2013-03-19 20:21:27 +01:00
2020-11-23 19:24:19 +01:00
if ( ! chatOpen && ctx . duration > 0 ) {
2013-01-29 02:55:36 +01:00
$ . gritter . add ( {
2020-10-20 02:48:53 +02:00
// Note: ctx.authorName and ctx.text are already HTML-escaped.
text : $ ( '<p>' )
. append ( $ ( '<span>' ) . addClass ( 'author-name' ) . html ( ctx . authorName ) )
. append ( ctx . text ) ,
2013-03-19 20:21:27 +01:00
sticky : ctx . sticky ,
2020-04-18 11:19:50 +02:00
time : 5000 ,
position : 'bottom' ,
2020-11-23 19:24:19 +01:00
class _name : 'chat-gritter-msg' ,
2013-01-29 02:55:36 +01:00
} ) ;
}
2011-11-27 17:35:20 +01:00
}
2013-03-19 20:21:27 +01:00
} ) ;
// Clear the chat mentions when the user clicks on the chat input box
2020-11-23 19:24:19 +01:00
$ ( '#chatinput' ) . click ( ( ) => {
2012-06-12 22:52:22 +02:00
chatMentions = 0 ;
2012-07-12 20:18:33 +02:00
Tinycon . setBubble ( 0 ) ;
2012-06-12 22:52:22 +02:00
} ) ;
2020-11-23 19:24:19 +01:00
if ( ! isHistoryAdd ) self . scrollDown ( ) ;
2011-07-14 17:15:38 +02:00
} ,
2020-11-23 19:24:19 +01:00
init ( pad ) {
2012-01-16 06:05:19 +01:00
this . _pad = pad ;
2020-11-23 19:24:19 +01:00
$ ( '#chatinput' ) . on ( 'keydown' , ( evt ) => {
2015-03-26 17:58:13 +01:00
// If the event is Alt C or Escape & we're already in the chat menu
// Send the users focus back to the pad
2020-11-23 19:24:19 +01:00
if ( ( evt . altKey == true && evt . which === 67 ) || evt . which === 27 ) {
2015-03-26 17:58:13 +01:00
// If we're in chat already..
$ ( ':focus' ) . blur ( ) ; // required to do not try to remove!
padeditor . ace . focus ( ) ; // Sends focus back to pad
2015-05-06 01:32:36 +02:00
evt . preventDefault ( ) ;
return false ;
2015-03-26 17:58:13 +01:00
}
} ) ;
2020-11-23 19:24:19 +01:00
$ ( 'body:not(#chatinput)' ) . on ( 'keypress' , function ( evt ) {
if ( evt . altKey && evt . which == 67 ) {
2015-03-31 14:45:11 +02:00
// Alt c focuses on the Chat window
$ ( this ) . blur ( ) ;
2015-05-06 01:32:36 +02:00
chat . show ( ) ;
2020-11-23 19:24:19 +01:00
$ ( '#chatinput' ) . focus ( ) ;
2015-03-31 14:45:11 +02:00
evt . preventDefault ( ) ;
}
} ) ;
2020-11-23 19:24:19 +01:00
$ ( '#chatinput' ) . keypress ( ( evt ) => {
// if the user typed enter, fire the send
if ( evt . which == 13 || evt . which == 10 ) {
2011-07-14 17:15:38 +02:00
evt . preventDefault ( ) ;
self . send ( ) ;
}
} ) ;
2013-12-05 08:41:29 +01:00
// initial messages are loaded in pad.js' _afterHandshake
2020-11-23 19:24:19 +01:00
$ ( '#chatcounter' ) . text ( 0 ) ;
$ ( '#chatloadmessagesbutton' ) . click ( ( ) => {
const start = Math . max ( self . historyPointer - 20 , 0 ) ;
const end = self . historyPointer ;
2013-01-07 17:43:03 +01:00
2020-11-23 19:24:19 +01:00
if ( start == end ) // nothing to load
{ return ; }
2013-01-07 19:15:55 +01:00
2020-11-23 19:24:19 +01:00
$ ( '#chatloadmessagesbutton' ) . css ( 'display' , 'none' ) ;
$ ( '#chatloadmessagesball' ) . css ( 'display' , 'block' ) ;
2013-01-07 17:36:03 +01:00
2020-11-23 19:24:19 +01:00
pad . collabClient . sendMessage ( { type : 'GET_CHAT_MESSAGES' , start , end } ) ;
2013-01-07 17:36:03 +01:00
self . historyPointer = start ;
2013-12-05 08:41:29 +01:00
} ) ;
2020-11-23 19:24:19 +01:00
} ,
} ;
2011-07-14 17:15:38 +02:00
return self ;
2011-12-04 16:33:56 +01:00
} ( ) ) ;
2012-01-16 02:23:48 +01:00
exports . chat = chat ;