Merge branch 'next' into next-respect

This commit is contained in:
Wyatt Johnson
2018-09-24 02:30:12 +00:00
committed by GitHub
71 changed files with 3989 additions and 2562 deletions
+2
View File
@@ -10,6 +10,8 @@ module.exports = {
"<rootDir>/src/core/build/polyfills.js",
"<rootDir>/src/core/client/test/setup.ts",
],
setupTestFrameworkScriptFile:
"<rootDir>/src/core/client/test/setupTestFramework.ts",
testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"],
testEnvironment: "node",
testURL: "http://localhost",
+28 -13
View File
@@ -1086,16 +1086,6 @@
"@babel/helper-plugin-utils": "7.0.0-beta.49"
}
},
"@babel/plugin-transform-sticky-regex": {
"version": "7.0.0-beta.49",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.49.tgz",
"integrity": "sha1-CMxbZM9qWUKoe92bSkgY1MuhLfM=",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "7.0.0-beta.49",
"@babel/helper-regex": "7.0.0-beta.49"
}
},
"@babel/plugin-transform-template-literals": {
"version": "7.0.0-beta.49",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.49.tgz",
@@ -1211,6 +1201,16 @@
"@babel/helper-plugin-utils": "7.0.0-beta.49",
"@babel/helper-simple-access": "7.0.0-beta.49"
}
},
"@babel/plugin-transform-sticky-regex": {
"version": "7.0.0-beta.49",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.49.tgz",
"integrity": "sha1-CMxbZM9qWUKoe92bSkgY1MuhLfM=",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "7.0.0-beta.49",
"@babel/helper-regex": "7.0.0-beta.49"
}
}
}
},
@@ -2162,9 +2162,13 @@
}
},
"@types/recompose": {
"version": "github:coralproject/patched#3ca0deec868322739fc0d750398fd97735d27aac",
"from": "github:coralproject/patched#types/recompose",
"dev": true
"version": "0.26.5",
"resolved": "https://registry.npmjs.org/@types/recompose/-/recompose-0.26.5.tgz",
"integrity": "sha512-Il5stz/Z3pVIMl48pyggl6nnhRLQ8N8YN8hi0Anm0M5UjVh2uMSY0ah2vzwZZKxnca4NzyJArloSjsJ9fL2vWw==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/relateurl": {
"version": "0.2.28",
@@ -14225,6 +14229,12 @@
"integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=",
"dev": true
},
"jest-mock-console": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jest-mock-console/-/jest-mock-console-0.4.0.tgz",
"integrity": "sha512-WElCbNvfqQlD7cpfHfTn1ytZ+RjKg1Ftrvr5wEjdWP7a9esXmaiZuEAPeYUSK5fd0Cra+dR1oF8HAjjKKxDQdg==",
"dev": true
},
"jest-regex-util": {
"version": "23.3.0",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz",
@@ -18041,6 +18051,11 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"permit": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/permit/-/permit-0.2.4.tgz",
"integrity": "sha512-Mp2XTEMD3mPsZIWq3bp0claE4IxXKa4C6nhSDPZgGri8Q4CLjEjAQrP/xGKq2548a2KFENmA1V7W0Lob8kTuzw=="
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+3 -1
View File
@@ -74,6 +74,7 @@
"passport-oauth2": "^1.4.0",
"passport-strategy": "^1.0.0",
"performance-now": "^2.1.0",
"permit": "^0.2.4",
"subscriptions-transport-ws": "^0.9.12",
"tlds": "^1.203.1",
"uuid": "^3.3.2"
@@ -133,7 +134,7 @@
"@types/react-relay": "^1.3.9",
"@types/react-responsive": "^3.0.1",
"@types/react-test-renderer": "^16.0.1",
"@types/recompose": "github:coralproject/patched#types/recompose",
"@types/recompose": "^0.26.5",
"@types/relay-runtime": "^1.3.6",
"@types/sane": "^2.0.0",
"@types/sinon": "^5.0.1",
@@ -185,6 +186,7 @@
"jest": "^23.4.1",
"jest-junit": "^5.1.0",
"jest-localstorage-mock": "^2.2.0",
"jest-mock-console": "^0.4.0",
"jsdom": "^11.11.0",
"loader-utils": "^1.1.0",
"material-design-icons": "^3.0.1",
+74 -45
View File
@@ -288,6 +288,25 @@ export default function createWebpackConfig({
},
],
},
// Makes sure node_modules are transpiled the way we need them to be.
{
test: /\.js$/,
include: /node_modules\//,
use: [
{
loader: require.resolve("babel-loader"),
options: {
presets: [
[
"@babel/env",
{ targets: "last 2 versions, ie 11", modules: false },
],
],
cacheDirectory: true,
},
},
],
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
@@ -363,6 +382,22 @@ export default function createWebpackConfig({
},
};
const devServerEntries = isProduction
? []
: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
require.resolve("react-dev-utils/webpackHotDevClient"),
];
return [
/* Webpack config for our different target, e.g. stream, admin... */
{
@@ -371,39 +406,16 @@ export default function createWebpackConfig({
stream: [
// We ship polyfills by default
paths.appPolyfill,
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
(isProduction && "") ||
require.resolve("react-dev-utils/webpackHotDevClient"),
...devServerEntries,
paths.appStreamIndex,
// Remove deactivated entries.
].filter(s => s),
],
auth: [
// We ship polyfills by default
paths.appPolyfill,
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
(isProduction && "") ||
require.resolve("react-dev-utils/webpackHotDevClient"),
...devServerEntries,
paths.appAuthIndex,
// Remove deactivated entries.
].filter(s => s),
],
},
plugins: [
...baseConfig.plugins!,
@@ -440,33 +452,50 @@ export default function createWebpackConfig({
{
...baseConfig,
entry: [
// No polyfills for the embed.
(isProduction && "") ||
require.resolve("react-dev-utils/webpackHotDevClient"),
/* Use minimal amount of polyfills (for IE) */
"core-js/fn/object/assign",
"core-js/fn/symbol",
"core-js/fn/symbol/iterator",
...devServerEntries,
paths.appEmbedIndex,
// Remove deactivated entries.
].filter(s => s),
],
output: {
...baseConfig.output,
library: "Talk",
library: "Coral",
// don't hash the embed, cache-busting must be completed by the requester
// as this lives in a static template on the embed site.
filename: "assets/js/embed.js",
},
plugins: [
...baseConfig.plugins!,
// Generates an `stream.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "embed.html",
template: paths.appEmbedHTML,
inject: "head",
...htmlWebpackConfig,
}),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In development, this will be an empty string.
new InterpolateHtmlPlugin(env),
...(isProduction
? []
: [
// Generates an `embed.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "embed.html",
template: paths.appEmbedHTML,
inject: "head",
...htmlWebpackConfig,
}),
new HtmlWebpackPlugin({
filename: "article.html",
template: paths.appEmbedArticleHTML,
inject: "head",
...htmlWebpackConfig,
}),
new HtmlWebpackPlugin({
filename: "articleButton.html",
template: paths.appEmbedArticleButtonHTML,
inject: "head",
...htmlWebpackConfig,
}),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In development, this will be an empty string.
new InterpolateHtmlPlugin(env),
]),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
+4 -4
View File
@@ -95,9 +95,9 @@ function generateTarget(target, context) {
`
)
.join("\n")}
contents = contents.concat(suffixes.map(function(suffix) { return require(\`${path
contents = contents.concat(suffixes.map(function(suffix) { return require("${path
.join(getLocalePath(locale), target)
.replace(/\\/g, "/")}\${suffix}\`); }));
.replace(/\\/g, "/")}" + suffix); }));
ret.bundled[${JSON.stringify(locale)}] = contents.join("\\n");
}
`
@@ -132,9 +132,9 @@ function generateTarget(target, context) {
/* webpackChunkName: ${JSON.stringify(
`${target}-locale-${locale}`
)}, webpackMode: "lazy-once" */
\`${path
"${path
.join(getLocalePath(locale), target)
.replace(/\\/g, "/")}\${suffix}\`
.replace(/\\/g, "/")}" + suffix
)
}));
return Promise.all(promises).then(function(modules) {
+2
View File
@@ -31,6 +31,8 @@ export default {
appEmbedIndex: resolveSrc("core/client/embed/index.ts"),
appEmbedHTML: resolveSrc("core/client/embed/index.html"),
appEmbedArticleHTML: resolveSrc("core/client/embed/article.html"),
appEmbedArticleButtonHTML: resolveSrc("core/client/embed/articleButton.html"),
appDistStatic: resolveApp("dist/static"),
appPublic: resolveApp("public"),
+7 -1
View File
@@ -2,13 +2,18 @@ import pym from "pym.js";
import { CleanupCallback, Decorator } from "./decorators";
interface PymControlConfig {
export interface PymControlConfig {
id: string;
url: string;
title: string;
decorators?: ReadonlyArray<Decorator>;
}
export type PymControlFactory = (config: PymControlConfig) => PymControl;
export const defaultPymControlFactory: PymControlFactory = config =>
new PymControl(config);
export default class PymControl {
private pym: pym.Parent;
private cleanups: CleanupCallback[];
@@ -20,6 +25,7 @@ export default class PymControl {
title: config.title,
id: `${config.id}_iframe`,
name: `${config.id}_iframe`,
optionalparams: "",
});
this.cleanups = decorators
-70
View File
@@ -1,70 +0,0 @@
import sinon from "sinon";
import { createStreamInterface } from "./Stream";
it("should call eventEmitter.on", () => {
const control = {};
const cb = () => "";
const eventEmitter = {
on: sinon
.mock()
.once()
.withArgs("eventName", cb),
};
const stream = createStreamInterface(control as any, eventEmitter as any);
stream.on("eventName", cb);
eventEmitter.on.verify();
});
it("should call eventEmitter.off", () => {
const control = {};
const cb = () => "";
const eventEmitter = {
off: sinon
.mock()
.once()
.withArgs("eventName", cb),
};
const stream = createStreamInterface(control as any, eventEmitter as any);
stream.off("eventName", cb);
eventEmitter.off.verify();
});
it("should call control.login", () => {
const control = {
sendMessage: sinon
.mock()
.once()
.withArgs("login", "token"),
};
const eventEmitter = {};
const stream = createStreamInterface(control as any, eventEmitter as any);
stream.login("token");
control.sendMessage.verify();
});
it("should call control.logout", () => {
const control = {
sendMessage: sinon
.mock()
.once()
.withArgs("logout"),
};
const eventEmitter = {};
const stream = createStreamInterface(control as any, eventEmitter as any);
stream.logout();
control.sendMessage.verify();
});
it("should call control.remove", () => {
const control = {
remove: sinon
.mock()
.once()
.withArgs(),
};
const eventEmitter = {};
const stream = createStreamInterface(control as any, eventEmitter as any);
stream.remove();
control.remove.verify();
});
-89
View File
@@ -1,89 +0,0 @@
import { EventEmitter2 } from "eventemitter2";
import qs from "query-string";
import {
Decorator,
withAutoHeight,
withClickEvent,
withEventEmitter,
withIOSSafariWidthWorkaround,
withPymStorage,
withSetCommentID,
} from "./decorators";
import PymControl from "./PymControl";
import { ensureEndSlash } from "./utils";
interface CreatePymControlConfig {
assetID?: string;
assetURL?: string;
commentID?: string;
title?: string;
eventEmitter: EventEmitter2;
id: string;
rootURL: string;
}
export function createPymControl(config: CreatePymControlConfig) {
const streamDecorators: ReadonlyArray<Decorator> = [
withIOSSafariWidthWorkaround,
withAutoHeight,
withClickEvent,
withSetCommentID,
withEventEmitter(config.eventEmitter),
withPymStorage(localStorage, "localStorage"),
withPymStorage(sessionStorage, "sessionStorage"),
];
const query = qs.stringify({
assetID: config.assetID,
assetURL: config.assetURL,
commentID: config.commentID,
});
const url = `${ensureEndSlash(config.rootURL)}stream.html?${query}`;
return new PymControl({
id: config.id,
title: config.title || "Talk Embed Stream",
decorators: streamDecorators,
url,
});
}
type EventCallback = (data: any) => void;
export function createStreamInterface(
control: PymControl,
eventEmitter: EventEmitter2
) {
return {
on(eventName: string, callback: EventCallback) {
return eventEmitter.on(eventName, callback);
},
off(eventName: string, callback: EventCallback) {
return eventEmitter.off(eventName, callback);
},
login(token: string) {
control.sendMessage("login", token);
},
logout() {
control.sendMessage("logout");
},
remove() {
return control.remove();
},
};
}
export type StreamInterface = ReturnType<typeof createStreamInterface>;
export interface CreateConfig {
assetID?: string;
assetURL?: string;
commentID?: string;
title?: string;
eventEmitter: EventEmitter2;
id: string;
rootURL: string;
}
export default function create(config: CreateConfig) {
return createStreamInterface(createPymControl(config), config.eventEmitter);
}
+166
View File
@@ -0,0 +1,166 @@
import { EventEmitter2 } from "eventemitter2";
import { omit } from "lodash";
import sinon from "sinon";
import { PymControlConfig } from "./PymControl";
import { StreamEmbed, StreamEmbedConfig } from "./StreamEmbed";
it("should throw when calling pym dependent methods but was not rendered", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
const streamEmbed = new StreamEmbed(config);
[
() => streamEmbed.login("token"),
() => streamEmbed.logout(),
() => streamEmbed.remove(),
].forEach(cb => {
expect(cb).toThrow();
});
});
it("should return rendered", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
const pymControl = {
remove: sinon.stub(),
};
const fakeFactory: any = () => pymControl;
const streamEmbed = new StreamEmbed(config, fakeFactory);
expect(streamEmbed.rendered).toBe(false);
streamEmbed.render();
expect(streamEmbed.rendered).toBe(true);
streamEmbed.remove();
expect(streamEmbed.rendered).toBe(false);
expect(pymControl.remove.called).toBe(true);
});
it("should relay events methods to event emitter", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
// tslint:disable-next-line:no-empty
const callback = () => {};
const fakeFactory: any = () => ({});
const emitterMock = sinon.mock(config.eventEmitter);
emitterMock
.expects("on")
.withArgs("event", callback)
.once();
emitterMock
.expects("off")
.withArgs("event", callback)
.once();
const streamEmbed = new StreamEmbed(config, fakeFactory);
streamEmbed.on("event", callback);
streamEmbed.off("event", callback);
});
it("should send login message to PymControl", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
const pymControl = {
// tslint:disable-next-line:no-empty
sendMessage: () => {},
};
const pymControlMock = sinon.mock(pymControl);
pymControlMock.expects("sendMessage").withArgs("login", "token");
const fakeFactory: any = () => pymControl;
const streamEmbed = new StreamEmbed(config, fakeFactory);
streamEmbed.render();
streamEmbed.login("token");
pymControlMock.verify();
});
it("should send logout message to PymControl", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
const pymControl = {
// tslint:disable-next-line:no-empty
sendMessage: () => {},
};
const pymControlMock = sinon.mock(pymControl);
pymControlMock.expects("sendMessage").withArgs("logout");
const fakeFactory: any = () => pymControl;
const streamEmbed = new StreamEmbed(config, fakeFactory);
streamEmbed.render();
streamEmbed.logout();
pymControlMock.verify();
});
it("should pass default values to pymControl", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
let pymControlConfig: PymControlConfig | null = null;
const fakeFactory: any = (cfg: PymControlConfig) => {
pymControlConfig = cfg;
return {};
};
const streamEmbed = new StreamEmbed(config, fakeFactory);
streamEmbed.render();
expect(omit(pymControlConfig, "decorators")).toMatchSnapshot();
});
it("should pass correct values to pymControl", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
commentID: "comment-id",
assetID: "asset-id",
assetURL: "asset-url",
};
let pymControlConfig: PymControlConfig | null = null;
const fakeFactory: any = (cfg: PymControlConfig) => {
pymControlConfig = cfg;
return {};
};
const streamEmbed = new StreamEmbed(config, fakeFactory);
streamEmbed.render();
expect(omit(pymControlConfig, "decorators")).toMatchSnapshot();
});
it("should emit showPermalink", () => {
jest.useFakeTimers();
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
commentID: "comment-id",
};
// tslint:disable-next-line:no-empty
const fakeFactory: any = () => ({});
const emitterMock = sinon.mock(config.eventEmitter);
emitterMock
.expects("emit")
.withArgs("showPermalink")
.once();
// tslint:disable-next-line:no-unused-expression
new StreamEmbed(config, fakeFactory);
jest.runOnlyPendingTimers();
jest.useRealTimers();
emitterMock.verify();
});
+127
View File
@@ -0,0 +1,127 @@
import { EventEmitter2 } from "eventemitter2";
import qs from "query-string";
import {
Decorator,
withAutoHeight,
withClickEvent,
withEventEmitter,
withIOSSafariWidthWorkaround,
withPymStorage,
withSetCommentID,
} from "./decorators";
import onIntersect from "./onIntersect";
import PymControl, {
defaultPymControlFactory,
PymControlFactory,
} from "./PymControl";
import { ensureEndSlash } from "./utils";
export interface StreamEmbedConfig {
assetID?: string;
assetURL?: string;
commentID?: string;
autoRender?: boolean;
title: string;
eventEmitter: EventEmitter2;
id: string;
rootURL: string;
}
export class StreamEmbed {
private config: StreamEmbedConfig;
private pymControl?: PymControl;
private pymControlFactory: PymControlFactory;
constructor(
config: StreamEmbedConfig,
pymControlFactory = defaultPymControlFactory
) {
this.config = config;
this.pymControlFactory = pymControlFactory;
if (config.commentID) {
// Delay emit of `showPermalink` event to allow
// user enough time to setup event listeners.
setTimeout(() => config.eventEmitter.emit("showPermalink"), 0);
}
if (config.autoRender) {
if (config.commentID) {
this.render();
} else {
onIntersect(document.getElementById(config.id)!, () => {
if (!this.rendered) {
this.render();
}
});
}
}
}
private assertRendered() {
if (!this.pymControl) {
throw new Error("Stream Embed must be rendered first");
}
}
public on(eventName: string, callback: (data: any) => void) {
return this.config.eventEmitter.on(eventName, callback);
}
public off(eventName: string, callback: (data: any) => void) {
return this.config.eventEmitter.off(eventName, callback);
}
public login(token: string) {
this.assertRendered();
this.pymControl!.sendMessage("login", token);
}
public logout() {
this.assertRendered();
this.pymControl!.sendMessage("logout");
}
public remove() {
this.assertRendered();
this.pymControl!.remove();
this.pymControl = undefined;
}
get rendered() {
return !!this.pymControl;
}
public render() {
if (this.pymControl) {
throw new Error("Stream Embed already rendered");
}
const streamDecorators: ReadonlyArray<Decorator> = [
withIOSSafariWidthWorkaround,
withAutoHeight,
withClickEvent,
withSetCommentID,
withEventEmitter(this.config.eventEmitter),
withPymStorage(localStorage, "localStorage"),
withPymStorage(sessionStorage, "sessionStorage"),
];
const query = qs.stringify({
assetID: this.config.assetID,
assetURL: this.config.assetURL,
commentID: this.config.commentID,
});
const url = `${ensureEndSlash(this.config.rootURL)}stream.html${
query ? `?${query}` : ""
}`;
this.pymControl = this.pymControlFactory({
id: this.config.id,
title: this.config.title,
decorators: streamDecorators,
url,
});
}
}
export default function createStreamEmbed(config: StreamEmbedConfig) {
return new StreamEmbed(config);
}
+60
View File
@@ -0,0 +1,60 @@
import { EventEmitter2 } from "eventemitter2";
import qs from "query-string";
import { default as create, StreamEmbed } from "./StreamEmbed";
export interface Config {
assetID?: string;
assetURL?: string;
commentID?: string;
rootURL?: string;
id?: string;
autoRender?: boolean;
events?: (eventEmitter: EventEmitter2) => void;
}
function getLocationOrigin() {
return (
location.origin ||
`${window.location.protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : ""
}`
);
}
function resolveAssetURL() {
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. Talk has inferred this asset_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 = qs.parse(location.search);
const eventEmitter = new EventEmitter2({ wildcard: true });
if (config.events) {
config.events(eventEmitter);
}
return create({
title: "Talk Embed Stream",
assetID: config.assetID || query.assetID,
assetURL: config.assetURL || resolveAssetURL(),
commentID: config.commentID || query.commentID,
id: config.id || "talk-embed-stream",
rootURL: config.rootURL || getLocationOrigin(),
autoRender: config.autoRender,
eventEmitter,
});
}
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PymControl should create iframe 1`] = `"<iframe src=\\"http://coralproject.net/?initialWidth=0&amp;childId=pymcontrol-test-id&amp;parentTitle=&amp;parentUrl=http%3A%2F%2Flocalhost%2F\\" width=\\"100%\\" scrolling=\\"no\\" marginheight=\\"0\\" frameborder=\\"0\\" title=\\"iFrame title\\" id=\\"pymcontrol-test-id_iframe\\" name=\\"pymcontrol-test-id_iframe\\"></iframe>"`;
exports[`PymControl should create iframe 1`] = `"<iframe src=\\"http://coralproject.net/?initialWidth=0&amp;childId=pymcontrol-test-id\\" width=\\"100%\\" scrolling=\\"no\\" marginheight=\\"0\\" frameborder=\\"0\\" title=\\"iFrame title\\" id=\\"pymcontrol-test-id_iframe\\" name=\\"pymcontrol-test-id_iframe\\"></iframe>"`;
exports[`PymControl should send message 1`] = `"pymxPYMxpymcontrol-test-idxPYMxtestxPYMxhello world"`;
@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should pass correct values to pymControl 1`] = `
Object {
"id": "container-id",
"title": "StreamEmbed",
"url": "http://localhost/stream.html?assetID=asset-id&assetURL=asset-url&commentID=comment-id",
}
`;
exports[`should pass default values to pymControl 1`] = `
Object {
"id": "container-id",
"title": "StreamEmbed",
"url": "http://localhost/stream.html",
}
`;
@@ -1,3 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Basic integration test should render iframe 1`] = `"<iframe src=\\"http://localhost/stream.html?&amp;initialWidth=0&amp;childId=basic-integration-test-id&amp;parentTitle=&amp;parentUrl=http%3A%2F%2Flocalhost%2F\\" width=\\"100%\\" scrolling=\\"no\\" marginheight=\\"0\\" frameborder=\\"0\\" title=\\"Talk Embed Stream\\" id=\\"basic-integration-test-id_iframe\\" name=\\"basic-integration-test-id_iframe\\" style=\\"width: 1px; min-width: 100%;\\"></iframe>"`;
exports[`Basic integration test should render iframe 1`] = `"<iframe src=\\"http://localhost/stream.html?assetURL=http%3A%2F%2Flocalhost%2F&amp;initialWidth=0&amp;childId=basic-integration-test-id\\" width=\\"100%\\" scrolling=\\"no\\" marginheight=\\"0\\" frameborder=\\"0\\" title=\\"Talk Embed Stream\\" id=\\"basic-integration-test-id_iframe\\" name=\\"basic-integration-test-id_iframe\\" style=\\"width: 1px; min-width: 100%;\\"></iframe>"`;
exports[`Basic integration test should use canonical link 1`] = `"<iframe src=\\"http://localhost/stream.html?assetURL=http%3A%2F%2Flocalhost%2Fcanonical&amp;initialWidth=0&amp;childId=basic-integration-test-id\\" width=\\"100%\\" scrolling=\\"no\\" marginheight=\\"0\\" frameborder=\\"0\\" title=\\"Talk Embed Stream\\" name=\\"basic-integration-test-id_iframe\\" style=\\"width: 1px; min-width: 100%;\\"></iframe>"`;
+88
View File
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<title>Talk 5.0 Embed Stream</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">
<style>
body {
margin: 0;
padding: 0 100px 50px 100px;
}
</style>
</head>
<body>
<p style="text-align: center">
<a href="/">Default</a> | <a href="/articleButton.html">Article With Button</a>
</p>
<h1 style="text-align: center">Talk 5.0 Article</h1>
<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 of the computer screen, so stares at human while pushing stuff off a table chew
the plant meow hiss at vacuum cleaner. Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not
sorry chew the plant. Litter kitter kitty litty little kitten big roar roar feed me rub whiskers on bare skin act
innocent sleep on keyboard, so give me attention or face the wrath of my claws for demand to be let outside at
once, and expect owner to wait for me as i think about it spread kitty litter all over house so nya nya nyan. Catty
ipsum massacre a bird in the living room and then look like the cutest and most innocent animal on the planet you
have cat to be kitten me right meow. Hiss and stare at nothing then run suddenly away refuse to come home when
humans are going to bed; stay out all night then yowl like i am dying at 4am and lick plastic bags. Chase dog then
run away purrr purr littel cat, little cat purr purr and step on your keyboard while you're gaming and then turn in
a circle . Twitch tail in permanent irritation put butt in owner's face and the dog smells bad yet attempt to leap
between furniture but woefully miscalibrate and bellyflop onto the floor; what's your problem? i meant to do that
now i shall wash myself intently. Sniff all the things groom forever, stretch tongue and leave it slightly out,
blep, but bring your owner a dead bird decide to want nothing to do with my owner today for lay on arms while
you're using the keyboard meow meow, i tell my human or scratch. Sleep on my human's head then cats take over the
world bleghbleghvomit my furball really tie the room together sleep more napping, more napping all the napping is
exhausting. When in doubt, wash drink water out of the faucet, cats are fats i like to pets them they like to meow
back and cat dog hate mouse eat string barf pillow no baths hate everything yet swat at dog kitty kitty but you
call this cat food. Cough furball into food bowl then scratch owner for a new one flex claws on the human's belly
and purr like a lawnmower for has closed eyes but still sees you groom yourself 4 hours - checked, have your beauty
sleep 18 hours - checked, be fabulous for the rest of the day - checked. Freak human out make funny noise mow mow
mow mow mow mow success now attack human flex claws on the human's belly and purr like a lawnmower or meowwww.
Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not sorry paw at your fat belly so yowling
nonstop the whole night small kitty warm kitty little balls of fur or eat owner's food reward the chosen human with
a slow blink. Gate keepers of hell plan steps for world domination for more napping, more napping all the napping
is exhausting give me some of your food give me some of your food give me some of your food meh, i don't want it so
flop over. Make meme, make cute face ears back wide eyed so sit and stare. Dead stare with ears cocked furrier and
even more furrier hairball. Stand in front of the computer screen demand to have some of whatever the human is
cooking, then sniff the offering and walk away for catasstrophe, kitty scratches couch bad kitty. Wack the mini
furry mouse intrigued by the shower, and pooping rainbow while flying in a toasted bread costume in space.
Mesmerizing birds love me! shake treat bag, yet lies down where is my slave? I'm getting hungry so lick face hiss
at owner, pee a lot, and meow repeatedly scratch at fence purrrrrr eat muffins and poutine until owner comes back.
You have cat to be kitten me right meow sniff other cat's butt and hang jaw half open thereafter but run outside as
soon as door open so munch on tasty moths or munch on tasty moths, for paw at beetle and eat it before it gets
away. Sit on human. Gnaw the corn cob massacre a bird in the living room and then look like the cutest and most
innocent animal on the planet for sit on the laptop. Meow scratch leg; meow for can opener to feed me cat fur is
the new black but hide when guests come over, and Gate keepers of hell. Refuse to come home when humans are going
to bed; stay out all night then yowl like i am dying at 4am cat slap dog in face or eat a rug and furry furry hairs
everywhere oh no human coming lie on counter don't get off counter for i like fish sit on human they not getting up
ever but meow meow but cuddle no cuddle cuddle love scratch scratch.</p>
<p>I show my fluffy belly but it's a trap! if you pet it i will tear up your hand refuse to drink water except out of
someone's glass mice, so cough hairball, eat toilet paper or curl into a furry donut lick sellotape but wack the
mini furry mouse. When owners are asleep, cry for no apparent reason. Chase imaginary bugs. Stinky cat reward the
chosen human with a slow blink, or chase dog then run away. Chew on cable scratch the furniture for you are a
captive audience while sitting on the toilet, pet me for i like cats because they are fat and fluffy and spend all
night ensuring people don't sleep sleep all day. Scoot butt on the rug need to check on human, have not seen in an
hour might be dead oh look, human is alive, hiss at human, feed me, leave fur on owners clothes, so instantly break
out into full speed gallop across the house for no reason play riveting piece on synthesizer keyboard and scoot
butt on the rug yet meow meow. Attack dog, run away and pretend to be victim annoy the old grumpy cat, start a
fight and then retreat to wash when i lose or meow go back to sleep owner brings food and water tries to pet on
head, so scratch get sprayed by water because bad cat. Meowwww pelt around the house and up and down stairs chasing
phantoms drink water out of the faucet meow meow, i tell my human. Destroy couch.</p>
<p>Ask to go outside and ask to come inside and ask to go outside and ask to come inside the dog smells bad. Lick
butt and make a weird face. Toilet paper attack claws fluff everywhere meow miao french ciao litterbox. Shake treat
bag immediately regret falling into bathtub or white cat sleeps on a black shirt so what a cat-ass-trophy! eat
owner's food spit up on light gray carpet instead of adjacent linoleum. Warm up laptop with butt lick butt fart
rainbows until owner yells pee in litter box hiss at cats scratch the box so loved it, hated it, loved it, hated it
but need to check on human, have not seen in an hour might be dead oh look, human is alive, hiss at human, feed me.
</p>
<div id="coralStreamEmbed" style="max-width: 640px; margin: 0 auto"></div>
<script>
const TalkStreamEmbed = Coral.Talk.createStreamEmbed({ id: 'coralStreamEmbed', autoRender: true });
window.TalkStreamEmbed = TalkStreamEmbed;
</script>
</body>
</html>
+103
View File
@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
<title>Talk 5.0 Embed Stream</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">
<style>
body {
margin: 0;
padding: 0 100px 50px 100px;
}
</style>
</head>
<body>
<p style="text-align: center">
<a href="/">Default</a> | <a href="/article.html">Article</a>
</p>
<h1 style="text-align: center">Talk 5.0 Article with Button</h1>
<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 of the computer screen, so stares at human while pushing stuff off a table chew
the plant meow hiss at vacuum cleaner. Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not
sorry chew the plant. Litter kitter kitty litty little kitten big roar roar feed me rub whiskers on bare skin act
innocent sleep on keyboard, so give me attention or face the wrath of my claws for demand to be let outside at
once, and expect owner to wait for me as i think about it spread kitty litter all over house so nya nya nyan. Catty
ipsum massacre a bird in the living room and then look like the cutest and most innocent animal on the planet you
have cat to be kitten me right meow. Hiss and stare at nothing then run suddenly away refuse to come home when
humans are going to bed; stay out all night then yowl like i am dying at 4am and lick plastic bags. Chase dog then
run away purrr purr littel cat, little cat purr purr and step on your keyboard while you're gaming and then turn in
a circle . Twitch tail in permanent irritation put butt in owner's face and the dog smells bad yet attempt to leap
between furniture but woefully miscalibrate and bellyflop onto the floor; what's your problem? i meant to do that
now i shall wash myself intently. Sniff all the things groom forever, stretch tongue and leave it slightly out,
blep, but bring your owner a dead bird decide to want nothing to do with my owner today for lay on arms while
you're using the keyboard meow meow, i tell my human or scratch. Sleep on my human's head then cats take over the
world bleghbleghvomit my furball really tie the room together sleep more napping, more napping all the napping is
exhausting. When in doubt, wash drink water out of the faucet, cats are fats i like to pets them they like to meow
back and cat dog hate mouse eat string barf pillow no baths hate everything yet swat at dog kitty kitty but you
call this cat food. Cough furball into food bowl then scratch owner for a new one flex claws on the human's belly
and purr like a lawnmower for has closed eyes but still sees you groom yourself 4 hours - checked, have your beauty
sleep 18 hours - checked, be fabulous for the rest of the day - checked. Freak human out make funny noise mow mow
mow mow mow mow success now attack human flex claws on the human's belly and purr like a lawnmower or meowwww.
Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not sorry paw at your fat belly so yowling
nonstop the whole night small kitty warm kitty little balls of fur or eat owner's food reward the chosen human with
a slow blink. Gate keepers of hell plan steps for world domination for more napping, more napping all the napping
is exhausting give me some of your food give me some of your food give me some of your food meh, i don't want it so
flop over. Make meme, make cute face ears back wide eyed so sit and stare. Dead stare with ears cocked furrier and
even more furrier hairball. Stand in front of the computer screen demand to have some of whatever the human is
cooking, then sniff the offering and walk away for catasstrophe, kitty scratches couch bad kitty. Wack the mini
furry mouse intrigued by the shower, and pooping rainbow while flying in a toasted bread costume in space.
Mesmerizing birds love me! shake treat bag, yet lies down where is my slave? I'm getting hungry so lick face hiss
at owner, pee a lot, and meow repeatedly scratch at fence purrrrrr eat muffins and poutine until owner comes back.
You have cat to be kitten me right meow sniff other cat's butt and hang jaw half open thereafter but run outside as
soon as door open so munch on tasty moths or munch on tasty moths, for paw at beetle and eat it before it gets
away. Sit on human. Gnaw the corn cob massacre a bird in the living room and then look like the cutest and most
innocent animal on the planet for sit on the laptop. Meow scratch leg; meow for can opener to feed me cat fur is
the new black but hide when guests come over, and Gate keepers of hell. Refuse to come home when humans are going
to bed; stay out all night then yowl like i am dying at 4am cat slap dog in face or eat a rug and furry furry hairs
everywhere oh no human coming lie on counter don't get off counter for i like fish sit on human they not getting up
ever but meow meow but cuddle no cuddle cuddle love scratch scratch.</p>
<p>I show my fluffy belly but it's a trap! if you pet it i will tear up your hand refuse to drink water except out of
someone's glass mice, so cough hairball, eat toilet paper or curl into a furry donut lick sellotape but wack the
mini furry mouse. When owners are asleep, cry for no apparent reason. Chase imaginary bugs. Stinky cat reward the
chosen human with a slow blink, or chase dog then run away. Chew on cable scratch the furniture for you are a
captive audience while sitting on the toilet, pet me for i like cats because they are fat and fluffy and spend all
night ensuring people don't sleep sleep all day. Scoot butt on the rug need to check on human, have not seen in an
hour might be dead oh look, human is alive, hiss at human, feed me, leave fur on owners clothes, so instantly break
out into full speed gallop across the house for no reason play riveting piece on synthesizer keyboard and scoot
butt on the rug yet meow meow. Attack dog, run away and pretend to be victim annoy the old grumpy cat, start a
fight and then retreat to wash when i lose or meow go back to sleep owner brings food and water tries to pet on
head, so scratch get sprayed by water because bad cat. Meowwww pelt around the house and up and down stairs chasing
phantoms drink water out of the faucet meow meow, i tell my human. Destroy couch.</p>
<p>Ask to go outside and ask to come inside and ask to go outside and ask to come inside the dog smells bad. Lick
butt and make a weird face. Toilet paper attack claws fluff everywhere meow miao french ciao litterbox. Shake treat
bag immediately regret falling into bathtub or white cat sleeps on a black shirt so what a cat-ass-trophy! eat
owner's food spit up on light gray carpet instead of adjacent linoleum. Warm up laptop with butt lick butt fart
rainbows until owner yells pee in litter box hiss at cats scratch the box so loved it, hated it, loved it, hated it
but need to check on human, have not seen in an hour might be dead oh look, human is alive, hiss at human, feed me.
</p>
<div id="coralStreamEmbed" style="max-width: 640px; margin: 0 auto"></div>
<div style="text-align: center">
<button id="showComments">Show Comments</button>
</div>
<script>
const TalkStreamEmbed = Coral.Talk.createStreamEmbed({
id: 'coralStreamEmbed',
});
window.TalkStreamEmbed = TalkStreamEmbed;
const button = document.getElementById("showComments");
const showStreamEmbed = () => {
TalkStreamEmbed.render();
button.parentElement.removeChild(button);
};
button.onclick = showStreamEmbed;
TalkStreamEmbed.on("showPermalink", showStreamEmbed);
</script>
</body>
</html>
+8 -3
View File
@@ -9,16 +9,21 @@
<style>
body {
margin: 0;
padding: 0;
padding: 0 0 50px 0;
}
</style>
</head>
<body>
<h1 style="text-align: center" }>Talk 5.0 Embed Stream</h1>
<p style="text-align: center">
<a href="/article.html">Article</a> | <a href="/articleButton.html">Article With Button</a>
</p>
<h1 style="text-align: center">Talk 5.0 Embed Stream</h1>
<div id="coralStreamEmbed" style="max-width: 640px; margin: 0 auto"></div>
<script>
window.TalkEmbed = Talk.render(document.getElementById('coralStreamEmbed'));
const TalkStreamEmbed = Coral.Talk.createStreamEmbed({ id: 'coralStreamEmbed' });
window.TalkStreamEmbed = TalkStreamEmbed;
TalkStreamEmbed.render();
</script>
</body>
+33 -4
View File
@@ -1,8 +1,10 @@
import * as Talk from "./";
import mockConsole from "jest-mock-console";
import * as Coral from "./";
// tslint:disable:no-console
describe("Basic integration test", () => {
const container: HTMLElement = document.createElement("div");
let streamInterface: ReturnType<typeof Talk.render>;
beforeAll(() => {
container.id = "basic-integration-test-id";
document.body.appendChild(container);
@@ -11,13 +13,40 @@ describe("Basic integration test", () => {
document.body.removeChild(container);
});
it("should render iframe", () => {
streamInterface = Talk.render({
mockConsole();
const TalkEmbedStream = Coral.Talk.createStreamEmbed({
id: "basic-integration-test-id",
});
TalkEmbedStream.render();
expect(container.innerHTML).toMatchSnapshot();
expect(console.warn).toHaveBeenCalledTimes(1);
expect(console.error).not.toHaveBeenCalled();
});
it("should use canonical link", () => {
mockConsole();
const link = document.createElement("link");
link.rel = "canonical";
link.href = "http://localhost/canonical";
document.head.appendChild(link);
const TalkEmbedStream = Coral.Talk.createStreamEmbed({
id: "basic-integration-test-id",
});
TalkEmbedStream.render();
expect(container.innerHTML).toMatchSnapshot();
document.head.removeChild(link);
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).not.toHaveBeenCalled();
});
it("should remove iframe", () => {
streamInterface.remove();
mockConsole();
const TalkEmbedStream = Coral.Talk.createStreamEmbed({
id: "basic-integration-test-id",
});
TalkEmbedStream.render();
TalkEmbedStream.remove();
expect(container.innerHTML).toBe("");
// tslint:disable-next-line:no-console
expect(console.warn).toHaveBeenCalledTimes(1);
expect(console.error).not.toHaveBeenCalled();
});
});
+2 -32
View File
@@ -1,32 +1,2 @@
import { EventEmitter2 } from "eventemitter2";
import qs from "query-string";
import createStreamInterface from "./Stream";
export interface Config {
assetID?: string;
assetURL?: string;
commentID?: string;
rootURL?: string;
id?: string;
events?: (eventEmitter: EventEmitter2) => void;
}
export function render(config: Config = {}) {
// Parse query params
const query = qs.parse(location.search);
const eventEmitter = new EventEmitter2({ wildcard: true });
if (config.events) {
config.events(eventEmitter);
}
return createStreamInterface({
assetID: config.assetID || query.assetID,
assetURL: config.assetURL || query.assetURL,
commentID: config.commentID || query.commentID,
id: config.id || "talk-embed-stream",
rootURL: config.rootURL || location.origin,
eventEmitter,
});
}
import * as TalkImport from "./Talk";
export const Talk = TalkImport;
+20
View File
@@ -0,0 +1,20 @@
export default function onIntersect(el: HTMLElement, callback: () => void) {
if (!IntersectionObserver) {
// tslint:disable-next-line:no-console
console.warn("IntersectionObserver not available");
callback();
return;
}
const options = {
rootMargin: "100px",
threshold: 1.0,
};
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
observer.disconnect();
callback();
}
}, options);
observer.observe(el);
}
+1
View File
@@ -2,3 +2,4 @@ export { default as buildURL } from "./buildURL";
export { default as ensureEndSlash } from "./ensureEndSlash";
export { default as startsWith } from "./startsWith";
export { default as prefixStorage } from "./prefixStorage";
export { default as parseHashQuery } from "./parseHashQuery";
@@ -0,0 +1,24 @@
import parseHashQuery from "./parseHashQuery";
it("should parse hash", () => {
const testCases: Array<[string, ReturnType<typeof parseHashQuery>]> = [
[
"#commentID=comment-id",
{
commentID: "comment-id",
},
],
[
"#commentID=comment-id&assetURL=asset-url",
{
commentID: "comment-id",
assetURL: "asset-url",
},
],
["#", {}],
["", {}],
];
testCases.forEach(tc => {
expect(parseHashQuery(tc[0])).toEqual(tc[1]);
});
});
@@ -0,0 +1,5 @@
import qs from "query-string";
export default function parseQueryHash(hash: string): Record<string, string> {
return qs.parse(hash);
}
+1
View File
@@ -1,2 +1,3 @@
export { default as buildURL } from "./buildURL";
export { default as parseURL } from "./parseURL";
export { default as modifyQuery } from "./modifyQuery";
@@ -0,0 +1,30 @@
import modifyQuery from "./modifyQuery";
it("should modify query", () => {
const testCases: Array<[string, Record<string, any>, string]> = [
[
"http://localhost:8080/?a=b#hash",
{
c: "d",
},
"http://localhost:8080/?a=b&c=d#hash",
],
[
"http://localhost:8080/#hash",
{
a: "b",
},
"http://localhost:8080/?a=b#hash",
],
[
"http://localhost:8080/?a=b#hash",
{
a: undefined,
},
"http://localhost:8080/#hash",
],
];
testCases.forEach(([url, params, expected]) => {
expect(modifyQuery(url, params)).toEqual(expected);
});
});
@@ -0,0 +1,11 @@
import qs from "query-string";
import buildURL from "./buildURL";
import parseURL from "./parseURL";
export default function modifyQuery(url: string, params: {}) {
const parsed = parseURL(url);
const query = qs.parse(parsed.search);
parsed.search = qs.stringify({ ...query, ...params });
return buildURL(parsed);
}
@@ -0,0 +1,24 @@
import parseHashQuery from "./parseHashQuery";
it("should parse hash", () => {
const testCases: Array<[string, ReturnType<typeof parseHashQuery>]> = [
[
"#commentID=comment-id",
{
commentID: "comment-id",
},
],
[
"#commentID=comment-id&assetURL=asset-url",
{
commentID: "comment-id",
assetURL: "asset-url",
},
],
["#", {}],
["", {}],
];
testCases.forEach(([url, expected]) => {
expect(parseHashQuery(url)).toEqual(expected);
});
});
@@ -0,0 +1,5 @@
import qs from "query-string";
export default function parseQueryHash(hash: string): Record<string, string> {
return qs.parse(hash);
}
@@ -42,12 +42,8 @@ export default async function initLocalState(
localRecord.setValue(query.assetID, "assetID");
}
// Saving location host for permalink until we get the asset url - the url now points to the tenant
if (location && query.assetID) {
localRecord.setValue(
`${location.origin}/?assetID=${query.assetID}`,
"assetURL"
);
if (query.assetURL) {
localRecord.setValue(query.assetURL, "assetURL");
}
if (query.commentID) {
@@ -7,6 +7,7 @@ import Comment from "./Comment";
it("renders username and body", () => {
const props: PropTypesOf<typeof Comment> = {
id: "comment-id",
author: {
username: "Marvin",
},
@@ -10,6 +10,7 @@ import TopBarLeft from "./TopBarLeft";
import Username from "./Username";
export interface CommentProps {
id?: string;
className?: string;
author: {
username: string | null;
@@ -28,6 +29,7 @@ const Comment: StatelessComponent<CommentProps> = props => {
className={styles.topBar}
direction="row"
justifyContent="space-between"
id={props.id}
>
<TopBarLeft>
{props.author &&
@@ -8,6 +8,7 @@ exports[`renders username and body 1`] = `
<withPropsOnChange(Flex)
className="Comment-topBar"
direction="row"
id="comment-id"
justifyContent="space-between"
>
<TopBarLeft>
@@ -6,6 +6,30 @@
border-left: 3px solid var(--palette-grey-darkest);
}
.level2 {
padding-left: var(--spacing-unit);
margin-left: var(--spacing-unit);
border-left: 3px solid var(--palette-grey-dark);
}
.level3 {
padding-left: var(--spacing-unit);
margin-left: calc(2 * var(--spacing-unit));
border-left: 3px solid var(--palette-grey-main);
}
.level4 {
padding-left: var(--spacing-unit);
margin-left: calc(3 * var(--spacing-unit));
border-left: 3px solid var(--palette-grey-light);
}
.level5 {
padding-left: var(--spacing-unit);
margin-left: calc(4 * var(--spacing-unit));
border-left: 3px solid var(--palette-grey-lighter);
}
.noBorder {
border: 0;
}
@@ -12,13 +12,19 @@ export interface IndentProps {
const Indent: StatelessComponent<IndentProps> = props => {
return (
<div
className={cn(props.className, styles.root, {
[styles.level1]: props.level === 1,
[styles.noBorder]: props.noBorder,
})}
>
{props.children}
<div className={cn(props.className, styles.root)}>
<div
className={cn({
[styles.level1]: props.level === 1,
[styles.level2]: props.level === 2,
[styles.level3]: props.level === 3,
[styles.level4]: props.level === 4,
[styles.level5]: props.level === 5,
[styles.noBorder]: props.noBorder,
})}
>
{props.children}
</div>
</div>
);
};
@@ -15,7 +15,7 @@ import PermalinkPopover from "./PermalinkPopover";
interface PermalinkProps {
commentID: string;
assetURL: string | null;
url: string;
}
class Permalink extends React.Component<PermalinkProps> {
@@ -28,7 +28,7 @@ class Permalink extends React.Component<PermalinkProps> {
);
public render() {
const { commentID, assetURL } = this.props;
const { commentID, url } = this.props;
const popoverID = `permalink-popover-${commentID}`;
return (
<Popover
@@ -43,7 +43,7 @@ class Permalink extends React.Component<PermalinkProps> {
}
>
<PermalinkPopover
permalinkURL={`${assetURL}&commentID=${commentID}`}
permalinkURL={url}
toggleVisibility={toggleVisibility}
/>
</ClickOutside>
@@ -21,6 +21,17 @@ export interface ReplyListProps {
hasMore: boolean;
disableShowAll: boolean;
indentLevel?: number;
ReplyListComponent?: React.ComponentType<any>;
}
function getReplyListElement(
{ ReplyListComponent, me, asset }: ReplyListProps,
comment: PropTypesOf<typeof CommentContainer>["comment"]
) {
if (!ReplyListComponent) {
return null;
}
return <ReplyListComponent me={me} comment={comment} asset={asset} />;
}
const ReplyList: StatelessComponent<ReplyListProps> = props => {
@@ -30,13 +41,16 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
role="log"
>
{props.comments.map(comment => (
<CommentContainer
key={comment.id}
me={props.me}
comment={comment}
asset={props.asset}
indentLevel={props.indentLevel}
/>
<HorizontalGutter key={comment.id}>
<CommentContainer
key={comment.id}
me={props.me}
comment={comment}
asset={props.asset}
indentLevel={props.indentLevel}
/>
{getReplyListElement(props, comment)}
</HorizontalGutter>
))}
{props.hasMore && (
<Indent level={props.indentLevel} noBorder>
@@ -4,28 +4,40 @@ exports[`renders level0 1`] = `
<div
className="Indent-root"
>
<div>
Hello World
<div
className=""
>
<div>
Hello World
</div>
</div>
</div>
`;
exports[`renders level1 1`] = `
<div
className="Indent-root Indent-level1"
className="Indent-root"
>
<div>
Hello World
<div
className="Indent-level1"
>
<div>
Hello World
</div>
</div>
</div>
`;
exports[`renders without border 1`] = `
<div
className="Indent-root Indent-level1 Indent-noBorder"
className="Indent-root"
>
<div>
Hello World
<div
className="Indent-level1 Indent-noBorder"
>
<div>
Hello World
</div>
</div>
</div>
`;
@@ -5,36 +5,44 @@ exports[`renders correctly 1`] = `
id="talk-comments-replyList-log--comment-id"
role="log"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
comment={
Object {
"id": "comment-1",
}
}
indentLevel={1}
<withPropsOnChange(HorizontalGutter)
key="comment-1"
me={null}
/>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
}
comment={
Object {
"id": "comment-2",
comment={
Object {
"id": "comment-1",
}
}
}
indentLevel={1}
indentLevel={1}
key="comment-1"
me={null}
/>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
key="comment-2"
me={null}
/>
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
comment={
Object {
"id": "comment-2",
}
}
indentLevel={1}
key="comment-2"
me={null}
/>
</withPropsOnChange(HorizontalGutter)>
</withPropsOnChange(HorizontalGutter)>
`;
@@ -43,36 +51,44 @@ exports[`when there is more disables load more button 1`] = `
id="talk-comments-replyList-log--comment-id"
role="log"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
comment={
Object {
"id": "comment-1",
}
}
indentLevel={1}
<withPropsOnChange(HorizontalGutter)
key="comment-1"
me={null}
/>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
}
comment={
Object {
"id": "comment-2",
comment={
Object {
"id": "comment-1",
}
}
}
indentLevel={1}
indentLevel={1}
key="comment-1"
me={null}
/>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
key="comment-2"
me={null}
/>
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
comment={
Object {
"id": "comment-2",
}
}
indentLevel={1}
key="comment-2"
me={null}
/>
</withPropsOnChange(HorizontalGutter)>
<Indent
level={1}
noBorder={true}
@@ -100,36 +116,44 @@ exports[`when there is more renders a load more button 1`] = `
id="talk-comments-replyList-log--comment-id"
role="log"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
comment={
Object {
"id": "comment-1",
}
}
indentLevel={1}
<withPropsOnChange(HorizontalGutter)
key="comment-1"
me={null}
/>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
}
comment={
Object {
"id": "comment-2",
comment={
Object {
"id": "comment-1",
}
}
}
indentLevel={1}
indentLevel={1}
key="comment-1"
me={null}
/>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
key="comment-2"
me={null}
/>
>
<withContext(createMutationContainer(Relay(CommentContainer)))
asset={
Object {
"id": "asset-id",
}
}
comment={
Object {
"id": "comment-2",
}
}
indentLevel={1}
key="comment-2"
me={null}
/>
</withPropsOnChange(HorizontalGutter)>
<Indent
level={1}
noBorder={true}
@@ -35,7 +35,7 @@ exports[`renders correctly 1`] = `
}
me={null}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -67,7 +67,7 @@ exports[`renders correctly 1`] = `
}
me={null}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -121,7 +121,7 @@ exports[`when there is more disables load more button 1`] = `
}
me={null}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -153,7 +153,7 @@ exports[`when there is more disables load more button 1`] = `
}
me={null}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -221,7 +221,7 @@ exports[`when there is more renders a load more button 1`] = `
}
me={null}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -253,7 +253,7 @@ exports[`when there is more renders a load more button 1`] = `
}
me={null}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -323,7 +323,7 @@ exports[`when use is logged in renders correctly 1`] = `
}
me={Object {}}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -355,7 +355,7 @@ exports[`when use is logged in renders correctly 1`] = `
}
me={Object {}}
/>
<Relay(ReplyListContainer)
<withProps(Relay(ReplyListContainer))
asset={
Object {
"id": "asset-id",
@@ -120,6 +120,7 @@ export class CommentContainer extends Component<InnerProps, State> {
return (
<>
<Comment
id={`comment-${comment.id}`}
indentLevel={indentLevel}
author={comment.author}
body={comment.body}
@@ -1,6 +1,7 @@
import React, { StatelessComponent } from "react";
import { graphql } from "react-relay";
import { withLocalStateContainer } from "talk-framework/lib/relay";
import { modifyQuery } from "talk-framework/utils";
import { PermalinkButtonContainerLocal as Local } from "talk-stream/__generated__/PermalinkButtonContainerLocal.graphql";
import PermalinkButton from "../components/PermalinkButton";
@@ -15,7 +16,10 @@ export const PermalinkContainer: StatelessComponent<InnerProps> = ({
commentID,
}) => {
return local.assetURL ? (
<PermalinkButton assetURL={local.assetURL} commentID={commentID} />
<PermalinkButton
commentID={commentID}
url={modifyQuery(local.assetURL, { commentID })}
/>
) : null;
};
@@ -40,6 +40,18 @@ class PermalinkViewContainer extends React.Component<
// Remove the commentId url param.
return buildURL({ ...urlParts, search });
}
public componentDidMount() {
if (this.props.pym) {
const scrollTo = this.props.comment
? document
.getElementById(`comment-${this.props.comment.id}`)!
.getBoundingClientRect().top + window.pageYOffset
: 50;
setTimeout(() => this.props.pym!.scrollParentToChildPos(scrollTo), 100);
}
}
public render() {
const { comment, asset, me } = this.props;
return (
@@ -66,6 +78,7 @@ const enhanced = withContext(ctx => ({
`,
comment: graphql`
fragment PermalinkViewContainer_comment on Comment {
id
...CommentContainer_comment
}
`,
@@ -27,6 +27,8 @@ it("renders correctly", () => {
isLoading: noop,
} as any,
me: null,
indentLevel: 1,
ReplyListComponent: () => null,
};
const wrapper = shallow(<ReplyListContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -46,6 +48,8 @@ it("renders correctly when replies are null", () => {
isLoading: noop,
} as any,
me: null,
indentLevel: 1,
ReplyListComponent: undefined,
};
const wrapper = shallow(<ReplyListContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -69,6 +73,8 @@ describe("when has more replies", () => {
loadMore: (_: any, callback: () => void) => (finishLoading = callback),
} as any,
me: null,
indentLevel: 1,
ReplyListComponent: undefined,
};
let wrapper: ShallowWrapper;
@@ -1,15 +1,15 @@
import React from "react";
import { graphql, RelayPaginationProp } from "react-relay";
import { graphql, GraphQLTaggedNode, RelayPaginationProp } from "react-relay";
import { withProps } from "recompose";
import { withPaginationContainer } from "talk-framework/lib/relay";
import { PropTypesOf } from "talk-framework/types";
import { ReplyListContainer_asset as AssetData } from "talk-stream/__generated__/ReplyListContainer_asset.graphql";
import { ReplyListContainer_comment as CommentData } from "talk-stream/__generated__/ReplyListContainer_comment.graphql";
import { ReplyListContainer_me as MeData } from "talk-stream/__generated__/ReplyListContainer_me.graphql";
import { ReplyListContainer1_asset as AssetData } from "talk-stream/__generated__/ReplyListContainer1_asset.graphql";
import { ReplyListContainer1_comment as CommentData } from "talk-stream/__generated__/ReplyListContainer1_comment.graphql";
import { ReplyListContainer1_me as MeData } from "talk-stream/__generated__/ReplyListContainer1_me.graphql";
import {
COMMENT_SORT,
ReplyListContainerPaginationQueryVariables,
} from "talk-stream/__generated__/ReplyListContainerPaginationQuery.graphql";
ReplyListContainer1PaginationQueryVariables,
} from "talk-stream/__generated__/ReplyListContainer1PaginationQuery.graphql";
import ReplyList from "../components/ReplyList";
@@ -18,8 +18,16 @@ export interface InnerProps {
asset: AssetData;
comment: CommentData;
relay: RelayPaginationProp;
indentLevel: number;
ReplyListComponent: React.ComponentType<any> | undefined;
}
// TODO: (cvle) This should be autogenerated.
interface FragmentVariables {
count: number;
cursor?: string;
orderBy: COMMENT_SORT;
}
export class ReplyListContainer extends React.Component<InnerProps> {
public state = {
disableShowAll: false,
@@ -42,7 +50,8 @@ export class ReplyListContainer extends React.Component<InnerProps> {
onShowAll={this.showAll}
hasMore={this.props.relay.hasMore()}
disableShowAll={this.state.disableShowAll}
indentLevel={1}
indentLevel={this.props.indentLevel}
ReplyListComponent={this.props.ReplyListComponent}
/>
);
}
@@ -66,31 +75,61 @@ export class ReplyListContainer extends React.Component<InnerProps> {
};
}
// TODO: (cvle) This should be autogenerated.
interface FragmentVariables {
count: number;
cursor?: string;
orderBy: COMMENT_SORT;
function createReplyListContainer(
indentLevel: number,
fragments: {
me: GraphQLTaggedNode;
asset: GraphQLTaggedNode;
comment: GraphQLTaggedNode;
},
query: GraphQLTaggedNode,
ReplyListComponent?: React.ComponentType<any>
) {
return withProps({ indentLevel, ReplyListComponent })(
withPaginationContainer<
InnerProps,
ReplyListContainer1PaginationQueryVariables,
FragmentVariables
>(fragments, {
direction: "forward",
getConnectionFromProps(props) {
return props.comment && props.comment.replies;
},
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, { count, cursor }, fragmentVariables) {
return {
count,
cursor,
orderBy: fragmentVariables.orderBy,
commentID: props.comment.id,
};
},
query,
})(ReplyListContainer)
);
}
const enhanced = withPaginationContainer<
InnerProps,
ReplyListContainerPaginationQueryVariables,
FragmentVariables
>(
const ReplyListContainer5 = createReplyListContainer(
5,
{
me: graphql`
fragment ReplyListContainer_me on User {
fragment ReplyListContainer5_me on User {
...CommentContainer_me
}
`,
asset: graphql`
fragment ReplyListContainer_asset on Asset {
fragment ReplyListContainer5_asset on Asset {
...CommentContainer_asset
}
`,
comment: graphql`
fragment ReplyListContainer_comment on Comment
fragment ReplyListContainer5_comment on Comment
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
@@ -109,43 +148,237 @@ const enhanced = withPaginationContainer<
}
`,
},
graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ReplyListContainer5PaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$commentID: ID!
) {
comment(id: $commentID) {
...ReplyListContainer5_comment
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
}
}
`
);
const ReplyListContainer4 = createReplyListContainer(
4,
{
direction: "forward",
getConnectionFromProps(props) {
return props.comment && props.comment.replies;
},
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, { count, cursor }, fragmentVariables) {
return {
count,
cursor,
orderBy: fragmentVariables.orderBy,
commentID: props.comment.id,
};
},
query: graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ReplyListContainerPaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$commentID: ID!
) {
comment(id: $commentID) {
...ReplyListContainer_comment
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
me: graphql`
fragment ReplyListContainer4_me on User {
...ReplyListContainer5_me
...CommentContainer_me
}
`,
asset: graphql`
fragment ReplyListContainer4_asset on Asset {
...ReplyListContainer5_asset
...CommentContainer_asset
}
`,
comment: graphql`
fragment ReplyListContainer4_comment on Comment
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC }
) {
id
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...ReplyListContainer5_comment
}
}
}
}
`,
}
)(ReplyListContainer);
},
graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ReplyListContainer4PaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$commentID: ID!
) {
comment(id: $commentID) {
...ReplyListContainer4_comment
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
}
}
`,
ReplyListContainer5
);
export type ReplyListContainerProps = PropTypesOf<typeof enhanced>;
export default enhanced;
const ReplyListContainer3 = createReplyListContainer(
3,
{
me: graphql`
fragment ReplyListContainer3_me on User {
...ReplyListContainer4_me
...CommentContainer_me
}
`,
asset: graphql`
fragment ReplyListContainer3_asset on Asset {
...ReplyListContainer4_asset
...CommentContainer_asset
}
`,
comment: graphql`
fragment ReplyListContainer3_comment on Comment
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC }
) {
id
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...ReplyListContainer4_comment
}
}
}
}
`,
},
graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ReplyListContainer3PaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$commentID: ID!
) {
comment(id: $commentID) {
...ReplyListContainer3_comment
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
}
}
`,
ReplyListContainer4
);
const ReplyListContainer2 = createReplyListContainer(
2,
{
me: graphql`
fragment ReplyListContainer2_me on User {
...ReplyListContainer3_me
...CommentContainer_me
}
`,
asset: graphql`
fragment ReplyListContainer2_asset on Asset {
...ReplyListContainer3_asset
...CommentContainer_asset
}
`,
comment: graphql`
fragment ReplyListContainer2_comment on Comment
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC }
) {
id
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...ReplyListContainer3_comment
}
}
}
}
`,
},
graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ReplyListContainer2PaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$commentID: ID!
) {
comment(id: $commentID) {
...ReplyListContainer2_comment
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
}
}
`,
ReplyListContainer3
);
const ReplyListContainer1 = createReplyListContainer(
1,
{
me: graphql`
fragment ReplyListContainer1_me on User {
...ReplyListContainer2_me
...CommentContainer_me
}
`,
asset: graphql`
fragment ReplyListContainer1_asset on Asset {
...ReplyListContainer2_asset
...CommentContainer_asset
}
`,
comment: graphql`
fragment ReplyListContainer1_comment on Comment
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC }
) {
id
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...ReplyListContainer2_comment
}
}
}
}
`,
},
graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ReplyListContainer1PaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$commentID: ID!
) {
comment(id: $commentID) {
...ReplyListContainer1_comment
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
}
}
`,
ReplyListContainer2
);
export default ReplyListContainer1;
@@ -23,7 +23,7 @@ graphql`
fragment StreamContainer_comment on Comment {
id
...CommentContainer_comment
...ReplyListContainer_comment
...ReplyListContainer1_comment
}
`;
@@ -95,12 +95,12 @@ const enhanced = withPaginationContainer<
}
}
...CommentContainer_asset
...ReplyListContainer_asset
...ReplyListContainer1_asset
}
`,
me: graphql`
fragment StreamContainer_me on User {
...ReplyListContainer_me
...ReplyListContainer1_me
...CommentContainer_me
...UserBoxContainer_me
}
@@ -135,7 +135,7 @@ const enhanced = withPaginationContainer<
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$assetID: ID!
$assetID: ID
) {
asset(id: $assetID) {
...StreamContainer_asset
@@ -24,6 +24,7 @@ exports[`renders body only 1`] = `
/>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
showEditedMarker={false}
/>
@@ -54,6 +55,7 @@ exports[`renders username and body 1`] = `
/>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
showEditedMarker={false}
/>
@@ -2,6 +2,7 @@
exports[`renders correctly 1`] = `
<ReplyList
ReplyListComponent={[Function]}
asset={
Object {
"id": "asset-id",
@@ -45,15 +45,19 @@ export const render = ({
};
const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
local: { commentID, assetID },
local: { commentID, assetID, assetURL },
}) => (
<QueryRenderer<QueryTypes>
query={graphql`
query PermalinkViewQuery($commentID: ID!, $assetID: ID!) {
query PermalinkViewQuery(
$commentID: ID!
$assetID: ID
$assetURL: String
) {
me {
...PermalinkViewContainer_me
}
asset(id: $assetID) {
asset(id: $assetID, url: $assetURL) {
...PermalinkViewContainer_asset
}
comment(id: $commentID) {
@@ -62,8 +66,9 @@ const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
}
`}
variables={{
assetID: assetID!,
commentID: commentID!,
assetID,
assetURL,
}}
render={render}
/>
@@ -74,6 +79,7 @@ const enhanced = withLocalStateContainer(
fragment PermalinkViewQueryLocal on Local {
assetID
commentID
assetURL
}
`
)(PermalinkViewQuery);
@@ -38,21 +38,22 @@ export const render = ({
};
const StreamQuery: StatelessComponent<InnerProps> = ({
local: { assetID },
local: { assetID, assetURL },
}) => (
<QueryRenderer<QueryTypes>
query={graphql`
query StreamQuery($assetID: ID!) {
query StreamQuery($assetID: ID, $assetURL: String) {
me {
...StreamContainer_me
}
asset(id: $assetID) {
asset(id: $assetID, url: $assetURL) {
...StreamContainer_asset
}
}
`}
variables={{
assetID: assetID!,
assetID,
assetURL,
}}
render={render}
/>
@@ -62,6 +63,7 @@ const enhanced = withLocalStateContainer(
graphql`
fragment StreamQueryLocal on Local {
assetID
assetURL
}
`
)(StreamQuery);
File diff suppressed because it is too large Load Diff
@@ -152,60 +152,65 @@ exports[`loads more comments 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -217,60 +222,65 @@ exports[`loads more comments 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:20:00.000Z
</time>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -282,60 +292,65 @@ exports[`loads more comments 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-2"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Isabelle
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:14:00.000Z
</time>
Isabelle
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
>
2018-07-06T18:14:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Hey!",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Hey!",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-2"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-2"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -497,60 +512,65 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -562,60 +582,65 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:20:00.000Z
</time>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -27,60 +27,65 @@ exports[`renders permalink view 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -240,60 +245,65 @@ exports[`show all comments 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -184,60 +184,65 @@ exports[`show all comments 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -185,36 +185,67 @@ exports[`post a comment: optimistic response 1`] = `
className="IndentedComment-blur Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-uuid-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
id="comments-commentContainer-editButton-uuid-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
<div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "<strong>Hello world!</strong>",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
id="comments-commentContainer-editButton-uuid-0"
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-uuid-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -224,37 +255,81 @@ exports[`post a comment: optimistic response 1`] = `
onTouchEnd={[Function]}
type="button"
>
Edit
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className=""
>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "<strong>Hello world!</strong>",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
className="Comment-root"
role="article"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-uuid-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span>
Reply
</span>
</button>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -266,125 +341,65 @@ exports[`post a comment: optimistic response 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -585,60 +600,65 @@ exports[`post a comment: server response 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-x"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "<strong>Hello world! (from server)</strong>",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "<strong>Hello world! (from server)</strong>",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-x"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-x"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -650,60 +670,65 @@ exports[`post a comment: server response 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -715,60 +740,65 @@ exports[`post a comment: server response 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:20:00.000Z
</time>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -969,60 +999,65 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -1034,60 +1069,65 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:20:00.000Z
</time>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -152,135 +152,7 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "I like yoghurt",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-with-replies"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-replyList-log--comment-with-replies"
role="log"
>
<div
className="Indent-root Indent-level1"
className=""
>
<div
className="Comment-root"
@@ -288,6 +160,7 @@ exports[`renders comment stream 1`] = `
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
@@ -340,8 +213,16 @@ exports[`renders comment stream 1`] = `
</div>
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className="Indent-root Indent-level1"
className=""
>
<div
className="Comment-root"
@@ -349,6 +230,7 @@ exports[`renders comment stream 1`] = `
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-with-deep-replies"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
@@ -356,17 +238,17 @@ exports[`renders comment stream 1`] = `
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:20:00.000Z
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
@@ -375,7 +257,7 @@ exports[`renders comment stream 1`] = `
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
"__html": "I like yoghurt",
}
}
/>
@@ -384,7 +266,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
id="comments-commentContainer-replyButton-comment-with-deep-replies"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -402,6 +284,298 @@ exports[`renders comment stream 1`] = `
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-replyList-log--comment-with-deep-replies"
role="log"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className="Indent-level1"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-with-replies"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "I like yoghurt",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-with-replies"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-replyList-log--comment-with-replies"
role="log"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className="Indent-level2"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-3"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Isabelle
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
>
2018-07-06T18:14:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Comment Body 3",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-3"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className="Indent-level2"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-4"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Isabelle
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
>
2018-07-06T18:14:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Comment Body 4",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-4"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className="Indent-level1"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-5"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Isabelle
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
>
2018-07-06T18:14:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Comment Body 5",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-5"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -152,60 +152,65 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -217,60 +222,65 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:20:00.000Z
</time>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -152,70 +152,7 @@ exports[`renders comment stream 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-replyList-log--comment-0"
role="log"
>
<div
className="Indent-root Indent-level1"
className=""
>
<div
className="Comment-root"
@@ -223,6 +160,7 @@ exports[`renders comment stream 1`] = `
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
@@ -230,17 +168,17 @@ exports[`renders comment stream 1`] = `
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:20:00.000Z
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
@@ -249,7 +187,7 @@ exports[`renders comment stream 1`] = `
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
"__html": "Joining Too",
}
}
/>
@@ -258,7 +196,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -275,25 +213,105 @@ exports[`renders comment stream 1`] = `
</div>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-replyList-log--comment-0"
role="log"
>
<div
className="Indent-root Indent-level1 Indent-noBorder"
className="HorizontalGutter-root HorizontalGutter-full"
>
<button
aria-controls="talk-comments-replyList-log--comment-0"
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantOutlined Button-fullWidth"
disabled={false}
id="talk-comments-replyList-showAll--comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Indent-root"
>
Show All
</button>
<div
className="Indent-level1"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
className="Indent-root"
>
<div
className="Indent-level1 Indent-noBorder"
>
<button
aria-controls="talk-comments-replyList-log--comment-0"
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantOutlined Button-fullWidth"
disabled={false}
id="talk-comments-replyList-showAll--comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Show All
</button>
</div>
</div>
</div>
</div>
@@ -454,60 +472,65 @@ exports[`show all replies 1`] = `
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<span>
Reply
</span>
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -517,125 +540,143 @@ exports[`show all replies 1`] = `
role="log"
>
<div
className="Indent-root Indent-level1"
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
className="Indent-root"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Indent-level1"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Comment-root"
role="article"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
2018-07-06T18:20:00.000Z
</time>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
<div
className="Indent-root Indent-level1"
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
className="Indent-root"
>
<div
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
className="Indent-level1"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Comment-root"
role="article"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Isabelle
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Comment-topBar Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-2"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
2018-07-06T18:14:00.000Z
</time>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Isabelle
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
>
2018-07-06T18:14:00.000Z
</time>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Hey!",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-2"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Hey!",
}
}
/>
<div
className="Flex-root Comment-footer Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-2"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
</div>
</div>
</div>
</div>
@@ -11,7 +11,10 @@ function createTestRenderer() {
Query: {
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0])
s =>
s
.withArgs(undefined, { id: assets[0].id, url: null })
.returns(assets[0])
),
me: createSinonStub(
s => s.throws(),
@@ -1,4 +1,5 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
@@ -55,7 +56,15 @@ beforeEach(() => {
Query: {
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub)
s =>
s
.withArgs(
undefined,
sinon
.match({ id: assetStub.id, url: null })
.or(sinon.match({ id: assetStub.id }))
)
.returns(assetStub)
),
},
};
@@ -36,7 +36,10 @@ beforeEach(() => {
),
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub)
s =>
s
.withArgs(undefined, { id: assetStub.id, url: null })
.returns(assetStub)
),
},
};
@@ -33,7 +33,10 @@ beforeEach(() => {
comment: () => null,
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub)
s =>
s
.withArgs(undefined, { id: assetStub.id, url: null })
.returns(assetStub)
),
},
};
@@ -3,7 +3,7 @@ import { ReactTestRenderer } from "react-test-renderer";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import { assetWithReplies } from "../fixtures";
import { assetWithDeepReplies } from "../fixtures";
import create from "./create";
let testRenderer: ReactTestRenderer;
@@ -14,8 +14,8 @@ beforeEach(() => {
s => s.throws(),
s =>
s
.withArgs(undefined, { id: assetWithReplies.id })
.returns(assetWithReplies)
.withArgs(undefined, { id: assetWithDeepReplies.id, url: null })
.returns(assetWithDeepReplies)
),
},
};
@@ -25,7 +25,7 @@ beforeEach(() => {
logNetwork: false,
resolvers,
initLocalState: localRecord => {
localRecord.setValue(assetWithReplies.id, "assetID");
localRecord.setValue(assetWithDeepReplies.id, "assetID");
},
}));
});
@@ -12,7 +12,10 @@ beforeEach(() => {
Query: {
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0])
s =>
s
.withArgs(undefined, { id: assets[0].id, url: null })
.returns(assets[0])
),
},
};
@@ -71,7 +71,10 @@ beforeEach(() => {
),
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub)
s =>
s
.withArgs(undefined, { id: assetStub.id, url: null })
.returns(assetStub)
),
},
};
+73 -2
View File
@@ -47,6 +47,39 @@ export const comments = [
editableUntil: "2018-07-06T18:14:30.000Z",
},
},
{
id: "comment-3",
author: users[2],
body: "Comment Body 3",
createdAt: "2018-07-06T18:14:00.000Z",
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
editing: {
edited: false,
editableUntil: "2018-07-06T18:14:30.000Z",
},
},
{
id: "comment-4",
author: users[2],
body: "Comment Body 4",
createdAt: "2018-07-06T18:14:00.000Z",
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
editing: {
edited: false,
editableUntil: "2018-07-06T18:14:30.000Z",
},
},
{
id: "comment-5",
author: users[2],
body: "Comment Body 5",
createdAt: "2018-07-06T18:14:00.000Z",
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
editing: {
edited: false,
editableUntil: "2018-07-06T18:14:30.000Z",
},
},
];
export const assets = [
@@ -73,8 +106,28 @@ export const commentWithReplies = {
createdAt: "2018-07-06T18:24:00.000Z",
replies: {
edges: [
{ node: comments[0], cursor: comments[0].createdAt },
{ node: comments[1], cursor: comments[1].createdAt },
{ node: comments[3], cursor: comments[3].createdAt },
{ node: comments[4], cursor: comments[4].createdAt },
],
pageInfo: {
hasNextPage: false,
},
},
editing: {
edited: false,
editableUntil: "2018-07-06T18:24:30.000Z",
},
};
export const commentWithDeepReplies = {
id: "comment-with-deep-replies",
author: users[0],
body: "I like yoghurt",
createdAt: "2018-07-06T18:24:00.000Z",
replies: {
edges: [
{ node: commentWithReplies, cursor: commentWithReplies.createdAt },
{ node: comments[5], cursor: comments[5].createdAt },
],
pageInfo: {
hasNextPage: false,
@@ -100,3 +153,21 @@ export const assetWithReplies = {
},
},
};
export const assetWithDeepReplies = {
id: "asset-with-deep-replies",
url: "http://localhost/assets/asset-with-replies",
isClosed: false,
comments: {
edges: [
{ node: comments[0], cursor: comments[0].createdAt },
{
node: commentWithDeepReplies,
cursor: commentWithDeepReplies.createdAt,
},
],
pageInfo: {
hasNextPage: false,
},
},
};
@@ -0,0 +1,2 @@
// Automatically unmock console.
import "jest-mock-console/dist/setupTestFramework";
@@ -4,65 +4,34 @@ import { Config } from "talk-common/config";
import {
createJWTSigningConfig,
extractJWTFromRequest,
parseAuthHeader,
} from "talk-server/app/middleware/passport/jwt";
import { Request } from "talk-server/types/express";
describe("parseAuthHeader", () => {
it("parses valid headers", () => {
const parsed = {
scheme: "bearer",
value: "token",
};
expect(parseAuthHeader("Bearer token")).toEqual(parsed);
expect(parseAuthHeader("bearer token")).toEqual(parsed);
expect(parseAuthHeader("bearer token")).toEqual(parsed);
});
it("parses invalid headers", () => {
expect(parseAuthHeader("this-is-a-wrong-header")).toEqual(null);
expect(parseAuthHeader("bearerthis-is-a-wrong-header")).toEqual(null);
});
});
describe("extractJWTFromRequest", () => {
it("extracts the token from header", () => {
const req = {
get: sinon
.stub()
.withArgs("authorization")
.returns("Bearer token"),
headers: {
authorization: "Bearer token",
},
url: "",
};
expect(extractJWTFromRequest((req as any) as Request)).toEqual("token");
expect(req.get.calledOnce).toBeTruthy();
req.get.reset();
req.get.returns(null);
delete req.headers.authorization;
expect(extractJWTFromRequest((req as any) as Request)).toEqual(null);
expect(req.get.calledOnce).toBeTruthy();
});
it("extracts the token from query string", () => {
const req = {
get: sinon
.stub()
.withArgs("authorization")
.returns(null),
query: { access_token: "token" },
url: "",
};
expect(extractJWTFromRequest((req as any) as Request)).toEqual(null);
req.url = "https://talk.coralproject.net/api?access_token=token";
expect(extractJWTFromRequest((req as any) as Request)).toEqual("token");
expect(req.get.calledOnce).toBeTruthy();
delete req.query.access_token;
req.get.reset();
expect(extractJWTFromRequest((req as any) as Request)).toEqual(null);
expect(req.get.calledOnce).toBeTruthy();
});
});
+6 -27
View File
@@ -2,41 +2,20 @@ import { Redis } from "ioredis";
import jwt, { SignOptions } from "jsonwebtoken";
import { Db } from "mongodb";
import { Strategy } from "passport-strategy";
import { Bearer } from "permit";
import uuid from "uuid";
import { Config } from "talk-common/config";
import { retrieveUser, User } from "talk-server/models/user";
import { Request } from "talk-server/types/express";
const authHeaderRegex = /(\S+)\s+(\S+)/;
export function parseAuthHeader(header: string) {
const matches = header.match(authHeaderRegex);
if (!matches || matches.length < 3) {
return null;
}
return {
scheme: matches[1].toLowerCase(),
value: matches[2],
};
}
export function extractJWTFromRequest(req: Request) {
const header = req.get("authorization");
if (header) {
const parts = parseAuthHeader(header);
if (parts && parts.scheme === "bearer") {
return parts.value;
}
}
const permit = new Bearer({
basic: "password",
query: "access_token",
});
const token: string | undefined | false = req.query && req.query.access_token;
if (token) {
return token;
}
return null;
return permit.check(req) || null;
}
function generateJTIBlacklistKey(jti: string) {
+31
View File
@@ -0,0 +1,31 @@
// TODO: (wyattjoh) following https://github.com/DefinitelyTyped/DefinitelyTyped/pull/29061 to merge then replace this with @types/permit.
declare module "permit" {
import { IncomingMessage, ServerResponse } from "http";
export interface PermitOptions {
scheme?: string;
proxy?: string;
realm?: string;
}
export interface BearerOptions extends PermitOptions {
basic?: string;
header?: string;
query?: string;
}
export class Permit {
constructor(options: PermitOptions);
check(req: IncomingMessage): void;
fail(res: ServerResponse): void;
}
export class Bearer extends Permit {
constructor(options: BearerOptions);
check(req: IncomingMessage): string;
}
export class Basic extends Permit {
check(req: IncomingMessage): [string, string];
}
}