Add emoji to chat (#1013)
* add emoji picker unstyled * rename junto.css.erb => junto.scss.erb * junto scss-ified * add emoji mart css * emoji are replaced in the text area * remove unicode emoji from messages before sending to db * add emoji back into messages on display * bigger font size on chat messages * tweak styling * codeclimate (eslint)
This commit is contained in:
parent
8e50efb3c1
commit
cb95e027c4
8 changed files with 759 additions and 348 deletions
263
app/assets/stylesheets/emoji-mart-0.3.5.css
Normal file
263
app/assets/stylesheets/emoji-mart-0.3.5.css
Normal file
|
@ -0,0 +1,263 @@
|
|||
.emoji-mart,
|
||||
.emoji-mart * {
|
||||
box-sizing: border-box;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.emoji-mart {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
color: #222427;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.emoji-mart .emoji-mart-emoji {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.emoji-mart-bar:first-child {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
.emoji-mart-bar:last-child {
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.emoji-mart-anchors {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 6px;
|
||||
color: #858585;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 12px 4px;
|
||||
overflow: hidden;
|
||||
transition: color .1s ease-out;
|
||||
}
|
||||
.emoji-mart-anchor:hover,
|
||||
.emoji-mart-anchor-selected {
|
||||
color: #464646;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor-selected .emoji-mart-anchor-bar {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor-bar {
|
||||
position: absolute;
|
||||
bottom: -3px; left: 0;
|
||||
width: 100%; height: 3px;
|
||||
background-color: #464646;
|
||||
}
|
||||
|
||||
.emoji-mart-anchors i {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
max-width: 22px;
|
||||
}
|
||||
|
||||
.emoji-mart-anchors svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.emoji-mart-scroll {
|
||||
overflow-y: scroll;
|
||||
height: 270px;
|
||||
padding: 0 6px 6px 6px;
|
||||
border: solid #d9d9d9;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
|
||||
.emoji-mart-search {
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: .2em .6em;
|
||||
margin-top: 6px;
|
||||
border-radius: 25px;
|
||||
border: 1px solid #d9d9d9;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-category .emoji-mart-emoji span {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.emoji-mart-category .emoji-mart-emoji:hover:before {
|
||||
z-index: 0;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background-color: #f4f4f4;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.emoji-mart-category-label {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-category-label span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
padding: 5px 6px;
|
||||
background-color: #fff;
|
||||
background-color: rgba(255, 255, 255, .95);
|
||||
}
|
||||
|
||||
.emoji-mart-emoji {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-no-results {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding-top: 70px;
|
||||
color: #858585;
|
||||
}
|
||||
.emoji-mart-no-results span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.emoji-mart-preview {
|
||||
position: relative;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.emoji-mart-preview-emoji,
|
||||
.emoji-mart-preview-data,
|
||||
.emoji-mart-preview-skins {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.emoji-mart-preview-emoji {
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.emoji-mart-preview-data {
|
||||
left: 68px; right: 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.emoji-mart-preview-skins {
|
||||
right: 30px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.emoji-mart-preview-name {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.emoji-mart-preview-shortname {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.emoji-mart-preview-shortname + .emoji-mart-preview-shortname,
|
||||
.emoji-mart-preview-shortname + .emoji-mart-preview-emoticon,
|
||||
.emoji-mart-preview-emoticon + .emoji-mart-preview-emoticon {
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.emoji-mart-preview-emoticon {
|
||||
font-size: 11px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.emoji-mart-title span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.emoji-mart-title .emoji-mart-emoji {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-title-label {
|
||||
color: #999A9C;
|
||||
font-size: 26px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.emoji-mart-skin-swatches {
|
||||
font-size: 0;
|
||||
padding: 2px 0;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch {
|
||||
width: 16px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch-selected:after {
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.emoji-mart-skin-swatch {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
vertical-align: middle;
|
||||
transition-property: width, padding;
|
||||
transition-duration: .125s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.emoji-mart-skin-swatch:nth-child(1) { transition-delay: 0 }
|
||||
.emoji-mart-skin-swatch:nth-child(2) { transition-delay: .03s }
|
||||
.emoji-mart-skin-swatch:nth-child(3) { transition-delay: .06s }
|
||||
.emoji-mart-skin-swatch:nth-child(4) { transition-delay: .09s }
|
||||
.emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s }
|
||||
.emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s }
|
||||
|
||||
.emoji-mart-skin-swatch-selected {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.emoji-mart-skin-swatch-selected:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
width: 4px; height: 4px;
|
||||
margin: -2px 0 0 -2px;
|
||||
background-color: #fff;
|
||||
border-radius: 100%;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease-out;
|
||||
}
|
||||
|
||||
.emoji-mart-skin {
|
||||
display: inline-block;
|
||||
width: 100%; padding-top: 100%;
|
||||
max-width: 12px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.emoji-mart-skin-tone-1 { background-color: #ffc93a }
|
||||
.emoji-mart-skin-tone-2 { background-color: #fadcbc }
|
||||
.emoji-mart-skin-tone-3 { background-color: #e0bb95 }
|
||||
.emoji-mart-skin-tone-4 { background-color: #bf8f68 }
|
||||
.emoji-mart-skin-tone-5 { background-color: #9b643d }
|
||||
.emoji-mart-skin-tone-6 { background-color: #594539 }
|
|
@ -1,328 +0,0 @@
|
|||
.collaborator-video {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
cursor: default;
|
||||
color: #FFF;
|
||||
}
|
||||
.collaborator-video .video-receive {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
padding: 20px 20px 20px 170px;
|
||||
background: #424242;
|
||||
height: 110px;
|
||||
border-top-left-radius: 75px;
|
||||
border-bottom-left-radius: 75px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
.collaborator-video .video-receive .video-statement {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.collaborator-video .video-receive .btn-group .btn-yes {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.collaborator-video .video-receive .btn-group .btn-no {
|
||||
background-color: #c04f4f;
|
||||
}
|
||||
.collaborator-video .video-receive .btn-group .btn-no:hover {
|
||||
background-color: #A54242;
|
||||
}
|
||||
.collaborator-video .video-cutoff {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
border-radius: 75px;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
-webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
-moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.collaborator-video .video-cutoff video {
|
||||
height: 150px;
|
||||
margin-left: -25px;
|
||||
}
|
||||
.collaborator-video .video-cutoff .collaborator-video-avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
display: none;
|
||||
}
|
||||
.collaborator-video .video-audio {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 85%;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.collaborator-video .video-audio:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.collaborator-video .video-audio.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
.collaborator-video .video-video {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 85%;
|
||||
left: 0px;
|
||||
cursor: pointer;
|
||||
background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.collaborator-video .video-video:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.collaborator-video .video-video.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
.collaborator-video.my-video {
|
||||
left: 30px;
|
||||
top: 72px;
|
||||
}
|
||||
#chat-box-wrapper {
|
||||
height: 100%;
|
||||
float: right;
|
||||
}
|
||||
.chat-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background: #424242;
|
||||
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
||||
}
|
||||
.chat-box .chat-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -36px;
|
||||
width: 36px;
|
||||
height: 49px;
|
||||
background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
.chat-box .chat-button.active {
|
||||
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
||||
}
|
||||
.chat-box .chat-button .chat-unread {
|
||||
background: #DAB539;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -11px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 11px;
|
||||
border: 2px solid #424242;
|
||||
color: #424242;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
.chat-box .junto-header {
|
||||
width: 276px;
|
||||
padding: 16px 8px 16px 16px;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
background-color: #000000;
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
||||
}
|
||||
.chat-box .junto-header .cursor-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 8px;
|
||||
margin-top: -8px;
|
||||
float: right;
|
||||
background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.chat-box .junto-header .cursor-toggle:hover {
|
||||
background-position-x: -32px;
|
||||
}
|
||||
.chat-box .junto-header .cursor-toggle.active {
|
||||
background-position-y: -32px;
|
||||
}
|
||||
.chat-box .junto-header .video-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 10px;
|
||||
margin-top: -8px;
|
||||
float: right;
|
||||
background: url(<%= asset_path 'video_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.chat-box .junto-header .video-toggle:hover {
|
||||
background-position-x: -32px;
|
||||
}
|
||||
.chat-box .junto-header .video-toggle.active {
|
||||
background-position-y: -32px;
|
||||
}
|
||||
.chat-box .participants {
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
padding: 16px 0px 16px 0px;
|
||||
text-align: left;
|
||||
color: #f5f5f5;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.chat-box .participants .conversation-live {
|
||||
padding: 5px 10px 5px 10px;
|
||||
background: #c04f4f;
|
||||
margin: 5px 10px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.chat-box .participants .conversation-live .call-action {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
color: #EBFF00;
|
||||
}
|
||||
.chat-box .participants .participant {
|
||||
width: 89%;
|
||||
padding: 8px 8px 2px 8px;
|
||||
color: #f5f5f5;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-image {
|
||||
width: 15%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
color: #BBB;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-image img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-name {
|
||||
width: 53%;
|
||||
float: left;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin-top: 12px;
|
||||
padding: 2px 8px 0;
|
||||
text-align: left;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-invite-call,
|
||||
.chat-box .participants .participant .chat-participant-invite-join
|
||||
{
|
||||
float: right;
|
||||
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-invite-call.pending,
|
||||
.chat-box .participants .participant .chat-participant-invite-join.pending {
|
||||
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-participating {
|
||||
float: right;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-participating .green-dot {
|
||||
background: #4fc059;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.chat-box .chat-header {
|
||||
width: 276px;
|
||||
padding: 16px 8px 16px 16px;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
background-color: #000000;
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
||||
}
|
||||
.chat-box .chat-header .sound-toggle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 10px;
|
||||
margin-top: -2px;
|
||||
float: right;
|
||||
background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.chat-box .chat-header .sound-toggle:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.chat-box .chat-header .sound-toggle.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
.chat-box .chat-input {
|
||||
min-height: 80px;
|
||||
width: 94%;
|
||||
padding: 8px 3% 8px 3%;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
}
|
||||
.chat-box .chat-messages {
|
||||
width: 100%;
|
||||
padding: 16px 0px;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message {
|
||||
width: 89%;
|
||||
padding: 8px 8px 2px 8px;
|
||||
color: #f5f5f5;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message a:link {
|
||||
color: #4fb5c0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message a:visited {
|
||||
color: #aea9fd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message a:hover {
|
||||
color: #dab539;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-user {
|
||||
width: 12%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
color: #BBB;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-user img {
|
||||
border: 2px solid #424242;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-meta {
|
||||
padding: 0 8px;
|
||||
float: left;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-username {
|
||||
color: #4fc059;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-text {
|
||||
width: 80%;
|
||||
float: left;
|
||||
padding: 2px 8px 0;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-time {
|
||||
font-size: 10px;
|
||||
color: #757575;
|
||||
}
|
370
app/assets/stylesheets/junto.scss.erb
Normal file
370
app/assets/stylesheets/junto.scss.erb
Normal file
|
@ -0,0 +1,370 @@
|
|||
.collaborator-video {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
cursor: default;
|
||||
color: #FFF;
|
||||
|
||||
.video-receive {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
padding: 20px 20px 20px 170px;
|
||||
background: #424242;
|
||||
height: 110px;
|
||||
border-top-left-radius: 75px;
|
||||
border-bottom-left-radius: 75px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
||||
.video-statement {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.btn-group {
|
||||
.btn-yes {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.btn-no {
|
||||
background-color: #c04f4f;
|
||||
&:hover {
|
||||
background-color: #A54242;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-cutoff {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
border-radius: 75px;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
-webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
-moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
|
||||
video {
|
||||
height: 150px;
|
||||
margin-left: -25px;
|
||||
}
|
||||
.collaborator-video-avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.video-audio {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 85%;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.video-audio:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.video-audio.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
.video-video {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 85%;
|
||||
left: 0px;
|
||||
cursor: pointer;
|
||||
background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.video-video:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.video-video.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
}
|
||||
|
||||
.collaborator-video.my-video {
|
||||
left: 30px;
|
||||
top: 72px;
|
||||
}
|
||||
|
||||
#chat-box-wrapper {
|
||||
height: 100%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background: #424242;
|
||||
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
||||
|
||||
.chat-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -36px;
|
||||
width: 36px;
|
||||
height: 49px;
|
||||
background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
||||
}
|
||||
.chat-unread {
|
||||
background: #DAB539;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -11px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 11px;
|
||||
border: 2px solid #424242;
|
||||
color: #424242;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.junto-header {
|
||||
width: 276px;
|
||||
padding: 16px 8px 16px 16px;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
background-color: #000000;
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
||||
|
||||
.cursor-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 8px;
|
||||
margin-top: -8px;
|
||||
float: right;
|
||||
background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.cursor-toggle:hover {
|
||||
background-position-x: -32px;
|
||||
}
|
||||
.cursor-toggle.active {
|
||||
background-position-y: -32px;
|
||||
}
|
||||
.video-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 10px;
|
||||
margin-top: -8px;
|
||||
float: right;
|
||||
background: url(<%= asset_path 'video_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.video-toggle:hover {
|
||||
background-position-x: -32px;
|
||||
}
|
||||
.video-toggle.active {
|
||||
background-position-y: -32px;
|
||||
}
|
||||
}
|
||||
|
||||
.participants {
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
padding: 16px 0px 16px 0px;
|
||||
text-align: left;
|
||||
color: #f5f5f5;
|
||||
overflow-y: auto;
|
||||
|
||||
.conversation-live {
|
||||
padding: 5px 10px 5px 10px;
|
||||
background: #c04f4f;
|
||||
margin: 5px 10px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.conversation-live .call-action {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
color: #EBFF00;
|
||||
}
|
||||
.participant {
|
||||
width: 89%;
|
||||
padding: 8px 8px 2px 8px;
|
||||
color: #f5f5f5;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
|
||||
.chat-participant-image {
|
||||
width: 15%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
color: #BBB;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.chat-participant-image img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
.chat-participant-name {
|
||||
width: 53%;
|
||||
float: left;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin-top: 12px;
|
||||
padding: 2px 8px 0;
|
||||
text-align: left;
|
||||
}
|
||||
.chat-participant-invite-call,
|
||||
.chat-participant-invite-join
|
||||
{
|
||||
float: right;
|
||||
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
||||
}
|
||||
.chat-participant-invite-call.pending,
|
||||
.chat-participant-invite-join.pending {
|
||||
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
||||
}
|
||||
.chat-participant-participating {
|
||||
float: right;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.chat-participant-participating .green-dot {
|
||||
background: #4fc059;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
width: 276px;
|
||||
padding: 16px 8px 16px 16px;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
background-color: #000000;
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
||||
|
||||
.sound-toggle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 10px;
|
||||
margin-top: -2px;
|
||||
float: right;
|
||||
background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat;
|
||||
}
|
||||
.sound-toggle:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.sound-toggle.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
}
|
||||
|
||||
$chat_font_size: 16px;
|
||||
|
||||
.chat-input {
|
||||
min-height: 80px;
|
||||
width: 88%;
|
||||
padding: 8px 9% 8px 3%;
|
||||
font-size: $chat_font_size;
|
||||
outline: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
width: 100%;
|
||||
padding: 16px 0px;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
|
||||
.chat-message {
|
||||
width: 89%;
|
||||
padding: 8px 8px 2px 8px;
|
||||
color: #f5f5f5;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: $chat_font_size;
|
||||
line-height: $chat_font_size + 1px;
|
||||
|
||||
a:link {
|
||||
color: #4fb5c0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:visited {
|
||||
color: #aea9fd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
color: #dab539;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chat-message-user {
|
||||
width: 12%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
color: #BBB;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.chat-message-user img {
|
||||
border: 2px solid #424242;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.chat-message-meta {
|
||||
padding: 0 px;
|
||||
float: left;
|
||||
}
|
||||
.chat-message-username {
|
||||
color: #4fc059;
|
||||
}
|
||||
.chat-message-text {
|
||||
width: 80%;
|
||||
float: left;
|
||||
padding: 2px 8px 0;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.chat-message-time {
|
||||
font-size: 10px;
|
||||
color: #757575;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-message-area {
|
||||
position: relative;
|
||||
|
||||
.extra-message-options {
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 74px;
|
||||
|
||||
.emoji-picker-button {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,14 @@
|
|||
/* global $ */
|
||||
|
||||
import { Parser, HtmlRenderer } from 'commonmark'
|
||||
import { emojiIndex } from 'emoji-mart'
|
||||
import { escapeRegExp } from 'lodash'
|
||||
|
||||
const emojiToShortcodes = {}
|
||||
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||
const emoji = emojiIndex.emojis[key]
|
||||
emojiToShortcodes[emoji.native] = emoji.colons
|
||||
})
|
||||
|
||||
const Util = {
|
||||
// helper function to determine how many lines are needed
|
||||
|
@ -150,6 +158,29 @@ const Util = {
|
|||
canvas.scale(oldAttr.scaleX, oldAttr.scaleY)
|
||||
const newAttr = Util.logCanvasAttributes(canvas)
|
||||
canvas.translate(newAttr.centreCoords.x - oldAttr.centreCoords.x, newAttr.centreCoords.y - oldAttr.centreCoords.y)
|
||||
},
|
||||
removeEmoji: function(withEmoji) {
|
||||
let text = withEmoji
|
||||
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||
const emoji = emojiIndex.emojis[key]
|
||||
text = text.replace(new RegExp(escapeRegExp(emoji.native), 'g'), emoji.colons)
|
||||
})
|
||||
return text
|
||||
},
|
||||
addEmoji: function(withoutEmoji, opts = { emoticons: false }) {
|
||||
let text = withoutEmoji
|
||||
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||
const emoji = emojiIndex.emojis[key]
|
||||
text = text.replace(new RegExp(escapeRegExp(emoji.colons), 'g'), emoji.native)
|
||||
})
|
||||
if (opts.emoticons) {
|
||||
Object.keys(emojiIndex.emoticons).forEach(emoticon => {
|
||||
const key = emojiIndex.emoticons[emoticon]
|
||||
const emoji = emojiIndex.emojis[key]
|
||||
text = text.replace(new RegExp(escapeRegExp(emoticon), 'g'), emoji.native)
|
||||
})
|
||||
}
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import Autolinker from 'autolinker'
|
||||
import Util from '../../Metamaps/Util'
|
||||
|
||||
const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })
|
||||
|
||||
|
@ -19,7 +20,8 @@ function formatDate(created_at) {
|
|||
|
||||
const Message = props => {
|
||||
const { user_image, user_name, message, created_at, heading } = props
|
||||
const messageHtml = {__html: linker.link(message)}
|
||||
const messageHtml = {__html: linker.link(Util.addEmoji(message, { emoticons: false }))}
|
||||
|
||||
return (
|
||||
<div className="chat-message">
|
||||
<div className="chat-message-user">
|
||||
|
|
68
frontend/src/components/MapChat/NewMessage.js
Normal file
68
frontend/src/components/MapChat/NewMessage.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
import React, { PropTypes, Component } from 'react'
|
||||
import { Picker } from 'emoji-mart'
|
||||
import Util from '../../Metamaps/Util'
|
||||
|
||||
class NewMessage extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
showEmojiPicker: false
|
||||
}
|
||||
}
|
||||
|
||||
toggleEmojiPicker = () => {
|
||||
this.setState({ showEmojiPicker: !this.state.showEmojiPicker })
|
||||
}
|
||||
|
||||
textAreaValue = () => {
|
||||
return Util.addEmoji(this.props.messageText)
|
||||
}
|
||||
|
||||
handleClick = (emoji, event) => {
|
||||
const { messageText } = this.props
|
||||
this.props.handleChange({ target: {
|
||||
value: messageText + emoji.colons
|
||||
}})
|
||||
|
||||
this.setState({ showEmojiPicker: false })
|
||||
}
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<div className="new-message-area">
|
||||
<Picker set="emojione"
|
||||
onClick={this.handleClick}
|
||||
style={{
|
||||
display: this.state.showEmojiPicker ? 'block' : 'none',
|
||||
maxWidth: '100%'
|
||||
}}
|
||||
emoji="upside_down_face"
|
||||
title="Emoji"
|
||||
/>
|
||||
<div className="extra-message-options">
|
||||
<span className="emoji-picker-button" onClick={this.toggleEmojiPicker}>🙃</span>
|
||||
</div>
|
||||
<textarea value={this.textAreaValue()}
|
||||
onChange={this.props.handleChange}
|
||||
{...this.props.textAreaProps}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
NewMessage.propTypes = {
|
||||
messageText: PropTypes.string,
|
||||
handleChange: PropTypes.func,
|
||||
textAreaProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
ref: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
onKeyUp: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func
|
||||
})
|
||||
}
|
||||
|
||||
export default NewMessage
|
|
@ -2,6 +2,8 @@ import React, { PropTypes, Component } from 'react'
|
|||
import Unread from './Unread'
|
||||
import Participant from './Participant'
|
||||
import Message from './Message'
|
||||
import NewMessage from './NewMessage'
|
||||
import Util from '../../Metamaps/Util'
|
||||
|
||||
function makeList(messages) {
|
||||
let currentHeader
|
||||
|
@ -29,7 +31,7 @@ class MapChat extends Component {
|
|||
messageText: '',
|
||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||
cursorsShowing: true,
|
||||
videosShowing: true
|
||||
videosShowing: true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +42,7 @@ class MapChat extends Component {
|
|||
messageText: '',
|
||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||
cursorsShowing: true,
|
||||
videosShowing: true
|
||||
videosShowing: true
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -94,7 +96,7 @@ class MapChat extends Component {
|
|||
handleTextareaKeyUp = e => {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault()
|
||||
const text = this.state.messageText
|
||||
const text = Util.removeEmoji(this.state.messageText)
|
||||
this.props.handleInputMessage(text)
|
||||
this.setState({ messageText: '' })
|
||||
}
|
||||
|
@ -116,12 +118,12 @@ class MapChat extends Component {
|
|||
<div className="participants">
|
||||
{conversationLive && <div className="conversation-live">
|
||||
LIVE
|
||||
{isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
|
||||
LEAVE
|
||||
</span>}
|
||||
{!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
|
||||
JOIN
|
||||
</span>}
|
||||
{isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
|
||||
LEAVE
|
||||
</span>}
|
||||
{!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
|
||||
JOIN
|
||||
</span>}
|
||||
</div>}
|
||||
{participants.map(participant => <Participant
|
||||
key={participant.id}
|
||||
|
@ -140,17 +142,19 @@ class MapChat extends Component {
|
|||
<div className="tooltips">Chat</div>
|
||||
<Unread count={unreadMessages} />
|
||||
</div>
|
||||
<div className="chat-messages" ref={div => this.messagesDiv = div}>
|
||||
<div className="chat-messages" ref={div => { this.messagesDiv = div }}>
|
||||
{makeList(messages)}
|
||||
</div>
|
||||
<textarea className="chat-input"
|
||||
ref={textarea => this.messageInput = textarea}
|
||||
placeholder="Send a message..."
|
||||
value={this.state.messageText}
|
||||
onChange={this.handleChange('messageText')}
|
||||
onKeyUp={this.handleTextareaKeyUp}
|
||||
onFocus={this.props.inputFocus}
|
||||
onBlur={this.props.inputBlur}
|
||||
<NewMessage textAreaProps={{
|
||||
className: 'chat-input',
|
||||
ref: textarea => { this.messageInput = textarea },
|
||||
placeholder: 'Send a message...',
|
||||
onKeyUp: this.handleTextareaKeyUp,
|
||||
onFocus: this.props.inputFocus,
|
||||
onBlur: this.props.inputBlur
|
||||
}}
|
||||
handleChange={this.handleChange('messageText')}
|
||||
messageText={this.state.messageText}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -168,7 +172,7 @@ MapChat.propTypes = {
|
|||
inviteToJoin: PropTypes.func,
|
||||
videoToggleClick: PropTypes.func,
|
||||
cursorToggleClick: PropTypes.func,
|
||||
soundToggleClick: PropTypes.func,
|
||||
soundToggleClick: PropTypes.func,
|
||||
participants: PropTypes.arrayOf(PropTypes.shape({
|
||||
color: PropTypes.string, // css color
|
||||
id: PropTypes.number,
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"clipboard-js": "0.3.2",
|
||||
"commonmark": "0.27.0",
|
||||
"csv-parse": "1.1.7",
|
||||
"emoji-mart": "^0.3.5",
|
||||
"getScreenMedia": "git://github.com/devvmh/getScreenMedia#patch-1",
|
||||
"hark": "git://github.com/devvmh/hark#patch-1",
|
||||
"howler": "2.0.2",
|
||||
|
|
Loading…
Add table
Reference in a new issue