Merge branch 'master' into name-collision

This commit is contained in:
Wyatt Johnson
2018-03-14 14:55:54 -06:00
committed by GitHub
56 changed files with 1433 additions and 77 deletions
+7 -1
View File
@@ -10,6 +10,12 @@ const serve = require('../serve');
program
.option('-j, --jobs', 'enable job processing on this thread')
.option(
'--disabled-jobs <jobs>',
'disable jobs specified if the -j option is passed, specified as a comma separated list',
val => val.split(','),
[]
)
.option(
'-w, --websockets',
'enable the websocket (subscriptions) handler on this thread'
@@ -17,7 +23,7 @@ program
.parse(process.argv);
// Start serving.
serve({ jobs: program.jobs, websockets: program.websockets }).catch(err => {
serve(program).catch(err => {
console.error(err);
util.shutdown(1);
});
+49
View File
@@ -312,10 +312,59 @@ async function verifyUserEmail(userID, email) {
}
}
/**
* createUser will prompt the user for the user information when creating a
* local user.
*/
async function createUser() {
try {
const answers = await inquirer.prompt([
{
name: 'email',
message: 'Email',
},
{
name: 'username',
message: 'Username',
},
{
name: 'password',
message: 'Password',
type: 'password',
},
{
name: 'role',
message: 'Role',
type: 'list',
choices: USER_ROLES,
},
]);
const { email, username, password, role } = answers;
// Create the user.
const user = await UsersService.createLocalUser(email, password, username);
// Set the role.
await UsersService.setRole(user.id, role);
console.log(`Created User[${user.id}]`);
util.shutdown(0);
} catch (err) {
console.error(err);
util.shutdown(1);
}
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('create')
.description('creates a local user')
.action(createUser);
program
.command('delete <userID>')
.description('delete a user')
+5 -8
View File
@@ -2,6 +2,7 @@
require('../services/env');
const debug = require('debug')('talk:util');
const { uniq } = require('lodash');
const util = (module.exports = {});
@@ -23,11 +24,7 @@ util.shutdown = (defaultCode = 0, signal = null) => {
debug(`${util.toshutdown.length} jobs now being called`);
Promise.all(
util.toshutdown
.map(func => (func ? func(signal) : null))
.filter(func => func)
)
Promise.all(util.toshutdown.map(func => (func ? func(signal) : null)))
.then(() => {
debug('Shutdown complete, now exiting');
process.exit(defaultCode);
@@ -49,14 +46,14 @@ util.onshutdown = jobs => {
debug(`${jobs.length} jobs registered to be called during shutdown`);
// Add the new jobs to shutdown to the object reference.
util.toshutdown = util.toshutdown.concat(jobs);
util.toshutdown = uniq(util.toshutdown.concat(jobs));
};
// Attach to the SIGTERM + SIGINT handles to ensure a clean shutdown in the
// event that we have an external event. SIGUSR2 is called when the app is asked
// to be 'killed', same procedure here.
process.on('SIGTERM', () => util.shutdown(0, 'SIGTERM'));
process.on('SIGINT', () => util.shutdown(0, 'SIGINT'));
process.once('SIGTERM', () => util.shutdown(0, 'SIGTERM'));
process.once('SIGINT', () => util.shutdown(0, 'SIGINT'));
process.once('SIGUSR2', () => util.shutdown(0, 'SIGUSR2'));
// Makes the script crash on unhandled rejections instead of silently
+5
View File
@@ -4,6 +4,7 @@ import get from 'lodash/get';
import merge from 'lodash/merge';
import moment from 'moment';
import 'moment/locale/ar';
import 'moment/locale/da';
import 'moment/locale/de';
import 'moment/locale/es';
@@ -12,6 +13,7 @@ import 'moment/locale/pt-br';
import { createStorage } from 'coral-framework/services/storage';
import arTA from 'timeago.js/locales/ar';
import daTA from 'timeago.js/locales/da';
import deTA from 'timeago.js/locales/de';
import esTA from 'timeago.js/locales/es';
@@ -21,6 +23,7 @@ import zh_CNTA from 'timeago.js/locales/zh_CN';
import zh_TWTA from 'timeago.js/locales/zh_TW';
import nl from 'timeago.js/locales/nl';
import ar from '../../../locales/ar.yml';
import en from '../../../locales/en.yml';
import da from '../../../locales/da.yml';
import de from '../../../locales/de.yml';
@@ -33,6 +36,7 @@ import nl_NL from '../../../locales/nl_NL.yml';
const defaultLanguage = process.env.TALK_DEFAULT_LANG;
const translations = {
...ar,
...en,
...da,
...de,
@@ -88,6 +92,7 @@ export function setupTranslations() {
lang = defaultLanguage;
}
ta.register('ar', arTA);
ta.register('es', esTA);
ta.register('da', daTA);
ta.register('de', deTA);
+9 -7
View File
@@ -73,14 +73,12 @@ deploy:
sidebar:
top:
- title: Github
- title: GitHub
url: https://github.com/coralproject/
- title: Docker
url: https://hub.docker.com/r/coralproject/
- title: Roadmap
url: https://www.pivotaltracker.com/n/projects/1863625
- title: Google Group
url: https://groups.google.com/forum/#!forum/coral-talk-users
side:
- title: Installation
children:
@@ -136,22 +134,26 @@ sidebar:
url: /plugins-directory/
- title: Plugin Recipes
url: /plugin-recipes/
- title: Tutorials
children:
- title: Creating a Basic Plugin
url: /building-basic-plugin/
- title: Customizing Plugins with Coral UI
url: /customizing-plugins-coral-ui/
- title: API
children:
- title: Server Plugins
url: /reference/server/
- title: GraphQL
url: /reference/graphql/
- title: FAQ
children:
- title: FAQ
url: /faq/
- title: Migrating
children:
- title: Migrating to v4.0.0
url: /migration/4/
- title: Migrating to v4.1.0
url: /migration/4.1/
- title: Contact
url: /contact/
marked:
gfm: true
@@ -6,7 +6,7 @@ permalink: /installation-from-source/
To install Talk from Source, ensure that you have Node version 8+.
Installing via source is the recommended method when developing as it give you
the best tooling. We release versions using semantic versioning, and do so to
our [Github Releases](https://github.com/coralproject/talk/releases) page.
our [GitHub Releases](https://github.com/coralproject/talk/releases) page.
There you can download archives of older versions or the latest release. The
examples following will download the latest code on our master branch.
+256
View File
@@ -0,0 +1,256 @@
---
title: Creating a Basic Pride Reaction Plugin
permalink: /building-basic-plugin/
---
In this tutorial, we will build a basic reaction plugin.
## What is a plugin?
Talk has two parts - the first is core. Our core code includes all commenting and moderation features that are necessary for a comment section, and ones that we believe are important to be universal. This code can be found in our [Talk repo](https://github.com/coralproject/talk).
The other part is plugins. Plugins are additional functionality which are optional to use with Talk. You can turn these on or off, depending on your specific needs. Plugins are either part of our core plugins, which ship with Talk, or they are developed by 3rd parties and either used privately and internally, or are open sourced for use across the greater community.
## Reactions
Talk exposes a friendly API to create new reactions. To explore the capabilities of Talk we are going to create a new reaction together step-by-step.
In Talk, there are currently three ways commenters can react to comments: Like, Love, and Respect. These reactions are separated into plugins so that you can customize which reactions you want to use by toggling them on or off - or by adding your own custom reactions, which is what we are going to do today.
We can create a new plugin from scratch or we can use the Talk CLI to generate a plugin template for us to use. CLI stands for Command Line Interface, meaning can access utilities via the command line that make it easy to interact and integrate with Talk.
Please note this tutorial assumes you have already [installed and configured Talk locally](/talk/).
## Creating a new plugin using the Talk CLI
* Open your terminal
* Go to the Talk folder
* Enter `./bin/cli-plugins` in the command line
![Using the Plugin CLI](/talk/images/pride_reaction_tutorial_1.png)
You will see 3 options: `create`, `list` and `reconcile`
* `create`: This is what we use to create new plugins. It will display a wizard and ask us a couple of questions in order to understand how we want to build our plugin.
* `list`: Shows a list of all plugins.
* `reconcile`: Reconciles local plugins and downloads their external dependencies.
In order to create our new plugin, enter this: `./bin/cli-plugins create`.
The CLI will now ask us 4 questions:
![Generating our Plugin](/talk/images/pride_reaction_tutorial_2.png)
#### Explaining the questions of the cli-plugins create
1. This is where you will submit the name of your plugin; our usual naming convention is `talk-plugin-`, so we will enter `talk-plugin-pride` for ours.
2. *Does this plugin extend the capabilities of the server?* If your plugin needs to extend the schema of the database, or interact with route or services you will say `yes`. In this case, we will need to store the user's comment reaction, so we will say `yes`
3. *Does this plugin extend the capabilities of the client?* If your plugin adds visual content to Talk, you will say `yes`. In this case we need to add a button with which the users can react to the comments, so we will again put `yes`.
4. *Should we add it to plugins.json?* Choosing yes will activate our plugin instantly. Select `yes` in this case.
So now a plugin has been created inside our local `/plugins` folder. We can see our plugin here now, listed as `talk-plugin-pride`.
## The structure of our plugin
This is the structure of our plugin. Let's see what each piece does.
![The Structure of a Plugin](/talk/images/pride_reaction_tutorial_3.png)
* `index.js`
The index file contains everything we export to the server. In this case, we see only one thing: `module.exports = {}`. This means we are currently not exporting anything to the server - but we will do this later.
* `/client`
The `client` folder contains all the necessary files to extend the client.
* `index.js`
In this file we will describe how we are going to extend our client. It is generally useful to indicate where the plugin will be embedded. In our case we want to put it in each comment. Later we will see how to do this. It also serves to add functionality such as how to use `reducers` and `translations`.
* `.eslintrc.json`
These are the ESLint rules. By default they are the ones that Talk uses.
* `translations.yml`
This file is not mandatory but we can use it to add translations of the copy that is shown to users.
* `/components`
These are the components. By default we will find the generated file `MyPluginComponent.js` and its CSS styles in `ModulesMyPluginComponent.css`
Now let's run our Talk instance. We can see the plugin was generated and we can see it in our embed:
![Viewing our Plugin](/talk/images/pride_reaction_tutorial_4.png)
It is important to note that Talk does not dictate the architecture of the plugins. But for reasons of performance and consistency it is important that we follow certain basic guidelines.
To create components you must be familiar with React. If you're not, I recommend the official guides, especially [Components and Props - React](https://reactjs.org/docs/components-and-props.html).
It's important to note that the files that were generated by default with the plugin creator can be deleted or reused. Whatever your preference!
Now that we know what all of our plugin files do, let's create our plugin :sunglasses:
## Building our plugin
The first thing we should think about is what our plugin consists of and what experience we want to offer. We know that we want there to be a button, that it can be clicked, and that it creates a reaction in the comment. So let's build it.
Since our button is a component let's create a new file inside that folder. Let's call this `PrideButton.js`.
The minimum expression of our button looks like this:
```js
import React from 'react';
class PrideButton extends React.Component {
render() {
return <button>Pride!</button>;
}
}
export default PrideButton;
```
Alright, we have our button. So now we want to tell it where to show - in this case, we want it under every comment. To do this we are going to make use of **slots**. Slots are small places inside Talk where we can place plugins. We already have a slot in Talk where we can place reactions. This slot is called `commentReactions`.
To add it there, we will go to `client/index.js` and add the following lines:
```
import PrideButton from './components/PrideButton';
export default {
slots: {
commentReactions: [PrideButton],
},
};
```
You will notice that we deleted the slot object `MyPluginComponent`. This is because we don't want to show the little example code that the CLI generated for us. We can also delete any files we won't us, or we can just leave them but not export them; if they're not exported, they won't be added to the Talk `bundle.js`.
Now, if we go to Talk we will see that our `PrideButton` is now there on each comment - now it's time to tell the button what to do.
![Our Newly Created Pride Button](/talk/images/pride_reaction_tutorial_5.png)
## Adding functionality with the Talk API
Talk exposes a series of tools that plugins can use. In this case we can use `withReaction`. `withReaction` is a HOC (High Order Component) that adds functionality to our components.
We will use it like so:
```js
import React from 'react';
import { withReaction } from 'plugin-api/beta/client/hocs';
class PrideButton extends React.Component {
render() {
return <button>Pride!</button>;
}
}
export default withReaction('pride')(PrideButton);
```
The first parameter we passed to `withReactions` is the name of the reaction. In our case, we will use 'pride'. We must be consistent with this since this will impact storing our data later.
In our next step, let's make clicking our button either generate a reaction or remove the reaction, in case they have already acted on the comment with the same reaction.
```js
import React from 'react';
import { withReaction } from 'plugin-api/beta/client/hocs';
class PrideButton extends React.Component {
handleClick = () => {
const { postReaction, deleteReaction, alreadyReacted } = this.props;
if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
}
};
render() {
return <button onClick={this.handleClick}>Pride!</button>;
}
}
export default withReaction('pride')(PrideButton);
```
`withReactions` makes the component receive `postReaction`, `deleteReaction` and `alreadyReacted`:
* `postReaction`: Posts the reaction to the served <Function>
* `deleteReaction`: Removes the reaction <Function>
* `alreadyReacted`: Lets us know if a user has already reaction to the comment
* `count`: Tells us the number of times that users have reacted to the comment
Now, our frontend functionality is complete, but for all this to work, we still need to add something else to our `index.js` in our main plugin folder. This time we want to extend the server.
```js
const { getReactionConfig } = require('../../plugin-api/beta/server');
module.exports = getReactionConfig('pride');
```
`getReactionConfig` adds the necessary functionality on the server side.
Now our plugin works! People can react to comments with pride!
We don't want to stop quite yet though - let's improve how our button looks visually - and also we aren't checking if a user has already reacted to the comment or not. Let's change that.
### Adding CSS
We are going to create a `PrideButton.css` inside of the folder components. Let's make our button noticable and bright:
```css
.reacted {
background: red;
}
.button {
background: wheat
}
```
And we will add the the use case if someone has already reacted:
```js
import React from 'react';
import styles from './PrideButton.css';
import { withReaction } from 'plugin-api/beta/client/hocs';
class PrideButton extends React.Component {
handleClick = () => {
const { postReaction, deleteReaction, alreadyReacted } = this.props;
if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
}
};
render() {
const { alreadyReacted, count } = this.props;
return (
<button
className={alreadyReacted ? styles.reacted : styles.button}
onClick={this.handleClick}
>
Orgullo!
{count > 0 && count}
</button>
);
}
}
export default withReaction('pride')(PrideButton);
````
And that's it! You've created your first reaction button! :rainbow:
If you would like to continue to the next part of our Plugin Tutorial, see Part 2 in the sidebar.
@@ -0,0 +1,337 @@
---
title: Customizing Plugins with Coral UI
permalink: /customizing-plugins-coral-ui/
---
This is Part 2 of our Plugin Tutorial and assumes you've already completed [Building a Basic Plugin](/building-basic-plugin.md).
Note: We will be using Sketch in this tutorial to generate our SVG code. You can download Sketch here: https://www.sketchapp.com/.
## Coral UI
Within Talk, we have a set of tools we can leverage for our user interface, or UI. We simply call these tools Coral UI.
Within Coral UI, we have icons, buttons, alerts and other components. You can see all the elements available to use within `client/coral-ui`.
To get started using Coral UI, we're going to import it into our component.
```js
import { Icon, Button } from 'plugin-api/beta/client/components/ui';
const myButton = () =>
<Button>
<Icon name="favorite" />
Favorito
</Button>
```
The Coral UI Icon component uses icons from Material Design. You can see the entire list of icons and their respective names at [Material icons - Material Design](https://material.io/icons/).
## Using SVGs
For the Pride Plugin icon, none of the Material icons really seemed to fit so we decided to be a little creative and make our own from scratch.
To do this, we needed to create two states:
- The inactive icon (before someone has clicked/reacted)
- The active icon (after someone has clicked/reacted)
To add a little additional creativity here, we thought that the inactive icon could be grayscale and the active one could be in full color. And a rainbow would be a great idea!
![Mockups for our Pride icon](/talk/images/pride_reaction_tutorial_6.png)
## Export / Copy SVG code
[Sketch](https://www.sketchapp.com/) gives us a way to export the SVG code:
* Right click on the SVG
* Click "Copy SVG code" or "Copy SVG code"
![Exporting SVG Code from Sketch](/talk/images/pride_reaction_tutorial_7.png)
We can export it as a file or copy the inline code to our component. We personally prefer to have the inline code to have more control over the classes and the customization. In this case, we can pass different color palettes, `grayscale` and the other colors `colored`.
Then we can create `RainBowIcon.js` and write the following code:
```js
import React from 'react';
import PropTypes from 'prop-types';
// Las paletas de colores que vamos a utilizar
const colorPalette = {
grayscale: ['#C6C6C6', '#C6C6C6', '#7E7E7E', '#7C7C7C', '#7C7C7C', '#9F9F9F'],
colored: ['#F5C15F', '#EB7835', '#EB5242', '#CB4AB0', '#49B1DE', '#61C482'],
};
const RainbowIcon = ({ paletteType = 'colored', palette = [] }) => {
return (
<svg
width="19px"
height="9px"
viewBox="0 0 19 9"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g transform="translate(-492.000000, -630.000000)">
<g transform="translate(492.000000, 630.000000)">
<path
d="M9.5,0 C4.24785714,0 0,4.02428571 0,9 L2.71428571,9 C2.71428571,5.45142857 5.75428571,2.57142857 9.5,2.57142857 C13.2457143,2.57142857 16.2857143,5.45142857 16.2857143,9 L19,9 C19,4.02428571 14.7521429,0 9.5,0 Z"
fill={palette[0] || colorPalette[paletteType][0]}
/>
<path
d="M9.5,1 C4.80071429,1 1,4.57714286 1,9 L3.42857143,9 C3.42857143,5.84571429 6.14857143,3.28571429 9.5,3.28571429 C12.8514286,3.28571429 15.5714286,5.84571429 15.5714286,9 L18,9 C18,4.57714286 14.1992857,1 9.5,1 Z"
fill={palette[1] || colorPalette[paletteType][1]}
/>
<path
d="M9.5,2 C5.35357143,2 2,5.13 2,9 L4.14285714,9 C4.14285714,6.24 6.54285714,4 9.5,4 C12.4571429,4 14.8571429,6.24 14.8571429,9 L17,9 C17,5.13 13.6464286,2 9.5,2 Z"
fill={palette[2] || colorPalette[paletteType][2]}
/>
<path
d="M9.5,3 C5.90642857,3 3,5.68285714 3,9 L4.85714286,9 C4.85714286,6.63428571 6.93714286,4.71428571 9.5,4.71428571 C12.0628571,4.71428571 14.1428571,6.63428571 14.1428571,9 L16,9 C16,5.68285714 13.0935714,3 9.5,3 Z"
fill={palette[3] || colorPalette[paletteType][3]}
/>
<path
d="M9.5,4 C6.45928571,4 4,6.23571429 4,9 L5.57142857,9 C5.57142857,7.02857143 7.33142857,5.42857143 9.5,5.42857143 C11.6685714,5.42857143 13.4285714,7.02857143 13.4285714,9 L15,9 C15,6.23571429 12.5407143,4 9.5,4 Z"
fill={palette[4] || colorPalette[paletteType][4]}
/>
<path
d="M9.5,5 C7.01214286,5 5,6.78857143 5,9 L6.28571429,9 C6.28571429,7.42285714 7.72571429,6.14285714 9.5,6.14285714 C11.2742857,6.14285714 12.7142857,7.42285714 12.7142857,9 L14,9 C14,6.78857143 11.9878571,5 9.5,5 Z"
fill={palette[5] || colorPalette[paletteType][5]}
/>
</g>
</g>
</g>
</svg>
);
};
// This is important to do so we pass the correct properties to the component
RainbowIcon.propTypes = {
paletteType: PropTypes.oneOf(['colored', 'grayscale']),
palette: PropTypes.array,
};
export default RainbowIcon;
````
Most of the component is code generated by Sketch, except for the properties that we can control control with the palettes. The color of the rainbow lines will be given based on the order of the colors of the palette.
We have two props for our component: `paletteType` and `palette`:
`paletteType`: since we we have two palettes we've created, we can pass these directly as `colored` and `greyscale`
`palette`: if we want to pass an array of colors we can do it using this property
Ready! So now we have our icon. Now let's modify the our button `PrideButton.js` to use our new icon.
```js
import React from 'react';
import cn from 'classnames';
import styles from './PrideButton.css';
import { withReaction } from 'plugin-api/beta/client/hocs';
import RainbowIcon from './RainbowIcon';
class PrideButton extends React.Component {
handleClick = () => {
const { postReaction, deleteReaction, alreadyReacted } = this.props;
if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
}
};
render() {
const { alreadyReacted } = this.props;
return (
<div className={cn(styles.container, 'talk-plugin-pride-container')}>
<a
className={cn(styles.button, 'talk-plugin-pride-button')}
onClick={this.handleClick}
>
{alreadyReacted ? (
<RainbowIcon />
) : (
<RainbowIcon paletteType="grayscale" />
)}
</a>
</div>
);
}
}
export default withReaction('pride')(PrideButton);
```
We will use the property `alreadyReacted` to change the icon and render one in grayscale (using the property `grayscale`).
There are many pros and cons of using inline SVGs that are outside the scope of this tutorial. If you'd like to learn more, you can read [5 Gotchas You're Gonna Face Getting Inline SVG Into Production](https://css-tricks.com/gotchas-on-getting-svg-into-production/) and its follow-up post [Part 2 Gotchas](https://css-tricks.com/gotchas-getting-inline-svg-production-part-ii/).
You can view the source code up to this point here: [talk-plugin-pride @ ae5c1a5](https://github.com/coralproject/talk-plugin-pride/commit/ae5c1a5e26390b9374c87ce5530d60c10b5c325e).
To keep performance top of mine, and given that this portion of SVG code can not be cached, we will create separate SVG files for the two icon states.
We will create the folder `assets` and place our two files inside it: `ColoredRainbowIcon.svg` and `GrayscaleRainbowIcon.svg`. We can export them both with Sketch or simply copy the SVG code into each file.
## Using an SVG in our components
We are going to import our SVG icons just as we did with our components, the only difference is the `.svg` at the end.
```js
import ColoredRainbowIcon from '../assets/ColoredRainbowIcon.svg';
import GrayscaleRainbowIcon from '../assets/GrayscaleRainbowIcon.svg';
```
Since Webpack will give us the new url of the resource, we can us it like this:
```js
<img
src={ColoredRainbowIcon}
className={cn(styles.icon, `${plugin}-icon`)}
/>
```
## Using media queries
Now of course we will need to support several devices and browsers, so we'll need to make sure our plugin responds correctly. For this we can use media queries.
In this case, we want to make sure that on mobile devices that are less than 425px, the reaction label is not shown.
```
@media (max-width: 425px) {
.label {
display: none;
}
}
```
If you look at our PostCSS configuration, you will notice that we use PreCSS. PreCSS allows us to optionally use a syntax that is similar to Sass and allows us to make use of variables:
```css
@custom-media --viewport-medium (width <= 50rem);
@custom-selector :--heading h1, h2, h3, h4, h5, h6;
:root {
--fontSize: 1rem;
--mainColor: #12345678;
}
@media (--viewport-medium) {
body {
color: var(--mainColor);
font-family: system-ui;
font-size: var(--fontSize);
line-height: calc(var(--fontSize) * 1.5);
overflow-wrap: break-word;
padding-inline: calc((var(--fontSize) / 2) + 1px);
}
}
```
To learn more about PreCSS: https://github.com/jonathantneal/precss
## Adding animations
To make the user experience even more fun, we wanted the user to see a small animation when they click on our Pride Button:
```css
.reacted {
animation: rainbow 1s 1;
}
@keyframes rainbow{
20%{color: #EB5242;}
40%{color: #F5C15F;}
60%{color: #61C482;}
80%{color: #49B1DE;}
100%{color: #EB7835;}
}
```
Now lets add this styling through our `classnames` library:
```js
<button
className={cn(
styles.button,
{[styles.reacted]: alreadyReacted}
)}
onClick={this.handleClick}
>
```
Perfect! Now every time a user clicks our reaction, the style is activated, and the animation is triggered.
This is what our completed Reaction now looks like:
```js
import React from 'react';
import cn from 'classnames';
import styles from './PrideButton.css';
import { withReaction } from 'plugin-api/beta/client/hocs';
import ColoredRainbowIcon from '../assets/ColoredRainbowIcon.svg';
import GrayscaleRainbowIcon from '../assets/GrayscaleRainbowIcon.svg';
const plugin = 'talk-plugin-pride';
class PrideButton extends React.Component {
handleClick = () => {
const { postReaction, deleteReaction, alreadyReacted, user } = this.props;
// If the current user does not exist, trigger sign in dialog.
if (!user) {
showSignInDialog();
return;
}
if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
}
};
render() {
const { count, alreadyReacted } = this.props;
return (
<div className={cn(styles.container, `${plugin}-container`)}>
<button
className={cn(
styles.button,
{
[`${styles.reacted} talk-plugin-pride-reacted`]: alreadyReacted,
},
`${plugin}-button`
)}
onClick={this.handleClick}
>
<span className={cn(`${plugin}-label`, styles.label)}>Pride</span>
{alreadyReacted ? (
<img
src={ColoredRainbowIcon}
className={cn(styles.icon, `${plugin}-icon`)}
/>
) : (
<img
src={GrayscaleRainbowIcon}
className={cn(styles.icon, `${plugin}-icon`)}
/>
)}
<span className={cn(`${plugin}-count`)}>{count > 0 && count}</span>
</button>
</div>
);
}
}
export default withReaction('pride')(PrideButton);
```
To view the completed source code, look here: https://github.com/coralproject/talk-plugin-pride
+17
View File
@@ -0,0 +1,17 @@
---
title: Contact
permalink: /contact/
---
## How can I get help integrating Talk into my newsroom?
We're here to help with newsrooms of all sizes. Email our Integration Engineer
([jeff@mozillafoundation.org](mailto:jeff@mozillafoundation.org)) to set up a meeting.
## How do I request a feature or submit a bug?
The best way is to [submit a GitHub issue](https://github.com/coralproject/talk/issues). Make sure you give plenty of details, our Core Team can usually respond within a few hours on weekdays.
## How can our dev team contribute to Talk?
We are lucky to work with newsroom dev teams and individual contributors who span the world, and come from newsrooms of all sizes. You can read our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) to get started, but feel free to reach out to us via GitHub, or get in touch directly with Jeff via jeff@mozillafoundation.org.
Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

+2 -2
View File
@@ -23,6 +23,6 @@ That would set the language to French.
To add a new Talk translation, simply translate the `en.yml` file (https://github.com/coralproject/talk/blob/master/locales/en.yml) into a new yml file with the language code of your choice. You can find supported language codes here: http://www.localeplanet.com/icu/iso639.html
If you are a developer contributing a new language, you'll need to add the required i18n support in the i18n files (or you can leave that to us if you like). If you're a non-developer, you can submit the translation via Github if you feel comfortable doing that, or feel free to email it to us via our Support: support@coralproject.net
If you are a developer contributing a new language, you'll need to add the required i18n support in the i18n files (or you can leave that to us if you like). If you're a non-developer, you can submit the translation via GitHub if you feel comfortable doing that, or feel free to email it to us via our Support: support@coralproject.net
If you want to suggest a new language or put a placeholder for a translation youre working on, feel free to create a Github issue: https://github.com/coralproject/talk/issues/new
If you want to suggest a new language or put a placeholder for a translation youre working on, feel free to create a GitHub issue: https://github.com/coralproject/talk/issues/new
+16 -10
View File
@@ -7,16 +7,22 @@
</a>
<ul class="sidebar__list">
{% for item in config.sidebar.side %}
<li class="sidebar__section{% for item in item.children %}{% if is_current(item.url) %} active toggled{% endif %}{% endfor %}">
<a href="#" class="sidebar__header">{{ item.title }}</a>
<ul class="sidebar__links">
{% for item in item.children %}
<li class="{% if is_current(item.url) %}active{% endif %}">
<a href="{% if !is_current(item.url) %}{{ url_for(item.url) }}{% else %}#{% endif %}">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% if item.url %}
<li class="sidebar__section{% if is_current(item.url) %} active toggled{% endif %}">
<a href="{{ url_for(item.url) }}" class="sidebar__header sidebar__header__link">{{ item.title }}</a>
</li>
{% else %}
<li class="sidebar__section{% for item in item.children %}{% if is_current(item.url) %} active toggled{% endif %}{% endfor %}">
<a href="#" class="sidebar__header">{{ item.title }}</a>
<ul class="sidebar__links">
{% for item in item.children %}
<li class="{% if is_current(item.url) %}active{% endif %}">
<a href="{% if !is_current(item.url) %}{{ url_for(item.url) }}{% else %}#{% endif %}">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
+4 -1
View File
@@ -15,6 +15,9 @@ body {
}
}
img {
max-width: 800px;
}
#graphql-docs {
& > div > div {
@@ -444,4 +447,4 @@ a.brand {
.plugin {
display: none;
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ $(document).ready(function() {
});
// Setup the menu controls.
$('a.sidebar__header').on('click', function(e) {
$('a.sidebar__header:not(.sidebar__header__link)').on('click', function(e) {
e.preventDefault();
$('.sidebar__section.toggled').removeClass('toggled');
$(this)
+17 -2
View File
@@ -1,5 +1,20 @@
const jobs = [require('./mailer'), require('./scraper')];
const mailer = require('./mailer');
const scraper = require('./scraper');
const { createLogger } = require('../services/logging');
const logger = createLogger('jobs');
const process = () => jobs.forEach(job => job());
const jobs = { mailer, scraper };
const process = (...disabledJobs) =>
Object.entries(jobs).forEach(([taskName, taskFnc]) => {
if (disabledJobs.includes(taskName)) {
logger.info({ taskName }, 'Not starting job, disabled');
return;
}
logger.info({ taskName }, 'Starting job');
taskFnc();
});
module.exports = { process };
+9 -9
View File
@@ -1,6 +1,7 @@
const { task } = require('../services/mailer');
const nodemailer = require('nodemailer');
const debug = require('debug')('talk:jobs:mailer');
const { createLogger } = require('../services/logging');
const logger = createLogger('jobs:mailer');
const Context = require('../graph/context');
const { get } = require('lodash');
@@ -112,16 +113,17 @@ const processJob = transport => async ({ id, data }, done) => {
// Get the email address from the job data.
message.to = await getEmailAddress(data);
debug(`Starting to send mail for Job[${id}]`);
const log = logger.child({ jobID: id });
log.info('Starting to send mail');
// Actually send the email.
transport.sendMail(message, err => {
if (err) {
debug(`Failed to send mail for Job[${id}]:`, err);
logger.error({ err }, 'Failed to send mail');
return done(err);
}
debug(`Finished sending mail for Job[${id}]`);
logger.info('Finished sending mail');
return done();
});
};
@@ -133,15 +135,13 @@ module.exports = () => {
// Get a transport.
const transport = getTransport();
if (transport === null) {
console.warn(
new Error(
'sending email is not enabled because required configuration is not available'
)
logger.warn(
'Sending email is not enabled because required configuration is not available'
);
return;
}
debug(`Now processing ${task.name} jobs`);
logger.info({ taskName: task.name }, 'Now processing jobs');
return task.process(processJob(transport));
};
+480
View File
@@ -0,0 +1,480 @@
ar:
your_account_has_been_suspended: تم تعليق حسابك مؤقتا.
your_account_has_been_banned: تم حظر حسابك.
your_username_has_been_rejected: تم تعليق حسابك لعدم صلاحية اسم المستخدم الخاصة بك. لاستعادة حسابك رجاء أدخل اسم مستخدم جديدة.
embed_comments_tab: تعليقات
bandialog:
are_you_sure: "أنت متأكد أنك تريد حظر {0}؟"
ban_user: "حظر المستخدم؟"
banned_user: "مستخدم محظور"
cancel: "إلغاء"
note: "ملاحظة: {0}"
note_reject_comment: "حظر هذا المستخدم سيضع هذا التعليق في قائمة الرفض."
note_ban_user: "حظر هذا المستخدم لن يسمح له بالتعليق أو التفاعل أو الإبلاغ عن تعليقات."
yes_ban_user: "نعم، احظر المستخدم"
write_a_message: "أكتب رسالة"
send: "أرسل"
notify_ban_headline: "أبلغ المستخدم بالحظر"
notify_ban_description: "سيتم إبلاغ المستخدم عن طريق البريد الإلكتروني بأنه قد تم حظره من المجموعة"
email_message_ban: "العزيز {0},\n\nتم الدخول باستخدام حسابك وانتهاك قواعد المجموعة، لذا لقد تم حظر حسابك. لن تستطيع التعليق مجددا، أو الإعجاب أو الإبلاغ عن تعليقات. إذا كنت ترى أن ذلك تم بالخطأ، رجاء إبلاغ أحد أعضاء الفريق الخاص بالمجموعة."
bio_offensive: "السيرة الذاتية مسيئة"
cancel: "إلغاء"
confirm_email:
click_to_confirm: "إضغط في الأسفل لتأكيد البريد الألكتروني"
confirm: "تأكيد"
password_reset:
mail_sent: 'إذا كان لديك حساب مسجل، فقد تم إرسال رابط إعادة تعيين كلمة المرور إلى هذا البريد الإلكتروني'
set_new_password: "تغيير كلمة السر الخاصة بك"
new_password: "كلمة السر الجديدة"
new_password_help: "كلمة السر يجب أن تكون 8 أحرف على الأقل"
confirm_new_password: "تأكيد كلمة السر الجديدة"
change_password: "تغيير كلمة السر"
characters_remaining: "هناك أحرف متبقية"
comment:
anon: "غير معلوم"
undo_reject: "ابطال العملية"
ban_user: "حظر المستخدم"
comment: "نشر تعليق"
edited: محرر
flagged: "إشارة"
view_context: "عرض السياق"
comment_box:
post: "نشر"
cancel: "إلغاء"
reply: "رد"
comment: "نشر تعليق"
name: "الاسم"
comment_post_notif: "تم نشر تعليقك."
comment_post_notif_premod: "شكرا لك على النشر. سيراجع فريق الإشراف لدينا تعليقك قريبا."
comment_post_banned_word: "تعليقك يحتوي على كلمة أو أكثر غير مسموح بها، لذا لن يتم نشره. إذا كنت تعتقد أن هذه الرسالة خطأ، رجاء الاتصال بفريق الإشراف لدينا."
characters_remaining: "أحرف متبقية"
comment_offensive: "هذا التعليق مسيء"
comment_singular: تعليق
comment_plural: تعليقات
comment_post_banned_word: "تعليقك يحتوي على كلمة أو أكثر غير مسموح بها، لذا لن يتم نشره. إذا كنت تعتقد أن هذه الرسالة خطأ، رجاء الاتصال بفريق الإشراف لدينا."
comment_post_notif: "تم نشر تعليقك."
comment_post_notif_premod: "شكرا لك على النشر. سيراجع فريق الإشراف لدينا تعليقك قريبا."
common:
copy: 'نسخ'
error: 'حدث خطأ.'
reply: 'رد'
replies: 'ردود'
reaction: 'رد فعل'
reactions: 'ردود أفعال'
story: 'قصة'
flagged_usernames:
notify_approved: '{0} وافق على اسم المستخدم {1}'
notify_rejected: '{0} رفض اسم المستخدم {1}'
notify_flagged: '{0} بلغ عن اسم المستخدم {1}'
notify_changed: 'المستخدم {0} غير اسم المستخدم الخاص به إلى {1}'
community:
account_creation_date: "تاريخ إنشاء الحساب"
active: فعال
admin: إداري
ads_marketing: "هذا يبدو وكأنه إعلان/تسويق"
are_you_sure: "متأكد أنك تريد حظر {0}؟"
ban_user: "حظر المستخدم؟"
banned: محظور
banned_user: "مستخدم محظور"
cancel: إلغاء
dont_like_username: "غير معجب باسم المستخدم"
flaggedaccounts: "أسماء المستخدمين المبلغ عنها"
flags: شارات
impersonating: "انتحال شخصية"
loading: "تحميل النتائج"
moderator: مشرف
newsroom_role: "دور غرفة الأخبار"
no_flagged_accounts: "قائمة أسماء المستخدمين المبلغ عنها فارغة حاليا."
no_results: "لم يتم العثور على مستخدمين باسم المستخدم أو عنوان البريد الإلكتروني هذا. انهم يختبئون!"
offensive: "مسيء"
other: أخرى
people: أشخاص
role: "اختر الدور..."
select_status: "اختر الحالة..."
spam_ads: "بريد مؤذي/إعلانات"
staff: "فريق العمل"
status: الحالة
username_and_email: "اسم المستخدم والبريد الإلكتروني"
yes_ban_user: "نعم إحظر المستخدم"
commenter: "معلق"
configure:
apply: طبق
banned_word_text: "التعليقات التي تحتوي على هذه الكلمات أو العبارات سيتم حذفها آليا من جدول التعليقات. اطبع كلمة واضغط Enter أو Tab لزيادة الكلمة. اختياريا الصق قائمة مقسمة بالفصلات."
banned_words_title: "قائمة بالكلمات المحظورة"
close: "اغلق"
close_after: "اغلق التعليقات بعد"
close_stream: "اغلق الجدول"
close_stream_configuration: "تم إغلاق جدول التعليقات هذا. بفتحك لهذا الجدول سيتم قبول وعرض تعليقات جديدة"
closed_comments_desc: "اكتب رسالة ليتم عرضها عندما يتم إغلاق جدول التعليقات ولا يتم قبول أي تعليقات جديدة."
closed_comments_label: "اكتب رسالة..."
closed_stream_settings: "رسالة الجدول المغلق"
comment_count_error: "رجاء كتابة رقم صحيح."
comment_count_header: "الحد من طول التعليق"
comment_count_text_post: حروف
comment_count_text_pre: "سيتم وضع حد للتعليقات عند"
comment_settings: إعدادات
comment_stream: "جدول التعليقات"
comment_stream_will_close: "سيتم إغلاق جدول التعليق"
community: المجموعة
configure: تهيئة
copy_and_paste: "انسخ والصق التعليمات البرمجية في الأسفل بنظام إدارة المحتوى لتضمين تعليقك داخل المقالات"
custom_css_url: "رابط CSS مخصص"
custom_css_url_desc: "رابط CSS الذي سيتجاوز أنماط جدول التعليقات المضمن. يمكن أن يكون داخلي أو خارجي."
days: أيام
description: "كإداري يمكنك تعديل إعدادات جدول التعليقات لهذه القصة:"
domain_list_text: "أدخل عناوين النطاقات التي سوف تسمح فيها لTalk.. مثلا بيئات التدريج و الإنتاج (مثلا localhost:3000 staging.domain.com domain.com)."
domain_list_title: "النطاقات المسموح بها"
edit_comment_timeframe_heading: "عدل الإطار الزمني للتعليق"
edit_comment_timeframe_text_pre: "سيكون لدى المعلقين"
edit_comment_timeframe_text_post: "ثوان لتحرير تعليقاتهم."
embed_comment_stream: "تضمين الجدول"
enable_premod_links_text: "يجب على المشرفين الموافقة على أي تعليق يحتوي على رابط قبل نشره."
enable_pre_moderation: "تمكين الإشراف المسبق"
enable_pre_moderation_text: "يجب على المشرفين الموافقة على أي تعليق قبل نشره."
enable_premod_links: "إشراف مسبق على التعليقات التي تحتوي على روابط"
enable_premod: "تمكين الإشراف المسبق"
enable_premod_description: "يجب على المشرفين الموافقة على أي تعليق قبل نشره."
enable_premod_links_description: "يجب على المشرفين الموافقة على أي تعليق يحتوي على رابط قبل نشره."
enable_questionbox: "اطرح سؤال على القراء"
enable_questionbox_description: "هذا السؤال سيظهر في الجزء العلوي من جدول التعليقات هذا. اسأل القراء عن مسألة معينة في المقال او اطرح أسئلة نقاشية.. الخ."
hours: ساعات
include_comment_stream: "ادراج وصف جدول التعليقات للقراء"
include_comment_stream_desc: "اكتب رسالة ليتم إضافتها إلى الجزء العلوي من جدول التعليقات. ضع موضوعا، اشمل القواعد الإرشادية للمجموعة .. الخ"
include_text: "ادرج النص هنا."
include_question_here: "اكتب سؤالك هنا:"
moderate: اشرف
moderation_settings: "اعدادات الإشراف"
open: "مفتوح"
open_stream: "افتح الجدول"
open_stream_configuration: "جدول التعليقات هذا مفتوح. بإغلاق هذا الجدول لن يتم قبول تعليقات جديدة، وستبقى التعليقات القديمة ظاهرة."
require_email_verification: "يلزم التحقق من البريد الإلكتروني"
require_email_verification_text: "يجب على المستخدمين الجدد التحقق من بريدهم الإلكتروني قبل التعليق"
save_changes: "احفظ التعديلات"
shortcuts: اختصارات
sign_out: "خروج"
stories: قصص
stream_settings: "اعدادات الجدول"
suspect_word_title: "قائمة الكلمات المشبوهة"
suspect_word_text: "التعليقات التي تحتوي على هذه الكلمات أو العبارات سيتم تمييزها في جدول التعليقات. اطبع كلمة واضغط Enter أو Tab لزيادة الكلمة. اختياريا الصق قائمة مقسمة بالفصلات."
tech_settings: "إعدادات تقنية"
title: "تهيئة جدول التعليق"
weeks: أسابيع
wordlist: "الكلمات المحظورة"
continue: "واصل"
createdisplay:
check_the_form: "استمارة غير صالحة. يرجى التحقق من الحقول"
continue: "تابع بنفس اسم المستخدم الخاص بفيسبوك"
error_create: "حدث خطأ أثناء تغيير اسم المستخدم"
fake_comment_body: "هذا مثال للتعليق. يمكن للقراء تبادل الأفكار والآراء مع غرف الأخبار في قسم التعليقات."
fake_comment_date: "منذ دقيقة"
if_you_dont_change_your_name: "إذا لم تقم بتغيير اسم المستخدم الخاص بك في هذه الخطوة سوف يظهر اسم المستخدم الخاص بفيسبوك جنبا إلى جنب مع كل تعليقاتك."
required_field: "حقل مطلوب"
save: حفظ
special_characters: "يمكن أن تحتوي أسماء المستخدمين على أحرف وأرقام و _ فقط"
username: اسم المستخدم
write_your_username: "عدل اسم المستخدم"
your_username: "يظهر اسم المستخدم في كل تعليق تنشره."
done: تم
edit_comment:
body_input_label: "عدل هذا التعليق"
save_button: "حفظ التغييرات"
edit_window_expired: "لم يعد بإمكانك تعديل هذا التعليق. انتهت صلاحية نافذة الوقت للقيام بذلك. لماذا لا تنشر آخر؟"
edit_window_expired_close: "أغلق"
edit_window_timer_prefix: "نافذة التعديل : "
second: "ثانية"
seconds_plural: "ثوان"
minute: "دقيقة"
minutes_plural: "دقائق"
email:
suspended:
subject: "تم تعليق حسابك"
banned:
subject: "تم حظر حسابك"
body: "وفقا لإرشادات مجتمع كورال بروجيكت، تم حظر حسابك. لم يعد مسموحا لك التعليق أو وضع إشارات أو التفاعل مع مجتمعنا."
confirm:
has_been_requested: "تم طلب تأكيد بالبريد الإلكتروني للحساب التالي:"
to_confirm: "لتأكيد الحساب، يرجى زيارة الرابط التالي:"
confirm_email: ".تأكيد عنوان البريد الإلكتروني"
if_you_did_not: "إذا لم تطلب ذلك، يمكنك تجاهل هذه الرسالة الإلكترونية."
subject: "تأكيد البريد الإلكتروني"
password_reset:
we_received_a_request: "لقد تلقينا طلبا لإعادة تعيين كلمة المرور. إذا لم تطلب هذا التغيير، فيمكنك تجاهل هذه الرسالة الإلكترونية."
if_you_did: "اذا فعلت،"
please_click: "الرجاء النقر هنا لإعادة تعيين كلمة المرور"
embedlink:
copy: "نسخ إلى الحافظة"
error:
COMMENT_PARENT_NOT_VISIBLE: "التعليق الذي ترد عليه تمت إزالته أو غير موجود."
EMAIL_VERIFICATION_TOKEN_INVALID: "رمز التحقق من البريد الإلكتروني غير صالح."
PASSWORD_RESET_TOKEN_INVALID: "رابط إعادة تعيين كلمة المرور غير صالح."
COMMENT_TOO_SHORT: "يجب أن تكون التعليقات أكثر من حرف واحد، يرجى مراجعة تعليقك وإعادة المحاولة."
NOT_AUTHORIZED: "غير مصرح لك بتنفيذ هذا الإجراء."
NO_SPECIAL_CHARACTERS: "يمكن أن تحتوي أسماء المستخدمين على أحرف, أرقام و _ فقط"
PASSWORD_LENGTH: "كلمة المرور قصيرة جدا"
PROFANITY_ERROR: "يجب ألا تحتوي أسماء المستخدمين على الألفاظ النابية. يرجى الاتصال بالإداري إذا كنت تعتقد أن هذا خطأ."
RATE_LIMIT_EXCEEDED: "تجاوز حد المعدل"
USERNAME_IN_USE: "اسم المستخدم قيد الاستخدام"
USERNAME_REQUIRED: "يجب إدخال اسم مستخدم"
EMAIL_NOT_VERIFIED: "لم يتم التحقق من عنوان البريد الإلكتروني"
EDIT_WINDOW_ENDED: "لم يعد بإمكانك تحرير هذا التعليق. نافذة الوقت للقيام بذلك قد انتهت صلاحيتها."
EDIT_USERNAME_NOT_AUTHORIZED: "ليس لديك إذن بتحديث اسم المستخدم الخاص بك."
SAME_USERNAME_PROVIDED: "يجب عليك تقديم اسم مستخدم مختلف."
EMAIL_IN_USE: "البريد الالكتروني قيد الاستخدام"
EMAIL_REQUIRED: "مطلوب عنوان البريد الإلكتروني"
LOGIN_MAXIMUM_EXCEEDED: "لقد أجريت العديد من محاولات إدخال كلمة المرور غير الناجحة. أرجو الإنتظار."
PASSWORD_REQUIRED: "يجب إدخال كلمة مرور"
COMMENTING_CLOSED: "تم إغلاق فاعلية التعليق"
NOT_FOUND: "المورد غير موجود"
ALREADY_EXISTS: "المورد موجود من قبل"
INVALID_ASSET_URL: "رابط المادة غير صالح"
CANNOT_IGNORE_STAFF: "لا يمكن تجاهل الموظفين."
email: "ليس بريدا إلكترونيا صالحا"
confirm_password: "كلمات المرور غير متطابقة. يرجى التحقق مرة أخرى"
network_error: "فشل الاتصال بالخادم. تحقق من اتصالك بالإنترنت وحاول مرة أخرى."
email_not_verified: "عنوان البريد الإلكتروني {0} لم يتم التحقق منه."
email_password: "مجموعة البريد الإلكتروني و / أو كلمة المرور غير صحيحة."
organization_name: "يجب أن يحتوي اسم المؤسسة على أحرف أو أرقام فقط."
password: "يجب أن تكون كلمة المرور 8 أحرف على الأقل"
username: "يمكن أن تحتوي أسماء المستخدمين على أرقام, أحرف و _ فقط"
unexpected: "حدث خطأ غير متوقع. آسف!"
required_field: "هذه الخانة مطلوبه"
temporarily_suspended: "حسابك معلق حاليا. سيتم إعادة تنشيطه {0}. يرجى الاتصال بنا إذا كان لديك أي أسئلة."
flag_comment: "الإبلاغ عن تعليق"
flag_reason: "سبب الإبلاغ (اختياري)"
flag_username: "بلغ عن اسم المستخدم"
framework:
banned_account_header: "حسابك محظور حاليا."
banned_account_body: "هذا يعني أنه لا يمكنك الإعجاب ، أو الإبلاغ ، أو كتابة التعليقات."
comment: تعليق
comment_is_ignored: "هذا التعليق مخفي لأنك تجاهلت هذا المستخدم."
comment_is_rejected: "لقد رفضت هذا التعليق."
comment_is_hidden: "هذا التعليق غير متاح."
comments: تعليقات
configure_stream: "تهيئة"
content_not_available: "هذا المحتوى غير متوفر"
edit_name:
button: أرسل
error: "يمكن أن تحتوي أسماء المستخدمين على أحرف, أرقام و _ فقط"
label: "اسم مستخدم جديد"
msg: "تم تعليق حسابك حاليا نظرا لأن اسم المستخدم قد اعتبر غير لائق. لاستعادة حسابك، يرجى إدخال اسم مستخدم جديد. يرجى الاتصال بنا إذا كان لديك أي أسئلة."
changed_name:
msg: "يتم مراجعة تغيير اسم المستخدم من قبل فريق الإشراف لدينا."
my_comments: "تعليقاتي"
my_profile: "ملفي"
new_count: "شاهد {0} أكثر {1}"
profile: الملف الشخصي
show_all_comments: "عرض كل التعليقات"
success_bio_update: "تم تحديث السيرة الذاتية"
success_name_update: "تم تحديث اسم المستخدم"
success_update_settings: "تم تطبيق التغييرات التي أجريتها على جدول التعليقات في هذه المقالة"
show_all_replies: عرض جميع الردود
show_more_replies: عرض المزيد من الردود
view_more_comments: "عرض مزيد من التعليقات"
view_reply: "عرض الرد"
from_settings_page: "من صفحة الملف الشخصي يمكنك مشاهدة سجل التعليقات."
like: إعجاب
loading_results: "جار تحميل النتائج"
marketing: "هذا يشبه الإعلان / التسويق"
moderate_this_stream: "أشرف على هذا الجدول"
flags:
reasons:
user:
username_offensive: "مسيء"
username_nolike: "لم يعجبنى"
username_impersonating: "إنتحال شخصية"
username_spam: "غير مرغوب فيه"
username_other: "آخر"
comment:
comment_offensive: "مسيء"
comment_spam: "غير مرغوب فيه"
comment_noagree: "أعارض"
comment_other: "آخر"
suspect_word: "كلمة مشتبهة"
banned_word: "كلمة محظورة"
body_count: "يتجاوز النص الحد الأقصى للطول المسموح"
trust: "ثقة"
links: "رابط"
modqueue:
account: "account flags"
actions: Actions
all: all
all_streams: "All Streams"
notify_edited: '{0} edited comment "{1}"'
notify_accepted: '{0} accepted comment "{1}"'
notify_rejected: '{0} rejected comment "{1}"'
notify_flagged: '{0} flagged comment "{1}"'
notify_reset: '{0} reset status of comment "{1}"'
approve: "Approve"
approved: "Approved"
ban_user: "Ban"
billion: B
close: Close
empty_queue: "No more comments to moderate! You're all caught up. Go have some ☕️"
flagged: flagged
reported: reported
less_detail: "Less detail"
likes: likes
million: M
mod_faster: "Moderate faster with keyboard shortcuts"
moderate: "Moderate →"
more_detail: "More detail"
new: New
newest_first: "Newest First"
navigation: Navigation
next_comment: "Go to the next comment"
toggle_search: "Open search"
next_queue: "Switch queues"
oldest_first: "Oldest First"
premod: pre-mod
prev_comment: "Go to the previous comment"
reject: "Reject"
rejected: "Rejected"
reply: "Reply"
select_stream: "Select Stream"
shift_key: "⇧"
shortcuts: "Shortcuts"
sort: "Sort"
show_shortcuts: "Show Shortcuts"
singleview: "Zen mode"
thismenu: "Open this menu"
jump_to_queue: "Jump to specific queue"
thousand: k
try_these: "Try these"
view_more_shortcuts: "View more shortcuts"
my_comment_history: "سجل التعليقات"
name: اسم
no_agree_comment: "لا أوافق على هذا التعليق"
no_like_bio: "أنا لا أحب هذه السيرة الذاتية"
no_like_username: "أنا لا أحب اسم المستخدم هذا"
already_flagged_username: "لقد سبق لك وضع علامة باسم المستخدم هذا."
other: آخر
permalink: شارك
personal_info: "هذا التعليق يكشف عن معلومات تعريف شخصية"
post: نشر
profile: الملف الشخصي
profile_settings: "إعدادات الملف الشخصي"
reply: رد
report: أبلغ
report_notif: "شكرا على الإبلاغ عن هذا التعليق. تم إبلاغ فريق الإشراف لدينا وسيراجعه قريبًا."
report_notif_remove: "لقد تمت إزالة بلاغك."
reported: بلغ عنه
comment_history_blank:
title: You have not written any comments
info: A history of your comments will appear here
settings:
from_settings_page: "من صفحة الملف الشخصي يمكنك مشاهدة سجل التعليقات."
my_comment_history: "سجل التعليقات"
profile: الملف الشخصي
profile_settings: "إعدادات الملف الشخصي"
sign_in: "تسجيل الدخول"
to_access: "للوصول إلى الملف الشخصي"
user_no_comment: "لم تترك تعليقا مطلقا. إنضم إلى المحادثة!"
stream:
all_comments: "كل التعليقات"
temporarily_suspended: "وفقا لإرشادات المجموعة {0}، تم تعليق حسابك مؤقتا. الرجاء إعادة الانضمام إلى المحادثة {1}."
comment_not_found: "تمت إزالة هذا التعليق أو أنه غير موجود."
no_comments: "لا توجد تعليقات حتى الآن، لماذا لا تكتب واحد؟"
no_comments_and_closed: "لم تكن هناك تعليقات على هذه المقالة."
step_1_header: "بلغ عن مشكلة"
step_2_header: "ساعدنا على الفهم"
step_3_header: "شكرا لك على المساهمة الخاصة بك"
streams:
all: All
article: Story
closed: Closed
empty_result: "No assets match this search. Maybe try widening your search?"
filter_streams: "Filter Streams"
newest: Newest
oldest: Oldest
open: Open
pubdate: "Publication Date"
search: Search
sort_by: "Sort By"
status: "Stream Status"
stream_status: "Stream Status"
suspenduser:
title_suspend: "Suspend User"
description_suspend: "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete."
select_duration: "Select suspension duration"
one_hour: "1 hour"
hours: "{0} hours"
days: "{0} days"
hour: "{0} hours"
day: "{0} days"
cancel: "Cancel"
suspend_user: "Suspend User"
email_message_suspend: "Dear {0},\n\nIn accordance with {1}s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}."
title_notify: "Notify the user of their temporary suspension"
notify_suspend_until: "User {0} has been temporarily suspended. This suspension will automatically end {1}."
description_notify: "Suspending this user will temporarily disable their account."
write_message: "Write a message"
send: Send
reject_username:
username: username
no_cancel: "No cancel"
description_reject: "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily suspend this user until they rewrite their {0}."
title_notify: "Notify the user of their temporary suspension"
description_notify: "Suspending this user will temporarily disable their account."
title_reject: "We noticed you rejected a username"
suspend_user: "Suspend User"
yes_suspend: "Yes suspend"
email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns."
write_message: "Write a message"
send: Send
thank_you: "نحن نقدر سلامتك وردود الفعل. سيراجع المشرف التقرير الخاص بك"
user:
bio_flags: "flags for this bio"
user_bio: "User Bio"
username_flags: "flags for this username"
user_detail:
remove_suspension: "Remove Suspension"
suspend: "Suspend User"
remove_ban: "Remove Ban"
ban: "Ban User"
member_since: "Member Since"
email: "Email"
total_comments: "Total Comments"
reject_rate: "Reject Rate"
reports: "Reports"
all: "All"
rejected: "Rejected"
account_history: "Account History"
account_history:
user_banned: "User banned"
ban_removed: "Ban removed"
username_status: "Username {0}"
suspended: "Suspended, {0}"
suspension_removed: "Suspension removed"
system: "System"
date: "Date"
action: "Action"
taken_by: "Taken By"
user_impersonating: "هذا المستخدم ينتحل شخصية"
user_no_comment: "لم تترك تعليقا مطلقا. إنضم إلى المحادثة!"
username_offensive: "اسم المستخدم هذا مسيء"
view_conversation: "عرض المحادثة"
install:
initial:
description: "Let's set up your Talk community in just a few short steps."
submit: "Get Started"
add_organization:
description: "Please tell us the name of your organization. This will appear in emails when inviting new team members."
label: "Organization Name"
save: "Save"
create:
email: "Email address"
username: "Username"
password: "Password"
confirm_password: "Confirm Password"
save: "Save"
permitted_domains:
title: "Permitted domains"
description: "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)."
submit: "Finish install"
final:
description: "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now."
launch: "Launch Talk"
close: "Close this Installer"
admin_sidebar:
view_options: "View Options"
sort_comments: "Sort Comments"
+2 -1
View File
@@ -1 +1,2 @@
module.exports = JSON.parse(process.env.TALK_PLUGINS_JSON);
const hjson = require('hjson');
module.exports = hjson.parse(process.env.TALK_PLUGINS_JSON);
+12 -9
View File
@@ -26,17 +26,20 @@ try {
if (PLUGINS_JSON && PLUGINS_JSON.length > 0) {
debug('Now using TALK_PLUGINS_JSON environment variable for plugins');
pluginsPath = envPlugins;
} else if (fs.existsSync(customPlugins)) {
debug(`Now using ${customPlugins} for plugins`);
pluginsPath = customPlugins;
plugins = require(pluginsPath);
} else {
debug(`Now using ${defaultPlugins} for plugins`);
pluginsPath = defaultPlugins;
}
if (fs.existsSync(customPlugins)) {
debug(`Now using ${customPlugins} for plugins`);
pluginsPath = customPlugins;
} else {
debug(`Now using ${defaultPlugins} for plugins`);
pluginsPath = defaultPlugins;
}
// Load/parse the plugin content using hjson.
const pluginContent = fs.readFileSync(pluginsPath, 'utf8');
plugins = hjson.parse(pluginContent);
// Load/parse the plugin content using hjson.
const pluginContent = fs.readFileSync(pluginsPath, 'utf8');
plugins = hjson.parse(pluginContent);
}
} catch (err) {
if (err.code === 'ENOENT') {
console.error(
@@ -1,3 +1,17 @@
ar:
error:
COMMENT_IS_SPAM: |
تبدو اللغة في هذا التعليق مثل الرسائل غير المرغوب فيها. تستطيع تعديل التعليق أو تقديمه على أي حال لمراجعة مشرف.
talk-plugin-akismet:
spam: "بريد غير مرغوب فيه"
spam_comment: "بريد غير مرغوب فيه"
detected: "الكشف عنها من قبل Akismet"
still_spam: |
شكرا لكم. سيراجع فريق الإشراف لدينا تعليقك قريبا.
flags:
reasons:
comment:
spam_comment: "تم اكتشاف الرسائل غير المرغوب فيها"
da:
error:
COMMENT_IS_SPAM: |
@@ -61,7 +75,7 @@ fr:
nl_NL:
error:
COMMENT_IS_SPAM: |
Deze reactie ziet er uit als spam. Je kan je reactie
Deze reactie ziet er uit als spam. Je kan je reactie
wijzigen, of indienen zodat ons moderatieteam het kan beoordelen.
talk-plugin-akismet:
spam: "Spam"
@@ -1,3 +1,48 @@
ar:
talk-plugin-auth:
login:
email_verify_cta: "يرجى التحقق من عنوان البريد الإلكتروني الخاص بك."
request_new_verify_email: "طلب بريد إلكتروني آخر"
verify_email: "شكرا لك على إنشاء حساب جديد! لقد أرسلنا رسالة إلكترونية إلى البريد الإلكتروني الذي قدمته للتحقق من حسابك."
verify_email2: "يجب إثبات ملكية حسابك قبل التفاعل مع المجموعة."
not_you: "ليس انت؟"
logged_in_as: "تسجيل الدخول ك"
facebook_sign_in: "تسجيل الدخول باستخدام الفيسبوك"
facebook_sign_up: "اشترك عبر حساب فايسبوك"
logout: "خروج"
sign_in: "تسجيل الدخول"
sign_in_to_join: "سجل الدخول للانضمام إلى المحادثة"
or: "أو"
email: "البريد الإلكتروني"
password: "كلمة المرور"
forgot_your_pass: "نسيت كلمة المرور؟"
need_an_account: "تحتاج الى حساب؟"
register: "تسجيل"
sign_up: "سجل"
confirm_password: "تأكيد كلمة المرور"
username: "اسم المستخدم"
already_have_an_account: "هل لديك حساب؟"
recover_password: "إستعادة كلمة المرور"
email_in_use: "البريد الالكتروني قيد الاستخدام"
email_or_username_in_use: "البريد الإلكتروني أو اسم المستخدم قيد الاستخدام"
required_field: "هذه الخانة مطلوبه"
passwords_dont_match: "كلمات المرور غير متطابقة."
special_characters: "يمكن أن تحتوي أسماء المستخدمين على أحرف وأرقام و _ فقط"
sign_in_to_comment: "تسجيل الدخول للتعليق"
check_the_form: "استمارة غير صالحة. يرجى التحقق من الحقول"
set_username_dialog:
check_the_form: "استمارة غير صالحة. يرجى التحقق من الحقول"
continue: "تابع بنفس اسم المستخدم الخاص بفيسبوك"
error_create: "حدث خطأ أثناء تغيير اسم المستخدم"
fake_comment_body: "هذا مثال للتعليق. يمكن للقراء تبادل الأفكار والآراء مع غرف الأخبار في قسم التعليقات."
fake_comment_date: "منذ دقيقة"
if_you_dont_change_your_name: "إذا لم تقم بتغيير اسم المستخدم الخاص بك في هذه الخطوة سوف يظهر اسم المستخدم الخاص بفيسبوك جنبا إلى جنب مع كل تعليقاتك."
required_field: "حقل مطلوب"
save: حفظ
special_characters: "يمكن أن تحتوي أسماء المستخدمين على أحرف, أرقام و _ فقط"
username: اسم المستخدم
write_your_username: "عدل اسم المستخدم"
your_username: "يظهر اسم المستخدم في كل تعليق تنشره."
da:
talk-plugin-auth:
login:
@@ -1,3 +1,5 @@
ar:
talk-plugin-author-menu:
da:
talk-plugin-author-menu:
en:
@@ -1,3 +1,7 @@
ar:
talk-plugin-facebook-auth:
sign_in: "تسجيل الدخول عبر حساب الفيسبوك"
sign_up: "اشترك عبر حساب الفيسبوك"
en:
talk-plugin-facebook-auth:
sign_in: "Sign in with Facebook"
@@ -21,4 +25,4 @@ zh_TW:
de:
talk-plugin-facebook-auth:
sign_in: "Mit Facebook anmelden"
sign_up: "Mit Facebook registrieren"
sign_up: "Mit Facebook registrieren"
@@ -1,3 +1,18 @@
ar:
talk-plugin-featured-comments:
un_feature: إلغاء التميّز
feature: ميّز
featured: متميز
featured_comments: التعليقات المميزة
go_to_conversation: انتقل إلى المحادثة
tooltip_description: تعليقات مختارة من قبل فريقنا تستحق القراءة
notify_self_featured: 'التعليق من {0} هو الآن مميّز و موافق عليه'
notify_featured: '{0} ميّز و وافق على التعليق "{1}"'
notify_unfeatured: '{0} ألغي تميّز التعليق "{1}"'
feature_comment: ميّز التعليق؟
are_you_sure: هل أنت متأكد أنك تريد تميّز هذا التعليق؟
cancel: إلغاء
yes_feature_comment: نعم، ميّز التعليق؟
da:
talk-plugin-featured-comments:
un_feature: Un-Feature
@@ -1,3 +1,6 @@
ar:
talk-plugin-flag-details:
flags: تقارير
da:
talk-plugin-flag-details:
flags: Reports
@@ -1,3 +1,7 @@
ar:
talk-plugin-google-auth:
sign_in: "تسجيل الدخول عبر حساب جوجل"
sign_up: "اشترك عبر حساب جوجل"
en:
talk-plugin-google-auth:
sign_in: "Sign in with Google"
@@ -1,3 +1,15 @@
ar:
talk-plugin-ignore-user:
section_title: المستخدمون الذين تم تجاهلهم
section_info: لأنك تجاهلت المعلقين التاليين، يتم إخفاء تعليقاتهم.
stop_ignoring: إيقاف التجاهل
ignore_user: تجاهل المستخدم
cancel: إلغاء
confirmation: |
عند تجاهل المستخدم، سيتم إخفاء جميع التعليقات التي كتبها على الموقع منك. يمكنك التراجع عن هذا لاحقا من ملفي الشخصي.
notify_success: |
أنت الآن تتجاهل {0}. يمكنك التراجع عن هذا الإجراء من ملفي الشخصي.
confirmation_title: تجاهل {0}؟
da:
talk-plugin-ignore-user:
section_title: Ignored users
@@ -1,3 +1,7 @@
ar:
talk-plugin-like:
like: أعجبني
liked: إعجاب
da:
talk-plugin-like:
like: Like
@@ -1,3 +1,7 @@
ar:
talk-plugin-love:
love: Love
loved: Loved
da:
talk-plugin-love:
love: Love
@@ -1,3 +1,6 @@
ar:
talk-plugin-member-since:
member_since: "عضو منذ"
da:
talk-plugin-member-since:
member_since: "Member Since"
@@ -1,3 +1,15 @@
ar:
talk-plugin-moderation-actions:
reject_comment: "رفض"
approve_comment: "موافقة"
approved_comment: "موافق عليه"
moderation_actions: "إجراءات الإشراف"
ban_user: "حجب المستخدم"
ban_user_dialog_sub: "هل أنت متأكد أنك تريد حجب هذا المستخدم؟"
ban_user_dialog_copy: "ملاحظة: سيؤدي حجب هذا المستخدم أيضا إلى وضع هذا التعليق في قائمة الرفض."
ban_user_dialog_cancel: "إلغاء"
ban_user_dialog_yes: "نعم، احجب المستخدم"
ban_user_dialog_headline: "احجب المستخدم؟"
da:
talk-plugin-moderation-actions:
reject_comment: "Reject"
@@ -165,7 +165,6 @@ class NotificationManager {
);
const flattenedDigestCategories = this.flattenDigests(ctx, digests);
console.log(JSON.stringify(flattenedDigestCategories));
// Get all the notifications together.
const allMessages = await renderDigestMessage(
@@ -1,4 +1,8 @@
{
"ar": {
"off_topic": "خارج الموضوع",
"hide_off_topic": "إخفاء التعليقات التي خارجة عن الموضوع"
},
"da": {
"off_topic": "Off Topic",
"hide_off_topic": "Hide Off-Topic Comments"
@@ -1,3 +1,6 @@
ar:
talk-plugin-profile-settings:
tab: إعدادات
en:
talk-plugin-profile-settings:
tab: Settings
@@ -1,3 +1,7 @@
ar:
talk-plugin-respect:
respect: احترم
respected: احترام
da:
talk-plugin-respect:
respect: Respect
@@ -7,6 +7,6 @@
"author": "The Coral Project Team <coral@mozillafoundation.org>",
"license": "Apache-2.0",
"dependencies": {
"pell": "^0.7.0"
"pell": "^1.0.1"
}
}
@@ -10,7 +10,7 @@ if (process.env.NODE_ENV === 'test') {
module.exports = {
RootMutation: {
createComment: {
async post(_, _, context, info, result) {
async post(root, args, 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;
@@ -1,3 +1,6 @@
ar:
talk-plugin-sort-most-liked:
label: الأكثر إعجاباً أولاً
da:
talk-plugin-sort-most-liked:
label: Most liked first
@@ -1,3 +1,6 @@
ar:
talk-plugin-sort-most-loved:
label: الأكثر حبً أولاً
da:
talk-plugin-sort-most-loved:
label: Most loved first
@@ -1,3 +1,6 @@
ar:
talk-plugin-sort-most-replied:
label: الأكثر ردودً أولاً
da:
talk-plugin-sort-most-replied:
label: Most replied first
@@ -1,3 +1,6 @@
ar:
talk-plugin-sort-most-respected:
label: الأكثر احتراماً أولاً
da:
talk-plugin-sort-most-respected:
label: Most respected first
@@ -1,3 +1,6 @@
ar:
talk-plugin-sort-newest:
label: الأحدث أولاً
da:
talk-plugin-sort-newest:
label: Newest first
@@ -1,3 +1,6 @@
ar:
talk-plugin-sort-oldest:
label: الأقدم أولاً
da:
talk-plugin-sort-oldest:
label: Oldest first
@@ -1,3 +1,6 @@
ar:
talk-plugin-subscriber:
subscriber: "Subscriber"
da:
talk-plugin-subscriber:
subscriber: "Subscriber"
@@ -1,3 +1,19 @@
ar:
error:
COMMENT_IS_TOXIC: |
هل أنت واثق؟ قد تنتهك اللغة الواردة في هذا التعليق إرشادات المجموعة. يمكنك تعديل التعليق أو إرساله لمراجعته.
talk-plugin-toxic-comments:
unlikely: "من غير المرجح"
highly_likely: "من المرجح جدا"
possibly: "ربما"
likely: "مرجح"
toxic_comment: "تعليق سام"
still_toxic: |
قد يظل هذا التعليق المعدل منتهكاً لإرشادات المجموعة. سيراجع فريق الإشراف لدينا تعليقك قريبا.
flags:
reasons:
comment:
toxic_comment: "من المرجح جدا أن يكون ساماً"
da:
error:
COMMENT_IS_TOXIC: |
@@ -1,3 +1,8 @@
ar:
talk-plugin-viewing-options:
viewing_options: "خيارات العرض"
sort: فرز
filter: تصفية
da:
talk-plugin-viewing-options:
viewing_options: "Viewing Options"
+17 -12
View File
@@ -1,5 +1,4 @@
const app = require('./app');
const debug = require('debug')('talk:cli:serve');
const errors = require('./errors');
const { createServer } = require('http');
const jobs = require('./jobs');
@@ -12,6 +11,8 @@ const cache = require('./services/cache');
const util = require('./bin/util');
const { createSubscriptionManager } = require('./graph/subscriptions');
const { PORT } = require('./config');
const { createLogger } = require('./services/logging');
const logger = createLogger('jobs');
const port = normalizePort(PORT);
@@ -70,15 +71,17 @@ function normalizePort(val) {
*/
async function onListening() {
let addr = server.address();
let bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
console.log(`API Server Listening on ${bind}`);
logger.info({ port }, 'API server started');
}
/**
* Start the app.
*/
async function serve({ jobs: processJobs = false, websockets = false } = {}) {
async function serve({
jobs: enableJobs = false,
disabledJobs = [],
websockets = false,
} = {}) {
// Run the deferred plugins.
PluginsService.runDeferred();
@@ -91,13 +94,15 @@ async function serve({ jobs: processJobs = false, websockets = false } = {}) {
// just means we don't have to check that the migrations have run.
await SetupService.isAvailable();
debug('setup is currently available, migrations not being checked');
logger.info('Setup is currently available, migrations not being checked');
} catch (e) {
// Check the error.
switch (e) {
case errors.ErrInstallLock:
case errors.ErrSettingsInit:
debug('setup is not currently available, migrations now being checked');
logger.info(
'Setup is not currently available, migrations now being checked'
);
// The error was expected, just continue.
break;
@@ -115,7 +120,7 @@ async function serve({ jobs: processJobs = false, websockets = false } = {}) {
process.exit(1);
}
debug('migrations do not have to be run');
logger.info('Migrations do not have to be run');
}
/**
@@ -127,7 +132,7 @@ async function serve({ jobs: processJobs = false, websockets = false } = {}) {
server.listen(port, () => {
// Mount the websocket server if requested.
if (websockets) {
console.log(`Websocket Server Listening on ${port}`);
logger.info({ port }, 'Websocket server started');
// Mount the subscriptions server on the application server.
createSubscriptionManager(server);
@@ -135,16 +140,16 @@ async function serve({ jobs: processJobs = false, websockets = false } = {}) {
});
// Enable job processing on the thread if enabled.
if (processJobs) {
if (enableJobs) {
// Start the mail processor.
jobs.process();
jobs.process(...disabledJobs);
}
// Define a safe shutdown function to call in the event we need to shutdown
// because the node hooks are below which will interrupt the shutdown process.
// Shutdown the mongoose connection, the app server, and the scraper.
util.onshutdown([
() => (processJobs ? kue.Task.shutdown() : null),
() => (enableJobs ? kue.Task.shutdown() : null),
() => mongoose.disconnect(),
() => server.close(),
]);
+5 -5
View File
@@ -19,7 +19,7 @@ describe('services.TokensService', () => {
describe('#create', () => {
it('can create the token without error', async () => {
let token = await TokensService.create(user.id, 'Github Token');
let token = await TokensService.create(user.id, 'GitHub Token');
expect(token).to.be.an.object;
expect(token.jwt).to.be.a.string;
expect(token.pat).to.be.an.object;
@@ -35,7 +35,7 @@ describe('services.TokensService', () => {
describe('#revoke', () => {
it('can revoke a token', async () => {
let { pat: { id } } = await TokensService.create(user.id, 'Github Token');
let { pat: { id } } = await TokensService.create(user.id, 'GitHub Token');
let tokens = await TokensService.list(user.id);
expect(tokens).to.have.length(1);
@@ -54,7 +54,7 @@ describe('services.TokensService', () => {
describe('#validate', () => {
it('will allow a valid token', async () => {
// Create a token.
let { pat: { id } } = await TokensService.create(user.id, 'Github Token');
let { pat: { id } } = await TokensService.create(user.id, 'GitHub Token');
// Validate it.
await TokensService.validate(user.id, id);
@@ -62,7 +62,7 @@ describe('services.TokensService', () => {
it('will not allow an invalid token', async () => {
// Create a token.
let { pat: { id } } = await TokensService.create(user.id, 'Github Token');
let { pat: { id } } = await TokensService.create(user.id, 'GitHub Token');
// Revoke it.
await TokensService.revoke(user.id, id);
@@ -78,7 +78,7 @@ describe('services.TokensService', () => {
expect(tokens).to.have.length(0);
// Create a token.
let { pat: { id } } = await TokensService.create(user.id, 'Github Token');
let { pat: { id } } = await TokensService.create(user.id, 'GitHub Token');
tokens = await TokensService.list(user.id);
expect(tokens).to.have.length(1);
+3 -3
View File
@@ -8205,9 +8205,9 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
pell@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/pell/-/pell-0.7.0.tgz#46b3fcdfa8dd7e5999f73c550a337ecc80193dcc"
pell@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pell/-/pell-1.0.1.tgz#8f1e97165001024e5f371e0ce0b329457c847b5d"
pend@~1.2.0:
version "1.2.0"