Merge branch 'master' of github.com:umputun/remark
This commit is contained in:
+5
-3
@@ -39,6 +39,8 @@ const (
|
||||
userLimit = 50
|
||||
)
|
||||
|
||||
const tsNano = "2006-01-02T15:04:05.000000000Z07:00"
|
||||
|
||||
// BoltSite defines single site param
|
||||
type BoltSite struct {
|
||||
FileName string // full path to boltdb
|
||||
@@ -117,7 +119,7 @@ func (b *BoltDB) Create(comment Comment) (commentID string, err error) {
|
||||
// add reference to comment to "last" bucket
|
||||
lastBkt := tx.Bucket([]byte(lastBucketName))
|
||||
ref := b.makeRef(comment)
|
||||
commentTs := []byte(comment.Timestamp.Format(time.RFC3339Nano))
|
||||
commentTs := []byte(comment.Timestamp.Format(tsNano))
|
||||
e = lastBkt.Put(commentTs, ref)
|
||||
if e != nil {
|
||||
return errors.Wrapf(e, "can't put reference %s to %s", ref, lastBucketName)
|
||||
@@ -288,7 +290,7 @@ func (b *BoltDB) SetBlock(siteID string, userID string, status bool) error {
|
||||
bucket := tx.Bucket([]byte(blocksBucketName))
|
||||
switch status {
|
||||
case true:
|
||||
if e := bucket.Put([]byte(userID), []byte(time.Now().Format(time.RFC3339Nano))); e != nil {
|
||||
if e := bucket.Put([]byte(userID), []byte(time.Now().Format(tsNano))); e != nil {
|
||||
return errors.Wrapf(e, "failed to put %s to %s", userID, blocksBucketName)
|
||||
}
|
||||
case false:
|
||||
@@ -328,7 +330,7 @@ func (b *BoltDB) Blocked(siteID string) (users []BlockedUser, err error) {
|
||||
err = bdb.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(blocksBucketName))
|
||||
return bucket.ForEach(func(k []byte, v []byte) error {
|
||||
ts, e := time.ParseInLocation(time.RFC3339Nano, string(v), time.Local)
|
||||
ts, e := time.ParseInLocation(tsNano, string(v), time.Local)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "can't parse block ts")
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
major features:
|
||||
|
||||
- improve design
|
||||
- add true preloader
|
||||
- disable inputs for guests
|
||||
- hide reply links for guests
|
||||
- check mobile ui
|
||||
- add icons for social networks
|
||||
- remove grey avatars 'cause we have default img
|
||||
- add time format 'X hours ago'
|
||||
- use static buttons 'reply', 'pin', etc instead of dynamic
|
||||
- maybe we should change layout: vote buttons | avatar | info; info: (top: name, points, time), (center: text), (bottom: controls)
|
||||
- edit comment
|
||||
- `PUT /api/v1/comment/{id}?site=site-id&url=post-url`
|
||||
- add description of web part to readme
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
.auth-panel__pseudo-link {
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
color: #06c5c5;
|
||||
color: #0aa;
|
||||
|
||||
&:hover {
|
||||
color: #06c5c5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.comment__action {
|
||||
margin-left: 8px;
|
||||
font-weight: 700;
|
||||
color: #777;
|
||||
color: #0aa;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -8,5 +8,4 @@
|
||||
height: $avatarSize;
|
||||
margin-right: $avatarOffset;
|
||||
border-radius: 4px;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
user-select: none;
|
||||
|
||||
@media (hover: hover) {
|
||||
margin-left: 0;
|
||||
transition: transform .2s .1s ease-out, opacity .2s .1s ease-out;
|
||||
font-weight: 700;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,28 @@
|
||||
margin: 0;
|
||||
|
||||
+ p {
|
||||
margin-top: 5px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
// imported comments have br instead of p
|
||||
br {
|
||||
content: '';
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0aa;
|
||||
|
||||
&:hover {
|
||||
color: #06c5c5;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.comment__username {
|
||||
color: #06c5c5;
|
||||
color: #777;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.comment_view_admin {
|
||||
.comment__username {
|
||||
font-weight: 700;
|
||||
color: #ff4700;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ export default class Comment extends Component {
|
||||
render(props, { userBlocked, pinned, score, scoreIncreased, scoreDecreased, isInputVisible }) {
|
||||
const { data, mix, mods = {} } = props;
|
||||
const isAdmin = store.get('user').admin;
|
||||
const isGuest = !Object.keys(store.get('user')).length;
|
||||
|
||||
const time = new Date(data.time);
|
||||
// TODO: which format for datetime should we choose?
|
||||
@@ -176,7 +177,6 @@ export default class Comment extends Component {
|
||||
|
||||
<div className="comment__content">
|
||||
<div className="comment__info">
|
||||
{/* TODO: add link to user's profile in social if we have it */}
|
||||
<span className="comment__username">{o.user.name}</span>
|
||||
|
||||
<span className="comment__score">
|
||||
@@ -195,7 +195,7 @@ export default class Comment extends Component {
|
||||
<span className="comment__time">{o.time}</span>
|
||||
|
||||
{
|
||||
!mods.disabled && (
|
||||
!mods.disabled && !isGuest && (
|
||||
<span className="comment__controls">
|
||||
<span className="comment__action" onClick={this.onReplyClick}>reply</span>
|
||||
</span>
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
.comment {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: $offset;
|
||||
margin-bottom: $offset / 2;
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
.comment__controls {
|
||||
transform: translateX(8px);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
width: $buttonWidth;
|
||||
height: 100%;
|
||||
padding: 0 10px;
|
||||
font-size: 16px;
|
||||
border: 0;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
.preloader__bounce {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 3px;
|
||||
background-color: #333;
|
||||
border-radius: 100%;
|
||||
animation: preloaderBounce 1.4s infinite ease-in-out both;
|
||||
|
||||
&:first-child {
|
||||
animation-delay: -.32s;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: -.16s;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@keyframes preloaderBounce {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
80%, 100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default } from './preloader';
|
||||
|
||||
require('./preloader.scss');
|
||||
|
||||
require('./__bounce/preloader__bounce.scss');
|
||||
@@ -0,0 +1,13 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
export default class Preloader extends Component {
|
||||
render(props, state) {
|
||||
return (
|
||||
<div className={b('preloader', props)}>
|
||||
<div className="preloader__bounce"/>
|
||||
<div className="preloader__bounce"/>
|
||||
<div className="preloader__bounce"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.preloader {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.root__auth-panel {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
.root__input {
|
||||
margin-bottom: 24px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import 'common/variables';
|
||||
|
||||
.root__pinned-comments {
|
||||
margin-bottom: $offset * 2;
|
||||
margin-top: 24px;
|
||||
padding: ($offset / 3) ($offset / 2);
|
||||
border: 2px solid #0aa;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.root__preloader {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.root__threads {
|
||||
margin-top: 24px;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// TODO: add true preloader
|
||||
|
||||
.root_loading {
|
||||
position: relative;
|
||||
height: 3em;
|
||||
|
||||
&::after {
|
||||
content: 'Loading..';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
animation: rootLoading 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes rootLoading {
|
||||
0% {
|
||||
content: 'Loading..';
|
||||
}
|
||||
|
||||
25% {
|
||||
content: 'Loading...';
|
||||
}
|
||||
|
||||
50% {
|
||||
content: 'Loading..';
|
||||
}
|
||||
|
||||
75% {
|
||||
content: 'Loading.';
|
||||
}
|
||||
|
||||
100% {
|
||||
content: 'Loading.';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@ export { default } from './root';
|
||||
|
||||
require('./root.scss');
|
||||
|
||||
require('./__auth-panel/root__auth-panel.scss');
|
||||
require('./__input/root__input.scss');
|
||||
require('./__preloader/root__preloader.scss');
|
||||
require('./__pinned-comment/root__pinned-comment.scss');
|
||||
require('./__pinned-comments/root__pinned-comments.scss');
|
||||
require('./__thread/root__thread.scss');
|
||||
|
||||
require('./_loading/root_loading.scss');
|
||||
require('./__threads/root__threads.scss');
|
||||
|
||||
@@ -8,6 +8,7 @@ import store from 'common/store';
|
||||
import AuthPanel from 'components/auth-panel';
|
||||
import Comment from 'components/comment';
|
||||
import Input from 'components/input';
|
||||
import Preloader from 'components/preloader';
|
||||
import Thread from 'components/thread';
|
||||
|
||||
export default class Root extends Component {
|
||||
@@ -91,29 +92,35 @@ export default class Root extends Component {
|
||||
if (!loaded) {
|
||||
return (
|
||||
<div id={NODE_ID}>
|
||||
<div className="root root_loading"/>
|
||||
<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;
|
||||
|
||||
return (
|
||||
<div id={NODE_ID}>
|
||||
<div className="root root__loading">
|
||||
<div className="root">
|
||||
<AuthPanel
|
||||
mix="root__auth-panel"
|
||||
user={user}
|
||||
providers={config.auth_providers}
|
||||
onSignIn={this.onSignIn}
|
||||
onSignOut={this.onSignOut}
|
||||
/>
|
||||
|
||||
<Input
|
||||
mix="root__input"
|
||||
onSubmit={this.addComment}
|
||||
/>
|
||||
{
|
||||
!isGuest && (
|
||||
<Input
|
||||
mix="root__input"
|
||||
onSubmit={this.addComment}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!!pinnedComments.length && (
|
||||
@@ -132,14 +139,20 @@ export default class Root extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
comments.map(thread => (
|
||||
<Thread
|
||||
mix="root__thread"
|
||||
mods={{ level: 0 }}
|
||||
data={thread}
|
||||
onReply={this.addComment}
|
||||
/>
|
||||
))
|
||||
!!comments.length && (
|
||||
<div className="root__threads">
|
||||
{
|
||||
comments.map(thread => (
|
||||
<Thread
|
||||
mix="root__thread"
|
||||
mods={{ level: 0 }}
|
||||
data={thread}
|
||||
onReply={this.addComment}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.root {
|
||||
position: relative;
|
||||
font-family: 'PT Sans', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import 'common/variables';
|
||||
|
||||
.thread {
|
||||
margin-bottom: $offset;
|
||||
margin-bottom: $offset / 2;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const commonStyleLoaders = [
|
||||
plugins: [
|
||||
require('autoprefixer')({ browsers: ['> 1%'] }),
|
||||
require('postcss-url')({ url: 'inline', maxSize: 5 }),
|
||||
require('postcss-wrap')({ selector: `#${NODE_ID}` }),
|
||||
require('postcss-wrap')({ selector: `#${NODE_ID}` , skip: /^html|body$/}),
|
||||
require('postcss-csso'),
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user