[CORL-181] Comment Count Injection (#2581)

* feat: inject comment counts

* fix: tests

* feat: integrate stream embed with coral counts

* chore: refactor constants

* test: test for count bundle

* test: test live comment count integration with stream embed

* feat: use defer

* fix: snapshot

* feat: auto add count.js in when calling Coral.createStreamEmbed

* fix: tests

* fix: rm duplicate test

* chore: remove unuse file
This commit is contained in:
Vinh
2019-10-01 19:22:15 +00:00
committed by Wyatt Johnson
parent 53fa5f43e5
commit 808b355a27
48 changed files with 888 additions and 50 deletions
@@ -2,9 +2,9 @@ import { stripIndent } from "common-tags";
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useMemo } from "react";
import { getLocationOrigin } from "coral-common/utils";
import { CopyButton } from "coral-framework/components";
import { GetMessage, withGetMessage } from "coral-framework/lib/i18n";
import { getLocationOrigin } from "coral-framework/utils";
import { HorizontalGutter, Typography } from "coral-ui/components";
import Header from "../../Header";
@@ -56,6 +56,8 @@ const EmbedCode: FunctionComponent<Props> = ({ staticURI, getMessage }) => {
(function() {
var d = document, s = d.createElement('script');
s.src = '${script}/assets/js/embed.js';
s.async = false;
s.defer = true;
s.onload = function() {
Coral.createStreamEmbed({
id: "coral_thread",
@@ -138,12 +138,14 @@ each of your sites stories.
<textarea
className="EmbedCode-textArea"
readOnly={true}
rows={22}
rows={24}
value="<div id=\\"coral_thread\\"></div>
<script type=\\"text/javascript\\">
(function() {
var d = document, s = d.createElement('script');
s.src = 'http://localhost/assets/js/embed.js';
s.async = false;
s.defer = true;
s.onload = function() {
Coral.createStreamEmbed({
id: \\"coral_thread\\",
@@ -0,0 +1,27 @@
/**
* getCurrentScriptOrigin will try to find the script origin.
* For legacy browsers a fallbackIdentifier is required.
*
* @argument fallbackID id attached to a script tag to get its origin from for legacy browsrs.
*/
function getCurrentScriptOrigin(fallbackID?: string) {
// Find current script (modern browsers).
let script = document.currentScript as HTMLScriptElement | null;
if (!script && fallbackID) {
// Find script tag with `fallbackIdentifier` as its id.
script = document.getElementById(fallbackID) as HTMLScriptElement | null;
if (!script) {
// Find script tag with `fallbackIdentifier` as its className.
script = document.querySelector(
`.${fallbackID}`
) as HTMLScriptElement | null;
}
}
if (!script) {
throw new Error("Current script not found");
}
return new URL(script.src).origin;
}
export default getCurrentScriptOrigin;
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Coral - Count</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" />
<link rel="canonical" href="http://localhost:8080/" />
</head>
<body>
<h1>Comment Counts</h1>
<h3>Specified by canonical link</h3>
<p><a href="http://localhost:8080/">Default: <span class="coral-count"></span></a></p>
<h3>Specified by data-coral-url</h3>
<p><a href="http://localhost:8080/story.html">Story: <span class="coral-count" data-coral-url="http://localhost:8080/story.html"></span></a></p>
<p><a href="http://localhost:8080/storyButton.html">Story With Button: <span class="coral-count" data-coral-url="http://localhost:8080/storyButton.html"></span></a></p>
<h3>No text</h3>
<p><a href="http://localhost:8080/">Default notext: <span class="coral-count" data-coral-notext="true" data-coral-url="http://localhost:8080/"></span></a></p>
</body>
</html>
+61
View File
@@ -0,0 +1,61 @@
import { COUNT_SELECTOR, ORIGIN_FALLBACK_ID } from "coral-framework/constants";
import detectCountScript from "coral-framework/helpers/detectCountScript";
import resolveStoryURL from "coral-framework/helpers/resolveStoryURL";
import jsonp from "coral-framework/utils/jsonp";
import getCurrentScriptOrigin from "./getCurrentScriptOrigin";
import injectJSONPCallback from "./injectJSONPCallback";
/** Arguments that will be send to the server. */
interface CountQueryArgs {
id?: string;
url?: string;
notext?: boolean;
}
/** createCountQueryRef creates a unique reference from the query args */
function createCountQueryRef(args: CountQueryArgs) {
return btoa(`${JSON.stringify(!!args.notext)};${args.id || args.url}`);
}
/** Detects count elements and use jsonp to inject the counts. */
function detectAndInject() {
const ORIGIN = getCurrentScriptOrigin(ORIGIN_FALLBACK_ID);
const STORY_URL = resolveStoryURL();
/** A map of references pointing to the count query arguments */
const queryMap: Record<string, CountQueryArgs> = {};
// Find all the selected elements and fill the queryMap.
const elements = document.querySelectorAll(COUNT_SELECTOR);
Array.prototype.forEach.call(elements, (element: HTMLElement) => {
let url = element.dataset.coralUrl;
const id = element.dataset.coralId;
const notext = element.dataset.coralNotext === "true";
if (!url && !id) {
url = STORY_URL;
element.dataset.coralUrl = STORY_URL;
}
const args = { id, url, notext };
const ref = createCountQueryRef(args);
if (!(ref in queryMap)) {
queryMap[ref] = args;
}
element.dataset.coralRef = ref;
});
// Call server using JSONP.
Object.keys(queryMap).forEach(ref => {
const { url, id, notext } = queryMap[ref];
const args = { url, id, notext: notext ? "true" : "false", ref };
jsonp(`${ORIGIN}/api/story/count.js`, "CoralCount.setCount", args);
});
}
export function main() {
injectJSONPCallback();
detectAndInject();
}
if (!detectCountScript() && process.env.NODE_ENV !== "test") {
main();
}
@@ -0,0 +1,18 @@
import { COUNT_SELECTOR } from "coral-framework/constants";
/** Injects a global CoralCount callback into the window object to be used in JSONP */
function injectJSONPCallback() {
(window as any).CoralCount = {
setCount: (data: { ref: string; html: string }) => {
// Find all the elements with ref.
const elements = document.querySelectorAll(
`${COUNT_SELECTOR}[data-coral-ref='${data.ref}']`
);
Array.prototype.forEach.call(elements, (element: HTMLElement) => {
element.innerHTML = data.html;
});
},
};
}
export default injectJSONPCallback;
@@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Calls JSONP 1`] = `
<body>
<span
class="coral-count"
data-coral-ref="ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwL3N0b3J5Lmh0bWw="
data-coral-url="http://localhost:8080/story.html"
/>
<span
class="coral-count"
data-coral-id="1234-5678-91021"
data-coral-ref="ZmFsc2U7MTIzNC01Njc4LTkxMDIx"
/>
<span
class="coral-count"
data-coral-ref="ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwLw=="
data-coral-url="http://localhost:8080/"
data-notext="true"
/>
<span
class="coral-count"
data-coral-ref="ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwLw=="
data-coral-url="http://localhost:8080/"
/>
<script
src="http://localhost:8080/api/story/count.js?callback=CoralCount.setCount&url=http%3A%2F%2Flocalhost%3A8080%2Fstory.html&notext=false&ref=ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwL3N0b3J5Lmh0bWw%3D"
/>
<script
src="http://localhost:8080/api/story/count.js?callback=CoralCount.setCount&id=1234-5678-91021&notext=false&ref=ZmFsc2U7MTIzNC01Njc4LTkxMDIx"
/>
<script
src="http://localhost:8080/api/story/count.js?callback=CoralCount.setCount&url=http%3A%2F%2Flocalhost%3A8080%2F&notext=false&ref=ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwLw%3D%3D"
/>
</body>
`;
exports[`Inject counts 1`] = `
<body>
<span
class="coral-count"
data-coral-ref="ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwL3N0b3J5Lmh0bWw="
data-coral-url="http://localhost:8080/story.html"
/>
<span
class="coral-count"
data-coral-id="1234-5678-91021"
data-coral-ref="ZmFsc2U7MTIzNC01Njc4LTkxMDIx"
/>
<span
class="coral-count"
data-coral-ref="ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwLw=="
data-coral-url="http://localhost:8080/"
data-notext="true"
>
<span
class="coral-count-number"
>
5
</span>
<span
class="coral-count-text"
>
Comments
</span>
</span>
<span
class="coral-count"
data-coral-ref="ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwLw=="
data-coral-url="http://localhost:8080/"
>
<span
class="coral-count-number"
>
5
</span>
<span
class="coral-count-text"
>
Comments
</span>
</span>
<script
src="http://localhost:8080/api/story/count.js?callback=CoralCount.setCount&url=http%3A%2F%2Flocalhost%3A8080%2Fstory.html&notext=false&ref=ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwL3N0b3J5Lmh0bWw%3D"
/>
<script
src="http://localhost:8080/api/story/count.js?callback=CoralCount.setCount&id=1234-5678-91021&notext=false&ref=ZmFsc2U7MTIzNC01Njc4LTkxMDIx"
/>
<script
src="http://localhost:8080/api/story/count.js?callback=CoralCount.setCount&url=http%3A%2F%2Flocalhost%3A8080%2F&notext=false&ref=ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwLw%3D%3D"
/>
</body>
`;
+53
View File
@@ -0,0 +1,53 @@
beforeAll(async () => {
const script = document.createElement("script");
script.src = "http://localhost:8080/assets/js/count.js";
Object.defineProperty(window.document, "currentScript", {
value: script,
});
const link = document.createElement("link");
link.rel = "canonical";
link.href = "http://localhost:8080/";
document.head.appendChild(link);
});
beforeEach(async () => {
document.body.innerHTML = "";
const tags = [
{
coralUrl: "http://localhost:8080/story.html",
},
{
coralId: "1234-5678-91021",
},
{
notext: "true",
},
{},
];
tags.forEach(attrs => {
const element = document.createElement("span");
element.className = "coral-count";
Object.assign(element.dataset, attrs);
document.body.appendChild(element);
});
(await import("../")).main();
});
it("Sets the JSONP callback", async () => {
expect((window as any).CoralCount).toBeDefined();
});
it("Calls JSONP", async () => {
expect(document.body).toMatchSnapshot();
});
it("Inject counts", async () => {
(window as any).CoralCount.setCount({
ref: "ZmFsc2U7aHR0cDovL2xvY2FsaG9zdDo4MDgwLw==",
html:
'<span class="coral-count-number">5</span> <span class="coral-count-text">Comments</span>',
});
expect(document.body).toMatchSnapshot();
});
+3 -17
View File
@@ -1,6 +1,8 @@
import { EventEmitter2 } from "eventemitter2";
import { getLocationOrigin, parseQuery } from "coral-common/utils";
import { parseQuery } from "coral-common/utils";
import resolveStoryURL from "coral-framework/helpers/resolveStoryURL";
import getLocationOrigin from "coral-framework/utils/getLocationOrigin";
import { default as create, StreamEmbed } from "./StreamEmbed";
@@ -15,22 +17,6 @@ export interface Config {
accessToken?: string;
}
function resolveStoryURL() {
const canonical = document.querySelector(
'link[rel="canonical"]'
) as HTMLLinkElement;
if (canonical) {
return canonical.href;
}
// tslint:disable-next-line:no-console
console.warn(
"This page does not include a canonical link tag. Coral has inferred this story_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages."
);
return getLocationOrigin() + window.location.pathname;
}
export function createStreamEmbed(config: Config): StreamEmbed {
// Parse query params
const query = parseQuery(location.search);
+7
View File
@@ -12,9 +12,11 @@ import {
withConfig,
withEventEmitter,
withIOSSafariWidthWorkaround,
withLiveCommentCount,
withPymStorage,
withSetCommentID,
} from "./decorators";
import injectCountScriptIfNeeded from "./injectCountScriptIfNeeded";
import onIntersect, { OnIntersectCancellation } from "./onIntersect";
import PymControl, {
defaultPymControlFactory,
@@ -46,6 +48,10 @@ export class StreamEmbed {
) {
this.config = config;
this.pymControlFactory = pymControlFactory;
// Detect if comment count injection is needed and add the count script.
injectCountScriptIfNeeded(config.rootURL);
if (config.commentID) {
// Delay emit of `showPermalink` event to allow
// user enough time to setup event listeners.
@@ -132,6 +138,7 @@ export class StreamEmbed {
withClickEvent,
withSetCommentID,
withEventEmitter(this.config.eventEmitter),
withLiveCommentCount(this.config.eventEmitter),
withPymStorage(localStorage, "localStorage"),
withPymStorage(sessionStorage, "sessionStorage"),
withConfig(externalConfig),
@@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should live update counts when receiving a commentCount event 1`] = `
<body>
<span
class="coral-count"
data-coral-id="story-1"
>
<span
class="coral-count-number"
>
2
</span>
<span
class="coral-count-text"
>
Comments
</span>
</span>
<span
class="coral-count"
data-coral-url="http://localhost/stories/story-1"
>
<span
class="coral-count-number"
>
2
</span>
<span
class="coral-count-text"
>
Comments
</span>
</span>
<span
class="coral-count"
data-coral-id="story-x"
>
<span
class="coral-count-number"
>
4
</span>
<span
class="coral-count-text"
>
Comments
</span>
</span>
<span
class="x-count"
data-coral-id="story-1"
>
<span
class="coral-count-number"
>
1
</span>
<span
class="coral-count-text"
>
Comment
</span>
</span>
</body>
`;
@@ -5,6 +5,7 @@ export { default as withSetCommentID } from "./withSetCommentID";
export { default as withEventEmitter } from "./withEventEmitter";
export { default as withPymStorage } from "./withPymStorage";
export { default as withConfig } from "./withConfig";
export { default as withLiveCommentCount } from "./withLiveCommentCount";
export {
default as withIOSSafariWidthWorkaround,
} from "./withIOSSafariWidthWorkaround";
@@ -0,0 +1,46 @@
import { EventEmitter2 } from "eventemitter2";
import withLiveCommentCount from "./withLiveCommentCount";
beforeAll(() => {
const elementWithID = document.createElement("span");
elementWithID.className = "coral-count";
elementWithID.dataset.coralId = "story-1";
elementWithID.innerHTML =
'<span class="coral-count-number">1</span> <span class="coral-count-text">Comment</span>';
document.body.appendChild(elementWithID);
const elementWithURL = document.createElement("span");
elementWithURL.className = "coral-count";
elementWithURL.dataset.coralUrl = "http://localhost/stories/story-1";
elementWithURL.innerHTML =
'<span class="coral-count-number">1</span> <span class="coral-count-text">Comment</span>';
document.body.appendChild(elementWithURL);
const nonMatchingElement = document.createElement("span");
nonMatchingElement.className = "coral-count";
nonMatchingElement.dataset.coralId = "story-x";
nonMatchingElement.innerHTML =
'<span class="coral-count-number">4</span> <span class="coral-count-text">Comments</span>';
document.body.appendChild(nonMatchingElement);
const wrongSelectorElement = document.createElement("span");
wrongSelectorElement.className = "x-count";
wrongSelectorElement.dataset.coralId = "story-1";
wrongSelectorElement.innerHTML =
'<span class="coral-count-number">1</span> <span class="coral-count-text">Comment</span>';
document.body.appendChild(wrongSelectorElement);
});
it("should live update counts when receiving a commentCount event", () => {
const events = new EventEmitter2({ wildcard: true });
const fakePym = null as any;
withLiveCommentCount(events)(fakePym);
events.emit("commentCount", {
number: 2,
storyID: "story-1",
storyURL: "http://localhost/stories/story-1",
text: "Comments",
});
expect(document.body).toMatchSnapshot();
});
@@ -0,0 +1,32 @@
import { EventEmitter2 } from "eventemitter2";
import { COUNT_SELECTOR } from "coral-framework/constants";
import { Decorator } from "./types";
/**
* withLiveCommentCount will listen to `commentCount` events
* and update any comment counts managed by our `count.js` script.
*/
const withLiveCommentCount = (eventEmitter: EventEmitter2): Decorator => () => {
eventEmitter.on("commentCount", args => {
// Find all matching elements.
const elements = document.querySelectorAll(
`${COUNT_SELECTOR}[data-coral-url='${
args.storyURL
}'], ${COUNT_SELECTOR}[data-coral-id='${args.storyID}']`
);
elements.forEach(element => {
// Replace number.
element.querySelectorAll(".coral-count-number").forEach(no => {
no.innerHTML = args.number;
});
// Replace text.
element.querySelectorAll(".coral-count-text").forEach(no => {
no.innerHTML = args.text;
});
});
});
};
export default withLiveCommentCount;
@@ -0,0 +1,22 @@
import { COUNT_SELECTOR } from "coral-framework/constants";
import detectCountScript from "coral-framework/helpers/detectCountScript";
/**
* injectCountScriptIfNeeded will detect if comment count injection is necessary and
* automatically includes the `count.js` script.
*/
const injectCountScriptIfNeeded = (rootURL: string) => {
if (detectCountScript()) {
return;
}
// Detect if we need to inject counts.
if (document.querySelector(COUNT_SELECTOR)) {
const s = document.createElement("script");
s.src = `${rootURL}/assets/js/count.js`;
s.async = false;
s.defer = true;
(document.head || document.body).appendChild(s);
}
};
export default injectCountScriptIfNeeded;
+3
View File
@@ -19,6 +19,9 @@
<a href="/storyButton.html">Story With Button</a>
</p>
<h1 style="text-align: center">Coral 5.0 Story</h1>
<p>
<a href="#coralStreamEmbed"><span class="coral-count"></span></a>&nbsp;
</p>
<p>
Dismember a mouse and then regurgitate parts of it on the family room
floor. Dont wait for the storm to pass, dance in the rain stand in front
+3
View File
@@ -19,6 +19,9 @@
<a href="/story.html">Story</a>
</p>
<h1 style="text-align: center">Coral 5.0 Story with Button</h1>
<p>
<a href="#coralStreamEmbed"><span class="coral-count"></span></a>&nbsp;
</p>
<p>
Dismember a mouse and then regurgitate parts of it on the family room
floor. Dont wait for the storm to pass, dance in the rain stand in front
@@ -1,5 +1,5 @@
import mockConsole from "jest-mock-console";
import * as Coral from "./";
import * as Coral from "../";
// tslint:disable:no-console
+57
View File
@@ -0,0 +1,57 @@
import mockConsole from "jest-mock-console";
import * as Coral from "../";
import { COUNT_SELECTOR } from "coral-framework/constants";
// tslint:disable:no-console
describe("Basic integration test", () => {
const container: HTMLElement = document.createElement("div");
beforeAll(() => {
container.id = "basic-integration-test-id";
document.body.appendChild(container);
});
afterAll(() => {
document.body.removeChild(container);
});
it("should not inject count script", () => {
mockConsole();
const CoralEmbedStream = Coral.createStreamEmbed({
id: "basic-integration-test-id",
});
CoralEmbedStream.render();
expect(document.head.querySelector("script")).toBeNull();
});
it("should inject count script", () => {
mockConsole();
const commentCount: HTMLElement = document.createElement("div");
commentCount.className = "coral-count";
document.body.appendChild(commentCount);
Coral.createStreamEmbed({
id: "basic-integration-test-id",
});
const s = document.head.querySelector("script");
expect(s).not.toBeNull();
expect(s!.defer).toBe(true);
document.head.removeChild(s!);
document.body.removeChild(commentCount);
});
it("should not inject count script when it's already there", () => {
mockConsole();
// Make detectCountScript return true.
(window as any).CoralCount = {};
const commentCount: HTMLElement = document.createElement("div");
commentCount.className = COUNT_SELECTOR;
document.body.appendChild(commentCount);
const CoralEmbedStream = Coral.createStreamEmbed({
id: "basic-integration-test-id",
});
CoralEmbedStream.render();
expect(document.head.querySelector("script")).toBeNull();
document.body.removeChild(commentCount);
delete (window as any).CoralCount;
});
});
+12
View File
@@ -0,0 +1,12 @@
/**
* COUNT_SELECTOR is a css selector used to identify elements that
* will be replaced by the story count.
*/
export const COUNT_SELECTOR = ".coral-count";
/**
* ORIGIN_FALLBACK_ID can be attached to any <script /> tag as an
* id to allow the `count.js` script to find its origin when
* `document.currentScript` is not available (for legacy browsers).
*/
export const ORIGIN_FALLBACK_ID = "coral-origin";
@@ -0,0 +1,7 @@
function detectCountScript() {
// If CoralCount JSONP callback has been defined, then the
// count script has already run.
return (window as any).CoralCount !== undefined;
}
export default detectCountScript;
@@ -10,3 +10,5 @@ export {
export { default as getParamsFromHash } from "./getParamsFromHash";
export { default as clearHash } from "./clearHash";
export { default as roleIsAtLeast } from "./roleIsAtLeast";
export { default as resolveStoryURL } from "./resolveStoryURL";
export { default as detectCountScript } from "./detectCountScript";
@@ -0,0 +1,19 @@
import { getLocationOrigin } from "coral-framework/utils";
function resolveStoryURL() {
const canonical = document.querySelector(
'link[rel="canonical"]'
) as HTMLLinkElement;
if (canonical) {
return canonical.href;
}
// tslint:disable-next-line:no-console
console.warn(
"This page does not include a canonical link tag. Coral has inferred this story_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages."
);
return getLocationOrigin() + window.location.pathname;
}
export default resolveStoryURL;
@@ -0,0 +1,5 @@
export default function getLocationOrigin() {
return (
location.origin || `${window.location.protocol}//${window.location.host}`
);
}
+2
View File
@@ -3,3 +3,5 @@ export { default as parseURL } from "./parseURL";
export { default as modifyQuery } from "./modifyQuery";
export { default as areWeInIframe } from "./areWeInIframe";
export { default as parseHashQuery } from "./parseHashQuery";
export { default as getLocationOrigin } from "./getLocationOrigin";
export { default as jsonp } from "./jsonp";
+29
View File
@@ -0,0 +1,29 @@
/**
* Initiate a jsonp request.
* @argument endpoint jsonp endpoint
* @argument callback name of global callback to receive response
* @argument args args to send along the jsonp request.
*/
function jsonp(
endpoint: string,
callback: string,
args: Record<string, string | number | null | undefined>
) {
const script = document.createElement("script");
script.src = `${endpoint}?callback=${callback}`;
Object.keys(args).forEach(key => {
let val = "";
if (args[key] === undefined) {
return;
}
if (typeof args[key] === "string") {
val = args[key] as string;
} else {
val = JSON.stringify(args[key]);
}
script.src += `&${key}=${encodeURIComponent(val)}`;
});
document.body.appendChild(script);
}
export default jsonp;
@@ -36,6 +36,7 @@ import SortMenu from "./SortMenu";
import StoryClosedTimeoutContainer from "./StoryClosedTimeout";
import styles from "./StreamContainer.css";
import { SuspendedInfoContainer } from "./SuspendedInfo/index";
import useCommentCountEvent from "./useCommentCountEvent";
interface Props {
story: StoryData;
@@ -96,6 +97,9 @@ export const StreamContainer: FunctionComponent<Props> = props => {
const allCommentsCount = props.story.commentCounts.totalPublished;
const featuredCommentsCount = props.story.commentCounts.tags.FEATURED;
// Emit comment count event.
useCommentCountEvent(props.story.id, props.story.url, allCommentsCount);
useEffect(() => {
// If the comment tab is still in its uninitialized state, "NONE", then we
// should evaluate that based on the featuredCommentsCount if we should show
@@ -221,6 +225,8 @@ const enhanced = withFragmentContainer<Props>({
...StoryClosedTimeoutContainer_story
...CreateCommentReplyMutation_story
...CreateCommentMutation_story
id
url
commentCounts {
totalPublished
tags {
@@ -0,0 +1,40 @@
import { useEffect } from "react";
import { useEffectWhenChanged } from "coral-framework/hooks";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { getMessage } from "coral-framework/lib/i18n";
/**
* useCommentCountEvent is a React hook that will
* emit `commentCount` events.
* @param storyID story id of the comment count
* @param storyURL story url of the comment count
* @param commentCount number of total published comments
*/
function useCommentCountEvent(
storyID: string,
storyURL: string,
commentCount: number
) {
const { eventEmitter, localeBundles } = useCoralContext();
const callback = () => {
eventEmitter.emit("commentCount", {
number: commentCount,
text: getMessage(localeBundles, "comment-count-text", "Comment", {
count: commentCount,
}),
storyID,
storyURL,
});
};
useEffect(callback, []);
useEffectWhenChanged(callback, [
eventEmitter,
commentCount,
localeBundles,
storyID,
storyURL,
]);
}
export default useCommentCountEvent;
@@ -320,7 +320,7 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
Cancel
</Button>
</Localized>
<Localized id="profile-changeUsername-submit-button-save">
<Localized id="profile-changeUsername-save">
<Button
className={CLASSES.myUsername.form.saveButton}
variant={pristine || invalid ? "outlined" : "filled"}
@@ -0,0 +1,56 @@
import { pureMerge } from "coral-common/utils";
import { GQLResolver } from "coral-framework/schema";
import {
createResolversStub,
CreateTestRendererParams,
} from "coral-framework/testHelpers";
import { commenters, settings, stories } from "../../fixtures";
import create from "./create";
const story = stories[0];
const viewer = commenters[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
story: () => story,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(story.id, "storyID");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
return {
testRenderer,
context,
};
}
it("emit commentCount events", done => {
createTestRenderer().then(({ context: { eventEmitter } }) => {
eventEmitter.on("commentCount", args => {
expect(args).toMatchInlineSnapshot(`
Object {
"number": 2,
"storyID": "story-1",
"storyURL": "http://localhost/stories/story-1",
"text": "Comments",
}
`);
done();
});
});
});
@@ -123,9 +123,9 @@ exports[`renders configure 1`] = `
<p
className="Box-root Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
>
When enabled, the comments will be updated instantly
as new comments and replies are submitted, instead of
requiring a page refresh. You can disable this in the
When enabled, the comments will be updated instantly
as new comments and replies are submitted, instead of
requiring a page refresh. You can disable this in the
unusual situation of an article getting so much traffic that the comments are loading slowly.
</p>
</div>
@@ -258,8 +258,8 @@ unusual situation of an article getting so much traffic that the comments are lo
<p
className="Box-root Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
>
Add a message to the top of the comment box for your readers.
Use this to suggest a discussion topic, ask a question or make
Add a message to the top of the comment box for your readers.
Use this to suggest a discussion topic, ask a question or make
announcements relating to the comments on this story.
</p>
</div>
@@ -300,7 +300,7 @@ exports[`renders the empty settings pane 1`] = `
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
An email has been sent to $email to verify your account.
You must verify your new email address before it can be used
You must verify your new email address before it can be used
to sign in to your account or to receive notifications.
</p>
<button
+1
View File
@@ -11,6 +11,7 @@
"coral-account/*": ["./account/*"],
"coral-admin/*": ["./admin/*"],
"coral-auth/*": ["./auth/*"],
"coral-count/*": ["./count/*"],
"coral-embed/*": ["./embed/*"],
"coral-stream/*": ["./stream/*"],
"coral-framework/*": ["./framework/*"],