Add date markers
This commit is contained in:
parent
34d89c75b2
commit
50d735aaa3
File diff suppressed because one or more lines are too long
@ -595,8 +595,6 @@ input.chat-title {
|
||||
top: 50px;
|
||||
bottom: 50px;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-channel .messagebox {
|
||||
@ -615,6 +613,24 @@ input.chat-title {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.messagebox-topdate-container {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.messagebox-topdate {
|
||||
position: relative;
|
||||
top: -12px;
|
||||
background: #f0f0f0;
|
||||
color: #999;
|
||||
border-radius: 50vh;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 4px 15px;
|
||||
}
|
||||
@ -637,6 +653,18 @@ input.chat-title {
|
||||
color: #ff6698;
|
||||
}
|
||||
|
||||
.message-date {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.message-date hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@ -706,7 +734,7 @@ input.message-input-nick.invalid {
|
||||
width: 200px;
|
||||
border-left: 1px solid #ddd;
|
||||
background: #f0f0f0;
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,7 @@ export default class Chat extends Component {
|
||||
hasMoreMessages={hasMoreMessages}
|
||||
messages={messages}
|
||||
tab={tab}
|
||||
hideTopDate={search.show}
|
||||
onAddMore={addFetchedMessages}
|
||||
onFetchMore={fetchMessages}
|
||||
onNickClick={this.handleNickClick}
|
||||
|
@ -7,6 +7,15 @@ const Message = ({ message, coloredNick, style, onNickClick }) => {
|
||||
[`message-${message.type}`]: message.type
|
||||
});
|
||||
|
||||
if (message.type === 'date') {
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
{message.content}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
style = {
|
||||
...style,
|
||||
paddingLeft: `${window.messageIndent + 15}px`,
|
||||
|
@ -2,6 +2,7 @@ import React, { PureComponent, createRef } from 'react';
|
||||
import { VariableSizeList as List } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { formatDate, measureScrollBarWidth } from 'utils';
|
||||
import { getScrollPos, saveScrollPos } from 'utils/scrollPosition';
|
||||
import { windowHeight } from 'utils/size';
|
||||
import Message from './Message';
|
||||
@ -12,7 +13,10 @@ const fetchThreshold = 600;
|
||||
// this is done to prevent the scroll from jumping all over the place
|
||||
const scrollbackDebounce = 150;
|
||||
|
||||
const scrollBarWidth = measureScrollBarWidth() + 'px';
|
||||
|
||||
export default class MessageBox extends PureComponent {
|
||||
state = { topDate: '' };
|
||||
list = createRef();
|
||||
outer = createRef();
|
||||
|
||||
@ -177,6 +181,17 @@ export default class MessageBox extends PureComponent {
|
||||
this.bottom = scrollOffset + clientHeight >= scrollHeight - 20;
|
||||
};
|
||||
|
||||
handleItemsRendered = ({ visibleStartIndex }) => {
|
||||
const startIndex = visibleStartIndex === 0 ? 0 : visibleStartIndex - 1;
|
||||
const firstVisibleMessage = this.props.messages[startIndex];
|
||||
|
||||
if (firstVisibleMessage && firstVisibleMessage.date) {
|
||||
this.setState({ topDate: formatDate(firstVisibleMessage.date) });
|
||||
} else {
|
||||
this.setState({ topDate: '' });
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseDown = () => {
|
||||
this.mouseDown = true;
|
||||
};
|
||||
@ -221,12 +236,27 @@ export default class MessageBox extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { messages, hideTopDate } = this.props;
|
||||
const { topDate } = this.state;
|
||||
|
||||
const dateContainerStyle = {
|
||||
right: scrollBarWidth
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="messagebox"
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}
|
||||
>
|
||||
<div
|
||||
className="messagebox-topdate-container"
|
||||
style={dateContainerStyle}
|
||||
>
|
||||
{!hideTopDate && topDate && (
|
||||
<span className="messagebox-topdate">{topDate}</span>
|
||||
)}
|
||||
</div>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<List
|
||||
@ -234,12 +264,13 @@ export default class MessageBox extends PureComponent {
|
||||
outerRef={this.outer}
|
||||
width={width}
|
||||
height={height}
|
||||
itemCount={this.props.messages.length + 2}
|
||||
itemCount={messages.length + 2}
|
||||
itemKey={this.getItemKey}
|
||||
itemSize={this.getRowHeight}
|
||||
estimatedItemSize={32}
|
||||
initialScrollOffset={this.initialScrollTop}
|
||||
onScroll={this.handleScroll}
|
||||
onItemsRendered={this.handleItemsRendered}
|
||||
className="messagebox-window"
|
||||
overscanCount={5}
|
||||
>
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
messageHeight,
|
||||
linkify,
|
||||
timestamp,
|
||||
isChannel
|
||||
isChannel,
|
||||
formatDate
|
||||
} from 'utils';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getApp } from './app';
|
||||
@ -43,22 +44,71 @@ function init(state, server, tab) {
|
||||
}
|
||||
}
|
||||
|
||||
let nextID = 0;
|
||||
|
||||
function createDateMessage(date) {
|
||||
const message = {
|
||||
id: nextID,
|
||||
type: 'date',
|
||||
content: formatDate(date),
|
||||
height: 40
|
||||
};
|
||||
|
||||
nextID++;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
function isSameDay(d1, d2) {
|
||||
return (
|
||||
d1.getDate() === d2.getDate() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getFullYear() === d2.getFullYear()
|
||||
);
|
||||
}
|
||||
|
||||
function reducerAddMessage(message, server, tab, state, prepend) {
|
||||
const messages = state[server][tab];
|
||||
|
||||
if (messages.length > 0) {
|
||||
if (prepend) {
|
||||
const firstMessage = messages[0];
|
||||
if (firstMessage.date && !isSameDay(firstMessage.date, message.date)) {
|
||||
messages.unshift(createDateMessage(firstMessage.date));
|
||||
}
|
||||
} else {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (lastMessage.date && !isSameDay(lastMessage.date, message.date)) {
|
||||
messages.push(createDateMessage(message.date));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prepend) {
|
||||
messages.unshift(message);
|
||||
} else {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
export default createReducer(
|
||||
{},
|
||||
{
|
||||
[actions.ADD_MESSAGE](state, { server, tab, message }) {
|
||||
init(state, server, tab);
|
||||
state[server][tab].push(message);
|
||||
reducerAddMessage(message, server, tab, state);
|
||||
},
|
||||
|
||||
[actions.ADD_MESSAGES](state, { server, tab, messages, prepend }) {
|
||||
if (prepend) {
|
||||
init(state, server, tab);
|
||||
state[server][tab].unshift(...messages);
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
reducerAddMessage(messages[i], server, tab, state, true);
|
||||
}
|
||||
} else {
|
||||
messages.forEach(message => {
|
||||
init(state, server, message.tab || tab);
|
||||
state[server][message.tab || tab].push(message);
|
||||
reducerAddMessage(message, server, message.tab || tab, state);
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -78,6 +128,10 @@ export default createReducer(
|
||||
Object.keys(state).forEach(server =>
|
||||
Object.keys(state[server]).forEach(target =>
|
||||
state[server][target].forEach(message => {
|
||||
if (message.type === 'date') {
|
||||
return;
|
||||
}
|
||||
|
||||
message.height = messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
@ -100,15 +154,15 @@ export default createReducer(
|
||||
}
|
||||
);
|
||||
|
||||
let nextID = 0;
|
||||
|
||||
function initMessage(message, tab, state) {
|
||||
if (message.time) {
|
||||
message.time = timestamp(new Date(message.time * 1000));
|
||||
message.date = new Date(message.time * 1000);
|
||||
} else {
|
||||
message.time = timestamp();
|
||||
message.date = new Date();
|
||||
}
|
||||
|
||||
message.time = timestamp(message.date);
|
||||
|
||||
if (!message.id) {
|
||||
message.id = nextID;
|
||||
nextID++;
|
||||
|
@ -137,6 +137,9 @@ export function timestamp(date = new Date()) {
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
|
||||
const dateFmt = new Intl.DateTimeFormat(window.navigator.language);
|
||||
export const formatDate = dateFmt.format;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user