Add date markers

This commit is contained in:
Ken-Håvard Lieng 2018-12-14 14:24:23 +01:00
parent 34d89c75b2
commit 50d735aaa3
7 changed files with 218 additions and 92 deletions

File diff suppressed because one or more lines are too long

View File

@ -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;
}

View File

@ -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}

View File

@ -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`,

View File

@ -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}
>

View File

@ -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++;

View File

@ -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');