mirror of
https://github.com/wassname/talk.git
synced 2026-09-13 13:10:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9e9c016f4b | ||
|
|
40b247350b | ||
|
|
2945184b07 | ||
|
|
a55472e086 | ||
|
|
033433549a | ||
|
|
2ef07429c2 | ||
|
|
b83d737530 | ||
|
|
18346d1683 | ||
|
|
ce4a3408fc | ||
|
|
2dbba52fbd | ||
|
|
59b8dfccda | ||
|
|
3394628f27 | ||
|
|
8ee74b7d24 | ||
|
|
f85af8e88f | ||
|
|
9d6592feef | ||
|
|
53c87118db | ||
|
|
bdc9c5f05c | ||
|
|
e334c3266a | ||
|
|
a31ee3bc96 | ||
|
|
a7eb757be7 | ||
|
|
4038fcbcd6 | ||
|
|
5714f441bb |
@@ -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,15 +1,17 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
about: Create a bug report to help us improve Coral
|
||||
labels: "bug"
|
||||
---
|
||||
|
||||
<!--
|
||||
Please provide as much detail as possible, including a screenshot or copy-paste of any related error messages, logs, or other output that might be related. Places to look for information include your browser console, server console, and network logs. The more information you can give the better.
|
||||
Please note that but submitting a bug report, you agree to our Code of Conduct: http://code-of-conduct.voxmedia.com/
|
||||
|
||||
When submitting a bug report, provide as much detail as possible, including a screenshot or copy-paste of any related error messages, logs, or other output that might be related. Places to look for information include your browser console, server console, and network logs. The more information you can give the better.
|
||||
|
||||
Please help us by doing the following steps before logging an issue:
|
||||
* Search: https://github.com/coralproject/talk/search
|
||||
* Read the docs: https://docs.coralproject.net
|
||||
* Search for other similar issues: https://github.com/coralproject/talk/search
|
||||
* Read our docs: https://docs.coralproject.net
|
||||
|
||||
Please fill in the *entire* template below.
|
||||
-->
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
about: Suggest a feature idea for Coral
|
||||
labels: "feature idea"
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Please note that but submitting a feature request, you agree to our Code of Conduct: http://code-of-conduct.voxmedia.com/
|
||||
|
||||
Please help us by doing the following steps before logging an issue:
|
||||
* Search: https://github.com/coralproject/talk/search
|
||||
* Search to see if there are similar requests and ideas that are already logged: https://github.com/coralproject/talk/search
|
||||
|
||||
-->
|
||||
|
||||
|
||||
@@ -1,10 +1,48 @@
|
||||
<!--
|
||||
Thank you for submitting a pull request!
|
||||
|
||||
Please verify that:
|
||||
* [ ] Code is up-to-date with the base branch
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
# 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>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
{
|
||||
body: "Hello world!",
|
||||
storyID: "238b95ec-2b80-43f4-ab68-a6ea1f4e2584",
|
||||
rtt: 307,
|
||||
success: {
|
||||
id: "6fecfb11-4d0f-4edc-89b7-878a9928addd"
|
||||
status: "APPROVED"`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
createComment.error
|
||||
{
|
||||
body: "Hi!",
|
||||
storyID: "238b95ec-2b80-43f4-ab68-a6ea1f4e2584",
|
||||
rtt: 229,
|
||||
error: {
|
||||
code: "COMMENT_BODY_TOO_SHORT"
|
||||
message: "Comment body must have at least 10 characters."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event List
|
||||
|
||||
<!-- START docs:events -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN npm run docs:events -->
|
||||
### Index
|
||||
- <a href="#approveComment">approveComment</a>
|
||||
- <a href="#banUser">banUser</a>
|
||||
- <a href="#cancelAccountDeletion">cancelAccountDeletion</a>
|
||||
- <a href="#changeEmail">changeEmail</a>
|
||||
- <a href="#changePassword">changePassword</a>
|
||||
- <a href="#changeUsername">changeUsername</a>
|
||||
- <a href="#closeStory">closeStory</a>
|
||||
- <a href="#copyPermalink">copyPermalink</a>
|
||||
- <a href="#createComment">createComment</a>
|
||||
- <a href="#createCommentFocus">createCommentFocus</a>
|
||||
- <a href="#createCommentReaction">createCommentReaction</a>
|
||||
- <a href="#createCommentReply">createCommentReply</a>
|
||||
- <a href="#editComment">editComment</a>
|
||||
- <a href="#featureComment">featureComment</a>
|
||||
- <a href="#gotoModeration">gotoModeration</a>
|
||||
- <a href="#ignoreUser">ignoreUser</a>
|
||||
- <a href="#loadMoreAllComments">loadMoreAllComments</a>
|
||||
- <a href="#loadMoreFeaturedComments">loadMoreFeaturedComments</a>
|
||||
- <a href="#loadMoreHistoryComments">loadMoreHistoryComments</a>
|
||||
- <a href="#loginPrompt">loginPrompt</a>
|
||||
- <a href="#openSortMenu">openSortMenu</a>
|
||||
- <a href="#openStory">openStory</a>
|
||||
- <a href="#rejectComment">rejectComment</a>
|
||||
- <a href="#removeCommentReaction">removeCommentReaction</a>
|
||||
- <a href="#removeUserIgnore">removeUserIgnore</a>
|
||||
- <a href="#replyCommentFocus">replyCommentFocus</a>
|
||||
- <a href="#reportComment">reportComment</a>
|
||||
- <a href="#requestAccountDeletion">requestAccountDeletion</a>
|
||||
- <a href="#requestDownloadCommentHistory">requestDownloadCommentHistory</a>
|
||||
- <a href="#resendEmailVerification">resendEmailVerification</a>
|
||||
- <a href="#setCommentsOrderBy">setCommentsOrderBy</a>
|
||||
- <a href="#setCommentsTab">setCommentsTab</a>
|
||||
- <a href="#setMainTab">setMainTab</a>
|
||||
- <a href="#setProfileTab">setProfileTab</a>
|
||||
- <a href="#showAbsoluteTimestamp">showAbsoluteTimestamp</a>
|
||||
- <a href="#showAllReplies">showAllReplies</a>
|
||||
- <a href="#showAuthPopup">showAuthPopup</a>
|
||||
- <a href="#showEditEmailDialog">showEditEmailDialog</a>
|
||||
- <a href="#showEditForm">showEditForm</a>
|
||||
- <a href="#showEditPasswordDialog">showEditPasswordDialog</a>
|
||||
- <a href="#showEditUsernameDialog">showEditUsernameDialog</a>
|
||||
- <a href="#showFeaturedCommentTooltip">showFeaturedCommentTooltip</a>
|
||||
- <a href="#showIgnoreUserdDialog">showIgnoreUserdDialog</a>
|
||||
- <a href="#showModerationPopover">showModerationPopover</a>
|
||||
- <a href="#showMoreOfConversation">showMoreOfConversation</a>
|
||||
- <a href="#showMoreReplies">showMoreReplies</a>
|
||||
- <a href="#showReplyForm">showReplyForm</a>
|
||||
- <a href="#showReportPopover">showReportPopover</a>
|
||||
- <a href="#showSharePopover">showSharePopover</a>
|
||||
- <a href="#showUserPopover">showUserPopover</a>
|
||||
- <a href="#signOut">signOut</a>
|
||||
- <a href="#unfeatureComment">unfeatureComment</a>
|
||||
- <a href="#updateNotificationSettings">updateNotificationSettings</a>
|
||||
- <a href="#updateStorySettings">updateStorySettings</a>
|
||||
- <a href="#viewConversation">viewConversation</a>
|
||||
- <a href="#viewFullDiscussion">viewFullDiscussion</a>
|
||||
- <a href="#viewNewComments">viewNewComments</a>
|
||||
|
||||
### Events
|
||||
- <a id="approveComment">**approveComment.success**, **approveComment.error**</a>: This event is emitted when the viewer approves a comment.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="banUser">**banUser.success**, **banUser.error**</a>: This event is emitted when the viewer bans a user.
|
||||
```ts
|
||||
{
|
||||
userID: string;
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="cancelAccountDeletion">**cancelAccountDeletion.success**, **cancelAccountDeletion.error**</a>: This event is emitted when the viewer cancels the account deletion.
|
||||
```ts
|
||||
{
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="changeEmail">**changeEmail.success**, **changeEmail.error**</a>: This event is emitted when the viewer changes its email.
|
||||
```ts
|
||||
{
|
||||
oldEmail: string;
|
||||
newEmail: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="changePassword">**changePassword.success**, **changePassword.error**</a>: This event is emitted when the viewer changes its password.
|
||||
```ts
|
||||
{
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="changeUsername">**changeUsername.success**, **changeUsername.error**</a>: This event is emitted when the viewer changes its username.
|
||||
```ts
|
||||
{
|
||||
oldUsername: string;
|
||||
newUsername: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="closeStory">**closeStory.success**, **closeStory.error**</a>: This event is emitted when the viewer closes the story.
|
||||
```ts
|
||||
{
|
||||
storyID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="copyPermalink">**copyPermalink**</a>: This event is emitted when the viewer copies the permalink with the button.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="createComment">**createComment.success**, **createComment.error**</a>: This event is emitted when a top level comment is created.
|
||||
```ts
|
||||
{
|
||||
storyID: string;
|
||||
body: string;
|
||||
success: {
|
||||
id: string;
|
||||
status: COMMENT_STATUS;
|
||||
};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="createCommentFocus">**createCommentFocus**</a>: This event is emitted when the viewer focus on the RTE to create a comment.
|
||||
- <a id="createCommentReaction">**createCommentReaction.success**, **createCommentReaction.error**</a>: This event is emitted when the viewer reacts to a comment.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="createCommentReply">**createCommentReply.success**, **createCommentReply.error**</a>: This event is emitted when a comment reply is created.
|
||||
```ts
|
||||
{
|
||||
body: string;
|
||||
parentID: string;
|
||||
success: {
|
||||
id: string;
|
||||
status: COMMENT_STATUS;
|
||||
};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="editComment">**editComment.success**, **editComment.error**</a>: This event is emitted when the viewer edits a comment.
|
||||
```ts
|
||||
{
|
||||
body: string;
|
||||
commentID: string;
|
||||
success: {
|
||||
status: COMMENT_STATUS;
|
||||
};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="featureComment">**featureComment.success**, **featureComment.error**</a>: This event is emitted when the viewer features a comment.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="gotoModeration">**gotoModeration**</a>: This event is emitted when the viewer goes to moderation.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="ignoreUser">**ignoreUser.success**, **ignoreUser.error**</a>: This event is emitted when the viewer ignores a user.
|
||||
```ts
|
||||
{
|
||||
userID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="loadMoreAllComments">**loadMoreAllComments.success**, **loadMoreAllComments.error**</a>: This event is emitted when the viewer loads more top level comments into the comment stream.
|
||||
```ts
|
||||
{
|
||||
storyID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="loadMoreFeaturedComments">**loadMoreFeaturedComments.success**, **loadMoreFeaturedComments.error**</a>: This event is emitted when the viewer loads more featured comments.
|
||||
```ts
|
||||
{
|
||||
storyID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="loadMoreHistoryComments">**loadMoreHistoryComments.success**, **loadMoreHistoryComments.error**</a>: This event is emitted when the viewer loads more top level comments into the history comment stream.
|
||||
```ts
|
||||
{
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="loginPrompt">**loginPrompt**</a>: This event is emitted when the viewer does an action that will prompt a login dialog.
|
||||
- <a id="openSortMenu">**openSortMenu**</a>: This event is emitted when the viewer clicks on the sort menu.
|
||||
- <a id="openStory">**openStory.success**, **openStory.error**</a>: This event is emitted when the viewer opens the story.
|
||||
```ts
|
||||
{
|
||||
storyID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="rejectComment">**rejectComment.success**, **rejectComment.error**</a>: This event is emitted when the viewer rejects a comment.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="removeCommentReaction">**removeCommentReaction.success**, **removeCommentReaction.error**</a>: This event is emitted when the viewer removes its reaction from a comment.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="removeUserIgnore">**removeUserIgnore.success**, **removeUserIgnore.error**</a>: This event is emitted when the viewer remove a user from its ignored users list.
|
||||
```ts
|
||||
{
|
||||
userID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="replyCommentFocus">**replyCommentFocus**</a>: This event is emitted when the viewer focus on the RTE to reply to a comment.
|
||||
- <a id="reportComment">**reportComment.success**, **reportComment.error**</a>: This event is emitted when the viewer reports a comment.
|
||||
```ts
|
||||
{
|
||||
reason: string;
|
||||
commentID: string;
|
||||
additionalDetails?: string | undefined;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="requestAccountDeletion">**requestAccountDeletion.success**, **requestAccountDeletion.error**</a>: This event is emitted when the viewer requests to delete its account.
|
||||
```ts
|
||||
{
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="requestDownloadCommentHistory">**requestDownloadCommentHistory.success**, **requestDownloadCommentHistory.error**</a>: This event is emitted when the viewer requests to download its comment history.
|
||||
```ts
|
||||
{
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="resendEmailVerification">**resendEmailVerification.success**, **resendEmailVerification.error**</a>: This event is emitted when the viewer request another email verification email.
|
||||
```ts
|
||||
{
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="setCommentsOrderBy">**setCommentsOrderBy**</a>: This event is emitted when the viewer changes the sort order of the comments.
|
||||
```ts
|
||||
{
|
||||
orderBy: string;
|
||||
}
|
||||
```
|
||||
- <a id="setCommentsTab">**setCommentsTab**</a>: This event is emitted when the viewer changes the tab of the comments tab bar.
|
||||
```ts
|
||||
{
|
||||
tab: string;
|
||||
}
|
||||
```
|
||||
- <a id="setMainTab">**setMainTab**</a>: This event is emitted when the viewer changes the tab of the main tab bar.
|
||||
```ts
|
||||
{
|
||||
tab: string;
|
||||
}
|
||||
```
|
||||
- <a id="setProfileTab">**setProfileTab**</a>: This event is emitted when the viewer changes the tab of the profile tab bar.
|
||||
```ts
|
||||
{
|
||||
tab: string;
|
||||
}
|
||||
```
|
||||
- <a id="showAbsoluteTimestamp">**showAbsoluteTimestamp**</a>: This event is emitted when the viewer clicks on the relative timestamp to show the absolute time.
|
||||
- <a id="showAllReplies">**showAllReplies.success**, **showAllReplies.error**</a>: This event is emitted when the viewer reveals all replies of a comment.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="showAuthPopup">**showAuthPopup**</a>: This event is emitted when the viewer requests the auth popup.
|
||||
```ts
|
||||
{
|
||||
view: string;
|
||||
}
|
||||
```
|
||||
- <a id="showEditEmailDialog">**showEditEmailDialog**</a>: This event is emitted when the viewer opens the edit email dialog.
|
||||
- <a id="showEditForm">**showEditForm**</a>: This event is emitted when the viewer opens the edit form.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="showEditPasswordDialog">**showEditPasswordDialog**</a>: This event is emitted when the viewer opens the edit password dialog.
|
||||
- <a id="showEditUsernameDialog">**showEditUsernameDialog**</a>: This event is emitted when the viewer opens the edit username dialog.
|
||||
- <a id="showFeaturedCommentTooltip">**showFeaturedCommentTooltip**</a>: This event is emitted when the viewer clicks to show the featured comment tooltip.
|
||||
- <a id="showIgnoreUserdDialog">**showIgnoreUserdDialog**</a>: This event is emitted when the viewer opens the ignore user dialog.
|
||||
- <a id="showModerationPopover">**showModerationPopover**</a>: This event is emitted when the viewer opens the moderation popover.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="showMoreOfConversation">**showMoreOfConversation.success**, **showMoreOfConversation.error**</a>: This event is emitted when the viewer reveals more of the parent conversation thread.
|
||||
```ts
|
||||
{
|
||||
commentID: string | null;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="showMoreReplies">**showMoreReplies**</a>: This event is emitted when the viewer reveals new live replies.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
count: number;
|
||||
}
|
||||
```
|
||||
- <a id="showReplyForm">**showReplyForm**</a>: This event is emitted when the viewer opens the reply form.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="showReportPopover">**showReportPopover**</a>: This event is emitted when the viewer opens the report popover.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="showSharePopover">**showSharePopover**</a>: This event is emitted when the viewer opens the share popover.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="showUserPopover">**showUserPopover**</a>: This event is emitted when the viewer clicks on a username which shows the user popover.
|
||||
```ts
|
||||
{
|
||||
userID: string;
|
||||
}
|
||||
```
|
||||
- <a id="signOut">**signOut.success**, **signOut.error**</a>: This event is emitted when the viewer signs out.
|
||||
```ts
|
||||
{
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="unfeatureComment">**unfeatureComment.success**, **unfeatureComment.error**</a>: This event is emitted when the viewer unfeatures a comment.
|
||||
```ts
|
||||
{
|
||||
commentID: string;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="updateNotificationSettings">**updateNotificationSettings.success**, **updateNotificationSettings.error**</a>: This event is emitted when the viewer updates its notification settings.
|
||||
```ts
|
||||
{
|
||||
onReply?: boolean | null | undefined;
|
||||
onFeatured?: boolean | null | undefined;
|
||||
onStaffReplies?: boolean | null | undefined;
|
||||
onModeration?: boolean | null | undefined;
|
||||
digestFrequency?: "NONE" | "DAILY" | "HOURLY" | null | undefined;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="updateStorySettings">**updateStorySettings.success**, **updateStorySettings.error**</a>: This event is emitted when the viewer updates the story settings.
|
||||
```ts
|
||||
{
|
||||
storyID: string;
|
||||
live?: {
|
||||
enabled?: boolean | null | undefined;
|
||||
} | null | undefined;
|
||||
moderation?: "POST" | "PRE" | null | undefined;
|
||||
premodLinksEnable?: boolean | null | undefined;
|
||||
messageBox?: {
|
||||
enabled?: boolean | null | undefined;
|
||||
icon?: string | null | undefined;
|
||||
content?: string | null | undefined;
|
||||
} | null | undefined;
|
||||
success: {};
|
||||
error: {
|
||||
message: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="viewConversation">**viewConversation**</a>: This event is emitted when the viewer changes to the single conversation view.
|
||||
```ts
|
||||
{
|
||||
from: "FEATURED_COMMENTS" | "COMMENT_STREAM" | "COMMENT_HISTORY";
|
||||
commentID: string;
|
||||
}
|
||||
```
|
||||
- <a id="viewFullDiscussion">**viewFullDiscussion**</a>: This event is emitted when the viewer exits the single conversation.
|
||||
```ts
|
||||
{
|
||||
commentID: string | null;
|
||||
}
|
||||
```
|
||||
- <a id="viewNewComments">**viewNewComments**</a>: This event is emitted when the viewer reveals new live comments.
|
||||
```ts
|
||||
{
|
||||
storyID: string;
|
||||
count: number;
|
||||
}
|
||||
```
|
||||
<!-- END docs:events -->
|
||||
+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"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Coral 
|
||||
# Coral 
|
||||
|
||||
Online comments are broken. Our open-source commenting platform, Coral, rethinks
|
||||
how moderation, comment display, and conversation function, creating the
|
||||
|
||||
+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
+3745
-1832
File diff suppressed because it is too large
Load Diff
+83
-56
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coralproject/talk",
|
||||
"version": "5.2.1",
|
||||
"version": "6.0.0",
|
||||
"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,7 +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 ./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/",
|
||||
@@ -45,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",
|
||||
@@ -57,6 +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",
|
||||
@@ -74,59 +79,59 @@
|
||||
"cron": "^1.7.1",
|
||||
"csv-stringify": "^5.3.0",
|
||||
"dataloader": "^1.4.0",
|
||||
"dompurify": "^1.0.8",
|
||||
"dompurify": "^2.0.7",
|
||||
"dotenv": "^6.0.0",
|
||||
"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",
|
||||
"lru-cache": "^5.1.1",
|
||||
"luxon": "^1.12.0",
|
||||
"metascraper-author": "^3.11.8",
|
||||
"metascraper-date": "^3.11.4",
|
||||
"metascraper-description": "^3.11.8",
|
||||
"metascraper-image": "^3.11.8",
|
||||
"metascraper-title": "^3.11.8",
|
||||
"metascraper-author": "^5.8.0",
|
||||
"metascraper-description": "^5.8.0",
|
||||
"metascraper-image": "^5.8.0",
|
||||
"metascraper-title": "^5.8.0",
|
||||
"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",
|
||||
"performance-now": "^2.1.0",
|
||||
"permit": "^0.2.4",
|
||||
"prom-client": "^11.3.0",
|
||||
"proxy-agent": "^3.1.0",
|
||||
"querystringify": "^2.1.0",
|
||||
"proxy-agent": "^3.1.1",
|
||||
"querystringify": "^2.1.1",
|
||||
"react-helmet": "^5.2.1",
|
||||
"source-map-support": "^0.5.12",
|
||||
"stack-utils": "^1.0.2",
|
||||
@@ -138,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.10.15",
|
||||
"@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",
|
||||
@@ -167,22 +171,26 @@
|
||||
"@types/cors": "^2.8.4",
|
||||
"@types/cron": "^1.7.1",
|
||||
"@types/cross-spawn": "^6.0.0",
|
||||
"@types/dompurify": "0.0.33",
|
||||
"@types/dotenv": "^4.0.3",
|
||||
"@types/enzyme": "^3.1.15",
|
||||
"@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",
|
||||
@@ -192,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",
|
||||
@@ -236,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",
|
||||
@@ -254,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",
|
||||
@@ -271,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",
|
||||
@@ -286,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",
|
||||
@@ -301,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",
|
||||
@@ -313,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",
|
||||
@@ -357,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",
|
||||
@@ -377,8 +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,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);
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
import { codeBlock, stripIndent } from "common-tags";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import ts from "typescript";
|
||||
|
||||
interface DocEntry {
|
||||
name: string;
|
||||
docs?: string;
|
||||
type: "ViewerNetworkEvent" | "ViewerEvent";
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* We use this regexp to find a previous block that we
|
||||
* are going to update in the readme file.
|
||||
*/
|
||||
const BLOCK_REGEXP = /<!-- START docs:events -->(.|\n)*<!-- END docs:events -->/gm;
|
||||
|
||||
/** Build flags that affects AST generation */
|
||||
const buildFlags =
|
||||
// Do not truncate output.
|
||||
ts.NodeBuilderFlags.NoTruncation |
|
||||
// Use multiline object literals format.
|
||||
ts.NodeBuilderFlags.MultilineObjectLiterals;
|
||||
|
||||
/** Generate documentation for all classes in a set of .ts files */
|
||||
function gatherEntries(
|
||||
fileNames: string[],
|
||||
options: ts.CompilerOptions
|
||||
): DocEntry[] {
|
||||
// Build a program using the set of root file names in fileNames
|
||||
const program = ts.createProgram(fileNames, options);
|
||||
|
||||
const printer = ts.createPrinter({
|
||||
noEmitHelpers: true,
|
||||
omitTrailingSemicolon: true,
|
||||
removeComments: false,
|
||||
});
|
||||
|
||||
// Get the checker, we will use it to find more about classes
|
||||
const checker = program.getTypeChecker();
|
||||
|
||||
const data: DocEntry[] = [];
|
||||
|
||||
/** Hold a pointer to the sourcefile we are currently processing. */
|
||||
let currentSourceFile: ts.SourceFile;
|
||||
|
||||
// Visit every sourceFile in the program
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (!sourceFile.isDeclarationFile) {
|
||||
currentSourceFile = sourceFile;
|
||||
// Walk the tree to search for classes
|
||||
ts.forEachChild(sourceFile, visit);
|
||||
}
|
||||
}
|
||||
|
||||
const sorted = data.sort((a, b) => {
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
if (b.name > a.name) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
|
||||
/** visit nodes finding exported events */
|
||||
function visit(node: ts.Node) {
|
||||
// Only consider exported nodes
|
||||
if (!isNodeExported(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ts.isVariableStatement(node)) {
|
||||
if (
|
||||
!node.getFullText().includes("createViewerNetworkEvent") &&
|
||||
!node.getFullText().includes("createViewerEvent")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const firstChild = node.declarationList.declarations[0];
|
||||
if (ts.isVariableDeclaration(firstChild)) {
|
||||
const symbol = checker.getSymbolAtLocation(firstChild.name);
|
||||
if (symbol) {
|
||||
serializeEventSymbol(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function serializeEventSymbol(symbol: ts.Symbol) {
|
||||
const type = checker.getTypeOfSymbolAtLocation(
|
||||
symbol,
|
||||
symbol.valueDeclaration
|
||||
);
|
||||
const typeNode = checker.typeToTypeNode(type, undefined, buildFlags)!;
|
||||
const typeName = symbol.getName();
|
||||
const entry: DocEntry = {
|
||||
name: typeName,
|
||||
docs: ts.displayPartsToString(symbol.getDocumentationComment(checker)),
|
||||
type: type.getSymbol()!.getName() as DocEntry["type"],
|
||||
};
|
||||
typeNode.forEachChild(ch => {
|
||||
if (ts.isTypeLiteralNode(ch)) {
|
||||
const text = printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
ch,
|
||||
currentSourceFile
|
||||
);
|
||||
if (text !== "{}") {
|
||||
entry.text = text;
|
||||
}
|
||||
/*
|
||||
Go through each parameter.
|
||||
ch.members.forEach(m => {
|
||||
if (ts.isPropertySignature(m)) {
|
||||
if (ts.isIdentifier(m.name)) {
|
||||
data.parameters[m.name.text] = printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
m.type!,
|
||||
currentSourceFile
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
});
|
||||
data.push(entry);
|
||||
}
|
||||
|
||||
/** True if this is visible outside this file, false otherwise */
|
||||
function isNodeExported(node: ts.Node): boolean {
|
||||
return (
|
||||
// eslint-disable-next-line no-bitwise, @typescript-eslint/no-unnecessary-type-assertion
|
||||
(ts.getCombinedModifierFlags(node as ts.Declaration) &
|
||||
ts.ModifierFlags.Export) !==
|
||||
0 ||
|
||||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function prefixLines(text: string, prefix: string) {
|
||||
return text.split("\n").join(`\n${prefix}`);
|
||||
}
|
||||
|
||||
function getEventName(typeName: string) {
|
||||
return (
|
||||
typeName[0].toLocaleLowerCase() +
|
||||
typeName.slice(1, typeName.length - "Event".length)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes "%future added value" from text. This is a placeholder type
|
||||
* added by Relay to help with future proofness.
|
||||
*/
|
||||
function removeFutureAddedValue(text: string) {
|
||||
return text
|
||||
.replace(': "%future added value" | ', ": ")
|
||||
.replace(' | "%future added value"', "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Append or update previous documention in markdownFile.
|
||||
*
|
||||
* @param markdownFile The markdown file we want to inject the docs too.
|
||||
* @param entries data as returned by gatherEntries.
|
||||
*/
|
||||
function emitDocs(markdownFile: string, entries: DocEntry[], verify = false) {
|
||||
const previousContent = fs.existsSync(markdownFile)
|
||||
? fs.readFileSync(markdownFile).toString()
|
||||
: "";
|
||||
const summary = stripIndent`
|
||||
- ${entries
|
||||
.map(
|
||||
e => `<a href="#${getEventName(e.name)}">${getEventName(e.name)}</a>`
|
||||
)
|
||||
.join("\n - ")}
|
||||
`;
|
||||
const list = entries
|
||||
.map(
|
||||
e =>
|
||||
codeBlock`
|
||||
- ${
|
||||
e.type === "ViewerEvent"
|
||||
? `<a id="${getEventName(e.name)}">**${getEventName(e.name)}**</a>`
|
||||
: `<a id="${getEventName(e.name)}">**${getEventName(
|
||||
e.name
|
||||
)}.success**, **${getEventName(e.name)}.error**</a>`
|
||||
}: ${e.docs ? e.docs.replace("\n", " ") : ""}
|
||||
${
|
||||
e.text
|
||||
? codeBlock`
|
||||
\`\`\`ts
|
||||
${removeFutureAddedValue(e.text)}
|
||||
\`\`\`
|
||||
`
|
||||
: ""
|
||||
}
|
||||
`
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
const output = stripIndent`
|
||||
<!-- START docs:events -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN npm run docs:events -->
|
||||
### Index
|
||||
${prefixLines(summary, " ")}
|
||||
|
||||
### Events
|
||||
${prefixLines(list, " ")}
|
||||
<!-- END docs:events -->
|
||||
`;
|
||||
|
||||
let newContent;
|
||||
// Find previous block.
|
||||
if (BLOCK_REGEXP.test(previousContent)) {
|
||||
newContent = previousContent.replace(BLOCK_REGEXP, output);
|
||||
} else {
|
||||
newContent = previousContent + "\n" + output;
|
||||
}
|
||||
if (previousContent === newContent) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${markdownFile} is up to date`);
|
||||
return;
|
||||
}
|
||||
if (verify) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`${markdownFile} is outdated, please run \`npm run docs:events\``
|
||||
);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(markdownFile, newContent);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Successfully injected documentation into ${markdownFile}`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (process.argv.length < 4) {
|
||||
throw new Error("Must provide path to events and a markdown file.");
|
||||
}
|
||||
|
||||
const eventFile = process.argv[2];
|
||||
const markdownFile = process.argv[3];
|
||||
|
||||
// Find tsconfig file.
|
||||
const configFile = ts.findConfigFile(eventFile, fs.existsSync);
|
||||
if (!configFile) {
|
||||
throw new Error("tsconfig file not found");
|
||||
}
|
||||
const configText = fs.readFileSync(configFile).toString();
|
||||
const result = ts.parseConfigFileTextToJson(configFile, configText);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
// Parse the JSON raw data into actual consumable compiler options.
|
||||
const config = ts.parseJsonConfigFileContent(
|
||||
result.config,
|
||||
ts.sys,
|
||||
path.dirname(configFile)
|
||||
);
|
||||
|
||||
const entries = gatherEntries([eventFile], config.options);
|
||||
emitDocs(markdownFile, entries, process.argv[4] === "--verify");
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -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";
|
||||
@@ -11,8 +11,9 @@ interface Props {
|
||||
|
||||
const DownloadForm: FunctionComponent<Props> = ({ token }) => {
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const onClick = useCallback(() => {
|
||||
const onSubmit = useCallback(() => {
|
||||
setSubmitted(true);
|
||||
return true;
|
||||
}, [setSubmitted]);
|
||||
|
||||
return (
|
||||
@@ -21,15 +22,15 @@ const DownloadForm: FunctionComponent<Props> = ({ token }) => {
|
||||
className={styles.form}
|
||||
method="post"
|
||||
action="/api/account/download"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<input name="token" type="hidden" value={token} />
|
||||
<Localized id="download-landingPage-downloadComments ">
|
||||
<Localized id="download-landingPage-downloadComments">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="filled"
|
||||
color="primary"
|
||||
disabled={submitted}
|
||||
onClick={onClick}
|
||||
className={styles.downloadButton}
|
||||
>
|
||||
Download My Comment History
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user