Files
remark42/web/app/components/root/root.jsx
T

336 lines
9.2 KiB
React

/** @jsx h */
import { h, Component } from 'preact';
import api from 'common/api';
import {
BASE_URL,
NODE_ID,
COMMENT_NODE_CLASSNAME_PREFIX,
DEFAULT_SORT,
LS_SORT_KEY,
MAX_SHOWN_ROOT_COMMENTS,
} from 'common/constants';
import { siteId, url, maxShownComments } from 'common/settings';
import store from 'common/store';
import AuthPanel from 'components/auth-panel';
import BlockedUsers from 'components/blocked-users';
import Comment from 'components/comment';
import Input from 'components/input';
import Preloader from 'components/preloader';
import Thread from 'components/thread';
const IS_MOBILE = /Android|webOS|iPhone|iPad|iPod|Opera Mini|Windows Phone/i.test(navigator.userAgent);
export default class Root extends Component {
constructor(props) {
super(props);
let sort;
try {
sort = localStorage.getItem(LS_SORT_KEY) || DEFAULT_SORT;
} catch (e) {
sort = DEFAULT_SORT;
}
this.state = {
isLoaded: false,
isCommentsListLoading: false,
user: {},
sort,
commentsShown: maxShownComments || MAX_SHOWN_ROOT_COMMENTS,
};
this.addComment = this.addComment.bind(this);
this.replaceComment = this.replaceComment.bind(this);
this.onSignIn = this.onSignIn.bind(this);
this.onSignOut = this.onSignOut.bind(this);
this.onBlockedUsersShow = this.onBlockedUsersShow.bind(this);
this.onBlockedUsersHide = this.onBlockedUsersHide.bind(this);
this.onCommentsDisable = this.onCommentsDisable.bind(this);
this.onCommentsEnable = this.onCommentsEnable.bind(this);
this.onSortChange = this.onSortChange.bind(this);
this.onUnblockSomeone = this.onUnblockSomeone.bind(this);
this.checkUrlHash = this.checkUrlHash.bind(this);
this.showMore = this.showMore.bind(this);
}
componentWillMount() {
store.onUpdate('comments', comments => this.setState({ comments }));
store.onUpdate('info', info => this.setState({ info }));
}
componentDidMount() {
const { sort } = this.state;
api.getConfig().then(config => {
store.set('config', config);
this.setState({ config });
});
Promise.all([
api
.getUser()
.then(data => store.set('user', data))
.catch(() => store.set('user', {})),
api
.getPostComments({ sort, url })
.then(({ comments = [], info = {} } = {}) => {
store.set('comments', comments);
store.set('info', info);
})
.catch(() => store.set('comments', [])),
]).finally(() => {
this.setState({
isLoaded: true,
user: store.get('user'),
});
setTimeout(this.checkUrlHash);
window.addEventListener('hashchange', this.checkUrlHash);
});
}
checkUrlHash(e) {
const hash = e ? `#${e.newURL.split('#')[1]}` : window.location.hash;
if (hash.indexOf(`#${COMMENT_NODE_CLASSNAME_PREFIX}`) === 0) {
if (e) e.preventDefault();
const comment = document.querySelector(hash);
if (comment) {
setTimeout(() => {
window.parent.postMessage(JSON.stringify({ scrollTo: comment.getBoundingClientRect().top }), '*');
}, 500);
}
}
}
onSignOut() {
api.logOut().then(() => {
store.set('user', {});
this.setState({ user: {} });
});
}
onSignIn(provider) {
const newWindow = window.open(
`${BASE_URL}/auth/${provider}/login?from=${encodeURIComponent(
location.origin + location.pathname + '?selfClose'
)}&site=${siteId}`
);
let secondsPass = 0;
const checkMsDelay = 300;
const checkInterval = setInterval(() => {
let shouldProceed;
secondsPass += checkMsDelay;
try {
shouldProceed = newWindow.closed || secondsPass > 30000;
} catch (e) {}
if (shouldProceed) {
clearInterval(checkInterval);
api
.getUser()
.then(user => {
store.set('user', user);
this.setState({ user });
})
.catch(() => {}); // TODO: we need to handle it and write error to user
}
}, checkMsDelay);
}
onBlockedUsersShow() {
api.getBlocked().then(bannedUsers => {
this.setState({ bannedUsers, isBlockedVisible: true });
});
}
onCommentsEnable() {
api.enableComments(siteId, url).then(() => {
const info = store.get('info');
info.read_only = false;
this.setState({ info });
});
}
onCommentsDisable() {
api.disableComments(siteId, url).then(() => {
const info = store.get('info');
info.read_only = true;
this.setState({ info });
});
}
onBlockedUsersHide() {
const { wasSomeoneUnblocked, sort } = this.state;
// if someone was unblocked let's reload comments
if (wasSomeoneUnblocked) {
api.getPostComments({ sort, url }).then(({ comments, info } = {}) => {
store.set('comments', comments);
store.set('info', info);
});
}
this.setState({
isBlockedVisible: false,
wasSomeoneUnblocked: false,
});
}
onSortChange(sort) {
if (sort === this.state.sort) return;
this.setState({ sort, isCommentsListLoading: true });
try {
localStorage.setItem(LS_SORT_KEY, sort);
} catch (e) {
// can't save; ignore it
}
api
.getPostComments({ sort, url })
.then(({ comments, info } = {}) => {
store.set('comments', comments);
store.set('info', info);
})
.finally(() => {
this.setState({ isCommentsListLoading: false });
});
}
onUnblockSomeone() {
this.setState({ wasSomeoneUnblocked: true });
}
addComment(comment) {
store.addComment(comment);
}
replaceComment(comment) {
store.replaceComment(comment);
}
showMore() {
this.setState({
commentsShown: this.state.commentsShown + MAX_SHOWN_ROOT_COMMENTS,
});
}
render(
props,
{
config = {},
comments = [],
info = {},
user,
sort,
isLoaded,
isBlockedVisible,
isCommentsListLoading,
bannedUsers,
commentsShown,
}
) {
if (!isLoaded) {
return (
<div id={NODE_ID}>
<div className="root">
<Preloader mix="root__preloader" />
</div>
</div>
);
}
// TODO: i think we should do it on backend
const pinnedComments = store.getPinnedComments();
const isGuest = !Object.keys(user).length;
const isCommentsDisabled = info != null && info.read_only === true;
return (
<div id={NODE_ID}>
<div className="root">
<AuthPanel
user={user}
sort={sort}
providers={config.auth_providers}
isCommentsDisabled={isCommentsDisabled}
onSignIn={this.onSignIn}
onSignOut={this.onSignOut}
onBlockedUsersShow={this.onBlockedUsersShow}
onBlockedUsersHide={this.onBlockedUsersHide}
onCommentsEnable={this.onCommentsEnable}
onCommentsDisable={this.onCommentsDisable}
onSortChange={this.onSortChange}
/>
{!isBlockedVisible && (
<div className="root__main">
{!isGuest &&
!isCommentsDisabled && <Input mix="root__input" mods={{ type: 'main' }} onSubmit={this.addComment} />}
{!!pinnedComments.length && (
<div className="root__pinned-comments" role="region" aria-label="Pinned comments">
{pinnedComments.map(comment => (
<Comment data={comment} mods={{ level: 0, disabled: true }} mix="root__pinned-comment" />
))}
</div>
)}
{!!comments.length &&
!isCommentsListLoading && (
<div className="root__threads" role="list">
{(IS_MOBILE ? comments.slice(0, commentsShown) : comments).map(thread => (
<Thread
key={thread.comment.id}
mix="root__thread"
mods={{ level: 0 }}
data={thread}
isCommentsDisabled={isCommentsDisabled}
onReply={this.addComment}
onEdit={this.replaceComment}
/>
))}
{commentsShown < comments.length &&
IS_MOBILE && (
<button className="root__show-more" onClick={this.showMore}>
Show more
</button>
)}
</div>
)}
{isCommentsListLoading && (
<div className="root__threads" role="list">
<Preloader mix="root__preloader" />
</div>
)}
</div>
)}
{isBlockedVisible && (
<div className="root__main">
<BlockedUsers users={bannedUsers} onUnblock={this.onUnblockSomeone} />
</div>
)}
<p className="root__copyright" role="contentinfo">
Powered by{' '}
<a href="https://remark42.com/" className="root__copyright-link">
Remark42
</a>
</p>
</div>
</div>
);
}
}