Add topic modal

This commit is contained in:
Ken-Håvard Lieng 2019-01-25 03:57:58 +01:00
parent aab1ad3e99
commit 24960f23b9
11 changed files with 235 additions and 172 deletions

File diff suppressed because one or more lines are too long

View File

@ -19,6 +19,15 @@ h6 {
font-family: Montserrat, sans-serif; font-family: Montserrat, sans-serif;
} }
a {
text-decoration: none;
color: #0066ff;
}
a:hover {
text-decoration: underline;
}
input { input {
font: 16px Roboto Mono, monospace; font: 16px Roboto Mono, monospace;
border: none; border: none;
@ -504,20 +513,24 @@ input::-webkit-inner-spin-button {
display: none; display: none;
} }
.editable-wrap {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.editable-wrap-editable {
cursor: pointer;
}
.chat-title { .chat-title {
margin-left: 10px; margin-left: 15px;
padding: 0 5px;
font: 24px Montserrat, sans-serif; font: 24px Montserrat, sans-serif;
font-weight: 700; font-weight: 700;
color: #222; color: #222;
white-space: nowrap;
line-height: 50px; line-height: 50px;
} }
.chat-server .chat-title {
cursor: pointer;
}
input.chat-title { input.chat-title {
background: none; background: none;
cursor: text !important; cursor: text !important;
@ -526,7 +539,7 @@ input.chat-title {
.chat-topic-wrap { .chat-topic-wrap {
flex: 1; flex: 1;
position: relative; position: relative;
margin: 0 15px; margin-left: 15px;
} }
.chat-topic { .chat-topic {
@ -535,20 +548,12 @@ input.chat-title {
top: 3px; top: 3px;
font-size: 16px; font-size: 16px;
color: #999; color: #999;
cursor: pointer;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.chat-topic a {
color: #999;
text-decoration: none;
}
.chat-topic a:hover {
text-decoration: underline;
}
.userlist-bar { .userlist-bar {
position: absolute; position: absolute;
top: 0; top: 0;
@ -705,15 +710,6 @@ input.chat-title {
cursor: pointer; cursor: pointer;
} }
.message a {
text-decoration: none;
color: #0066ff;
}
.message a:hover {
text-decoration: underline;
}
.message-input-wrap { .message-input-wrap {
position: absolute; position: absolute;
left: 0; left: 0;
@ -751,7 +747,7 @@ input.message-input-nick.invalid {
flex: 1; flex: 1;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0 15px; padding: 0 10px;
} }
.userlist { .userlist {
@ -954,6 +950,27 @@ input.message-input-nick.invalid {
margin-top: 10px; margin-top: 10px;
} }
.modal-header {
display: flex;
}
.modal-header h2 {
flex: 1;
}
.modal-close {
color: #999;
cursor: pointer;
}
.modal-close:hover {
color: #222;
}
.modal-content {
margin-top: 15px;
}
.modal-channel { .modal-channel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -980,12 +997,6 @@ input.message-input-nick.invalid {
.modal-channel-close { .modal-channel-close {
padding: 15px; padding: 15px;
background: #fff; background: #fff;
color: #999;
cursor: pointer;
}
.modal-channel-close:hover {
color: #222;
} }
.modal-channel-result { .modal-channel-result {
@ -1057,8 +1068,12 @@ input.message-input-nick.invalid {
margin-left: 0; margin-left: 0;
} }
.chat-topic { .chat-title-bar .editable-wrap {
font-size: 12px; flex: 1;
}
.chat-topic-wrap {
display: none;
} }
.userlist-bar { .userlist-bar {

View File

@ -114,7 +114,10 @@ const AddChannel = ({ search, payload: { server }, onClose, ...props }) => {
onKeyDown={handleKey} onKeyDown={handleKey}
onChange={handleSearch} onChange={handleSearch}
/> />
<i className="icon-cancel modal-channel-close" onClick={onClose} /> <i
className="icon-cancel modal-close modal-channel-close"
onClick={onClose}
/>
</div> </div>
<div ref={resultsEl} className="modal-channel-results"> <div ref={resultsEl} className="modal-channel-results">
{search.results.map(channel => ( {search.results.map(channel => (

View File

@ -0,0 +1,19 @@
import React from 'react';
import withModal from 'components/modals/withModal';
import { linkify } from 'utils';
const Topic = ({ payload: { topic, channel }, onClose }) => {
return (
<>
<div className="modal-header">
<h2>Topic in {channel}</h2>
<i className="icon-cancel modal-close" onClick={onClose} />
</div>
<p className="modal-content">{linkify(topic)}</p>
</>
);
};
export default withModal({
name: 'topic'
})(Topic);

View File

@ -1,11 +1,13 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import AddChannel from 'components/modals/AddChannel'; import AddChannel from 'components/modals/AddChannel';
import Confirm from 'components/modals/Confirm'; import Confirm from 'components/modals/Confirm';
import Topic from 'components/modals/Topic';
const Modals = () => ( const Modals = () => (
<> <>
<AddChannel /> <AddChannel />
<Confirm /> <Confirm />
<Topic />
</> </>
); );

View File

@ -65,6 +65,7 @@ export default class Chat extends Component {
addFetchedMessages, addFetchedMessages,
fetchMessages, fetchMessages,
inputActions, inputActions,
openModal,
runCommand, runCommand,
sendMessage, sendMessage,
toggleSearch, toggleSearch,
@ -86,6 +87,7 @@ export default class Chat extends Component {
status={status} status={status}
tab={tab} tab={tab}
title={title} title={title}
openModal={openModal}
onCloseClick={this.handleCloseClick} onCloseClick={this.handleCloseClick}
onTitleChange={this.handleTitleChange} onTitleChange={this.handleTitleChange}
onToggleSearch={toggleSearch} onToggleSearch={toggleSearch}

View File

@ -2,13 +2,14 @@ import React, { memo } from 'react';
import Navicon from 'containers/Navicon'; import Navicon from 'containers/Navicon';
import Editable from 'components/ui/Editable'; import Editable from 'components/ui/Editable';
import { isValidServerName } from 'state/servers'; import { isValidServerName } from 'state/servers';
import { isChannel, linkify } from 'utils'; import { isChannel } from 'utils';
const ChatTitle = ({ const ChatTitle = ({
status, status,
title, title,
tab, tab,
channel, channel,
openModal,
onTitleChange, onTitleChange,
onToggleSearch, onToggleSearch,
onToggleUserList, onToggleUserList,
@ -44,9 +45,19 @@ const ChatTitle = ({
<span className="chat-title">{title}</span> <span className="chat-title">{title}</span>
</Editable> </Editable>
<div className="chat-topic-wrap"> <div className="chat-topic-wrap">
<span className="chat-topic"> {channel && channel.topic && (
{channel && linkify(channel.topic)} <span
</span> className="chat-topic"
onClick={() =>
openModal('topic', {
topic: channel.topic,
channel: channel.name
})
}
>
{channel.topic}
</span>
)}
{serverError} {serverError}
</div> </div>
<i className="icon-search" title="Search" onClick={onToggleSearch} /> <i className="icon-search" title="Search" onClick={onToggleSearch} />

View File

@ -1,4 +1,5 @@
import React, { PureComponent, createRef } from 'react'; import React, { PureComponent, createRef } from 'react';
import cn from 'classnames';
import { stringWidth } from 'utils'; import { stringWidth } from 'utils';
export default class Editable extends PureComponent { export default class Editable extends PureComponent {
@ -75,7 +76,7 @@ export default class Editable extends PureComponent {
}; };
render() { render() {
const { children, className, value } = this.props; const { children, className, editable, value } = this.props;
const style = { const style = {
width: this.state.width, width: this.state.width,
@ -86,7 +87,7 @@ export default class Editable extends PureComponent {
return this.state.editing ? ( return this.state.editing ? (
<input <input
ref={this.inputEl} ref={this.inputEl}
className={className} className={`editable-wrap ${className}`}
type="text" type="text"
value={value} value={value}
onBlur={this.handleBlur} onBlur={this.handleBlur}
@ -97,7 +98,14 @@ export default class Editable extends PureComponent {
spellCheck={false} spellCheck={false}
/> />
) : ( ) : (
<div onClick={this.startEditing}>{children}</div> <div
className={cn('editable-wrap', {
'editable-wrap-editable': editable
})}
onClick={this.startEditing}
>
{children}
</div>
); );
} }
} }

View File

@ -22,6 +22,7 @@ import {
fetchMessages, fetchMessages,
addFetchedMessages addFetchedMessages
} from 'state/messages'; } from 'state/messages';
import { openModal } from 'state/modals';
import { openPrivateChat, closePrivateChat } from 'state/privateChats'; import { openPrivateChat, closePrivateChat } from 'state/privateChats';
import { getSearch, searchMessages, toggleSearch } from 'state/search'; import { getSearch, searchMessages, toggleSearch } from 'state/search';
import { import {
@ -58,6 +59,7 @@ const mapDispatch = dispatch => ({
closePrivateChat, closePrivateChat,
disconnect, disconnect,
fetchMessages, fetchMessages,
openModal,
openPrivateChat, openPrivateChat,
part, part,
runCommand, runCommand,

View File

@ -61,7 +61,7 @@ function init(state, server, channel) {
state[server] = {}; state[server] = {};
} }
if (channel && !state[server][channel]) { if (channel && !state[server][channel]) {
state[server][channel] = { users: [], joined: false }; state[server][channel] = { name: channel, users: [], joined: false };
} }
} }
@ -135,6 +135,7 @@ export default createReducer(
[actions.socket.JOIN](state, { server, channels, user }) { [actions.socket.JOIN](state, { server, channels, user }) {
const channel = channels[0]; const channel = channels[0];
init(state, server, channel); init(state, server, channel);
state[server][channel].name = channel;
state[server][channel].joined = true; state[server][channel].joined = true;
state[server][channel].users.push(createUser(user)); state[server][channel].users.push(createUser(user));
}, },

View File

@ -80,7 +80,7 @@ func (d *Dispatch) initFileServer() {
if cfg.Dev { if cfg.Dev {
renderIndexPage(indexTemplateData{ renderIndexPage(indexTemplateData{
Scripts: []string{"boot.js", "main.js"}, Scripts: []string{"/boot.js", "/main.js"},
}) })
} else { } else {
bootloader := decompressedAsset(findAssetName("boot*.js")) bootloader := decompressedAsset(findAssetName("boot*.js"))