Add prettier
This commit is contained in:
parent
0cbbc1b8ff
commit
b176b79144
46 changed files with 832 additions and 544 deletions
|
@ -4,7 +4,10 @@ import { sendMessage, raw } from 'state/messages';
|
|||
import { setNick, disconnect, whois, away } from 'state/servers';
|
||||
import { select } from 'state/tab';
|
||||
import { find } from 'utils';
|
||||
import createCommandMiddleware, { beforeHandler, notFoundHandler } from './middleware/command';
|
||||
import createCommandMiddleware, {
|
||||
beforeHandler,
|
||||
notFoundHandler
|
||||
} from './middleware/command';
|
||||
|
||||
const help = [
|
||||
'/join <channel> - Join a channel',
|
||||
|
@ -26,7 +29,8 @@ const help = [
|
|||
const text = content => ({ content });
|
||||
const error = content => ({ content, type: 'error' });
|
||||
const prompt = content => ({ content, type: 'prompt' });
|
||||
const findHelp = cmd => find(help, line => line.slice(1, line.indexOf(' ')) === cmd);
|
||||
const findHelp = cmd =>
|
||||
find(help, line => line.slice(1, line.indexOf(' ')) === cmd);
|
||||
|
||||
export default createCommandMiddleware(COMMAND, {
|
||||
join({ dispatch, server }, channel) {
|
||||
|
@ -160,10 +164,7 @@ export default createCommandMiddleware(COMMAND, {
|
|||
dispatch(raw(cmd, server));
|
||||
return prompt(`=> ${cmd}`);
|
||||
}
|
||||
return [
|
||||
prompt('=> /raw'),
|
||||
error('Missing message')
|
||||
];
|
||||
return [prompt('=> /raw'), error('Missing message')];
|
||||
},
|
||||
|
||||
help(_, ...commands) {
|
||||
|
|
|
@ -14,14 +14,26 @@ export default class App extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { connected, tab, channels, servers,
|
||||
privateChats, showTabList, select, push } = this.props;
|
||||
const mainClass = showTabList ? 'main-container off-canvas' : 'main-container';
|
||||
const {
|
||||
connected,
|
||||
tab,
|
||||
channels,
|
||||
servers,
|
||||
privateChats,
|
||||
showTabList,
|
||||
select,
|
||||
push
|
||||
} = this.props;
|
||||
const mainClass = showTabList
|
||||
? 'main-container off-canvas'
|
||||
: 'main-container';
|
||||
return (
|
||||
<div className="wrap">
|
||||
{!connected &&
|
||||
<div className="app-info">Connection lost, attempting to reconnect...</div>
|
||||
}
|
||||
{!connected && (
|
||||
<div className="app-info">
|
||||
Connection lost, attempting to reconnect...
|
||||
</div>
|
||||
)}
|
||||
<div className="app-container" onClick={this.handleClick}>
|
||||
<TabList
|
||||
tab={tab}
|
||||
|
@ -33,9 +45,15 @@ export default class App extends Component {
|
|||
push={push}
|
||||
/>
|
||||
<div className={mainClass}>
|
||||
<Route name="chat"><Chat /></Route>
|
||||
<Route name="connect"><Connect /></Route>
|
||||
<Route name="settings"><Settings /></Route>
|
||||
<Route name="chat">
|
||||
<Chat />
|
||||
</Route>
|
||||
<Route name="connect">
|
||||
<Connect />
|
||||
</Route>
|
||||
<Route name="settings">
|
||||
<Settings />
|
||||
</Route>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,36 +24,48 @@ export default class TabList extends PureComponent {
|
|||
/>
|
||||
);
|
||||
|
||||
server.forEach((channel, name) => tabs.push(
|
||||
<TabListItem
|
||||
key={address + name}
|
||||
server={address}
|
||||
target={name}
|
||||
content={name}
|
||||
selected={tab.server === address && tab.name === name}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
));
|
||||
|
||||
if (privateChats.has(address) && privateChats.get(address).size > 0) {
|
||||
tabs.push(<div key={`${address}-pm}`} className="tab-label">Private messages</div>);
|
||||
|
||||
privateChats.get(address).forEach(nick => tabs.push(
|
||||
server.forEach((channel, name) =>
|
||||
tabs.push(
|
||||
<TabListItem
|
||||
key={address + nick}
|
||||
key={address + name}
|
||||
server={address}
|
||||
target={nick}
|
||||
content={nick}
|
||||
selected={tab.server === address && tab.name === nick}
|
||||
target={name}
|
||||
content={name}
|
||||
selected={tab.server === address && tab.name === name}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
if (privateChats.has(address) && privateChats.get(address).size > 0) {
|
||||
tabs.push(
|
||||
<div key={`${address}-pm}`} className="tab-label">
|
||||
Private messages
|
||||
</div>
|
||||
);
|
||||
|
||||
privateChats
|
||||
.get(address)
|
||||
.forEach(nick =>
|
||||
tabs.push(
|
||||
<TabListItem
|
||||
key={address + nick}
|
||||
server={address}
|
||||
target={nick}
|
||||
content={nick}
|
||||
selected={tab.server === address && tab.name === nick}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<button className="button-connect" onClick={this.handleConnectClick}>Connect</button>
|
||||
<button className="button-connect" onClick={this.handleConnectClick}>
|
||||
Connect
|
||||
</button>
|
||||
<div className="tab-container">{tabs}</div>
|
||||
<div className="side-buttons">
|
||||
<i className="icon-user" />
|
||||
|
|
|
@ -90,10 +90,7 @@ export default class Chat extends Component {
|
|||
onToggleSearch={toggleSearch}
|
||||
onToggleUserList={toggleUserList}
|
||||
/>
|
||||
<Search
|
||||
search={search}
|
||||
onSearch={this.handleSearch}
|
||||
/>
|
||||
<Search search={search} onSearch={this.handleSearch} />
|
||||
<MessageBox
|
||||
hasMoreMessages={hasMoreMessages}
|
||||
messages={messages}
|
||||
|
|
|
@ -7,8 +7,16 @@ import { linkify } from 'utils';
|
|||
|
||||
export default class ChatTitle extends PureComponent {
|
||||
render() {
|
||||
const { status, title, tab, channel, onTitleChange,
|
||||
onToggleSearch, onToggleUserList, onCloseClick } = this.props;
|
||||
const {
|
||||
status,
|
||||
title,
|
||||
tab,
|
||||
channel,
|
||||
onTitleChange,
|
||||
onToggleSearch,
|
||||
onToggleUserList,
|
||||
onCloseClick
|
||||
} = this.props;
|
||||
|
||||
let closeTitle;
|
||||
if (tab.isChannel()) {
|
||||
|
@ -21,7 +29,9 @@ export default class ChatTitle extends PureComponent {
|
|||
|
||||
let serverError = null;
|
||||
if (!tab.name && status.error) {
|
||||
serverError = <span className="chat-topic error">Error: {status.error}</span>;
|
||||
serverError = (
|
||||
<span className="chat-topic error">Error: {status.error}</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -38,7 +48,9 @@ export default class ChatTitle extends PureComponent {
|
|||
<span className="chat-title">{title}</span>
|
||||
</Editable>
|
||||
<div className="chat-topic-wrap">
|
||||
<span className="chat-topic">{linkify(channel.get('topic')) || null}</span>
|
||||
<span className="chat-topic">
|
||||
{linkify(channel.get('topic')) || null}
|
||||
</span>
|
||||
{serverError}
|
||||
</div>
|
||||
<i className="icon-search" title="Search" onClick={onToggleSearch} />
|
||||
|
@ -51,7 +63,9 @@ export default class ChatTitle extends PureComponent {
|
|||
</div>
|
||||
<div className="userlist-bar">
|
||||
<i className="icon-user" />
|
||||
<span className="chat-usercount">{channel.get('users', List()).size}</span>
|
||||
<span className="chat-usercount">
|
||||
{channel.get('users', List()).size}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,9 @@ export default class Message extends PureComponent {
|
|||
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
const className = message.type ? `message message-${message.type}` : 'message';
|
||||
const className = message.type
|
||||
? `message message-${message.type}`
|
||||
: 'message';
|
||||
const style = {
|
||||
paddingLeft: `${window.messageIndent + 15}px`,
|
||||
textIndent: `-${window.messageIndent}px`,
|
||||
|
@ -15,11 +17,13 @@ export default class Message extends PureComponent {
|
|||
return (
|
||||
<p className={className} style={style}>
|
||||
<span className="message-time">{message.time}</span>
|
||||
{message.from &&
|
||||
{message.from && (
|
||||
<span className="message-sender" onClick={this.handleNickClick}>
|
||||
{' '}{message.from}
|
||||
{' '}
|
||||
{message.from}
|
||||
</span>
|
||||
}{' '}{message.content}
|
||||
)}{' '}
|
||||
{message.content}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ export default class MessageBox extends PureComponent {
|
|||
|
||||
if (nextProps.messages.get(0) !== this.props.messages.get(0)) {
|
||||
if (nextProps.tab === this.props.tab) {
|
||||
const addedMessages = nextProps.messages.size - this.props.messages.size;
|
||||
const addedMessages =
|
||||
nextProps.messages.size - this.props.messages.size;
|
||||
let addedHeight = 0;
|
||||
for (let i = 0; i < addedMessages; i++) {
|
||||
addedHeight += nextProps.messages.get(i).height;
|
||||
|
@ -126,10 +127,12 @@ export default class MessageBox extends PureComponent {
|
|||
|
||||
handleScroll = ({ scrollTop, clientHeight, scrollHeight }) => {
|
||||
if (this.mounted) {
|
||||
if (!this.loading &&
|
||||
if (
|
||||
!this.loading &&
|
||||
this.props.hasMoreMessages &&
|
||||
scrollTop <= fetchThreshold &&
|
||||
scrollTop < this.prevScrollTop) {
|
||||
scrollTop < this.prevScrollTop
|
||||
) {
|
||||
this.fetchMore();
|
||||
}
|
||||
|
||||
|
@ -165,11 +168,7 @@ export default class MessageBox extends PureComponent {
|
|||
if (index === 0) {
|
||||
if (this.props.hasMoreMessages) {
|
||||
return (
|
||||
<div
|
||||
key="top"
|
||||
className="messagebox-top-indicator"
|
||||
style={style}
|
||||
>
|
||||
<div key="top" className="messagebox-top-indicator" style={style}>
|
||||
Loading messages...
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -7,8 +7,16 @@ export default class MessageInput extends PureComponent {
|
|||
};
|
||||
|
||||
handleKey = e => {
|
||||
const { tab, onCommand, onMessage,
|
||||
add, reset, increment, decrement, currentHistoryEntry } = this.props;
|
||||
const {
|
||||
tab,
|
||||
onCommand,
|
||||
onMessage,
|
||||
add,
|
||||
reset,
|
||||
increment,
|
||||
decrement,
|
||||
currentHistoryEntry
|
||||
} = this.props;
|
||||
|
||||
if (e.key === 'Enter' && e.target.value) {
|
||||
if (e.target.value[0] === '/') {
|
||||
|
@ -36,7 +44,12 @@ export default class MessageInput extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { nick, currentHistoryEntry, onNickChange, onNickEditDone } = this.props;
|
||||
const {
|
||||
nick,
|
||||
currentHistoryEntry,
|
||||
onNickChange,
|
||||
onNickEditDone
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="message-input-wrap">
|
||||
<Editable
|
||||
|
|
|
@ -8,7 +8,9 @@ export default class Search extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
inputRef = el => { this.input = el; };
|
||||
inputRef = el => {
|
||||
this.input = el;
|
||||
};
|
||||
|
||||
handleSearch = e => this.props.onSearch(e.target.value);
|
||||
|
||||
|
|
|
@ -11,12 +11,14 @@ export default class SearchResult extends PureComponent {
|
|||
|
||||
return (
|
||||
<p className="search-result" style={style}>
|
||||
<span className="message-time">{timestamp(new Date(result.time * 1000))}</span>
|
||||
<span className="message-time">
|
||||
{timestamp(new Date(result.time * 1000))}
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
<span className="message-sender">{result.from}</span>
|
||||
</span>
|
||||
<span>{' '}{linkify(result.content)}</span>
|
||||
<span> {linkify(result.content)}</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ export default class UserList extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
listRef = el => { this.list = el; };
|
||||
listRef = el => {
|
||||
this.list = el;
|
||||
};
|
||||
|
||||
renderUser = ({ index, style, key }) => {
|
||||
const { users, onNickClick } = this.props;
|
||||
|
|
|
@ -14,7 +14,10 @@ export default class Connect extends Component {
|
|||
|
||||
let address = e.target.address.value.trim();
|
||||
const nick = e.target.nick.value.trim();
|
||||
const channels = e.target.channels.value.split(',').map(s => s.trim()).filter(s => s);
|
||||
const channels = e.target.channels.value
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s);
|
||||
const opts = {
|
||||
name: e.target.name.value.trim(),
|
||||
tls: e.target.ssl.checked
|
||||
|
@ -78,18 +81,32 @@ export default class Connect extends Component {
|
|||
<Navicon />
|
||||
<form className="connect-form" onSubmit={this.handleSubmit}>
|
||||
<h1>Connect</h1>
|
||||
<input name="name" type="text" placeholder="Name" defaultValue={defaults.name} />
|
||||
<input name="address" type="text" placeholder="Address" defaultValue={defaults.address} />
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
defaultValue={defaults.name}
|
||||
/>
|
||||
<input
|
||||
name="address"
|
||||
type="text"
|
||||
placeholder="Address"
|
||||
defaultValue={defaults.address}
|
||||
/>
|
||||
<input name="nick" type="text" placeholder="Nick" />
|
||||
<input
|
||||
name="channels"
|
||||
type="text"
|
||||
placeholder="Channels"
|
||||
defaultValue={defaults.channels ? defaults.channels.join(',') : null}
|
||||
defaultValue={
|
||||
defaults.channels ? defaults.channels.join(',') : null
|
||||
}
|
||||
/>
|
||||
{optionals}
|
||||
<p>
|
||||
<label htmlFor="ssl"><input name="ssl" type="checkbox" defaultChecked={defaults.ssl} />SSL</label>
|
||||
<label htmlFor="ssl">
|
||||
<input name="ssl" type="checkbox" defaultChecked={defaults.ssl} />SSL
|
||||
</label>
|
||||
<i className="icon-ellipsis" onClick={this.handleShowClick} />
|
||||
</p>
|
||||
<input type="submit" value="Connect" />
|
||||
|
|
|
@ -26,7 +26,7 @@ const Settings = ({ settings, onCertChange, onKeyChange, uploadCert }) => {
|
|||
/>
|
||||
</div>
|
||||
<button onClick={uploadCert}>{status}</button>
|
||||
{ error ? <p className="error">{error}</p> : null }
|
||||
{error ? <p className="error">{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -30,7 +30,8 @@ export default class Editable extends PureComponent {
|
|||
getInputWidth(value) {
|
||||
if (this.input) {
|
||||
const style = window.getComputedStyle(this.input);
|
||||
const padding = parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10);
|
||||
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;
|
||||
|
@ -68,7 +69,9 @@ export default class Editable extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
inputRef = el => { this.input = el; }
|
||||
inputRef = el => {
|
||||
this.input = el;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, className, value } = this.props;
|
||||
|
@ -77,21 +80,21 @@ export default class Editable extends PureComponent {
|
|||
width: this.state.width
|
||||
};
|
||||
|
||||
return (
|
||||
this.state.editing ?
|
||||
<input
|
||||
autoFocus
|
||||
ref={this.inputRef}
|
||||
className={className}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKey}
|
||||
style={style}
|
||||
spellCheck={false}
|
||||
/> :
|
||||
<div onClick={this.startEditing}>{children}</div>
|
||||
return this.state.editing ? (
|
||||
<input
|
||||
autoFocus
|
||||
ref={this.inputRef}
|
||||
className={className}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKey}
|
||||
style={style}
|
||||
spellCheck={false}
|
||||
/>
|
||||
) : (
|
||||
<div onClick={this.startEditing}>{children}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,9 @@ export default class FileInput extends PureComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<button className="input-file" onClick={this.handleClick}>{this.props.name}</button>
|
||||
<button className="input-file" onClick={this.handleClick}>
|
||||
{this.props.name}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,35 @@ import { connect } from 'react-redux';
|
|||
import { createStructuredSelector } from 'reselect';
|
||||
import Chat from 'components/pages/Chat';
|
||||
import { getSelectedTabTitle } from 'state';
|
||||
import { getSelectedChannel, getSelectedChannelUsers, part } from 'state/channels';
|
||||
import { getCurrentInputHistoryEntry, addInputHistory, resetInputHistory,
|
||||
incrementInputHistory, decrementInputHistory } from 'state/input';
|
||||
import { getSelectedMessages, getHasMoreMessages,
|
||||
runCommand, sendMessage, fetchMessages, addFetchedMessages } from 'state/messages';
|
||||
import {
|
||||
getSelectedChannel,
|
||||
getSelectedChannelUsers,
|
||||
part
|
||||
} from 'state/channels';
|
||||
import {
|
||||
getCurrentInputHistoryEntry,
|
||||
addInputHistory,
|
||||
resetInputHistory,
|
||||
incrementInputHistory,
|
||||
decrementInputHistory
|
||||
} from 'state/input';
|
||||
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, getCurrentServerStatus, disconnect, setNick, setServerName } from 'state/servers';
|
||||
import {
|
||||
getCurrentNick,
|
||||
getCurrentServerStatus,
|
||||
disconnect,
|
||||
setNick,
|
||||
setServerName
|
||||
} from 'state/servers';
|
||||
import { getSelectedTab, select } from 'state/tab';
|
||||
import { getShowUserList, toggleUserList } from 'state/ui';
|
||||
|
||||
|
@ -29,29 +50,35 @@ const mapState = createStructuredSelector({
|
|||
});
|
||||
|
||||
const mapDispatch = dispatch => ({
|
||||
...bindActionCreators({
|
||||
addFetchedMessages,
|
||||
closePrivateChat,
|
||||
disconnect,
|
||||
fetchMessages,
|
||||
openPrivateChat,
|
||||
part,
|
||||
runCommand,
|
||||
searchMessages,
|
||||
select,
|
||||
sendMessage,
|
||||
setNick,
|
||||
setServerName,
|
||||
toggleSearch,
|
||||
toggleUserList
|
||||
}, dispatch),
|
||||
...bindActionCreators(
|
||||
{
|
||||
addFetchedMessages,
|
||||
closePrivateChat,
|
||||
disconnect,
|
||||
fetchMessages,
|
||||
openPrivateChat,
|
||||
part,
|
||||
runCommand,
|
||||
searchMessages,
|
||||
select,
|
||||
sendMessage,
|
||||
setNick,
|
||||
setServerName,
|
||||
toggleSearch,
|
||||
toggleUserList
|
||||
},
|
||||
dispatch
|
||||
),
|
||||
|
||||
inputActions: bindActionCreators({
|
||||
add: addInputHistory,
|
||||
reset: resetInputHistory,
|
||||
increment: incrementInputHistory,
|
||||
decrement: decrementInputHistory
|
||||
}, dispatch)
|
||||
inputActions: bindActionCreators(
|
||||
{
|
||||
add: addInputHistory,
|
||||
reset: resetInputHistory,
|
||||
increment: incrementInputHistory,
|
||||
decrement: decrementInputHistory
|
||||
},
|
||||
dispatch
|
||||
)
|
||||
});
|
||||
|
||||
export default connect(mapState, mapDispatch)(Chat);
|
||||
|
|
|
@ -10,7 +10,9 @@ import routes from './routes';
|
|||
import runModules from './modules';
|
||||
|
||||
const production = process.env.NODE_ENV === 'production';
|
||||
const host = production ? window.location.host : `${window.location.hostname}:1337`;
|
||||
const host = production
|
||||
? window.location.host
|
||||
: `${window.location.hostname}:1337`;
|
||||
const socket = new Socket(host);
|
||||
const store = configureStore(socket);
|
||||
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { socketAction } from 'state/actions';
|
||||
import { setConnected } from 'state/app';
|
||||
import { broadcast, inform, print, addMessage, addMessages } from 'state/messages';
|
||||
import {
|
||||
broadcast,
|
||||
inform,
|
||||
print,
|
||||
addMessage,
|
||||
addMessages
|
||||
} from 'state/messages';
|
||||
import { reconnect } from 'state/servers';
|
||||
import { select } from 'state/tab';
|
||||
import { normalizeChannel } from 'utils';
|
||||
|
@ -21,7 +27,10 @@ function findChannels(state, server, user) {
|
|||
return channels;
|
||||
}
|
||||
|
||||
export default function handleSocket({ socket, store: { dispatch, getState } }) {
|
||||
export default function handleSocket({
|
||||
socket,
|
||||
store: { dispatch, getState }
|
||||
}) {
|
||||
const handlers = {
|
||||
message(message) {
|
||||
dispatch(addMessage(message, message.server, message.to));
|
||||
|
@ -41,10 +50,12 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
const [joinedChannel] = channels;
|
||||
if (tab.server && tab.name) {
|
||||
const { nick } = state.servers.get(tab.server);
|
||||
if (tab.server === server &&
|
||||
if (
|
||||
tab.server === server &&
|
||||
nick === user &&
|
||||
tab.name !== joinedChannel &&
|
||||
normalizeChannel(tab.name) === normalizeChannel(joinedChannel)) {
|
||||
normalizeChannel(tab.name) === normalizeChannel(joinedChannel)
|
||||
) {
|
||||
dispatch(select(server, joinedChannel));
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +64,9 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
},
|
||||
|
||||
part({ user, server, channel, reason }) {
|
||||
dispatch(inform(withReason(`${user} left the channel`, reason), server, channel));
|
||||
dispatch(
|
||||
inform(withReason(`${user} left the channel`, reason), server, channel)
|
||||
);
|
||||
},
|
||||
|
||||
quit({ user, server, reason }) {
|
||||
|
@ -63,7 +76,9 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
|
||||
nick({ server, oldNick, newNick }) {
|
||||
const channels = findChannels(getState(), server, oldNick);
|
||||
dispatch(broadcast(`${oldNick} changed nick to ${newNick}`, server, channels));
|
||||
dispatch(
|
||||
broadcast(`${oldNick} changed nick to ${newNick}`, server, channels)
|
||||
);
|
||||
},
|
||||
|
||||
topic({ server, channel, topic, nick }) {
|
||||
|
@ -84,14 +99,20 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
whois(data) {
|
||||
const tab = getState().tab.selected;
|
||||
|
||||
dispatch(print([
|
||||
`Nick: ${data.nick}`,
|
||||
`Username: ${data.username}`,
|
||||
`Realname: ${data.realname}`,
|
||||
`Host: ${data.host}`,
|
||||
`Server: ${data.server}`,
|
||||
`Channels: ${data.channels}`
|
||||
], tab.server, tab.name));
|
||||
dispatch(
|
||||
print(
|
||||
[
|
||||
`Nick: ${data.nick}`,
|
||||
`Username: ${data.username}`,
|
||||
`Realname: ${data.realname}`,
|
||||
`Host: ${data.host}`,
|
||||
`Server: ${data.server}`,
|
||||
`Channels: ${data.channels}`
|
||||
],
|
||||
tab.server,
|
||||
tab.name
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
print(message) {
|
||||
|
@ -100,11 +121,17 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
},
|
||||
|
||||
connection_update({ server, errorType }) {
|
||||
if (errorType === 'verify' &&
|
||||
confirm('The server is using a self-signed certificate, continue anyway?')) {
|
||||
dispatch(reconnect(server, {
|
||||
skipVerify: true
|
||||
}));
|
||||
if (
|
||||
errorType === 'verify' &&
|
||||
confirm(
|
||||
'The server is using a self-signed certificate, continue anyway?'
|
||||
)
|
||||
) {
|
||||
dispatch(
|
||||
reconnect(server, {
|
||||
skipVerify: true
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { connect, setServerName } from '../servers';
|
||||
import { connect, setServerName } from '../servers';
|
||||
|
||||
describe('connect()', () => {
|
||||
it('sets host and port correctly', () => {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import Immutable from 'immutable';
|
||||
import reducer, { compareUsers } from '../channels';
|
||||
import { connect } from '../servers';
|
||||
import { connect } from '../servers';
|
||||
import * as actions from '../actions';
|
||||
|
||||
describe('channel reducer', () => {
|
||||
it('removes channels on PART', () => {
|
||||
let state = Immutable.fromJS({
|
||||
srv1: {
|
||||
chan1: {}, chan2: {}, chan3: {}
|
||||
chan1: {},
|
||||
chan2: {},
|
||||
chan3: {}
|
||||
},
|
||||
srv2: {
|
||||
chan1: {}
|
||||
|
@ -45,14 +47,10 @@ describe('channel reducer', () => {
|
|||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick1', renderName: 'nick1' },
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -64,9 +62,7 @@ describe('channel reducer', () => {
|
|||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick1', renderName: 'nick1' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -86,9 +82,7 @@ describe('channel reducer', () => {
|
|||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick1', renderName: 'nick1' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
},
|
||||
chan2: {
|
||||
users: []
|
||||
|
@ -118,9 +112,7 @@ describe('channel reducer', () => {
|
|||
]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -131,13 +123,7 @@ describe('channel reducer', () => {
|
|||
type: actions.socket.USERS,
|
||||
server: 'srv',
|
||||
channel: 'chan1',
|
||||
users: [
|
||||
'user3',
|
||||
'user2',
|
||||
'@user4',
|
||||
'user1',
|
||||
'+user5'
|
||||
]
|
||||
users: ['user3', 'user2', '@user4', 'user1', '+user5']
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
|
@ -152,7 +138,7 @@ describe('channel reducer', () => {
|
|||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('handles SOCKET_TOPIC', () => {
|
||||
|
@ -188,14 +174,12 @@ describe('channel reducer', () => {
|
|||
]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick1' ,'v', 'o'));
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick1', 'v', 'o'));
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick2', 'o', ''));
|
||||
state = reducer(state, socket_mode('srv', 'chan2', 'not_there', 'x', ''));
|
||||
|
||||
|
@ -208,9 +192,7 @@ describe('channel reducer', () => {
|
|||
]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -240,10 +222,7 @@ describe('channel reducer', () => {
|
|||
it('handles SOCKET_SERVERS', () => {
|
||||
const state = reducer(undefined, {
|
||||
type: actions.socket.SERVERS,
|
||||
data: [
|
||||
{ host: '127.0.0.1' },
|
||||
{ host: 'thehost' }
|
||||
]
|
||||
data: [{ host: '127.0.0.1' }, { host: 'thehost' }]
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
|
@ -280,7 +259,8 @@ describe('channel reducer', () => {
|
|||
function socket_join(server, channel, user) {
|
||||
return {
|
||||
type: actions.socket.JOIN,
|
||||
server, user,
|
||||
server,
|
||||
user,
|
||||
channels: [channel]
|
||||
};
|
||||
}
|
||||
|
@ -288,29 +268,35 @@ function socket_join(server, channel, user) {
|
|||
function socket_mode(server, channel, user, add, remove) {
|
||||
return {
|
||||
type: actions.socket.MODE,
|
||||
server, channel, user, add, remove
|
||||
server,
|
||||
channel,
|
||||
user,
|
||||
add,
|
||||
remove
|
||||
};
|
||||
}
|
||||
|
||||
describe('compareUsers()', () => {
|
||||
it('compares users correctly', () => {
|
||||
expect([
|
||||
{ renderName: 'user5' },
|
||||
{ renderName: '@user2' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user2' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: '~bob' },
|
||||
{ renderName: '%apples' },
|
||||
{ renderName: '&cake' }
|
||||
].sort(compareUsers)).toEqual([
|
||||
expect(
|
||||
[
|
||||
{ renderName: 'user5' },
|
||||
{ renderName: '@user2' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user2' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: '~bob' },
|
||||
{ renderName: '%apples' },
|
||||
{ renderName: '&cake' }
|
||||
].sort(compareUsers)
|
||||
).toEqual([
|
||||
{ renderName: '~bob' },
|
||||
{ renderName: '&cake' },
|
||||
{ renderName: '@user2' },
|
||||
{ renderName: '%apples' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: 'user2' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user5' }
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -17,10 +17,12 @@ describe('message reducer', () => {
|
|||
|
||||
expect(state.toJS()).toMatchObject({
|
||||
srv: {
|
||||
'#chan1': [{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}]
|
||||
'#chan1': [
|
||||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -34,10 +36,12 @@ describe('message reducer', () => {
|
|||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
from: 'bar',
|
||||
content: 'msg'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
tab: '#chan2',
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
|
@ -51,15 +55,18 @@ describe('message reducer', () => {
|
|||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
from: 'bar',
|
||||
content: 'msg'
|
||||
}
|
||||
],
|
||||
'#chan2': [{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}]
|
||||
'#chan2': [
|
||||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -92,10 +99,9 @@ describe('message reducer', () => {
|
|||
};
|
||||
|
||||
const thunk = broadcast('test', 'srv', ['#chan1', '#chan3']);
|
||||
thunk(
|
||||
action => { state.messages = reducer(undefined, action); },
|
||||
() => state
|
||||
);
|
||||
thunk(action => {
|
||||
state.messages = reducer(undefined, action);
|
||||
}, () => state);
|
||||
|
||||
const messages = state.messages.toJS();
|
||||
|
||||
|
@ -109,18 +115,11 @@ describe('message reducer', () => {
|
|||
it('deletes all messages related to server when disconnecting', () => {
|
||||
let state = fromJS({
|
||||
srv: {
|
||||
'#chan1': [
|
||||
{ content: 'msg1' },
|
||||
{ content: 'msg2' }
|
||||
],
|
||||
'#chan2': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
||||
'#chan2': [{ content: 'msg' }]
|
||||
},
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -131,9 +130,7 @@ describe('message reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -141,18 +138,11 @@ describe('message reducer', () => {
|
|||
it('deletes all messages related to channel when parting', () => {
|
||||
let state = fromJS({
|
||||
srv: {
|
||||
'#chan1': [
|
||||
{ content: 'msg1' },
|
||||
{ content: 'msg2' }
|
||||
],
|
||||
'#chan2': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
||||
'#chan2': [{ content: 'msg' }]
|
||||
},
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -164,14 +154,10 @@ describe('message reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
'#chan2': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan2': [{ content: 'msg' }]
|
||||
},
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,9 +32,12 @@ describe('server reducer', () => {
|
|||
}
|
||||
});
|
||||
|
||||
state = reducer(state, connect('127.0.0.2:1337', 'nick', {
|
||||
name: 'srv'
|
||||
}));
|
||||
state = reducer(
|
||||
state,
|
||||
connect('127.0.0.2:1337', 'nick', {
|
||||
name: 'srv'
|
||||
})
|
||||
);
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
'127.0.0.1': {
|
||||
|
@ -190,7 +193,7 @@ describe('server reducer', () => {
|
|||
status: {
|
||||
connected: false
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
|
|
@ -8,9 +8,7 @@ describe('tab reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: 'srv', name: '#chan' },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' }
|
||||
]
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
});
|
||||
|
||||
state = reducer(state, setSelectedTab('srv', 'user1'));
|
||||
|
@ -62,7 +60,7 @@ describe('tab reducer', () => {
|
|||
history: [
|
||||
{ server: 'srv', name: '#chan' },
|
||||
{ server: 'srv', name: '#chan' },
|
||||
{ server: 'srv', name: '#chan3' }
|
||||
{ server: 'srv', name: '#chan3' }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
@ -75,14 +73,12 @@ describe('tab reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.DISCONNECT,
|
||||
server: 'srv',
|
||||
server: 'srv'
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: 'srv', name: '#chan3' },
|
||||
history: [
|
||||
{ server: 'srv1', name: 'bob' },
|
||||
]
|
||||
history: [{ server: 'srv1', name: 'bob' }]
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -93,14 +89,13 @@ describe('tab reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: null, name: null },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' }
|
||||
]
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('selects the tab and adds it to history when navigating to a tab', () => {
|
||||
const state = reducer(undefined,
|
||||
const state = reducer(
|
||||
undefined,
|
||||
locationChanged('chat', {
|
||||
server: 'srv',
|
||||
name: '#chan'
|
||||
|
@ -109,9 +104,7 @@ describe('tab reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: 'srv', name: '#chan' },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' }
|
||||
]
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,11 +29,13 @@ function updateRenderName(user) {
|
|||
}
|
||||
|
||||
function createUser(nick, mode) {
|
||||
return updateRenderName(new User({
|
||||
nick,
|
||||
renderName: nick,
|
||||
mode: mode || ''
|
||||
}));
|
||||
return updateRenderName(
|
||||
new User({
|
||||
nick,
|
||||
renderName: nick,
|
||||
mode: mode || ''
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function loadUser(nick) {
|
||||
|
@ -80,13 +82,14 @@ export const getChannels = state => state.channels;
|
|||
|
||||
const key = (v, k) => k.toLowerCase();
|
||||
|
||||
export const getSortedChannels = createSelector(
|
||||
getChannels,
|
||||
channels => channels.withMutations(c =>
|
||||
c.forEach((server, address) =>
|
||||
c.update(address, chans => chans.sortBy(key))
|
||||
export const getSortedChannels = createSelector(getChannels, channels =>
|
||||
channels
|
||||
.withMutations(c =>
|
||||
c.forEach((server, address) =>
|
||||
c.update(address, chans => chans.sortBy(key))
|
||||
)
|
||||
)
|
||||
).sortBy(key)
|
||||
.sortBy(key)
|
||||
);
|
||||
|
||||
export const getSelectedChannel = createSelector(
|
||||
|
@ -125,7 +128,9 @@ export default createReducer(Map(), {
|
|||
[actions.socket.QUIT](state, { server, user }) {
|
||||
return state.withMutations(s => {
|
||||
s.get(server).forEach((v, channel) => {
|
||||
s.updateIn([server, channel, 'users'], users => users.filter(u => u.nick !== user));
|
||||
s.updateIn([server, channel, 'users'], users =>
|
||||
users.filter(u => u.nick !== user)
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -139,8 +144,8 @@ export default createReducer(Map(), {
|
|||
return users;
|
||||
}
|
||||
|
||||
return users.update(i,
|
||||
user => updateRenderName(user.set('nick', newNick))
|
||||
return users.update(i, user =>
|
||||
updateRenderName(user.set('nick', newNick))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -148,7 +153,8 @@ export default createReducer(Map(), {
|
|||
},
|
||||
|
||||
[actions.socket.USERS](state, { server, channel, users }) {
|
||||
return state.setIn([server, channel, 'users'],
|
||||
return state.setIn(
|
||||
[server, channel, 'users'],
|
||||
List(users.map(user => loadUser(user)))
|
||||
);
|
||||
},
|
||||
|
@ -183,10 +189,13 @@ export default createReducer(Map(), {
|
|||
|
||||
return state.withMutations(s => {
|
||||
data.forEach(channel => {
|
||||
s.setIn([channel.server, channel.name], Map({
|
||||
users: List(),
|
||||
topic: channel.topic
|
||||
}));
|
||||
s.setIn(
|
||||
[channel.server, channel.name],
|
||||
Map({
|
||||
users: List(),
|
||||
topic: channel.topic
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { List, Map, Record } from 'immutable';
|
||||
import { createSelector } from 'reselect';
|
||||
import { findBreakpoints, messageHeight, linkify, timestamp, isChannel } from 'utils';
|
||||
import {
|
||||
findBreakpoints,
|
||||
messageHeight,
|
||||
linkify,
|
||||
timestamp,
|
||||
isChannel
|
||||
} from 'utils';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getApp } from './app';
|
||||
import { getSelectedTab } from './tab';
|
||||
|
@ -24,7 +30,8 @@ export const getMessages = state => state.messages;
|
|||
export const getSelectedMessages = createSelector(
|
||||
getSelectedTab,
|
||||
getMessages,
|
||||
(tab, messages) => messages.getIn([tab.server, tab.name || tab.server], List())
|
||||
(tab, messages) =>
|
||||
messages.getIn([tab.server, tab.name || tab.server], List())
|
||||
);
|
||||
|
||||
export const getHasMoreMessages = createSelector(
|
||||
|
@ -37,18 +44,24 @@ export const getHasMoreMessages = createSelector(
|
|||
|
||||
export default createReducer(Map(), {
|
||||
[actions.ADD_MESSAGE](state, { server, tab, message }) {
|
||||
return state.updateIn([server, tab], List(), list => list.push(new Message(message)));
|
||||
return state.updateIn([server, tab], List(), list =>
|
||||
list.push(new Message(message))
|
||||
);
|
||||
},
|
||||
|
||||
[actions.ADD_MESSAGES](state, { server, tab, messages, prepend }) {
|
||||
return state.withMutations(s => {
|
||||
if (prepend) {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
s.updateIn([server, tab], List(), list => list.unshift(new Message(messages[i])));
|
||||
s.updateIn([server, tab], List(), list =>
|
||||
list.unshift(new Message(messages[i]))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
messages.forEach(message =>
|
||||
s.updateIn([server, message.tab || tab], List(), list => list.push(new Message(message)))
|
||||
s.updateIn([server, message.tab || tab], List(), list =>
|
||||
list.push(new Message(message))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -60,18 +73,28 @@ export default createReducer(Map(), {
|
|||
|
||||
[actions.PART](state, { server, channels }) {
|
||||
return state.withMutations(s =>
|
||||
channels.forEach(channel =>
|
||||
s.deleteIn([server, channel])
|
||||
)
|
||||
channels.forEach(channel => s.deleteIn([server, channel]))
|
||||
);
|
||||
},
|
||||
|
||||
[actions.UPDATE_MESSAGE_HEIGHT](state, { wrapWidth, charWidth, windowWidth }) {
|
||||
[actions.UPDATE_MESSAGE_HEIGHT](
|
||||
state,
|
||||
{ wrapWidth, charWidth, windowWidth }
|
||||
) {
|
||||
return state.withMutations(s =>
|
||||
s.forEach((server, serverKey) =>
|
||||
server.forEach((target, targetKey) =>
|
||||
target.forEach((message, index) => s.setIn([serverKey, targetKey, index, 'height'],
|
||||
messageHeight(message, wrapWidth, charWidth, 6 * charWidth, windowWidth))
|
||||
target.forEach((message, index) =>
|
||||
s.setIn(
|
||||
[serverKey, targetKey, index, 'height'],
|
||||
messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
6 * charWidth,
|
||||
windowWidth
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -111,7 +134,13 @@ function initMessage(message, tab, state) {
|
|||
|
||||
message.length = message.content.length;
|
||||
message.breakpoints = findBreakpoints(message.content);
|
||||
message.height = messageHeight(message, wrapWidth, charWidth, 6 * charWidth, windowWidth);
|
||||
message.height = messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
6 * charWidth,
|
||||
windowWidth
|
||||
);
|
||||
message.content = linkify(message.content);
|
||||
|
||||
return message;
|
||||
|
@ -175,10 +204,14 @@ export function sendMessage(content, to, server) {
|
|||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
tab: to,
|
||||
message: initMessage({
|
||||
from: state.servers.getIn([server, 'nick']),
|
||||
content
|
||||
}, to, state),
|
||||
message: initMessage(
|
||||
{
|
||||
from: state.servers.getIn([server, 'nick']),
|
||||
content
|
||||
},
|
||||
to,
|
||||
state
|
||||
),
|
||||
socket: {
|
||||
type: 'message',
|
||||
data: { content, to, server }
|
||||
|
@ -190,12 +223,13 @@ export function sendMessage(content, to, server) {
|
|||
export function addMessage(message, server, to) {
|
||||
const tab = getMessageTab(server, to);
|
||||
|
||||
return (dispatch, getState) => dispatch({
|
||||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
tab,
|
||||
message: initMessage(message, tab, getState())
|
||||
});
|
||||
return (dispatch, getState) =>
|
||||
dispatch({
|
||||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
tab,
|
||||
message: initMessage(message, tab, getState())
|
||||
});
|
||||
}
|
||||
|
||||
export function addMessages(messages, server, to, prepend, next) {
|
||||
|
@ -209,7 +243,9 @@ export function addMessages(messages, server, to, prepend, next) {
|
|||
messages[0].next = true;
|
||||
}
|
||||
|
||||
messages.forEach(message => initMessage(message, message.tab || tab, state));
|
||||
messages.forEach(message =>
|
||||
initMessage(message, message.tab || tab, state)
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: actions.ADD_MESSAGES,
|
||||
|
@ -222,25 +258,36 @@ export function addMessages(messages, server, to, prepend, next) {
|
|||
}
|
||||
|
||||
export function broadcast(message, server, channels) {
|
||||
return addMessages(channels.map(channel => ({
|
||||
tab: channel,
|
||||
content: message,
|
||||
type: 'info'
|
||||
})), server);
|
||||
return addMessages(
|
||||
channels.map(channel => ({
|
||||
tab: channel,
|
||||
content: message,
|
||||
type: 'info'
|
||||
})),
|
||||
server
|
||||
);
|
||||
}
|
||||
|
||||
export function print(message, server, channel, type) {
|
||||
if (Array.isArray(message)) {
|
||||
return addMessages(message.map(line => ({
|
||||
content: line,
|
||||
type
|
||||
})), server, channel);
|
||||
return addMessages(
|
||||
message.map(line => ({
|
||||
content: line,
|
||||
type
|
||||
})),
|
||||
server,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
return addMessage({
|
||||
content: message,
|
||||
type
|
||||
}, server, channel);
|
||||
return addMessage(
|
||||
{
|
||||
content: message,
|
||||
type
|
||||
},
|
||||
server,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
export function inform(message, server, channel) {
|
||||
|
|
|
@ -10,11 +10,12 @@ const lowerCaseValue = v => v.toLowerCase();
|
|||
|
||||
export const getSortedPrivateChats = createSelector(
|
||||
getPrivateChats,
|
||||
privateChats => privateChats.withMutations(p =>
|
||||
p.forEach((server, address) =>
|
||||
p.update(address, chats => chats.sortBy(lowerCaseValue))
|
||||
privateChats =>
|
||||
privateChats.withMutations(p =>
|
||||
p.forEach((server, address) =>
|
||||
p.update(address, chats => chats.sortBy(lowerCaseValue))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
function open(state, server, nick) {
|
||||
|
|
|
@ -45,10 +45,13 @@ export const getCurrentServerStatus = createSelector(
|
|||
export default createReducer(Map(), {
|
||||
[actions.CONNECT](state, { host, nick, options }) {
|
||||
if (!state.has(host)) {
|
||||
return state.set(host, new Server({
|
||||
nick,
|
||||
name: options.name || host
|
||||
}));
|
||||
return state.set(
|
||||
host,
|
||||
new Server({
|
||||
nick,
|
||||
name: options.name || host
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return state;
|
||||
|
@ -73,9 +76,8 @@ export default createReducer(Map(), {
|
|||
|
||||
[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.update(server, s =>
|
||||
s.set('nick', newNick).set('editedNick', null)
|
||||
);
|
||||
}
|
||||
return state;
|
||||
|
|
|
@ -40,19 +40,29 @@ export default createReducer(new State(), {
|
|||
[actions.SELECT_TAB]: selectTab,
|
||||
|
||||
[actions.PART](state, action) {
|
||||
return state.set('history', state.history.filter(tab =>
|
||||
!(tab.server === action.server && tab.name === action.channels[0])
|
||||
));
|
||||
return state.set(
|
||||
'history',
|
||||
state.history.filter(
|
||||
tab =>
|
||||
!(tab.server === action.server && tab.name === action.channels[0])
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
||||
return state.set('history', state.history.filter(tab =>
|
||||
!(tab.server === action.server && tab.name === action.nick)
|
||||
));
|
||||
return state.set(
|
||||
'history',
|
||||
state.history.filter(
|
||||
tab => !(tab.server === action.server && tab.name === action.nick)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, action) {
|
||||
return state.set('history', state.history.filter(tab => tab.server !== action.server));
|
||||
return state.set(
|
||||
'history',
|
||||
state.history.filter(tab => tab.server !== action.server)
|
||||
);
|
||||
},
|
||||
|
||||
[LOCATION_CHANGED](state, action) {
|
||||
|
|
|
@ -7,20 +7,24 @@ import createSocketMiddleware from './middleware/socket';
|
|||
import commands from './commands';
|
||||
|
||||
export default function configureStore(socket) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const composeEnhancers =
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
const reducer = createReducer(routeReducer);
|
||||
|
||||
const store = createStore(reducer, composeEnhancers(
|
||||
applyMiddleware(
|
||||
thunk,
|
||||
routeMiddleware,
|
||||
createSocketMiddleware(socket),
|
||||
message,
|
||||
commands
|
||||
const store = createStore(
|
||||
reducer,
|
||||
composeEnhancers(
|
||||
applyMiddleware(
|
||||
thunk,
|
||||
routeMiddleware,
|
||||
createSocketMiddleware(socket),
|
||||
message,
|
||||
commands
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export default class Socket {
|
|||
this.retry();
|
||||
};
|
||||
|
||||
this.ws.onmessage = (e) => {
|
||||
this.ws.onmessage = e => {
|
||||
this.setTimeoutPing();
|
||||
|
||||
const msg = JSON.parse(e.data);
|
||||
|
|
|
@ -2,55 +2,40 @@ import React from 'react';
|
|||
import linkify from '../linkify';
|
||||
|
||||
describe('linkify()', () => {
|
||||
const proto = href => href.indexOf('http') !== 0 ? `http://${href}` : href;
|
||||
const linkTo = href => <a href={proto(href)} rel="noopener noreferrer" target="_blank">{href}</a>;
|
||||
const proto = href => (href.indexOf('http') !== 0 ? `http://${href}` : href);
|
||||
const linkTo = href => (
|
||||
<a href={proto(href)} rel="noopener noreferrer" target="_blank">
|
||||
{href}
|
||||
</a>
|
||||
);
|
||||
|
||||
it('returns the arg when no matches are found', () => [
|
||||
null,
|
||||
undefined,
|
||||
10,
|
||||
false,
|
||||
true,
|
||||
'just some text',
|
||||
''
|
||||
].forEach(input => expect(linkify(input)).toBe(input)));
|
||||
it('returns the arg when no matches are found', () =>
|
||||
[null, undefined, 10, false, true, 'just some text', ''].forEach(input =>
|
||||
expect(linkify(input)).toBe(input)
|
||||
));
|
||||
|
||||
it('linkifies text', () => Object.entries({
|
||||
'google.com': linkTo('google.com'),
|
||||
'google.com stuff': [
|
||||
linkTo('google.com'),
|
||||
' stuff'
|
||||
],
|
||||
'cake google.com stuff': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff'
|
||||
],
|
||||
'cake google.com stuff https://google.com': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff ',
|
||||
linkTo('https://google.com')
|
||||
],
|
||||
'cake google.com stuff pie https://google.com ': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff pie ',
|
||||
linkTo('https://google.com'),
|
||||
' '
|
||||
],
|
||||
' google.com': [
|
||||
' ',
|
||||
linkTo('google.com')
|
||||
],
|
||||
'google.com ': [
|
||||
linkTo('google.com'),
|
||||
' '
|
||||
],
|
||||
'/google.com?': [
|
||||
'/',
|
||||
linkTo('google.com'),
|
||||
'?'
|
||||
]
|
||||
}).forEach(([ input, expected ]) => expect(linkify(input)).toEqual(expected)));
|
||||
it('linkifies text', () =>
|
||||
Object.entries({
|
||||
'google.com': linkTo('google.com'),
|
||||
'google.com stuff': [linkTo('google.com'), ' stuff'],
|
||||
'cake google.com stuff': ['cake ', linkTo('google.com'), ' stuff'],
|
||||
'cake google.com stuff https://google.com': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff ',
|
||||
linkTo('https://google.com')
|
||||
],
|
||||
'cake google.com stuff pie https://google.com ': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff pie ',
|
||||
linkTo('https://google.com'),
|
||||
' '
|
||||
],
|
||||
' google.com': [' ', linkTo('google.com')],
|
||||
'google.com ': [linkTo('google.com'), ' '],
|
||||
'/google.com?': ['/', linkTo('google.com'), '?']
|
||||
}).forEach(([input, expected]) =>
|
||||
expect(linkify(input)).toEqual(expected)
|
||||
));
|
||||
});
|
||||
|
|
|
@ -8,7 +8,10 @@ export function normalizeChannel(channel) {
|
|||
return channel;
|
||||
}
|
||||
|
||||
return channel.split('#').join('').toLowerCase();
|
||||
return channel
|
||||
.split('#')
|
||||
.join('')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function isChannel(name) {
|
||||
|
|
|
@ -30,12 +30,19 @@ export default function linkify(text) {
|
|||
}
|
||||
|
||||
result.push(
|
||||
<a target="_blank" rel="noopener noreferrer" href={match.getAnchorHref()}>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={match.getAnchorHref()}
|
||||
>
|
||||
{match.matchedText}
|
||||
</a>
|
||||
);
|
||||
} else if (typeof result[result.length - 1] === 'string') {
|
||||
result[result.length - 1] += text.slice(pos, match.offset + match.matchedText.length);
|
||||
result[result.length - 1] += text.slice(
|
||||
pos,
|
||||
match.offset + match.matchedText.length
|
||||
);
|
||||
} else {
|
||||
result.push(text.slice(pos, match.offset + match.matchedText.length));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,13 @@ export function findBreakpoints(text) {
|
|||
return breakpoints;
|
||||
}
|
||||
|
||||
export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowWidth) {
|
||||
export function messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
indent = 0,
|
||||
windowWidth
|
||||
) {
|
||||
let pad = (6 + (message.from ? message.from.length + 1 : 0)) * charWidth;
|
||||
let height = lineHeight + 8;
|
||||
|
||||
|
@ -26,7 +32,7 @@ export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowW
|
|||
wrapWidth -= userListWidth;
|
||||
}
|
||||
|
||||
if (pad + (message.length * charWidth) < wrapWidth) {
|
||||
if (pad + message.length * charWidth < wrapWidth) {
|
||||
return height;
|
||||
}
|
||||
|
||||
|
@ -35,7 +41,7 @@ export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowW
|
|||
let prevPos = 0;
|
||||
|
||||
for (let i = 0; i < breaks.length; i++) {
|
||||
if (pad + ((breaks[i].end - prevBreak) * charWidth) >= wrapWidth) {
|
||||
if (pad + (breaks[i].end - prevBreak) * charWidth >= wrapWidth) {
|
||||
prevBreak = prevPos;
|
||||
pad = indent;
|
||||
height += lineHeight;
|
||||
|
@ -44,7 +50,7 @@ export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowW
|
|||
prevPos = breaks[i].next;
|
||||
}
|
||||
|
||||
if (pad + ((message.length - prevBreak) * charWidth) >= wrapWidth) {
|
||||
if (pad + (message.length - prevBreak) * charWidth >= wrapWidth) {
|
||||
height += lineHeight;
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,10 @@ export default function initRouter(routes, store) {
|
|||
|
||||
history.listen(location => {
|
||||
const nextMatch = match(patterns, location);
|
||||
if (nextMatch && nextMatch.location.pathname !== matched.location.pathname) {
|
||||
if (
|
||||
nextMatch &&
|
||||
nextMatch.location.pathname !== matched.location.pathname
|
||||
) {
|
||||
matched = nextMatch;
|
||||
store.dispatch(matched);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue