Support changing the nick by clicking it in MessageInput
This commit is contained in:
parent
4a74463ae8
commit
f174d98107
File diff suppressed because one or more lines are too long
@ -323,8 +323,10 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
||||
|
||||
.chat-title {
|
||||
margin-left: 15px;
|
||||
font-size: 24px !important;
|
||||
font: 24px Montserrat, sans-serif;
|
||||
white-space: nowrap;
|
||||
background: none;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.chat-topic-wrap {
|
||||
@ -502,13 +504,14 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
||||
}
|
||||
|
||||
.message-input-nick {
|
||||
display: block;
|
||||
margin: 10px;
|
||||
line-height: 30px;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
background: #6BB758;
|
||||
background: #6BB758 !important;
|
||||
color: #FFF;
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-family: Montserrat, sans-serif !important;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import Editable from './ui/Editable';
|
||||
|
||||
export default class MessageInput extends PureComponent {
|
||||
state = {
|
||||
@ -35,10 +36,17 @@ export default class MessageInput extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { nick, currentHistoryEntry } = this.props;
|
||||
const { nick, currentHistoryEntry, onNickChange, onNickEditDone } = this.props;
|
||||
return (
|
||||
<div className="message-input-wrap">
|
||||
<Editable
|
||||
className="message-input-nick"
|
||||
value={nick}
|
||||
onBlur={onNickEditDone}
|
||||
onChange={onNickChange}
|
||||
>
|
||||
<span className="message-input-nick">{nick}</span>
|
||||
</Editable>
|
||||
<input
|
||||
className="message-input"
|
||||
type="text"
|
||||
|
@ -36,6 +36,16 @@ export default class Chat extends Component {
|
||||
setServerName(title, tab.server);
|
||||
};
|
||||
|
||||
handleNickChange = nick => {
|
||||
const { setNick, tab } = this.props;
|
||||
setNick(nick, tab.server, true);
|
||||
};
|
||||
|
||||
handleNickEditDone = nick => {
|
||||
const { setNick, tab } = this.props;
|
||||
setNick(nick, tab.server);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channel,
|
||||
@ -96,6 +106,8 @@ export default class Chat extends Component {
|
||||
tab={tab}
|
||||
onCommand={runCommand}
|
||||
onMessage={sendMessage}
|
||||
onNickChange={this.handleNickChange}
|
||||
onNickEditDone={this.handleNickEditDone}
|
||||
{...inputActions}
|
||||
/>
|
||||
<UserList
|
||||
|
@ -1,12 +1,41 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
const style = {
|
||||
background: 'none',
|
||||
font: 'inherit'
|
||||
};
|
||||
import { stringWidth } from '../../util';
|
||||
|
||||
export default class Editable extends PureComponent {
|
||||
state = { editing: false };
|
||||
static defaultProps = {
|
||||
editable: true
|
||||
};
|
||||
|
||||
state = {
|
||||
editing: false
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.state.editing && nextProps.value !== this.props.value) {
|
||||
this.setState({
|
||||
width: this.getInputWidth(nextProps.value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevState.editing && this.state.editing) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({
|
||||
width: this.getInputWidth(this.props.value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getInputWidth(value) {
|
||||
if (this.input) {
|
||||
const style = window.getComputedStyle(this.input);
|
||||
const padding = parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10);
|
||||
// Make sure the width is atleast 1px so the caret always shows
|
||||
const width = stringWidth(value, style.font) || 1;
|
||||
return padding + width;
|
||||
}
|
||||
}
|
||||
|
||||
startEditing = () => {
|
||||
if (this.props.editable) {
|
||||
@ -23,32 +52,46 @@ export default class Editable extends PureComponent {
|
||||
this.setState({ editing: false });
|
||||
};
|
||||
|
||||
handleKey = e => {
|
||||
if (e.key === 'Enter') {
|
||||
handleBlur = e => {
|
||||
const { onBlur } = this.props;
|
||||
this.stopEditing();
|
||||
if (onBlur) {
|
||||
onBlur(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = e => this.props.onChange(e.target.value);
|
||||
|
||||
handleKey = e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleBlur(e);
|
||||
}
|
||||
};
|
||||
|
||||
inputRef = el => { this.input = el; }
|
||||
|
||||
render() {
|
||||
const { children, className, value } = this.props;
|
||||
|
||||
const style = {
|
||||
width: this.state.width
|
||||
};
|
||||
|
||||
return (
|
||||
<div onClick={this.startEditing}>
|
||||
{this.state.editing ?
|
||||
this.state.editing ?
|
||||
<input
|
||||
autoFocus
|
||||
ref={this.inputRef}
|
||||
className={className}
|
||||
style={style}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={this.stopEditing}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKey}
|
||||
style={style}
|
||||
spellCheck={false}
|
||||
/> :
|
||||
children
|
||||
}
|
||||
</div>
|
||||
<div onClick={this.startEditing}>{children}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { getSelectedMessages, getHasMoreMessages,
|
||||
runCommand, sendMessage, fetchMessages, addFetchedMessages } from '../state/messages';
|
||||
import { openPrivateChat, closePrivateChat } from '../state/privateChats';
|
||||
import { getSearch, searchMessages, toggleSearch } from '../state/search';
|
||||
import { getCurrentNick, disconnect, setServerName } from '../state/servers';
|
||||
import { getCurrentNick, disconnect, setNick, setServerName } from '../state/servers';
|
||||
import { getSelectedTab, select } from '../state/tab';
|
||||
import { getShowUserList, toggleUserList } from '../state/ui';
|
||||
|
||||
@ -39,6 +39,7 @@ const mapDispatch = dispatch => ({
|
||||
searchMessages,
|
||||
select,
|
||||
sendMessage,
|
||||
setNick,
|
||||
setServerName,
|
||||
toggleSearch,
|
||||
toggleUserList
|
||||
|
@ -67,9 +67,9 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
||||
dispatch(broadcast(withReason(`${user} quit`, reason), server, channels));
|
||||
},
|
||||
|
||||
nick(data) {
|
||||
const channels = findChannels(getState(), data.server, data.old);
|
||||
dispatch(broadcast(`${data.old} changed nick to ${data.new}`, data.server, channels));
|
||||
nick({ server, oldNick, newNick }) {
|
||||
const channels = findChannels(getState(), server, oldNick);
|
||||
dispatch(broadcast(`${oldNick} changed nick to ${newNick}`, server, channels));
|
||||
},
|
||||
|
||||
topic({ server, channel, topic, nick }) {
|
||||
|
@ -105,8 +105,8 @@ describe('channel reducer', () => {
|
||||
state = reducer(state, {
|
||||
type: actions.socket.NICK,
|
||||
server: 'srv',
|
||||
old: 'nick1',
|
||||
new: 'nick3'
|
||||
oldNick: 'nick1',
|
||||
newNick: 'nick3'
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
|
@ -10,7 +10,8 @@ describe('server reducer', () => {
|
||||
'127.0.0.1': {
|
||||
connected: false,
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick'
|
||||
nick: 'nick',
|
||||
editedNick: null
|
||||
}
|
||||
});
|
||||
|
||||
@ -20,7 +21,8 @@ describe('server reducer', () => {
|
||||
'127.0.0.1': {
|
||||
connected: false,
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick'
|
||||
nick: 'nick',
|
||||
editedNick: null
|
||||
}
|
||||
});
|
||||
|
||||
@ -32,12 +34,14 @@ describe('server reducer', () => {
|
||||
'127.0.0.1': {
|
||||
connected: false,
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick'
|
||||
nick: 'nick',
|
||||
editedNick: null
|
||||
},
|
||||
'127.0.0.2': {
|
||||
connected: false,
|
||||
name: 'srv',
|
||||
nick: 'nick'
|
||||
nick: 'nick',
|
||||
editedNick: null
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -74,20 +78,87 @@ describe('server reducer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the nick on SOCKET_NICK', () => {
|
||||
it('sets editedNick when editing the nick', () => {
|
||||
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
||||
state = reducer(state, {
|
||||
type: actions.socket.NICK,
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
old: 'nick',
|
||||
new: 'nick2'
|
||||
nick: 'nick2',
|
||||
editing: true
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
'127.0.0.1': {
|
||||
connected: false,
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick2'
|
||||
nick: 'nick',
|
||||
editedNick: 'nick2'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('clears editedNick when receiving an empty nick after editing finishes', () => {
|
||||
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
||||
state = reducer(state, {
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
nick: 'nick2',
|
||||
editing: true
|
||||
});
|
||||
state = reducer(state, {
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
nick: ''
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
'127.0.0.1': {
|
||||
connected: false,
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the nick on SOCKET_NICK', () => {
|
||||
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
||||
state = reducer(state, {
|
||||
type: actions.socket.NICK,
|
||||
server: '127.0.0.1',
|
||||
oldNick: 'nick',
|
||||
newNick: 'nick2'
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
'127.0.0.1': {
|
||||
connected: false,
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick2',
|
||||
editedNick: null
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('clears editedNick on SOCKET_NICK_FAIL', () => {
|
||||
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
||||
state = reducer(state, {
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
nick: 'nick2',
|
||||
editing: true
|
||||
});
|
||||
state = reducer(state, {
|
||||
type: actions.socket.NICK_FAIL,
|
||||
server: '127.0.0.1'
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
'127.0.0.1': {
|
||||
connected: false,
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -115,11 +186,13 @@ describe('server reducer', () => {
|
||||
'127.0.0.1': {
|
||||
name: 'stuff',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
connected: true
|
||||
},
|
||||
'127.0.0.2': {
|
||||
name: 'stuffz',
|
||||
nick: 'nick2',
|
||||
editedNick: null,
|
||||
connected: false
|
||||
}
|
||||
});
|
||||
@ -136,6 +209,7 @@ describe('server reducer', () => {
|
||||
'127.0.0.1': {
|
||||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
connected: true
|
||||
}
|
||||
});
|
||||
|
@ -63,6 +63,7 @@ export const socket = createSocketActions([
|
||||
'join',
|
||||
'message',
|
||||
'mode',
|
||||
'nick_fail',
|
||||
'nick',
|
||||
'part',
|
||||
'pm',
|
||||
|
@ -123,18 +123,17 @@ export default createReducer(Map(), {
|
||||
});
|
||||
},
|
||||
|
||||
[actions.socket.NICK](state, action) {
|
||||
const { server } = action;
|
||||
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
||||
return state.withMutations(s => {
|
||||
s.get(server).forEach((v, channel) => {
|
||||
s.updateIn([server, channel, 'users'], users => {
|
||||
const i = users.findIndex(user => user.nick === action.old);
|
||||
const i = users.findIndex(user => user.nick === oldNick);
|
||||
if (i < 0) {
|
||||
return users;
|
||||
}
|
||||
|
||||
return users.update(i,
|
||||
user => updateRenderName(user.set('nick', action.new))
|
||||
user => updateRenderName(user.set('nick', newNick))
|
||||
).sort(compareUsers);
|
||||
});
|
||||
});
|
||||
|
@ -5,8 +5,9 @@ import { getSelectedTab, updateSelection } from './tab';
|
||||
import * as actions from './actions';
|
||||
|
||||
const Server = Record({
|
||||
nick: null,
|
||||
name: null,
|
||||
nick: '',
|
||||
editedNick: null,
|
||||
name: '',
|
||||
connected: false
|
||||
});
|
||||
|
||||
@ -15,13 +16,19 @@ export const getServers = state => state.servers;
|
||||
export const getCurrentNick = createSelector(
|
||||
getServers,
|
||||
getSelectedTab,
|
||||
(servers, tab) => servers.getIn([tab.server, 'nick'], '')
|
||||
(servers, tab) => {
|
||||
const editedNick = servers.getIn([tab.server, 'editedNick']);
|
||||
if (editedNick === null) {
|
||||
return servers.getIn([tab.server, 'nick']);
|
||||
}
|
||||
return editedNick;
|
||||
}
|
||||
);
|
||||
|
||||
export const getCurrentServerName = createSelector(
|
||||
getServers,
|
||||
getSelectedTab,
|
||||
(servers, tab) => servers.getIn([tab.server, 'name'], '')
|
||||
(servers, tab) => servers.getIn([tab.server, 'name'])
|
||||
);
|
||||
|
||||
export default createReducer(Map(), {
|
||||
@ -44,14 +51,29 @@ export default createReducer(Map(), {
|
||||
return state.setIn([server, 'name'], name);
|
||||
},
|
||||
|
||||
[actions.socket.NICK](state, action) {
|
||||
const { server, old } = action;
|
||||
if (!old || old === state.get(server).nick) {
|
||||
return state.update(server, s => s.set('nick', action.new));
|
||||
[actions.SET_NICK](state, { server, nick, editing }) {
|
||||
if (editing) {
|
||||
return state.setIn([server, 'editedNick'], nick);
|
||||
} else if (nick === '') {
|
||||
return state.setIn([server, 'editedNick'], null);
|
||||
}
|
||||
return state;
|
||||
},
|
||||
|
||||
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
||||
if (!oldNick || oldNick === state.get(server).nick) {
|
||||
return state.update(server, s => s
|
||||
.set('nick', newNick)
|
||||
.set('editedNick', null)
|
||||
);
|
||||
}
|
||||
return state;
|
||||
},
|
||||
|
||||
[actions.socket.NICK_FAIL](state, { server }) {
|
||||
return state.setIn([server, 'editedNick'], null);
|
||||
},
|
||||
|
||||
[actions.socket.SERVERS](state, { data }) {
|
||||
if (!data) {
|
||||
return state;
|
||||
@ -140,21 +162,29 @@ export function away(message, server) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setNick(nick, server) {
|
||||
return {
|
||||
export function setNick(nick, server, editing) {
|
||||
nick = nick.trim().replace(' ', '');
|
||||
|
||||
const action = {
|
||||
type: actions.SET_NICK,
|
||||
nick,
|
||||
server,
|
||||
socket: {
|
||||
editing
|
||||
};
|
||||
|
||||
if (!editing && nick !== '') {
|
||||
action.socket = {
|
||||
type: 'nick',
|
||||
data: {
|
||||
new: nick,
|
||||
newNick: nick,
|
||||
server
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function isValidServerName(name) {
|
||||
return name.trim() !== '';
|
||||
}
|
||||
|
@ -33,5 +33,6 @@ const (
|
||||
ReplyMotd = "372"
|
||||
ReplyMotdStart = "375"
|
||||
ReplyEndOfMotd = "376"
|
||||
ErrErroneousNickname = "432"
|
||||
ErrNicknameInUse = "433"
|
||||
)
|
||||
|
@ -273,6 +273,12 @@ func (i *ircHandler) motdEnd(msg *irc.Message) {
|
||||
i.motdBuffer = MOTD{}
|
||||
}
|
||||
|
||||
func (i *ircHandler) badNick(msg *irc.Message) {
|
||||
i.session.sendJSON("nick_fail", NickFail{
|
||||
Server: i.client.Host,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *ircHandler) initHandlers() {
|
||||
i.handlers = map[string]func(*irc.Message){
|
||||
irc.Nick: i.nick,
|
||||
@ -302,6 +308,7 @@ func (i *ircHandler) initHandlers() {
|
||||
irc.ReplyMotdStart: i.motdStart,
|
||||
irc.ReplyMotd: i.motd,
|
||||
irc.ReplyEndOfMotd: i.motdEnd,
|
||||
irc.ErrErroneousNickname: i.badNick,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,3 +304,21 @@ func TestHandleIRCMotd(t *testing.T) {
|
||||
Content: []string{"line 1", "line 2"},
|
||||
}, <-s.broadcast)
|
||||
}
|
||||
|
||||
func TestHandleIRCBadNick(t *testing.T) {
|
||||
c := irc.NewClient("nick", "user")
|
||||
c.Host = "host.com"
|
||||
s := NewSession(nil)
|
||||
i := newIRCHandler(c, s)
|
||||
|
||||
i.dispatchMessage(&irc.Message{
|
||||
Command: irc.ErrErroneousNickname,
|
||||
})
|
||||
|
||||
// It should print the error message first
|
||||
<-s.broadcast
|
||||
|
||||
checkResponse(t, "nick_fail", NickFail{
|
||||
Server: "host.com",
|
||||
}, <-s.broadcast)
|
||||
}
|
||||
|
@ -28,8 +28,12 @@ type Connect struct {
|
||||
|
||||
type Nick struct {
|
||||
Server string `json:"server"`
|
||||
Old string `json:"old"`
|
||||
New string `json:"new"`
|
||||
Old string `json:"oldNick"`
|
||||
New string `json:"newNick"`
|
||||
}
|
||||
|
||||
type NickFail struct {
|
||||
Server string `json:"server"`
|
||||
}
|
||||
|
||||
type Join struct {
|
||||
|
Loading…
Reference in New Issue
Block a user