Resolve "Make it easier to chat in different channels" Closes #1482 See merge request Isleward/isleward!482tags/v0.8.0
@@ -1,9 +1,8 @@ | |||
@import "colors.less"; | |||
@font-face | |||
{ | |||
font-family: bitty; | |||
src: url('../fonts/bitty.ttf'); | |||
@font-face { | |||
font-family: bitty; | |||
src: url('../fonts/bitty.ttf'); | |||
} | |||
.loader-container { | |||
@@ -99,4 +98,5 @@ | |||
} | |||
} | |||
} |
@@ -148,7 +148,8 @@ define([ | |||
method: 'chat', | |||
data: { | |||
message: '{' + item.name + '}', | |||
item: item | |||
item: item, | |||
type: 'global' | |||
} | |||
}); | |||
}, | |||
@@ -0,0 +1,208 @@ | |||
define([ | |||
'html!ui/templates/messages/tplTab' | |||
], function ( | |||
tplTab | |||
) { | |||
const extensionObj = { | |||
processChat: function (msgConfig) { | |||
const { message, event: keyboardEvent } = msgConfig; | |||
const { key } = keyboardEvent; | |||
const { el, currentChannel } = this; | |||
const optionContainer = this.find('.channelOptions'); | |||
if (message.length) { | |||
if (el.hasClass('picking')) | |||
msgConfig.cancel = true; | |||
return; | |||
} | |||
if (key === 'Enter') { | |||
const selectedSubPick = optionContainer.find('.option.selected'); | |||
if (selectedSubPick.length) { | |||
this.onPickSubChannel(selectedSubPick.html(), currentChannel); | |||
return; | |||
} | |||
} | |||
//If we're busy picking a sub channel, we can use keyboard nav | |||
const isPicking = el.hasClass('picking'); | |||
const currentSelection = optionContainer.find('.option.selected'); | |||
if (isPicking && currentSelection.length) { | |||
const delta = { | |||
ArrowUp: -1, | |||
ArrowDown: 1 | |||
}[key]; | |||
if (delta) { | |||
const options = optionContainer.find('.option'); | |||
const currentIndex = currentSelection.eq(0).index(); | |||
let nextIndex = (currentIndex + delta) % options.length; | |||
currentSelection.removeClass('selected'); | |||
options.eq(nextIndex).addClass('selected'); | |||
} | |||
} | |||
const pick = { | |||
'%': 'party', | |||
'!': 'global', | |||
$: 'custom', | |||
'@': 'direct' | |||
}[key]; | |||
if (!pick) { | |||
if (isPicking) | |||
msgConfig.cancel = true; | |||
return; | |||
} | |||
if (currentChannel === pick) { | |||
if (pick === 'direct') | |||
this.lastPrivateChannel = null; | |||
else if (pick === 'custom') | |||
this.lastCustomChannel = null; | |||
} | |||
this.onPickChannel(pick, true); | |||
msgConfig.cancel = true; | |||
}, | |||
onPickChannel: function (channel, autoPickSub) { | |||
this.currentChannel = channel; | |||
this.currentSubChannel = null; | |||
const showSubChannels = ( | |||
['direct', 'custom'].includes(channel) && | |||
( | |||
!autoPickSub || | |||
( | |||
channel === 'direct' && | |||
!this.lastPrivateChannel | |||
) || | |||
( | |||
channel === 'custom' && | |||
!this.lastCustomChannel | |||
) | |||
) | |||
); | |||
if (!showSubChannels) { | |||
this.find('.channelOptions').removeClass('active'); | |||
let showValue = { | |||
direct: this.lastPrivateChannel, | |||
custom: this.lastCustomChannel | |||
}[channel]; | |||
if (channel === 'direct' || channel === 'custom') | |||
this.currentSubChannel = showValue; | |||
showValue = showValue || channel; | |||
this.find('.channelPicker').html(showValue); | |||
this.find('input').focus(); | |||
this.el.removeClass('picking'); | |||
} else | |||
this.onShowChannelOptions(channel); | |||
}, | |||
onPickSubChannel: function (subChannel, channel) { | |||
this.currentSubChannel = subChannel; | |||
this.find('.channelOptions').removeClass('active'); | |||
this.find('.channelPicker').html(subChannel); | |||
const elInput = this.find('input'); | |||
elInput.focus(); | |||
if (channel === 'custom') { | |||
if (subChannel === 'join new') { | |||
elInput.val('/join channelName'); | |||
elInput[0].setSelectionRange(6, 17); | |||
} else if (subChannel === 'leave') { | |||
elInput.val('/leave channelName'); | |||
elInput[0].setSelectionRange(7, 18); | |||
} | |||
} | |||
this.el.removeClass('picking'); | |||
}, | |||
onShowChannelOptions: function (currentPick) { | |||
const optionContainer = this.find('.channelOptions') | |||
.addClass('active') | |||
.empty(); | |||
const options = []; | |||
let handlerOnClick = this.onPickChannel; | |||
this.el.addClass('picking'); | |||
if (!currentPick) { | |||
options.push('global', 'custom'); | |||
if (this.privateChannels.length) | |||
options.push('direct'); | |||
//Hack...surely we can find a more sane way to do this | |||
if ($('.uiParty .member').length) | |||
options.push('party'); | |||
} else { | |||
handlerOnClick = this.onPickSubChannel; | |||
if (currentPick === 'direct') | |||
options.push(...this.privateChannels); | |||
else if (currentPick === 'custom') | |||
options.push(...this.customChannels, 'join new', 'leave'); | |||
} | |||
if (!options.length) { | |||
this.onPickChannel('global'); | |||
return; | |||
} | |||
let addSelectStyleTo = null; | |||
if (currentPick) | |||
addSelectStyleTo = this.currentSubChannel || options[0]; | |||
options.forEach(o => { | |||
const shortcut = { | |||
global: ' (!)', | |||
direct: ' (@)', | |||
party: ' (%)', | |||
custom: ' ($)' | |||
}[o] || ''; | |||
const html = `<div class='option' shortcut='${shortcut}'>${o}</div>`; | |||
const el = $(html) | |||
.appendTo(optionContainer) | |||
.on('click', handlerOnClick.bind(this, o, currentPick)) | |||
.on('hover', this.stopKeyboardNavForOptions.bind(this)); | |||
if (o === addSelectStyleTo) | |||
el.addClass('selected'); | |||
}); | |||
}, | |||
stopKeyboardNavForOptions: function () { | |||
this.find('.channelOptions .option.selected').removeClass('selected'); | |||
} | |||
}; | |||
return { | |||
init: function () { | |||
$.extend(this, extensionObj); | |||
//This whole hoverFilter business is a filthy hack | |||
this.find('.channelPicker, .channelOptions, .filter:not(.channel)') | |||
.on('mouseover', this.onFilterHover.bind(this, true)) | |||
.on('mouseleave', this.onFilterHover.bind(this, false)); | |||
this.find('.channelPicker').on('click', this.onShowChannelOptions.bind(this, null)); | |||
} | |||
}; | |||
}); |
@@ -3,6 +3,8 @@ define([ | |||
'html!ui/templates/messages/template', | |||
'html!ui/templates/messages/tplTab', | |||
'css!ui/templates/messages/styles', | |||
'ui/templates/messages/mobile', | |||
'ui/templates/messages/channelPicker', | |||
'js/input', | |||
'js/system/client', | |||
'js/config' | |||
@@ -11,6 +13,8 @@ define([ | |||
template, | |||
tplTab, | |||
styles, | |||
messagesMobile, | |||
channelPicker, | |||
input, | |||
client, | |||
config | |||
@@ -18,53 +22,44 @@ define([ | |||
return { | |||
tpl: template, | |||
currentFilter: 'info', | |||
messages: [], | |||
maxTtl: 500, | |||
maxChatLength: 255, | |||
hoverItem: null, | |||
hoverFilter: false, | |||
lastChannel: null, | |||
currentChannel: 'global', | |||
currentSubChannel: null, | |||
privateChannels: [], | |||
lastPrivateChannel: null, | |||
customChannels: [], | |||
lastCustomChannel: null, | |||
postRender: function () { | |||
this.onEvent('onGetMessages', this.onGetMessages.bind(this)); | |||
this.onEvent('onDoWhisper', this.onDoWhisper.bind(this)); | |||
this.onEvent('onJoinChannel', this.onJoinChannel.bind(this)); | |||
this.onEvent('onLeaveChannel', this.onLeaveChannel.bind(this)); | |||
this.onEvent('onGetCustomChatChannels', this.onGetCustomChatChannels.bind(this)); | |||
this.onEvent('onToggleLastChannel', this.onToggleLastChannel.bind(this)); | |||
this | |||
.find('.filter:not(.channel)') | |||
.on('mouseover', this.onFilterHover.bind(this, true)) | |||
.on('mouseleave', this.onFilterHover.bind(this, false)) | |||
.on('click', this.onClickFilter.bind(this)); | |||
[ | |||
'onGetMessages', | |||
'onDoWhisper', | |||
'onJoinChannel', | |||
'onLeaveChannel', | |||
'onClickFilter', | |||
'onGetCustomChatChannels', | |||
'onKeyDown', | |||
'onKeyUp' | |||
].forEach(e => this.onEvent(e, this[e].bind(this))); | |||
if (isMobile) { | |||
this.kbUpper = 0; | |||
this.find('.filter:not(.channel)').on('click', this.onClickFilter.bind(this)); | |||
this.el.on('click', this.toggle.bind(this, true)); | |||
this.renderKeyboard(); | |||
channelPicker.init.call(this); | |||
$(tplTab) | |||
.appendTo(this.find('.filters')) | |||
.addClass('btnClose') | |||
.html('x') | |||
.on('click', this.toggle.bind(this, false, true)); | |||
} else { | |||
if (isMobile) | |||
messagesMobile.init.call(this); | |||
else { | |||
this.find('input') | |||
.on('keydown', this.sendChat.bind(this)) | |||
.on('input', this.checkChatLength.bind(this)) | |||
.on('input', this.enforceMaxMsgLength.bind(this)) | |||
.on('blur', this.toggle.bind(this, false, true)); | |||
} | |||
this.onEvent('onKeyDown', this.onKeyDown.bind(this)); | |||
this.onEvent('onKeyUp', this.onKeyUp.bind(this)); | |||
}, | |||
update: function () { | |||
@@ -75,122 +70,17 @@ define([ | |||
return; | |||
const time = new Date(); | |||
const hours = time.getUTCHours().toString().padStart(2, 0); | |||
const minutes = time.getUTCMinutes().toString().padStart(2, 0); | |||
let elTime = this.find('.time'); | |||
const timeString = ( | |||
'[ ' + | |||
time.getUTCHours().toString().padStart(2, 0) + | |||
':' + | |||
time.getUTCMinutes().toString().padStart(2, 0) + | |||
' ]' | |||
); | |||
const timeString = `[ ${hours}:${minutes} ]`; | |||
if (elTime.html() !== timeString) | |||
elTime.html(timeString); | |||
}, | |||
renderKeyboard: function () { | |||
this.find('.keyboard').remove(); | |||
let container = $('<div class="keyboard"></div>') | |||
.appendTo(this.el); | |||
let keyboard = { | |||
0: 'qwertyuiop|asdfghjkl|zxcvbnm', | |||
1: 'QWERTYUIOP|ASDFGHJKL|ZXCVBNM', | |||
2: '1234567890|@#&*-+=()|_$"\';/' | |||
}[this.kbUpper].split(''); | |||
//Hacky: Insert control characters in correct positions | |||
//Backspace goes after 'm' | |||
if (this.kbUpper === 0) { | |||
keyboard.splice(keyboard.indexOf('z'), 0, 'caps'); | |||
keyboard.splice(keyboard.indexOf('m') + 1, 0, '<<'); | |||
} else if (this.kbUpper === 1) { | |||
keyboard.splice(keyboard.indexOf('Z'), 0, 'caps'); | |||
keyboard.splice(keyboard.indexOf('M') + 1, 0, '<<'); | |||
} else if (this.kbUpper === 2) | |||
keyboard.splice(keyboard.indexOf('/') + 1, 0, '<<'); | |||
keyboard.push(...['|', '123', ',', 'space', '.', 'send']); | |||
let row = 0; | |||
keyboard.forEach(k => { | |||
if (k === '|') { | |||
row++; | |||
const postGapCount = row === 4 ? 0 : row - 1; | |||
for (let i = 0; i < postGapCount; i++) | |||
$('<div class="gap" />').appendTo(container); | |||
$('<div class="newline" />').appendTo(container); | |||
const preGapCount = row === 3 ? 0 : row; | |||
for (let i = 0; i < preGapCount; i++) | |||
$('<div class="gap" />').appendTo(container); | |||
return; | |||
} | |||
let className = (k.length === 1) ? 'key' : 'key special'; | |||
if (k === ' ') { | |||
k = '.'; | |||
className = 'key hidden'; | |||
} | |||
className += ' ' + k; | |||
let elKey = $(`<div class="${className}">${k}</div>`) | |||
.appendTo(container); | |||
if (!className.includes('hidden')) | |||
elKey.on('click', this.clickKey.bind(this, k)); | |||
}); | |||
}, | |||
clickKey: function (key) { | |||
window.navigator.vibrate(20); | |||
let elInput = this.find('input'); | |||
const handler = { | |||
caps: () => { | |||
this.kbUpper = (this.kbUpper + 1) % 2; | |||
this.renderKeyboard(); | |||
}, | |||
123: () => { | |||
this.kbUpper = (this.kbUpper === 2) ? 0 : 2; | |||
this.renderKeyboard(); | |||
}, | |||
space: () => { | |||
this.clickKey(' '); | |||
}, | |||
'<<': () => { | |||
elInput.val(elInput.val().slice(0, -1)); | |||
this.find('.input').html(elInput.val()); | |||
}, | |||
send: () => { | |||
this.sendChat({ | |||
which: 13 | |||
}); | |||
this.find('.input').html(''); | |||
this.find('input').val(''); | |||
} | |||
}[key]; | |||
if (handler) { | |||
handler(); | |||
return; | |||
} | |||
elInput.val(elInput.val() + key); | |||
this.checkChatLength(); | |||
this.find('.input').html(elInput.val()); | |||
}, | |||
checkChatLength: function () { | |||
enforceMaxMsgLength: function () { | |||
let textbox = this.find('input'); | |||
let val = textbox.val(); | |||
@@ -202,13 +92,13 @@ define([ | |||
}, | |||
onGetCustomChatChannels: function (channels) { | |||
channels.forEach(function (c) { | |||
this.onJoinChannel(c); | |||
}, this); | |||
channels.forEach(c => this.onJoinChannel(c)); | |||
}, | |||
onJoinChannel: function (channel) { | |||
this.find('[filter="' + channel.trim() + '"]').remove(); | |||
this.customChannels.push(channel); | |||
this.find(`[filter="${channel.trim()}"]`).remove(); | |||
let container = this.find('.filters'); | |||
$(tplTab) | |||
@@ -222,7 +112,9 @@ define([ | |||
}, | |||
onLeaveChannel: function (channel) { | |||
this.find('.filters [filter="' + channel + '"]').remove(); | |||
this.customChannels.spliceWhere(c => c === channel); | |||
this.find(`.filters div[filter="${channel}"]`).remove(); | |||
}, | |||
onFilterHover: function (hover) { | |||
@@ -246,10 +138,9 @@ define([ | |||
}, | |||
onKeyDown: function (key) { | |||
if (key === 'enter') { | |||
if (key === 'enter') | |||
this.toggle(true); | |||
this.find('input').val(this.lastChannel || ''); | |||
} else if (key === 'shift') | |||
else if (key === 'shift') | |||
this.showItemTooltip(); | |||
}, | |||
@@ -260,11 +151,37 @@ define([ | |||
onDoWhisper: function (charName) { | |||
this.toggle(true); | |||
let toName = charName; | |||
if (charName.indexOf(' ') > -1) | |||
toName = "'" + toName + "'"; | |||
this.find('input').val('@' + toName + ' '); | |||
this.currentChannel = 'direct'; | |||
this.currentSubChannel = charName; | |||
this.find('.channelPicker').html(charName); | |||
const elInput = this.find('input') | |||
.val('message') | |||
.focus(); | |||
elInput[0].setSelectionRange(0, 7); | |||
}, | |||
//Remember private and custom channels used | |||
trackHistory: function (msg) { | |||
const { subType, source, target, channel } = msg; | |||
if (subType === 'privateIn' || subType === 'privateOut') { | |||
const list = this.privateChannels; | |||
list.spliceWhere(l => l === source || l === target); | |||
//Newest sources are always at the end | |||
list.push(source || target); | |||
if (list.length > 5) | |||
list.splice(0, list.length - 5); | |||
if (subType === 'privateOut' && config.rememberChatChannel) | |||
this.lastPrivateChannel = target; | |||
} else if (subType === 'custom' && config.rememberChatChannel) | |||
this.lastCustomChannel = channel; | |||
}, | |||
onGetMessages: function (e) { | |||
@@ -275,10 +192,14 @@ define([ | |||
let container = this.find('.list'); | |||
messages.forEach(m => { | |||
this.trackHistory(m); | |||
let message = m.message; | |||
if (m.source && window.player.social.isPlayerBlocked(m.source)) | |||
return; | |||
if (m.source) { | |||
if (window.player.social.isPlayerBlocked(m.source)) | |||
return; | |||
} | |||
if (m.item) { | |||
let source = message.split(':')[0]; | |||
@@ -293,6 +214,9 @@ define([ | |||
else | |||
el.addClass('info'); | |||
if (m.has('channel')) | |||
el.addClass(m.channel); | |||
if (m.item) { | |||
let clickHander = () => {}; | |||
let moveHandler = this.showItemTooltip.bind(this, el, m.item); | |||
@@ -318,11 +242,6 @@ define([ | |||
}); | |||
} | |||
} | |||
this.messages.push({ | |||
ttl: this.maxTtl, | |||
el: el | |||
}); | |||
}); | |||
if (!this.el.hasClass('typing')) | |||
@@ -362,7 +281,7 @@ define([ | |||
}, | |||
toggle: function (show, isFake, e) { | |||
if ((isFake) && (this.hoverFilter)) | |||
if (isFake && this.hoverFilter) | |||
return; | |||
input.resetKeys(); | |||
@@ -373,81 +292,78 @@ define([ | |||
if (show) { | |||
this.el.addClass('typing'); | |||
if (!config.rememberChatChannel) { | |||
this.currentChannel = 'global'; | |||
this.currentSubChannel = null; | |||
} | |||
this.find('.channelPicker').html(this.currentSubChannel || this.currentChannel); | |||
textbox.focus(); | |||
this.find('.list').scrollTop(9999999); | |||
} else | |||
} else { | |||
this.find('.channelOptions').removeClass('active'); | |||
textbox.val(''); | |||
this.el.removeClass('picking'); | |||
if (['direct', 'custom'].includes(this.currentChannel) && (!this.currentSubChannel || ['join new', 'leave'].includes(this.currentSubChannel))) { | |||
this.currentSubChannel = null; | |||
this.currentChannel = 'global'; | |||
} | |||
} | |||
if (e) | |||
e.stopPropagation(); | |||
}, | |||
sendChat: function (e) { | |||
if (e.which === 27) { | |||
this.toggle(false); | |||
return; | |||
} else if (e.which === 9) { | |||
e.preventDefault(); | |||
let textfield = this.find('input'); | |||
textfield.val(`${textfield.val()} `); | |||
return; | |||
} else if (e.which !== 13) | |||
return; | |||
if (!this.el.hasClass('typing')) { | |||
this.toggle(true); | |||
return; | |||
} | |||
let textbox = this.find('input'); | |||
let msgConfig = { | |||
success: true, | |||
message: textbox.val() | |||
message: textbox.val(), | |||
event: e, | |||
cancel: false | |||
}; | |||
this.processChat(msgConfig); | |||
if (msgConfig.cancel || this.el.hasClass('picking')) | |||
return false; | |||
const { which: charCode } = e; | |||
if ([9, 27].includes(charCode) || charCode !== 13) { | |||
if (charCode === 9) { | |||
e.preventDefault(); | |||
textbox.val(`${textbox.val()} `); | |||
} else if (charCode === 27) | |||
this.toggle(false); | |||
return; | |||
} | |||
events.emit('onBeforeChat', msgConfig); | |||
let val = msgConfig.message | |||
.split('<') | |||
.join('<') | |||
.split('>') | |||
.join('>'); | |||
textbox.blur(); | |||
.split('<').join('<') | |||
.split('>').join('>'); | |||
if (!msgConfig.success) | |||
return; | |||
if (val.trim() === '') | |||
return; | |||
if (config.rememberChatChannel) { | |||
const firstChar = val[0]; | |||
let lastChannel = null; | |||
if ('@$'.includes(firstChar)) { | |||
const firstSpace = val.indexOf(' '); | |||
if (firstSpace === -1) | |||
lastChannel = val + ' '; | |||
else | |||
lastChannel = val.substr(0, firstSpace) + ' '; | |||
} else if (firstChar === '%') | |||
lastChannel = '%'; | |||
this.lastChannel = lastChannel; | |||
} | |||
client.request({ | |||
cpn: 'social', | |||
method: 'chat', | |||
data: { | |||
message: val | |||
message: val, | |||
type: this.currentChannel, | |||
subType: this.currentSubChannel | |||
} | |||
}); | |||
}, | |||
onToggleLastChannel: function (isOn) { | |||
if (!isOn) | |||
this.lastChannel = null; | |||
this.toggle(); | |||
} | |||
}; | |||
}); |
@@ -0,0 +1,123 @@ | |||
define([ | |||
'html!ui/templates/messages/tplTab' | |||
], function ( | |||
tplTab | |||
) { | |||
const extensionObj = { | |||
renderKeyboard: function () { | |||
this.find('.keyboard').remove(); | |||
let container = $('<div class="keyboard"></div>') | |||
.appendTo(this.el); | |||
let keyboard = { | |||
0: 'qwertyuiop|asdfghjkl|zxcvbnm', | |||
1: 'QWERTYUIOP|ASDFGHJKL|ZXCVBNM', | |||
2: '1234567890|@#&*-+=()|_$"\';/' | |||
}[this.kbUpper].split(''); | |||
//Hacky: Insert control characters in correct positions | |||
//Backspace goes after 'm' | |||
if (this.kbUpper === 0) { | |||
keyboard.splice(keyboard.indexOf('z'), 0, 'caps'); | |||
keyboard.splice(keyboard.indexOf('m') + 1, 0, '<<'); | |||
} else if (this.kbUpper === 1) { | |||
keyboard.splice(keyboard.indexOf('Z'), 0, 'caps'); | |||
keyboard.splice(keyboard.indexOf('M') + 1, 0, '<<'); | |||
} else if (this.kbUpper === 2) | |||
keyboard.splice(keyboard.indexOf('/') + 1, 0, '<<'); | |||
keyboard.push(...['|', '123', ',', 'space', '.', 'send']); | |||
let row = 0; | |||
keyboard.forEach(k => { | |||
if (k === '|') { | |||
row++; | |||
const postGapCount = row === 4 ? 0 : row - 1; | |||
for (let i = 0; i < postGapCount; i++) | |||
$('<div class="gap" />').appendTo(container); | |||
$('<div class="newline" />').appendTo(container); | |||
const preGapCount = row === 3 ? 0 : row; | |||
for (let i = 0; i < preGapCount; i++) | |||
$('<div class="gap" />').appendTo(container); | |||
return; | |||
} | |||
let className = (k.length === 1) ? 'key' : 'key special'; | |||
if (k === ' ') { | |||
k = '.'; | |||
className = 'key hidden'; | |||
} | |||
className += ' ' + k; | |||
let elKey = $(`<div class="${className}">${k}</div>`) | |||
.appendTo(container); | |||
if (!className.includes('hidden')) | |||
elKey.on('click', this.clickKey.bind(this, k)); | |||
}); | |||
}, | |||
clickKey: function (key) { | |||
window.navigator.vibrate(20); | |||
let elInput = this.find('input'); | |||
const handler = { | |||
caps: () => { | |||
this.kbUpper = (this.kbUpper + 1) % 2; | |||
this.renderKeyboard(); | |||
}, | |||
123: () => { | |||
this.kbUpper = (this.kbUpper === 2) ? 0 : 2; | |||
this.renderKeyboard(); | |||
}, | |||
space: () => this.clickKey(' '), | |||
'<<': () => { | |||
elInput.val(elInput.val().slice(0, -1)); | |||
this.find('.input').html(elInput.val()); | |||
}, | |||
send: () => { | |||
this.sendChat({ which: 13 }); | |||
this.find('.input').html(''); | |||
this.find('input').val(''); | |||
} | |||
}[key]; | |||
if (handler) { | |||
handler(); | |||
return; | |||
} | |||
elInput.val(elInput.val() + key); | |||
this.enforceMaxMsgLength(); | |||
this.find('.input').html(elInput.val()); | |||
} | |||
}; | |||
return { | |||
init: function () { | |||
$.extend(this, extensionObj); | |||
this.kbUpper = 0; | |||
this.el.on('click', this.toggle.bind(this, true)); | |||
this.renderKeyboard(); | |||
$(tplTab) | |||
.appendTo(this.find('.filters')) | |||
.addClass('btnClose') | |||
.html('x') | |||
.on('click', this.toggle.bind(this, false, true)); | |||
} | |||
}; | |||
}); |
@@ -7,56 +7,14 @@ | |||
left: 10px; | |||
bottom: 10px; | |||
width: 480px; | |||
padding: @pad; | |||
pointer-events: none; | |||
.input { | |||
display: none; | |||
} | |||
&.typing { | |||
pointer-events: all; | |||
.el.textbox { | |||
&.message:not(.input) { | |||
display: block; | |||
} | |||
&.time { | |||
display: none; | |||
} | |||
} | |||
.filters { | |||
pointer-events: all; | |||
display: block; | |||
} | |||
.list { | |||
overflow-y: auto; | |||
background-color: @darkGray; | |||
.list-message { | |||
filter: none; | |||
} | |||
} | |||
} | |||
&.active { | |||
.list-message { | |||
opacity: 1 !important; | |||
} | |||
} | |||
display: flex; | |||
flex-direction: column; | |||
.filters { | |||
display: none; | |||
width: 100%; | |||
height: 26px; | |||
margin-bottom: 10px; | |||
flex-wrap: wrap; | |||
.filter { | |||
height: 100%; | |||
@@ -64,6 +22,7 @@ | |||
float: left; | |||
color: @blackA; | |||
margin-right: 10px; | |||
margin-bottom: 10px; | |||
padding: 5px 10px; | |||
cursor: pointer; | |||
@@ -74,7 +33,7 @@ | |||
&.active { | |||
background-color: @blackB; | |||
color: @green; | |||
color: @blueB; | |||
&:hover { | |||
background-color: @blackA; | |||
@@ -169,19 +128,53 @@ | |||
} | |||
.el { | |||
&.textbox.message { | |||
display: none; | |||
background-color: @gray; | |||
text-align: left; | |||
padding: 5px 10px; | |||
.bottom { | |||
display: none; | |||
.channelPicker { | |||
min-width: 100px; | |||
color: 1; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
color: @white; | |||
background-color: @blueC; | |||
cursor: pointer; | |||
padding: 0px 10px; | |||
&:after { | |||
content: '▾'; | |||
margin-left: 10px; | |||
} | |||
&:hover { | |||
background-color: @blueD; | |||
} | |||
} | |||
.el { | |||
flex: 1; | |||
width: 100%; | |||
color: @white; | |||
&.textbox.message { | |||
display: none; | |||
background-color: @gray; | |||
text-align: left; | |||
padding: 5px 10px; | |||
} | |||
} | |||
&.time { | |||
display: block; | |||
display: flex; | |||
align-items: center; | |||
padding-left: 10px; | |||
background-color: transparent; | |||
height: 35px; | |||
color: @white; | |||
text-align: left; | |||
padding: 5px 10px; | |||
filter: drop-shadow(0px -2px 0px @blackD) | |||
drop-shadow(0px 2px 0px @blackD) | |||
drop-shadow(2px 0px 0px @blackD) | |||
@@ -192,8 +185,106 @@ | |||
drop-shadow(-2px 0px 0px @blackD); | |||
} | |||
width: 100%; | |||
color: @white; | |||
&.channelOptions { | |||
display: none; | |||
flex-direction: column; | |||
position: absolute; | |||
left: 0px; | |||
bottom: 0px; | |||
min-width: 100px; | |||
.option { | |||
height: 35px; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
color: @grayB; | |||
background-color: @blackA; | |||
cursor: pointer; | |||
padding: 0px 10px; | |||
&:after { | |||
content: attr(shortcut); | |||
margin-left: 10px; | |||
color: @grayC; | |||
} | |||
&:hover, | |||
&.selected { | |||
background-color: @grayD; | |||
color: @white; | |||
} | |||
} | |||
} | |||
} | |||
&.typing { | |||
pointer-events: all; | |||
.filters { | |||
pointer-events: all; | |||
display: flex; | |||
} | |||
.list { | |||
overflow-y: auto; | |||
background-color: @darkGray; | |||
.list-message { | |||
filter: none; | |||
} | |||
} | |||
.bottom { | |||
display: flex; | |||
.el.textbox { | |||
&.message:not(.input) { | |||
display: block; | |||
} | |||
} | |||
} | |||
.time { | |||
display: none; | |||
} | |||
.channelOptions { | |||
display: none; | |||
&.active { | |||
display: flex; | |||
} | |||
} | |||
} | |||
&.active { | |||
.list-message { | |||
opacity: 1 !important; | |||
} | |||
} | |||
&.picking { | |||
&:before { | |||
position: absolute; | |||
content: ''; | |||
left: 0px; | |||
top: 0px; | |||
width: 100%; | |||
height: 100%; | |||
background-color: @blackD; | |||
opacity: 0.7; | |||
} | |||
} | |||
} | |||
@@ -232,22 +323,29 @@ | |||
background-color: fade(@darkGray, 90%); | |||
display: flex; | |||
flex-direction: column; | |||
z-index: 999999999; | |||
z-index: 999999998; | |||
.input { | |||
display: block; | |||
height: 26px; | |||
flex-shrink: 0; | |||
.channelPicker { | |||
display: none; | |||
} | |||
.el.textbox:not(.input) { | |||
&.message, | |||
&.time { | |||
display: none; | |||
.channelOptions { | |||
z-index: 999999999; | |||
} | |||
.bottom { | |||
.input { | |||
display: block; | |||
height: 26px; | |||
flex-shrink: 0; | |||
} | |||
} | |||
.el.textbox.message:not(.input),.time { | |||
display: none; | |||
} | |||
.keyboard { | |||
display: flex; | |||
flex-direction: row; | |||
@@ -255,7 +353,7 @@ | |||
background-color: @blackC; | |||
justify-content: center; | |||
align-items: center; | |||
height: 128px; | |||
height: 140px; | |||
flex-shrink: 0; | |||
.key { | |||
@@ -5,10 +5,16 @@ | |||
<div class="btn filter active" filter="chat">players</div> | |||
<div class="btn filter active" filter="loot">loot</div> | |||
</div> | |||
<div class="list rep chat info loot"> | |||
<div class="list rep chat info loot"></div> | |||
<div class="bottom"> | |||
<div class="channelPicker"></div> | |||
<input type="text" class="el textbox message"> | |||
<div class="input el textbox message"></div> | |||
</div> | |||
<div class="bottom time"></div> | |||
<div class="bottom channelOptions"> | |||
<div class="option">global</div> | |||
<div class="option">party</div> | |||
<div class="option">direct</div> | |||
</div> | |||
<input type="text" class="el textbox message"> | |||
<div class="input el textbox message"></div> | |||
<div class="el textbox time"></div> | |||
</div> |
@@ -39,7 +39,7 @@ const sendPartyMessage = ({ party, obj }, msg) => { | |||
} | |||
let charname = obj.auth.charname; | |||
let message = msg.data.message.substr(1); | |||
let message = msg.data.message; | |||
party.forEach(p => { | |||
let player = cons.players.find(c => c.id === p); | |||
@@ -60,69 +60,53 @@ const sendPartyMessage = ({ party, obj }, msg) => { | |||
const sendCustomChannelMessage = (cpnSocial, msg) => { | |||
const { obj } = cpnSocial; | |||
let pList = cons.players; | |||
let pLen = pList.length; | |||
let origMessage = msg.data.message.substr(1); | |||
const { data: { message, subType: channel } } = msg; | |||
let channel = origMessage.split(' ')[0]; | |||
let message = origMessage.substr(channel.length); | |||
if ((!channel) || (!message)) { | |||
obj.socket.emit('events', { | |||
onGetMessages: [{ | |||
messages: [{ | |||
class: 'color-redA', | |||
message: 'syntax: $channel message', | |||
type: 'info' | |||
}] | |||
}] | |||
}); | |||
if (!channel) | |||
return; | |||
} else if (!cpnSocial.isInChannel(obj, channel)) { | |||
if (!cpnSocial.isInChannel(obj, channel)) { | |||
obj.socket.emit('events', { | |||
onGetMessages: [{ | |||
messages: [{ | |||
class: 'color-redA', | |||
message: 'you are not currently in channel: ' + channel, | |||
message: 'You are not currently in that channel', | |||
type: 'info' | |||
}] | |||
}] | |||
}); | |||
return; | |||
} else if (pLen > 0) { | |||
for (let i = 0; i < pLen; i++) { | |||
if (cpnSocial.isInChannel(pList[i], channel)) { | |||
pList[i].socket.emit('events', { | |||
onGetMessages: [{ | |||
messages: [{ | |||
class: 'color-grayB', | |||
message: '[' + channel + '] ' + obj.auth.charname + ': ' + message, | |||
type: channel.trim(), | |||
source: obj.name | |||
}] | |||
}] | |||
}); | |||
} | |||
} | |||
} | |||
}; | |||
const sendPrivateMessage = ({ obj: { name: sourcePlayerName, socket } }, msg) => { | |||
let message = msg.data.message.substr(1); | |||
const sendMessage = `[${channel}] ${obj.auth.charname}: ${message}`; | |||
const eventData = { | |||
onGetMessages: [{ | |||
messages: [{ | |||
class: 'color-grayB', | |||
message: sendMessage, | |||
type: 'chat', | |||
subType: 'custom', | |||
channel: channel.trim(), | |||
source: obj.name | |||
}] | |||
}] | |||
}; | |||
cons.players.forEach(p => { | |||
if (!cpnSocial.isInChannel(p, channel)) | |||
return; | |||
let playerName = ''; | |||
//Check if there's a space in the name | |||
if (message[0] === "'") | |||
playerName = message.substring(1, message.indexOf("'", 1)); | |||
else | |||
playerName = message.substring(0, message.indexOf(' ')); | |||
p.socket.emit('events', eventData); | |||
}); | |||
}; | |||
message = message.substr(playerName.length); | |||
const sendPrivateMessage = ({ obj: { name: sourceName, socket } }, msg) => { | |||
const { data: { message, subType: targetName } } = msg; | |||
if (playerName === sourcePlayerName) | |||
if (targetName === sourceName) | |||
return; | |||
let target = cons.players.find(p => p.name === playerName); | |||
let target = cons.players.find(p => p.name === targetName); | |||
if (!target) | |||
return; | |||
@@ -131,10 +115,10 @@ const sendPrivateMessage = ({ obj: { name: sourcePlayerName, socket } }, msg) => | |||
data: { | |||
messages: [{ | |||
class: 'color-yellowB', | |||
message: '(you to ' + playerName + '): ' + message, | |||
message: '(you to ' + targetName + '): ' + message, | |||
type: 'chat', | |||
subType: 'privateOut', | |||
source: sourcePlayerName | |||
target: targetName | |||
}] | |||
} | |||
}); | |||
@@ -144,10 +128,10 @@ const sendPrivateMessage = ({ obj: { name: sourcePlayerName, socket } }, msg) => | |||
data: { | |||
messages: [{ | |||
class: 'color-yellowB', | |||
message: '(' + sourcePlayerName + ' to you): ' + message, | |||
message: '(' + sourceName + ' to you): ' + message, | |||
type: 'chat', | |||
subType: 'privateIn', | |||
source: sourcePlayerName | |||
source: sourceName | |||
}] | |||
} | |||
}); | |||
@@ -230,13 +214,15 @@ module.exports = (cpnSocial, msg) => { | |||
}; | |||
events.emit('onBeforeSendMessage', msgEvent); | |||
const firstChar = messageString[0]; | |||
const messageHandler = { | |||
$: sendCustomChannelMessage, | |||
'@': sendPrivateMessage, | |||
'%': sendPartyMessage | |||
}[firstChar] || sendRegularMessage; | |||
global: sendRegularMessage, | |||
custom: sendCustomChannelMessage, | |||
direct: sendPrivateMessage, | |||
party: sendPartyMessage | |||
}[msgData.type]; | |||
if (!messageHandler) | |||
return; | |||
messageHandler(cpnSocial, msg); | |||
}; |