Focus RTE when opening reply

This commit is contained in:
Chi Vinh Le
2018-09-06 00:06:58 +02:00
parent 110becb075
commit 444c86cb3c
26 changed files with 143 additions and 56 deletions
+1
View File
@@ -151,6 +151,7 @@
"babel-plugin-module-resolver": "^3.1.1",
"babel-plugin-relay": "^1.7.0-rc.1",
"babel-preset-react-optimize": "^1.0.1",
"bowser": "^1.9.4",
"case-sensitive-paths-webpack-plugin": "^2.1.2",
"chalk": "^2.4.1",
"chokidar": "^2.0.4",
+3 -1
View File
@@ -3,7 +3,9 @@ import { Environment, ROOT_ID } from "relay-runtime";
export default function getMe(environment: Environment) {
const source = environment.getStore().getSource();
const root = source.get(ROOT_ID)!;
const meKey = Object.keys(root).find(s => s.startsWith("me("))!;
const meKey = Object.keys(root)
.reverse()
.find(s => s.startsWith("me("))!;
if (!root[meKey]) {
return null;
}
@@ -6,6 +6,7 @@ import { MediaQueryMatchers } from "react-responsive";
import { Formatter } from "react-timeago";
import { Environment } from "relay-runtime";
import { BrowserInfo } from "talk-framework/lib/browserInfo";
import { PostMessageService } from "talk-framework/lib/postMessage";
import { RestClient } from "talk-framework/lib/rest";
import { PymStorage } from "talk-framework/lib/storage";
@@ -51,6 +52,9 @@ export interface TalkContext {
/** A pym child that interacts with the pym parent. */
pym?: PymChild;
/** Browser detection. */
browserInfo: BrowserInfo;
}
const { Provider, Consumer } = React.createContext<TalkContext>({} as any);
@@ -5,6 +5,7 @@ import { Child as PymChild } from "pym.js";
import React from "react";
import { Formatter } from "react-timeago";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import { getBrowserInfo } from "talk-framework/lib/browserInfo";
import { LOCAL_ID } from "talk-framework/lib/relay";
import {
createLocalStorage,
@@ -121,6 +122,7 @@ export default async function createContext({
sessionStorage: createSessionStorage(),
pymLocalStorage: pym && createPymStorage(pym, "localStorage"),
pymSessionStorage: pym && createPymStorage(pym, "sessionStorage"),
browserInfo: getBrowserInfo(),
};
// Run custom initializations.
@@ -0,0 +1,11 @@
import bowser from "bowser";
export interface BrowserInfo {
ios: boolean;
}
export function getBrowserInfo(): BrowserInfo {
return {
ios: bowser.ios,
};
}
+5 -1
View File
@@ -1,6 +1,6 @@
import { Blockquote, Bold, CoralRTE, Italic } from "@coralproject/rte";
import { Localized as LocalizedOriginal } from "fluent-react/compat";
import React, { StatelessComponent } from "react";
import React, { Ref, StatelessComponent } from "react";
import { Icon } from "talk-ui/components";
@@ -47,6 +47,8 @@ export interface RTEProps {
onChange?: (data: { html: string; text: string }) => void;
disabled?: boolean;
forwardRef?: Ref<CoralRTE>;
}
// tslint:disable:jsx-wrap-multiline
@@ -83,6 +85,7 @@ const RTE: StatelessComponent<RTEProps> = props => {
onChange,
disabled,
defaultValue,
forwardRef,
...rest
} = props;
return (
@@ -97,6 +100,7 @@ const RTE: StatelessComponent<RTEProps> = props => {
disabled={disabled}
placeholder={placeholder}
features={features}
ref={forwardRef}
{...rest}
/>
</div>
@@ -1,6 +1,12 @@
import { CoralRTE } from "@coralproject/rte";
import { FormState } from "final-form";
import { Localized } from "fluent-react/compat";
import React, { EventHandler, MouseEvent, StatelessComponent } from "react";
import React, {
EventHandler,
MouseEvent,
Ref,
StatelessComponent,
} from "react";
import { Field, Form, FormSpy } from "react-final-form";
import { OnSubmit } from "talk-framework/lib/form";
@@ -26,6 +32,7 @@ export interface ReplyCommentFormProps {
onCancel?: EventHandler<MouseEvent<any>>;
onChange?: (state: FormState) => void;
initialValues?: FormProps;
rteRef?: Ref<CoralRTE>;
}
const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => {
@@ -58,6 +65,7 @@ const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => {
onChange={({ html }) => input.onChange(html)}
value={input.value}
placeholder="Write a reply"
forwardRef={props.rteRef}
/>
</Localized>
{meta.touched &&
@@ -31,6 +31,7 @@ it("renders correctly", async () => {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
autofocus: false,
};
const wrapper = shallow(<ReplyCommentFormContainerN {...props} />);
@@ -50,6 +51,7 @@ it("renders with initialValues", async () => {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
autofocus: false,
};
await props.pymSessionStorage.setItem(
@@ -74,6 +76,7 @@ it("save values", async () => {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
autofocus: false,
};
await props.pymSessionStorage.setItem(
@@ -111,6 +114,7 @@ it("creates a comment", async () => {
},
pymSessionStorage: createFakePymStorage(),
onClose: onCloseStub,
autofocus: false,
};
await props.pymSessionStorage.setItem(
@@ -149,6 +153,7 @@ it("closes on cancel", async () => {
},
pymSessionStorage: createFakePymStorage(),
onClose: onCloseStub,
autofocus: false,
};
await props.pymSessionStorage.setItem(
@@ -1,3 +1,4 @@
import { CoralRTE } from "@coralproject/rte";
import React, { Component } from "react";
import { graphql } from "react-relay";
@@ -20,6 +21,7 @@ interface InnerProps {
comment: CommentData;
asset: AssetData;
onClose?: () => void;
autofocus: boolean;
}
interface State {
@@ -36,6 +38,12 @@ export class ReplyCommentFormContainer extends Component<InnerProps, State> {
this.init();
}
private handleRTERef = (rte: CoralRTE | null) => {
if (rte && this.props.autofocus) {
rte.focus();
}
};
private async init() {
const body = await this.props.pymSessionStorage.getItem(this.contextKey);
if (body) {
@@ -101,12 +109,15 @@ export class ReplyCommentFormContainer extends Component<InnerProps, State> {
onChange={this.handleOnChange}
initialValues={this.state.initialValues}
onCancel={this.handleOnCancel}
rteRef={this.handleRTERef}
/>
);
}
}
const enhanced = withContext(({ pymSessionStorage }) => ({
const enhanced = withContext(({ pymSessionStorage, browserInfo }) => ({
pymSessionStorage,
// Disable autofocus on ios and enable for the rest.
autofocus: !browserInfo.ios,
}))(
withCreateCommentMutation(
withFragmentContainer<InnerProps>({
@@ -14,7 +14,7 @@ exports[`renders body only 1`] = `
<React.Fragment>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
@@ -42,7 +42,7 @@ exports[`renders username and body 1`] = `
<React.Fragment>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
@@ -2,14 +2,17 @@
exports[`renders correctly 1`] = `
<ReplyCommentForm
id="comment-id"
onCancel={[Function]}
onChange={[Function]}
onSubmit={[Function]}
rteRef={[Function]}
/>
`;
exports[`renders with initialValues 1`] = `
<ReplyCommentForm
id="comment-id"
initialValues={
Object {
"body": "Hello World!",
@@ -18,5 +21,6 @@ exports[`renders with initialValues 1`] = `
onCancel={[Function]}
onChange={[Function]}
onSubmit={[Function]}
rteRef={[Function]}
/>
`;
@@ -181,7 +181,7 @@ exports[`loads more comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -234,7 +234,7 @@ exports[`loads more comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -287,7 +287,7 @@ exports[`loads more comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-2"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -490,7 +490,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -543,7 +543,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -56,7 +56,7 @@ exports[`renders permalink view 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -257,7 +257,7 @@ exports[`show all comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -213,7 +213,7 @@ exports[`show all comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -213,7 +213,7 @@ exports[`post a comment 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-optimistic"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -266,7 +266,7 @@ exports[`post a comment 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -319,7 +319,7 @@ exports[`post a comment 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -560,7 +560,7 @@ exports[`post a comment 2`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-x"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -613,7 +613,7 @@ exports[`post a comment 2`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -666,7 +666,7 @@ exports[`post a comment 2`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -907,7 +907,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -960,7 +960,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -816,7 +816,7 @@ exports[`post a reply 2`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-96fdca5c-0068-4339-bc7e-1d09d55d56c5"
id="comments-commentContainer-replyButton-comment-optimistic"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -181,7 +181,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -234,7 +234,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-with-replies"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -291,7 +291,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -344,7 +344,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -181,7 +181,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -234,7 +234,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -181,7 +181,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -238,7 +238,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -463,7 +463,7 @@ exports[`show all replies 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -520,7 +520,7 @@ exports[`show all replies 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -573,7 +573,7 @@ exports[`show all replies 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
id="comments-commentContainer-replyButton-comment-2"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
+1
View File
@@ -46,6 +46,7 @@ export default function create(params: CreateParams) {
pymSessionStorage: createFakePymStorage(),
rest: new RestClient("http://localhost/api"),
postMessage: new PostMessageService(),
browserInfo: { ios: false },
};
const testRenderer = TestRenderer.create(
@@ -1,9 +1,11 @@
import { noop } from "lodash";
import { ReactElement } from "react";
export default function createNodeMock(element: ReactElement<any>) {
if (element.type === "div") {
return {
innerHtml: "",
focus: noop,
};
}
return null;
@@ -1,12 +1,16 @@
import { ReactTestRenderer } from "react-test-renderer";
import timekeeper from "timekeeper";
import uuid from "uuid/v4";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import { UUIDMock } from "talk-test/mocks";
import create from "./create";
import { assets, users } from "./fixtures";
const uuidMock = uuid as UUIDMock;
let testRenderer: ReactTestRenderer;
beforeEach(() => {
const resolvers = {
@@ -66,6 +70,9 @@ it("renders comment stream", async () => {
});
it("post a comment", async () => {
// set next UUID of the optimistic response.
uuidMock.setMockData(["comment-optimistic"]);
// Wait for loading.
await timeout();
testRenderer.root
@@ -1,12 +1,16 @@
import { ReactTestRenderer } from "react-test-renderer";
import timekeeper from "timekeeper";
import uuid from "uuid/v4";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import { UUIDMock } from "talk-test/mocks";
import create from "./create";
import { assets, users } from "./fixtures";
const uuidMock = uuid as UUIDMock;
let testRenderer: ReactTestRenderer;
beforeEach(() => {
const resolvers = {
@@ -70,6 +74,9 @@ it("renders comment stream", async () => {
});
it("post a reply", async () => {
// set next UUID of the optimistic response.
uuidMock.setMockData(["comment-optimistic"]);
// Wait for loading.
await timeout();
+38
View File
@@ -0,0 +1,38 @@
// TODO: Remove when fixed.
// Mock React.createContext because of https://github.com/airbnb/enzyme/issues/1509.
function mockReact() {
const originalReact = require.requireActual("react");
return {
...originalReact,
createContext: jest.fn(defaultValue => {
let value = defaultValue;
const Provider = (props: any) => {
value = props.value;
return props.children;
};
const Consumer = (props: any) => props.children(value);
return {
Provider,
Consumer,
};
}),
};
}
export interface UUIDMock {
(): string;
setMockData(mockData: string[]): void;
}
function mockUUID(): UUIDMock {
const originalUUID = require.requireActual("uuid/v4");
let mockData: string[] = [];
const uuid: UUIDMock = (() => {
return (mockData.length && mockData.splice(0, 1)[0]) || originalUUID();
}) as any;
uuid.setMockData = (nextMockData: string[]) => (mockData = nextMockData);
return uuid;
}
jest.mock("react", () => mockReact());
jest.mock("uuid/v4", () => mockUUID());
+1 -22
View File
@@ -1,25 +1,4 @@
import "jest-localstorage-mock";
import "./enzyme";
import "./jsdom";
// TODO: Remove when fixed.
// Mock React.createContext because of https://github.com/airbnb/enzyme/issues/1509.
function mockReact() {
const originalReact = require.requireActual("react");
return {
...originalReact,
createContext: jest.fn(defaultValue => {
let value = defaultValue;
const Provider = (props: any) => {
value = props.value;
return props.children;
};
const Consumer = (props: any) => props.children(value);
return {
Provider,
Consumer,
};
}),
};
}
jest.mock("react", () => mockReact());
import "./mocks";
+1
View File
@@ -13,6 +13,7 @@
"talk-stream/*": ["./stream/*"],
"talk-framework/*": ["./framework/*"],
"talk-ui/*": ["./ui/*"],
"talk-test/*": ["./test/*"],
"talk-common/*": ["../common/*"]
}
},