Add topic modal
This commit is contained in:
parent
aab1ad3e99
commit
24960f23b9
File diff suppressed because one or more lines are too long
@ -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 {
|
||||||
|
@ -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 => (
|
||||||
|
19
client/js/components/modals/Topic.js
Normal file
19
client/js/components/modals/Topic.js
Normal 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);
|
@ -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 />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -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} />
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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));
|
||||||
},
|
},
|
||||||
|
@ -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"))
|
||||||
|
Loading…
Reference in New Issue
Block a user