mirror of
https://github.com/wassname/talk.git
synced 2026-07-30 12:40:41 +08:00
Implement Timestamp
Merge branch 'next' into timestamp Fix test and translations Merge branch 'timestamp' of github.com:coralproject/talk into timestamp * 'timestamp' of github.com:coralproject/talk: Fix test and translations Implement Timestamp
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { LocalizationProvider } from "fluent-react/compat";
|
||||
import { MessageContext } from "fluent/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Formatter } from "react-timeago";
|
||||
import { Environment } from "relay-runtime";
|
||||
import { UIContext } from "talk-ui/components";
|
||||
|
||||
export interface TalkContext {
|
||||
// relayEnvironment for our relay framework.
|
||||
@@ -9,6 +11,9 @@ export interface TalkContext {
|
||||
|
||||
// localMessages for our i18n framework.
|
||||
localeMessages: MessageContext[];
|
||||
|
||||
// formatter for timeago.
|
||||
timeagoFormatter: Formatter;
|
||||
}
|
||||
|
||||
const { Provider, Consumer } = React.createContext<TalkContext>({} as any);
|
||||
@@ -27,7 +32,9 @@ export const TalkContextProvider: StatelessComponent<{
|
||||
}> = ({ value, children }) => (
|
||||
<Provider value={value}>
|
||||
<LocalizationProvider messages={value.localeMessages}>
|
||||
{children}
|
||||
<UIContext.Provider value={{ timeagoFormatter: value.timeagoFormatter }}>
|
||||
{children}
|
||||
</UIContext.Provider>
|
||||
</LocalizationProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
+23
@@ -1,4 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import { Formatter } from "react-timeago";
|
||||
import { Environment, Network, RecordSource, Store } from "relay-runtime";
|
||||
|
||||
import { generateMessages, LocalesData, negotiateLanguages } from "../i18n";
|
||||
@@ -16,6 +19,25 @@ interface CreateContextArguments {
|
||||
init?: ((context: TalkContext) => void | Promise<void>);
|
||||
}
|
||||
|
||||
/**
|
||||
* timeagoFormatter integrates timeago into our translation
|
||||
* framework. It gets injected into the UIContext.
|
||||
*/
|
||||
export const timeagoFormatter: Formatter = (value, unit, suffix) => {
|
||||
// We use 'in' instead of 'from now' for language consistency
|
||||
const ourSuffix = suffix === "from now" ? "in" : suffix;
|
||||
return (
|
||||
<Localized
|
||||
id="framework-timeago"
|
||||
$value={value}
|
||||
$unit={unit}
|
||||
$suffix={ourSuffix}
|
||||
>
|
||||
<span>now</span>
|
||||
</Localized>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* `createContext` manages the dependencies of our framework
|
||||
* and returns a `TalkContext` that can be passed to the
|
||||
@@ -46,6 +68,7 @@ export default async function createContext({
|
||||
const context = {
|
||||
relayEnvironment,
|
||||
localeMessages,
|
||||
timeagoFormatter,
|
||||
};
|
||||
|
||||
// Run custom initializations.
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
import Comment from "./Comment";
|
||||
|
||||
it("renders username and body", () => {
|
||||
const props = {
|
||||
author: {
|
||||
username: "Marvin",
|
||||
},
|
||||
body: "Woof",
|
||||
createdAt: new Date("December 17, 1995 03:24:00").toISOString(),
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<Comment {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with gutterBottom", () => {
|
||||
const props = {
|
||||
author: {
|
||||
username: "Marvin",
|
||||
},
|
||||
body: "Woof",
|
||||
createdAt: new Date("December 17, 1995 03:24:00").toISOString(),
|
||||
gutterBottom: true,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<Comment {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import cn from "classnames";
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { Typography } from "talk-ui/components";
|
||||
import { Timestamp, Typography } from "talk-ui/components";
|
||||
|
||||
import * as styles from "./Comment.css";
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface CommentProps {
|
||||
username: string;
|
||||
} | null;
|
||||
body: string | null;
|
||||
createdAt: string;
|
||||
gutterBottom?: boolean;
|
||||
}
|
||||
|
||||
@@ -24,6 +25,7 @@ const Comment: StatelessComponent<CommentProps> = props => {
|
||||
<Typography className={styles.author} gutterBottom>
|
||||
{props.author && props.author.username}
|
||||
</Typography>
|
||||
<Timestamp date={props.createdAt} />
|
||||
<Typography>{props.body}</Typography>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders username and body 1`] = `
|
||||
<div
|
||||
className="root"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
className="author"
|
||||
gutterBottom={true}
|
||||
>
|
||||
Marvin
|
||||
</withPropsOnChange(Typography)>
|
||||
<withPropsOnChange(Timestamp)
|
||||
date="1995-12-17T06:24:00.000Z"
|
||||
/>
|
||||
<withPropsOnChange(Typography)>
|
||||
Woof
|
||||
</withPropsOnChange(Typography)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders with gutterBottom 1`] = `
|
||||
<div
|
||||
className="root gutterBottom"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
className="author"
|
||||
gutterBottom={true}
|
||||
>
|
||||
Marvin
|
||||
</withPropsOnChange(Typography)>
|
||||
<withPropsOnChange(Timestamp)
|
||||
date="1995-12-17T06:24:00.000Z"
|
||||
/>
|
||||
<withPropsOnChange(Typography)>
|
||||
Woof
|
||||
</withPropsOnChange(Typography)>
|
||||
</div>
|
||||
`;
|
||||
@@ -20,6 +20,7 @@ const enhanced = withFragmentContainer<{ data: Data }>(
|
||||
author {
|
||||
username
|
||||
}
|
||||
createdAt
|
||||
body
|
||||
}
|
||||
`
|
||||
|
||||
@@ -5,40 +5,17 @@
|
||||
"module": "esnext",
|
||||
"jsx": "preserve",
|
||||
"allowJs": false,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es7",
|
||||
"scripthost",
|
||||
"es2015",
|
||||
"esnext.asynciterable"
|
||||
],
|
||||
"lib": ["dom", "es7", "scripthost", "es2015", "esnext.asynciterable"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"talk-admin/*": [
|
||||
"./admin/*"
|
||||
],
|
||||
"talk-stream/*": [
|
||||
"./stream/*"
|
||||
],
|
||||
"talk-framework/*": [
|
||||
"./framework/*"
|
||||
],
|
||||
"talk-ui/*": [
|
||||
"./ui/*"
|
||||
],
|
||||
"talk-common/*": [
|
||||
"../common/*"
|
||||
],
|
||||
"talk-locales/*": [
|
||||
"../../locales/*"
|
||||
]
|
||||
"talk-admin/*": ["./admin/*"],
|
||||
"talk-stream/*": ["./stream/*"],
|
||||
"talk-framework/*": ["./framework/*"],
|
||||
"talk-ui/*": ["./ui/*"],
|
||||
"talk-common/*": ["../common/*"],
|
||||
"talk-locales/*": ["../../locales/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../types/**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"include": ["./**/*", "../../types/**/*.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
composes: body1 from "talk-ui/shared/typography.css";
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Timestamp
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground } from 'docz'
|
||||
import Timestamp from './Timestamp'
|
||||
|
||||
# Timestamp
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<Timestamp date={`2018-07-04T12:14:14.564Z`} />
|
||||
</Playground>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { create } from "react-test-renderer";
|
||||
|
||||
import UIContext from "../UIContext";
|
||||
import Timestamp from "./Timestamp";
|
||||
|
||||
it("uses default formatter", () => {
|
||||
const props = {
|
||||
date: new Date("December 17, 2108 03:24:00").toISOString(),
|
||||
};
|
||||
const tree = create(<Timestamp {...props} />).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses formatter from context", () => {
|
||||
const context: any = {
|
||||
timeagoFormatter: () => "My Formatter",
|
||||
};
|
||||
const props = {
|
||||
date: new Date("December 17, 2108 03:24:00").toISOString(),
|
||||
};
|
||||
const tree = create(
|
||||
<UIContext.Provider value={context}>
|
||||
<Timestamp {...props} />
|
||||
</UIContext.Provider>
|
||||
).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import TimeAgo, { Formatter } from "react-timeago";
|
||||
|
||||
import { UIContext } from "talk-ui/components";
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import * as styles from "./Timestamp.css";
|
||||
|
||||
interface InnerProps {
|
||||
date: string;
|
||||
live?: boolean;
|
||||
classes: typeof styles;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const defaultFormatter: Formatter = (value, unit, suffix, timestamp: string) =>
|
||||
new Date(timestamp).toISOString();
|
||||
|
||||
class Timestamp extends React.Component<InnerProps> {
|
||||
public render() {
|
||||
const { date, classes, live, className } = this.props;
|
||||
return (
|
||||
<UIContext.Consumer>
|
||||
{({ timeagoFormatter }) => (
|
||||
<TimeAgo
|
||||
date={date}
|
||||
className={cn(className, classes.root)}
|
||||
live={live}
|
||||
formatter={timeagoFormatter || defaultFormatter}
|
||||
/>
|
||||
)}
|
||||
</UIContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withStyles(styles)(Timestamp);
|
||||
export type TimestampProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`uses default formatter 1`] = `
|
||||
<time
|
||||
className="root"
|
||||
dateTime="2108-12-17T06:24:00.000Z"
|
||||
title="2108-12-17T06:24:00.000Z"
|
||||
>
|
||||
2108-12-17T06:24:00.000Z
|
||||
</time>
|
||||
`;
|
||||
|
||||
exports[`uses formatter from context 1`] = `
|
||||
<time
|
||||
className="root"
|
||||
dateTime="2108-12-17T06:24:00.000Z"
|
||||
title="2108-12-17T06:24:00.000Z"
|
||||
>
|
||||
My Formatter
|
||||
</time>
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./Timestamp";
|
||||
export { default } from "./Timestamp";
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import { Formatter } from "react-timeago";
|
||||
|
||||
export interface UIContext {
|
||||
timeagoFormatter: Formatter;
|
||||
}
|
||||
|
||||
const UIContext = React.createContext<UIContext>({} as any);
|
||||
|
||||
export default UIContext;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./UIContext";
|
||||
@@ -2,3 +2,5 @@ export { default as BaseButton } from "./BaseButton";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as Center } from "./Center";
|
||||
export { default as Typography } from "./Typography";
|
||||
export { default as Timestamp } from "./Timestamp";
|
||||
export { default as UIContext } from "./UIContext";
|
||||
|
||||
@@ -33,11 +33,14 @@
|
||||
color: $palette-text-primary;
|
||||
}
|
||||
|
||||
.subtitle1 {}
|
||||
.subtitle1 {
|
||||
}
|
||||
|
||||
.subtitle2 {}
|
||||
.subtitle2 {
|
||||
}
|
||||
|
||||
.body2 {}
|
||||
.body2 {
|
||||
}
|
||||
|
||||
.body1 {
|
||||
font-size: calc(16rem / $rem-base);
|
||||
@@ -56,4 +59,5 @@
|
||||
letter-spacing: calc(0.57em / 16);
|
||||
}
|
||||
|
||||
.overline {}
|
||||
.overline {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user