mirror of
https://github.com/wassname/talk.git
synced 2026-09-13 13:10:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78cfb4cac8 | ||
|
|
d513e3807a | ||
|
|
faf4ad9480 | ||
|
|
e5ea1ee179 | ||
|
|
75c0bad073 | ||
|
|
8b92878e9b | ||
|
|
2b2ff7b9c4 | ||
|
|
2057b750f3 | ||
|
|
7abd703f72 | ||
|
|
6f1014289b | ||
|
|
45b778c522 | ||
|
|
7d967fc93b | ||
|
|
ae7b0fbb37 | ||
|
|
2478e9a250 | ||
|
|
fe8b6535a9 | ||
|
|
7989f87d02 | ||
|
|
769ab2a910 | ||
|
|
c052d37a6f | ||
|
|
a059e70d05 | ||
|
|
1a3401710f | ||
|
|
184f145c4a | ||
|
|
dcb2a10e72 | ||
|
|
7497e33046 | ||
|
|
330c30f1b3 | ||
|
|
e2185a33f5 | ||
|
|
d6db287c55 | ||
|
|
ca52cc3253 | ||
|
|
c2dadcc034 | ||
|
|
6865a71684 | ||
|
|
33556044a8 | ||
|
|
d883ab029c | ||
|
|
4b637a2dd5 | ||
|
|
e42c2b925d | ||
|
|
34ba2da88d | ||
|
|
cec76e0046 | ||
|
|
707d65a119 | ||
|
|
014aa2d86a | ||
|
|
94e1c75c0b | ||
|
|
3014017976 | ||
|
|
1388e0bc5e | ||
|
|
86191e388a | ||
|
|
2bf3e34218 | ||
|
|
75818cf808 | ||
|
|
503eb0b06a | ||
|
|
9cc3a57295 | ||
|
|
e42cb59661 | ||
|
|
a1a8652f7e | ||
|
|
a7b2af85fc | ||
|
|
516611e289 | ||
|
|
190010c898 | ||
|
|
2b1c9aea78 | ||
|
|
9c4a9753bb | ||
|
|
e43a3ddf75 | ||
|
|
6364eced63 | ||
|
|
62ce3a2c49 | ||
|
|
48d815aebb | ||
|
|
38ccbd0ef8 | ||
|
|
cfebe7733e | ||
|
|
e7b83c6b39 | ||
|
|
5c46884a97 | ||
|
|
4bd929f3f0 | ||
|
|
785735d36a | ||
|
|
20da16331e | ||
|
|
a95694dff6 | ||
|
|
f58f65c7ae | ||
|
|
22516457ef | ||
|
|
533d7633e1 | ||
|
|
e8fb234838 | ||
|
|
9b8ab6de5f | ||
|
|
a88644d98e | ||
|
|
745fb4056c | ||
|
|
a582773e1d | ||
|
|
6057edcb07 | ||
|
|
852466e9a1 | ||
|
|
f53db233ce | ||
|
|
31cf9a4f7d | ||
|
|
81338d1386 | ||
|
|
84bbc3d0c3 | ||
|
|
d26e331a4f | ||
|
|
e3e2e0f52e | ||
|
|
0dc3e8968a | ||
|
|
f900050ef4 | ||
|
|
0f456330c5 | ||
|
|
9d050e8b51 | ||
|
|
7581f0201d | ||
|
|
86b2ec1e56 | ||
|
|
fd1ee39db4 | ||
|
|
8c6f12c24d | ||
|
|
11a4a5ebe3 | ||
|
|
f3ecbf5dc6 | ||
|
|
116d3c0c81 | ||
|
|
3358cef433 | ||
|
|
ec93789cff | ||
|
|
f60fac651b | ||
|
|
514a549a77 | ||
|
|
88f6cad840 | ||
|
|
7615dc2aaf | ||
|
|
51bfde8cf8 | ||
|
|
5ebc39a2e1 | ||
|
|
d5020efa70 | ||
|
|
a396efef0d | ||
|
|
9618e5dab1 | ||
|
|
54296fa484 | ||
|
|
c109188efe | ||
|
|
08ead49629 | ||
|
|
bc27d7fbec | ||
|
|
4ee8cf7c96 | ||
|
|
0e15bc46d2 | ||
|
|
9bc41ad020 | ||
|
|
f1cdcb149b | ||
|
|
bf89fc2b3e | ||
|
|
7c2e56b751 | ||
|
|
4542dbdb43 | ||
|
|
787f1c68e7 | ||
|
|
09cf8c5490 | ||
|
|
ddedf4dc7e | ||
|
|
ca811a3496 | ||
|
|
10719bd874 | ||
|
|
bb23c80004 | ||
|
|
2491445579 | ||
|
|
3642c642d6 | ||
|
|
fce7b08ca0 | ||
|
|
aa9dcb4e04 |
@@ -9,7 +9,7 @@ job_defaults: &job_defaults
|
||||
working_directory: ~/coralproject/talk
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
- image: circleci/node:12
|
||||
environment:
|
||||
<<: *job_environment
|
||||
|
||||
@@ -65,11 +65,10 @@ jobs:
|
||||
name: Lint Source Code
|
||||
command: npm run lint
|
||||
- run:
|
||||
name: Lint README.md
|
||||
name: Lint Markdown
|
||||
command: |
|
||||
cp README.md README.md.orig
|
||||
npm run doctoc
|
||||
diff -q README.md README.md.orig
|
||||
git diff --exit-code
|
||||
|
||||
# unit_tests will run the unit tests.
|
||||
unit_tests:
|
||||
|
||||
@@ -102,6 +102,8 @@ let typescriptTypeCheckingOverrides = {
|
||||
},
|
||||
},
|
||||
}],
|
||||
// 28.11.19: (cvle) Disabled because behavior of regexp.exec seems different than str.match?
|
||||
"@typescript-eslint/prefer-regexp-exec": "off",
|
||||
"@typescript-eslint/require-await": "off",
|
||||
"@typescript-eslint/no-misused-promises": "off",
|
||||
"@typescript-eslint/unbound-method": "off", // 10.10.19: (cvle) seems to give false positive.
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
<!--
|
||||
Thank you for submitting a pull request! Please note that by contributing to Coral, you agree to our Code of Conduct: http://code-of-conduct.voxmedia.com/
|
||||
|
||||
Before submitting your PR, please verify that:
|
||||
Thank you for submitting a pull request! Please note that by contributing to
|
||||
Coral, you agree to our Code of Conduct: http://code-of-conduct.voxmedia.com/
|
||||
|
||||
Before submitting your Pull Request (or PR), please verify that:
|
||||
|
||||
* [ ] Your code is up-to-date with the base branch
|
||||
* [ ] You've successfully run `npm run test` locally
|
||||
|
||||
Refer to CONTRIBUTING.MD for more details.
|
||||
|
||||
https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md
|
||||
|
||||
-->
|
||||
|
||||
## What does this PR do?
|
||||
|
||||
<!--
|
||||
|
||||
In this section, you should be describing what other Github issues or tickets
|
||||
that this PR is designed to addressed.
|
||||
|
||||
Any related Github issue should be linked by adding its URL to this section.
|
||||
|
||||
-->
|
||||
|
||||
## What changes to the GraphQL/Database Schema does this PR introduce?
|
||||
|
||||
<!--
|
||||
|
||||
In this section, you should describe any changes to be made to the GraphQL
|
||||
schema file (located https://github.com/coralproject/talk/blob/master/src/core/server/graph/schema/schema.graphql) or any
|
||||
database model (located as types in the https://github.com/coralproject/talk/blob/master/src/core/server/models directory).
|
||||
|
||||
If no changes were added to the GraphQL/Database Schema as a part of this PR,
|
||||
simply write "None".
|
||||
|
||||
-->
|
||||
|
||||
## How do I test this PR?
|
||||
|
||||
<!--
|
||||
|
||||
In this section, you should be describing any manual testing that can be used to
|
||||
verify features introduced or bugs fixed in this PR.
|
||||
|
||||
-->
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"projects": {
|
||||
"tenant": {
|
||||
"schemaPath": "src/core/server/graph/tenant/schema/schema.graphql"
|
||||
"schemaPath": "src/core/server/graph/schema/schema.graphql"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+10
-9
@@ -12,10 +12,10 @@
|
||||
},
|
||||
"tslint.enable": false,
|
||||
"eslint.validate": [
|
||||
{ "language": "javascript", "autoFix": true },
|
||||
{ "language": "typescript", "autoFix": true },
|
||||
{ "language": "typescriptreact", "autoFix": true }
|
||||
],
|
||||
{ "language": "javascript", "autoFix": true },
|
||||
{ "language": "typescript", "autoFix": true },
|
||||
{ "language": "typescriptreact", "autoFix": true }
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"postcss.validate": false,
|
||||
"javascript.preferences.importModuleSpecifier": "non-relative",
|
||||
@@ -44,14 +44,15 @@
|
||||
{
|
||||
"regex": "^[.]",
|
||||
"orderLevel": 50
|
||||
},
|
||||
}
|
||||
],
|
||||
"importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.type": "newLineEachExpressionAfterCountLimitExceptIfOnlyOne",
|
||||
"importSorter.importStringConfiguration.trailingComma": "multiLine",
|
||||
"importSorter.importStringConfiguration.tabSize": 2,
|
||||
"eslint.enable": true,
|
||||
"importSorter.generalConfiguration.exclude": [
|
||||
"d\\.ts$",
|
||||
"__generated__"
|
||||
],
|
||||
"importSorter.generalConfiguration.exclude": ["d\\.ts$", "__generated__"],
|
||||
"search.exclude": {
|
||||
"package-lock.json": true
|
||||
},
|
||||
"debug.node.autoAttach": "on"
|
||||
}
|
||||
|
||||
@@ -1,31 +1,52 @@
|
||||
# Client Events Guide
|
||||
|
||||
This serves as a guide to events emitted by the javascript via the embed events
|
||||
hook, as described below in [Viewer Events](#viewer-events).
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [Viewer Events](#viewer-events)
|
||||
- [Viewer Network Events](#viewer-network-events)
|
||||
- [Event List](#event-list)
|
||||
- [Index](#index)
|
||||
- [Events](#events)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Viewer Events
|
||||
|
||||
_Viewer Events_ are emitted when the viewer performs certain actions.
|
||||
They can be subscribed to using the `events` parameter in
|
||||
`Coral.createStreamEmbed`.
|
||||
|
||||
```html
|
||||
<script>
|
||||
const CoralStreamEmbed = Coral.createStreamEmbed({
|
||||
events: function(events) {
|
||||
events.onAny(function(eventName, data) {
|
||||
console.log(eventName, data);
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
const CoralStreamEmbed = Coral.createStreamEmbed({
|
||||
events: function(events) {
|
||||
events.onAny(function(eventName, data) {
|
||||
console.log(eventName, data);
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Example events:
|
||||
|
||||
- `setMainTab {tab: "PROFILE"}`
|
||||
- `showFeaturedCommentTooltip`
|
||||
- `viewConversation {from: "FEATURED_COMMENTS", commentID: "c45fb5f5-03f9-49a3-a755-488c698ca0df"}`
|
||||
|
||||
### Viewer Network Events
|
||||
|
||||
_Viewer Network Events_ are events that involves a network request and thus can succeed or fail. Succeeding events will have a `.success` appended to the event name while failing events have an `.error` appended to the event name.
|
||||
_Viewer Network Events_ are events that involves a network request and thus can succeed or fail. Succeeding events will have a `.success` appended to the event name while failing events have an `.error` appended to the event name.
|
||||
|
||||
Moreover _Viewer Network Events_ contains the `rtt` field which indicates the time it needed from initiating the request until the _UI_ has been updated with the response data.
|
||||
|
||||
Example events:
|
||||
|
||||
```
|
||||
createComment.success
|
||||
{
|
||||
@@ -53,6 +74,7 @@ createComment.error
|
||||
```
|
||||
|
||||
## Event List
|
||||
|
||||
<!-- START docs:events -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN npm run docs:events -->
|
||||
### Index
|
||||
+497
-12
@@ -5,20 +5,60 @@ Welcome! We are very excited that you are interested in contributing to Coral.
|
||||
This document is a companion to help you approach contributing. If it does not
|
||||
do so, please [let us know how we can improve it](https://github.com/coralproject/talk/issues)!
|
||||
|
||||
By contributing to this project you agree to the
|
||||
[Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
By contributing to this project you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [What should I Contribute?](#what-should-i-contribute)
|
||||
- [Writing Code](#writing-code)
|
||||
- [When should I create an issue?](#when-should-i-create-an-issue)
|
||||
- [What should I include in my issue?](#what-should-i-include-in-my-issue)
|
||||
- [When should I create a pull request?](#when-should-i-create-a-pull-request)
|
||||
- [What should I include in my pull request?](#what-should-i-include-in-my-pull-request)
|
||||
- [Reviewing pull requests](#reviewing-pull-requests)
|
||||
- [Ensure contributions are linted and tested](#ensure-contributions-are-linted-and-tested)
|
||||
- [Review the feature/fixes](#review-the-featurefixes)
|
||||
- [Review architectural decisions](#review-architectural-decisions)
|
||||
- [Verify localizations](#verify-localizations)
|
||||
- [Localization](#localization)
|
||||
- [Documentation](#documentation)
|
||||
- [Design Principles](#design-principles)
|
||||
- [GraphQL](#graphql)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## What should I Contribute?
|
||||
|
||||
There are at least three ways to contribute to Coral:
|
||||
There are at least three different ways to contribute to Coral:
|
||||
|
||||
- Writing Code
|
||||
- Providing Translations
|
||||
- [Writing Code](#writing-code)
|
||||
- [Reviewing pull requests](#reviewing-pull-requests)
|
||||
- [Localization](#localization)
|
||||
- [Documentation](#documentation)
|
||||
|
||||
Typically these take the form of creating a Pull Request for Coral, and
|
||||
submitting it to be reviewed by a member of our team and the greater Coral
|
||||
community.
|
||||
|
||||
Working on your first Pull Request? You can learn how from this free video
|
||||
series:
|
||||
|
||||
[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
|
||||
|
||||
If you decide to fix an issue, please be sure to check the comment thread in
|
||||
case somebody is already working on a fix. If nobody is working on it at the
|
||||
moment, please leave a comment stating that you intend to work on it so other
|
||||
people don’t accidentally duplicate your effort.
|
||||
|
||||
If somebody claims an issue but doesn’t follow up for more than two weeks, it’s
|
||||
fine to take it over but you should still leave a comment.
|
||||
|
||||
## Writing Code
|
||||
|
||||
Conversation surrounding contributions begins in
|
||||
[issues](https://github.com/coralproject/talk/issues).
|
||||
Conversation surrounding contributions begins when you can create an issue
|
||||
describing your issue or suggestion.
|
||||
|
||||
### When should I create an issue?
|
||||
|
||||
@@ -26,9 +66,7 @@ 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 Coral ecosystem.
|
||||
|
||||
Please file issues if you would like to contribute to Coral.
|
||||
|
||||
### What should I include?
|
||||
### What should I include in my issue?
|
||||
|
||||
Coral has adopted an iterative, agile development philosophy. All contributions
|
||||
that make it into the Coral repository should start with a user story in this
|
||||
@@ -38,8 +76,8 @@ form:
|
||||
|
||||
This exercise does two things:
|
||||
|
||||
- allows us to ground our technical choices in a clear, simple product need.
|
||||
- expresses that product need in a way that doesn't imply a specific technical
|
||||
- Allows us to ground our technical choices in a clear, simple product need.
|
||||
- Expresses that product need in a way that doesn't imply a specific technical
|
||||
solution allowing for debate as to the best way to solve the problem.
|
||||
|
||||
Please feel free to provide as much detail as possible when filing the issue but
|
||||
@@ -49,6 +87,72 @@ technical or design solutions.
|
||||
If you have a specific technical or design solution in mind, please submit it as
|
||||
the first comment on the thread.
|
||||
|
||||
### When should I create a pull request?
|
||||
|
||||
File a pull request if you've created an issue in our [issues](https://github.com/coralproject/talk/issues)
|
||||
page and have heard back from a member or contributor to Coral. This allows our
|
||||
team to review the proposed changes prior to time being spent if the team
|
||||
already has the feature/fix in the road map.
|
||||
|
||||
### What should I include in my pull request?
|
||||
|
||||
When you create a pull request, the template will describe the required
|
||||
components needed for it to be reviewed by a member of the Coral team. You
|
||||
should end up filling out:
|
||||
|
||||
- What does this PR (pull request) do?
|
||||
- How do I test this PR?
|
||||
|
||||
You should describe what Github issue or ticket that the PR is associated with
|
||||
to assist the review process. If this PR is resolving a particular bug, a
|
||||
testing strategy should be described in the testing section. If this PR is
|
||||
contributing a new feature, a description should describe a scenario to test or
|
||||
verify the new functionality.
|
||||
|
||||
## Reviewing pull requests
|
||||
|
||||
Reviewing pull requests in Coral is generally completed by the core Coral team
|
||||
that is composed of developers employed by Vox Media Inc, but external reviews
|
||||
or suggestions are also welcomed.
|
||||
|
||||
Our review process generally follows a few core principles:
|
||||
|
||||
### Ensure contributions are linted and tested
|
||||
|
||||
It is the job of CI linting and tests to notify of style issues within the
|
||||
codebase. If it is not possible for style issues to be encapsulated as a
|
||||
linting rule, it shouldn't be concretely enforced during the review process.
|
||||
This can ensure that code reviews contain more meaningful feedback tied to the
|
||||
contribution rather than nit-picking on stylistic choices.
|
||||
|
||||
Reviewers must ensure that linting and tests pass in CI and locally prior to a
|
||||
review taking place. You can do this by running `npm run generate` followed by
|
||||
`npm run lint` and `npm run test`.
|
||||
|
||||
### Review the feature/fixes
|
||||
|
||||
Any new features added to Coral should be reviewed for bugs through a manual
|
||||
verification process to ensure that they function on your machine. If possible
|
||||
you should review any automated tests that were added (or not added) related to
|
||||
the feature.
|
||||
|
||||
While the Coral team is not strict on test driven development (or TDD), any
|
||||
contributions that include tests are greatly appreciated, and preferred over
|
||||
those that do not.
|
||||
|
||||
### Review architectural decisions
|
||||
|
||||
Any substantial changes made to the codebase should be reviewed to ensure
|
||||
that they conform to the current way code/services are laid out.
|
||||
|
||||
Architecture Decision Records (or [ADR](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)) are now being used to describe architectural decisions and can be found in the `src/docs/architecture/decisions` directory.
|
||||
|
||||
### Verify localizations
|
||||
|
||||
While we don't have any automated tools at the time of writing that will
|
||||
verify this in CI, any strings being added that are presented to the end user
|
||||
should be wrapped in localization components to support other languages.
|
||||
|
||||
## Localization
|
||||
|
||||
We use the [fluent](http://projectfluent.org/) library and store our
|
||||
@@ -64,3 +168,384 @@ where the string is used.
|
||||
|
||||
Once a language has enough coverage, it should be added to
|
||||
`src/core/common/helpers/i18n/locales.ts`.
|
||||
|
||||
The [Perspective API](https://github.com/conversationai/perspectiveapi/blob/master/2-api/methods.md#analyzecomment-request)
|
||||
also supports comments in specific languages. When the language is supported in
|
||||
Coral and supported by the Perspective API, the language should be added to the
|
||||
language map in `src/core/server/services/comments/pipeline/phases/toxic.ts`.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation that is publicly shown on
|
||||
[docs.coralproject.net](https://docs.coralproject.net/coral/) is stored under
|
||||
the `release/4` branch as it contains information for versions 4.0 onwards of
|
||||
Coral.
|
||||
|
||||
To contribute new docs, you can either click the "Suggest Edits" in the top
|
||||
right of each page, or you can edit directly via source. We suggest that for
|
||||
individual fixes or contributions to the documentation.
|
||||
|
||||
If you want to contribute via source files, you can follow the procedure
|
||||
outlined below:
|
||||
|
||||
1. Clone the Talk repository via `git clone https://github.com/coralproject/talk.git`
|
||||
2. Switch to the `release/4` branch via `git checkout release/4`
|
||||
3. Follow the procedure outlined on that branch's [CONTRIBUTING.md](https://github.com/coralproject/talk/blob/release/4/CONTRIBUTING.md#contributing-documentation) file for contributing documentation changes
|
||||
4. Create a pull request to merge your changes back into the `release/4` branch
|
||||
|
||||
## Design Principles
|
||||
|
||||
### GraphQL
|
||||
|
||||
Coral relies heavily on [GraphQL](https://graphql.org) as the query language for
|
||||
the API and the runtime on the server that powers resolving data from data
|
||||
sources. This heavily influences a lot of the decisions around how we create and
|
||||
consume it's API internally and how we expose it to others to interact with.
|
||||
|
||||
There are many GraphQL types in our [`schema.graphql`](https://github.com/coralproject/talk/blob/master/src/core/server/graph/schema/schema.graphql)
|
||||
that define the way we handle data in our API. We'll try to outline a few of
|
||||
them here with examples to help you understand their uses.
|
||||
|
||||
#### Types
|
||||
|
||||
Similar to defining an interface or a _struct_ definition, GraphQL has flexible
|
||||
types that can be used to define data types that are used for querying data from
|
||||
the API. This retrieval can happen directly via a query, or after executing an
|
||||
action using a mutation and querying its response result.
|
||||
|
||||
An example of these types is the `Comment` and its nested `CommentRevision`
|
||||
type:
|
||||
|
||||
```graphql
|
||||
"""
|
||||
Comment is a comment left by a User on an Story or another Comment as a reply.
|
||||
"""
|
||||
type Comment {
|
||||
"""
|
||||
id is the identifier of the Comment.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
body is the content of the Comment, and is an alias to the body of the
|
||||
`revision.body`.
|
||||
"""
|
||||
body: String
|
||||
|
||||
"""
|
||||
revision is the current revision of the Comment's body.
|
||||
"""
|
||||
revision: CommentRevision
|
||||
|
||||
"""
|
||||
revisionHistory stores the previous CommentRevision's, with the most recent
|
||||
edit last.
|
||||
"""
|
||||
revisionHistory: [CommentRevision!]!
|
||||
@auth(
|
||||
roles: [MODERATOR, ADMIN]
|
||||
userIDField: "author_id"
|
||||
permit: [SUSPENDED, BANNED, PENDING_DELETION]
|
||||
)
|
||||
|
||||
"""
|
||||
status represents the Comment's current status.
|
||||
"""
|
||||
status: COMMENT_STATUS!
|
||||
}
|
||||
```
|
||||
|
||||
Notice how the `Comment` type can nest more custom defined types. A `Comment`
|
||||
can have a current `CommentRevision` named `revision`. It also has a list of its
|
||||
historical `revisionHistory`:
|
||||
|
||||
```graphql
|
||||
type CommentRevision {
|
||||
"""
|
||||
id is the identifier of the CommentRevision.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
comment is the reference to the original Comment associated with the current
|
||||
Comment.
|
||||
"""
|
||||
comment: Comment!
|
||||
|
||||
"""
|
||||
actionCounts stores the counts of all the actions for the CommentRevision
|
||||
specifically.
|
||||
"""
|
||||
actionCounts: ActionCounts! @auth(roles: [MODERATOR, ADMIN])
|
||||
|
||||
"""
|
||||
body is the content of the CommentRevision. If null, it indicates that the
|
||||
body text was deleted.
|
||||
"""
|
||||
body: String
|
||||
}
|
||||
```
|
||||
|
||||
Another thing to note, see how `CommentRevision` is not only referenced by
|
||||
`revision` and `revisionHistory` on the `Comment` type. The `CommentRevision`
|
||||
also references back to its parent `Comment` via the `comment: Comment!`
|
||||
property. This is how defined types interact between each other in the GraphQL
|
||||
schema.
|
||||
|
||||
Our naming scheme is upper camel case (also known as Pascal Case) for these
|
||||
types:
|
||||
|
||||
- Start with a capital letter
|
||||
- Following characters are lower case
|
||||
- Every new word in the type name begins with a new capital letter
|
||||
- Acronyms are always capitalized (with the only exception being the
|
||||
`clientMutationId: String!` field in mutation input/payload types)
|
||||
|
||||
Some of the properties have an `!` beside their type (i.e `id: ID!`) which
|
||||
indicates that this property is required and is non-nullable. GraphQL will
|
||||
validate the input request for these properties and ensure they are provided
|
||||
during the GraphQL request.
|
||||
|
||||
You can learn more about GraphQL types in their documentation:
|
||||
|
||||
[Learn GraphQL: Schemas and Types](https://graphql.org/learn/schema/)
|
||||
|
||||
#### Enumeration Types
|
||||
|
||||
In the previous example with the `Comment` type. We also had a property called
|
||||
`status` which was of type `COMMENT_STATUS`.
|
||||
|
||||
This is another kind of defined type, an [Enumeration Type](https://graphql.org/learn/schema/#enumeration-types),
|
||||
also called _enums_.
|
||||
|
||||
```graphql
|
||||
enum COMMENT_STATUS {
|
||||
NONE
|
||||
APPROVED
|
||||
REJECTED
|
||||
PREMOD
|
||||
SYSTEM_WITHHELD
|
||||
}
|
||||
```
|
||||
|
||||
Like all enumeration types this definition enumerates out typed, named values
|
||||
that are reusable for state elsewhere on other types.
|
||||
|
||||
Our naming scheme for enumeration types and values in those types as:
|
||||
|
||||
- All capital letters
|
||||
- Spaces delimited with underscores
|
||||
|
||||
This is because they are treated as shared constant values across the schema.
|
||||
Rather than storing strings or numbers to capture selected state, we prefer
|
||||
using enumeration types because they are much more stricter in terms of value.
|
||||
|
||||
You can learn more about GraphQL Enumeration types in their documentation:
|
||||
|
||||
[Learn GraphQL: Enumeration Types](https://graphql.org/learn/schema/#enumeration-types)
|
||||
|
||||
#### Mutation Types
|
||||
|
||||
Mutations are a request to GraphQL to initiate an action which will result in a response. As such,they're broken up into an `Input` and `Payload` pair that matches a mutation's request and response pair.
|
||||
|
||||
An example is the `CreateCommentInput` and `CreateCommentPayload`:
|
||||
|
||||
```graphql
|
||||
"""
|
||||
CreateCommentInput provides the input for the createComment Mutation.
|
||||
"""
|
||||
input CreateCommentInput {
|
||||
"""
|
||||
storyID is the ID of the Story where we are creating a comment on.
|
||||
"""
|
||||
storyID: ID!
|
||||
|
||||
"""
|
||||
nudge when true will instead return an error related to recoverable moderation
|
||||
faults such as a toxic comment or spam comment to provide user feedback to
|
||||
nudge the user to correct the comment.
|
||||
"""
|
||||
nudge: Boolean = false
|
||||
|
||||
"""
|
||||
body is the Comment body, the content of the Comment.
|
||||
"""
|
||||
body: String!
|
||||
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
}
|
||||
|
||||
"""
|
||||
CreateCommentPayload contains the created Comment after the createComment
|
||||
mutation.
|
||||
"""
|
||||
type CreateCommentPayload {
|
||||
"""
|
||||
edge is the possibly created comment edge.
|
||||
"""
|
||||
edge: CommentEdge!
|
||||
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
}
|
||||
```
|
||||
|
||||
The `CreateCommentInput` type contains some parameters that allow us to execute
|
||||
the mutation. These are:
|
||||
|
||||
- `storyID` - the story we are submitting the comment to.
|
||||
- `body` - the body of our comment.
|
||||
- `nudge` - whether we should return validation prompts to the user about
|
||||
improper comment language.
|
||||
- `clientMutationId` - the identifier used by Relay to identify this mutation,
|
||||
our front-end state cache to process the mutation request. This is the only
|
||||
place in the code-base that we do not capitalize the acronym when written in
|
||||
camel-case, this is unfortunately due to legacy reasons from within Relay.
|
||||
|
||||
The returned response for a mutation is a what we call a Payload, in this case
|
||||
it's `CreateCommentPayload`. This usually has a response that is the full data
|
||||
type of whatever was modified by the earlier called mutation action. The
|
||||
properties on this type are:
|
||||
|
||||
- `edge` - we return the full comment edge that was created by the previous
|
||||
input. This is done so that the mutation request can query whatever it needs
|
||||
to from the returned input to update state on the client.
|
||||
- `clientMutationId` - an identifier used by Relay (our front-end client state
|
||||
cache) to process the mutation request.
|
||||
|
||||
#### Access Permissions On Types
|
||||
|
||||
Sometimes, you only want specific users to be allowed to view certain
|
||||
information. For instance, we have roles that are defined in our schema so we
|
||||
can filter who can have access to what.
|
||||
|
||||
These roles are used with directives on our schema that GraphQL then enforces
|
||||
when trying to resolve requests a user makes. If the user has the specified role
|
||||
associated with their signed-in account, they are given access to the requested
|
||||
data.
|
||||
|
||||
An example of this is the `revisionHistory` on the `Comment` type:
|
||||
|
||||
```graphql
|
||||
fragment on Comment {
|
||||
"""
|
||||
revisionHistory stores the previous CommentRevision's, with the most recent
|
||||
edit last.
|
||||
"""
|
||||
revisionHistory: [CommentRevision!]!
|
||||
@auth(
|
||||
roles: [MODERATOR, ADMIN]
|
||||
userIDField: "author_id"
|
||||
permit: [SUSPENDED, BANNED, PENDING_DELETION]
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Here we see the `@auth` directive. It has documentation describing the various
|
||||
parameters allowed located in the [`schema.graphql`](https://github.com/coralproject/talk/blob/master/src/core/server/graph/schema/schema.graphql)
|
||||
file, we'll discuss below what this particular set of parameters can be read as:
|
||||
|
||||
- The roles that are allowed to access this information are `MODERATOR` and
|
||||
`ADMIN` as defined by the `roles` argument.
|
||||
- We let the directive know that the author of the comment is `author_id` from
|
||||
the `Comment` by defining the `userIDField`. It's a rule of thumb in Coral if
|
||||
the Author created the document, they have permission to view it. A Comment
|
||||
for example is authored by a user, with the underlying field associated with
|
||||
the id of that author living on the `author_id` field. You can see how this
|
||||
is related if you look at the resolver for the `Comment` type to see that it
|
||||
is based off of the `Comment` interface from `src/core/server/models/comment`.
|
||||
- We permit returning comments when the author has the following conditions
|
||||
associated with their account: `SUSPENDED`, `BANNED`, or `PENDING_DELETION`.
|
||||
|
||||
These directives can be simpler, for example the `metadata` property on the
|
||||
`CommentRevision`:
|
||||
|
||||
```graphql
|
||||
fragment on CommentRevision {
|
||||
"""
|
||||
metadata stores details on a CommentRevision.
|
||||
"""
|
||||
metadata: CommentRevisionMetadata! @auth(roles: [ADMIN, MODERATOR])
|
||||
}
|
||||
```
|
||||
|
||||
Here we see an auth directive with only roles defined. This is sufficient to
|
||||
make sure that the metadata property is only accessible to `ADMIN` and
|
||||
`MODERATOR` user roles.
|
||||
|
||||
Note: Wondering how the user roles are defined? They're simply an enumeration
|
||||
type that is also defined in the schema.
|
||||
|
||||
```graphql
|
||||
enum USER_ROLE {
|
||||
COMMENTER
|
||||
STAFF
|
||||
MODERATOR
|
||||
ADMIN
|
||||
}
|
||||
```
|
||||
|
||||
#### Arrays of Items
|
||||
|
||||
Sometimes you don't want a singular property, your property is instead a
|
||||
collection of items.
|
||||
|
||||
The `revisionHistory` from the `Comment` is again useful as an example:
|
||||
|
||||
```graphql
|
||||
fragment on Comment {
|
||||
"""
|
||||
revisionHistory stores the previous CommentRevision's, with the most recent
|
||||
edit last.
|
||||
"""
|
||||
revisionHistory: [CommentRevision!]!
|
||||
}
|
||||
```
|
||||
|
||||
The interior type `CommentRevision` is required using the `!` and the outer
|
||||
array is also required using `!`.
|
||||
|
||||
We do this for a couple of reasons:
|
||||
|
||||
- This ensures that we do not return null/undefined values within the array.
|
||||
- Why would we return a null when we can just return nothing for null values?
|
||||
- We want the array to always be defined, if empty, we return and empty array (i.e. `[]`).
|
||||
- This can be handled nicely in our resolvers. We simply check if the retrieved values is null or undefined and simply return an empty array in its stead.
|
||||
|
||||
These little tweaks aren't necessary, but they ease the use of our API by making
|
||||
the results for arrays predictable and strongly typed.
|
||||
|
||||
#### Documenting
|
||||
|
||||
As you may have noticed, there is quite a bit of documentation in the schema examples listed here.
|
||||
|
||||
We typically follow these two rules in commenting our GraphQL types:
|
||||
|
||||
- Always comment the property within a type describing its purpose on its parent type
|
||||
|
||||
i.e. `createdAt` on our `Setting` type:
|
||||
|
||||
```graphql
|
||||
fragment on Setting {
|
||||
"""
|
||||
createdAt is the time that the Settings was created at.
|
||||
"""
|
||||
createdAt: Time! @auth(roles: [ADMIN])
|
||||
}
|
||||
```
|
||||
|
||||
- Always comment the purpose of each type
|
||||
|
||||
i.e. the `Comment` type:
|
||||
|
||||
```graphql
|
||||
"""
|
||||
Comment is a comment left by a User on an Story or another Comment as a reply.
|
||||
"""
|
||||
type Comment { }
|
||||
```
|
||||
|
||||
+3
-8
@@ -1,4 +1,4 @@
|
||||
FROM node:10-alpine
|
||||
FROM node:12-alpine
|
||||
|
||||
# Install build dependancies.
|
||||
RUN apk --no-cache add git python
|
||||
@@ -7,9 +7,6 @@ RUN apk --no-cache add git python
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Setup the environment for production.
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Bundle application source.
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -19,15 +16,13 @@ RUN mkdir -p dist/core/common/__generated__ && \
|
||||
echo "{\"revision\": \"${REVISION_HASH}\"}" > dist/core/common/__generated__/revision.json
|
||||
|
||||
# Install build static assets and clear caches.
|
||||
RUN NODE_ENV=development npm install && \
|
||||
npm run generate && \
|
||||
RUN npm ci && \
|
||||
npm run build && \
|
||||
npm prune --production
|
||||
|
||||
# Setup the environment
|
||||
ENV PATH /usr/src/app/bin:$PATH
|
||||
ENV NODE_ENV production
|
||||
ENV PORT 5000
|
||||
EXPOSE 5000
|
||||
ENV NODE_ENV production
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
# Webhooks Guide
|
||||
|
||||
This document is in reference to webhooks emitted by Coral. You can configure
|
||||
webhooks on your installation of Coral by visiting `/admin/configure/webhooks`.
|
||||
|
||||
Once you've configured a webhook endpoint in Coral, you will receive updates
|
||||
from Coral when those events occur. These will be in the form of `POST` requests
|
||||
with a `JSON` payload consisting of the schema represented below.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [Webhook Signing](#webhook-signing)
|
||||
- [How to verify the signature(s)](#how-to-verify-the-signatures)
|
||||
- [Schema](#schema)
|
||||
- [Events Listing](#events-listing)
|
||||
- [Events](#events)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Webhook Signing
|
||||
|
||||
Each webhook sent by Coral is signed by your webhook endpoint signing secret.
|
||||
The signature method closely resembles the signing method used by Stripe for
|
||||
their `v1` signing method. The `X-Coral-Signature` header contains one or more
|
||||
signatures prefixed by `sha256=`.
|
||||
|
||||
If you receive a signature containing multiple signatures, it is typically when
|
||||
you have rolled the signing secret from the administrative panel, and chosen to
|
||||
keep the previous secret active for a duration of time.
|
||||
|
||||
### How to verify the signature(s)
|
||||
|
||||
```js
|
||||
// Set your signing secret here from the administration panel.
|
||||
const SIGNING_SECRET = "< YOUR SIGNING SECRET HERE >";
|
||||
|
||||
// We're using crypto to verify the signatures.
|
||||
const crypto = require("crypto");
|
||||
|
||||
// We're using express to receive webhooks here.
|
||||
const app = require("express")();
|
||||
|
||||
// Use the body-parser to get the raw body as a buffer so we can use it with the
|
||||
// hashing functions.
|
||||
const parser = require("body-parser");
|
||||
|
||||
function extractEvent(body, sig) {
|
||||
// Step 1: Extract signatures from the header.
|
||||
const signatures = sig
|
||||
// Split the header by `,` to get a list of elements.
|
||||
.split(",")
|
||||
// Split each element by `=` to get a prefix and value pair.
|
||||
.map(element => element.split("="))
|
||||
// Grab all the elements with the prefix of `sha256`.
|
||||
.filter(([prefix]) => prefix === "sha256")
|
||||
// Grab the value from the prefix and value pair.
|
||||
.map(([, value]) => value);
|
||||
|
||||
// Step 2: Prepare the `signed_payload`.
|
||||
const signed_payload = body;
|
||||
|
||||
// Step 3: Calculate the expected signature.
|
||||
const expected = crypto
|
||||
.createHmac("sha256", SIGNING_SECRET)
|
||||
.update(signed_payload)
|
||||
.digest()
|
||||
.toString("hex");
|
||||
|
||||
// Step 4: Compare signatures.
|
||||
if (
|
||||
// For each of the signatures on the request...
|
||||
!signatures.some(signature =>
|
||||
// Compare the expected signature to the signature on in the header. If at
|
||||
// least one of the match, we should continue to process the event.
|
||||
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid signature");
|
||||
}
|
||||
|
||||
// Parse the JSON for the event.
|
||||
return JSON.parse(body.toString());
|
||||
}
|
||||
|
||||
app.post("/webhook", parser.raw({ type: "application/json" }), (req, res) => {
|
||||
const sig = req.headers["x-coral-signature"];
|
||||
|
||||
let event;
|
||||
|
||||
try {
|
||||
// Parse the JSON for the event.
|
||||
event = extractEvent(req.body, sig);
|
||||
} catch (err) {
|
||||
return res.status(400).send(`Webhook Error: ${err.message}`);
|
||||
}
|
||||
|
||||
// Handle the event.
|
||||
switch (event.type) {
|
||||
case "STORY_CREATED":
|
||||
const data = event.data;
|
||||
console.log(
|
||||
`A Story with ID ${data.storyID} and URL ${data.storyURL} was created!`
|
||||
);
|
||||
break;
|
||||
// ... handle other event types.
|
||||
default:
|
||||
// Unexpected event type
|
||||
return response.status(400).end();
|
||||
}
|
||||
|
||||
// Return a response to acknowledge receipt of the event
|
||||
res.json({ received: true });
|
||||
});
|
||||
|
||||
app.listen(4242, () => console.log("Running on port 4242"));
|
||||
```
|
||||
|
||||
The procedure of how to verify the signatures follows.
|
||||
|
||||
#### **Step 1**: Extract signatures from the header
|
||||
|
||||
Split the header using `,` as the separator, to get a list of elements. Then
|
||||
split each of these elements using `=` as the separator, to get a prefix and
|
||||
value pair. The value for the prefix `sha256` corresponds to the signature(s).
|
||||
|
||||
#### **Step 2**: Prepare the `signed_payload` string
|
||||
|
||||
You can do this by taking the string contents of the body (before parsing or the
|
||||
request body).
|
||||
|
||||
#### **Step 3**: Calculate the expected signature
|
||||
|
||||
Compute an HMAC signature using the SHA256 hash function. You can use the
|
||||
webhook endpoint's signing secret as the key, and the above calculated
|
||||
`signed_payload` as the message.
|
||||
|
||||
#### **Step 4**: Compare signatures
|
||||
|
||||
Compare the signature(s) in the header to the expected signature. To protect
|
||||
against timing attacks, ensure you use a constant-time string comparison
|
||||
function when comparing signatures.
|
||||
|
||||
## Schema
|
||||
|
||||
```ts
|
||||
{
|
||||
/**
|
||||
* id is the identifier for this event, each event
|
||||
* will have a unique id.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* type is the name of this event, this indicates
|
||||
* what is stored in the following `data` property.
|
||||
* Refer to the `Events List` below to see what the
|
||||
* type is for each event.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* data is the object representing this particular
|
||||
* event. Each type of event has a different shape
|
||||
* to the data property. Refer to the `Events List`
|
||||
* below to see what the data looks like for each
|
||||
* event.
|
||||
*/
|
||||
data: object;
|
||||
|
||||
/**
|
||||
* createdAt is the ISO 8601 representation of the
|
||||
* date when this event was created.
|
||||
*/
|
||||
createdAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Events Listing
|
||||
|
||||
- [`STORY_CREATED`](#story-created-event)
|
||||
|
||||
## Events
|
||||
|
||||
- <a id="story-created-event">**STORY_CREATED**</a>
|
||||
|
||||
```ts
|
||||
{
|
||||
id: string;
|
||||
type: "STORY_CREATED";
|
||||
data: {
|
||||
/**
|
||||
* storyID is the ID of the newly created Story.
|
||||
*/
|
||||
storyID: string;
|
||||
|
||||
/**
|
||||
* storyURL is the URL of the newly created Story.
|
||||
*/
|
||||
storyURL: string;
|
||||
}
|
||||
createdAt: string;
|
||||
}
|
||||
```
|
||||
+10
-18
@@ -1,28 +1,20 @@
|
||||
/**
|
||||
* This is a project wide babel configuration.
|
||||
* https://babeljs.io/docs/en/config-files#project-wide-configuration
|
||||
*
|
||||
* We use this file to apply babel configuration to packages in `node_modules`
|
||||
*/
|
||||
|
||||
const lodashOptimizations =
|
||||
// Note: If Webpack env is set, we are building for the client.
|
||||
|
||||
const plugins =
|
||||
process.env.WEBPACK === "true" ? ["use-lodash-es", "lodash"] : [];
|
||||
|
||||
const environment =
|
||||
process.env.WEBPACK === "true"
|
||||
? { targets: "IE 11", modules: false }
|
||||
: { targets: { node: "current" }, modules: "commonjs" };
|
||||
|
||||
module.exports = {
|
||||
babelrcRoots: ["./src/core/client/*"],
|
||||
env: {
|
||||
production: {
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
development: {
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
test: {
|
||||
presets: [
|
||||
["@babel/env", { targets: { node: "current" } }],
|
||||
"@babel/react",
|
||||
],
|
||||
plugins: ["dynamic-import-node"],
|
||||
},
|
||||
},
|
||||
plugins,
|
||||
presets: [["@babel/env", environment]],
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
setupFiles: [
|
||||
"<rootDir>/src/core/build/polyfills.js",
|
||||
"<rootDir>/src/core/client/test/polyfills.ts",
|
||||
"<rootDir>/src/core/client/test/setup.ts",
|
||||
],
|
||||
setupFilesAfterEnv: ["<rootDir>/src/core/client/test/setupTestFramework.ts"],
|
||||
|
||||
@@ -6,6 +6,7 @@ module.exports = {
|
||||
roots: ["<rootDir>/src"],
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
setupFilesAfterEnv: ["<rootDir>/src/core/server/test/setupTestFramework.ts"],
|
||||
testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"],
|
||||
testPathIgnorePatterns: ["/node_modules/", "/client/"],
|
||||
testEnvironment: "node",
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// Apply all the configuration provided in the .env file.
|
||||
require("dotenv").config();
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
const extensions = [".ts", ".tsx", ".js"];
|
||||
// TODO: There is some weird issue with including paths.ts here
|
||||
const postCSSConfigPath = "./src/core/build/postcss.config";
|
||||
const isProduction = process.NODE_ENV === "production";
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
|
||||
const styleLoader = {
|
||||
@@ -18,7 +16,7 @@ const styleLoader = {
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
module.exports = {
|
||||
title: "Coral 5.0",
|
||||
source: "./src",
|
||||
typescript: true,
|
||||
@@ -60,7 +58,7 @@ export default {
|
||||
"@babel/typescript",
|
||||
[
|
||||
"@babel/env",
|
||||
{ targets: { node: "10.0.0" }, modules: "commonjs" },
|
||||
{ targets: { node: "current" }, modules: "commonjs" },
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
|
||||
Generated
+3095
-1609
File diff suppressed because it is too large
Load Diff
+73
-51
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coralproject/talk",
|
||||
"version": "5.3.0",
|
||||
"version": "6.0.1",
|
||||
"author": "The Coral Project",
|
||||
"homepage": "https://coralproject.net/",
|
||||
"sideEffects": [
|
||||
@@ -11,8 +11,8 @@
|
||||
"url": "git://github.com/coralproject/talk.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=6.9.0"
|
||||
"node": "^12",
|
||||
"npm": "^6"
|
||||
},
|
||||
"bugs": "https://github.com/coralproject/talk/issues",
|
||||
"contributors": [
|
||||
@@ -25,8 +25,8 @@
|
||||
"build:client": "ts-node --transpile-only ./scripts/build.ts",
|
||||
"build:server": "gulp server",
|
||||
"migration:create": "ts-node --transpile-only ./scripts/migration/create.ts",
|
||||
"doctoc": "doctoc --title='## Table of Contents' --github README.md",
|
||||
"docs:events": "ts-node ./scripts/generateEventDocs.ts ./src/core/client/stream/events.ts ./events.md",
|
||||
"docs:events": "ts-node ./scripts/generateEventDocs.ts ./src/core/client/stream/events.ts ./CLIENT_EVENTS.md",
|
||||
"doctoc": "doctoc --maxlevel=3 --title '## Table of Contents' README.md CLIENT_EVENTS.md CONTRIBUTING.md WEBHOOKS.md",
|
||||
"generate": "npm-run-all generate:css-types generate:schema generate:relay",
|
||||
"generate-persist": "npm-run-all generate:css-types generate:schema generate:relay-persist",
|
||||
"generate:css-types": "tcm src/core/client/",
|
||||
@@ -46,9 +46,9 @@
|
||||
"lint:server": "eslint 'src/**/*.{js,ts,tsx}' --ignore-pattern 'src/core/client/**'",
|
||||
"lint:client": "eslint 'src/core/client/**/*.{js,ts,tsx}'",
|
||||
"lint:scripts": "eslint 'scripts/**/*.{js,ts,tsx}'",
|
||||
"lint:graphql": "graphql-schema-linter src/core/server/graph/tenant/schema/schema.graphql",
|
||||
"lint:graphql": "graphql-schema-linter src/core/server/graph/schema/schema.graphql",
|
||||
"lint-fix": "npm run lint:server -- --fix && npm run lint:client -- --fix && npm run lint:scripts -- --fix",
|
||||
"test": "node scripts/test.js --env=jsdom",
|
||||
"test": "node --trace-warnings scripts/test.js --env=jsdom",
|
||||
"tscheck": "npm-run-all --parallel tscheck:*",
|
||||
"tscheck:server": "tsc --project ./src/tsconfig.json --noEmit",
|
||||
"tscheck:client": "tsc --project ./src/core/client/tsconfig.json --noEmit",
|
||||
@@ -58,7 +58,10 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@coralproject/bunyan-prettystream": "^0.1.4",
|
||||
"@fluent/bundle": "^0.14.0",
|
||||
"@fluent/dom": "^0.6.0",
|
||||
"@metascraper/helpers": "^5.7.21",
|
||||
"abort-controller": "^3.0.0",
|
||||
"akismet-api": "^4.2.0",
|
||||
"apollo-server-express": "^2.8.1",
|
||||
"archiver": "^3.0.3",
|
||||
@@ -81,26 +84,27 @@
|
||||
"dotenv-expand": "^4.2.0",
|
||||
"env-rewrite": "^1.0.2",
|
||||
"express": "^4.16.3",
|
||||
"express-enforces-ssl": "^1.1.0",
|
||||
"express-static-gzip": "^0.3.2",
|
||||
"fluent": "^0.10.0",
|
||||
"fluent-dom": "^0.4.1",
|
||||
"fs-extra": "^6.0.1",
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-config": "^2.0.1",
|
||||
"graphql-extensions": "^0.2.1",
|
||||
"graphql-fields": "^1.1.0",
|
||||
"graphql-import": "^0.7.1",
|
||||
"graphql-playground-html": "^1.6.0",
|
||||
"graphql-redis-subscriptions": "^2.1.0",
|
||||
"graphql-subscriptions": "^1.1.0",
|
||||
"graphql-tools": "^3.0.5",
|
||||
"helmet": "^3.21.2",
|
||||
"html-minifier": "^3.5.21",
|
||||
"html-to-text": "^4.0.0",
|
||||
"ioredis": "^4.9.0",
|
||||
"joi": "^13.4.0",
|
||||
"jsdom": "^15.0.0",
|
||||
"jsonwebtoken": "^8.3.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"juice": "^5.2.0",
|
||||
"jwks-rsa": "^1.3.0",
|
||||
"jwks-rsa": "^1.6.0",
|
||||
"keymaster": "^1.6.2",
|
||||
"linkifyjs": "^2.1.8",
|
||||
"lodash": "^4.17.15",
|
||||
@@ -113,13 +117,13 @@
|
||||
"mongodb": "^3.2.7",
|
||||
"mongodb-core": "^3.2.7",
|
||||
"ms": "^2.1.1",
|
||||
"node-fetch": "^2.2.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"nodemailer": "^4.6.7",
|
||||
"nunjucks": "^3.1.3",
|
||||
"on-finished": "^2.3.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-google-oauth2": "^0.1.6",
|
||||
"passport": "^0.4.1",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-oauth2": "^1.4.0",
|
||||
"passport-strategy": "^1.0.0",
|
||||
@@ -127,7 +131,7 @@
|
||||
"permit": "^0.2.4",
|
||||
"prom-client": "^11.3.0",
|
||||
"proxy-agent": "^3.1.1",
|
||||
"querystringify": "^2.1.0",
|
||||
"querystringify": "^2.1.1",
|
||||
"react-helmet": "^5.2.1",
|
||||
"source-map-support": "^0.5.12",
|
||||
"stack-utils": "^1.0.2",
|
||||
@@ -139,15 +143,14 @@
|
||||
"verror": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.4.4",
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
"@babel/preset-env": "^7.4.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@babel/core": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.3",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@babel/runtime-corejs3": "^7.8.3",
|
||||
"@coralproject/npm-run-all": "^4.1.5",
|
||||
"@coralproject/rte": "^0.11.0",
|
||||
"@coralproject/rte": "^0.11.1",
|
||||
"@fluent/react": "^0.10.0",
|
||||
"@intervolga/optimize-cssnano-plugin": "^1.0.6",
|
||||
"@types/agent-base": "^4.2.0",
|
||||
"@types/archiver": "^3.0.0",
|
||||
@@ -174,17 +177,20 @@
|
||||
"@types/enzyme-adapter-react-16": "^1.0.3",
|
||||
"@types/eventemitter2": "^4.1.0",
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/express-enforces-ssl": "^1.1.1",
|
||||
"@types/flat": "0.0.28",
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/graphql": "^0.13.3",
|
||||
"@types/helmet": "0.0.45",
|
||||
"@types/html-minifier": "^3.5.2",
|
||||
"@types/html-to-text": "^1.4.31",
|
||||
"@types/html-webpack-plugin": "^3.2.0",
|
||||
"@types/ioredis": "^4.0.10",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/jest": "^24.0.23",
|
||||
"@types/jest-axe": "^3.2.1",
|
||||
"@types/joi": "^13.0.8",
|
||||
"@types/jsdom": "^12.2.3",
|
||||
"@types/jsonwebtoken": "^7.2.7",
|
||||
"@types/jsonwebtoken": "^8.3.5",
|
||||
"@types/linkifyjs": "^2.1.1",
|
||||
"@types/lodash": "^4.14.118",
|
||||
"@types/lru-cache": "^5.1.0",
|
||||
@@ -194,33 +200,36 @@
|
||||
"@types/mongodb": "^3.1.22",
|
||||
"@types/ms": "^0.7.30",
|
||||
"@types/node": "^10.5.2",
|
||||
"@types/node-fetch": "^2.3.3",
|
||||
"@types/node-fetch": "^2.5.3",
|
||||
"@types/nodemailer": "^4.6.2",
|
||||
"@types/nunjucks": "^3.1.1",
|
||||
"@types/object-diff": "0.0.0",
|
||||
"@types/on-finished": "^2.3.1",
|
||||
"@types/passport": "^0.4.6",
|
||||
"@types/passport": "^1.0.2",
|
||||
"@types/passport-facebook": "^2.1.8",
|
||||
"@types/passport-local": "^1.0.33",
|
||||
"@types/passport-oauth2": "^1.4.5",
|
||||
"@types/passport-strategy": "^0.2.33",
|
||||
"@types/permit": "^0.2.1",
|
||||
"@types/prettier": "^1.19.0",
|
||||
"@types/prop-types": "^15.5.8",
|
||||
"@types/react": "^16.8.15",
|
||||
"@types/react-axe": "^3.1.0",
|
||||
"@types/react-copy-to-clipboard": "^4.2.6",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/react-helmet": "^5.0.10",
|
||||
"@types/react-relay": "^1.3.14",
|
||||
"@types/react-relay": "^5.0.1",
|
||||
"@types/react-responsive": "^3.0.1",
|
||||
"@types/react-test-renderer": "^16.8.1",
|
||||
"@types/react-transition-group": "^2.0.14",
|
||||
"@types/recompose": "^0.26.5",
|
||||
"@types/relay-runtime": "^1.3.12",
|
||||
"@types/relay-runtime": "^5.0.5",
|
||||
"@types/sane": "^2.0.0",
|
||||
"@types/shallow-equals": "^1.0.0",
|
||||
"@types/simplemde": "^1.11.7",
|
||||
"@types/sinon": "^7.0.11",
|
||||
"@types/source-map-support": "^0.5.0",
|
||||
"@types/stack-trace": "0.0.29",
|
||||
"@types/stack-utils": "^1.0.1",
|
||||
"@types/throng": "^4.0.2",
|
||||
"@types/uuid": "^3.4.4",
|
||||
@@ -238,14 +247,12 @@
|
||||
"ansi-styles": "^3.2.0",
|
||||
"autoprefixer": "^9.5.1",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^24.8.0",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-dynamic-import-node": "^2.2.0",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"babel-plugin-module-resolver": "^3.2.0",
|
||||
"babel-plugin-relay": "^4.0.0",
|
||||
"babel-plugin-relay": "^5.0.0",
|
||||
"babel-plugin-use-lodash-es": "^0.2.0",
|
||||
"babel-preset-react-optimize": "^1.0.1",
|
||||
"bowser": "^1.9.4",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.2.0",
|
||||
"chalk": "^2.4.2",
|
||||
@@ -256,8 +263,10 @@
|
||||
"common-tags": "^1.8.0",
|
||||
"compression-webpack-plugin": "^2.0.0",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"core-js": "^3.6.4",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"css-loader": "^1.0.1",
|
||||
"css-vars-ponyfill": "^2.1.2",
|
||||
"del": "^4.1.1",
|
||||
"doctoc": "^1.4.0",
|
||||
"docz": "^0.13.7",
|
||||
@@ -273,14 +282,13 @@
|
||||
"eslint-plugin-react": "^7.15.1",
|
||||
"eventemitter2": "^5.0.1",
|
||||
"farce": "^0.2.6",
|
||||
"final-form": "4.11.0",
|
||||
"final-form": "4.18.6",
|
||||
"final-form-arrays": "1.1.2",
|
||||
"flat": "^4.1.0",
|
||||
"fluent-intl-polyfill": "^0.1.0",
|
||||
"fluent-langneg": "^0.1.1",
|
||||
"fluent-react": "^0.8.4",
|
||||
"fork-ts-checker-webpack-plugin": "^1.5.0",
|
||||
"found": "^0.4.0-alpha.17",
|
||||
"found-relay": "^0.4.0-alpha.8",
|
||||
"found": "^0.4.9",
|
||||
"found-relay": "^0.4.3",
|
||||
"graphql-schema-linter": "^0.2.0",
|
||||
"graphql-schema-typescript": "^1.2.9",
|
||||
"gulp": "^4.0.2",
|
||||
@@ -288,10 +296,11 @@
|
||||
"gulp-cli": "^2.2.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"html-webpack-plugin": "^4.0.0-beta.5",
|
||||
"html-webpack-plugin": "^4.0.0-beta.11",
|
||||
"husky": "^2.2.0",
|
||||
"intersection-observer": "^0.6.0",
|
||||
"jest": "^24.8.0",
|
||||
"jest-axe": "^3.2.0",
|
||||
"jest-junit": "^6.4.0",
|
||||
"jest-localstorage-mock": "^2.4.0",
|
||||
"jest-mock-console": "^1.0.0",
|
||||
@@ -303,6 +312,7 @@
|
||||
"mini-css-extract-plugin": "^0.6.0",
|
||||
"object-diff": "0.0.4",
|
||||
"postcss-advanced-variables": "^3.0.0",
|
||||
"postcss-calc-function": "^1.1.0",
|
||||
"postcss-css-variables": "^0.11.0",
|
||||
"postcss-flexbugs-fixes": "^4.1.0",
|
||||
"postcss-font-magician": "^2.2.1",
|
||||
@@ -315,35 +325,41 @@
|
||||
"postcss-preset-env": "^6.5.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prop-types": "^15.6.2",
|
||||
"proxy-polyfill": "^0.3.0",
|
||||
"pstree.remy": "^1.1.6",
|
||||
"pym.js": "^1.3.2",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.9.0-alpha.0",
|
||||
"react": "^16.9.0",
|
||||
"react-axe": "^3.3.0",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-dev-utils": "^9.0.0",
|
||||
"react-dom": "^16.9.0-alpha.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-error-overlay": "^5.1.6",
|
||||
"react-final-form": "4.0.2",
|
||||
"react-final-form": "6.3.0",
|
||||
"react-final-form-arrays": "3.1.0",
|
||||
"react-popper": "^1.3.2",
|
||||
"react-relay": "^4.0.0",
|
||||
"react-relay": "^5.0.0",
|
||||
"react-relay-network-modern": "^4.0.4",
|
||||
"react-responsive": "^7.0.0",
|
||||
"react-test-renderer": "^16.9.0-alpha.0",
|
||||
"react-test-renderer": "^16.9.0",
|
||||
"react-timeago": "^4.1.9",
|
||||
"react-transition-group": "^2.9.0",
|
||||
"react-with-state-props": "^2.0.4",
|
||||
"recompose": "0.27.1",
|
||||
"relay-compiler": "^4.0.0",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"relay-compiler": "^5.0.0",
|
||||
"relay-compiler-language-typescript": "^4.1.0",
|
||||
"relay-local-schema": "^0.8.0",
|
||||
"relay-runtime": "^4.0.0",
|
||||
"relay-runtime": "^5.0.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sane": "^4.1.0",
|
||||
"scheduler": "^0.15.0",
|
||||
"shallow-equals": "^1.0.0",
|
||||
"simplemde": "^1.11.2",
|
||||
"simulant": "^0.2.2",
|
||||
"sinon": "^7.3.2",
|
||||
"sockjs-client": "^1.3.0",
|
||||
"stack-trace": "0.0.10",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"subscriptions-transport-ws": "^0.9.16",
|
||||
@@ -359,11 +375,13 @@
|
||||
"tslint": "^5.20.0",
|
||||
"typed-css-modules": "^0.4.2",
|
||||
"typeface-manuale": "^0.0.71",
|
||||
"typeface-nunito": "0.0.72",
|
||||
"typeface-open-sans": "0.0.75",
|
||||
"typeface-source-sans-pro": "^0.0.54",
|
||||
"typescript": "3.3.4000",
|
||||
"typescript-snapshots-plugin": "^1.6.0",
|
||||
"wait-for-expect": "^1.1.1",
|
||||
"webpack": "^4.30.0",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-assets-manifest": "^3.1.1",
|
||||
"webpack-bundle-analyzer": "^3.3.2",
|
||||
"webpack-cli": "^3.3.2",
|
||||
@@ -379,11 +397,15 @@
|
||||
"*.{j,t}s{,x}": [
|
||||
"eslint"
|
||||
],
|
||||
"src/core/server/graph/tenant/schema/schema.graphql": [
|
||||
"src/core/server/graph/schema/schema.graphql": [
|
||||
"graphql-schema-linter"
|
||||
],
|
||||
"{src/core/client/stream/events.ts,scripts/generateEventDocs.ts,events.md}": [
|
||||
"{src/core/client/stream/events.ts,scripts/generateEventDocs.ts,CLIENT_EVENTS.md}": [
|
||||
"npm run docs:events -- --verify"
|
||||
],
|
||||
"{README,CLIENT_EVENTS,CONTRIBUTING,WEBHOOKS}.md": [
|
||||
"npm run doctoc",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"bundlesize": [
|
||||
|
||||
@@ -52,6 +52,9 @@ const args = [
|
||||
"--language",
|
||||
"typescript",
|
||||
"--no-watchman",
|
||||
"--customScalars.Time=String",
|
||||
"--customScalars.Cursor=unknown",
|
||||
"--customScalars.Locale=string",
|
||||
"--src",
|
||||
program.src,
|
||||
"--artifactDirectory",
|
||||
@@ -74,7 +77,7 @@ if (persist) {
|
||||
if (fs.existsSync(persist)) {
|
||||
// Create the new filename.
|
||||
const name = path.basename(program.src);
|
||||
const generated = "./src/core/server/graph/common/persisted/__generated__";
|
||||
const generated = "./src/core/server/graph/persisted/__generated__";
|
||||
|
||||
// Create the generated directory if it doesn't exist.
|
||||
fs.ensureDirSync(generated);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
import { codeBlock, stripIndent } from "common-tags";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import ts from "typescript";
|
||||
|
||||
interface DocEntry {
|
||||
|
||||
@@ -12,12 +12,12 @@ async function main() {
|
||||
name: "tenant",
|
||||
fileName: path.join(
|
||||
__dirname,
|
||||
"../src/core/server/graph/tenant/schema/__generated__/types.ts"
|
||||
"../src/core/server/graph/schema/__generated__/types.ts"
|
||||
),
|
||||
config: {
|
||||
contextType: "TenantContext",
|
||||
contextType: "GraphContext",
|
||||
importStatements: [
|
||||
'import TenantContext from "coral-server/graph/tenant/context";',
|
||||
'import GraphContext from "coral-server/graph/context";',
|
||||
'import { Cursor } from "coral-server/models/helpers";',
|
||||
],
|
||||
customScalarType: { Cursor: "Cursor", Time: "Date" },
|
||||
|
||||
@@ -23,9 +23,10 @@ export default class ChokidarWatcher implements Watcher {
|
||||
let firstError: Error | null = null;
|
||||
|
||||
// If this is set, a pending promise is waiting for the next result.
|
||||
let pending:
|
||||
| ({ resolve: (result: string) => void; reject: (error: Error) => void })
|
||||
| null = null;
|
||||
let pending: {
|
||||
resolve: (result: string) => void;
|
||||
reject: (error: Error) => void;
|
||||
} | null = null;
|
||||
|
||||
// Only start client if we have something to watch.
|
||||
if (paths.length) {
|
||||
|
||||
@@ -48,7 +48,7 @@ export default class SaneWatcher implements Watcher {
|
||||
const queue: string[] = [];
|
||||
|
||||
// If this is set, a pending promise is waiting for the next result.
|
||||
let pending: ({ resolve: (result: string) => void }) | null = null;
|
||||
let pending: { resolve: (result: string) => void } | null = null;
|
||||
|
||||
// Only start client if we have something to watch.
|
||||
if (paths.length) {
|
||||
|
||||
@@ -24,7 +24,8 @@ var url = require("url");
|
||||
var launchEditorEndpoint = require("react-dev-utils/launchEditorEndpoint");
|
||||
var formatWebpackMessages = require("react-dev-utils/formatWebpackMessages");
|
||||
var ErrorOverlay = require("react-error-overlay");
|
||||
var { debounce } = require("lodash");
|
||||
var lodash = require("lodash");
|
||||
var debounce = lodash.debounce;
|
||||
|
||||
ErrorOverlay.setEditorHandler(function editorHandler(errorLocation) {
|
||||
// Keep this sync with errorOverlayMiddleware.js
|
||||
@@ -124,7 +125,7 @@ function handleWarnings(warnings) {
|
||||
|
||||
// TODO: remove this workaround when we can upgrade to WebpackDevServer >= v3.3.0,
|
||||
// which includes proper `warningsFilter` support.
|
||||
warnings = warnings.filter(w => !/export .* was not found in/.test(w));
|
||||
warnings = warnings.filter(function(w){ return !/export .* was not found in/.test(w)});
|
||||
|
||||
function printWarnings() {
|
||||
// Print warnings to the console.
|
||||
@@ -198,7 +199,7 @@ function handleAvailableHash(hash) {
|
||||
mostRecentCompilationHash = hash;
|
||||
}
|
||||
|
||||
const debouncedReload = debounce(() => {
|
||||
const debouncedReload = debounce(function() {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
||||
// TODO: import form coral-common/version, for some reason this fails currently.
|
||||
// Try again when we have a chance to upgrade typescript.
|
||||
import { version } from "../common/version";
|
||||
import { Config } from "./config";
|
||||
import { createClientEnv } from "./config";
|
||||
import { Config, createClientEnv } from "./config";
|
||||
import paths from "./paths";
|
||||
|
||||
/**
|
||||
@@ -392,7 +391,7 @@ export default function createWebpackConfig(
|
||||
"@babel/typescript",
|
||||
[
|
||||
"@babel/env",
|
||||
{ targets: { node: "10.0.0" }, modules: "commonjs" },
|
||||
{ targets: { node: "current" }, modules: "commonjs" },
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
@@ -450,19 +449,11 @@ export default function createWebpackConfig(
|
||||
{
|
||||
test: /\.js$/,
|
||||
include: /node_modules\//,
|
||||
exclude: /node_modules\/(@babel|babel|core-js|regenerator-runtime)/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
// This will ensure that all packages in node_modules that
|
||||
// import lodash do so in a way that supports tree shaking.
|
||||
plugins: ["lodash"],
|
||||
presets: [
|
||||
[
|
||||
"@babel/env",
|
||||
{ targets: "last 2 versions", modules: false },
|
||||
],
|
||||
],
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
@@ -510,11 +501,6 @@ export default function createWebpackConfig(
|
||||
exclude: [/\.(js|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
loader: require.resolve("file-loader"),
|
||||
options: {
|
||||
// Because the resources loaded via CSS can sometimes be loaded
|
||||
// directly from a CSS file, this will ensure that they are
|
||||
// relative to those referencing files.
|
||||
publicPath: (loaderPublicPath: string) =>
|
||||
"../../" + loaderPublicPath,
|
||||
name: isProduction
|
||||
? "assets/media/[name].[hash:8].[ext]"
|
||||
: "assets/media/[name].[ext]",
|
||||
@@ -712,8 +698,7 @@ export default function createWebpackConfig(
|
||||
sideEffects: true,
|
||||
},
|
||||
entry: [
|
||||
/* Use minimal amount of polyfills (for IE) */
|
||||
"intersection-observer", // also for Safari
|
||||
// Polyfills are in the index.ts file.
|
||||
paths.appEmbedIndex,
|
||||
],
|
||||
output: {
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
appLoaders: resolveSrc("core/build/loaders"),
|
||||
appSrc: resolveSrc("."),
|
||||
appTsconfig: resolveSrc("core/client/tsconfig.json"),
|
||||
appPolyfill: resolveSrc("core/build/polyfills.js"),
|
||||
appPolyfill: resolveSrc("core/build/polyfills.ts"),
|
||||
appPublicPath: resolveSrc("core/build/publicPath.js"),
|
||||
appLocales: resolveSrc("locales"),
|
||||
appThemeVariables: resolveSrc("core/client/ui/theme/variables.ts"),
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
require("@babel/polyfill");
|
||||
require("intersection-observer");
|
||||
@@ -0,0 +1,6 @@
|
||||
import polyfillNodeListForEach from "../client/framework/helpers/polyfillNodeListForEach";
|
||||
|
||||
import "core-js/stable";
|
||||
import "regenerator-runtime/runtime";
|
||||
|
||||
polyfillNodeListForEach();
|
||||
@@ -7,6 +7,7 @@ const pickBy = require("lodash/pickBy");
|
||||
const flat = require("flat");
|
||||
const paths = require("./paths").default;
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const postcssCalcFunction = require("postcss-calc-function").default;
|
||||
const postcssFontMagician = require("postcss-font-magician");
|
||||
const postcssFlexbugsFixes = require("postcss-flexbugs-fixes");
|
||||
const postcssPresetEnv = require("postcss-preset-env");
|
||||
@@ -52,6 +53,8 @@ module.exports = {
|
||||
postcssNested(),
|
||||
// Sass style variables to be used in media queries.
|
||||
postcssAdvancedVariables({ variables: mediaQueryVariables }),
|
||||
// Reduce some calc()
|
||||
postcssCalcFunction(),
|
||||
// Provides a modern CSS environment.
|
||||
postcssPresetEnv(),
|
||||
// Does all the font handling logic.
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
const lodashOptimizations = ["use-lodash-es", "lodash"];
|
||||
|
||||
module.exports = {
|
||||
presets: ["@babel/react"],
|
||||
plugins: ["@babel/syntax-dynamic-import"],
|
||||
env: {
|
||||
production: {
|
||||
presets: [["@babel/env", { targets: "last 2 versions", modules: false }]],
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
development: {
|
||||
presets: [["@babel/env", { targets: "last 2 versions", modules: false }]],
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
test: {
|
||||
presets: [["@babel/env", { targets: { node: "current" } }]],
|
||||
plugins: ["@babel/transform-modules-commonjs"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
input::-ms-clear, input::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<title>Coral - Account</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import injectConditionalPolyfills from "coral-framework/helpers/injectConditionalPolyfills";
|
||||
import potentiallyInjectAxe from "coral-framework/helpers/potentiallyInjectAxe";
|
||||
import { createManaged } from "coral-framework/lib/bootstrap";
|
||||
|
||||
import App from "./App";
|
||||
@@ -11,6 +13,9 @@ import localesData from "./locales";
|
||||
import "coral-ui/theme/variables.css";
|
||||
|
||||
async function main() {
|
||||
await injectConditionalPolyfills();
|
||||
// Potentially inject react-axe for runtime a11y checks.
|
||||
await potentiallyInjectAxe();
|
||||
const ManagedCoralContextProvider = await createManaged({
|
||||
initLocalState,
|
||||
localesData,
|
||||
|
||||
@@ -3,8 +3,10 @@ import React, { FunctionComponent } from "react";
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
|
||||
const NotFound: FunctionComponent = () => (
|
||||
<HorizontalGutter>
|
||||
<Typography variant="heading3">Not Found</Typography>
|
||||
<HorizontalGutter container="main">
|
||||
<Typography variant="heading3" container="h1">
|
||||
Not Found
|
||||
</Typography>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import cn from "classnames";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Flex, HorizontalGutter, Icon, Typography } from "coral-ui/components";
|
||||
@@ -10,7 +10,7 @@ const DownloadDescription: FunctionComponent = () => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<Localized id="download-landingPage-title">
|
||||
<div className={styles.title}>Download Your Comment History</div>
|
||||
<h1 className={styles.title}>Download Your Comment History</h1>
|
||||
</Localized>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.section}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent, useCallback, useState } from "react";
|
||||
|
||||
import { Button, HorizontalGutter } from "coral-ui/components";
|
||||
|
||||
@@ -32,35 +32,35 @@ const DownloadRoute: FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<HorizontalGutter size="double">
|
||||
<Loading />
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<HorizontalGutter size="double">
|
||||
<DownloadDescription />
|
||||
<Sorry />
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<DownloadDescription />
|
||||
<DownloadForm token={token!} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { CallOut, Flex, Icon } from "coral-ui/components";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { useCallback } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
|
||||
@@ -35,36 +35,36 @@ const ConfirmRoute: React.FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Sorry reason={error} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return !finished ? (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<ConfirmForm token={token!} onSuccess={onSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Success />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { CallOut, HorizontalGutter, Typography } from "coral-ui/components";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { CallOut, HorizontalGutter, Typography } from "coral-ui/components";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { useCallback } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
|
||||
@@ -35,36 +35,36 @@ const UnsubscribeRoute: React.FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Sorry reason={error} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return !finished ? (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<UnsubscribeForm token={token!} onSuccess={onSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Success />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { useCallback } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
|
||||
|
||||
@@ -35,36 +35,36 @@ const ResetRoute: React.FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Sorry reason={error} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return !finished ? (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<ResetPasswordForm token={token!} onSuccess={onSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Success />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { CallOut, HorizontalGutter, Typography } from "coral-ui/components";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`renders form 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ConfirmRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -58,7 +58,7 @@ exports[`renders form 1`] = `
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -73,7 +73,7 @@ exports[`renders missing confirm token 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ConfirmRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -90,7 +90,9 @@ exports[`renders missing confirm token 1`] = `
|
||||
<div
|
||||
className="CallOut-root CallOut-colorError CallOut-fullWidth"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="CallOut-inner"
|
||||
>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
>
|
||||
@@ -100,7 +102,7 @@ exports[`renders missing confirm token 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`renders form 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ResetRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -115,7 +115,7 @@ Make sure it is unique and be sure to keep it secure.
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -130,7 +130,7 @@ exports[`renders missing reset token 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ResetRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -147,7 +147,9 @@ exports[`renders missing reset token 1`] = `
|
||||
<div
|
||||
className="CallOut-root CallOut-colorError CallOut-fullWidth"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="CallOut-inner"
|
||||
>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
>
|
||||
@@ -157,7 +159,7 @@ exports[`renders missing reset token 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`renders form 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="UnsubscribeRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -48,7 +48,7 @@ exports[`renders form 1`] = `
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -63,7 +63,7 @@ exports[`renders missing confirm token 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="UnsubscribeRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -80,7 +80,9 @@ exports[`renders missing confirm token 1`] = `
|
||||
<div
|
||||
className="CallOut-root CallOut-colorError CallOut-fullWidth"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="CallOut-inner"
|
||||
>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
>
|
||||
@@ -90,7 +92,7 @@ exports[`renders missing confirm token 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -31,7 +31,9 @@ it("renders missing confirm token", async () => {
|
||||
replaceHistoryLocation("http://localhost/account/email/confirm");
|
||||
const { root } = await createTestRenderer();
|
||||
await waitForElement(() => within(root).getByTestID("invalid-link"));
|
||||
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it("renders form", async () => {
|
||||
@@ -57,6 +59,7 @@ it("renders form", async () => {
|
||||
);
|
||||
});
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
@@ -98,6 +101,7 @@ it("renders error from server", async () => {
|
||||
);
|
||||
});
|
||||
restMock.verify();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ it("renders missing reset token", async () => {
|
||||
const { root } = await createTestRenderer();
|
||||
await waitForElement(() => within(root).getByTestID("invalid-link"));
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it("renders form", async () => {
|
||||
@@ -57,6 +58,7 @@ it("renders form", async () => {
|
||||
);
|
||||
});
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
@@ -98,6 +100,7 @@ it("renders error from server", async () => {
|
||||
);
|
||||
});
|
||||
restMock.verify();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ it("renders missing confirm token", async () => {
|
||||
const { root } = await createTestRenderer();
|
||||
await waitForElement(() => within(root).getByTestID("invalid-link"));
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it("renders form", async () => {
|
||||
@@ -53,6 +54,7 @@ it("renders form", async () => {
|
||||
await waitForElement(() => within(root).getByTestID("unsubscribe-form"));
|
||||
});
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
@@ -93,6 +95,7 @@ it("renders error from server", async () => {
|
||||
);
|
||||
});
|
||||
restMock.verify();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Typography } from "coral-ui/components";
|
||||
|
||||
import ApprovedIcon from "./ApprovedIcon";
|
||||
import DecisionItem from "./DecisionItem";
|
||||
import DotDivider from "./DotDivider";
|
||||
@@ -10,6 +8,7 @@ import Footer from "./Footer";
|
||||
import GoToCommentLink from "./GoToCommentLink";
|
||||
import Info from "./Info";
|
||||
import Timestamp from "./Timestamp";
|
||||
import Username from "./Username";
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
@@ -18,10 +17,6 @@ interface Props {
|
||||
onGotoComment?: React.EventHandler<React.MouseEvent>;
|
||||
}
|
||||
|
||||
const Username: FunctionComponent<{ username: string }> = ({ username }) => (
|
||||
<strong>{username}</strong>
|
||||
);
|
||||
|
||||
const ApprovedComment: FunctionComponent<Props> = props => (
|
||||
<DecisionItem icon={<ApprovedIcon />}>
|
||||
<Localized
|
||||
@@ -31,9 +26,7 @@ const ApprovedComment: FunctionComponent<Props> = props => (
|
||||
<Info>{"Approved comment by <Username></Username>"}</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<Typography variant="timestamp">
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
</Typography>
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
<DotDivider />
|
||||
<GoToCommentLink href={props.href} onClick={props.onGotoComment} />
|
||||
</Footer>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.root {
|
||||
color: var(--palette-success-main);
|
||||
color: var(--v2-palette-success-main);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Icon } from "coral-ui/components";
|
||||
import { Icon } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./ApprovedIcon.css";
|
||||
|
||||
const ApprovedIcon: FunctionComponent = () => (
|
||||
<Icon className={styles.root}>check</Icon>
|
||||
<Icon size="md" className={styles.root}>
|
||||
check_circled
|
||||
</Icon>
|
||||
);
|
||||
|
||||
export default ApprovedIcon;
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
}
|
||||
|
||||
.historyIcon {
|
||||
color: var(--palette-text-secondary);
|
||||
margin-right: calc(1.5 * var(--mini-unit));
|
||||
color: var(--v2-palette-text-secondary);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { BaseButton, ClickOutside, Icon, Popover } from "coral-ui/components";
|
||||
import {
|
||||
BaseButton,
|
||||
ClickOutside,
|
||||
Icon,
|
||||
Popover,
|
||||
} from "coral-ui/components/v2";
|
||||
|
||||
import DecisionHistoryQuery from "./DecisionHistoryQuery";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Delay, Flex, Spinner } from "coral-ui/components";
|
||||
import { Delay, Flex, Spinner } from "coral-ui/components/v2";
|
||||
|
||||
import Main from "./Main";
|
||||
import Title from "./Title";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.root {
|
||||
padding: calc(0.5 * var(--mini-unit)) calc(0.5 * var(--mini-unit))
|
||||
calc(0.5 * var(--mini-unit)) 0;
|
||||
padding: var(--v2-spacing-2) var(--v2-spacing-1);
|
||||
}
|
||||
|
||||
.leftCol {
|
||||
@@ -8,6 +7,6 @@
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: calc(3 * var(--mini-unit));
|
||||
padding-top: 3px;
|
||||
margin-right: var(--v2-spacing-2);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Flex } from "coral-ui/components";
|
||||
import { Flex } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./DecisionItem.css";
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
$decisionHistoryBorderColor: var(--v2-colors-grey-300);
|
||||
|
||||
.root {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
& > * {
|
||||
border-bottom: 1px solid var(--palette-divider);
|
||||
border-bottom: 1px solid $decisionHistoryBorderColor;
|
||||
}
|
||||
& > *:last-child {
|
||||
border-bottom: 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.root {
|
||||
height: 100%;
|
||||
font-size: calc(12rem / var(--rem-base));
|
||||
line-height: calc(12em / 12);
|
||||
color: var(--palette-divider);
|
||||
padding: 0 calc(0.5 * var(--mini-unit));
|
||||
font-size: var(--v2-font-size-1);
|
||||
line-height: var(--v2-line-height-reset);
|
||||
color: var(--v2-colors-mono-100);
|
||||
padding: 0 var(--v2-spacing-1);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
.root {
|
||||
padding: calc(1.5 * var(--mini-unit)) calc(2 * var(--mini-unit));
|
||||
}
|
||||
|
||||
.text {
|
||||
font-family: var(--v2-font-family-primary);
|
||||
font-weight: var(--v2-font-weight-primary-regular);
|
||||
font-size: var(--v2-font-size-2);
|
||||
line-height: var(--v2-line-height-body-short);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Flex, Typography } from "coral-ui/components";
|
||||
import { Flex } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./Empty.css";
|
||||
|
||||
const Empty: FunctionComponent<{}> = () => (
|
||||
<Flex justifyContent="center" alignItems="center" className={styles.root}>
|
||||
<Localized id="decisionHistory-youWillSeeAList">
|
||||
<Typography>
|
||||
<div className={styles.text}>
|
||||
You will see a list of your post moderation actions here.
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
.root {
|
||||
margin-top: var(--v2-spacing-1);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Flex } from "coral-ui/components";
|
||||
import { Flex } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./Footer.css";
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
$moderateCardDecisionHistoryLinkTextColor: var(--v2-colors-teal-700);
|
||||
|
||||
.root {
|
||||
font-size: calc(12rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
line-height: calc(14em / 12);
|
||||
letter-spacing: calc(0.2em / 12);
|
||||
color: var(--palette-primary-main);
|
||||
text-decoration: none;
|
||||
line-height: var(--v2-line-height-reset);
|
||||
font-weight: var(--v2-font-weight-primary-bold);
|
||||
font-family: var(--v2-font-family-primary);
|
||||
font-size: var(--v2-font-size-1);
|
||||
color: $moderateCardDecisionHistoryLinkTextColor;
|
||||
font-style: var(--v2-font-style-underline);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import { Link } from "found";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Icon, TextLink } from "coral-ui/components";
|
||||
import { Icon, TextLink } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./GoToCommentLink.css";
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
.root {
|
||||
font-size: calc(14rem / var(--rem-base));
|
||||
line-height: calc(16em / 12);
|
||||
/*
|
||||
Fallback begin
|
||||
|
||||
@@ -10,4 +8,9 @@
|
||||
word-break: break-all;
|
||||
/* Fallback end */
|
||||
word-break: break-word;
|
||||
color: var(--v2-palette-text-primary);
|
||||
font-weight: var(--v2-font-weight-primary-semi-bold);
|
||||
font-family: var(--v2-font-family-primary);
|
||||
line-height: var(--v2-line-height-body-short);
|
||||
font-size: var(--v2-font-size-2);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Typography } from "coral-ui/components";
|
||||
|
||||
import styles from "./Info.css";
|
||||
|
||||
interface Props {
|
||||
@@ -9,7 +7,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Info: FunctionComponent<Props> = props => (
|
||||
<Typography className={styles.root}>{props.children}</Typography>
|
||||
<span className={styles.root}>{props.children}</span>
|
||||
);
|
||||
|
||||
export default Info;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Typography } from "coral-ui/components";
|
||||
|
||||
import DecisionItem from "./DecisionItem";
|
||||
import DotDivider from "./DotDivider";
|
||||
import Footer from "./Footer";
|
||||
@@ -10,6 +8,7 @@ import GoToCommentLink from "./GoToCommentLink";
|
||||
import Info from "./Info";
|
||||
import RejectedIcon from "./RejectedIcon";
|
||||
import Timestamp from "./Timestamp";
|
||||
import Username from "./Username";
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
@@ -18,10 +17,6 @@ interface Props {
|
||||
onGotoComment?: React.EventHandler<React.MouseEvent>;
|
||||
}
|
||||
|
||||
const Username: FunctionComponent<{ username: string }> = ({ username }) => (
|
||||
<strong>{username}</strong>
|
||||
);
|
||||
|
||||
const RejectedComment: FunctionComponent<Props> = props => (
|
||||
<DecisionItem icon={<RejectedIcon />}>
|
||||
<Localized
|
||||
@@ -31,9 +26,7 @@ const RejectedComment: FunctionComponent<Props> = props => (
|
||||
<Info>{"Rejected comment by <Username></Username>"}</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<Typography variant="timestamp">
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
</Typography>
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
<DotDivider />
|
||||
<GoToCommentLink href={props.href} onClick={props.onGotoComment} />
|
||||
</Footer>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.root {
|
||||
color: var(--palette-error-main);
|
||||
color: var(--v2-palette-error-main);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Icon } from "coral-ui/components";
|
||||
import { Icon } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./RejectedIcon.css";
|
||||
|
||||
const RejectedIcon: FunctionComponent = () => (
|
||||
<Icon className={styles.root}>cancel</Icon>
|
||||
<Icon size="md" className={styles.root}>
|
||||
cancel
|
||||
</Icon>
|
||||
);
|
||||
|
||||
export default RejectedIcon;
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
$moderateCardDecisionHistoryLinkTextColor: var(--v2-colors-teal-700);
|
||||
$moderateCardDecisionHistoryBorderColor: var(--v2-colors-grey-300);
|
||||
|
||||
.root {
|
||||
font-size: var(--v2-font-size-2);
|
||||
font-weight: var(--v2-font-weight-primary-bold);
|
||||
font-family: var(--v2-font-family-primary);
|
||||
line-height: var(--v2-line-height-reset);
|
||||
text-transform: uppercase;
|
||||
font-size: calc(12rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
line-height: calc(2 * var(--mini-unit));
|
||||
letter-spacing: calc(0.2em / 12);
|
||||
color: var(--palette-grey-main);
|
||||
color: $moderateCardDecisionHistoryLinkTextColor;
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--palette-divider);
|
||||
border-top: 1px solid $moderateCardDecisionHistoryBorderColor;
|
||||
text-align: center;
|
||||
padding-top: var(--v2-spacing-1);
|
||||
padding-bottom: var(--v2-spacing-1);
|
||||
|
||||
&:disabled {
|
||||
color: var(--palette-grey-lighter);
|
||||
color: var(--v2-palette-grey-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { BaseButton } from "coral-ui/components";
|
||||
import { BaseButton } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./ShowMoreButton.css";
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
$timestampColor: var(--v2-colors-mono-100);
|
||||
|
||||
.root {
|
||||
color: var(--palette-text-secondary);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: calc(12rem / var(--rem-base));
|
||||
line-height: calc(14em / 12);
|
||||
letter-spacing: 0;
|
||||
color: $timestampColor;
|
||||
font-family: var(--v2-font-family-primary);
|
||||
font-weight: var(--v2-font-weight-primary-bold);
|
||||
font-size: var(--v2-font-size-1);
|
||||
line-height: var(--v2-line-height-reset);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { RelativeTime } from "coral-ui/components";
|
||||
import { Timestamp } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./Timestamp.css";
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const DecisionHistory: FunctionComponent<Props> = props => (
|
||||
<RelativeTime className={styles.root} date={props.children} />
|
||||
<Timestamp className={styles.root}>{props.children}</Timestamp>
|
||||
);
|
||||
|
||||
export default DecisionHistory;
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
$decisionHistoryBorderColor: var(--v2-colors-grey-300);
|
||||
$decisionHistoryTitleBackgroundColor: var(--v2-colors-grey-200);
|
||||
|
||||
.root {
|
||||
padding: 0 calc(0.5 * var(--mini-unit));
|
||||
background-color: #f2f2f2;
|
||||
color: var(--palette-text-primary);
|
||||
border-bottom: 1px solid var(--palette-grey-lighter);
|
||||
background: $decisionHistoryTitleBackgroundColor;
|
||||
padding: var(--v2-spacing-1);
|
||||
color: var(--v2-palette-text-primary);
|
||||
border-bottom: 1px solid $decisionHistoryBorderColor;
|
||||
font-size: var(--v2-font-size-icon-medium);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: var(--v2-spacing-2);
|
||||
}
|
||||
|
||||
.text {
|
||||
text-transform: uppercase;
|
||||
font-size: calc(12rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
line-height: calc(12em / 12);
|
||||
letter-spacing: calc(0.2em / 12);
|
||||
color: var(--palette-text-primary);
|
||||
font-weight: var(--v2-font-weight-primary-bold);
|
||||
color: var(--v2-palette-text-primary);
|
||||
font-size: var(--v2-font-size-2);
|
||||
line-height: 1;
|
||||
font-family: var(--v2-font-family-primary);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Flex, Icon, Typography } from "coral-ui/components";
|
||||
import { Flex, Icon } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./Title.css";
|
||||
|
||||
const Title: FunctionComponent = () => (
|
||||
<Flex className={styles.root} alignItems="center">
|
||||
<Icon>history</Icon>{" "}
|
||||
<Icon className={styles.icon} size="md">
|
||||
history
|
||||
</Icon>
|
||||
<Localized id="decisionHistory-yourDecisionHistory">
|
||||
<Typography container="span" className={styles.text}>
|
||||
Your Decision History
|
||||
</Typography>
|
||||
<span className={styles.text}>Your Decision History</span>
|
||||
</Localized>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
$moderateCardDecisionHistoryColor: var(--v2-colors-mono-900);
|
||||
|
||||
.root {
|
||||
color: $moderateCardDecisionHistoryColor;
|
||||
font-weight: var(--v2-font-weight-secondary-bold);
|
||||
font-family: var(--v2-font-family-secondary);
|
||||
line-height: var(--v2-line-height-body-short);
|
||||
font-size: var(--v2-font-size-3);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import styles from "./Username.css";
|
||||
|
||||
interface Props {
|
||||
username: string;
|
||||
}
|
||||
|
||||
const Username: FunctionComponent<Props> = ({ username }) => (
|
||||
<strong className={styles.root}>{username}</strong>
|
||||
);
|
||||
|
||||
export default Username;
|
||||
+3
-7
@@ -17,13 +17,9 @@ exports[`renders correctly 1`] = `
|
||||
</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<ForwardRef(forwardRef)
|
||||
variant="timestamp"
|
||||
>
|
||||
<DecisionHistory>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</DecisionHistory>
|
||||
</ForwardRef(forwardRef)>
|
||||
<DecisionHistory>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</DecisionHistory>
|
||||
<Footer />
|
||||
<GoToCommentLink
|
||||
href="#"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
className="ApprovedIcon-root"
|
||||
size="md"
|
||||
>
|
||||
check
|
||||
check_circled
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
@@ -9,9 +9,11 @@ exports[`renders correctly 1`] = `
|
||||
<Localized
|
||||
id="decisionHistory-youWillSeeAList"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<div
|
||||
className="Empty-text"
|
||||
>
|
||||
You will see a list of your post moderation actions here.
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
</Localized>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
<span
|
||||
className="Info-root"
|
||||
>
|
||||
children
|
||||
</ForwardRef(forwardRef)>
|
||||
</span>
|
||||
`;
|
||||
|
||||
+3
-7
@@ -17,13 +17,9 @@ exports[`renders correctly 1`] = `
|
||||
</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<ForwardRef(forwardRef)
|
||||
variant="timestamp"
|
||||
>
|
||||
<DecisionHistory>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</DecisionHistory>
|
||||
</ForwardRef(forwardRef)>
|
||||
<DecisionHistory>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</DecisionHistory>
|
||||
<Footer />
|
||||
<GoToCommentLink
|
||||
href="#"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
className="RejectedIcon-root"
|
||||
size="md"
|
||||
>
|
||||
cancel
|
||||
</ForwardRef(forwardRef)>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
<Timestamp
|
||||
className="Timestamp-root"
|
||||
date="2018-07-06T18:24:00.000Z"
|
||||
/>
|
||||
toggleAbsolute={true}
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</Timestamp>
|
||||
`;
|
||||
|
||||
@@ -5,19 +5,20 @@ exports[`renders correctly 1`] = `
|
||||
alignItems="center"
|
||||
className="Title-root"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<ForwardRef(forwardRef)
|
||||
className="Title-icon"
|
||||
size="md"
|
||||
>
|
||||
history
|
||||
</ForwardRef(forwardRef)>
|
||||
|
||||
<Localized
|
||||
id="decisionHistory-yourDecisionHistory"
|
||||
>
|
||||
<ForwardRef(forwardRef)
|
||||
<span
|
||||
className="Title-text"
|
||||
container="span"
|
||||
>
|
||||
Your Decision History
|
||||
</ForwardRef(forwardRef)>
|
||||
</span>
|
||||
</Localized>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, { createContext, ReactNode, useMemo, useReducer } from "react";
|
||||
|
||||
interface State {
|
||||
message: ReactNode | null;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: "SET_MESSAGE";
|
||||
} & State
|
||||
| {
|
||||
type: "CLEAR_MESSAGE";
|
||||
};
|
||||
|
||||
// TODO (tessalt) can't figure out types for this
|
||||
const NotificationContext = createContext<State | any>({
|
||||
message: null,
|
||||
visible: false,
|
||||
});
|
||||
|
||||
function notificationReducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case "SET_MESSAGE": {
|
||||
return { message: action.message, visible: true };
|
||||
}
|
||||
case "CLEAR_MESSAGE": {
|
||||
return { message: null, visible: false };
|
||||
}
|
||||
default: {
|
||||
throw new Error("unsupported action");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function NotificationProvider(props: any) {
|
||||
const [state, dispatch] = useReducer(notificationReducer, {
|
||||
message: null,
|
||||
visible: false,
|
||||
});
|
||||
const value = useMemo(() => [state, dispatch], [state]);
|
||||
return <NotificationContext.Provider value={value} {...props} />;
|
||||
}
|
||||
|
||||
export { NotificationProvider, NotificationContext };
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import useNotification from "./useNotification";
|
||||
|
||||
const NotificationContainer: FunctionComponent<{}> = () => {
|
||||
const { state } = useNotification();
|
||||
if (!state.visible) {
|
||||
return null;
|
||||
}
|
||||
return <div role="status">{state.message}</div>;
|
||||
};
|
||||
|
||||
export default NotificationContainer;
|
||||
@@ -0,0 +1,3 @@
|
||||
export { NotificationProvider } from "./GlobalNotificationContext";
|
||||
export { default as NotificationContainer } from "./NotificationContainer";
|
||||
export { default as useNotification } from "./useNotification";
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useRouter } from "found";
|
||||
import { ReactNode, useContext } from "react";
|
||||
|
||||
import { NotificationContext } from "./GlobalNotificationContext";
|
||||
|
||||
function useNotification() {
|
||||
const context = useContext(NotificationContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useNotification must be used within a NotificationProvider"
|
||||
);
|
||||
}
|
||||
const [state, dispatch] = context;
|
||||
|
||||
const { router } = useRouter();
|
||||
|
||||
const setMessage = (message: ReactNode, timeout?: number) => {
|
||||
dispatch({ type: "SET_MESSAGE", message });
|
||||
if (timeout) {
|
||||
setTimeout(() => {
|
||||
dispatch({ type: "CLEAR_MESSAGE" });
|
||||
}, timeout);
|
||||
}
|
||||
router.addTransitionHook(() => {
|
||||
dispatch({ type: "CLEAR_MESSAGE" });
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
const clearMessage = () => dispatch({ type: "CLEAR_MESSAGE" });
|
||||
return {
|
||||
state,
|
||||
dispatch,
|
||||
setMessage,
|
||||
clearMessage,
|
||||
};
|
||||
}
|
||||
|
||||
export default useNotification;
|
||||
@@ -3,6 +3,9 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
input::-ms-clear, input::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
|
||||
@@ -2,9 +2,13 @@ import React, { FunctionComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "coral-framework/types";
|
||||
import { LogoHorizontal } from "coral-ui/components";
|
||||
import { AppBar, Begin, Divider, End } from "coral-ui/components/AppBar";
|
||||
import { AppBar, Begin, Divider, End } from "coral-ui/components/v2/AppBar";
|
||||
|
||||
import { DecisionHistoryButton } from "./DecisionHistory";
|
||||
import {
|
||||
NotificationContainer,
|
||||
NotificationProvider,
|
||||
} from "./GlobalNotification";
|
||||
import NavigationContainer from "./Navigation";
|
||||
import UserMenuContainer from "./UserMenu";
|
||||
import Version from "./Version";
|
||||
@@ -32,7 +36,10 @@ const Main: FunctionComponent<Props> = ({ children, viewer }) => (
|
||||
<UserMenuContainer viewer={viewer} />
|
||||
</End>
|
||||
</AppBar>
|
||||
{children}
|
||||
<NotificationProvider>
|
||||
<NotificationContainer />
|
||||
{children}
|
||||
</NotificationProvider>
|
||||
<Version />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { AppBarNavigation } from "coral-ui/components";
|
||||
import { AppBarNavigation } from "coral-ui/components/v2";
|
||||
|
||||
import NavigationLink from "./NavigationLink";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Link, LocationDescriptor } from "found";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { AppBarNavigationItem } from "coral-ui/components";
|
||||
import { AppBarNavigationItem } from "coral-ui/components/v2";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const NavigationLink: FunctionComponent<Props> = props => (
|
||||
<Link to={props.to} as={AppBarNavigationItem} activePropName="active">
|
||||
<Link to={props.to} as={AppBarNavigationItem as any} activePropName="active">
|
||||
{props.children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user