mirror of
https://github.com/wassname/talk.git
synced 2026-07-04 14:49:20 +08:00
Merge branch 'master' into search-stories
This commit is contained in:
@@ -11,4 +11,5 @@ plugins/*
|
||||
!plugins/coral-plugin-mod
|
||||
!plugins/coral-plugin-love
|
||||
!plugins/coral-plugin-viewing-options
|
||||
!plugins/coral-plugin-comment-content
|
||||
node_modules
|
||||
|
||||
@@ -24,5 +24,6 @@ plugins/*
|
||||
!plugins/coral-plugin-mod
|
||||
!plugins/coral-plugin-love
|
||||
!plugins/coral-plugin-viewing-options
|
||||
!plugins/coral-plugin-comment-content
|
||||
|
||||
**/node_modules/*
|
||||
|
||||
+108
-48
@@ -8,9 +8,12 @@ const program = require('./commander');
|
||||
const parseDuration = require('ms');
|
||||
const Table = require('cli-table');
|
||||
const AssetModel = require('../models/asset');
|
||||
const CommentModel = require('../models/comment');
|
||||
const AssetsService = require('../services/assets');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const scraper = require('../services/scraper');
|
||||
const util = require('./util');
|
||||
const inquirer = require('inquirer');
|
||||
|
||||
// Register the shutdown criteria.
|
||||
util.onshutdown([
|
||||
@@ -20,65 +23,112 @@ util.onshutdown([
|
||||
/**
|
||||
* Lists all the assets registered in the database.
|
||||
*/
|
||||
function listAssets() {
|
||||
AssetModel
|
||||
.find({})
|
||||
.sort({'created_at': 1})
|
||||
.then((asset) => {
|
||||
let table = new Table({
|
||||
head: [
|
||||
'ID',
|
||||
'Title',
|
||||
'URL'
|
||||
]
|
||||
});
|
||||
async function listAssets() {
|
||||
try {
|
||||
let assets = await AssetModel.find({}).sort({'created_at': 1});
|
||||
|
||||
asset.forEach((asset) => {
|
||||
table.push([
|
||||
asset.id,
|
||||
asset.title ? asset.title : '',
|
||||
asset.url ? asset.url : ''
|
||||
]);
|
||||
});
|
||||
|
||||
console.log(table.toString());
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
let table = new Table({
|
||||
head: [
|
||||
'ID',
|
||||
'Title',
|
||||
'URL'
|
||||
]
|
||||
});
|
||||
|
||||
assets.forEach((asset) => {
|
||||
table.push([
|
||||
asset.id,
|
||||
asset.title ? asset.title : '',
|
||||
asset.url ? asset.url : ''
|
||||
]);
|
||||
});
|
||||
|
||||
console.log(table.toString());
|
||||
util.shutdown();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
util.shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAssets(ageString) {
|
||||
const now = new Date().getTime();
|
||||
const ageMs = parseDuration(ageString);
|
||||
const age = new Date(now - ageMs);
|
||||
async function refreshAssets(ageString) {
|
||||
try {
|
||||
const now = new Date().getTime();
|
||||
const ageMs = parseDuration(ageString);
|
||||
const age = new Date(now - ageMs);
|
||||
|
||||
AssetModel.find({
|
||||
$or: [
|
||||
{
|
||||
scraped: {
|
||||
$lte: age
|
||||
let assets = await AssetModel.find({
|
||||
$or: [
|
||||
{
|
||||
scraped: {
|
||||
$lte: age
|
||||
}
|
||||
},
|
||||
{
|
||||
scraped: null
|
||||
}
|
||||
},
|
||||
{
|
||||
scraped: null
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// Queue all the assets for scraping.
|
||||
.then((assets) => Promise.all(assets.map(scraper.create)))
|
||||
// Queue all the assets for scraping.
|
||||
await Promise.all(assets.map(scraper.create));
|
||||
|
||||
.then(() => {
|
||||
console.log('Assets were queued to be scraped');
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
util.shutdown(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateURL(assetID, assetURL) {
|
||||
try {
|
||||
await AssetsService.updateURL(assetID, assetURL);
|
||||
|
||||
console.log(`Asset ${assetID} was updated to have url ${assetURL}.`);
|
||||
util.shutdown();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
util.shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function merge(srcID, dstID) {
|
||||
try {
|
||||
|
||||
// Grab the assets...
|
||||
let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcID, dstID]);
|
||||
if (!srcAsset || !dstAsset) {
|
||||
throw new Error('Not all assets indicated by id exist, cannot merge');
|
||||
}
|
||||
|
||||
// Count the affected resources...
|
||||
let srcCommentCount = await CommentModel.find({asset_id: srcID}).count();
|
||||
|
||||
console.log(`Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].`);
|
||||
|
||||
let {confirm} = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: 'Proceed with merge',
|
||||
default: false
|
||||
}
|
||||
]);
|
||||
|
||||
if (confirm) {
|
||||
|
||||
// Perform the merge!
|
||||
await AssetsService.merge(srcID, dstID);
|
||||
} else {
|
||||
console.warn('Aborting merge');
|
||||
}
|
||||
|
||||
util.shutdown(0);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
util.shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@@ -95,6 +145,16 @@ program
|
||||
.description('queues the assets that exceed the age requested')
|
||||
.action(refreshAssets);
|
||||
|
||||
program
|
||||
.command('update-url <assetID> <url>')
|
||||
.description('update the URL of an asset')
|
||||
.action(updateURL);
|
||||
|
||||
program
|
||||
.command('merge <srcID> <dstID>')
|
||||
.description('merges two assets together by moving comments from src to dst and deleting the src asset')
|
||||
.action(merge);
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
// If there is no command listed, output help.
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
letter-spacing: .8;
|
||||
|
||||
&:hover {
|
||||
background-color: #232323;
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
&.active {
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
right: 0;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
padding: 40px 18px;
|
||||
padding: 50px 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -310,24 +310,33 @@
|
||||
.flaggedByCount {
|
||||
display: block;
|
||||
text-align: left;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.flaggedByCount i {
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.flaggedBy {
|
||||
display: inline;
|
||||
padding: 3px;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.flaggedByLabel {
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.flaggedReasons {
|
||||
padding-top: 15px;
|
||||
margin-left: 24px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.flaggedByReason {
|
||||
p.flaggedByReason {
|
||||
font-size: 1tpx;
|
||||
margin: 0px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ span {
|
||||
}
|
||||
}
|
||||
|
||||
.approve {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.notFound {
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
@@ -193,7 +197,7 @@ span {
|
||||
top: 0;
|
||||
padding: 40px 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.itemHeader {
|
||||
display: flex;
|
||||
@@ -255,6 +259,8 @@ span {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.empty {
|
||||
color: #444;
|
||||
margin-top: 50px;
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
.heading {
|
||||
margin: 0;
|
||||
padding-left: 10px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: #2c2c2c;
|
||||
}
|
||||
|
||||
.widgetTable {
|
||||
@@ -32,7 +33,8 @@
|
||||
}
|
||||
|
||||
.widgetHead p {
|
||||
color: rgb(35, 102, 223);
|
||||
color: #2c2c2c;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
text-transform: capitalize;
|
||||
@@ -47,11 +49,11 @@
|
||||
}
|
||||
|
||||
.rowLinkify {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid lightgrey;
|
||||
color: #555;
|
||||
height: var(--row-height);
|
||||
padding: 10px;
|
||||
transition: background-color 200ms;
|
||||
}
|
||||
|
||||
.rowLinkify:last-child {
|
||||
@@ -60,6 +62,7 @@
|
||||
|
||||
.rowLinkify:hover {
|
||||
background-color: #f8f8f8;
|
||||
pointer: default;
|
||||
}
|
||||
|
||||
.linkToAsset {
|
||||
@@ -68,16 +71,17 @@
|
||||
}
|
||||
|
||||
.linkToModerate {
|
||||
background-color: #e0e0e0;
|
||||
background-color: #BDBDBD;
|
||||
padding: 10px 14px;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
float: right;
|
||||
margin-left: 15px;
|
||||
transition: background-color 200ms;
|
||||
}
|
||||
|
||||
.linkToModerate:hover {
|
||||
background-color: #ccc;
|
||||
background-color: #9E9E9E;
|
||||
}
|
||||
|
||||
.lede {
|
||||
@@ -89,14 +93,10 @@
|
||||
color: #555;
|
||||
text-decoration: none;
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.assetTitle:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.widgetCount {
|
||||
color: #555;
|
||||
font-size: 1.3em;
|
||||
|
||||
@@ -12,4 +12,5 @@
|
||||
right: 0;
|
||||
margin-top: -2px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
display: flex;
|
||||
|
||||
.stat {
|
||||
margin: 0 4px 12px;
|
||||
margin: 0 4px 10px 0px;
|
||||
}
|
||||
|
||||
.stat:last-child {
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
@@ -18,14 +18,20 @@
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
color: #EEEEEE;
|
||||
color: #C0C0C0;
|
||||
text-transform: capitalize;
|
||||
font-weight: 100;
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
transition: border-bottom 200ms;
|
||||
padding: 0px 5px;
|
||||
margin-right: 30px;
|
||||
transition: color 200ms;
|
||||
padding: 0px 10px;
|
||||
margin-right: 20px;
|
||||
&:hover {
|
||||
color: white;
|
||||
border-bottom: solid 2px #F36451;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
@@ -33,6 +39,10 @@
|
||||
box-sizing: border-box;
|
||||
border-bottom: solid 4px #F36451;
|
||||
font-weight: 400;
|
||||
&:hover {
|
||||
border-bottom: solid 4px #F36451;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.active > span {
|
||||
@@ -103,19 +113,18 @@ span {
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
letter-spacing: 1px;
|
||||
transition: opacity 200ms;
|
||||
transition: background-color 200ms;
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: #212121;
|
||||
}
|
||||
span {
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
@@ -245,7 +254,6 @@ span {
|
||||
padding: 5px;
|
||||
color: #262626;
|
||||
font-size: 14px;
|
||||
margin-left: 15px;
|
||||
line-height: 1px;
|
||||
font-weight: 300;
|
||||
}
|
||||
@@ -421,17 +429,21 @@ span {
|
||||
|
||||
.tabIcon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
top: 3px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.username {
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
color: #393B44;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
font-weight: 600;
|
||||
padding: 2px 5px;
|
||||
border-radius: 2px;
|
||||
margin-left: -5px;
|
||||
transition: background-color 200ms ease;
|
||||
&:hover {
|
||||
background-color: rgba(255, 0, 0, .1);
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import React, {PropTypes} from 'react';
|
||||
import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton';
|
||||
import AuthorName from 'coral-plugin-author-name/AuthorName';
|
||||
import TagLabel from 'coral-plugin-tag-label/TagLabel';
|
||||
import Content from 'coral-plugin-commentcontent/CommentContent';
|
||||
import PubDate from 'coral-plugin-pubdate/PubDate';
|
||||
import {ReplyBox, ReplyButton} from 'coral-plugin-replies';
|
||||
import FlagComment from 'coral-plugin-flags/FlagComment';
|
||||
import {can} from 'coral-framework/services/perms';
|
||||
import {TransitionGroup} from 'react-transition-group';
|
||||
import cn from 'classnames';
|
||||
import styles from './Comment.css';
|
||||
|
||||
import {
|
||||
BestButton,
|
||||
@@ -18,14 +18,14 @@ import {
|
||||
commentIsBest,
|
||||
BestIndicator
|
||||
} from 'coral-plugin-best/BestButton';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import LoadMore from './LoadMore';
|
||||
import {getEditableUntilDate} from './util';
|
||||
import {TopRightMenu} from './TopRightMenu';
|
||||
import CommentContent from './CommentContent';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
|
||||
import {EditableCommentContent} from './EditableCommentContent';
|
||||
import {getActionSummary, iPerformedThisAction} from 'coral-framework/utils';
|
||||
import {getEditableUntilDate} from './util';
|
||||
import styles from './Comment.css';
|
||||
|
||||
const isStaff = (tags) => !tags.every((t) => t.tag.name !== 'STAFF');
|
||||
const hasTag = (tags, lookupTag) => !!tags.filter((t) => t.tag.name === lookupTag).length;
|
||||
@@ -466,8 +466,7 @@ export default class Comment extends React.Component {
|
||||
stopEditing={this.stopEditing}
|
||||
/>
|
||||
: <div>
|
||||
<Content body={comment.body} />
|
||||
<Slot fill="commentContent" />
|
||||
<Slot fill="commentContent" comment={comment} defaultComponent={CommentContent} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
const CommentContent = ({comment}) => {
|
||||
const textbreaks = comment.body.split('\n');
|
||||
return <div className={`${name}-text`}>
|
||||
{
|
||||
textbreaks.map((line, i) => {
|
||||
return (
|
||||
<span key={i} className={`${name}-line`}>
|
||||
{line}
|
||||
<br className={`${name}-linebreak`}/>
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default CommentContent;
|
||||
@@ -4,10 +4,15 @@ import styles from './Slot.css';
|
||||
import {connect} from 'react-redux';
|
||||
import {getSlotElements} from 'coral-framework/helpers/plugins';
|
||||
|
||||
function Slot ({fill, inline = false, className, plugin_config: config, ...rest}) {
|
||||
function Slot ({fill, inline = false, className, plugin_config: config, defaultComponent: DefaultComponent, ...rest}) {
|
||||
let children = getSlotElements(fill, {...rest, config});
|
||||
if (children.length === 0 && DefaultComponent) {
|
||||
children = <DefaultComponent {...rest} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn({[styles.inline]: inline, [styles.debug]: config.debug}, className)}>
|
||||
{getSlotElements(fill, {...rest, config})}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
'SuspendUserResponse',
|
||||
'RejectUsernameResponse',
|
||||
'SetUserStatusResponse',
|
||||
'PostCommentResponse',
|
||||
'CreateFlagResponse',
|
||||
'EditCommentResponse',
|
||||
'PostFlagResponse',
|
||||
'CreateDontAgreeResponse',
|
||||
@@ -17,3 +17,4 @@ export default {
|
||||
'StopIgnoringUserResponse',
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
const name = 'coral-plugin-commentcontent';
|
||||
|
||||
const Content = ({body, styles}) => {
|
||||
const textbreaks = body.split('\n');
|
||||
return <div
|
||||
className={`${name}-text`}
|
||||
style={styles && styles.text}>
|
||||
{
|
||||
textbreaks.map((line, i) => <span key={i} className={`${name}-line`}>
|
||||
{line} <br className={`${name}-linebreak`}/>
|
||||
</span>)
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default Content;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {expect} from 'chai';
|
||||
import CommentContent from '../CommentContent';
|
||||
|
||||
describe('CommentContent', () => {
|
||||
it('should render content', () => {
|
||||
const render = shallow(<CommentContent content="test"/>);
|
||||
expect(render.contains('test')).to.be.truthy;
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {Icon} from '../coral-ui';
|
||||
import styles from './Comment.css';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import PubDate from '../coral-plugin-pubdate/PubDate';
|
||||
import Content from '../coral-plugin-commentcontent/CommentContent';
|
||||
import CommentContent from '../coral-embed-stream/src/components/CommentContent';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
@@ -10,9 +11,11 @@ const Comment = (props) => {
|
||||
return (
|
||||
<div className={styles.myComment}>
|
||||
<div>
|
||||
<Content
|
||||
<Slot
|
||||
fill="commentContent"
|
||||
defaultComponent={CommentContent}
|
||||
className={`${styles.commentBody} myCommentBody`}
|
||||
body={props.comment.body}
|
||||
comment={props.comment}
|
||||
/>
|
||||
<p className="myCommentAsset">
|
||||
<a
|
||||
|
||||
@@ -104,6 +104,20 @@ class ErrAuthentication extends APIError {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrAlreadyExists is returned when an attempt to create a resource failed due to an existing one.
|
||||
*/
|
||||
class ErrAlreadyExists extends APIError {
|
||||
constructor(existing = null) {
|
||||
super('resource already exists', {
|
||||
translation_key: 'ALREADY_EXISTS',
|
||||
status: 409
|
||||
}, {
|
||||
existing
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ErrContainsProfanity is returned in the event that the middleware detects
|
||||
// profanity/wordlisted words in the payload.
|
||||
const ErrContainsProfanity = new APIError('This username contains elements which are not permitted in our community. If you think this is in error, please contact us or try again.', {
|
||||
@@ -168,9 +182,17 @@ const ErrCommentTooShort = new APIError('Comment was too short', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
// ErrAssetURLAlreadyExists is returned when a rename operation is requested
|
||||
// but an asset already exists with the new url.
|
||||
const ErrAssetURLAlreadyExists = new APIError('Asset URL already exists, cannot rename', {
|
||||
translation_key: 'ASSET_URL_ALREADY_EXISTS',
|
||||
status: 409
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
ExtendableError,
|
||||
APIError,
|
||||
ErrAlreadyExists,
|
||||
ErrPasswordTooShort,
|
||||
ErrSettingsNotInit,
|
||||
ErrMissingEmail,
|
||||
@@ -191,5 +213,6 @@ module.exports = {
|
||||
ErrInstallLock,
|
||||
ErrLoginAttemptMaximumExceeded,
|
||||
ErrEditWindowHasEnded,
|
||||
ErrCommentTooShort
|
||||
ErrCommentTooShort,
|
||||
ErrAssetURLAlreadyExists
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ const createAction = async ({user = {}}, {item_id, item_type, action_type, group
|
||||
* Deletes an action based on the user id if the user owns that action.
|
||||
* @param {Object} user the user performing the request
|
||||
* @param {String} id the id of the action to delete
|
||||
* @return {Promise} resolves when the action is deleted
|
||||
* @return {Promise} resolves to the deleted action, or null if not found.
|
||||
*/
|
||||
const deleteAction = ({user}, {id}) => {
|
||||
return ActionModel.findOneAndRemove({
|
||||
|
||||
+7
-6
@@ -42,17 +42,17 @@ en:
|
||||
banned: Banned
|
||||
banned_user: "Banned User"
|
||||
cancel: Cancel
|
||||
dont_like_username: "I don't like this username"
|
||||
dont_like_username: "Dislike username"
|
||||
flaggedaccounts: "Flagged Usernames"
|
||||
flags: Flags
|
||||
impersonating: "This user is impersonating"
|
||||
impersonating: "Impersonation"
|
||||
loading: "Loading results"
|
||||
moderator: Moderator
|
||||
newsroom_role: "Newsroom Role"
|
||||
no_flagged_accounts: "The Account Flags queue is currently empty."
|
||||
no_flagged_accounts: "The Flagged Usernames queue is currently empty."
|
||||
no_results: "No users found with that user name or email address. They're hiding!"
|
||||
note: "Note: Banning this user will not let them edit comment or remove anything."
|
||||
offensive: "This comment is offensive"
|
||||
offensive: "Offensive"
|
||||
other: Other
|
||||
people: People
|
||||
role: "Select role..."
|
||||
@@ -144,8 +144,8 @@ en:
|
||||
auto_update: "Data automatically updates every five minutes or when you Reload."
|
||||
comment_count: comments
|
||||
flags: Flags
|
||||
most_flags: "Articles with the most flags"
|
||||
most_conversations: "Articles with the most conversations"
|
||||
most_flags: "Stories with the most flags"
|
||||
most_conversations: "Stories with the most conversations"
|
||||
next_update: "{0} minutes until next update."
|
||||
no_activity: "There haven't been any comments anywhere in the last five minutes."
|
||||
no_flags: "There have been no flags in the last 5 minutes! Hooray!"
|
||||
@@ -189,6 +189,7 @@ en:
|
||||
PASSWORD_REQUIRED: "Must input a password"
|
||||
COMMENTING_CLOSED: "Commenting is already closed"
|
||||
NOT_FOUND: "Resource not found"
|
||||
ALREADY_EXISTS: "Resource already exists"
|
||||
INVALID_ASSET_URL: "Assert URL is invalid"
|
||||
email: "Not a valid E-Mail"
|
||||
confirm_password: "Passwords don't match. Please check again"
|
||||
|
||||
+3
-3
@@ -44,14 +44,14 @@ es:
|
||||
dont_like_username: "No me gusta este nombre de usuario"
|
||||
flaggedaccounts: "Nombres de Usuario Reportados"
|
||||
flags: Reportes
|
||||
impersonating: "El usuario esta suplantando identidad"
|
||||
impersonating: Impersonando
|
||||
loading: "Cargando resultados"
|
||||
moderator: Moderator
|
||||
newsroom_role: "Rol en la redacción"
|
||||
no_flagged_accounts: "No hay ninguna cuenta reportada en este momento."
|
||||
no_flagged_accounts: "No hay ningún nombre de usario reportado en este momento."
|
||||
no_results: "No se encontraron usuarios con ese nombre o correo."
|
||||
note: "Nota: Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar."
|
||||
offensive: "Este comentario es ofensivo"
|
||||
offensive: Ofensivo
|
||||
other: Otro
|
||||
people: Gente
|
||||
role: "Seleccionar rol..."
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "1.9.1",
|
||||
"version": "2.0.0",
|
||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export {Slot} from 'coral-framework/components';
|
||||
export {Icon} from 'coral-ui';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from 'coral-ui';
|
||||
@@ -149,6 +149,9 @@ export default (reaction) => (WrappedComponent) => {
|
||||
client: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
// Whether or not a mutation is currently active.
|
||||
duringMutation = false;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@@ -208,6 +211,26 @@ export default (reaction) => (WrappedComponent) => {
|
||||
}
|
||||
}
|
||||
|
||||
postReaction = () => {
|
||||
if (this.duringMutation) {
|
||||
return;
|
||||
}
|
||||
this.duringMutation = true;
|
||||
return this.props.postReaction(this.props.comment)
|
||||
.then((result) => {this.duringMutation = false; return Promise.resolve(result); })
|
||||
.catch((err) => {this.duringMutation = false; throw err; });
|
||||
}
|
||||
|
||||
deleteReaction = () => {
|
||||
if (this.duringMutation) {
|
||||
return;
|
||||
}
|
||||
this.duringMutation = true;
|
||||
return this.props.deleteReaction(this.props.comment)
|
||||
.then((result) => {this.duringMutation = false; return Promise.resolve(result); })
|
||||
.catch((err) => {this.duringMutation = false; throw err; });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {comment} = this.props;
|
||||
|
||||
@@ -223,9 +246,16 @@ export default (reaction) => (WrappedComponent) => {
|
||||
|
||||
const alreadyReacted = !!reactionSummary;
|
||||
|
||||
const withReactionProps = {reactionSummary, count, alreadyReacted};
|
||||
|
||||
return <WrappedComponent {...this.props} {...withReactionProps} />;
|
||||
return <WrappedComponent
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
user={this.props.user}
|
||||
comment={comment}
|
||||
reactionSummary={reactionSummary}
|
||||
count={count}
|
||||
alreadyReacted={alreadyReacted}
|
||||
postReaction={this.postReaction}
|
||||
deleteReaction={this.deleteReaction}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,16 +270,16 @@ export default (reaction) => (WrappedComponent) => {
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate, ownProps}) => ({
|
||||
deleteReaction: () => {
|
||||
props: ({mutate}) => ({
|
||||
deleteReaction: (comment) => {
|
||||
|
||||
const reactionSummary = getMyActionSummary(
|
||||
`${Reaction}ActionSummary`,
|
||||
ownProps.comment
|
||||
comment
|
||||
);
|
||||
|
||||
const id = reactionSummary.current_user.id;
|
||||
const item_id = ownProps.comment.id;
|
||||
const item_id = comment.id;
|
||||
|
||||
const input = {id};
|
||||
return mutate({
|
||||
@@ -283,11 +313,11 @@ export default (reaction) => (WrappedComponent) => {
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate, ownProps}) => ({
|
||||
postReaction: () => {
|
||||
props: ({mutate}) => ({
|
||||
postReaction: (comment) => {
|
||||
|
||||
const input = {
|
||||
item_id: ownProps.comment.id,
|
||||
item_id: comment.id,
|
||||
};
|
||||
|
||||
return mutate({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const wrapResponse = require('../../../graph/helpers/response');
|
||||
const {SEARCH_OTHER_USERS} = require('../../../perms/constants');
|
||||
const errors = require('../../../errors');
|
||||
|
||||
function getReactionConfig(reaction) {
|
||||
reaction = reaction.toLowerCase();
|
||||
@@ -134,13 +135,24 @@ function getReactionConfig(reaction) {
|
||||
// The comment is needed to allow better filtering e.g. by asset_id.
|
||||
pubsub.publish(`${reaction}ActionCreated`, {action, comment});
|
||||
return Promise.resolve(action);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err instanceof errors.ErrAlreadyExists) {
|
||||
return Promise.resolve(err.metadata.existing);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
return wrapResponse(reaction)(response);
|
||||
},
|
||||
[`delete${Reaction}Action`]: (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
|
||||
const response = Action.delete({id})
|
||||
.then((action) => {
|
||||
|
||||
// Action doesn't exist or was already deleted.
|
||||
if (!action) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return Comments.get.load(action.item_id).then((comment) => {
|
||||
|
||||
// The comment is needed to allow better filtering e.g. by asset_id.
|
||||
@@ -148,6 +160,7 @@ function getReactionConfig(reaction) {
|
||||
return Promise.resolve(action);
|
||||
});
|
||||
});
|
||||
|
||||
return wrapResponse(reaction)(response);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"coral-plugin-like",
|
||||
"coral-plugin-auth",
|
||||
"coral-plugin-offtopic",
|
||||
"coral-plugin-viewing-options"
|
||||
"coral-plugin-viewing-options",
|
||||
"coral-plugin-comment-content"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import {Dialog, Alert, TextField} from 'coral-ui';
|
||||
import {Dialog, Alert, TextField, Button} from 'plugin-api/beta/client/components/ui';
|
||||
import {FakeComment} from './FakeComment';
|
||||
import Button from 'coral-ui/components/Button';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const CreateUsernameDialog = ({
|
||||
@@ -33,7 +32,7 @@ const CreateUsernameDialog = ({
|
||||
className={styles.fakeComment}
|
||||
username={formData.username}
|
||||
created_at={Date.now()}
|
||||
body={t('createdisplay.fake_comment_body')}
|
||||
comment={{body:t('createdisplay.fake_comment_body')}}
|
||||
/>
|
||||
<p className={styles.ifyoudont}>
|
||||
{t('createdisplay.if_you_dont_change_your_name')}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {ReplyButton} from 'coral-plugin-replies';
|
||||
import PubDate from 'coral-plugin-pubdate/PubDate';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import AuthorName from 'coral-plugin-author-name/AuthorName';
|
||||
import Content from 'coral-plugin-commentcontent/CommentContent';
|
||||
import styles from 'coral-embed-stream/src/components/Comment.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
export const FakeComment = ({username, created_at, body}) => (
|
||||
export const FakeComment = ({username, created_at, comment}) => (
|
||||
<div className={`comment ${styles.Comment}`} style={{marginLeft: 0 * 30}}>
|
||||
<hr aria-hidden={true} />
|
||||
<AuthorName author={{name: username}} />
|
||||
<PubDate created_at={created_at} />
|
||||
<Content body={body} />
|
||||
<Slot comment={comment} />
|
||||
<div className="commentActionsLeft comment__action-container">
|
||||
<div className={`${'coral-plugin-likes'}-container`}>
|
||||
<button className={'coral-plugin-likes-button'}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import Button from 'coral-ui/components/Button';
|
||||
import {Button} from 'plugin-api/beta/client/components/ui';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class ForgotContent extends React.Component {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {Dialog} from 'coral-ui';
|
||||
import {Dialog} from 'plugin-api/beta/client/components/ui';
|
||||
import styles from './styles.css';
|
||||
|
||||
import SignInContent from './SignInContent';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {Button} from 'coral-ui';
|
||||
import {Button} from 'plugin-api/beta/client/components/ui';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {showSignInDialog} from 'coral-framework/actions/auth';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {Button, TextField, Spinner, Success, Alert} from 'coral-ui';
|
||||
import {Button, TextField, Spinner, Success, Alert} from 'plugin-api/beta/client/components/ui';
|
||||
import styles from './styles.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import styles from './styles.css';
|
||||
import React from 'react';
|
||||
import {Button, TextField, Spinner, Success, Alert} from 'coral-ui';
|
||||
import {Button, TextField, Spinner, Success, Alert} from 'plugin-api/beta/client/components/ui';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class SignUpContent extends React.Component {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
"add-module-exports",
|
||||
"transform-class-properties",
|
||||
"transform-decorators-legacy",
|
||||
"transform-object-assign",
|
||||
"transform-object-rest-spread",
|
||||
"transform-async-to-generator",
|
||||
"transform-react-jsx"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import Linkify from 'react-linkify';
|
||||
|
||||
const name = 'coral-plugin-comment-content';
|
||||
|
||||
const CommentContent = ({comment}) => {
|
||||
const textbreaks = comment.body.split('\n');
|
||||
return <div className={`${name}-text`}>
|
||||
{
|
||||
textbreaks.map((line, i) => {
|
||||
return (
|
||||
<span key={i} className={`${name}-line`}>
|
||||
<Linkify properties={{target: '_blank'}}>
|
||||
{line}
|
||||
</Linkify>
|
||||
<br className={`${name}-linebreak`}/>
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default CommentContent;
|
||||
@@ -0,0 +1,7 @@
|
||||
import CommentContent from './components/CommentContent';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
commentContent: [CommentContent]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "coral-plugin-comment-content",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"react-linkify": "^0.2.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
asap@~2.0.3:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"
|
||||
|
||||
core-js@^1.0.0:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
|
||||
encoding@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
||||
dependencies:
|
||||
iconv-lite "~0.4.13"
|
||||
|
||||
fbjs@^0.8.9:
|
||||
version "0.8.12"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
isomorphic-fetch "^2.1.1"
|
||||
loose-envify "^1.0.0"
|
||||
object-assign "^4.1.0"
|
||||
promise "^7.1.1"
|
||||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.9"
|
||||
|
||||
iconv-lite@~0.4.13:
|
||||
version "0.4.18"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
|
||||
|
||||
is-stream@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
|
||||
isomorphic-fetch@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
dependencies:
|
||||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
js-tokens@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
|
||||
|
||||
linkify-it@^1.2.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-1.2.4.tgz#0773526c317c8fd13bd534ee1d180ff88abf881a"
|
||||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
|
||||
dependencies:
|
||||
js-tokens "^3.0.0"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5"
|
||||
dependencies:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
object-assign@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
promise@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf"
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.5.8:
|
||||
version "15.5.10"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
|
||||
dependencies:
|
||||
fbjs "^0.8.9"
|
||||
loose-envify "^1.3.1"
|
||||
|
||||
react-linkify@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-linkify/-/react-linkify-0.2.1.tgz#b28d3f9544539a622fec8d42b4800eb9d23bf981"
|
||||
dependencies:
|
||||
linkify-it "^1.2.0"
|
||||
prop-types "^15.5.8"
|
||||
tlds "^1.57.0"
|
||||
|
||||
setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
|
||||
tlds@^1.57.0:
|
||||
version "1.189.0"
|
||||
resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.189.0.tgz#b8cb46ea76dc2f4a01d45b8d907bf19a66e9f729"
|
||||
|
||||
ua-parser-js@^0.7.9:
|
||||
version "0.7.12"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
|
||||
|
||||
uc.micro@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"
|
||||
|
||||
whatwg-fetch@>=0.10.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import {withReaction} from 'plugin-api/beta/client/hocs';
|
||||
import {t, can} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
const plugin = 'coral-plugin-like';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import {withReaction} from 'plugin-api/beta/client/hocs';
|
||||
import {t, can} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
const plugin = 'coral-plugin-love';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import Box from './Box';
|
||||
import {Button} from 'coral-ui';
|
||||
import {Button} from 'plugin-api/beta/client/components/ui';
|
||||
import styles from './styles.css';
|
||||
|
||||
export default class Footer extends React.Component {
|
||||
|
||||
+27
-10
@@ -1,5 +1,6 @@
|
||||
const ActionModel = require('../models/action');
|
||||
const _ = require('lodash');
|
||||
const errors = require('../errors');
|
||||
|
||||
module.exports = class ActionsService {
|
||||
|
||||
@@ -12,10 +13,10 @@ module.exports = class ActionsService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an action.
|
||||
* @param {String} item_id identifier of the comment (uuid)
|
||||
* Inserts an action.
|
||||
* @param {String} item_id identifier of the item (uuid)
|
||||
* @param {String} user_id user id of the action (uuid)
|
||||
* @param {String} action the new action to the comment
|
||||
* @param {String} action the new action to the item
|
||||
* @return {Promise}
|
||||
*/
|
||||
static insertUserAction(action) {
|
||||
@@ -31,16 +32,32 @@ module.exports = class ActionsService {
|
||||
};
|
||||
|
||||
// Create/Update the action.
|
||||
return ActionModel.findOneAndUpdate(query, action, {
|
||||
return new Promise((resolve, reject) => {
|
||||
ActionModel.findOneAndUpdate(
|
||||
query, {
|
||||
|
||||
// Ensure that if it's new, we return the new object created.
|
||||
new: true,
|
||||
// Only set when not existing.
|
||||
$setOnInsert: action,
|
||||
}, {
|
||||
|
||||
// Perform an upsert in the event that this doesn't exist.
|
||||
upsert: true,
|
||||
// Ensure that if it's new, we return the new object created.
|
||||
new: true,
|
||||
|
||||
// Set the default values if not provided based on the mongoose models.
|
||||
setDefaultsOnInsert: true
|
||||
// Use raw result to get `updatedExisting`.
|
||||
passRawResult: true,
|
||||
|
||||
// Perform an upsert in the event that this doesn't exist.
|
||||
upsert: true,
|
||||
|
||||
// Set the default values if not provided based on the mongoose models.
|
||||
setDefaultsOnInsert: true
|
||||
}, (err, doc, raw) => {
|
||||
if (err) { return reject(err); }
|
||||
if (raw.lastErrorObject.updatedExisting) {
|
||||
return reject(new errors.ErrAlreadyExists(raw.value));
|
||||
}
|
||||
return resolve(raw.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+56
-3
@@ -1,3 +1,4 @@
|
||||
const CommentModel = require('../models/comment');
|
||||
const AssetModel = require('../models/asset');
|
||||
const SettingsService = require('./settings');
|
||||
const domainlist = require('./domainlist');
|
||||
@@ -128,9 +129,61 @@ module.exports = class AssetsService {
|
||||
* @param {Array} ids an array of Strings of asset_id
|
||||
* @return {Promise} resolves to list of Assets
|
||||
*/
|
||||
static findMultipleById(ids) {
|
||||
const query = ids.map((id) => ({id}));
|
||||
return AssetModel.find(query);
|
||||
static async findByIDs(ids) {
|
||||
|
||||
// Find the assets.
|
||||
let assets = await AssetModel.find({
|
||||
id: {
|
||||
$in: ids
|
||||
}
|
||||
});
|
||||
|
||||
// Return them in the right order.
|
||||
return ids.map((id) => assets.find((asset) => asset.id === id));
|
||||
}
|
||||
|
||||
static async updateURL(id, url) {
|
||||
|
||||
// Try to see if an asset already exists with the given url.
|
||||
let asset = await AssetsService.findByUrl(url);
|
||||
if (asset !== null) {
|
||||
throw errors.ErrAssetURLAlreadyExists;
|
||||
}
|
||||
|
||||
// Seems that there was no other asset with the same url, try and perform
|
||||
// the rename operation! An error may be thrown from this if the operation
|
||||
// fails. This is ok.
|
||||
await AssetModel.update({id}, {$set: {url}});
|
||||
}
|
||||
|
||||
static async merge(srcAssetID, dstAssetID) {
|
||||
|
||||
// Fetch both assets.
|
||||
let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcAssetID, dstAssetID]);
|
||||
if (!srcAsset || !dstAsset) {
|
||||
throw errors.ErrNotFound;
|
||||
}
|
||||
|
||||
// Resolve the merge operation, this invloves moving all resources attached
|
||||
// to the src asset to the dst asset, and then removing the src asset.
|
||||
|
||||
// First, update all the old comments to the new asset.
|
||||
await CommentModel.update({
|
||||
asset_id: srcAssetID
|
||||
}, {
|
||||
$set: {
|
||||
asset_id: dstAssetID
|
||||
}
|
||||
}, {
|
||||
multi: true
|
||||
});
|
||||
|
||||
// Second remove the old asset.
|
||||
await AssetModel.remove({
|
||||
id: srcAssetID
|
||||
});
|
||||
|
||||
// That's it!
|
||||
}
|
||||
|
||||
static all(skip = null, limit = null) {
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
const AssetModel = require('../../../models/asset');
|
||||
const CommentModel = require('../../../models/comment');
|
||||
const AssetsService = require('../../../services/assets');
|
||||
const CommentsService = require('../../../services/comments');
|
||||
const SettingsService = require('../../../services/settings');
|
||||
|
||||
const url = require('url');
|
||||
const chai = require('chai');
|
||||
const expect = chai.expect;
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
// Use the chai should.
|
||||
chai.should();
|
||||
|
||||
const settings = {id: '1', moderation: 'PRE', domains: {whitelist: ['new.test.com', 'test.com', 'override.test.com']}};
|
||||
const defaults = {url:'http://test.com'};
|
||||
|
||||
describe('services.AssetsService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const settings = {id: '1', moderation: 'PRE', domains: {whitelist: ['new.test.com', 'test.com', 'override.test.com']}};
|
||||
const defaults = {url:'http://test.com'};
|
||||
let asset;
|
||||
beforeEach(async () => {
|
||||
await SettingsService.init(settings);
|
||||
|
||||
return SettingsService.init(settings).then(() => {
|
||||
return AssetModel.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
|
||||
});
|
||||
asset = await AssetModel.findOneAndUpdate({id: '1'}, {$setOnInsert: defaults}, {upsert: true, new: true});
|
||||
});
|
||||
|
||||
describe('#findById', ()=> {
|
||||
@@ -120,4 +127,79 @@ describe('services.AssetsService', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateURL', () => {
|
||||
|
||||
it('should change the url if the asset was found, and there was no conflict', async () => {
|
||||
let newURL = url.resolve(asset.url, '/new-url');
|
||||
|
||||
// Update the asset.
|
||||
await AssetsService.updateURL(asset.id, newURL);
|
||||
|
||||
// Check that the url was updated.
|
||||
let {url: databaseURL} = await AssetsService.findById(asset.id);
|
||||
|
||||
expect(databaseURL).to.equal(newURL);
|
||||
});
|
||||
|
||||
it('should error if the new url already exists', async () => {
|
||||
let newURL = url.resolve(asset.url, '/new-url');
|
||||
|
||||
// Create a new asset with our new URL.
|
||||
await AssetModel.findOneAndUpdate({id: '2'}, {$setOnInsert: {url: newURL}}, {upsert: true, new: true});
|
||||
|
||||
return AssetsService.updateURL(asset.id, newURL).should.eventually.be.rejected;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#merge', () => {
|
||||
|
||||
it('should error if either the src or the dst is missing', () => {
|
||||
return AssetsService.merge('not-found', asset.id).should.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('should merge the assets', async () => {
|
||||
let newURL = url.resolve(asset.url, '/new-url');
|
||||
|
||||
// Create a new asset with our new URL.
|
||||
await AssetModel.findOneAndUpdate({id: '2'}, {$setOnInsert: {url: newURL}}, {upsert: true, new: true});
|
||||
|
||||
// Create some comments on both assets.
|
||||
await CommentsService.publicCreate([
|
||||
{
|
||||
asset_id: '1',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED'
|
||||
},
|
||||
{
|
||||
asset_id: '1',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED'
|
||||
},
|
||||
{
|
||||
asset_id: '2',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED'
|
||||
},
|
||||
{
|
||||
asset_id: '2',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED'
|
||||
}
|
||||
]);
|
||||
|
||||
// Merge all the comments from asset 1 into asset 2, followed by deleting
|
||||
// asset 1.
|
||||
await AssetsService.merge('1', '2');
|
||||
|
||||
// Check to see if the comments are moved.
|
||||
expect(await CommentModel.find({asset_id: '1'}).count()).to.equal(0);
|
||||
expect(await CommentModel.find({asset_id: '2'}).count()).to.equal(4);
|
||||
|
||||
// Check to see if the asset was removed.
|
||||
expect(await AssetModel.findOne({id: '1'})).to.equal(null);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user