mirror of
https://github.com/wassname/talk.git
synced 2026-07-24 13:20:47 +08:00
Merge branch 'master' into moderate-shortcuts
This commit is contained in:
@@ -10,5 +10,12 @@
|
||||
"transform-async-to-generator",
|
||||
"transform-react-jsx",
|
||||
"syntax-dynamic-import"
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": [
|
||||
["transform-es2015-modules-commonjs", "dynamic-import-node"]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,5 +45,6 @@ plugins/*
|
||||
!plugins/talk-plugin-deep-reply-count
|
||||
!plugins/talk-plugin-subscriber
|
||||
!plugins/talk-plugin-flag-details
|
||||
!plugins/talk-plugin-slack-notifications
|
||||
|
||||
**/node_modules/*
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# Talk [](https://circleci.com/gh/coralproject/talk)
|
||||
|
||||
[](https://dashboard.heroku.com/new?template=https%3A%2F%2Fgithub.com%2Fcoralproject%2Ftalk&env[TALK_FACEBOOK_APP_ID]=ignore&env[TALK_FACEBOOK_APP_SECRET]=ignore)
|
||||
|
||||
Online comments are broken. Our open-source Talk tool rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [Read more about Talk here](https://coralproject.net/products/talk.html).
|
||||
|
||||
## Documentation
|
||||
Built with <3 by The Coral Project & Mozilla.
|
||||
|
||||
Developer Documentation & Setup Guides https://coralproject.github.io/talk/.
|
||||
## Getting Started
|
||||
|
||||
Check out our Docs: https://coralproject.github.io/talk/
|
||||
|
||||
## Relevant Links
|
||||
|
||||
|
||||
@@ -16,19 +16,18 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
min-height: 58px;
|
||||
display: block;
|
||||
background-color: #696969;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
.header > div {
|
||||
background-color: #696969;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
min-width: 1280px;
|
||||
width: 1280px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.layout {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
padding-bottom: 200px;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mainFlaggedContent {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
|
||||
h3 {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
.Dashboard {
|
||||
display: flex;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.heading {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
.container {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.leftColumn {
|
||||
|
||||
@@ -270,6 +270,7 @@ button.comment__action-button[disabled],
|
||||
width: 75%;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
max-width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
/* Close comments */
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {expect} from 'chai';
|
||||
import Markdown from '../Markdown';
|
||||
|
||||
const render = (props) => shallow(<Markdown {...props} />);
|
||||
@@ -9,12 +8,12 @@ describe('Markdown', () => {
|
||||
it('should convert Markdown to html', () => {
|
||||
const wrapper = render({content: '*test*'});
|
||||
const html = wrapper.html();
|
||||
expect(html).to.contain('<em>');
|
||||
expect(html).toMatch('<em>');
|
||||
});
|
||||
|
||||
it('should set target="_parent" for links', () => {
|
||||
const wrapper = render({content: '[link](https://coralproject.net)'});
|
||||
const html = wrapper.html();
|
||||
expect(html).to.contain('target="_parent"');
|
||||
expect(html).toMatch('target="_parent"');
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ import pt_BR from '../../../locales/pt_BR.yml';
|
||||
|
||||
// Translations are happening at https://translate.lingohub.com/the-coral-project/dashboard
|
||||
|
||||
const defaultLanguage = 'en';
|
||||
const defaultLanguage = process.env.TALK_DEFAULT_LANG;
|
||||
const translations = {...en, ...es, ...fr, ...pt_BR};
|
||||
|
||||
let lang;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {expect} from 'chai';
|
||||
import CommentBox from '../CommentBox';
|
||||
|
||||
describe('CommentBox', () => {
|
||||
let comment;
|
||||
let render;
|
||||
beforeEach(() => {
|
||||
comment = {};
|
||||
const postItem = (item) => {
|
||||
comment.posted = item;
|
||||
return Promise.resolve(4);
|
||||
};
|
||||
render = shallow(<CommentBox
|
||||
postItem={postItem}
|
||||
updateItem={(e) => comment.text = e.target.value}
|
||||
item_id={'1'}
|
||||
comments={['1', '2', '3']}/>);
|
||||
});
|
||||
|
||||
it('should render the CommentBox appropriately', () => {
|
||||
expect(render.contains('<div class="CommentBox"')).to.be.true;
|
||||
expect(render.contains('<button class="postCommentButton"')).to.be.true;
|
||||
});
|
||||
});
|
||||
+14
-7
@@ -1,29 +1,36 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {expect} from 'chai';
|
||||
import InfoBox from '../InfoBox';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
const render = (props) => shallow(<InfoBox {...props} />);
|
||||
|
||||
describe('InfoBox', () => {
|
||||
it('renders correctly', () => {
|
||||
const tree = renderer.create(
|
||||
<InfoBox content='test' enable/>
|
||||
).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render hidden InfoBox', () => {
|
||||
const wrapper = render();
|
||||
const className = wrapper.prop('className');
|
||||
expect(className).to.include('-info');
|
||||
expect(className).to.include('hidden');
|
||||
expect(className).toMatch('-info');
|
||||
expect(className).toMatch('hidden');
|
||||
});
|
||||
|
||||
it('should render enabled InfoBox', () => {
|
||||
const wrapper = render({enable: true});
|
||||
const className = wrapper.prop('className');
|
||||
expect(className).to.include('-info');
|
||||
expect(className).to.not.include('hidden');
|
||||
expect(className).toMatch('-info');
|
||||
expect(className).not.toMatch('hidden');
|
||||
});
|
||||
|
||||
it('should render Markdown', () => {
|
||||
const wrapper = render({content: 'x'});
|
||||
const Markddown = wrapper.find('Markdown');
|
||||
expect(Markddown).to.have.length(1);
|
||||
expect(Markddown.prop('content')).to.equal('x');
|
||||
expect(Markddown).toHaveLength(1);
|
||||
expect(Markddown.prop('content')).toEqual('x');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`InfoBox renders correctly 1`] = `
|
||||
<div
|
||||
className="talk-plugin-infobox-info "
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>test</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -24,6 +24,10 @@ otherwise the application will fail to start.
|
||||
Configure the duration for which comment counts are cached for, parsed by
|
||||
[ms](https://www.npmjs.com/package/ms){:target="_blank"}. (Default `1hr`)
|
||||
|
||||
## TALK_DEFAULT_LANG
|
||||
|
||||
Specify the default translation language. (Default `en`)
|
||||
|
||||
## TALK_DEFAULT_STREAM_TAB
|
||||
|
||||
Specify the default stream tab in the admin. (Default `all`)
|
||||
@@ -439,4 +443,4 @@ Could be read as:
|
||||
again, then they must have two of their comments approved in order to get
|
||||
added back to the queue.
|
||||
- At the moment of writing, behavior is not attached to the flagging
|
||||
reliability, but it is recorded.
|
||||
reliability, but it is recorded.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
const path = require('path');
|
||||
const {pluginsPath} = require('./plugins');
|
||||
|
||||
const buildTargets = [
|
||||
'coral-admin',
|
||||
'coral-docs'
|
||||
];
|
||||
|
||||
const buildEmbeds = [
|
||||
'stream'
|
||||
];
|
||||
|
||||
// jest.config.js
|
||||
module.exports = {
|
||||
testMatch: ['**/client/**/__tests__/**/*.js?(x)'],
|
||||
setupTestFrameworkScriptFile: '<rootDir>/test/client/setupJest.js',
|
||||
modulePaths: [
|
||||
'<rootDir>/plugins',
|
||||
'<rootDir>/client',
|
||||
...buildTargets.map((target) => path.join('<rootDir>', 'client', target, 'src')),
|
||||
...buildEmbeds.map((embed) => path.join('<rootDir>', 'client', `coral-embed-${embed}`, 'src')),
|
||||
],
|
||||
moduleFileExtensions: ['js', 'jsx', 'json', 'yaml', 'yml'],
|
||||
moduleDirectories: ['node_modules'],
|
||||
|
||||
transform: {
|
||||
'^.+\\.jsx?$': 'babel-jest',
|
||||
'\\.ya?ml$': '<rootDir>/test/client/yamlTransformer.js'
|
||||
},
|
||||
|
||||
moduleNameMapper: {
|
||||
'^plugin-api\\/(.*)$': '<rootDir>/plugin-api/$1',
|
||||
'^plugins\\/(.*)$': '<rootDir>/plugins/$1',
|
||||
'^pluginsConfig$': pluginsPath,
|
||||
|
||||
'\\.(scss|css|less)$': 'identity-obj-proxy',
|
||||
'\\.(gif|ttf|eot|svg)$': '<rootDir>/test/client/fileMock.js'
|
||||
}
|
||||
};
|
||||
+1
-1
@@ -186,7 +186,7 @@ en:
|
||||
embedlink:
|
||||
copy: "Copy to Clipboard"
|
||||
error:
|
||||
COMMENT_TOO_SHORT: "Your comment must have something in it"
|
||||
COMMENT_TOO_SHORT: "Comments should be more than one character, please revise your comment and try again."
|
||||
NOT_AUTHORIZED: "You are not authorized to perform this action."
|
||||
NO_SPECIAL_CHARACTERS: "Usernames can contain letters numbers and _ only"
|
||||
PASSWORD_LENGTH: "Password is too short"
|
||||
|
||||
+12
-3
@@ -14,7 +14,8 @@
|
||||
"build-watch": "WEBPACK=TRUE NODE_ENV=development webpack --progress --config webpack.config.js --watch",
|
||||
"lint": "eslint --ext=.js --ext=.json bin/* .",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}",
|
||||
"jest-watch": "TEST_MODE=unit NODE_ENV=test jest --watch",
|
||||
"test": "TEST_MODE=unit NODE_ENV=test jest && TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}",
|
||||
"test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec",
|
||||
"heroku-postbuild": "./bin/cli plugins reconcile && yarn build",
|
||||
"generate-introspection": "WEBPACK=TRUE NODE_ENV=test ./scripts/generateIntrospectionResult.js"
|
||||
@@ -159,6 +160,7 @@
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^3.0.0",
|
||||
"react-tagsinput": "^3.17.0",
|
||||
"react-test-renderer": "15.5",
|
||||
"react-toastify": "^1.5.0",
|
||||
"react-transition-group": "^1.1.3",
|
||||
"recompose": "^0.23.1",
|
||||
@@ -179,17 +181,24 @@
|
||||
"url-search-params": "^0.9.0",
|
||||
"uuid": "^3.1.0",
|
||||
"webpack": "^2.3.1",
|
||||
"webpack-sources": "^1.0.1",
|
||||
"yaml-loader": "^0.4.0",
|
||||
"yamljs": "^0.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coralproject/eslint-config-talk": "^0.0.3",
|
||||
"@coralproject/eslint-config-talk": "^0.0.4",
|
||||
"babel-jest": "^21.2.0",
|
||||
"babel-plugin-dynamic-import-node": "^1.1.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^6.0.0",
|
||||
"chai-http": "^3.0.0",
|
||||
"enzyme": "^2.9.1",
|
||||
"enzyme": "^3.0.0",
|
||||
"enzyme-adapter-react-15": "^1.0.0",
|
||||
"eslint": "^4.5.0",
|
||||
"eslint-plugin-mocha": "^4.11.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^21.2.1",
|
||||
"mocha": "^3.1.2",
|
||||
"mocha-junit-reporter": "^1.12.1",
|
||||
"nodemon": "^1.11.0",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const hooks = require('./server/hooks');
|
||||
|
||||
module.exports = {
|
||||
hooks,
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@coralproject/talk-plugin-slack-notifications",
|
||||
"pluginName": "talk-plugin-slack-notifications",
|
||||
"version": "0.0.1",
|
||||
"description": "Posts new comments to Slack via a webhook",
|
||||
"main": "index.js",
|
||||
"author": "The Coral Project Team <coral@mozillafoundation.org>",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
const config = {
|
||||
SLACK_WEBHOOK_URL: process.env.TALK_SLACK_WEBHOOK_URL,
|
||||
SLACK_WEBHOOK_TIMEOUT: process.env.TALK_SLACK_WEBHOOK_TIMEOUT || 5000,
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'test' && !config.SLACK_WEBHOOK_URL) {
|
||||
// TODO this error should point users to Talk's Slack app once that's in place
|
||||
throw new Error('Please set the TALK_SLACK_WEBHOOK_URL environment variable to use the slack-notifications plugin.');
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
@@ -0,0 +1,46 @@
|
||||
const fetch = require('node-fetch');
|
||||
const {SLACK_WEBHOOK_URL, SLACK_WEBHOOK_TIMEOUT} = require('./config');
|
||||
const debug = require('debug')('talk:plugin:slack-notifications');
|
||||
|
||||
// We don't add the hooks during _test_ as the Slack API is not available.
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RootMutation: {
|
||||
createComment: {
|
||||
async post(_, {input}, context, _info, result) {
|
||||
debug(`Posting notification to Slack webhook: ${SLACK_WEBHOOK_URL}`);
|
||||
const {
|
||||
comment: {
|
||||
body: text,
|
||||
created_at: createdAt
|
||||
}
|
||||
} = result;
|
||||
const username = context.user.username;
|
||||
process.nextTick(async () => {
|
||||
const response = await fetch(SLACK_WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: SLACK_WEBHOOK_TIMEOUT,
|
||||
body: JSON.stringify({
|
||||
attachments: [{
|
||||
text: text,
|
||||
footer: `Comment by ${username}`,
|
||||
ts: Math.floor(Date.parse(createdAt) / 1000),
|
||||
}]
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const responseText = await response.text();
|
||||
console.trace(`Posting to Slack failed with HTTP code ${response.status} and body '${responseText}'`);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk/client"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = 'test-file-stub';
|
||||
@@ -0,0 +1,38 @@
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
Enzyme.configure({adapter: new Adapter()});
|
||||
|
||||
// Storage Mock
|
||||
|
||||
// TODO: Some places in our code (e.g. translations) has a hardcoded dependency
|
||||
// to the local storage. Fixing it and we can remove this global mock.
|
||||
|
||||
function storageMock() {
|
||||
let storage = {};
|
||||
|
||||
return {
|
||||
setItem: function(key, value) {
|
||||
storage[key] = value || '';
|
||||
},
|
||||
getItem: function(key) {
|
||||
return key in storage ? storage[key] : null;
|
||||
},
|
||||
removeItem: function(key) {
|
||||
delete storage[key];
|
||||
},
|
||||
get length() {
|
||||
return Object.keys(storage).length;
|
||||
},
|
||||
key: function(i) {
|
||||
let keys = Object.keys(storage);
|
||||
return keys[i] || null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// mock the localStorage
|
||||
window.localStorage = storageMock();
|
||||
|
||||
// mock the sessionStorage
|
||||
window.sessionStorage = storageMock();
|
||||
@@ -0,0 +1,8 @@
|
||||
const yaml = require('yamljs');
|
||||
|
||||
module.exports = {
|
||||
process(src) {
|
||||
const data = yaml.parse(src);
|
||||
return `module.exports = ${JSON.stringify(data)};`;
|
||||
},
|
||||
};
|
||||
+2
-1
@@ -111,7 +111,8 @@ const config = {
|
||||
new webpack.EnvironmentPlugin({
|
||||
'TALK_PLUGINS_JSON': '{}',
|
||||
'TALK_THREADING_LEVEL': '3',
|
||||
'TALK_DEFAULT_STREAM_TAB': 'all'
|
||||
'TALK_DEFAULT_STREAM_TAB': 'all',
|
||||
'TALK_DEFAULT_LANG': 'en'
|
||||
})
|
||||
],
|
||||
resolveLoader: {
|
||||
|
||||
Reference in New Issue
Block a user