mirror of
https://github.com/wassname/talk.git
synced 2026-07-05 03:57:01 +08:00
Merge branch 'master' into docs-update
This commit is contained in:
@@ -14,13 +14,13 @@ const ModerationHeader = props => (
|
||||
<span>{props.asset.title}</span>
|
||||
<Icon className={styles.settingsButton} name="open_in_new"/>
|
||||
</a>
|
||||
<Link className="mdl-tabs__tab" to="/admin/streams">Select Stream</Link>
|
||||
<Link className="mdl-tabs__tab" to="/admin/stories">Select Stream</Link>
|
||||
</div>
|
||||
:
|
||||
<div className={`mdl-tabs__tab-bar ${styles.moderateAsset}`}>
|
||||
<a className="mdl-tabs__tab" />
|
||||
<a className="mdl-tabs__tab">All Streams</a>
|
||||
<Link className="mdl-tabs__tab" to="/admin/streams">Select Stream</Link>
|
||||
<Link className="mdl-tabs__tab" to="/admin/stories">Select Stream</Link>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ const NotFound = props => (
|
||||
<div className={`mdl-card mdl-shadow--2dp ${styles.notFound}`}>
|
||||
<p>
|
||||
The provided asset id <Link to={`/admin/moderate/${props.assetId}`}>{props.assetId}</Link> does not exist.
|
||||
<Link className={styles.goToStreams} to="/admin/streams">Go to Streams</Link>
|
||||
<Link className={styles.goToStreams} to="/admin/stories">Go to Streams</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {Component} from 'react';
|
||||
import React from 'react';
|
||||
import {compose} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
@@ -36,15 +36,22 @@ import HighlightedComment from './Comment';
|
||||
import LoadMore from './LoadMore';
|
||||
import NewCount from './NewCount';
|
||||
|
||||
class Embed extends Component {
|
||||
class Embed extends React.Component {
|
||||
|
||||
state = {activeTab: 0, showSignInDialog: false, activeReplyBox: ''};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeTab: 0,
|
||||
showSignInDialog: false,
|
||||
activeReplyBox: ''
|
||||
};
|
||||
}
|
||||
|
||||
changeTab = (tab) => {
|
||||
const {isAdmin} = this.props.auth;
|
||||
|
||||
// Everytime the comes from another tab, the Stream needs to be updated.
|
||||
if (tab === 0 && isAdmin) {
|
||||
if (tab === 0) {
|
||||
this.props.viewAllComments();
|
||||
this.props.data.refetch();
|
||||
}
|
||||
|
||||
@@ -79,10 +86,12 @@ class Embed extends Component {
|
||||
if(!isEqual(nextProps.data.asset, this.props.data.asset)) {
|
||||
loadAsset(nextProps.data.asset);
|
||||
|
||||
const {getCounts, updateCountCache} = this.props;
|
||||
const {getCounts, updateCountCache, asset: {countCache}} = this.props;
|
||||
const {asset} = nextProps.data;
|
||||
|
||||
updateCountCache(asset.id, asset.commentCount);
|
||||
if (!countCache) {
|
||||
updateCountCache(asset.id, asset.commentCount);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
countPoll: setInterval(() => {
|
||||
@@ -127,6 +136,12 @@ class Embed extends Component {
|
||||
|
||||
const banned = user && user.status === 'BANNED';
|
||||
|
||||
const hasOlderComments = !!(
|
||||
asset &&
|
||||
asset.lastComment &&
|
||||
asset.lastComment.id !== asset.comments[asset.comments.length - 1].id
|
||||
);
|
||||
|
||||
const expandForLogin = showSignInDialog ? {
|
||||
minHeight: document.body.scrollHeight + 200
|
||||
} : {};
|
||||
@@ -144,7 +159,8 @@ class Embed extends Component {
|
||||
<div style={expandForLogin}>
|
||||
<div className="commentStream">
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab}>
|
||||
<Tab><Count count={asset.totalCommentCount}/></Tab>
|
||||
<Tab><Count count={asset.totalCommentCount}/>
|
||||
</Tab>
|
||||
<Tab>{lang.t('MY_COMMENTS')}</Tab>
|
||||
<Tab restricted={!isAdmin}>Configure Stream</Tab>
|
||||
</TabBar>
|
||||
@@ -259,7 +275,7 @@ class Embed extends Component {
|
||||
topLevel={true}
|
||||
assetId={asset.id}
|
||||
comments={asset.comments}
|
||||
moreComments={countCache[asset.id] > asset.comments.length}
|
||||
moreComments={hasOlderComments}
|
||||
loadMore={this.props.loadMore} />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ const onLoadMoreClick = ({loadMore, commentCount, firstCommentDate, assetId, upd
|
||||
const NewCount = (props) => {
|
||||
const newComments = props.commentCount - props.countCache;
|
||||
|
||||
return <div className='coral-new-comments'>
|
||||
return <div className='coral-new-comments coral-load-more'>
|
||||
{
|
||||
props.countCache && newComments > 0 ?
|
||||
<button onClick={onLoadMoreClick(props)} className='coral-load-more'>
|
||||
<button onClick={onLoadMoreClick(props)}>
|
||||
{newComments === 1
|
||||
? lang.t('newCount', newComments, lang.t('comment'))
|
||||
: lang.t('newCount', newComments, lang.t('comments'))}
|
||||
|
||||
@@ -38,6 +38,9 @@ query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comme
|
||||
}
|
||||
commentCount
|
||||
totalCommentCount
|
||||
lastComment {
|
||||
id
|
||||
}
|
||||
comments(limit: 10) {
|
||||
...commentView
|
||||
replyCount
|
||||
|
||||
@@ -3,6 +3,7 @@ const {
|
||||
GraphQLInterfaceType
|
||||
} = require('graphql');
|
||||
const debug = require('debug')('talk:graph:schema');
|
||||
const Joi = require('joi');
|
||||
|
||||
/**
|
||||
* XXX taken from graphql-js: src/execution/execute.js, because that function
|
||||
@@ -82,6 +83,8 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa
|
||||
Object.keys(hooks).forEach((hook) => {
|
||||
switch (hook) {
|
||||
case 'pre':
|
||||
Joi.assert(hooks.pre, Joi.func().maxArity(4));
|
||||
|
||||
debug(`adding pre hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`);
|
||||
|
||||
if (typeof hooks.pre !== 'function') {
|
||||
@@ -91,6 +94,8 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa
|
||||
acc.pre.push(hooks.pre);
|
||||
break;
|
||||
case 'post':
|
||||
Joi.assert(hooks.pre, Joi.func().maxArity(5));
|
||||
|
||||
debug(`adding post hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`);
|
||||
|
||||
if (typeof hooks.post !== 'function') {
|
||||
@@ -129,6 +134,9 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure it matches the format we expect.
|
||||
Joi.assert(post, Joi.array().items(Joi.func().maxArity(3)), `invalid post hooks were found for ${typeName}.${fieldName}`);
|
||||
|
||||
// Cache the original resolverType function.
|
||||
let resolveType = field.resolveType;
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
const Asset = {
|
||||
lastComment({id}, _, {loaders: {Comments}}) {
|
||||
return Comments.getByQuery({
|
||||
asset_id: id,
|
||||
limit: 1,
|
||||
parent_id: null
|
||||
}).then(data => data[0]);
|
||||
},
|
||||
recentComments({id}, _, {loaders: {Comments}}) {
|
||||
return Comments.genRecentComments.load(id);
|
||||
},
|
||||
|
||||
@@ -413,6 +413,9 @@ type Asset {
|
||||
# The URL that the asset is located on.
|
||||
url: String
|
||||
|
||||
# Returns last comment
|
||||
lastComment: Comment
|
||||
|
||||
# Returns recent comments
|
||||
recentComments: [Comment]
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ const AssetSchema = new Schema({
|
||||
type: Schema.Types.Mixed,
|
||||
default: null
|
||||
},
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object
|
||||
}
|
||||
}, {
|
||||
versionKey: false,
|
||||
timestamps: {
|
||||
|
||||
+7
-1
@@ -75,7 +75,13 @@ const CommentSchema = new Schema({
|
||||
default: 'NONE'
|
||||
},
|
||||
tags: [TagSchema],
|
||||
parent_id: String
|
||||
parent_id: String,
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object
|
||||
}
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* The key must be composed of alpha characters with periods seperating them.
|
||||
*/
|
||||
const KEY_REGEX = /^(?:[A-Za-z][A-Za-z\.]*[A-Za-z])?(?:[A-Za-z]*)$/;
|
||||
|
||||
/**
|
||||
* Allows metadata properties to be set/unset from specific models. It is the
|
||||
* expecatation of this API that the metadata field is either accessed later
|
||||
* directly, or accessed as a result of another database load rather than
|
||||
* this service providing an interface to do so.
|
||||
*
|
||||
* @class MetadataService
|
||||
*/
|
||||
class MetadataService {
|
||||
|
||||
/**
|
||||
* Parses a key by ensuring that if it is either a string, or an array with
|
||||
* only characters defined in the `KEY_REGEX`
|
||||
*
|
||||
* @static
|
||||
* @param {String|Array} key
|
||||
* @returns {String} string form of the key
|
||||
*
|
||||
* @memberOf Metadata
|
||||
*/
|
||||
static parseKey(key) {
|
||||
if (Array.isArray(key)) {
|
||||
key = key.join('.');
|
||||
}
|
||||
|
||||
if ((typeof key !== 'string') || !KEY_REGEX.test(key) || key.length === 0) {
|
||||
throw new Error(`${key} is not valid, only a-zA-Z. allowed`);
|
||||
}
|
||||
|
||||
return ['metadata', key].join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an object on the metadata field of an object.
|
||||
*
|
||||
* @static
|
||||
* @param {mongoose.Model} model the mongoose model for the object
|
||||
* @param {String} id the value for the field `id` of the model
|
||||
* @param {String|Array} key key for the metadata field
|
||||
* @param {any} value javascript object to set the value of the metadata to
|
||||
* @returns {Promise} resolves when the update is complete
|
||||
*
|
||||
* @memberOf Metadata
|
||||
*/
|
||||
static async set(model, id, key, value) {
|
||||
key = MetadataService.parseKey(key);
|
||||
|
||||
return model.update({id}, {
|
||||
$set: {
|
||||
[key]: value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the value for the metadata field as the specific key.
|
||||
*
|
||||
* @static
|
||||
* @param {mongoose.Model} model the mongoose model for the object
|
||||
* @param {String} id the value for the field `id` of the model
|
||||
* @param {String|Array} key key for the metadata field
|
||||
* @returns
|
||||
*
|
||||
* @memberOf Metadata
|
||||
*/
|
||||
static async unset(model, id, key) {
|
||||
key = MetadataService.parseKey(key);
|
||||
|
||||
return model.update({id}, {
|
||||
$unset: {[key]: ''}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetadataService;
|
||||
Reference in New Issue
Block a user