mirror of
https://github.com/wassname/talk.git
synced 2026-09-13 13:10:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1098c068f2 | ||
|
|
f0c95d0044 | ||
|
|
d31969592c | ||
|
|
794487b1a9 | ||
|
|
9f059e0c4a | ||
|
|
7d369f2d35 | ||
|
|
5fb5240f89 | ||
|
|
976931135c | ||
|
|
1e3d47eb87 | ||
|
|
2d804d783b | ||
|
|
3404578ecc | ||
|
|
48c5355a5b | ||
|
|
e3a300c643 | ||
|
|
a6653152e4 | ||
|
|
441422542c | ||
|
|
1d1822c086 | ||
|
|
1c2ff7d2ae | ||
|
|
9319bb4e1f | ||
|
|
6611f8b636 | ||
|
|
738542ab99 | ||
|
|
cd4ae47958 | ||
|
|
2ec66007d9 | ||
|
|
ea34c936d0 | ||
|
|
ef1fd9c159 | ||
|
|
c7ccf03132 | ||
|
|
8015098305 | ||
|
|
c85a2703f9 | ||
|
|
9aec6b026c | ||
|
|
b2ca9dba40 | ||
|
|
9186c73fff | ||
|
|
86d55fa2de | ||
|
|
12c4cfbbf6 | ||
|
|
c875a8d9ed | ||
|
|
4d082165aa | ||
|
|
08c01981ac | ||
|
|
cd01aae76c | ||
|
|
53367eeaac | ||
|
|
32962aa1e8 | ||
|
|
049357a09c | ||
|
|
5e7fe7cf5a | ||
|
|
6243fcba91 | ||
|
|
805825e5d2 | ||
|
|
a40e721a68 | ||
|
|
1dd8d0a706 | ||
|
|
da1a8bb539 |
+5
-4
@@ -1,6 +1,7 @@
|
||||
# Mozilla Community Participation Guidelines
|
||||
# Vox Media Code of Conduct
|
||||
|
||||
## Version 2.3 - Updated May 16, 2017
|
||||
## Introduction
|
||||
|
||||
The heart of Mozilla is people. We put people first and do our best to recognize, appreciate and respect the diversity of our global contributors. The Mozilla Project welcomes contributions from everyone who shares our goals and wants to contribute in a healthy and constructive manner within our community. As such, we have adopted this code of conduct and require all those who participate to agree and adhere to these Community Participation Guidelines in order to help us create a safe and positive community experience for all.
|
||||
https://www.mozilla.org/en-US/about/governance/policies/participation/
|
||||
This code of conduct governs the environment of the Vox Product team. We created it not because we anticipate bad behavior, but because we believe that articulating our values and obligations to one another reinforces the already exceptional level of respect among the team and because having a code provides us with clear avenues to correct our culture should it ever stray from that course. We make this code public in the hopes of contributing to the ongoing conversation about inclusion in the tech, design, and media communities and encourage other teams to fork it and make it their own. To our team, we commit to enforce and evolve this code as our team grows.
|
||||
|
||||
Read the rest here: http://code-of-conduct.voxmedia.com/
|
||||
|
||||
+3
-23
@@ -18,7 +18,7 @@ There are at least three ways to contribute to Talk:
|
||||
|
||||
Conversation surrounding contributions begins in [issues](https://github.com/coralproject/talk/issues).
|
||||
|
||||
### When should I Create an Issue?
|
||||
### When should I create an issue?
|
||||
|
||||
File an issue as soon as you have an idea of something you'd like to contribute. We would love to hear what you're thinking and help refine the idea to make it into the Talk ecosystem.
|
||||
|
||||
@@ -30,9 +30,9 @@ Please file issues if:
|
||||
|
||||
### What should I include?
|
||||
|
||||
Coral has adopted an iterative, agile development philosophy. All contributions that make it into the Talk repository should start with a story or this form:
|
||||
Coral has adopted an iterative, agile development philosophy. All contributions that make it into the Talk repository should start with a user story in this form:
|
||||
|
||||
> As a [type of person] I'd like to be able to [do something] so that I can [get some result].
|
||||
> As a [type of Coral user] I'd like to [do something] so that I can [get some result/value].
|
||||
|
||||
This exercise does two things:
|
||||
|
||||
@@ -71,23 +71,3 @@ If you'd like to discuss a contribution, please [file an issue](https://github.c
|
||||
Talk's translations are stored in `.yml` files [here](https://github.com/coralproject/talk/tree/master/locales).
|
||||
|
||||
Translations can be submitted via pull request. If you do not use github, you can use 'en.yml' as a template and [email](https://coralproject.net/contact) the translations to us. We can import it into the repository.
|
||||
|
||||
## I want to contribute but I'm not sure what to do!
|
||||
|
||||
If you want to contribute but don't have a clear idea of exactly what that may be, here are some resources that may help:
|
||||
|
||||
### Product Roadmap
|
||||
|
||||
Please visit our product roadmap here: https://www.pivotaltracker.com/n/projects/1863625. If you'd like to take on any of our scheduled tasks we'd be forever grateful!
|
||||
|
||||
### Discussion Forum
|
||||
|
||||
If you'd like to discuss what we're up to, please visit or [community](https://community.coralproject.net/) or [contact us](https://coralproject.net/contact).
|
||||
|
||||
### Integrations
|
||||
|
||||
Have a favorite analytics engine? Data science service? CMS? Auth platform? Deployment platform or pipeline? Pet project? Consider building a plugin to integrate them!
|
||||
|
||||
### Favorite Features?
|
||||
|
||||
Do you have a favorite feature of an existing platform that's not yet been done in Talk? Sounds like Talk needs that feature.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Online comments are broken. Our open-source commenting platform, Talk, 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/talk).
|
||||
|
||||
Built with <3 by The Coral Project, a part of [Vox Media](https://www.voxmedia.com/a/go-deeper/about).
|
||||
Built with <3 by The Coral Project, a part of [Vox Media](https://www.voxmedia.com/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -140,8 +140,11 @@ const LOAD_MORE_QUERY = gql`
|
||||
$limit: Int
|
||||
$cursor: Cursor
|
||||
$value: String
|
||||
$state: UserStateInput
|
||||
) {
|
||||
users(query: { value: $value, limit: $limit, cursor: $cursor }) {
|
||||
users(
|
||||
query: { value: $value, limit: $limit, cursor: $cursor, state: $state }
|
||||
) {
|
||||
hasNextPage
|
||||
endCursor
|
||||
nodes {
|
||||
|
||||
@@ -14,6 +14,11 @@ class TechSettings extends React.Component {
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateCustomAdminCssUrl = event => {
|
||||
const updater = { customAdminCssUrl: { $set: event.target.value } };
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateDomainlist = (listName, list) => {
|
||||
this.props.updatePending({
|
||||
updater: {
|
||||
@@ -50,6 +55,14 @@ class TechSettings extends React.Component {
|
||||
onChange={this.updateCustomCssUrl}
|
||||
/>
|
||||
</ConfigureCard>
|
||||
<ConfigureCard title={t('configure.custom_admin_css_url')}>
|
||||
<p>{t('configure.custom_admin_css_url_desc')}</p>
|
||||
<input
|
||||
className={styles.customCSSInput}
|
||||
value={settings.customAdminCssUrl}
|
||||
onChange={this.updateCustomAdminCssUrl}
|
||||
/>
|
||||
</ConfigureCard>
|
||||
<Slot fill="adminTechSettings" passthrough={slotPassthrough} />
|
||||
</ConfigurePage>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,7 @@ export default compose(
|
||||
settings: gql`
|
||||
fragment TalkAdmin_TechSettings_settings on Settings {
|
||||
customCssUrl
|
||||
customAdminCssUrl
|
||||
domains {
|
||||
whitelist
|
||||
}
|
||||
|
||||
@@ -43,6 +43,32 @@ const CONFIG = {
|
||||
process.env.TALK_WHITELISTED_LANGUAGES &&
|
||||
process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim()),
|
||||
|
||||
// USERNAME_CAST_REGEXP defiles the regex expression that will be used to
|
||||
// strip characters from a username during a username cast operation.
|
||||
USERNAME_CAST_REGEXP: new RegExp(
|
||||
process.env.USERNAME_CAST_REGEXP || '[^a-zA-Z_]',
|
||||
'g'
|
||||
),
|
||||
|
||||
// USERNAME_REPLACEMENT_CAST_REGEXP defiles the regex expression that will be
|
||||
// used to replace characters with the replacement character during a username
|
||||
// cast operation. First duplicates will be replaced, then
|
||||
USERNAME_REPLACEMENT_CAST_REGEXP: new RegExp(
|
||||
process.env.USERNAME_REPLACEMENT_CAST_REGEXP || ' +',
|
||||
'g'
|
||||
),
|
||||
|
||||
// USERNAME_REPLACEMENT_CHARACTER is the character used to replace other
|
||||
// characters matching the USERNAME_REPLACEMENT_CAST_REGEXP.
|
||||
USERNAME_REPLACEMENT_CHARACTER:
|
||||
process.env.USERNAME_REPLACEMENT_CHARACTER || '_',
|
||||
|
||||
// USERNAME_VALIDATION_REGEX defines the allowed characters for a username in
|
||||
// Talk.
|
||||
USERNAME_VALIDATION_REGEX: new RegExp(
|
||||
process.env.USERNAME_VALIDATION_REGEX || '^[A-Za-z0-9_]+$'
|
||||
),
|
||||
|
||||
// When TRUE, it ensures that database indexes created in core will not add
|
||||
// indexes.
|
||||
CREATE_MONGO_INDEXES: process.env.DISABLE_CREATE_MONGO_INDEXES !== 'TRUE',
|
||||
|
||||
+1
-3
@@ -76,9 +76,7 @@ sidebar:
|
||||
- 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
|
||||
url: https://hub.docker.com/r/coralproject/talk/
|
||||
side:
|
||||
- title: Installation
|
||||
children:
|
||||
|
||||
@@ -5,9 +5,18 @@ permalink: /
|
||||
|
||||
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 our product features and
|
||||
goals [here](https://coralproject.net/talk). The
|
||||
documentation available here is pertaining to the technical details for
|
||||
smarter discussions around your work.
|
||||
|
||||
More than 60 newsrooms use Talk to run their on-site communities, including the Washington Post, the Wall Street Journal, and IGN.
|
||||
|
||||
[Read more about our product features and
|
||||
goals here](https://coralproject.net/talk).
|
||||
|
||||
<div class="callout">
|
||||
We offer hosting and support packages for Talk. [Contact us for more information.](https://coralproject.net/pricing/)
|
||||
</div>
|
||||
|
||||
The documentation available here is pertaining to the technical details for
|
||||
installing, configuring, and deploying Talk.
|
||||
|
||||
Talk is a [Node](https://nodejs.org/) application with
|
||||
@@ -46,7 +55,7 @@ Start by making a new directory and create a file called `docker-compose.yml` an
|
||||
version: '2'
|
||||
services:
|
||||
talk:
|
||||
image: coralproject/talk:4.5
|
||||
image: coralproject/talk:4
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
@@ -43,7 +43,7 @@ be used to setup Talk:
|
||||
version: '2'
|
||||
services:
|
||||
talk:
|
||||
image: coralproject/talk:4.5
|
||||
image: coralproject/talk:4
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
@@ -121,7 +121,7 @@ base installation with additional custom plugins. Images can be created with the
|
||||
most basic of `Dockerfile`'s:
|
||||
|
||||
```docker
|
||||
FROM coralproject/talk:4.5-onbuild
|
||||
FROM coralproject/talk:4-onbuild
|
||||
```
|
||||
|
||||
And running the following to build the docker image:
|
||||
@@ -153,7 +153,7 @@ your containerized infrastructure. The versioning of our Docker tags as well
|
||||
lets you do something like:
|
||||
|
||||
```docker
|
||||
FROM coralproject/talk:4.5-onbuild
|
||||
FROM coralproject/talk:4-onbuild
|
||||
```
|
||||
|
||||
Which would pin your image to `4.5.x release's.
|
||||
Which would pin your image to `4.x.x release's.
|
||||
|
||||
@@ -13,6 +13,9 @@ permalink: /pre-launch-checklist/
|
||||
- [ ] Do you have a domain name for Talk?
|
||||
- **Recommended**: You should host Talk on a subdomain on your main site (e.g. if your site is mysitefornews.org, you should serve Talk from a subdomain like talk.mysitefornews.org) to avoid issues with third-party cookie sharing.
|
||||
|
||||
|
||||
- [ ] Do you have a Content Delivery Network (CDN) to improve performance and reduce load on your Talk servers?
|
||||
|
||||
|
||||
- [ ] Do you have a MongoDB instance?
|
||||
- A MongoDB [Docker instance](/talk/installation-from-docker/) hosted alongside Talk?
|
||||
@@ -28,9 +31,11 @@ permalink: /pre-launch-checklist/
|
||||
- [ ] Do you want to provide single sign-on (SSO) by integrating with an external auth system?
|
||||
- See [Authenticating with Talk](/talk/integrating/authentication/)
|
||||
|
||||
|
||||
- [ ] Do you want to integrate Talk with your CMS to automate embedding Talk Comment Stream into your site?
|
||||
- See [CMS Integration](/talk/integrating/cms-integration/)
|
||||
|
||||
|
||||
- [ ] Do you want to use Social sign-on?
|
||||
- Facebook
|
||||
- Install [talk-plugin-facebook-auth](/talk/plugin/talk-plugin-facebook-auth/)
|
||||
|
||||
@@ -416,13 +416,19 @@ cannot. You would use this option in the latter situation.
|
||||
|
||||
## TALK_SMTP_FROM_ADDRESS
|
||||
|
||||
The email address to send emails from using the SMTP provider in the format:
|
||||
The email address to send emails from using the SMTP provider. You can include the name and email address or only provide the email.
|
||||
|
||||
```plain
|
||||
TALK_SMTP_FROM_ADDRESS="The Coral Project" <support@coralproject.net>
|
||||
TALK_SMTP_FROM_ADDRESS="The Coral Project" support@coralproject.net
|
||||
|
||||
```
|
||||
or
|
||||
|
||||
```plain
|
||||
TALK_SMTP_FROM_ADDRESS=support@coralproject.net
|
||||
|
||||
```
|
||||
|
||||
Including the name and email address.
|
||||
|
||||
## TALK_SMTP_HOST
|
||||
|
||||
@@ -499,8 +505,9 @@ Could be read as:
|
||||
pre-moderated once in order to post freely again. If they instead get rejected
|
||||
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.
|
||||
- At the moment of writing, behavior is not attached to reliable commenters
|
||||
as well as the flagging reliability and unreliability. Regardless,
|
||||
all of these scores are recorded for future implementation.
|
||||
|
||||
## TALK_DISABLE_IGNORE_FLAGS_AGAINST_STAFF
|
||||
|
||||
|
||||
@@ -88,8 +88,9 @@ Comments with suspect words will display until a moderator takes action on them.
|
||||
|
||||
#### Permitted Domains
|
||||
|
||||
A list of domains where your Talk instance is allowed to be embedded. Typical
|
||||
use is `localhost`, `staging.yourdomain.com`, `yourdomain.com`, etc.
|
||||
A list of domains where your Talk instance is allowed to be embedded. List each domain and subdomain in use
|
||||
separately, for example: `localhost`, `staging.yourdomain.com`, `yourdomain.com`, `www.yourdomain.com`, etc.
|
||||
|
||||
|
||||
#### Embed Script
|
||||
|
||||
|
||||
@@ -47,3 +47,9 @@ Source: [talk-recipes/tree/master/plugins/author-name](https://github.com/coralp
|
||||
Enables the ability to hover over a commenter’s name and add plugin
|
||||
functionality there. The Member Since plugin that is provided in this recipe is
|
||||
an example of this.
|
||||
|
||||
## recipe-custom-auth
|
||||
|
||||
Source: [talk-recipes/tree/master/plugins/custom-auth](https://github.com/coralproject/talk-recipes/tree/master/plugins/custom-auth)
|
||||
|
||||
Example code for creating, and updating Users in Talk from JWT data provided by your custom SSO authentication service.
|
||||
|
||||
@@ -33,24 +33,24 @@ Our team is small, so it's difficult for us to provide support packages. However
|
||||
|
||||
## Is there a hosted version I can purchase by monthly subscription?
|
||||
|
||||
Yes! We are happy to announce that as of July 2018, we provide a SaaS version of Talk, called the Coral Cloud. For a monthly subscription, you get your own hosted Talk instance to embed on your news and blog articles. [Reach out to us](mailto:support@coralproject.net) if you're interested in this option.
|
||||
Yes! [Visit our hosting page](https://coralproject.net/pricing/) to submit information and receive a quote.
|
||||
|
||||
## Where is our data when we use Talk?
|
||||
|
||||
If you are hosting Talk on your own:
|
||||
|
||||
* Your data is stored in a MongoDB database that you provide
|
||||
* The Coral Team doesn’t have any access to your data
|
||||
* The Coral team doesn’t have any access to your data
|
||||
|
||||
If you are using Coral Cloud Hosting for Talk:
|
||||
If you are using our Hosted SaaS version of Talk:
|
||||
|
||||
* Your data is stored in a dedicated MongDB database that is provisioned for your in the Cloud
|
||||
* Your data is completely isolated from other customer’s data
|
||||
* The Coral Team and its third party database hosting providers use strict access controls and auditing to protected your data from unauthorized access by team members
|
||||
* Your data is stored in a dedicated MongoDB database that is provisioned for you
|
||||
* Your data is completely isolated from other customers' data
|
||||
* The Coral Team and its third-party database hosting providers use strict access controls and auditing to protected your data from unauthorized access
|
||||
|
||||
## Does Talk have any automated moderation features to protect against spam and trolling?
|
||||
|
||||
Talk features a couple of plugins that provide advanced moderation:
|
||||
As well as basic word/phrase filtering and user reputation scores, Talk offers optional advanced features via third-party services:
|
||||
|
||||
* The [Toxic Comments plugin](/talk/plugin/talk-plugin-toxic-comments) integrates with the [Perspective API from Google](https://www.perspectiveapi.com/) to detect the likelihood of toxicity of comments in real-time
|
||||
* The [Akismet plugin](/talk/plugin/talk-plugin-akismet) detects and blocks spam comments
|
||||
@@ -64,4 +64,4 @@ Talk features a couple of plugins that provide advanced moderation:
|
||||
|
||||
* The Talk software is freely available under the Apache 2.0 open source license
|
||||
* Associated costs are those for the infrastructure required to run Talk (i.e. cloud hosting fee or bare-metal server costs)
|
||||
* The Coral Project offers a SaaS version of Talk. Please [get in touch with us](mailto:support@coralproject.net) to discuss pricing for your custom requirements.
|
||||
* The Coral Project offers a SaaS/hosted version of Talk. Please [get in touch with us](https://coralproject.net/pricing/) to discuss pricing for your requirements.
|
||||
|
||||
+245
-168
@@ -56,16 +56,32 @@ Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of
|
||||
definitions. `enum`'s will be appended to, types will be appended, and new types
|
||||
will be added.
|
||||
|
||||
This can be included as a plugin via:
|
||||
|
||||
```js
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
typeDefs: fs.readFileSync(path.join(__dirname, "typeDefs.graphql"), "utf8")
|
||||
};
|
||||
```
|
||||
|
||||
Assuming the above GraphQL schema is located in the plugin folder at
|
||||
`typeDefs.graphql`.
|
||||
|
||||
### context
|
||||
|
||||
```js
|
||||
{
|
||||
Slack: (context) => ({
|
||||
notify: (message) => {
|
||||
// return a promise after we're done sending notifications.
|
||||
}
|
||||
})
|
||||
}
|
||||
module.exports = {
|
||||
context: {
|
||||
Slack: context => ({
|
||||
notify: message => {
|
||||
// return a promise after we're done sending notifications.
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Any property provided here will be added to the context parameter available
|
||||
@@ -83,41 +99,50 @@ A special context hook, `Sort` will allow plugin authors to provide new
|
||||
methods to sort data. An example is as follows:
|
||||
|
||||
```js
|
||||
{
|
||||
Sort: () => ({
|
||||
Comments: { // <-- (1)
|
||||
likes: { // <-- (2)
|
||||
startCursor(ctx, nodes, {cursor}) { // <-- (3)
|
||||
return cursor != null ? cursor : 0;
|
||||
},
|
||||
endCursor(ctx, nodes, {cursor}) { // <-- (4)
|
||||
return nodes.length ? (cursor != null ? cursor : 0) + nodes.length : null;
|
||||
},
|
||||
sort(ctx, query, {cursor, sort}) { // <-- (5)
|
||||
if (cursor) {
|
||||
query = query.skip(cursor);
|
||||
}
|
||||
module.exports = {
|
||||
context: {
|
||||
Sort: () => ({
|
||||
Comments: {
|
||||
// <-- (1)
|
||||
likes: {
|
||||
// <-- (2)
|
||||
startCursor(ctx, nodes, { cursor }) {
|
||||
// <-- (3)
|
||||
return cursor != null ? cursor : 0;
|
||||
},
|
||||
endCursor(ctx, nodes, { cursor }) {
|
||||
// <-- (4)
|
||||
return nodes.length
|
||||
? (cursor != null ? cursor : 0) + nodes.length
|
||||
: null;
|
||||
},
|
||||
sort(ctx, query, { cursor, sort }) {
|
||||
// <-- (5)
|
||||
if (cursor) {
|
||||
query = query.skip(cursor);
|
||||
}
|
||||
|
||||
return query.sort({
|
||||
'action_counts.like': sort === 'DESC' ? -1 : 1,
|
||||
created_at: sort === 'DESC' ? -1 : 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
return query.sort({
|
||||
"action_counts.like": sort === "DESC" ? -1 : 1,
|
||||
created_at: sort === "DESC" ? -1 : 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
This has a bunch of special features:
|
||||
|
||||
1. `Comments` is the name of the type being sorted, this is pluralized and
|
||||
capitalized.
|
||||
capitalized.
|
||||
2. `likes` is the `sortBy` field in lowercase.
|
||||
3. `startCursor` will retrieve the start cursor based on the current set of
|
||||
nodes and the current cursor.
|
||||
nodes and the current cursor.
|
||||
4. `endCursor` will retrieve the end cursor based on the current set of nodes
|
||||
and the current cursor.
|
||||
and the current cursor.
|
||||
5. `sort` will mutate the `query` to apply the sort operations.
|
||||
|
||||
All the `startCursor`, `endCursor`, and `sort` functions must be provided in
|
||||
@@ -126,11 +151,13 @@ order for the sorting to apply properly.
|
||||
### loaders
|
||||
|
||||
```js
|
||||
(context) => ({
|
||||
People: {
|
||||
load: () => db.people.find({user: context.user})
|
||||
}
|
||||
})
|
||||
module.exports = {
|
||||
context: context => ({
|
||||
People: {
|
||||
load: () => db.people.find({ user: context.user })
|
||||
}
|
||||
})
|
||||
};
|
||||
```
|
||||
|
||||
Loaders should be provided as a function which returns a map which is used in
|
||||
@@ -139,13 +166,15 @@ the resolvers function. These must return a promise or a value.
|
||||
### mutators
|
||||
|
||||
```js
|
||||
(context) => ({
|
||||
People: {
|
||||
create: (name) => {
|
||||
return db.people.insert({user: context.user, name});
|
||||
module.exports = {
|
||||
context: context => ({
|
||||
People: {
|
||||
create: name => {
|
||||
return db.people.insert({ user: context.user, name });
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
```
|
||||
|
||||
Mutators should be provided as a function which returns a map which is used in
|
||||
@@ -154,27 +183,41 @@ the resolvers function. These must return a promise or a value.
|
||||
### resolvers
|
||||
|
||||
```js
|
||||
{
|
||||
Person: {
|
||||
name(obj, args, context) {
|
||||
return obj.name;
|
||||
module.exports = {
|
||||
resolvers: {
|
||||
Person: {
|
||||
name(obj, args, context) {
|
||||
return obj.name;
|
||||
},
|
||||
colour(obj, args, context) {
|
||||
// Bill likes the colour red, everyone else likes blue.
|
||||
return obj.name === "bill" ? "RED" : "BLUE";
|
||||
}
|
||||
},
|
||||
colour(obj, args, context) {
|
||||
// Bill likes the colour red, everyone else likes blue.
|
||||
return obj.name === 'bill' ? 'RED' : 'BLUE';
|
||||
}
|
||||
},
|
||||
RootQuery: {
|
||||
people(obj, args, {loaders: {People}}) {
|
||||
return People.load();
|
||||
}
|
||||
},
|
||||
RootMutation: {
|
||||
createPerson(obj, {name}, {mutators: {People}}) {
|
||||
return People.create(name);
|
||||
RootQuery: {
|
||||
people(
|
||||
obj,
|
||||
args,
|
||||
{
|
||||
loaders: { People }
|
||||
}
|
||||
) {
|
||||
return People.load();
|
||||
}
|
||||
},
|
||||
RootMutation: {
|
||||
createPerson(
|
||||
obj,
|
||||
{ name },
|
||||
{
|
||||
mutators: { People }
|
||||
}
|
||||
) {
|
||||
return People.create(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Should return a resolver map as described in the
|
||||
@@ -185,40 +228,44 @@ This will merge with the existing resolvers in core and from previous plugins.
|
||||
### hooks
|
||||
|
||||
```js
|
||||
{
|
||||
RootMutation: {
|
||||
createPerson: {
|
||||
post: async (obj, args, {plugins: {Slack}}, info, person) {
|
||||
if (!person) {
|
||||
module.exports = {
|
||||
hooks: {
|
||||
RootMutation: {
|
||||
createPerson: {
|
||||
post: async (obj, args, {plugins: {Slack}}, info, person) {
|
||||
if (!person) {
|
||||
return person;
|
||||
}
|
||||
|
||||
await Slack.notify(`A new person just was created with name ${person.name}`);
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
await Slack.notify(`A new person just was created with name ${person.name}`);
|
||||
|
||||
return person;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Hooks here are pretty special, for each resolver field, you can specify a
|
||||
pre/post hook that will execute pre and post field resolution.
|
||||
|
||||
If your post function accepts four parameters, then it can modify the field
|
||||
result. It is *required* that the function resolves a promise (or returns) with
|
||||
result. It is _required_ that the function resolves a promise (or returns) with
|
||||
the modified value or simply the original if you didn't modify it.
|
||||
|
||||
### setupFunctions
|
||||
|
||||
```js
|
||||
setupFunctions: {
|
||||
leader: (options, args) => ({
|
||||
leader: {
|
||||
filter: (person) => person.place === 1
|
||||
},
|
||||
}),
|
||||
}
|
||||
module.exports = {
|
||||
setupFunctions: {
|
||||
leader: (options, args) => ({
|
||||
leader: {
|
||||
filter: person => person.place === 1
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Setup functions allow you to create filters that control which pubsub.publish() events
|
||||
@@ -229,28 +276,32 @@ For more information, see the [Apollo Docs](https://github.com/apollographql/gra
|
||||
### tokenUserNotFound
|
||||
|
||||
```js
|
||||
tokenUserNotFound: async ({jwt, token}) => {
|
||||
let profile = await someExternalService(token);
|
||||
if (!profile) {
|
||||
return null;
|
||||
const Users = require("services/users");
|
||||
|
||||
module.exports = {
|
||||
tokenUserNotFound: async ({ jwt }) => {
|
||||
const user = await Users.upsertExternalUser(
|
||||
null,
|
||||
jwt.sub,
|
||||
jwt.iss,
|
||||
jwt.username
|
||||
);
|
||||
|
||||
const email = jwt.email.toLowerCase();
|
||||
|
||||
//upsertExternalUser will also create a profile with provider:jwt.iss, id:jwt.sub
|
||||
//add a "local" profile to persist email on the user
|
||||
user.profiles.push({
|
||||
provider: "local",
|
||||
id: email
|
||||
});
|
||||
user.created_at = new Date(jwt.memberSince * 1000);
|
||||
|
||||
await user.save();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
let user = await UserModel.findOneAndUpdate({
|
||||
id: profile.id
|
||||
}, {
|
||||
id: profile.id,
|
||||
username: profile.username,
|
||||
lowercaseUsername: profile.username.toLowerCase(),
|
||||
roles: [],
|
||||
profiles: []
|
||||
}, {
|
||||
setDefaultsOnInsert: true,
|
||||
new: true,
|
||||
upsert: true
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `tokenUserNotFound` hook allows auth integrations to hook into the event
|
||||
@@ -258,8 +309,15 @@ when a valid token is provided but a user can't be found in the database that
|
||||
matches the provided id.
|
||||
|
||||
The function is async, and should return the user object that was created in the
|
||||
database, or null if the user wasn't found. The `jwt` parameter of the object
|
||||
is the unpacked token, while `token` is the original jwt token string.
|
||||
database. The `jwt` parameter of the object is the unpacked token, which has already been validated.
|
||||
|
||||
More details about the `upsertExternalUser` method can be found at:
|
||||
https://github.com/coralproject/talk/blob/32962aa1e84c09d87141440a1f04cbd1659b3336/services/users.js/#L592
|
||||
|
||||
In this example, a unix timestamp was included on the jwt with a date that is being persisted on the user. This is optional, but can be used to handle any extra claims you want to include on the jwt. You can use the metadata object on the user to store custom values.
|
||||
|
||||
Having trouble implementing your own `tokenUserNotFound` hook?
|
||||
Submit a Support ticket ([support@coralproject.net](mailto:support@coralproject.net))
|
||||
|
||||
### tags
|
||||
|
||||
@@ -268,18 +326,20 @@ or enabled by code). Below is an example pulled from the core off topic plugin
|
||||
on how to create a hook for the `OFF_TOPIC` name:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
name: 'OFF_TOPIC',
|
||||
permissions: {
|
||||
public: true,
|
||||
self: true,
|
||||
roles: []
|
||||
},
|
||||
models: ['COMMENTS'],
|
||||
created_at: new Date()
|
||||
}
|
||||
]
|
||||
module.exports = {
|
||||
tags: [
|
||||
{
|
||||
name: "OFF_TOPIC",
|
||||
permissions: {
|
||||
public: true,
|
||||
self: true,
|
||||
roles: []
|
||||
},
|
||||
models: ["COMMENTS"],
|
||||
created_at: new Date()
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
You can refer to `models/schema/tag.js` for the available schema to match when
|
||||
@@ -288,11 +348,13 @@ creating models to enable/disable specific features.
|
||||
### router
|
||||
|
||||
```js
|
||||
(router) => {
|
||||
router.get('/api/v1/people', (req, res) => {
|
||||
res.json({people: [{name: 'Bob'}]});
|
||||
});
|
||||
}
|
||||
module.exports = {
|
||||
router: router => {
|
||||
router.get("/api/v1/people", (req, res) => {
|
||||
res.json({ people: [{ name: "Bob" }] });
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The Router hook allows you to create a function that accepts the base express
|
||||
@@ -302,56 +364,73 @@ action needed by external applications.
|
||||
### passport
|
||||
|
||||
```js
|
||||
const FacebookStrategy = require('passport-facebook').Strategy;
|
||||
const UsersService = require('services/users');
|
||||
const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport');
|
||||
const FacebookStrategy = require("passport-facebook").Strategy;
|
||||
const UsersService = require("services/users");
|
||||
const {
|
||||
ValidateUserLogin,
|
||||
HandleAuthPopupCallback
|
||||
} = require("services/passport");
|
||||
|
||||
module.exports = {
|
||||
passport(passport) {
|
||||
passport.use(new FacebookStrategy({
|
||||
clientID: process.env.TALK_FACEBOOK_APP_ID,
|
||||
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
|
||||
callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`,
|
||||
passReqToCallback: true,
|
||||
profileFields: ['id', 'displayName', 'picture.type(large)']
|
||||
}, async (req, accessToken, refreshToken, profile, done) => {
|
||||
passport: passport => {
|
||||
passport.use(
|
||||
new FacebookStrategy(
|
||||
{
|
||||
clientID: process.env.TALK_FACEBOOK_APP_ID,
|
||||
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
|
||||
callbackURL: `${
|
||||
process.env.TALK_ROOT_URL
|
||||
}/api/v1/auth/facebook/callback`,
|
||||
passReqToCallback: true,
|
||||
profileFields: ["id", "displayName", "picture.type(large)"]
|
||||
},
|
||||
async (req, accessToken, refreshToken, profile, done) => {
|
||||
let user;
|
||||
try {
|
||||
const { id, provider, displayName } = profile;
|
||||
user = await UsersService.upsertSocialUser(
|
||||
req.context,
|
||||
id,
|
||||
provider,
|
||||
displayName
|
||||
);
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
let user;
|
||||
try {
|
||||
const { id, provider, displayName } = profile;
|
||||
user = await UsersService.upsertSocialUser(
|
||||
req.context,
|
||||
id,
|
||||
provider,
|
||||
displayName
|
||||
);
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
return ValidateUserLogin(profile, user, done);
|
||||
}));
|
||||
return ValidateUserLogin(profile, user, done);
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
router(router) {
|
||||
|
||||
router: router => {
|
||||
// Note that we have to import the passport instance here, it is
|
||||
// instantiated after all the strategies have been mounted.
|
||||
const {passport} = require('services/passport');
|
||||
const { passport } = require("services/passport");
|
||||
|
||||
/**
|
||||
* Facebook auth endpoint, this will redirect the user immediately to facebook
|
||||
* for authorization.
|
||||
*/
|
||||
router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
|
||||
router.get(
|
||||
"/facebook",
|
||||
passport.authenticate("facebook", {
|
||||
display: "popup",
|
||||
authType: "rerequest",
|
||||
scope: ["public_profile"]
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Facebook callback endpoint, this will send the user a html page designed to
|
||||
* send back the user credentials upon successful login.
|
||||
*/
|
||||
router.get('/facebook/callback', (req, res, next) => {
|
||||
|
||||
router.get("/facebook/callback", (req, res, next) => {
|
||||
// Perform the facebook login flow and pass the data back through the opener.
|
||||
passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next);
|
||||
passport.authenticate(
|
||||
"facebook",
|
||||
HandleAuthPopupCallback(req, res, next)
|
||||
)(req, res, next);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -360,10 +439,10 @@ module.exports = {
|
||||
### translations
|
||||
|
||||
```js
|
||||
const path = require('path');
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
translations: path.join(__dirname, 'translations.yml'),
|
||||
translations: path.join(__dirname, "translations.yml")
|
||||
};
|
||||
```
|
||||
|
||||
@@ -377,11 +456,11 @@ en:
|
||||
|
||||
Which overrides the copy for the `embedlink.copy` template. You can
|
||||
also provide other languages as well by using the correct language
|
||||
prefix.
|
||||
prefix.
|
||||
|
||||
When creating a plugin using this `translations` hook to override copy
|
||||
When creating a plugin using this `translations` hook to override copy
|
||||
from another plugin, be sure to list it after the plugin it's overriding
|
||||
in the `plugins.json` file.
|
||||
in the `plugins.json` file.
|
||||
|
||||
### websockets
|
||||
|
||||
@@ -392,12 +471,12 @@ module.exports = {
|
||||
// Do something with the connection params or connection, like
|
||||
// logging it out, or incrementing a metric.
|
||||
},
|
||||
onDisconnect: (connection) => {
|
||||
onDisconnect: connection => {
|
||||
// Do something with the connection params or connection, like
|
||||
// logging it out, or decrementing a metric.
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
This `websockets` hook can be used to attach methods to the
|
||||
@@ -411,12 +490,13 @@ connections.
|
||||
module.exports = {
|
||||
schemaLevelResolveFunction: (root, args, ctx, info) => {
|
||||
// The GraphQL Operation Name. Example: CoralEmbedStream_Embed
|
||||
const name = info.operation.name !== null ? info.operation.name.value : null;
|
||||
const name =
|
||||
info.operation.name !== null ? info.operation.name.value : null;
|
||||
// Maybe increment a metric based on the operation name...
|
||||
|
||||
// You must _always_ return the root.
|
||||
return root;
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -431,9 +511,7 @@ Contents of `plugins.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": [
|
||||
"people"
|
||||
]
|
||||
"server": ["people"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -527,5 +605,4 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -3,25 +3,18 @@ title: Authenticating with Talk
|
||||
permalink: /integrating/authentication/
|
||||
---
|
||||
|
||||
Out of the box Talk supports account registration with username and password, as well as features like forgot password.
|
||||
### Authentication Strategies
|
||||
|
||||
You can also integrate Talk with any external authentication service that will enable
|
||||
seamless single sign-on (SSO) for users within your organization. There are a few
|
||||
methods of doing so:
|
||||
Talk comes with built in user management features like account registration with username/email and password, as well as features like resetting a forgotten password.
|
||||
|
||||
1. Passport Middleware
|
||||
2. Custom Token Integration
|
||||
There are four strategies to choose from when it comes to user authentication:
|
||||
|
||||
Both methods work, but there are product decisions that will affect the overall
|
||||
choice.
|
||||
#### Talk Email Authentication
|
||||
Best when you want Talk to manage all user registration, creation and authentication. Requires the least amount of setup and configuration. Users register by creating a Username (must be unique across users, no spaces or special chars), Email, and Password and are stored in Talk’s database. Optionally - you can require an email verification step at time of account creation. *Plugins required: [talk-plugin-local-auth](/talk/plugin/talk-plugin-local-auth), [talk-plugin-auth](/talk/plugin/talk-plugin-auth)*
|
||||
|
||||
## Passport Middleware
|
||||
#### Social Sign-in (FaceBook or Google via Passport Middleware)
|
||||
|
||||
You would choose the **Passport Middleware** route when you are OK using an auth
|
||||
that is triggered from inside Talk that is not connected to an external auth
|
||||
state (you don't use the auth anywhere else now).
|
||||
|
||||
Plugins are available for the following 3rd party authentication providers:
|
||||
Allows users to use existing social media account to sign in to Talk. Requires you to setup API keys with social sign on provider, and configure your API keys in Talk. Users register and login via a *“Sign in with ….”*, but are not required to set user details such as password or email address. *Plugins required: [talk-plugin-auth](/talk/plugin/talk-plugin-auth), plus at least one of the following:*
|
||||
|
||||
* [Facebook](/talk/plugin/talk-plugin-facebook-auth/)
|
||||
* [Google](/talk/plugin/talk-plugin-google-auth/)
|
||||
@@ -29,15 +22,20 @@ Plugins are available for the following 3rd party authentication providers:
|
||||
_FAQ: Can I create a Twitter auth plugin?_
|
||||
This is currently not possible because Talk uses passport.js which does not support Twitter's oAuth2 requirements.
|
||||
|
||||
## Custom Token Integration
|
||||
#### Social Sign-in with Email Association
|
||||
This strategy is actually a combination of the first two. Talk’s built in Email Authentication is augmented by also allowing registration with a social account. Users with social accounts are prompted to associate an email address with their social profile to enable notifications. *Plugins required: [talk-plugin-auth](/talk/plugin/talk-plugin-auth), [talk-plugin-local-auth](/talk/plugin/talk-plugin-local-auth), [talk-plugin-facebook-auth](/talk/plugin/talk-plugin-facebook-auth/), and or [talk-plugin-google-auth](/talk/plugin/talk-plugin-google-auth/)*
|
||||
|
||||
#### Custom SSO Token Integration
|
||||
When you want users to authenticate via your existing identity system, and be automatically registered/logged into Talk. Users register, login, and manage their user details on your existing site, your user database maintains the source of truth for all user information. Users are created and updated in Talk via jwt tokens passed from your auth service to Talk. *Required Plugins: requires custom plugin development, as well as disabling talk-plugin-auth, and talk-plugin-local-auth*
|
||||
|
||||
## Setting Up Custom SSO Token Integration
|
||||
|
||||
You can integrate Talk with any authentication service to enable single sign-on
|
||||
for users. The steps to do that are:
|
||||
|
||||
1. Create a service that generates [JWT tokens](https://jwt.io/introduction/).
|
||||
2. Push the token into the embed.
|
||||
3. Implement the [`tokenUserNotFound`](#implement-tokenusernotfound) hook to
|
||||
process the token.
|
||||
2. Include the JWT token in your Talk embed script
|
||||
3. Create a custom plugin to generate and authenticate Talk users from the jwt token
|
||||
|
||||
### Create JWT Token
|
||||
|
||||
@@ -67,7 +65,7 @@ The generated JWT must contain the following claims:
|
||||
### Generate a key to sign the JWT
|
||||
Optionally you can use https://github.com/coralproject/coralcert to generate a key with which to sign the JWTs and specify the secret as an environment variable.
|
||||
|
||||
### Push token into embed
|
||||
### Include token in embed
|
||||
|
||||
We're assuming that your CMS is capable of authenticating a user account, or
|
||||
at least having the user's details available to send off to the token creation
|
||||
@@ -83,15 +81,17 @@ Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
|
||||
});
|
||||
```
|
||||
|
||||
Which will pass down the token to Talk and will fire the next steps
|
||||
Which will pass down the token to Talk and will fire the
|
||||
`tokenUserNotFound` hook to complete the auth flow.
|
||||
|
||||
### Implement `tokenUserNotFound`
|
||||
### Create a Custom Plugin to Generate Talk Users
|
||||
|
||||
This is the only piece of code you'll have to write that lives inside Talk.
|
||||
The role of this code is to live as a plugin and provide Talk with a way of
|
||||
taking the token that you gave it, and turning into a user.
|
||||
|
||||
Your custom plugin will need to include a `tokenUserNotFound` function that will handle creating a user in Talk from the claims provided on the JWT, refer to [`tokenUserNotFound`](/talk/api/server/#tokenusernotfound) for reference.
|
||||
|
||||
Using the example application we were working with in the JWT issuing step
|
||||
above, we'll need to ensure that the configuration is consistent in-between both
|
||||
Talk and the JWT issuer. Namely, the following environment variables from our
|
||||
@@ -108,5 +108,4 @@ example issuer and Talk must match:
|
||||
reference, the basic takeaway is that the secret used to sign the tokens issued
|
||||
by the issuer must be able to be verified by Talk.
|
||||
|
||||
For an example of implementing the plugin, refer to [`tokenUserNotFound`](/talk/api/server/#tokenusernotfound)
|
||||
reference.
|
||||
For an example of implementing the plugin, check out our [SSO Custom Auth Recipe](https://github.com/coralproject/talk-recipes/tree/master/plugins/custom-auth)
|
||||
|
||||
@@ -3,9 +3,19 @@ title: Tracking Talk Events and Metrics
|
||||
permalink: /integrating/event-tracking-metrics/
|
||||
---
|
||||
|
||||
Talk supports event emitting via Redux, Apollo and GraphQL. This means that common actions taken within Talk, such as successfully posting a comment, posting a reaction, or changing a setting, are automatically emitted from Talk. To send these events to your analytics tool of choice, however, will require some integration on your part.
|
||||
Talk supports event emitting. This means that common actions taken within Talk, such as successfully posting a comment, posting a reaction, or changing a setting, are automatically emitted from Talk. To send these events to your analytics tool of choice, you’ll need to configure your Talk embed to process them.
|
||||
|
||||
First, we want to uncomment the tracking code in `article.ejs` (https://github.com/coralproject/talk/blob/93bda87ad061a2dc5eb8dc5b65a579a20efb76f7/views/article.ejs#L34). This will enable events to be sent via the Talk embed that is on your article pages. This will start a stream of events to the browser console, so that you can see which events are available.
|
||||
The following example demonstrates how the event emitter works on a sample article page generated by Talk. When you’re ready to capture and handle events from your actual site, add the events property along with your event handling functions to your Talk embed script.
|
||||
|
||||
## Example Using Local Development Instance of Talk
|
||||
If you have Talk running in a local development environment you can follow these steps to first view all of the events emitted by Talk, and then capture the `PostComment.success` event and send it to a third party analytics provider.
|
||||
|
||||
|
||||
First, with Talk running, ensure that you can access the `/dev/assets` endpoint and create a random article (i.e.: `localhost:3000/dev/assets`).
|
||||
|
||||
|
||||
Then in Talk, uncomment the events code in `/views/dev/article.njk`
|
||||
(https://github.com/coralproject/talk/blob/master/views/dev/article.njk#L32). This will enable events to be sent via the Talk embed that is on the sample article page. This will start a stream of events to the browser console, so that you can see which events are available.
|
||||
|
||||
```
|
||||
events: function(events) {
|
||||
@@ -16,7 +26,10 @@ First, we want to uncomment the tracking code in `article.ejs` (https://github.c
|
||||
},
|
||||
```
|
||||
|
||||
Now, we want to add our code that sends the events to our analytics system. In this case, we're sending the `PostComment.success` event. The particular way you send this will depend on what tool you're using. Refer to your tool's API and docs to determine this.
|
||||
Save your changes to `article.njk`, restart Talk, and try refreshing your article in a browser with development console open. You should see the console log the list of Talk’s events.
|
||||
|
||||
|
||||
Next, we want to add our code that sends the events to our analytics system. In this example, we're sending the `PostComment.success` event. The particular way you send this will depend on what tool you're using. Refer to your tool's API and docs to determine this.
|
||||
|
||||
```
|
||||
events: function(events) {
|
||||
@@ -30,4 +43,24 @@ Now, we want to add our code that sends the events to our analytics system. In t
|
||||
```
|
||||
You can continue this process for any specific events you'd like to track. You can also remove the `console.log` to stop events being emitted to the browser and instead only send the events to your analytics tool.
|
||||
|
||||
PR for Reference: https://github.com/coralproject/talk/pull/785
|
||||
## Metrics List
|
||||
|
||||
Note: This is not an exhaustive list, but should get you started.
|
||||
|
||||
```
|
||||
mutation.CreateRespectAction.success
|
||||
mutation.CreateLikeAction.success
|
||||
mutation.PostComment.success
|
||||
mutation.PostFlag.success
|
||||
mutation.EditComment.success
|
||||
|
||||
action.CORAL_STREAM_SET_ACTIVE_TAB
|
||||
action.SHOW_SIGNIN_DIALOG
|
||||
action.FOCUS_SIGNIN_DIALOG
|
||||
action.TALK_FRAMEWORK_HANDLE_SUCCESSFUL_LOGIN
|
||||
action.HIDE_SIGNIN_DIALOG
|
||||
action.TALK_VIEWING_OPTIONS_OPEN_MENU
|
||||
action.TALK_VIEWING_OPTIONS_CLOSE_MENU
|
||||
action.CORAL_STREAM_SET_SORT
|
||||
action.SET_ACTIVE_REPLY_BOX
|
||||
```
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: talk-plugin-notifications-category-moderation-actions
|
||||
permalink: /plugin/talk-plugin-notifications-category-moderation-actions/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-moderation-actions
|
||||
default: true
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
provides:
|
||||
- Server
|
||||
- Client
|
||||
---
|
||||
|
||||
When a comment that is initially withheld from publication and is then approved or rejected, the user will receive a notification email.
|
||||
|
||||
Configuration:
|
||||
`TALK_MODERATION_NOTIFICATION_TYPES`. This plugin requires values to be set. Available options: `APPROVED`, `REJECTED` as a single string (comma separated).
|
||||
@@ -111,7 +111,7 @@ If you deploy using Docker, you can extend from the `*-onbuild` image, an
|
||||
example `Dockerfile` for your project could be:
|
||||
|
||||
```Dockerfile
|
||||
FROM coralproject/talk:4.5-onbuild
|
||||
FROM coralproject/talk:4-onbuild
|
||||
```
|
||||
|
||||
Establish a private repository for your instance that includes the following:
|
||||
|
||||
+3
-4
@@ -28,9 +28,8 @@
|
||||
|
||||
|
||||
<div class="notice">
|
||||
<p>Don't see what you're looking for? We're upgrading the docs, check out our <a href="https://github.com/coralproject/talk/tree/ef49d9a3d2acc4d2fc03b00e0c872dfbc57f005a/docs/_docs" target="_blank" class="coral-link-invert">old docs</a>.</p>
|
||||
<p><a href="https://coralproject.net/" class="coral-link-invert" target="_blank">The Coral Project</a> from Mozilla</p>
|
||||
<a href="https://www.mozilla.org/" target="_blank" title="Mozilla"><figure class="mozilla" alt="Mozilla">{{ image_tag('images/moz-logo-bw-rgb.svg') }}</figure></a>
|
||||
<p>Made by <a href="https://coralproject.net/" class="coral-link-invert" target="_blank">The Coral Project</a> by <a href="https://www.voxmedia.com/" class="coral-link-invert" target="_blank" title="Vox Media">Vox Media</a>.</p>
|
||||
<a href="https://www.voxmedia.com/" target="_blank" title="Vox Media"><figure class="voxmedia" alt="Vox Media">{{ image_tag('images/coralproject.png') }}</figure></a>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="sidebar__backdrop"></div>
|
||||
<div class="sidebar__backdrop"></div>
|
||||
|
||||
+25
-2
@@ -344,9 +344,13 @@ a.brand {
|
||||
padding: $content_gutter $content_gutter 60px;
|
||||
}
|
||||
|
||||
.mozilla {
|
||||
width: 50%;
|
||||
.voxmedia {
|
||||
// width: 50%;
|
||||
margin: 0 0 $sidebar_gutter;
|
||||
|
||||
> img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@@ -461,3 +465,22 @@ a.brand {
|
||||
background: rgb(0, 102, 176);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.callout {
|
||||
background-color: $coral_button_background;
|
||||
color: white;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
@extend .coral-link;
|
||||
color: #FFF;
|
||||
border-color: #FFF;
|
||||
|
||||
&:hover {
|
||||
color: #FFF;
|
||||
border-color: darken(#FFF, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 360 102.8" style="enable-background:new 0 0 360 102.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<rect y="-0.2" width="360" height="102.9"/>
|
||||
<path class="st0" d="M343.8,73.7c-0.7,0.2-1.3,0.3-1.9,0.3c-2.1,0-3.1-0.9-3.1-3.5V51c0-10.3-8.2-15.3-17.9-15.3
|
||||
c-7.4,0-11.4,0.9-19.3,4.1L300,50.2l10.3,1.1l1.5-5.1c2.1-1.1,4.2-1.3,6.9-1.3c7.3,0,7.4,5.5,7.4,10.1v1.5
|
||||
c-2.3-0.3-4.9-0.4-7.4-0.4c-10.3,0-21,2.6-21,13.7c0,9.4,7.4,12.9,13.9,12.9c7.3,0,11.9-4.4,14.5-9c0.6,5.5,3.9,9,9.9,9
|
||||
c2.8,0,5.7-0.8,8.1-2.1L343.8,73.7z M316.2,73.4c-3.9,0-5.3-2.3-5.3-5.2c0-4.9,4-6.2,8.6-6.2c2.1,0,4.4,0.3,6.5,0.6
|
||||
C325.7,70.1,320.8,73.4,316.2,73.4z M298.9,12.9l-19.5,68.7h-12.7l19.5-68.7H298.9z M272.7,12.9l-19.5,68.7h-12.7L260,12.9H272.7z
|
||||
M218.7,36.6h13.5v16.2h-13.5V36.6z M218.7,65.5h13.5v16.2h-13.5V65.5z M199,64.9l10.2,1l-2.8,15.8h-39.3l-1.3-6.8l24.8-28.5h-14.1
|
||||
l-2,7l-9.3-1l1.6-15.8h39.5l1,6.8l-25,28.5h14.6L199,64.9z M134.5,35.7c-16.2,0-24.2,10.9-24.2,24.1c0,14.4,9.6,22.9,23.5,22.9
|
||||
c14.4,0,24.8-9.1,24.8-23.5C158.6,46.6,150.7,35.7,134.5,35.7z M134.2,72.7c-7,0-10.6-6-10.6-13.8c0-8.5,4.1-13.4,10.7-13.4
|
||||
c6.1,0,11,4.1,11,13.2C145.3,67.4,140.9,72.7,134.2,72.7z M98.7,71.8h6v9.8H85.9V56.3c0-7.8-2.6-10.8-7.7-10.8
|
||||
c-6.2,0-8.7,4.4-8.7,10.7v15.7h6v9.8H56.6V56.3c0-7.8-2.6-10.8-7.7-10.8c-6.2,0-8.7,4.4-8.7,10.7v15.7h8.6v9.8H21.4v-9.8h6V46.4h-6
|
||||
v-9.8h18.9v6.8c2.7-4.8,7.4-7.7,13.7-7.7c6.5,0,12.5,3.1,14.7,9.7c2.5-6,7.6-9.7,14.7-9.7c8.1,0,15.5,4.9,15.5,15.6V71.8z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -64,13 +64,13 @@ const wrapCheck = (
|
||||
|
||||
/**
|
||||
* checkPermissions checks that the current user has all the required
|
||||
* permissions.
|
||||
* permissions. It will return true if that's the case.
|
||||
*
|
||||
* @param {Object} ctx graph context
|
||||
* @param {Array<String>} permissions permissions that the user must have
|
||||
*/
|
||||
const checkPermissions = (ctx, permissions) =>
|
||||
!ctx.user || !ctx.user.can(...permissions);
|
||||
ctx.user && ctx.user.can(...permissions);
|
||||
|
||||
/**
|
||||
* wrapCheckPermissions will wrap a specific field with a permission check.
|
||||
@@ -89,7 +89,7 @@ const wrapCheckPermissions = (
|
||||
wrapCheck(
|
||||
typeResolver,
|
||||
field,
|
||||
(obj, args, ctx) => !checkPermissions(ctx, permissions),
|
||||
(obj, args, ctx) => checkPermissions(ctx, permissions),
|
||||
skipFieldResolver
|
||||
);
|
||||
|
||||
|
||||
@@ -879,6 +879,9 @@ type Settings {
|
||||
# customCssUrl is the URL of the custom CSS used to display on the frontend.
|
||||
customCssUrl: String
|
||||
|
||||
# customAdminCssUrl is the URL of the custom CSS used to display on the admin panel.
|
||||
customAdminCssUrl: String
|
||||
|
||||
# closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
# given asset will be considered closed.
|
||||
closedTimeout: Int
|
||||
@@ -1353,6 +1356,9 @@ input UpdateSettingsInput {
|
||||
# customCssUrl is the URL of the custom CSS used to display on the frontend.
|
||||
customCssUrl: String
|
||||
|
||||
# customAdminCssUrl is the URL of the custom CSS used to display on the admin panel.
|
||||
customAdminCssUrl: String
|
||||
|
||||
# closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
# given asset will be considered closed.
|
||||
closedTimeout: Int
|
||||
|
||||
+3
-1
@@ -124,6 +124,8 @@ en:
|
||||
copy_and_paste: 'Copy and paste code below into your CMS to embed your comment box in your articles'
|
||||
custom_css_url: 'Custom CSS URL'
|
||||
custom_css_url_desc: 'URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.'
|
||||
custom_admin_css_url: 'Custom Admin Panel CSS URL'
|
||||
custom_admin_css_url_desc: 'URL of a CSS stylesheet that will override default admin panel styles. Can be internal or external.'
|
||||
days: Days
|
||||
description: 'Change the comment settings on this story.'
|
||||
disable_commenting_desc: 'Write a message that will be displayed while commenting is deactivated.'
|
||||
@@ -219,7 +221,7 @@ en:
|
||||
subject: 'Your account has been banned'
|
||||
confirm:
|
||||
confirm_email: 'Confirm Email'
|
||||
has_been_requested: 'A email confirmation has been requested for the following account:'
|
||||
has_been_requested: 'An email confirmation has been requested for the following account:'
|
||||
if_you_did_not: 'If you did not request this, you can safely ignore this email.'
|
||||
subject: 'Email Confirmation'
|
||||
to_confirm: 'To confirm the account, please visit the following link:'
|
||||
|
||||
@@ -96,12 +96,18 @@ const createResolveFactory = (() => {
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
try {
|
||||
// Attach the custom css url and organization name.
|
||||
const { customCssUrl, organizationName } = await SettingsService.select(
|
||||
// Attach the custom css urls and organization name.
|
||||
const {
|
||||
customCssUrl,
|
||||
customAdminCssUrl,
|
||||
organizationName,
|
||||
} = await SettingsService.select(
|
||||
'customCssUrl',
|
||||
'customAdminCssUrl',
|
||||
'organizationName'
|
||||
);
|
||||
res.locals.customCssUrl = customCssUrl;
|
||||
res.locals.customAdminCssUrl = customAdminCssUrl;
|
||||
res.locals.organizationName = organizationName;
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
|
||||
@@ -28,6 +28,10 @@ const Setting = new Schema(
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
customAdminCssUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
infoBoxContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "4.8.0",
|
||||
"version": "4.8.7",
|
||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||
"main": "app.js",
|
||||
"private": true,
|
||||
|
||||
+22
-6
@@ -1,26 +1,42 @@
|
||||
{
|
||||
"server": [
|
||||
"talk-plugin-featured-comments",
|
||||
"talk-plugin-local-auth",
|
||||
"talk-plugin-featured-comments",
|
||||
"talk-plugin-respect",
|
||||
"talk-plugin-rich-text",
|
||||
"talk-plugin-profile-data",
|
||||
"talk-plugin-respect"
|
||||
"talk-plugin-notifications",
|
||||
"talk-plugin-notifications-category-featured",
|
||||
"talk-plugin-notifications-category-moderation-actions",
|
||||
"talk-plugin-notifications-category-reply",
|
||||
"talk-plugin-notifications-category-staff",
|
||||
"talk-plugin-notifications-digest-hourly",
|
||||
"talk-plugin-notifications-digest-daily"
|
||||
],
|
||||
"client": [
|
||||
"talk-plugin-auth",
|
||||
"talk-plugin-local-auth",
|
||||
"talk-plugin-author-menu",
|
||||
"talk-plugin-featured-comments",
|
||||
"talk-plugin-flag-details",
|
||||
"talk-plugin-ignore-user",
|
||||
"talk-plugin-local-auth",
|
||||
"talk-plugin-member-since",
|
||||
"talk-plugin-moderation-actions",
|
||||
"talk-plugin-permalink",
|
||||
"talk-plugin-profile-data",
|
||||
"talk-plugin-respect",
|
||||
"talk-plugin-rich-text",
|
||||
"talk-plugin-viewing-options",
|
||||
"talk-plugin-sort-most-replied",
|
||||
"talk-plugin-sort-most-respected",
|
||||
"talk-plugin-sort-newest",
|
||||
"talk-plugin-sort-oldest",
|
||||
"talk-plugin-viewing-options"
|
||||
"talk-plugin-profile-data",
|
||||
"talk-plugin-notifications",
|
||||
"talk-plugin-notifications-category-featured",
|
||||
"talk-plugin-notifications-category-moderation-actions",
|
||||
"talk-plugin-notifications-category-reply",
|
||||
"talk-plugin-notifications-category-staff",
|
||||
"talk-plugin-notifications-digest-hourly",
|
||||
"talk-plugin-notifications-digest-daily",
|
||||
"talk-plugin-ignore-user"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -102,6 +102,21 @@ he:
|
||||
reasons:
|
||||
comment:
|
||||
spam_comment: "זוהה ספאם"
|
||||
it:
|
||||
error:
|
||||
COMMENT_IS_SPAM: |
|
||||
Il linguaggio in questo commento sembra spam. Puoi
|
||||
modificare il commento o sottometterlo alla revisione dei moderatori.
|
||||
talk-plugin-akismet:
|
||||
spam: "Spam"
|
||||
spam_comment: "Spam"
|
||||
detected: "Rilevato da Akismet"
|
||||
still_spam: |
|
||||
Grazie. Il nostro team di moderatori revisionerà il tuo commento a breve.
|
||||
flags:
|
||||
reasons:
|
||||
comment:
|
||||
spam_comment: "Rilevata spam"
|
||||
nl_NL:
|
||||
error:
|
||||
COMMENT_IS_SPAM: |
|
||||
|
||||
@@ -339,6 +339,59 @@ he:
|
||||
username: "שם משתמש"
|
||||
write_your_username: "ערוך את שם המשתמש שלך"
|
||||
your_username: "שם המשתמש שלך מופיע בכל תגובה שתפרסם."
|
||||
it:
|
||||
talk-plugin-auth:
|
||||
login:
|
||||
email_verify_cta: "Perfavore verifica il tuo indirizzo email."
|
||||
request_new_verify_email: "Richiedi un'altra email"
|
||||
verify_email: "Grazie per aver creato un account! Abbiamo inviato una email all'indirizzo che ci hai fornito per verificare il tuo account."
|
||||
verify_email2: "Devi verificare il tuo account prima di poter interagire con la community."
|
||||
not_you: "Non sei tu?"
|
||||
logged_in_as: "Registrato come"
|
||||
logout: "Esci"
|
||||
sign_in: "Accedi"
|
||||
sign_in_to_join: "Accedi per partecipare alla conversazione"
|
||||
or: "o"
|
||||
email: "Indirizzo email"
|
||||
password: "Password"
|
||||
password_error: "La password deve essere lunga almeno 8 caratteri."
|
||||
forgot_your_pass: "Hai dimenticato la password?"
|
||||
need_an_account: "Vuoi un account?"
|
||||
register: "Registra"
|
||||
sign_up: "Registrati"
|
||||
confirm_password: "Conferma password"
|
||||
username: "Username"
|
||||
already_have_an_account: "Hai già un account?"
|
||||
recover_password: "Recupera password"
|
||||
email_in_use: "Indirizzo email già in uso"
|
||||
email_or_username_in_use: "Indirizzo email o username già in uso"
|
||||
required_field: "Questo è un campo obbligatorio"
|
||||
passwords_dont_match: "Le password non coincidono."
|
||||
special_characters: "Gli username possono contenere solo lettere, numeri e _"
|
||||
sign_in_to_comment: "Accedi per commentare"
|
||||
check_the_form: "Modulo non valido. Perfavore ricontrolla i campi"
|
||||
set_username_dialog:
|
||||
check_the_form: "Modulo non valido. Perfavore ricontrolla i campi"
|
||||
continue: "Continua con lo stesso username di Facebook"
|
||||
error_create: "Errore durante la modifica dell'username"
|
||||
fake_comment_body: "Questo è un esempio di commento. I lettori possono condividere i loro pensieri e opinioni con le redazioni nella sezione dei commenti."
|
||||
fake_comment_date: "1 minuto fa"
|
||||
if_you_dont_change_your_name: "Se non modifichi il tuo username in questo momento il tuo nome su Facebook comparirà accanto a tutti i tuoi commenti."
|
||||
required_field: "Campo obbligatorio"
|
||||
save: Salva
|
||||
special_characters: "Gli usernames possono contenere solo lettere, numeri e _"
|
||||
username: Username
|
||||
write_your_username: "Modifica il tuo username"
|
||||
your_username: "Il tuo username comparirà in tutti i tuoi commenti che pubblicherai."
|
||||
change_password:
|
||||
change_password: "Modifica password"
|
||||
passwords_dont_match: "Le passwords non coincidono"
|
||||
required_field: "Questo campo è obbligatorio"
|
||||
forgot_password: "Hai dimenticato la password?"
|
||||
save: "Salva"
|
||||
cancel: "Annulla"
|
||||
edit: "Modifica"
|
||||
changed_password_msg: "La tua password è stata modificata con successo"
|
||||
nl_NL:
|
||||
talk-plugin-auth:
|
||||
login:
|
||||
|
||||
@@ -12,6 +12,8 @@ fr:
|
||||
talk-plugin-author-menu:
|
||||
he:
|
||||
talk-plugin-author-menu:
|
||||
it:
|
||||
talk-plugin-author-menu:
|
||||
nl_NL:
|
||||
talk-plugin-author-menu:
|
||||
pt_BR:
|
||||
|
||||
@@ -4,7 +4,6 @@ permalink: /plugin/talk-plugin-comment-content/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-comment-content
|
||||
default: true
|
||||
provides:
|
||||
- Client
|
||||
---
|
||||
@@ -12,4 +11,4 @@ plugin:
|
||||
Pluginizes the text of a comment to support custom treatment of this text. This
|
||||
plugin currently parses the given text to see if it contains a link, and makes
|
||||
them clickable using
|
||||
[react-linkify](https://www.npmjs.com/package/react-linkify).
|
||||
[react-linkify](https://www.npmjs.com/package/react-linkify).
|
||||
|
||||
@@ -29,7 +29,7 @@ Configuration:
|
||||
guide. This is only required while the `talk-plugin-facebook-auth` plugin is
|
||||
enabled.
|
||||
|
||||
_NOTE: FabceBook auth requires your site to use `https` (SSL) not `http`. If your site is not `https` you can not use this plugin!_
|
||||
_NOTE: Facebook auth requires your site to use `https` (SSL) not `http`. If your site is not `https` you can not use this plugin!_
|
||||
|
||||
## GDPR Compliance
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ he:
|
||||
talk-plugin-facebook-auth:
|
||||
sign_in: "התחבר עם פייסבוק"
|
||||
sign_up: "הרשם עם פייסבוק"
|
||||
it:
|
||||
talk-plugin-facebook-auth:
|
||||
sign_in: "Accedi con Facebook"
|
||||
sign_up: "Registrati con Facebook"
|
||||
nl_NL:
|
||||
talk-plugin-facebook-auth:
|
||||
sign_in: "Inloggen met Facebook"
|
||||
|
||||
@@ -14,6 +14,17 @@ Enables the ability for Moderators to feature and un-feature comments via the
|
||||
Stream and the Admin. Featured comments show in a first-place tab on the Stream
|
||||
if there are any featured comments on that story.
|
||||
|
||||
When paired with the [talk-plugin-moderator-actions](/talk/plugin/talk-plugin-moderator-actions)
|
||||
When paired with the [talk-plugin-moderation-actions](/talk/plugin/talk-plugin-moderation-actions)
|
||||
plugin, moderators will have the option of featuring comments from the comment
|
||||
stream.
|
||||
stream.
|
||||
|
||||
## Default Comments Tab
|
||||
|
||||
You can also change the default tab when rendering the comment stream to the
|
||||
featured tab so when a user reaches a comment stream, they will see the featured
|
||||
comments first by setting the [TALK_DEFAULT_STREAM_TAB](/talk/advanced-configuration/#talk-default-stream-tab)
|
||||
variable to `talk-plugin-featured-comments`.
|
||||
|
||||
This is a **Build Variable** and must be consumed during build. If using the
|
||||
[Docker-onbuild](/talk/installation-from-docker/#onbuild)
|
||||
image you can specify it with `--build-arg TALK_DEFAULT_STREAM_TAB=talk-plugin-featured-comments`.
|
||||
|
||||
@@ -42,19 +42,10 @@
|
||||
text-align: left;
|
||||
letter-spacing: 0.1px;
|
||||
margin: 0;
|
||||
quotes: '\201c' '\201d';
|
||||
margin-bottom: 10px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.quote:before {
|
||||
content: open-quote;
|
||||
}
|
||||
|
||||
.quote:after {
|
||||
content: close-quote;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,21 @@ he:
|
||||
are_you_sure: האם אתה בטוח שברצונך להמליץ את התגובה הזו?
|
||||
cancel: בטל
|
||||
yes_feature_comment: כן, ממליץ תגובה
|
||||
it:
|
||||
talk-plugin-featured-comments:
|
||||
un_feature: Rimuovi dal primo piano
|
||||
feature: Metti in primo piano
|
||||
featured: Messo in primo piano
|
||||
featured_comments: Commenti in primo piano
|
||||
go_to_conversation: Vai alla conversazione
|
||||
tooltip_description: Commenti selezionati dal tuo team come meritevoli di essere letti
|
||||
notify_self_featured: 'Il commento da {0} è ora in primo piano e approvato'
|
||||
notify_featured: '{0} ha messo in primo piano e approvato il commento "{1}"'
|
||||
notify_unfeatured: '{0} ha rimosso dal primo piano il commento "{1}"'
|
||||
feature_comment: Metti in primo piano il commento?
|
||||
are_you_sure: Sei sicuro di voler mettere in primo piano questo commento?
|
||||
cancel: Annulla
|
||||
yes_feature_comment: Sì, metti il commento in primo piano
|
||||
nl_NL:
|
||||
talk-plugin-featured-comments:
|
||||
un_feature: Uitlichten ongedaan maken.
|
||||
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-flag-details:
|
||||
flags: דיווחים
|
||||
it:
|
||||
talk-plugin-flag-details:
|
||||
flags: Rapporti
|
||||
nl_NL:
|
||||
talk-plugin-flag-details:
|
||||
flags: Meldingen
|
||||
|
||||
@@ -18,6 +18,10 @@ he:
|
||||
talk-plugin-google-auth:
|
||||
sign_in: "התחבר עם גוגל"
|
||||
sign_up: "הרשם עם גוגל"
|
||||
it:
|
||||
talk-plugin-google-auth:
|
||||
sign_in: "Accedi con Google"
|
||||
sign_up: "Registrati con Google"
|
||||
nl_NL:
|
||||
talk-plugin-google-auth:
|
||||
sign_in: "Inloggen met Google"
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"passport-google-oauth2": "^0.1.6"
|
||||
"passport-google-oauth2": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,20 @@ he:
|
||||
notify_success: |
|
||||
כעת אתה מתעלם {0}. תוכל לבטל את הפעולה הזו מהפרופיל שלי.
|
||||
confirmation_title: "התעלם {0}?"
|
||||
it:
|
||||
talk-plugin-ignore-user:
|
||||
blank_info: In questo momento non stai ignorando alcun utente
|
||||
section_title: Utenti ignorati
|
||||
section_info: Perché stai ignorando i seguenti utenti, i loro commenti saranno nascosti.
|
||||
stop_ignoring: Non ignorare più
|
||||
ignore_user: Ignora utente
|
||||
cancel: Annulla
|
||||
confirmation: |
|
||||
Quando ignori un utente, tutti i loro commenti saranno nascosti. Puoi
|
||||
annullarlo successivamente da Mio Profilo.
|
||||
notify_success: |
|
||||
Stai ignorando {0}. Puoi annullare questa azione da Mio Profilo.
|
||||
confirmation_title: Ignora {0}?
|
||||
nl_NL:
|
||||
talk-plugin-ignore-user:
|
||||
blank_info: Momenteel negeer je geen gebruikers
|
||||
|
||||
@@ -26,6 +26,10 @@ he:
|
||||
talk-plugin-like:
|
||||
like: לייק
|
||||
liked: לייקד
|
||||
it:
|
||||
talk-plugin-like:
|
||||
like: Mi piace
|
||||
liked: Piaciuto
|
||||
nl_NL:
|
||||
talk-plugin-like:
|
||||
like: Like
|
||||
|
||||
@@ -452,6 +452,82 @@ es:
|
||||
description_2: "Puedes cambiar la configuración de tu cuenta visitando"
|
||||
path: "Mi perfil > Configuración"
|
||||
alert: "¡Correo electrónico agregado!"
|
||||
it:
|
||||
email:
|
||||
email_change_original:
|
||||
subject: Modifica email
|
||||
body: La tua email è stata cambiata da {0} a {1}. Se non hai richiesto questa modifica, perfavore contatta {2}.
|
||||
error:
|
||||
NO_LOCAL_PROFILE: Nessun indirizzo email esistente è associato a questo account.
|
||||
LOCAL_PROFILE: Un indirizzo email è già associato a questo account.
|
||||
INCORRECT_PASSWORD: La password fornita non è corretta.
|
||||
talk-plugin-local-auth:
|
||||
change_password:
|
||||
change_password: "Cambia password"
|
||||
passwords_dont_match: "Le password non coincidono"
|
||||
required_field: "Questo campo è obbligatorio"
|
||||
forgot_password: "Dimenticato la password?"
|
||||
old_password: "Password vecchia"
|
||||
new_password: "Password nuova"
|
||||
confirm_new_password: "Conferma la nuova password"
|
||||
save: "Salva"
|
||||
cancel: "Annulla"
|
||||
edit: "Modifica"
|
||||
changed_password_msg: "Password modificata - La tua password è stata modificata con successo"
|
||||
forgot_password_sent: "Password dimenticata - Ti abbiamo inviato una email per recuperare la tua password"
|
||||
change_username:
|
||||
change_username_note: "Gli username possono essere modificati solo una volta ogni 14 giorni."
|
||||
is_not_eligible: "In questo momento non puoi cambiare il tuo username."
|
||||
save: "Salva"
|
||||
edit_profile: "Modifica Profilo"
|
||||
cancel: "Annulla"
|
||||
confirm_username_change: "Conferma cambio dell'username"
|
||||
description: "Stai cercando di cambiare il tuo username. Il tuo nuovo username apparirà in tutti i tuoi commenti passati e futuri."
|
||||
old_username: "Username vecchio"
|
||||
new_username: "Username nuovo"
|
||||
re_enter: "Ri-scrivi il nuovo username"
|
||||
bottom_note: "Nota: non potrai più cambiare il tuo username per i prossimi 14 giorni"
|
||||
confirm_changes: "Conferma modifiche"
|
||||
username_does_not_match: "Gli username non coincidono"
|
||||
cant_be_equal: "{0} non può essere uguale al corrente"
|
||||
changed_username_success_msg: "Username cambiato - Il tuo username è stato modificato con successo. Non potrai più cambiarlo per i prossimi 14 giorni."
|
||||
change_username_attempt: "L'username non può essere aggiornato. Gli username possono essre cambiati una sola volta ogni 14 giorni."
|
||||
change_email:
|
||||
confirm_email_change: "Conferma la modifica dell'indirizzo email"
|
||||
description: "Stai cercando di modificare il tuo indirizzo email. Il tuo nuovo indirizzo email sarà usato per il login e per ricevere notifiche relative all'account."
|
||||
old_email: "Indirizzo email vecchio"
|
||||
new_email: "Indirizzo email nuovo"
|
||||
enter_password: "Inserisci password"
|
||||
incorrect_password: "Password incorretta"
|
||||
confirm_change: "Conferma modifica"
|
||||
cancel: "Annulla"
|
||||
change_email_msg: "Indirizzo email modificato. Questo indirizzo email sarà usato per il login e per ricevere notifiche relative all'account."
|
||||
add_email:
|
||||
add_email_address: "Aggiungi indirizzo email"
|
||||
enter_email_address: "Inserisci indirizzo email:"
|
||||
invalid_email_address: "Indirizzo email non valido"
|
||||
confirm_email_address: "Conferma indirizzo email:"
|
||||
email_does_not_match: "Gli indirizzi email non coincidono"
|
||||
insert_password: "Inserisci la password:"
|
||||
confirm_password: "Conferma la password:"
|
||||
required_field: "Questo campo è obbligatorio"
|
||||
done: "Finito"
|
||||
content:
|
||||
title: "Aggiungi indirizzo email"
|
||||
description: "Per questioni di sicurezza, richiediamo agli utenti di aggiungere un indirizzo email associato ai loro account. Il tuo indirizzo email sarà usato per:"
|
||||
item_1: "Ricevere aggiornamenti riguardo modifiche relative al tuo account (indirizzo email, username, password, etc.)"
|
||||
item_2: "Permetterti di scaricare i tuoi commenti."
|
||||
item_3: "Inviare le notifiche che hai deciso di ricevere."
|
||||
verify:
|
||||
title: "Verifica il tuo indirizzo email"
|
||||
description: "Abbiamo inviato una email a {0} per verificare il tuo account. Devi verificare il tuo indirizzo email così che potrà essere usato per confermare modifiche relative al tuo account e notifiche."
|
||||
added:
|
||||
title: "Indirizzo email aggiunto"
|
||||
description: "Il tuo indirizzo email è stato aggiunto al tuo account."
|
||||
subtitle: "Vuoi modificare il tuo indirizzo email?"
|
||||
description_2: "Puoi modificare le impostazioni del tuo account visitando"
|
||||
path: "Mio Profilo > Impostazioni"
|
||||
alert: "Email aggiunta!"
|
||||
nl_NL:
|
||||
email:
|
||||
email_change_original:
|
||||
|
||||
@@ -26,6 +26,10 @@ he:
|
||||
talk-plugin-love:
|
||||
love: אוהב
|
||||
loved: אהבתי
|
||||
it:
|
||||
talk-plugin-love:
|
||||
love: Amo
|
||||
loved: Amato
|
||||
nl_NL:
|
||||
talk-plugin-love:
|
||||
love: Love
|
||||
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-member-since:
|
||||
member_since: "חבר מאז"
|
||||
it:
|
||||
talk-plugin-member-since:
|
||||
member_since: "Membro dal"
|
||||
nl_NL:
|
||||
talk-plugin-member-since:
|
||||
member_since: "Lid sinds"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: talk-plugin-moderator-actions
|
||||
permalink: /plugin/talk-plugin-moderator-actions/
|
||||
title: talk-plugin-moderation-actions
|
||||
permalink: /plugin/talk-plugin-moderation-actions/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-moderator-actions
|
||||
name: talk-plugin-moderation-actions
|
||||
default: true
|
||||
provides:
|
||||
- Client
|
||||
|
||||
@@ -82,6 +82,18 @@ he:
|
||||
ban_user_dialog_cancel: "בטל"
|
||||
ban_user_dialog_yes: "כן. חסום משתמש"
|
||||
ban_user_dialog_headline: "חסום משתמש?"
|
||||
it:
|
||||
talk-plugin-moderation-actions:
|
||||
reject_comment: "Rifiuta"
|
||||
approve_comment: "Approva"
|
||||
approved_comment: "Approvato"
|
||||
moderation_actions: "Azioni di moderazione"
|
||||
ban_user: "Bandisci utente"
|
||||
ban_user_dialog_sub: "Sei sicuro di voler bandire questo utente?"
|
||||
ban_user_dialog_copy: "Nota: Bandire questo utente metterà questo commento nella coda di commenti rifiutati."
|
||||
ban_user_dialog_cancel: "Cancella"
|
||||
ban_user_dialog_yes: "Sì. Bandisci utente"
|
||||
ban_user_dialog_headline: "Bandisci utente?"
|
||||
nl_NL:
|
||||
talk-plugin-moderation-actions:
|
||||
reject_comment: "Afkeuren"
|
||||
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-notifications-category-featured/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-category-featured
|
||||
default: true
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
- name: talk-plugin-featured-comments
|
||||
@@ -14,4 +15,4 @@ plugin:
|
||||
|
||||
When a comment is featured (via the
|
||||
[talk-plugin-featured-comments](/talk/plugin/talk-plugin-featured-comments)
|
||||
plugin), the user will receive a notification email.
|
||||
plugin), the user will receive a notification email.
|
||||
|
||||
@@ -13,6 +13,9 @@ es:
|
||||
he:
|
||||
talk-plugin-notifications-category-featured:
|
||||
toggle_description: התגובה שלי מובלטת
|
||||
it:
|
||||
talk-plugin-notifications-category-featured:
|
||||
toggle_description: Il mio commento è stato messo in primo piano
|
||||
nl_NL:
|
||||
talk-plugin-notifications-category-featured:
|
||||
toggle_description: Mijn bericht is uitgelicht
|
||||
@@ -35,6 +35,12 @@ he:
|
||||
featured:
|
||||
subject: "אחת מהתגובות שלך הוצגה ב {0}"
|
||||
body: "{0} \n חבר בצוות שלנו בחר תגובה זו כך שתוצג עבור קוראים אחרים: {1}"
|
||||
it:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
featured:
|
||||
subject: "Uno dei tuoi commenti è stato messo in primo piano su {0}"
|
||||
body: "{0}\nUn membro del nostro team ha selezionato questo commento per essere messo in primo piano per altri lettori: {1}"
|
||||
nl_NL:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-notifications-moderation-actions/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-moderation-actions
|
||||
default: true
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
provides:
|
||||
|
||||
@@ -4,3 +4,6 @@ en:
|
||||
es:
|
||||
talk-plugin-notifications-category-moderation-actions:
|
||||
toggle_description: Mis comentarios pendientes han sido revisados
|
||||
it:
|
||||
talk-plugin-notifications-category-moderation-actions:
|
||||
toggle_description: I miei commenti in sospeso sono stati esaminati
|
||||
@@ -18,3 +18,13 @@ es:
|
||||
rejected:
|
||||
subject: "Tu comentario en {0} no fue publicado"
|
||||
body: "{0}\nEl lenguaje utilizado en uno de tus comentarios no cumple con las pautas de nuestra comunidad, por lo que el comentario se eliminó."
|
||||
it:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
moderation-actions:
|
||||
approved:
|
||||
subject: "Il tuo commento su {0} è stato pubblicato"
|
||||
body: "{0}\nGrazie per aver commentato. Il tuo commento è stato pubblicato: {1}"
|
||||
rejected:
|
||||
subject: "Il tuo commento su {0} non è stato pubblicato"
|
||||
body: "{0}\nIl linguaggio usato in uno dei tuoi commenti non si attiene alle linee guida della community, e quindi il commento è stato rimosso."
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-notifications-category-reply/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-category-reply
|
||||
default: true
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
provides:
|
||||
@@ -12,4 +13,4 @@ plugin:
|
||||
---
|
||||
|
||||
Replies made to each user will trigger an email to be sent with the notification
|
||||
details if enabled.
|
||||
details if enabled.
|
||||
|
||||
@@ -13,6 +13,9 @@ es:
|
||||
he:
|
||||
talk-plugin-notifications-category-reply:
|
||||
toggle_description: התגובה שלי מקבלת תשובה
|
||||
it:
|
||||
talk-plugin-notifications-category-reply:
|
||||
toggle_description: Il mio commento ha ricevuto una risposta
|
||||
nl_NL:
|
||||
talk-plugin-notifications-category-reply:
|
||||
toggle_description: Iemand antwoord op mijn reactie
|
||||
|
||||
@@ -35,6 +35,12 @@ he:
|
||||
reply:
|
||||
subject: "מישהו השיב לתגובה שלך על {0}"
|
||||
body: "{0}\n{1} השיב לתגובתך: {2}"
|
||||
it:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
reply:
|
||||
subject: "Qualcuno ha risposto al tuo commento su {0}"
|
||||
body: "{0}\n{1} ha risposto al tuo commento: {2}"
|
||||
nl_NL:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-notifications-category-staff/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-category-staff
|
||||
default: true
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
provides:
|
||||
|
||||
@@ -13,6 +13,9 @@ es:
|
||||
he:
|
||||
talk-plugin-notifications-category-staff:
|
||||
toggle_description: חבר צוות עונה על תגובתי
|
||||
it:
|
||||
talk-plugin-notifications-category-staff:
|
||||
toggle_description: Un membro dello staff risponde al mio commento
|
||||
nl_NL:
|
||||
talk-plugin-notifications-category-staff:
|
||||
toggle_description: Een redactielid antwoord op mijn reactie
|
||||
|
||||
@@ -35,6 +35,12 @@ he:
|
||||
staff:
|
||||
subject: "מישהו ב {0} השיב לתגובתך"
|
||||
body: "{0}\n{1} עובד עבור {2} והוא השיב לתגובתך: {3}"
|
||||
it:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
staff:
|
||||
subject: "Qualcuno in {0} ha risposto al tuo commento"
|
||||
body: "{0}\n{1} lavora per {2} e ha risposto al tuo commento: {3}"
|
||||
nl_NL:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-notifications-digest-daily/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-digest-daily
|
||||
default: true
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
provides:
|
||||
@@ -13,4 +14,4 @@ plugin:
|
||||
|
||||
Enables a digesting option for users to digest their notifications on an `DAILY`
|
||||
basis, where the notification batching occurs every day at midnight in the
|
||||
`America/New_York` timezone.
|
||||
`America/New_York` timezone.
|
||||
|
||||
@@ -22,6 +22,10 @@ he:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
DAILY: בתקציר יומי
|
||||
it:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
DAILY: In un riassunto giornaliero
|
||||
nl_NL:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-notifications-digest-hourly/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-digest-hourly
|
||||
default: true
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
provides:
|
||||
@@ -13,4 +14,4 @@ plugin:
|
||||
|
||||
Enables a digesting option for users to digest their notifications on an `HOURLY`
|
||||
basis, where the notification batching occurs every hour in the
|
||||
`America/New_York` timezone.
|
||||
`America/New_York` timezone.
|
||||
|
||||
@@ -22,6 +22,10 @@ he:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
HOURLY: בתקציר שעתי
|
||||
it:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
HOURLY: In un riassunto orario
|
||||
nl_NL:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-notifications/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications
|
||||
default: true
|
||||
provides:
|
||||
- Server
|
||||
- Client
|
||||
@@ -28,4 +29,4 @@ line by adjusting the translation, Talk's default behavior is to add a prefix
|
||||
before the subject of each email sent. This is always set to the
|
||||
[TALK-EMAIL-SUBJECT-PREFIX](/talk/advanced-configuration/#TALK-EMAIL-SUBJECT-PREFIX)
|
||||
configuration variable. You should change this parameter if you want to affect
|
||||
how the subject is rendered.
|
||||
how the subject is rendered.
|
||||
|
||||
@@ -106,6 +106,24 @@ he:
|
||||
digest_option: שלח התראות
|
||||
digest_enum:
|
||||
NONE: מיד
|
||||
it:
|
||||
talk-plugin-notifications:
|
||||
settings_title: Notifiche
|
||||
settings_subtitle: Ricevi notifiche quando
|
||||
turn_off_all: Non voglio ricevere notifiche
|
||||
banner_info:
|
||||
title: Verifica dell'email richiesta
|
||||
text: Per ricevere notifiche email devi aver un indirizzo email verificato.
|
||||
verify_now: Verifica la tua email ora
|
||||
banner_success:
|
||||
title: Email di verifica inviata
|
||||
text: Una email contenente il link di verifica è stata inviata a {0}.
|
||||
banner_error:
|
||||
title: Errore
|
||||
text: C'è stato un errore nell'invio della email di verifica. Perfavore riprova più tardi.
|
||||
digest_option: Invia notifiche
|
||||
digest_enum:
|
||||
NONE: Immediatamente
|
||||
nl_NL:
|
||||
talk-plugin-notifications:
|
||||
settings_title: Notificaties
|
||||
|
||||
@@ -4,11 +4,10 @@ permalink: /plugin/talk-plugin-offtopic/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-offtopic
|
||||
default: true
|
||||
provides:
|
||||
- Server
|
||||
- Client
|
||||
---
|
||||
|
||||
Allows the comment authors to tag their comment as `Off-Topic` which will add a
|
||||
visible badge on the frontend to other users that their comment is off-topic.
|
||||
visible badge on the frontend to other users that their comment is off-topic.
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
"off_topic": "מחוץ לנושא",
|
||||
"hide_off_topic": "הסתר תגובות מחוץ לנושא"
|
||||
},
|
||||
"it": {
|
||||
"off_topic": "Fuori tema",
|
||||
"hide_off_topic": "Nascondi i commenti fuori tema"
|
||||
},
|
||||
"nl_NL": {
|
||||
"off_topic": "Off topic",
|
||||
"hide_off_topic": "Off-topic reacties verbergen"
|
||||
|
||||
@@ -304,6 +304,57 @@ he:
|
||||
subtitle: "האם אתה בטוח שברצונך למחוק את החשבון שלך?"
|
||||
description: "כדי לאשר שברצונך למחוק את חשבונך, הקלד את הביטוי הבא בתיבת הטקסט שבהמשך:"
|
||||
type_to_confirm: "הקלד ביטוי למטה כדי לאשר"
|
||||
it:
|
||||
download_request:
|
||||
section_title: "Scarica la mia cronologia di commenti"
|
||||
you_will_get_a_copy: "Riceverai una email con un link per scaricare la tua cronologia di commenti. Puoi fare"
|
||||
download_rate: "una richiesta di download ogni {0} giorni"
|
||||
most_recent_request: "La tua richiesta più recente"
|
||||
request: "Richiedi la cronologia di commenti"
|
||||
rate_limit: "Puoi presentare un'altra richiesta di cronologia dei commenti fra {0}"
|
||||
hours: "{0} ore"
|
||||
days: "{0} giorni"
|
||||
hour: "{0} ora"
|
||||
day: "{0} giorno"
|
||||
download_preparing: "Preparazione scaricamento account - Controlla la tua email a breve per un link di download"
|
||||
delete_request:
|
||||
account_deletion_cancelled: "Richiesta di eliminazione dell'account cancellata - La tua richiesta per eliminare il tuo account è stata cancellata."
|
||||
account_deletion_requested: "Richiesta di eliminazione dell'account"
|
||||
received_on: "Una richiesta di eliminazione dell'account è stata ricevuta su "
|
||||
cancel_request_description: "Se volessi riattivare il tuo account, puoi cancellare la tua richiesta di eliminazione dell'account"
|
||||
before: "prima"
|
||||
cancel_account_deletion_request: "Cancella la richiesta di eliminazione dell'account"
|
||||
delete_my_account: "Elimina il mio account"
|
||||
delete_my_account_description: "Eliminare il tuo account rimuoverà permanentemente il tuo profilo ed eliminerà tutti i tuoi commenti su questo sito."
|
||||
already_submitted_request_description: "Hai già inviato una richiesta di eliminazione dell'account. Il tuo account sarà eliminato dopo {0}. Potrai cancellare la richiesta fino a quel momento"
|
||||
your_request_submitted_description: "La richiesta è stata presentata e la conferma è stata inviata all'email associata al tua account."
|
||||
your_account_deletion_scheduled: "L'eliminazione del tuo account è stata programmata dopo:"
|
||||
changed_your_mind: "Hai cambiato idea?"
|
||||
simply_go_to: "Semplicemente accedi al tuo account prima di questo momento e premi"
|
||||
tell_us_why: "Dicci il perché"
|
||||
feedback_copy: "Ci piacerebbe sapere perché hai deciso di eliminare il tuo account. Inviaci un feedback scrivendoci all'indirizzo email"
|
||||
done: "Finito"
|
||||
cancel: "Cancella"
|
||||
proceed: "Procedi"
|
||||
input_is_not_correct: "L'input non è corretto"
|
||||
step_0:
|
||||
you_are_attempting: "Stai cercando di eliminare il tuo account. Questo significa:"
|
||||
item_1: "Tutti i tuoi commenti saranno rimossi da questo sito"
|
||||
item_2: "Tutti i tuoi commenti saranno eliminati dal nostro database"
|
||||
item_3: "Il tuo username e indirizzo email saranno eliminate dal nostro sistema"
|
||||
step_1:
|
||||
subtitle: "Quando sarà eliminato il mio account?"
|
||||
description: "Il tuo account sarà eliminato {0} ore dopo l'invio della tua richiesta."
|
||||
subtitle_2: "Posso comunque scrivere commenti fino a quando il mio account non sarà eliminato?"
|
||||
description_2: "Sì, potrai ancora commentare e rispondere ai commenti fino a quando le {0} ore non saranno scadute."
|
||||
step_2:
|
||||
description: "Prima che il tuo account venga eliminato, ti consigliamo di scaricare la tua cronologia di commenti. Quando il tuo account sarà definitivamente eliminato ti sarà impossibile richiedere la cronologia di commenti."
|
||||
to_download: "Per scaricare la tua cronologia di commenti vai a:"
|
||||
path: "Mio Profilo > Scarica la mia cronologia di commenti"
|
||||
step_3:
|
||||
subtitle: "Sei sicuro di voler eliminare il tuo account?"
|
||||
description: "Per confermare l'eliminazione dell'account riscrivi la frase seguente nella casella di testo qui sotto:"
|
||||
type_to_confirm: "Riscrivi la frase qui sotto per confermare"
|
||||
pt_BR:
|
||||
download_request:
|
||||
section_title: "Baixar meu histórico de comentários"
|
||||
|
||||
@@ -221,6 +221,43 @@ he:
|
||||
body: "ביטלת את בקשת מחיקת החשבון שלך עבור {0}. החשבון שלך מופעל מחדש כעת."
|
||||
error:
|
||||
DOWNLOAD_TOKEN_INVALID: "קישור ההורדה שלך אינו חוקי."
|
||||
it:
|
||||
download_landing:
|
||||
download_your_account: "Scarica la tua cronologia di commenti"
|
||||
download_details: "La tua cronologia di commenti sarà scaricata in un .zip file. Dopo che la tua cronologia di commenti sarà decompressa avrai un file di valori separati da virgola (o .csv) che potrai facilmente importare nella tua applicazione per fogli elettronici preferita."
|
||||
all_information_included: "Per ciascuno dei tuoi commenti saranno incluse le seguenti informazioni:"
|
||||
information_included:
|
||||
date: "Quando hai scritto il commento"
|
||||
url: "L'URL del link permanente per il commento"
|
||||
body: "Il testo del commento"
|
||||
asset_url: "L'URL dell'articolo o della storia dove appare il commento"
|
||||
confirm: "Scarica la mia cronologia di commenti"
|
||||
email:
|
||||
download:
|
||||
subject: "I tuoi commenti sono pronti per essere scaricati da {0}"
|
||||
download_link_ready: "Premi qui per scaricare i tuoi commenti da {0} per {1}:"
|
||||
download_archive: "Scarica Archivio"
|
||||
delete:
|
||||
subject: "Il tuo account per {0} è in programma per essere eliminato"
|
||||
body: |
|
||||
E' stata ricevuta una richiesta di eliminare il tuo account. Il tuo account è in programma per essere eliminato il {1}.
|
||||
|
||||
Dopo quel momento tutti i tuoi commenti saranno eliminati dal sito,tutti i tuoi commenti saranno eliminati dal nostro database, e il tuo username e email saranno rimossi dal nostro sistema.
|
||||
|
||||
Se dovessi cambiare idea puoi accedere al tuo account e cancellare la richiesta prima della data stabilita per l'eliminazione dell'account.
|
||||
deleted:
|
||||
subject: "Il tuo account per {0} è stato eliminato"
|
||||
body: |
|
||||
Il tuo account per {0} è stato eliminato. Ci dispiace di vederti andare!
|
||||
|
||||
Se volessi ri-partecipare alla discussione in futuro, potrai registrare un nuovo account.
|
||||
|
||||
Se vuoi lasciarci un feedback sul perché sei andato via e su come possiamo rendere l'esperienza di commento migliore, puoi inviarci un email a {1}.
|
||||
cancelDelete:
|
||||
subject: "La tua richiesta di eliminazione dell'account per {0} è stata cancellata"
|
||||
body: "Hai cancellato la tua richiesta di eliminazione dell'account per {0}. Il tuo account è ora riattivato."
|
||||
error:
|
||||
DOWNLOAD_TOKEN_INVALID: "Il tuo link per scaricare non è valido."
|
||||
nl_NL:
|
||||
download_landing:
|
||||
download_your_account: "Download je reactie geschiedenis"
|
||||
|
||||
@@ -26,6 +26,10 @@ he:
|
||||
talk-plugin-respect:
|
||||
respect: כבד
|
||||
respected: נכבד
|
||||
it:
|
||||
talk-plugin-respect:
|
||||
respect: Rispetto
|
||||
respected: Rispettato
|
||||
nl_NL:
|
||||
talk-plugin-respect:
|
||||
respect: Respect
|
||||
|
||||
@@ -4,6 +4,7 @@ permalink: /plugin/talk-plugin-rich-text/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-rich-text
|
||||
default: true
|
||||
provides:
|
||||
- Client
|
||||
- Server
|
||||
|
||||
@@ -22,7 +22,7 @@ class Editor extends React.Component {
|
||||
};
|
||||
|
||||
getHTML(props = this.props) {
|
||||
if (props.input.richTextBody) {
|
||||
if (props.input.richTextBody !== undefined) {
|
||||
return props.input.richTextBody;
|
||||
}
|
||||
return (
|
||||
@@ -119,7 +119,6 @@ Editor.propTypes = {
|
||||
isEdit: PropTypes.bool,
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
|
||||
@@ -12,4 +12,9 @@ he:
|
||||
talk-plugin-rich-text:
|
||||
format_bold: מודגש
|
||||
format_italic: נטוי
|
||||
format_blockquote: blockquote
|
||||
it:
|
||||
talk-plugin-rich-text:
|
||||
format_bold: bold
|
||||
format_italic: italic
|
||||
format_blockquote: blockquote
|
||||
@@ -11,7 +11,7 @@ plugin:
|
||||
Enables all new comments that are written to be posted to a Slack channel as
|
||||
well. Configure an
|
||||
[Incoming Webhook](https://api.slack.com/incoming-webhooks)
|
||||
app and provide that url in the form of the `SLACK_WEBHOOK_URL`
|
||||
app and provide that url in the form of the `TALK_SLACK_WEBHOOK_URL`
|
||||
detailed below.
|
||||
|
||||
*Warning: On high volume sites, this means every single comment will flow into
|
||||
@@ -20,5 +20,5 @@ recipe to further customize the behavior*.
|
||||
|
||||
Configuration:
|
||||
|
||||
- `SLACK_WEBHOOK_URL` (**required**) - The webhook url that will be
|
||||
used to post new comments to.
|
||||
- `TALK_SLACK_WEBHOOK_URL` (**required**) - The webhook url that will be
|
||||
used to post new comments to.
|
||||
|
||||
@@ -4,6 +4,9 @@ en:
|
||||
he:
|
||||
talk-plugin-sort-most-downvoted:
|
||||
label: השלילי ביותר קודם
|
||||
it:
|
||||
talk-plugin-sort-most-downvoted:
|
||||
label: Prima il più downvotato
|
||||
nl_NL:
|
||||
talk-plugin-sort-most-downvoted:
|
||||
label: Minst gerespecteerd eerst
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-sort-most-liked:
|
||||
label: הכי לייקד קודם
|
||||
it:
|
||||
talk-plugin-sort-most-liked:
|
||||
label: Prima il più apprezzato
|
||||
nl_NL:
|
||||
talk-plugin-sort-most-liked:
|
||||
label: Meest geliked eerst
|
||||
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-sort-most-loved:
|
||||
label: הכי אהוב קודם
|
||||
it:
|
||||
talk-plugin-sort-most-loved:
|
||||
label: Prima il più amato
|
||||
nl_NL:
|
||||
talk-plugin-sort-most-loved:
|
||||
label: Meest geliefd eerst
|
||||
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-sort-most-replied:
|
||||
label: הכי השיב קודם
|
||||
it:
|
||||
talk-plugin-sort-most-replied:
|
||||
label: Prima il più commentato
|
||||
nl_NL:
|
||||
talk-plugin-sort-most-replied:
|
||||
label: Meest beantwoord eerst
|
||||
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-sort-most-respected:
|
||||
label: הכי מכובד קודם
|
||||
it:
|
||||
talk-plugin-sort-most-respected:
|
||||
label: Prima il più rispettato
|
||||
nl_NL:
|
||||
talk-plugin-sort-most-respected:
|
||||
label: Meest gerespecteerd eerst
|
||||
|
||||
@@ -10,6 +10,9 @@ es:
|
||||
he:
|
||||
talk-plugin-sort-most-upvoted:
|
||||
label: החיובי ביותר קודם
|
||||
it:
|
||||
talk-plugin-sort-most-upvoted:
|
||||
label: Prima il più votato
|
||||
nl_NL:
|
||||
talk-plugin-sort-most-upvoted:
|
||||
label: Meest gerespecteerd eerst
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-sort-newest:
|
||||
label: מהראשונה לאחרונה
|
||||
it:
|
||||
talk-plugin-sort-newest:
|
||||
label: Prima il più nuovo
|
||||
nl_NL:
|
||||
talk-plugin-sort-newest:
|
||||
label: Nieuwste eerst
|
||||
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-sort-oldest:
|
||||
label: מהאחרונה לראשונה
|
||||
it:
|
||||
talk-plugin-sort-oldest:
|
||||
label: Prima il più vecchio
|
||||
nl_NL:
|
||||
talk-plugin-sort-oldest:
|
||||
label: Oudste eerst
|
||||
|
||||
@@ -19,6 +19,9 @@ fr:
|
||||
he:
|
||||
talk-plugin-subscriber:
|
||||
subscriber: "מנוי"
|
||||
it:
|
||||
talk-plugin-subscriber:
|
||||
subscriber: "Iscritto"
|
||||
nl_NL:
|
||||
talk-plugin-subscriber:
|
||||
subscriber: "Abonnee"
|
||||
|
||||
@@ -120,6 +120,24 @@ he:
|
||||
reasons:
|
||||
comment:
|
||||
toxic_comment: "סביר מאוד להיות רעיל"
|
||||
it:
|
||||
error:
|
||||
COMMENT_IS_TOXIC: |
|
||||
Sei sicuro? Il linguaggio in questo commento potrebbe violare le linee guida della community
|
||||
Puoi modificare questo commento o sottoporlo alla revisione del moderatore
|
||||
talk-plugin-toxic-comments:
|
||||
unlikely: "Improbabile"
|
||||
highly_likely: "Estremamente probabile"
|
||||
possibly: "Possibilmente"
|
||||
likely: "Probabilmente"
|
||||
toxic_comment: "Commento Tossico"
|
||||
still_toxic: |
|
||||
Questo commento modificato potrebbe comunque violare le linee guida della community
|
||||
Il nostro team di moderatori revisionerà il tuo commento a breve
|
||||
flags:
|
||||
reasons:
|
||||
comment:
|
||||
toxic_comment: "Estremamente probabile che sia Tossico"
|
||||
nl_NL:
|
||||
error:
|
||||
COMMENT_IS_TOXIC: |
|
||||
|
||||
@@ -33,6 +33,11 @@ he:
|
||||
viewing_options: "אפשרויות צפייה"
|
||||
sort: מיון
|
||||
filter: סינון
|
||||
it:
|
||||
talk-plugin-viewing-options:
|
||||
viewing_options: "Opzioni di visualizzazione"
|
||||
sort: Ordine
|
||||
filter: Filtro
|
||||
nl_NL:
|
||||
talk-plugin-viewing-options:
|
||||
viewing_options: "Weergave opties"
|
||||
|
||||
+31
-11
@@ -22,6 +22,10 @@ const {
|
||||
ROOT_URL,
|
||||
RECAPTCHA_WINDOW,
|
||||
RECAPTCHA_INCORRECT_TRIGGER,
|
||||
USERNAME_CAST_REGEXP,
|
||||
USERNAME_REPLACEMENT_CAST_REGEXP,
|
||||
USERNAME_REPLACEMENT_CHARACTER,
|
||||
USERNAME_VALIDATION_REGEX,
|
||||
} = require('../config');
|
||||
const { jwt: JWT_SECRET } = require('../secrets');
|
||||
const debug = require('debug')('talk:services:users');
|
||||
@@ -60,12 +64,17 @@ async function upsertUser(
|
||||
shouldSetDisplayName = false
|
||||
) {
|
||||
let user = await User.findOne({
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id,
|
||||
provider,
|
||||
$or: [
|
||||
{ id },
|
||||
{
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id,
|
||||
provider,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
if (user) {
|
||||
user.wasUpserted = false;
|
||||
@@ -80,6 +89,7 @@ async function upsertUser(
|
||||
|
||||
// The user was not found, lets create them!
|
||||
user = new User({
|
||||
id,
|
||||
username,
|
||||
lowercaseUsername: username.toLowerCase(),
|
||||
profiles: [{ id, provider }],
|
||||
@@ -519,7 +529,10 @@ class Users {
|
||||
}
|
||||
|
||||
static castUsername(username) {
|
||||
return username.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, '');
|
||||
return username
|
||||
.trim()
|
||||
.replace(USERNAME_REPLACEMENT_CAST_REGEXP, USERNAME_REPLACEMENT_CHARACTER)
|
||||
.replace(USERNAME_CAST_REGEXP, '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -548,7 +561,11 @@ class Users {
|
||||
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
||||
// Generate `GROUP_ATTEMPTS` guesses for the username.
|
||||
const usernameGuesses = Array.from(Array(GROUP_ATTEMPTS)).map(
|
||||
() => `${castedName}_${random(0, END_NUMBER_MAX)}`
|
||||
() =>
|
||||
`${castedName}${USERNAME_REPLACEMENT_CHARACTER}${random(
|
||||
0,
|
||||
END_NUMBER_MAX
|
||||
)}`
|
||||
);
|
||||
|
||||
// Map them all to lowercase.
|
||||
@@ -678,13 +695,11 @@ class Users {
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async isValidUsername(username, checkAgainstWordlist = true) {
|
||||
const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/;
|
||||
|
||||
if (!username) {
|
||||
throw new ErrMissingUsername();
|
||||
}
|
||||
|
||||
if (!onlyLettersNumbersUnderscore.test(username)) {
|
||||
if (!USERNAME_VALIDATION_REGEX.test(username)) {
|
||||
throw new ErrSpecialChars();
|
||||
}
|
||||
|
||||
@@ -798,8 +813,13 @@ class Users {
|
||||
* @param {Object} token a jwt token used to sign in the user
|
||||
*/
|
||||
static async findOrCreateByIDToken(id, token) {
|
||||
// FIXME: (wyattjoh) adding the `{ 'profiles.id': id }` search condition is
|
||||
// a workaround for the `upsertExternalUser` bug where the ID being set was
|
||||
// incorrect. Patch the user creation functions to adapt to the correct
|
||||
// behavior. Thankfully, this is currently still covered by an index.
|
||||
|
||||
// Try to get the user.
|
||||
let user = await User.findOne({ id });
|
||||
let user = await User.findOne({ $or: [{ id }, { 'profiles.id': id }] });
|
||||
|
||||
// If the user was not found, try to look it up.
|
||||
if (user === null) {
|
||||
|
||||
@@ -97,7 +97,7 @@ module.exports = {
|
||||
elements: {
|
||||
logoutButton: '.talk-stream-userbox-logout',
|
||||
signInButton: '#coralSignInButton',
|
||||
commentBoxTextarea: '.talk-plugin-commentbox-textarea',
|
||||
commentBoxTextarea: '.talk-plugin-rich-text-content',
|
||||
commentBoxPostButton: '.talk-plugin-commentbox-button',
|
||||
firstComment: '.talk-stream-comment.talk-stream-comment-level-0',
|
||||
firstCommentContent:
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
const mongoose = require('../../services/mongoose');
|
||||
|
||||
before(function(done) {
|
||||
const models = [
|
||||
require('../../models/action'),
|
||||
require('../../models/asset'),
|
||||
require('../../models/comment'),
|
||||
require('../../models/migration'),
|
||||
require('../../models/setting'),
|
||||
require('../../models/user'),
|
||||
require('../../models/migration'),
|
||||
];
|
||||
|
||||
before(async function() {
|
||||
this.timeout(30000);
|
||||
|
||||
mongoose.connection.on('open', function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
// Ensure we can connect to the database.
|
||||
await new Promise((resolve, reject) => {
|
||||
mongoose.connection.on('open', err => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return done();
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure all the models have indexes created.
|
||||
await Promise.all(models.map(model => model.ensureIndexes()));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('graph.mutations.createComment', () => {
|
||||
beforeEach(() => SettingsService.init());
|
||||
|
||||
const query = `
|
||||
mutation CreateComment($input: CreateCommentInput = {asset_id: 123, body: "Here's my comment!"}) {
|
||||
mutation CreateComment($input: CreateCommentInput = {asset_id: 123, body: "Here's my comment!", richTextBody: "Here's my comment!"}) {
|
||||
createComment(input: $input) {
|
||||
comment {
|
||||
id
|
||||
@@ -293,6 +293,7 @@ describe('graph.mutations.createComment', () => {
|
||||
input: {
|
||||
asset_id: '123',
|
||||
body,
|
||||
richTextBody: 'This is such a nice comment!',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -39,10 +39,12 @@ describe('graph.mutations.editComment', () => {
|
||||
it('a user can edit their own comment', async () => {
|
||||
const context = new Context({ user });
|
||||
const testStartedAt = new Date();
|
||||
const body = `hello there! ${String(Math.random()).slice(2)}`;
|
||||
const comment = await CommentsService.publicCreate({
|
||||
asset_id: asset.id,
|
||||
author_id: user.id,
|
||||
body: `hello there! ${String(Math.random()).slice(2)}`,
|
||||
body,
|
||||
richTextBody: body,
|
||||
});
|
||||
|
||||
// body_history should be there
|
||||
@@ -58,6 +60,7 @@ describe('graph.mutations.editComment', () => {
|
||||
asset_id: asset.id,
|
||||
edit: {
|
||||
body: newBody,
|
||||
richTextBody: newBody,
|
||||
},
|
||||
});
|
||||
if (response.errors && response.errors.length) {
|
||||
@@ -86,10 +89,12 @@ describe('graph.mutations.editComment', () => {
|
||||
});
|
||||
|
||||
it("A user can't edit their comment outside of the edit comment time window", async () => {
|
||||
const body = `hello there! ${String(Math.random()).slice(2)}`;
|
||||
const comment = await CommentsService.publicCreate({
|
||||
asset_id: asset.id,
|
||||
author_id: user.id,
|
||||
body: `hello there! ${String(Math.random()).slice(2)}`,
|
||||
body,
|
||||
richTextBody: body,
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
@@ -103,6 +108,7 @@ describe('graph.mutations.editComment', () => {
|
||||
asset_id: asset.id,
|
||||
edit: {
|
||||
body: newBody,
|
||||
richTextBody: newBody,
|
||||
},
|
||||
});
|
||||
if (response.errors && response.errors.length > 0) {
|
||||
@@ -120,10 +126,12 @@ describe('graph.mutations.editComment', () => {
|
||||
});
|
||||
|
||||
it("A user can't edit someone else's comment", async () => {
|
||||
const body = `hello there! ${String(Math.random()).slice(2)}`;
|
||||
const comment = await CommentsService.publicCreate({
|
||||
asset_id: asset.id,
|
||||
author_id: user.id,
|
||||
body: `hello there! ${String(Math.random()).slice(2)}`,
|
||||
body,
|
||||
richTextBody: body,
|
||||
});
|
||||
|
||||
const ctx = Context.forSystem();
|
||||
@@ -140,6 +148,7 @@ describe('graph.mutations.editComment', () => {
|
||||
asset_id: asset.id,
|
||||
edit: {
|
||||
body: newBody,
|
||||
richTextBody: newBody,
|
||||
},
|
||||
});
|
||||
expect(response.errors).to.be.empty;
|
||||
@@ -162,6 +171,7 @@ describe('graph.mutations.editComment', () => {
|
||||
asset_id: asset.id,
|
||||
edit: {
|
||||
body: newBody,
|
||||
richTextBody: newBody,
|
||||
},
|
||||
});
|
||||
if (response.errors && response.errors.length > 0) {
|
||||
@@ -182,10 +192,12 @@ describe('graph.mutations.editComment', () => {
|
||||
},
|
||||
beforeEdit: {
|
||||
body: 'I was offensive and thus REJECTED',
|
||||
richTextBody: 'I was offensive and thus REJECTED',
|
||||
status: 'REJECTED',
|
||||
},
|
||||
edit: {
|
||||
body: 'I have been edited to be less offensive',
|
||||
richTextBody: 'I have been edited to be less offensive',
|
||||
},
|
||||
error: true,
|
||||
},
|
||||
@@ -200,10 +212,12 @@ describe('graph.mutations.editComment', () => {
|
||||
},
|
||||
beforeEdit: {
|
||||
body: "I'm a perfectly acceptable comment",
|
||||
richTextBody: "I'm a perfectly acceptable comment",
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
edit: {
|
||||
body: `I have been sneakily edited to add a banned word: ${bannedWord}`,
|
||||
richTextBody: `I have been sneakily edited to add a banned word: ${bannedWord}`,
|
||||
},
|
||||
afterEdit: {
|
||||
status: 'REJECTED',
|
||||
@@ -220,10 +234,12 @@ describe('graph.mutations.editComment', () => {
|
||||
},
|
||||
beforeEdit: {
|
||||
body: `I'm a rejected comment with bad word ${bannedWord}`,
|
||||
richTextBody: `I'm a rejected comment with bad word ${bannedWord}`,
|
||||
status: 'REJECTED',
|
||||
},
|
||||
edit: {
|
||||
body: 'I have been edited to remove the bad word',
|
||||
richTextBody: 'I have been edited to remove the bad word',
|
||||
},
|
||||
error: true,
|
||||
},
|
||||
@@ -236,10 +252,13 @@ describe('graph.mutations.editComment', () => {
|
||||
},
|
||||
beforeEdit: {
|
||||
body: "I'm a perfectly acceptable comment",
|
||||
richTextBody: "I'm a perfectly acceptable comment",
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
edit: {
|
||||
body: 'I have been edited to add a link: https://coralproject.net/',
|
||||
richTextBody:
|
||||
'I have been edited to add a link: https://coralproject.net/',
|
||||
},
|
||||
afterEdit: {
|
||||
status: 'SYSTEM_WITHHELD',
|
||||
@@ -266,6 +285,7 @@ describe('graph.mutations.editComment', () => {
|
||||
asset_id: asset.id,
|
||||
edit: {
|
||||
body: newBody,
|
||||
richTextBody: newBody,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ describe('graph.queries.settings', () => {
|
||||
questionBoxIcon
|
||||
autoCloseStream
|
||||
customCssUrl
|
||||
customAdminCssUrl
|
||||
closedTimeout
|
||||
closedMessage
|
||||
charCountEnable
|
||||
|
||||
@@ -4,6 +4,7 @@ const mailer = require('../../../services/mailer');
|
||||
const Context = require('../../../graph/context');
|
||||
const timekeeper = require('timekeeper');
|
||||
const moment = require('moment');
|
||||
const uuid = require('uuid/v1');
|
||||
|
||||
const chai = require('chai');
|
||||
chai.use(require('chai-as-promised'));
|
||||
@@ -387,6 +388,64 @@ describe('services.UsersService', () => {
|
||||
expect(user.wasUpserted).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle legacy users as well', async () => {
|
||||
const ctx = Context.forSystem();
|
||||
let user = await UsersService.upsertExternalUser(
|
||||
ctx,
|
||||
'an-id',
|
||||
'a-provider',
|
||||
'a-display-name'
|
||||
);
|
||||
|
||||
expect(user).to.be.defined;
|
||||
expect(user).to.have.property('id', 'an-id');
|
||||
expect(user.wasUpserted).to.be.true;
|
||||
|
||||
// Change the ID to something else, mirroring the legacy behavior.
|
||||
const id = uuid();
|
||||
user.id = id;
|
||||
await user.save();
|
||||
expect(user).to.have.property('id', id);
|
||||
|
||||
// Ensure that the ID has been changed.
|
||||
user = await UsersService.findById(id);
|
||||
|
||||
expect(user).to.be.defined;
|
||||
expect(user).to.have.property('id', id);
|
||||
|
||||
// Test to see that future lookups work.
|
||||
user = await UsersService.upsertExternalUser(
|
||||
ctx,
|
||||
'an-id',
|
||||
'a-provider',
|
||||
'a-display-name'
|
||||
);
|
||||
|
||||
expect(user).to.be.defined;
|
||||
expect(user).to.have.property('id', id);
|
||||
expect(user.wasUpserted).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle token user lookups created via this method', async () => {
|
||||
const id = uuid();
|
||||
const ctx = Context.forSystem();
|
||||
let user = await UsersService.upsertExternalUser(
|
||||
ctx,
|
||||
id,
|
||||
'a-provider',
|
||||
'a-display-name'
|
||||
);
|
||||
|
||||
expect(user).to.be.defined;
|
||||
expect(user).to.have.property('id', id);
|
||||
expect(user.wasUpserted).to.be.true;
|
||||
|
||||
user = await UsersService.findOrCreateByIDToken(id, {});
|
||||
|
||||
expect(user).to.be.defined;
|
||||
expect(user).to.have.property('id', id);
|
||||
});
|
||||
|
||||
it('should return a user when the desired user is not found', async () => {
|
||||
const ctx = Context.forSystem();
|
||||
let user = await UsersService.upsertExternalUser(
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
{% block title %}Talk Admin{% endblock %}
|
||||
|
||||
{# Favicon Configuration #}
|
||||
{% include "partials/favicon.njk" %}
|
||||
|
||||
{% block css %}
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
|
||||
<link href="https://code.getmdl.io/1.2.1/material.min.css" rel="stylesheet">
|
||||
<link href="{{ resolve('coral-admin/bundle.css') }}" rel="stylesheet">
|
||||
|
||||
{# Custom CSS is included after the CSS block so that its overrides will apply #}
|
||||
{% include "partials/custom-admin-css.njk" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{% extends "templates/base.njk" %}
|
||||
|
||||
{% block title %}Talk - Login{% endblock %}
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<link href="{{ resolve('coral-login/bundle.css')}}" rel="stylesheet">
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{% if customAdminCssUrl %}
|
||||
<link nonce="{{ nonce }}" href="{{ customAdminCssUrl }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
@@ -6,9 +6,6 @@
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
{% block meta %}{% endblock %}
|
||||
|
||||
{# Favicon Configuration #}
|
||||
{% include "partials/favicon.njk" %}
|
||||
|
||||
{# CSP and security headers #}
|
||||
{% block security %}{% endblock %}
|
||||
|
||||
|
||||
@@ -9639,10 +9639,10 @@ passport-facebook@^2.1.1:
|
||||
dependencies:
|
||||
passport-oauth2 "1.x.x"
|
||||
|
||||
passport-google-oauth2@^0.1.6:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.1.6.tgz#dfd7016ac7449fe27cfeb252ae974afc23257a0d"
|
||||
integrity sha1-39cBasdEn+J8/rJSrpdK/CMleg0=
|
||||
passport-google-oauth2@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.2.0.tgz#fc9ea59e7091f02e24fd16d6be9257ea982ebbc3"
|
||||
integrity sha512-62EdPtbfVdc55nIXi0p1WOa/fFMM8v/M8uQGnbcXA4OexZWCnfsEi3wo2buag+Is5oqpuHzOtI64JpHk0Xi5RQ==
|
||||
dependencies:
|
||||
passport-oauth2 "^1.1.2"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user