[next] Moderate (#2118)

* fix: load .env before building / watching

* feat: Implement AppBar, Brand, and SubBar

* feat: add card ui component

* feat: add modqueue components

* feat: implement modqueue

* feat: add translations

* test: add unit tests

* feat: single comment view

* test: feature / integration tests for modqueue

* test: fix remaining tests

* feature: support TextMatchOptions

* fix: remove body count marker

* fix: remove accidently added package

* feat: testHelper toJSON

* chore: cleanup + comments

* chore: better types

* test: fix test

* chore: refactor decision history test

* chore: tiny fix

* fix: adjust to recent server changes

* fix: marking suspect and banned words

* feat: added moderation queue edge to accept/reject comment payloads

- Simplified moderationQueue returns
- Simplified resolvers

* feat: update counts

* feat: added id's to moderation queue and settings

* fix+test: test count changes, apply fix

* chore: adapt to server change, and remove custom mutation handlers

* fix: use common utils

* fix: purify fix, babel fix

* fix: workaround css treeshake issue and upgrade css plugins

* fix: fixed snapshot

* fix: support empty word lists

* feat: separate client config
This commit is contained in:
Kiwi
2018-12-18 18:00:39 +00:00
committed by Wyatt Johnson
parent 90e67ca479
commit 1fc49f8e50
316 changed files with 11587 additions and 3005 deletions
@@ -0,0 +1,85 @@
import * as React from "react";
import { createContextHOC } from "talk-framework/helpers";
import ensurePolyfill from "./ensurePolyfill";
export type IntersectionCallback = (entry: IntersectionObserverEntry) => void;
export type Observe = (
target: Element,
callback: IntersectionCallback
) => () => void;
export interface IntersectionContext {
observe: Observe;
}
const { Provider, Consumer } = React.createContext<IntersectionContext>(
{} as any
);
export const IntersectionConsumer = Consumer;
export class IntersectionProvider extends React.Component<any, any> {
private observer: IntersectionObserver;
private elements = new Map();
private elementBuffer: Element[] = [];
private unmounted = false;
public componentDidMount() {
ensurePolyfill().then(() => {
if (this.unmounted) {
return;
}
this.observer = new IntersectionObserver(this.onIntersect, {
root: this.props.node ? this.props.node : undefined,
rootMargin: "0px",
threshold: 0.25,
});
this.elementBuffer.forEach(element => this.observer.observe(element));
this.elementBuffer = [];
});
}
public componentWillUnmount() {
this.unmounted = true;
}
private unobserve = (element: Element) => {
this.elements.delete(element);
if (!this.observer) {
this.elementBuffer = this.elementBuffer.filter(e => e !== element);
} else {
this.observer.unobserve(element);
}
};
private observe: Observe = (element, callback) => {
this.elements.set(element, callback);
// this funny bit to handle react's lifecycle order and also wait
// for polyfill.
if (!this.observer) {
this.elementBuffer.push(element);
} else {
this.observer.observe(element);
}
return () => this.unobserve(element);
};
private onIntersect = (
entries: IntersectionObserverEntry[],
observer: IntersectionObserver
) => {
entries.forEach(entry => this.elements.get(entry.target)(entry));
};
public render() {
return (
<Provider value={{ observe: this.observe }}>
{this.props.children}
</Provider>
);
}
}
export const withIntersectionContext = createContextHOC<IntersectionContext>(
"withContext",
IntersectionConsumer
);
@@ -0,0 +1,9 @@
/**
* Loads intersection-observer polyfill if it doesn't exist.
*/
export default async function ensurePolyfill() {
if (!(window as any).IntersectionObserver) {
await import("intersection-observer");
}
return;
}
@@ -0,0 +1,7 @@
export {
IntersectionProvider,
Observe,
withIntersectionContext,
} from "./IntersectionContext";
export { default as withInView } from "./withInView";
export { default as ensurePolyfill } from "./ensurePolyfill";
@@ -0,0 +1,81 @@
import * as React from "react";
import { DefaultingInferableComponentEnhancer, hoistStatics } from "recompose";
import { Observe, withIntersectionContext } from "./IntersectionContext";
interface InjectedProps {
inView: boolean | undefined;
intersectionRef: React.Ref<any>;
}
interface Props {
observe: Observe;
}
interface State {
inView: boolean | undefined;
}
/**
* withInView provides a property `inView: boolean`
* to indicate whether or not the referenced element is
* in the current browser view.
*/
const withInView: DefaultingInferableComponentEnhancer<
InjectedProps
> = hoistStatics<InjectedProps>(
<T extends InjectedProps>(BaseComponent: React.ComponentType<T>) => {
class WithInView extends React.Component<Props, State> {
private unobserve: (() => void) | null = null;
public state = {
inView: undefined,
};
private changeRef = (ref: any) => {
if (this.unobserve) {
this.unobserve();
this.unobserve = null;
}
if (ref) {
this.unobserve = this.props.observe(
ref,
({ intersectionRatio }: any) => {
// Callback is called whenever we run observe.
if (this.state.inView === undefined) {
this.setState({ inView: intersectionRatio > 0 });
} else {
this.setState(s => ({
inView: !s.inView,
}));
}
}
);
}
};
public componentWillUnmount() {
if (this.unobserve) {
this.unobserve();
}
}
public render() {
return (
<BaseComponent
{...this.props}
inView={this.state.inView}
intersectionRef={this.changeRef}
/>
);
}
}
const enhanced = withIntersectionContext(({ observe }) => ({ observe }))(
WithInView
);
return enhanced as React.ComponentType<any>;
}
);
export default withInView;
@@ -25,8 +25,7 @@ export async function commitMutationPromiseNormalized<T extends OperationBase>(
config: MutationPromiseConfig<T>
): Promise<T["response"][keyof T["response"]]> {
try {
const response = await commitMutationPromise(environment, config);
return extractPayload(response);
return await commitMutationPromise(environment, config);
} catch (e) {
throw e;
}