mirror of
https://github.com/wassname/talk.git
synced 2026-07-06 05:17:19 +08:00
Adding Attachment and Popover Component
This commit is contained in:
@@ -40,6 +40,22 @@ const config: Config = {
|
||||
paths: [],
|
||||
executor: new LongRunningExecutor("npm run start:webpackDevServer"),
|
||||
},
|
||||
runDocz: {
|
||||
paths: [],
|
||||
executor: new LongRunningExecutor("npm run docz -- dev"),
|
||||
},
|
||||
},
|
||||
defaultSet: "client",
|
||||
sets: {
|
||||
server: ["runServer"],
|
||||
client: [
|
||||
"runServer",
|
||||
"runWebpackDevServer",
|
||||
"compileCSSTypes",
|
||||
"compileRelayStream",
|
||||
],
|
||||
docz: ["runDocz", "compileCSSTypes"],
|
||||
compile: ["compileCSSTypes", "compileRelayStream"],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ module.exports = {
|
||||
|
||||
// All available locales can be loadable on demand.
|
||||
// To restrict available locales set:
|
||||
// availableLocales: ["en-US"]
|
||||
// availableLocales: ["en-US"],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -247,6 +247,7 @@ module.exports = {
|
||||
options: {
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ const paths = require("./paths");
|
||||
const protocol = process.env.HTTPS === "true" ? "https" : "http";
|
||||
const host = process.env.HOST || "0.0.0.0";
|
||||
const serverPort = process.env.PORT || 3000;
|
||||
const doczPort = process.env.DOCZ_PORT || 3030;
|
||||
|
||||
module.exports = function(proxy, allowedHost) {
|
||||
return {
|
||||
@@ -81,8 +82,8 @@ module.exports = function(proxy, allowedHost) {
|
||||
disableDotRule: true,
|
||||
},
|
||||
public: allowedHost,
|
||||
// Proxy to the graphql server.
|
||||
proxy: proxy || {
|
||||
// Proxy to the graphql server.
|
||||
"/api": {
|
||||
target: `http://localhost:${serverPort}`,
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
source: "./src",
|
||||
typescript: true,
|
||||
host: process.env.HOST || "0.0.0.0",
|
||||
port: parseInt(process.env.DOCZ_PORT, 10) || 3000,
|
||||
port: parseInt(process.env.DOCZ_PORT, 10) || 3030,
|
||||
modifyBundlerConfig: config => {
|
||||
config.module.rules.push({
|
||||
test: /\.css$/,
|
||||
|
||||
Generated
+2530
-845
File diff suppressed because it is too large
Load Diff
+36
-28
@@ -8,7 +8,7 @@
|
||||
"build": "npm-run-all compile --parallel build:*",
|
||||
"build:client": "node ./scripts/build.js",
|
||||
"build:server": "tsc -p ./src/tsconfig.json",
|
||||
"watch": "NODE_ENV=development ts-node ./scripts/watcher/bin/watcher.ts ./config/watcher.ts",
|
||||
"watch": "NODE_ENV=development ts-node ./scripts/watcher/bin/watcher.ts --config ./config/watcher.ts",
|
||||
"compile": "npm-run-all --parallel compile:*",
|
||||
"compile:css-types": "tcm src/core/client/",
|
||||
"compile:relay-stream": "relay-compiler --src ./src/core/client/stream --schema $(ts-node ./scripts/schemaPath.ts tenant) --language typescript --artifactDirectory ./src/core/client/stream/__generated__ --no-watchman",
|
||||
@@ -19,7 +19,7 @@
|
||||
"lint:server": "tslint --project ./src/tsconfig.json",
|
||||
"lint:client": "tslint --project ./src/core/client/tsconfig.json",
|
||||
"lint:scripts": "tslint --project ./tsconfig.json",
|
||||
"docz:watch": "docz dev"
|
||||
"docz": "docz"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@types/react-tooltip": "^3.3.5",
|
||||
"apollo-server-express": "^1.3.6",
|
||||
"bunyan": "^1.8.12",
|
||||
"convict": "^4.3.0",
|
||||
"convict": "^4.3.1",
|
||||
"dataloader": "^1.4.0",
|
||||
"dotenv": "^6.0.0",
|
||||
"dotenv-expand": "^4.2.0",
|
||||
@@ -39,25 +39,30 @@
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-config": "^2.0.1",
|
||||
"graphql-redis-subscriptions": "^1.5.0",
|
||||
"graphql-tools": "^3.0.2",
|
||||
"graphql-tools": "^3.0.5",
|
||||
"ioredis": "^3.2.2",
|
||||
"joi": "^13.4.0",
|
||||
"lodash": "^4.17.10",
|
||||
"luxon": "^1.2.1",
|
||||
"mongodb": "^3.0.10",
|
||||
"luxon": "^1.3.1",
|
||||
"mongodb": "^3.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"performance-now": "^2.1.0",
|
||||
<<<<<<< HEAD
|
||||
"rc-tooltip": "^3.7.2",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-popper": "^1.0.0-beta.6",
|
||||
"react-tooltip": "^3.6.1",
|
||||
"subscriptions-transport-ws": "^0.9.11",
|
||||
"uuid": "^3.2.1"
|
||||
=======
|
||||
"subscriptions-transport-ws": "^0.9.12",
|
||||
"uuid": "^3.3.2"
|
||||
>>>>>>> abfa39529ff398007bb67cd814c4217a97bdb38b
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.0.0-beta.49",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.0.0-beta.49",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.0.0-beta.52",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.0.0-beta.53",
|
||||
"@babel/polyfill": "7.0.0-beta.49",
|
||||
"@babel/preset-env": "7.0.0-beta.49",
|
||||
"@babel/preset-react": "7.0.0-beta.49",
|
||||
@@ -71,15 +76,15 @@
|
||||
"@types/enzyme": "^3.1.11",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.2",
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/graphql": "^0.13.1",
|
||||
"@types/ioredis": "^3.2.8",
|
||||
"@types/jest": "^23.1.1",
|
||||
"@types/graphql": "^0.13.3",
|
||||
"@types/ioredis": "^3.2.12",
|
||||
"@types/jest": "^23.1.5",
|
||||
"@types/joi": "^13.0.8",
|
||||
"@types/jsdom": "^11.0.6",
|
||||
"@types/lodash": "^4.14.109",
|
||||
"@types/lodash": "^4.14.111",
|
||||
"@types/luxon": "^0.5.3",
|
||||
"@types/mongodb": "^3.0.19",
|
||||
"@types/node": "^10.3.1",
|
||||
"@types/mongodb": "^3.1.1",
|
||||
"@types/node": "^10.5.2",
|
||||
"@types/passport": "^0.4.5",
|
||||
"@types/query-string": "^6.1.0",
|
||||
"@types/react-dom": "^16.0.6",
|
||||
@@ -88,10 +93,11 @@
|
||||
"@types/react-test-renderer": "^16.0.1",
|
||||
"@types/recompose": "^0.26.1",
|
||||
"@types/relay-runtime": "github:coralproject/patched#types/relay-runtime",
|
||||
"@types/sane": "^2.0.0",
|
||||
"@types/sinon": "^5.0.1",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"@types/ws": "^5.1.2",
|
||||
"autoprefixer": "^8.6.0",
|
||||
"autoprefixer": "^8.6.5",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-loader": "^8.0.0-beta",
|
||||
"babel-plugin-relay": "github:coralproject/patched#babel-plugin-relay",
|
||||
@@ -104,21 +110,20 @@
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"css-loader": "^0.28.11",
|
||||
"docz": "^0.2.6",
|
||||
"docz-theme-default": "^0.2.10",
|
||||
"docz": "^0.5.8",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"final-form": "^4.8.1",
|
||||
"flat": "^4.0.0",
|
||||
"flat": "^4.1.0",
|
||||
"fluent": "^0.6.4",
|
||||
"fluent-intl-polyfill": "^0.1.0",
|
||||
"fluent-langneg": "^0.1.0",
|
||||
"fluent-react": "^0.7.0",
|
||||
"graphql-playground-middleware-express": "^1.7.0",
|
||||
"graphql-playground-middleware-express": "^1.7.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "^23.3.0",
|
||||
"jest": "^23.4.1",
|
||||
"jsdom": "^11.11.0",
|
||||
"loader-utils": "^1.1.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
@@ -127,32 +132,35 @@
|
||||
"postcss-flexbugs-fixes": "^3.3.1",
|
||||
"postcss-font-magician": "^2.2.1",
|
||||
"postcss-import": "^11.1.0",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"postcss-loader": "^2.1.6",
|
||||
"postcss-nested": "^3.0.0",
|
||||
"postcss-prepend-imports": "^1.0.1",
|
||||
"postcss-preset-env": "^5.2.1",
|
||||
"prettier": "^1.13.4",
|
||||
"prettier": "^1.13.7",
|
||||
"pstree.remy": "^1.1.0",
|
||||
"query-string": "^6.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.4.0",
|
||||
"react-dev-utils": "6.0.0-next.3e165448",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-final-form": "^3.6.0",
|
||||
"react-final-form": "^3.6.4",
|
||||
"react-relay": "github:coralproject/patched#react-relay",
|
||||
"react-responsive": "^4.1.0",
|
||||
"react-test-renderer": "^16.4.1",
|
||||
"react-timeago": "^4.1.9",
|
||||
"recompose": "^0.27.1",
|
||||
"relay-compiler": "github:coralproject/patched#relay-compiler",
|
||||
"relay-compiler-language-typescript": "github:coralproject/patched#relay-compiler-language-typescript",
|
||||
"relay-local-schema": "^0.7.0",
|
||||
"relay-runtime": "github:coralproject/patched#relay-runtime",
|
||||
"relay-test-utils": "github:coralproject/patched#relay-test-utils",
|
||||
"sinon": "^6.1.2",
|
||||
"sane": "^2.5.2",
|
||||
"sinon": "^6.1.3",
|
||||
"style-loader": "^0.21.0",
|
||||
"ts-jest": "^23.0.0",
|
||||
"ts-loader": "^4.3.1",
|
||||
"ts-node": "^6.1.0",
|
||||
"tsconfig-paths": "^3.4.0",
|
||||
"ts-loader": "^4.4.2",
|
||||
"ts-node": "^6.2.0",
|
||||
"tsconfig-paths": "^3.4.2",
|
||||
"tsconfig-paths-webpack-plugin": "^3.1.4",
|
||||
"tslint": "^5.10.0",
|
||||
"tslint-config-prettier": "^1.13.0",
|
||||
@@ -160,12 +168,12 @@
|
||||
"tslint-plugin-prettier": "^1.3.0",
|
||||
"tslint-react": "^3.6.0",
|
||||
"typed-css-modules": "^0.3.4",
|
||||
"typescript": "^2.9.1",
|
||||
"typescript": "^2.9.2",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"webpack": "4.12.0",
|
||||
"webpack-cli": "^3.0.2",
|
||||
"webpack-dev-server": "^3.1.4",
|
||||
"webpack-hot-client": "^4.0.3",
|
||||
"webpack-hot-client": "^4.1.1",
|
||||
"webpack-manifest-plugin": "^2.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import chokidar from "chokidar";
|
||||
import path from "path";
|
||||
import { Watcher, WatchOptions } from "./types";
|
||||
|
||||
function prependRootDir(
|
||||
prepend: string,
|
||||
paths: ReadonlyArray<string>
|
||||
): string[] {
|
||||
const prependFunc = (p: string) => path.resolve(prepend, p);
|
||||
return paths.map(prependFunc);
|
||||
}
|
||||
|
||||
export default class ChokidarWatcher implements Watcher {
|
||||
public watch(
|
||||
rootDir: string,
|
||||
paths: ReadonlyArray<string>,
|
||||
options: WatchOptions = {}
|
||||
): AsyncIterable<string> {
|
||||
const client = chokidar.watch(paths as string[], {
|
||||
ignored: options.ignore,
|
||||
});
|
||||
const resolvedPaths = prependRootDir(rootDir, paths);
|
||||
|
||||
// An array to hold all changes, that has not yet been yield.
|
||||
const queue: string[] = [];
|
||||
@@ -19,31 +27,38 @@ export default class ChokidarWatcher implements Watcher {
|
||||
| ({ resolve: (result: string) => void; reject: (error: Error) => void })
|
||||
| null = null;
|
||||
|
||||
// Listen for errors
|
||||
client.on("error", (error: Error) => {
|
||||
// Resolve pending request.
|
||||
if (pending) {
|
||||
pending.reject(error);
|
||||
pending = null;
|
||||
return;
|
||||
}
|
||||
if (!firstError) {
|
||||
firstError = error;
|
||||
}
|
||||
});
|
||||
// Only start client if we have something to watch.
|
||||
if (paths.length) {
|
||||
const client = chokidar.watch(resolvedPaths, {
|
||||
ignored: options.ignore && prependRootDir(rootDir, options.ignore),
|
||||
});
|
||||
|
||||
// Listen for changes
|
||||
client.on("change", (pathFile: string) => {
|
||||
// Resolve pending request.
|
||||
if (pending) {
|
||||
pending.resolve(pathFile);
|
||||
pending = null;
|
||||
return;
|
||||
}
|
||||
// Listen for errors
|
||||
client.on("error", (error: Error) => {
|
||||
// Resolve pending request.
|
||||
if (pending) {
|
||||
pending.reject(error);
|
||||
pending = null;
|
||||
return;
|
||||
}
|
||||
if (!firstError) {
|
||||
firstError = error;
|
||||
}
|
||||
});
|
||||
|
||||
// There is no pending request, save it into the queue.
|
||||
queue.unshift(pathFile);
|
||||
});
|
||||
// Listen for changes
|
||||
client.on("change", (pathFile: string) => {
|
||||
// Resolve pending request.
|
||||
if (pending) {
|
||||
pending.resolve(pathFile);
|
||||
pending = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There is no pending request, save it into the queue.
|
||||
queue.unshift(pathFile);
|
||||
});
|
||||
}
|
||||
return {
|
||||
[Symbol.asyncIterator]() {
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import chalk from "chalk";
|
||||
import spawn from "cross-spawn";
|
||||
import { Cancelable, debounce } from "lodash";
|
||||
|
||||
@@ -65,9 +66,9 @@ export default class CommandExecutor implements Executor {
|
||||
|
||||
child.on("close", (code: number) => {
|
||||
this.isRunning = false;
|
||||
if (code !== 0) {
|
||||
if (code !== 0 && code !== null) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.log(`We had an error building ${code}`);
|
||||
console.log(chalk.red(`Command exited with ${code}`));
|
||||
}
|
||||
if (this.shouldRespawn) {
|
||||
this.spawnProcessPotentiallyDebounced();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import chalk from "chalk";
|
||||
import { ChildProcess } from "child_process";
|
||||
import spawn from "cross-spawn";
|
||||
import { Cancelable, debounce } from "lodash";
|
||||
import psTree from "pstree.remy";
|
||||
|
||||
import { Executor } from "./types";
|
||||
|
||||
interface LongRunningExecutorOptions {
|
||||
@@ -31,9 +34,6 @@ export default class LongRunningExecutor implements Executor {
|
||||
this.isRunning = true;
|
||||
this.process = spawn(this.cmd, this.args as string[], {
|
||||
stdio: "inherit",
|
||||
// Have all child processes in their own group.
|
||||
// See `process.kill` below.
|
||||
detached: true,
|
||||
shell: !this.args,
|
||||
});
|
||||
|
||||
@@ -42,27 +42,53 @@ export default class LongRunningExecutor implements Executor {
|
||||
|
||||
if (code !== 0 && code !== null) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.error(`Exit code returned ${code}`);
|
||||
console.log(chalk.red(`Command exited with ${code}`));
|
||||
return;
|
||||
}
|
||||
if (this.shouldRestart) {
|
||||
this.shouldRestart = false;
|
||||
this.spawnProcess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private restart(): void {
|
||||
private restart() {
|
||||
this.shouldRestart = true;
|
||||
// Using the `-` will kill all child procceses in the group.
|
||||
// See: https://azimi.me/2014/12/31/kill-child_process-node-js.html
|
||||
process.kill(-this.process!.pid, "SIGTERM");
|
||||
return this.internalKill();
|
||||
}
|
||||
|
||||
private kill(): void {
|
||||
private kill() {
|
||||
this.shouldRestart = false;
|
||||
// Using the `-` will kill all child procceses in the group.
|
||||
// See: https://azimi.me/2014/12/31/kill-child_process-node-js.html
|
||||
process.kill(-this.process!.pid, "SIGTERM");
|
||||
return this.internalKill();
|
||||
}
|
||||
|
||||
private async internalKill(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const signal = "SIGTERM";
|
||||
if (process.platform === "win32") {
|
||||
// Force kill (/F) the whole child tree (/T) by PID
|
||||
spawn.sync("taskkill", [
|
||||
"/pid",
|
||||
this.process!.pid.toString(),
|
||||
"/T",
|
||||
"/F",
|
||||
]);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
psTree(this.process!.pid, (err, kids) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
spawn.sync("kill", [
|
||||
`-${signal}`,
|
||||
this.process!.pid.toString(),
|
||||
...kids.map(p => p.PID.toString()),
|
||||
]);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// This is called before watching starts.
|
||||
@@ -71,10 +97,10 @@ export default class LongRunningExecutor implements Executor {
|
||||
}
|
||||
|
||||
// This is called before exiting.
|
||||
public onCleanup() {
|
||||
public async onCleanup() {
|
||||
this.restartDebounced.cancel();
|
||||
if (this.isRunning) {
|
||||
this.kill();
|
||||
await this.kill();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import chalk from "chalk";
|
||||
import { execSync } from "child_process";
|
||||
import sane from "sane";
|
||||
|
||||
import { Watcher, WatchOptions } from "./types";
|
||||
|
||||
interface SaneWatcherOptions {
|
||||
/**
|
||||
* Set to true to use watchman, false to disabled, and undefined
|
||||
* for automatic detection.
|
||||
*/
|
||||
watchman?: boolean;
|
||||
/** Use polling, this might be required for network file systems. */
|
||||
poll?: boolean;
|
||||
}
|
||||
|
||||
function canUseWatchman(): boolean {
|
||||
try {
|
||||
execSync("watchman --version", { stdio: ["ignore"] });
|
||||
return true;
|
||||
// tslint:disable-next-line:no-empty
|
||||
} catch (e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default class SaneWatcher implements Watcher {
|
||||
private watchman?: boolean;
|
||||
private poll: boolean;
|
||||
|
||||
constructor(options: SaneWatcherOptions = {}) {
|
||||
this.watchman = options.watchman;
|
||||
this.poll = options.poll || false;
|
||||
|
||||
// Autodetect watchman.
|
||||
if (this.watchman === undefined && canUseWatchman()) {
|
||||
this.watchman = true;
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(chalk.grey(`Watchman detected`));
|
||||
}
|
||||
}
|
||||
|
||||
public watch(
|
||||
rootDir: string,
|
||||
paths: ReadonlyArray<string>,
|
||||
options: WatchOptions = {}
|
||||
): AsyncIterable<string> {
|
||||
// An array to hold all changes, that has not yet been yield.
|
||||
const queue: string[] = [];
|
||||
|
||||
// If this is set, a pending promise is waiting for the next result.
|
||||
let pending: ({ resolve: (result: string) => void }) | null = null;
|
||||
|
||||
// Only start client if we have something to watch.
|
||||
if (paths.length) {
|
||||
// Setup client
|
||||
const client = sane(rootDir, {
|
||||
glob: paths as string[],
|
||||
ignored: options.ignore as string[],
|
||||
watchman: this.watchman,
|
||||
poll: this.poll,
|
||||
});
|
||||
|
||||
// Listen for changes
|
||||
client.on("change", (pathFile: string) => {
|
||||
// Resolve pending request.
|
||||
if (pending) {
|
||||
pending.resolve(pathFile);
|
||||
pending = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There is no pending request, save it into the queue.
|
||||
queue.unshift(pathFile);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
[Symbol.asyncIterator]() {
|
||||
return {
|
||||
next: () =>
|
||||
new Promise<IteratorResult<string>>((resolve, reject) => {
|
||||
const wrapped = {
|
||||
resolve: (pathFile: string) =>
|
||||
resolve({
|
||||
done: false,
|
||||
value: pathFile,
|
||||
}),
|
||||
};
|
||||
|
||||
if (queue.length) {
|
||||
wrapped.resolve(queue.pop()!);
|
||||
return;
|
||||
}
|
||||
// We need to wait for the next change event.
|
||||
pending = wrapped;
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,24 +4,36 @@ import program from "commander";
|
||||
import path from "path";
|
||||
import watch from "../";
|
||||
|
||||
function list(val: string) {
|
||||
return val.split(",");
|
||||
async function run(
|
||||
args: ReadonlyArray<string>,
|
||||
options: Record<string, string>
|
||||
) {
|
||||
const only = args;
|
||||
const { config: configFile = "" } = options;
|
||||
if (!configFile) {
|
||||
throw new Error("Config file not specified");
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
let config: any = require(path.resolve(configFile));
|
||||
if (config.__esModule) {
|
||||
config = config.default;
|
||||
}
|
||||
|
||||
try {
|
||||
await watch(config, { only });
|
||||
} catch (err) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
program
|
||||
const cmd = program
|
||||
.version("0.1.0")
|
||||
.usage("<configFile>")
|
||||
.option("-o, --only <watcher>", "only run the specified watcher", list)
|
||||
.arguments("<configFile>")
|
||||
.usage("[watchers or sets]")
|
||||
.option("-c, --config <configFile>", "Use given config file")
|
||||
.description("Run watchers defined in <configFile>")
|
||||
.action((configFile, cmd) => {
|
||||
const { only = [] } = cmd;
|
||||
|
||||
let config: any = require(path.resolve(configFile));
|
||||
if (config.__esModule) {
|
||||
config = config.default;
|
||||
}
|
||||
|
||||
watch(config, { only });
|
||||
})
|
||||
.parse(process.argv);
|
||||
|
||||
run(cmd.args, cmd.opts());
|
||||
|
||||
@@ -5,28 +5,31 @@ export interface WatchOptions {
|
||||
}
|
||||
|
||||
export interface Watcher {
|
||||
onInit?(): void | Promise<void>;
|
||||
onCleanup?(): void | Promise<void>;
|
||||
watch(
|
||||
rootDir: string,
|
||||
paths: ReadonlyArray<string>,
|
||||
options?: WatchOptions
|
||||
): AsyncIterable<string>;
|
||||
}
|
||||
|
||||
export interface Executor {
|
||||
onInit?(): void;
|
||||
onCleanup?(): void;
|
||||
onInit?(): void | Promise<void>;
|
||||
onCleanup?(): void | Promise<void>;
|
||||
execute(filePath: string): void;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
only?: string[];
|
||||
only?: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
rootDir?: string;
|
||||
backend?: Watcher;
|
||||
watchers: {
|
||||
[key: string]: WatchConfig;
|
||||
};
|
||||
watchers: Record<string, WatchConfig>;
|
||||
defaultSet?: string;
|
||||
sets?: Record<string, ReadonlyArray<string>>;
|
||||
}
|
||||
|
||||
export interface WatchConfig {
|
||||
@@ -51,4 +54,13 @@ export const configSchema = Joi.object({
|
||||
executor: Joi.object(),
|
||||
})
|
||||
),
|
||||
});
|
||||
defaultSet: Joi.string().optional(),
|
||||
sets: Joi.object()
|
||||
.pattern(
|
||||
/.*/,
|
||||
Joi.array()
|
||||
.items(Joi.string())
|
||||
.unique()
|
||||
)
|
||||
.optional(),
|
||||
}).with("defaultSet", "sets");
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
declare module "pstree.remy" {
|
||||
export default function psTree(
|
||||
pid: number,
|
||||
callback: (err: Error, kids: Array<{ PID: number }>) => void
|
||||
): void;
|
||||
}
|
||||
+72
-37
@@ -1,7 +1,8 @@
|
||||
import chalk from "chalk";
|
||||
import Joi from "joi";
|
||||
import path from "path";
|
||||
import { pickBy } from "lodash";
|
||||
|
||||
import ChokidarWatcher from "./ChokidarWatcher";
|
||||
import SaneWatcher from "./SaneWatcher";
|
||||
import { Config, configSchema, Options, WatchConfig, Watcher } from "./types";
|
||||
|
||||
// Polyfill the asyncIterator symbol.
|
||||
@@ -9,63 +10,97 @@ if (Symbol.asyncIterator === undefined) {
|
||||
(Symbol as any).asyncIterator = Symbol.for("asyncIterator");
|
||||
}
|
||||
|
||||
async function beginWatch(watcher: Watcher, key: string, config: WatchConfig) {
|
||||
async function beginWatch(
|
||||
watcher: Watcher,
|
||||
key: string,
|
||||
config: WatchConfig,
|
||||
rootDir: string
|
||||
) {
|
||||
const { paths, ignore, executor } = config;
|
||||
if (executor.onInit) {
|
||||
executor.onInit();
|
||||
await executor.onInit();
|
||||
}
|
||||
for await (const filePath of watcher.watch(paths, { ignore })) {
|
||||
for await (const filePath of watcher.watch(rootDir, paths, { ignore })) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`Execute "${key}"`);
|
||||
console.log(chalk.cyanBright(`Execute "${key}"`));
|
||||
executor.execute(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
function prependRootDir(prepend: string, cfg: WatchConfig): WatchConfig {
|
||||
const prependFunc = (p: string) => path.resolve(prepend, p);
|
||||
return {
|
||||
...cfg,
|
||||
paths: cfg.paths.map(prependFunc),
|
||||
ignore: cfg.ignore ? cfg.ignore.map(prependFunc) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function setupCleanup(config: Config) {
|
||||
function setupCleanup(watcher: Watcher, config: Config) {
|
||||
["SIGINT", "SIGTERM"].forEach(signal =>
|
||||
process.once(signal as any, () => {
|
||||
process.once(signal as any, async () => {
|
||||
const cleanups = [];
|
||||
if (watcher.onCleanup) {
|
||||
cleanups.push(watcher.onCleanup());
|
||||
}
|
||||
for (const key of Object.keys(config.watchers)) {
|
||||
if (config.watchers[key].executor.onCleanup) {
|
||||
config.watchers[key].executor.onCleanup!();
|
||||
cleanups.push(config.watchers[key].executor.onCleanup!());
|
||||
}
|
||||
}
|
||||
await Promise.all(cleanups);
|
||||
process.exit(0);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function filterOnly(config: Config, only: string[]) {
|
||||
for (const key of Object.keys(config.watchers)) {
|
||||
if (only.indexOf(key) === -1) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`Disabled watcher "${key}"`);
|
||||
delete config.watchers[key];
|
||||
function resolveSets(
|
||||
sets: Record<string, ReadonlyArray<string>>,
|
||||
value: ReadonlyArray<string>
|
||||
) {
|
||||
const resolved: string[] = [];
|
||||
value.forEach(v => {
|
||||
if (v in sets) {
|
||||
resolved.push(...sets[v]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolved.push(v);
|
||||
});
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export default async function watch(config: Config, options?: Options) {
|
||||
Joi.assert(config, configSchema);
|
||||
const watcher = config.backend || new ChokidarWatcher();
|
||||
if (options && options.only && options.only.length > 0) {
|
||||
filterOnly(config, options.only);
|
||||
function filterOnly(
|
||||
watchers: Config["watchers"],
|
||||
only: ReadonlyArray<string>,
|
||||
sets?: Record<string, ReadonlyArray<string>>
|
||||
): Config["watchers"] {
|
||||
const resolved = sets ? resolveSets(sets, only) : only;
|
||||
const unknown = resolved.filter(r => !(r in watchers));
|
||||
if (unknown.length) {
|
||||
throw new Error(`Watcher Configuration or Set for ${unknown} not found`);
|
||||
}
|
||||
setupCleanup(config);
|
||||
for (const key of Object.keys(config.watchers)) {
|
||||
return pickBy(watchers, (value, key) => {
|
||||
if (resolved.indexOf(key) === -1) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(chalk.grey(`Disabled watcher "${key}"`));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}) as Config["watchers"];
|
||||
}
|
||||
|
||||
export default async function watch(config: Config, options: Options = {}) {
|
||||
Joi.assert(config, configSchema);
|
||||
const watcher: Watcher = config.backend || new SaneWatcher();
|
||||
const rootDir = config.rootDir || process.cwd();
|
||||
const defaultSet = config.defaultSet && [config.defaultSet];
|
||||
const only = options.only && options.only.length ? options.only : defaultSet;
|
||||
|
||||
let watchersConfigs = config.watchers;
|
||||
if (only) {
|
||||
watchersConfigs = filterOnly(watchersConfigs, only, config.sets);
|
||||
}
|
||||
|
||||
setupCleanup(watcher, config);
|
||||
if (watcher.onInit) {
|
||||
await watcher.onInit();
|
||||
}
|
||||
|
||||
for (const key of Object.keys(watchersConfigs)) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`Start watcher "${key}"`);
|
||||
const watcherConfig = config.rootDir
|
||||
? prependRootDir(config.rootDir, config.watchers[key])
|
||||
: config.watchers[key];
|
||||
beginWatch(watcher, key, watcherConfig);
|
||||
console.log(chalk.cyanBright(`Start watcher "${key}"`));
|
||||
const watcherConfig = watchersConfigs[key];
|
||||
beginWatch(watcher, key, watcherConfig, rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,14 @@
|
||||
/* Here we add global stylings for body and document */
|
||||
:global {
|
||||
body {
|
||||
margin: "0";
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import App from "./App";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof App> = {
|
||||
asset: {},
|
||||
};
|
||||
const wrapper = shallow(<App {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly when asset is null", () => {
|
||||
const props: PropTypesOf<typeof App> = {
|
||||
asset: null,
|
||||
};
|
||||
const wrapper = shallow(<App {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -5,6 +5,8 @@ import { Flex } from "talk-ui/components";
|
||||
|
||||
import StreamContainer from "../containers/StreamContainer";
|
||||
|
||||
import * as styles from "./App.css";
|
||||
|
||||
export interface AppProps {
|
||||
asset: {} | null;
|
||||
}
|
||||
@@ -12,7 +14,7 @@ export interface AppProps {
|
||||
const App: StatelessComponent<AppProps> = props => {
|
||||
if (props.asset) {
|
||||
return (
|
||||
<Flex justifyContent="center">
|
||||
<Flex justifyContent="center" className={styles.root}>
|
||||
<StreamContainer asset={props.asset} />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
|
||||
.gutterBottom {
|
||||
margin-bottom: calc(1px * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.topBar {
|
||||
margin-bottom: calc(0.5px * var(--spacing-unit));
|
||||
}
|
||||
+7
-1
@@ -1,9 +1,12 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Comment from "./Comment";
|
||||
|
||||
it("renders username and body", () => {
|
||||
<<<<<<< HEAD:src/core/client/stream/components/Comment.spec.tsx
|
||||
const props = {
|
||||
id: "id",
|
||||
author: {
|
||||
@@ -18,11 +21,14 @@ it("renders username and body", () => {
|
||||
it("renders with gutterBottom", () => {
|
||||
const props = {
|
||||
id: "id",
|
||||
=======
|
||||
const props: PropTypesOf<typeof Comment> = {
|
||||
>>>>>>> abfa39529ff398007bb67cd814c4217a97bdb38b:src/core/client/stream/components/Comment/Comment.spec.tsx
|
||||
author: {
|
||||
username: "Marvin",
|
||||
},
|
||||
body: "Woof",
|
||||
gutterBottom: true,
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
};
|
||||
const wrapper = shallow(<Comment {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
+9
-11
@@ -1,9 +1,9 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { Typography } from "talk-ui/components";
|
||||
import * as styles from "./Comment.css";
|
||||
import PermalinkPopover from "./PermalinkPopover";
|
||||
import PermalinkPopover from "../PermalinkPopover";
|
||||
import Timestamp from "./Timestamp";
|
||||
import TopBar from "./TopBar";
|
||||
import Username from "./Username";
|
||||
|
||||
export interface CommentProps {
|
||||
@@ -13,20 +13,18 @@ export interface CommentProps {
|
||||
username: string;
|
||||
} | null;
|
||||
body: string | null;
|
||||
gutterBottom?: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
const Comment: StatelessComponent<CommentProps> = props => {
|
||||
const rootClassName = cn(styles.root, props.className, {
|
||||
[styles.gutterBottom]: props.gutterBottom,
|
||||
});
|
||||
return (
|
||||
<div className={rootClassName} role="article">
|
||||
<div className={styles.topBar}>
|
||||
<div role="article">
|
||||
<TopBar>
|
||||
{props.author && <Username>{props.author.username}</Username>}
|
||||
</div>
|
||||
<Timestamp>{props.createdAt}</Timestamp>
|
||||
</TopBar>
|
||||
<Typography>{props.body}</Typography>
|
||||
<div className={cn("talk-comment-footer")}>
|
||||
<div>
|
||||
<PermalinkPopover commentId={props.id} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
composes: timestamp from "talk-ui/shared/typography.css";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Timestamp from "./Timestamp";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof Timestamp> = {
|
||||
children: "1995-12-17T03:24:00.000Z",
|
||||
};
|
||||
const wrapper = shallow(<Timestamp {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { RelativeTime } from "talk-ui/components";
|
||||
|
||||
import * as styles from "./Timestamp.css";
|
||||
|
||||
export interface TimestampProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
const Timestamp: StatelessComponent<TimestampProps> = props => (
|
||||
<RelativeTime className={styles.root} date={props.children} />
|
||||
);
|
||||
|
||||
export default Timestamp;
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
margin-bottom: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { UIContext, UIContextProps } from "talk-ui/components";
|
||||
|
||||
import TopBar from "./TopBar";
|
||||
|
||||
it("renders correctly on small screens", () => {
|
||||
const props: PropTypesOf<typeof TopBar> = {
|
||||
children: <div>Hello World</div>,
|
||||
};
|
||||
|
||||
const context: UIContextProps = {
|
||||
mediaQueryValues: {
|
||||
width: 320,
|
||||
},
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<UIContext.Provider value={context}>
|
||||
<TopBar {...props} />
|
||||
</UIContext.Provider>
|
||||
);
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly on big screens", () => {
|
||||
const props: PropTypesOf<typeof TopBar> = {
|
||||
children: <div>Hello World</div>,
|
||||
};
|
||||
|
||||
const context: UIContextProps = {
|
||||
mediaQueryValues: {
|
||||
width: 1600,
|
||||
},
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<UIContext.Provider value={context}>
|
||||
<TopBar {...props} />
|
||||
</UIContext.Provider>
|
||||
);
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { Flex, MatchMedia } from "talk-ui/components";
|
||||
|
||||
import * as styles from "./TopBar.css";
|
||||
|
||||
export interface TopBarProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const TopBar: StatelessComponent<TopBarProps> = props => {
|
||||
const rootClassName = cn(styles.root, props.className);
|
||||
return (
|
||||
<MatchMedia minWidth="xs">
|
||||
{matches => (
|
||||
<Flex
|
||||
className={rootClassName}
|
||||
alignItems="baseline"
|
||||
direction={matches ? "row" : "column"}
|
||||
itemGutter={matches ? true : "half"}
|
||||
>
|
||||
{props.children}
|
||||
</Flex>
|
||||
)}
|
||||
</MatchMedia>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopBar;
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { UIContext, UIContextProps } from "talk-ui/components";
|
||||
|
||||
import Username from "./Username";
|
||||
|
||||
it("renders correctly on small screens", () => {
|
||||
const props: PropTypesOf<typeof Username> = {
|
||||
children: "Marvin",
|
||||
};
|
||||
|
||||
const context: UIContextProps = {
|
||||
mediaQueryValues: {
|
||||
width: 320,
|
||||
},
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<UIContext.Provider value={context}>
|
||||
<Username {...props} />
|
||||
</UIContext.Provider>
|
||||
);
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly on big screens", () => {
|
||||
const props: PropTypesOf<typeof Username> = {
|
||||
children: "Marvin",
|
||||
};
|
||||
|
||||
const context: UIContextProps = {
|
||||
mediaQueryValues: {
|
||||
width: 1600,
|
||||
},
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<UIContext.Provider value={context}>
|
||||
<Username {...props} />
|
||||
</UIContext.Provider>
|
||||
);
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { MatchMedia, Typography } from "talk-ui/components";
|
||||
|
||||
import * as styles from "./Username.css";
|
||||
|
||||
export interface UsernameProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
const Username: StatelessComponent<UsernameProps> = props => {
|
||||
return (
|
||||
<MatchMedia minWidth="xs">
|
||||
{matches => (
|
||||
<Typography
|
||||
variant={matches ? "heading2" : "heading3"}
|
||||
className={styles.root}
|
||||
component="span"
|
||||
>
|
||||
{props.children}
|
||||
</Typography>
|
||||
)}
|
||||
</MatchMedia>
|
||||
);
|
||||
};
|
||||
|
||||
export default Username;
|
||||
@@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders username and body 1`] = `
|
||||
<div
|
||||
role="article"
|
||||
>
|
||||
<TopBar>
|
||||
<Username>
|
||||
Marvin
|
||||
</Username>
|
||||
<Timestamp>
|
||||
1995-12-17T03:24:00.000Z
|
||||
</Timestamp>
|
||||
</TopBar>
|
||||
<withPropsOnChange(Typography)>
|
||||
Woof
|
||||
</withPropsOnChange(Typography)>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(RelativeTime)
|
||||
className="Timestamp-root"
|
||||
date="1995-12-17T03:24:00.000Z"
|
||||
/>
|
||||
`;
|
||||
@@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly on big screens 1`] = `
|
||||
<div
|
||||
className="Flex-root TopBar-root Flex-itemGutter Flex-alignBaseline Flex-directionRow"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders correctly on small screens 1`] = `
|
||||
<div
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly on big screens 1`] = `
|
||||
<span
|
||||
className="Typography-root Typography-heading2 Username-root"
|
||||
>
|
||||
Marvin
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`renders correctly on small screens 1`] = `
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Marvin
|
||||
</span>
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
export { default, default as Comment, CommentProps } from "./Comment";
|
||||
@@ -1,6 +1,6 @@
|
||||
.root {
|
||||
border-left: 3px solid;
|
||||
padding-left: calc(1px * var(--spacing-unit));
|
||||
padding-left: var(--spacing-unit);
|
||||
}
|
||||
|
||||
.level0 {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Indent from "./Indent";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof Indent> = {
|
||||
children: <div>Hello World</div>,
|
||||
};
|
||||
const wrapper = shallow(<Indent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -5,7 +5,7 @@
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: calc(1px * var(--spacing-unit));
|
||||
margin-bottom: var(--spacing-unit);
|
||||
}
|
||||
|
||||
.postButtonContainer {
|
||||
|
||||
@@ -3,10 +3,12 @@ import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import sinon, { SinonSpy } from "sinon";
|
||||
|
||||
import ReplyList, { ReplyListProps } from "./ReplyList";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import ReplyList from "./ReplyList";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: ReplyListProps = {
|
||||
const props: PropTypesOf<typeof ReplyList> = {
|
||||
commentID: "comment-id",
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onShowAll: noop,
|
||||
@@ -18,7 +20,7 @@ it("renders correctly", () => {
|
||||
});
|
||||
|
||||
describe("when there is more", () => {
|
||||
const props: ReplyListProps = {
|
||||
const props: PropTypesOf<typeof ReplyList> = {
|
||||
commentID: "comment-id",
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onShowAll: sinon.spy(),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { Button } from "talk-ui/components";
|
||||
import { Button, Flex } from "talk-ui/components";
|
||||
|
||||
import CommentContainer from "../containers/CommentContainer";
|
||||
import Indent from "./Indent";
|
||||
@@ -18,9 +18,14 @@ export interface ReplyListProps {
|
||||
const ReplyList: StatelessComponent<ReplyListProps> = props => {
|
||||
return (
|
||||
<Indent>
|
||||
<div id={`talk-comments-replyList-log--${props.commentID}`} role="log">
|
||||
<Flex
|
||||
direction="column"
|
||||
id={`talk-comments-replyList-log--${props.commentID}`}
|
||||
role="log"
|
||||
itemGutter
|
||||
>
|
||||
{props.comments.map(comment => (
|
||||
<CommentContainer key={comment.id} data={comment} gutterBottom />
|
||||
<CommentContainer key={comment.id} data={comment} />
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="comments-replyList-showAll">
|
||||
@@ -37,7 +42,7 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
|
||||
</Button>
|
||||
</Localized>
|
||||
)}
|
||||
</div>
|
||||
</Flex>
|
||||
</Indent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import sinon from "sinon";
|
||||
import sinon, { SinonSpy } from "sinon";
|
||||
|
||||
import Stream, { StreamProps } from "./Stream";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Stream from "./Stream";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: StreamProps = {
|
||||
const props: PropTypesOf<typeof Stream> = {
|
||||
assetID: "asset-id",
|
||||
isClosed: false,
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
@@ -19,7 +21,7 @@ it("renders correctly", () => {
|
||||
});
|
||||
|
||||
describe("when there is more", () => {
|
||||
const props = {
|
||||
const props: PropTypesOf<typeof Stream> = {
|
||||
assetID: "asset-id",
|
||||
isClosed: false,
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
@@ -35,7 +37,7 @@ describe("when there is more", () => {
|
||||
|
||||
it("calls onLoadMore", () => {
|
||||
wrapper.find("#talk-comments-stream-loadMore").simulate("click");
|
||||
expect(props.onLoadMore.calledOnce).toBe(true);
|
||||
expect((props.onLoadMore as SinonSpy).calledOnce).toBe(true);
|
||||
});
|
||||
|
||||
const wrapperDisabledButton = shallow(<Stream {...props} disableLoadMore />);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { Button } from "talk-ui/components";
|
||||
import { Button, Flex } from "talk-ui/components";
|
||||
|
||||
import CommentContainer from "../containers/CommentContainer";
|
||||
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
|
||||
@@ -24,12 +24,18 @@ const Stream: StatelessComponent<StreamProps> = props => {
|
||||
<div className={styles.root}>
|
||||
<Logo gutterBottom />
|
||||
<PostCommentFormContainer assetID={props.assetID} />
|
||||
<div id="talk-comments-stream-log" role="log" aria-live="polite">
|
||||
<Flex
|
||||
direction="column"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
aria-live="polite"
|
||||
itemGutter
|
||||
>
|
||||
{props.comments.map(comment => (
|
||||
<div key={comment.id}>
|
||||
<CommentContainer data={comment} gutterBottom />
|
||||
<Flex direction="column" key={comment.id} itemGutter>
|
||||
<CommentContainer data={comment} />
|
||||
<ReplyListContainer comment={comment} />
|
||||
</div>
|
||||
</Flex>
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="comments-stream-loadMore">
|
||||
@@ -46,7 +52,7 @@ const Stream: StatelessComponent<StreamProps> = props => {
|
||||
</Button>
|
||||
</Localized>
|
||||
)}
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.root {
|
||||
composes: heading4 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import Username from "./Username";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props = {
|
||||
children: "Marvin",
|
||||
};
|
||||
const wrapper = shallow(<Username {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import * as styles from "./Username.css";
|
||||
|
||||
export interface CommentProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
const Username: StatelessComponent<CommentProps> = props => {
|
||||
return <span className={styles.root}>{props.children}</span>;
|
||||
};
|
||||
|
||||
export default Username;
|
||||
@@ -0,0 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<Flex
|
||||
className="App-root"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Relay(StreamContainer)
|
||||
asset={Object {}}
|
||||
/>
|
||||
</Flex>
|
||||
`;
|
||||
|
||||
exports[`renders correctly when asset is null 1`] = `
|
||||
<div>
|
||||
Asset not found
|
||||
</div>
|
||||
`;
|
||||
@@ -1,37 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders username and body 1`] = `
|
||||
<div
|
||||
className="Comment-root"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<Username>
|
||||
Marvin
|
||||
</Username>
|
||||
</div>
|
||||
<withPropsOnChange(Typography)>
|
||||
Woof
|
||||
</withPropsOnChange(Typography)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders with gutterBottom 1`] = `
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<Username>
|
||||
Marvin
|
||||
</Username>
|
||||
</div>
|
||||
<withPropsOnChange(Typography)>
|
||||
Woof
|
||||
</withPropsOnChange(Typography)>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="Indent-root Indent-level0"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<Indent>
|
||||
<div
|
||||
<Flex
|
||||
direction="column"
|
||||
id="talk-comments-replyList-log--comment-id"
|
||||
itemGutter={true}
|
||||
role="log"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -12,7 +14,6 @@ exports[`renders correctly 1`] = `
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-1"
|
||||
/>
|
||||
<Relay(CommentContainer)
|
||||
@@ -21,17 +22,18 @@ exports[`renders correctly 1`] = `
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-2"
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
</Indent>
|
||||
`;
|
||||
|
||||
exports[`when there is more renders a load more button 1`] = `
|
||||
<Indent>
|
||||
<div
|
||||
<Flex
|
||||
direction="column"
|
||||
id="talk-comments-replyList-log--comment-id"
|
||||
itemGutter={true}
|
||||
role="log"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -40,7 +42,6 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-1"
|
||||
/>
|
||||
<Relay(CommentContainer)
|
||||
@@ -49,7 +50,6 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-2"
|
||||
/>
|
||||
<Localized
|
||||
@@ -67,6 +67,6 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
Show All Replies
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</div>
|
||||
</Flex>
|
||||
</Indent>
|
||||
`;
|
||||
|
||||
@@ -10,12 +10,16 @@ exports[`renders correctly 1`] = `
|
||||
<withContext(createMutationContainer(PostCommentFormContainer))
|
||||
assetID="asset-id"
|
||||
/>
|
||||
<div
|
||||
<Flex
|
||||
aria-live="polite"
|
||||
direction="column"
|
||||
id="talk-comments-stream-log"
|
||||
itemGutter={true}
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
<Flex
|
||||
direction="column"
|
||||
itemGutter={true}
|
||||
key="comment-1"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -24,7 +28,6 @@ exports[`renders correctly 1`] = `
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
@@ -33,8 +36,10 @@ exports[`renders correctly 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
itemGutter={true}
|
||||
key="comment-2"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -43,7 +48,6 @@ exports[`renders correctly 1`] = `
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
@@ -52,8 +56,8 @@ exports[`renders correctly 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -67,12 +71,16 @@ exports[`when there is more disables load more button 1`] = `
|
||||
<withContext(createMutationContainer(PostCommentFormContainer))
|
||||
assetID="asset-id"
|
||||
/>
|
||||
<div
|
||||
<Flex
|
||||
aria-live="polite"
|
||||
direction="column"
|
||||
id="talk-comments-stream-log"
|
||||
itemGutter={true}
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
<Flex
|
||||
direction="column"
|
||||
itemGutter={true}
|
||||
key="comment-1"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -81,7 +89,6 @@ exports[`when there is more disables load more button 1`] = `
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
@@ -90,8 +97,10 @@ exports[`when there is more disables load more button 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
itemGutter={true}
|
||||
key="comment-2"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -100,7 +109,6 @@ exports[`when there is more disables load more button 1`] = `
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
@@ -109,7 +117,7 @@ exports[`when there is more disables load more button 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
<Localized
|
||||
id="comments-stream-loadMore"
|
||||
>
|
||||
@@ -125,7 +133,7 @@ exports[`when there is more disables load more button 1`] = `
|
||||
Load More
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -139,12 +147,16 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
<withContext(createMutationContainer(PostCommentFormContainer))
|
||||
assetID="asset-id"
|
||||
/>
|
||||
<div
|
||||
<Flex
|
||||
aria-live="polite"
|
||||
direction="column"
|
||||
id="talk-comments-stream-log"
|
||||
itemGutter={true}
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
<Flex
|
||||
direction="column"
|
||||
itemGutter={true}
|
||||
key="comment-1"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -153,7 +165,6 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
@@ -162,8 +173,10 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
itemGutter={true}
|
||||
key="comment-2"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
@@ -172,7 +185,6 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
@@ -181,7 +193,7 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
<Localized
|
||||
id="comments-stream-loadMore"
|
||||
>
|
||||
@@ -197,6 +209,6 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
Load More
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import { AppContainer } from "./AppContainer";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof AppContainer> = {
|
||||
data: {
|
||||
asset: {},
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(<AppContainer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -11,7 +11,7 @@ interface InnerProps {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
const AppContainer: StatelessComponent<InnerProps> = props => {
|
||||
export const AppContainer: StatelessComponent<InnerProps> = props => {
|
||||
return <App {...props.data} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import { CommentContainer } from "./CommentContainer";
|
||||
|
||||
it("renders username and body", () => {
|
||||
const props = {
|
||||
const props: PropTypesOf<typeof CommentContainer> = {
|
||||
data: {
|
||||
author: {
|
||||
username: "Marvin",
|
||||
},
|
||||
body: "Woof",
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ graphql`
|
||||
username
|
||||
}
|
||||
body
|
||||
createdAt
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import ReplyList from "../components/ReplyList";
|
||||
import { ReplyListContainer } from "./ReplyListContainer";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: any = {
|
||||
const props: PropTypesOf<typeof ReplyListContainer> = {
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
replies: {
|
||||
@@ -18,14 +18,14 @@ it("renders correctly", () => {
|
||||
relay: {
|
||||
hasMore: noop,
|
||||
isLoading: noop,
|
||||
},
|
||||
} as any,
|
||||
};
|
||||
const wrapper = shallow(<ReplyListContainer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly when replies are null", () => {
|
||||
const props: any = {
|
||||
const props: PropTypesOf<typeof ReplyListContainer> = {
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
replies: null,
|
||||
@@ -33,7 +33,7 @@ it("renders correctly when replies are null", () => {
|
||||
relay: {
|
||||
hasMore: noop,
|
||||
isLoading: noop,
|
||||
},
|
||||
} as any,
|
||||
};
|
||||
const wrapper = shallow(<ReplyListContainer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -22,7 +22,10 @@ export class ReplyListContainer extends React.Component<InnerProps> {
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (this.props.comment.replies === null) {
|
||||
if (
|
||||
this.props.comment.replies === null ||
|
||||
this.props.comment.replies.edges.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const comments = this.props.comment.replies.edges.map(edge => edge.node);
|
||||
|
||||
+3
-5
@@ -1,9 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Marvin
|
||||
</span>
|
||||
<App
|
||||
asset={Object {}}
|
||||
/>
|
||||
`;
|
||||
@@ -8,5 +8,6 @@ exports[`renders username and body 1`] = `
|
||||
}
|
||||
}
|
||||
body="Woof"
|
||||
createdAt="1995-12-17T03:24:00.000Z"
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`loads more comments 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
className="Flex-root App-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
@@ -40,22 +40,31 @@ exports[`loads more comments 1`] = `
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -64,19 +73,27 @@ exports[`loads more comments 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -85,19 +102,27 @@ exports[`loads more comments 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -113,7 +138,7 @@ exports[`loads more comments 1`] = `
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
className="Flex-root App-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
@@ -151,22 +176,31 @@ exports[`renders comment stream 1`] = `
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -175,19 +209,27 @@ exports[`renders comment stream 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
className="Flex-root App-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
@@ -40,22 +40,31 @@ exports[`renders comment stream 1`] = `
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -64,19 +73,27 @@ exports[`renders comment stream 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -88,21 +105,28 @@ exports[`renders comment stream 1`] = `
|
||||
className="Indent-root Indent-level0"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-replyList-log--comment-with-replies"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -111,17 +135,23 @@ exports[`renders comment stream 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
className="Flex-root App-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
@@ -40,22 +40,31 @@ exports[`renders comment stream 1`] = `
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -64,19 +73,27 @@ exports[`renders comment stream 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
className="Flex-root App-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
@@ -40,22 +40,31 @@ exports[`renders comment stream 1`] = `
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -67,21 +76,28 @@ exports[`renders comment stream 1`] = `
|
||||
className="Indent-root Indent-level0"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-replyList-log--comment-0"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -111,7 +127,7 @@ exports[`renders comment stream 1`] = `
|
||||
|
||||
exports[`show all replies 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
className="Flex-root App-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
@@ -149,22 +165,31 @@ exports[`show all replies 1`] = `
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -176,21 +201,28 @@ exports[`show all replies 1`] = `
|
||||
className="Indent-root Indent-level0"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter Flex-directionColumn"
|
||||
id="talk-comments-replyList-log--comment-0"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
@@ -199,17 +231,23 @@ exports[`show all replies 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
className="Typography-root Typography-heading3 Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<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>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
|
||||
@@ -18,19 +18,19 @@ export const comments = [
|
||||
id: "comment-0",
|
||||
author: users[0],
|
||||
body: "Joining Too",
|
||||
createdAt: "2018-07-06T18:24:00",
|
||||
createdAt: "2018-07-06T18:24:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "comment-1",
|
||||
author: users[1],
|
||||
body: "What's up?",
|
||||
createdAt: "2018-07-06T18:20:00",
|
||||
createdAt: "2018-07-06T18:20:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "comment-2",
|
||||
author: users[2],
|
||||
body: "Hey!",
|
||||
createdAt: "2018-07-06T18:14:00",
|
||||
createdAt: "2018-07-06T18:14:00.000Z",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -54,7 +54,7 @@ export const commentWithReplies = {
|
||||
id: "comment-with-replies",
|
||||
author: users[0],
|
||||
body: "I like yoghurt",
|
||||
createdAt: "2018-07-06T18:24:00",
|
||||
createdAt: "2018-07-06T18:24:00.000Z",
|
||||
replies: {
|
||||
edges: [
|
||||
{ node: comments[0], cursor: comments[0].createdAt },
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
.root {
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import { Manager, Popper, Reference } from "react-popper";
|
||||
import * as styles from "./Attachment.css";
|
||||
|
||||
interface InnerProps {
|
||||
body: React.ReactElement<any> | null;
|
||||
children: React.ReactElement<any>;
|
||||
className?: string;
|
||||
placement?:
|
||||
| "auto-start"
|
||||
| "auto"
|
||||
| "auto-end"
|
||||
| "top-start"
|
||||
| "top"
|
||||
| "top-end"
|
||||
| "right-start"
|
||||
| "right"
|
||||
| "right-end"
|
||||
| "bottom-end"
|
||||
| "bottom"
|
||||
| "bottom-start"
|
||||
| "left-end"
|
||||
| "left"
|
||||
| "left-start";
|
||||
}
|
||||
|
||||
class Attachment extends React.Component<InnerProps> {
|
||||
public render() {
|
||||
const { children, body, placement = "top", className } = this.props;
|
||||
return (
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => React.cloneElement(children, { ref })}
|
||||
</Reference>
|
||||
|
||||
<Popper
|
||||
placement={placement}
|
||||
modifiers={{ preventOverflow: { enabled: false } }}
|
||||
eventsEnabled
|
||||
positionFixed={false}
|
||||
>
|
||||
{({ ref, style }) =>
|
||||
body &&
|
||||
React.cloneElement(body, {
|
||||
ref,
|
||||
style,
|
||||
className: cn(styles.root, className),
|
||||
})
|
||||
}
|
||||
</Popper>
|
||||
</Manager>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Attachment;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./Attachment";
|
||||
export { default } from "./Attachment";
|
||||
@@ -2,6 +2,54 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.halfItemGutter {
|
||||
& > * {
|
||||
margin: 0 calc(0.5 * var(--spacing-unit)) 0 0;
|
||||
}
|
||||
&.directionRowReverse {
|
||||
& > * {
|
||||
margin: 0 0 0 calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
}
|
||||
&.directionColumn {
|
||||
& > * {
|
||||
margin: 0 0 calc(0.5 * var(--spacing-unit)) 0;
|
||||
}
|
||||
}
|
||||
&.directionColumnReverese {
|
||||
& > * {
|
||||
margin: calc(0.5 * var(--spacing-unit)) 0 0 0;
|
||||
}
|
||||
}
|
||||
& > *:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.itemGutter {
|
||||
& > * {
|
||||
margin: 0 var(--spacing-unit) 0 0;
|
||||
}
|
||||
&.directionRowReverse {
|
||||
& > * {
|
||||
margin: 0 0 0 var(--spacing-unit);
|
||||
}
|
||||
}
|
||||
&.directionColumn {
|
||||
& > * {
|
||||
margin: 0 0 var(--spacing-unit) 0;
|
||||
}
|
||||
}
|
||||
&.directionColumnReverese {
|
||||
& > * {
|
||||
margin: var(--spacing-unit) 0 0 0;
|
||||
}
|
||||
}
|
||||
& > *:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.justifyFlexStart {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { pascalCase } from "talk-common/utils";
|
||||
import * as styles from "./Flex.css";
|
||||
|
||||
interface InnerProps {
|
||||
id?: string;
|
||||
role?: string;
|
||||
justifyContent?:
|
||||
| "flex-start"
|
||||
| "flex-end"
|
||||
@@ -16,12 +18,24 @@ interface InnerProps {
|
||||
| "space-evenly";
|
||||
alignItems?: "flex-start" | "flex-end" | "center" | "baseline" | "stretch";
|
||||
direction?: "row" | "column" | "row-reverse" | "column-reverse";
|
||||
itemGutter?: boolean | "half";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Flex: StatelessComponent<InnerProps> = props => {
|
||||
const { justifyContent, alignItems, direction, ...rest } = props;
|
||||
const {
|
||||
className,
|
||||
justifyContent,
|
||||
alignItems,
|
||||
direction,
|
||||
itemGutter,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const classObject: Record<string, boolean> = {};
|
||||
const classObject: Record<string, boolean> = {
|
||||
[styles.itemGutter]: itemGutter === true,
|
||||
[styles.halfItemGutter]: itemGutter === "half",
|
||||
};
|
||||
|
||||
if (justifyContent) {
|
||||
classObject[(styles as any)[`justify${pascalCase(justifyContent)}`]] = true;
|
||||
@@ -35,7 +49,7 @@ const Flex: StatelessComponent<InnerProps> = props => {
|
||||
classObject[(styles as any)[`direction${pascalCase(direction)}`]] = true;
|
||||
}
|
||||
|
||||
const classNames: string = cn(styles.root, classObject);
|
||||
const classNames: string = cn(styles.root, className, classObject);
|
||||
|
||||
return <div className={classNames} {...rest} />;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import MatchMedia from "./MatchMedia";
|
||||
import { MatchMedia } from "./MatchMedia";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof MatchMedia> = {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import { ReactNode, StatelessComponent } from "react";
|
||||
import Responsive from "react-responsive";
|
||||
import Responsive, { MediaQueryMatchers } from "react-responsive";
|
||||
|
||||
import theme from "../../theme/variables";
|
||||
import UIContext from "../UIContext";
|
||||
|
||||
type Breakpoints = keyof typeof theme.breakpoints;
|
||||
|
||||
@@ -20,9 +21,10 @@ interface InnerProps {
|
||||
print?: boolean;
|
||||
screen?: boolean;
|
||||
speech?: boolean;
|
||||
values?: Partial<MediaQueryMatchers>;
|
||||
}
|
||||
|
||||
const MatchMedia: StatelessComponent<InnerProps> = props => {
|
||||
export const MatchMedia: StatelessComponent<InnerProps> = props => {
|
||||
const { speech, minWidth, maxWidth, ...rest } = props;
|
||||
const mapped = {
|
||||
// TODO: Temporarily map newer speech to older aural type until
|
||||
@@ -34,4 +36,12 @@ const MatchMedia: StatelessComponent<InnerProps> = props => {
|
||||
return <Responsive {...rest} {...mapped} />;
|
||||
};
|
||||
|
||||
export default MatchMedia;
|
||||
const MatchMediaWithContext: StatelessComponent<InnerProps> = props => (
|
||||
<UIContext.Consumer>
|
||||
{({ mediaQueryValues }) => (
|
||||
<MatchMedia {...props} values={mediaQueryValues} />
|
||||
)}
|
||||
</UIContext.Consumer>
|
||||
);
|
||||
|
||||
export default MatchMediaWithContext;
|
||||
|
||||
@@ -1,52 +1,19 @@
|
||||
import React from "react";
|
||||
import { Manager, Popper, Reference } from "react-popper";
|
||||
import Attachment from "../Attachment";
|
||||
import * as styles from "./Popover.css";
|
||||
|
||||
interface InnerProps {
|
||||
body: React.ReactElement<any> | null;
|
||||
children: React.ReactElement<any>;
|
||||
placement?:
|
||||
| "auto-start"
|
||||
| "auto"
|
||||
| "auto-end"
|
||||
| "top-start"
|
||||
| "top"
|
||||
| "top-end"
|
||||
| "right-start"
|
||||
| "right"
|
||||
| "right-end"
|
||||
| "bottom-end"
|
||||
| "bottom"
|
||||
| "bottom-start"
|
||||
| "left-end"
|
||||
| "left"
|
||||
| "left-start";
|
||||
}
|
||||
|
||||
class Popover extends React.Component<InnerProps> {
|
||||
public render() {
|
||||
const { children, body, placement = "top" } = this.props;
|
||||
const { body, children: reference } = this.props;
|
||||
return (
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => React.cloneElement(children, { ref })}
|
||||
</Reference>
|
||||
|
||||
<Popper
|
||||
placement={placement}
|
||||
modifiers={{ preventOverflow: { enabled: false } }}
|
||||
eventsEnabled
|
||||
positionFixed={false}
|
||||
>
|
||||
{({ ref, style }) =>
|
||||
React.cloneElement(body, {
|
||||
ref,
|
||||
style,
|
||||
className: styles.root,
|
||||
})
|
||||
}
|
||||
</Popper>
|
||||
</Manager>
|
||||
<Attachment body={body} className={styles.root}>
|
||||
{reference}
|
||||
</Attachment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
composes: body1 from "talk-ui/shared/typography.css";
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: RelativeTime
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground } from 'docz'
|
||||
import RelativeTime from './RelativeTime'
|
||||
|
||||
# RelativeTime
|
||||
|
||||
Renders relative time until given `date`.
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<RelativeTime date="2018-07-04T12:14"
|
||||
formatter={(value, unit, suffix, timestamp) => "".concat(value, " ", unit, " ", suffix)} />
|
||||
</Playground>
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import { create } from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import UIContext from "../UIContext";
|
||||
import RelativeTime from "./RelativeTime";
|
||||
|
||||
it("uses default formatter", () => {
|
||||
const props = {
|
||||
date: "2018-12-17T03:24:00.000Z",
|
||||
};
|
||||
const tree = create(<RelativeTime {...props} />).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses formatter from context", () => {
|
||||
const context: any = {
|
||||
timeagoFormatter: () => "My Context Formatter",
|
||||
};
|
||||
const props: PropTypesOf<typeof RelativeTime> = {
|
||||
date: "2018-12-17T03:24:00.000Z",
|
||||
};
|
||||
const tree = create(
|
||||
<UIContext.Provider value={context}>
|
||||
<RelativeTime {...props} />
|
||||
</UIContext.Provider>
|
||||
).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses formatter from props", () => {
|
||||
const props = {
|
||||
date: "2018-12-17T03:24:00.000Z",
|
||||
formatter: () => "My Props Formatter",
|
||||
};
|
||||
const tree = create(<RelativeTime {...props} />).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
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 "./RelativeTime.css";
|
||||
|
||||
interface InnerProps {
|
||||
date: string;
|
||||
live?: boolean;
|
||||
classes: typeof styles;
|
||||
className?: string;
|
||||
formatter?: Formatter;
|
||||
}
|
||||
|
||||
const defaultFormatter: Formatter = (value, unit, suffix, timestamp: string) =>
|
||||
new Date(timestamp).toISOString();
|
||||
|
||||
class RelativeTime extends React.Component<InnerProps> {
|
||||
public render() {
|
||||
const { date, classes, live, className, formatter } = this.props;
|
||||
return (
|
||||
<UIContext.Consumer>
|
||||
{({ timeagoFormatter }) => (
|
||||
<TimeAgo
|
||||
date={date}
|
||||
className={cn(className, classes.root)}
|
||||
live={live}
|
||||
formatter={timeagoFormatter || formatter || defaultFormatter}
|
||||
/>
|
||||
)}
|
||||
</UIContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withStyles(styles)(RelativeTime);
|
||||
export type RelativeTimeProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,31 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`uses default formatter 1`] = `
|
||||
<time
|
||||
className="RelativeTime-root"
|
||||
dateTime="2018-12-17T03:24:00.000Z"
|
||||
title="2018-12-17T03:24:00.000Z"
|
||||
>
|
||||
2018-12-17T03:24:00.000Z
|
||||
</time>
|
||||
`;
|
||||
|
||||
exports[`uses formatter from context 1`] = `
|
||||
<time
|
||||
className="RelativeTime-root"
|
||||
dateTime="2018-12-17T03:24:00.000Z"
|
||||
title="2018-12-17T03:24:00.000Z"
|
||||
>
|
||||
My Context Formatter
|
||||
</time>
|
||||
`;
|
||||
|
||||
exports[`uses formatter from props 1`] = `
|
||||
<time
|
||||
className="RelativeTime-root"
|
||||
dateTime="2018-12-17T03:24:00.000Z"
|
||||
title="2018-12-17T03:24:00.000Z"
|
||||
>
|
||||
My Context Formatter
|
||||
</time>
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./RelativeTime";
|
||||
export { default } from "./RelativeTime";
|
||||
@@ -43,6 +43,10 @@
|
||||
composes: overline from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
composes: timestamp from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.alignLeft {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -70,7 +74,7 @@
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
margin-bottom: calc(1px * var(--spacing-unit));
|
||||
margin-bottom: var(--spacing-unit);
|
||||
}
|
||||
|
||||
.colorInherit {
|
||||
|
||||
@@ -16,7 +16,8 @@ type Variant =
|
||||
| "subtitle2"
|
||||
| "body1"
|
||||
| "body2"
|
||||
| "button";
|
||||
| "button"
|
||||
| "timestamp";
|
||||
|
||||
// Based on Typography Component of Material UI.
|
||||
// https://github.com/mui-org/material-ui/blob/303199d39b42a321d28347d8440d69166f872f27/packages/material-ui/src/Typography/Typography.js
|
||||
@@ -99,6 +100,8 @@ const Typography: StatelessComponent<InnerProps> = props => {
|
||||
{
|
||||
[classes.colorPrimary]: color === "primary",
|
||||
[classes.colorSecondary]: color === "secondary",
|
||||
[classes.colorError]: color === "error",
|
||||
[classes.colorTextSecondary]: color === "textSecondary",
|
||||
[classes.noWrap]: noWrap,
|
||||
[classes.gutterBottom]: gutterBottom,
|
||||
[classes.paragraph]: paragraph,
|
||||
@@ -129,6 +132,7 @@ Typography.defaultProps = {
|
||||
subtitle2: "h3",
|
||||
body1: "p",
|
||||
body2: "aside",
|
||||
timestamp: "span",
|
||||
},
|
||||
noWrap: false,
|
||||
paragraph: false,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
import { MediaQueryMatchers } from "react-responsive";
|
||||
import { Formatter } from "react-timeago";
|
||||
|
||||
export interface UIContextProps {
|
||||
timeagoFormatter?: Formatter | null;
|
||||
mediaQueryValues?: Partial<MediaQueryMatchers>;
|
||||
}
|
||||
|
||||
const UIContext = React.createContext<UIContextProps>({} as any);
|
||||
|
||||
export default UIContext;
|
||||
@@ -0,0 +1 @@
|
||||
export { default, UIContextProps } from "./UIContext";
|
||||
@@ -1,10 +1,9 @@
|
||||
export { default as BaseButton } from "./BaseButton";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as Typography } from "./Typography";
|
||||
<<<<<<< HEAD
|
||||
export { default as Popover } from "./Popover";
|
||||
export { default as TextField } from "./TextField";
|
||||
=======
|
||||
export { default as RelativeTime } from "./RelativeTime";
|
||||
export { default as UIContext, UIContextProps } from "./UIContext";
|
||||
export { default as Flex } from "./Flex";
|
||||
export { default as MatchMedia } from "./MatchMedia";
|
||||
>>>>>>> cac0afa61e79b19ddcae873f543a6910b83c92e7
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
InferableComponentEnhancerWithProps,
|
||||
withPropsOnChange,
|
||||
} from "recompose";
|
||||
|
||||
/**
|
||||
* withForwardRef provides a property called `forwardRef` using
|
||||
* the `React.forwardRef` api.
|
||||
*/
|
||||
function withForwardRef<T>(): InferableComponentEnhancerWithProps<
|
||||
{ forwardRef: any },
|
||||
{}
|
||||
> {
|
||||
return WrappedComponent => {
|
||||
return React.forwardRef((props, ref) => (
|
||||
ref as button={ref} className="FancyButton">
|
||||
{props.children}
|
||||
</button>
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
export default withForwardRef;
|
||||
@@ -61,3 +61,12 @@
|
||||
|
||||
.overline {
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: var(--palette-text-secondary);
|
||||
font-family: "Source Sans Pro";
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: 14px;
|
||||
line-height: calc(18em / 16);
|
||||
letter-spacing: calc(0.2em / 16);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
*/
|
||||
|
||||
:root {
|
||||
--spacing-unit: var(--spacing-unit-small);
|
||||
--spacing-unit: calc(1px * var(--spacing-unit-small));
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoints-xs) {
|
||||
:root {
|
||||
--spacing-unit: var(--spacing-unit-large);
|
||||
--spacing-unit: calc(1px * var(--spacing-unit-large));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,14 +49,14 @@ const config = convict({
|
||||
mongodb: {
|
||||
doc: "The MongoDB database to connect to.",
|
||||
format: "mongo-uri",
|
||||
default: "mongodb://localhost/talk",
|
||||
default: "mongodb://127.0.0.1:27017/talk",
|
||||
env: "MONGODB",
|
||||
arg: "mongodb",
|
||||
},
|
||||
redis: {
|
||||
doc: "The Redis database to connect to.",
|
||||
format: "redis-uri",
|
||||
default: "redis://localhost:6379",
|
||||
default: "redis://127.0.0.1:6379",
|
||||
env: "REDIS",
|
||||
arg: "redis",
|
||||
},
|
||||
|
||||
@@ -2,6 +2,8 @@ import Context from "talk-server/graph/tenant/context";
|
||||
import { Comment, ConnectionInput } from "talk-server/models/comment";
|
||||
|
||||
export default {
|
||||
createdAt: async (comment: Comment, _: any, ctx: Context) =>
|
||||
comment.created_at,
|
||||
author: async (comment: Comment, _: any, ctx: Context) =>
|
||||
ctx.loaders.Users.user.load(comment.author_id),
|
||||
replies: async (comment: Comment, input: ConnectionInput, ctx: Context) =>
|
||||
|
||||
@@ -202,6 +202,11 @@ type Comment {
|
||||
"""
|
||||
body: String
|
||||
|
||||
"""
|
||||
createdAt is the date in which the comment was created.
|
||||
"""
|
||||
createdAt: Time
|
||||
|
||||
"""
|
||||
author is the User that authored the Comment.
|
||||
"""
|
||||
|
||||
@@ -8,7 +8,10 @@ import { Config } from "talk-server/config";
|
||||
*/
|
||||
export async function createMongoDB(config: Config): Promise<Db> {
|
||||
// Connect and create a client for MongoDB.
|
||||
const client = await MongoClient.connect(config.get("mongodb"));
|
||||
const client = await MongoClient.connect(
|
||||
config.get("mongodb"),
|
||||
{ useNewUrlParser: true }
|
||||
);
|
||||
|
||||
// Return the database handle, which defaults to the database name provided
|
||||
// in the config connection string.
|
||||
|
||||
@@ -5,3 +5,41 @@
|
||||
## Validation
|
||||
|
||||
framework-validation-required = Dies ist ein Pflichtpfeld.
|
||||
|
||||
framework-timeago =
|
||||
{ $suffix ->
|
||||
[ago] vor
|
||||
[in] in
|
||||
}
|
||||
{ $value }
|
||||
{ $unit ->
|
||||
[second] { $value ->
|
||||
[1] Sekunde
|
||||
*[other] Sekunden
|
||||
}
|
||||
[minuto] { $value ->
|
||||
[1] Minute
|
||||
*[other] Minuten
|
||||
}
|
||||
[hour] { $value ->
|
||||
[0] Stunde
|
||||
*[other] Stunden
|
||||
}
|
||||
[day] { $value ->
|
||||
[1] Tag
|
||||
*[other] Tage
|
||||
}
|
||||
[week] { $value ->
|
||||
[1] Woche
|
||||
*[other] Wochen
|
||||
}
|
||||
[month] { $value ->
|
||||
[1] Monat
|
||||
*[other] Monate
|
||||
}
|
||||
[year] { $value ->
|
||||
[1] Jahr
|
||||
*[other] Jahre
|
||||
}
|
||||
*[other] unknown unit
|
||||
}
|
||||
|
||||
@@ -5,3 +5,48 @@
|
||||
## Validation
|
||||
|
||||
framework-validation-required = This field is required.
|
||||
|
||||
framework-timeago-time =
|
||||
{ $value }
|
||||
{ $unit ->
|
||||
[second] { $value ->
|
||||
[1] second
|
||||
*[other] seconds
|
||||
}
|
||||
[minute] { $value ->
|
||||
[1] minute
|
||||
*[other] minutes
|
||||
}
|
||||
[hour] { $value ->
|
||||
[0] hour
|
||||
*[other] hours
|
||||
}
|
||||
[day] { $value ->
|
||||
[1] day
|
||||
*[other] days
|
||||
}
|
||||
[week] { $value ->
|
||||
[1] week
|
||||
*[other] weeks
|
||||
}
|
||||
[month] { $value ->
|
||||
[1] month
|
||||
*[other] months
|
||||
}
|
||||
[year] { $value ->
|
||||
[1] year
|
||||
*[other] years
|
||||
}
|
||||
*[other] unknown unit
|
||||
}
|
||||
|
||||
framework-timeago =
|
||||
{ $value ->
|
||||
[0] now
|
||||
*[other]
|
||||
{ $suffix ->
|
||||
[ago] {framework-timeago-time} ago
|
||||
[in] in {framework-timeago-time}
|
||||
*[other] unknown suffix
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,45 @@
|
||||
### All keys must start with `framework` because this file is shared
|
||||
### among different targets.
|
||||
|
||||
framework-timeago =
|
||||
{ $value ->
|
||||
[0] ahora
|
||||
*[other]
|
||||
{ $suffix ->
|
||||
[ago] hace
|
||||
[in] en
|
||||
*[other] unknown suffix
|
||||
}
|
||||
{ $value }
|
||||
{ $unit ->
|
||||
[second] { $value ->
|
||||
[1] segundo
|
||||
*[other] segundos
|
||||
}
|
||||
[minute] { $value ->
|
||||
[1] minuto
|
||||
*[other] minutos
|
||||
}
|
||||
[hour] { $value ->
|
||||
[0] hora
|
||||
*[other] horas
|
||||
}
|
||||
[day] { $value ->
|
||||
[1] dia
|
||||
*[other] dias
|
||||
}
|
||||
[week] { $value ->
|
||||
[1] semana
|
||||
*[other] semanas
|
||||
}
|
||||
[month] { $value ->
|
||||
[1] mes
|
||||
*[other] meses
|
||||
}
|
||||
[year] { $value ->
|
||||
[1] año
|
||||
*[other] años
|
||||
}
|
||||
*[other] unknown unit
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
declare module "react-timeago" {
|
||||
import React from "react";
|
||||
|
||||
export type Formatter = (
|
||||
value: number,
|
||||
unit: "second" | "minute" | "hour" | "day" | "week" | "month" | "year",
|
||||
suffix: "ago" | "from now",
|
||||
epochMiliseconds: string
|
||||
) => string | React.ReactElement<any>;
|
||||
|
||||
export interface LocaleDefinition {
|
||||
prefixAgo?: string;
|
||||
prefixFromNow?: string;
|
||||
suffixAgo?: string;
|
||||
suffixFromNow?: string;
|
||||
second?: string;
|
||||
seconds?: string;
|
||||
minute?: string;
|
||||
minutes?: string;
|
||||
hour?: string;
|
||||
hours?: string;
|
||||
day?: string;
|
||||
days?: string;
|
||||
week?: string;
|
||||
weeks?: string;
|
||||
month?: string;
|
||||
months?: string;
|
||||
year?: string;
|
||||
years?: string;
|
||||
wordSeparator?: string;
|
||||
numbers?: number[];
|
||||
}
|
||||
|
||||
export interface TimeAgoProps {
|
||||
date: string;
|
||||
live?: boolean;
|
||||
className: string;
|
||||
formatter?: Formatter;
|
||||
}
|
||||
|
||||
const TimeAgo: React.ComponentType<TimeAgoProps>;
|
||||
export default TimeAgo;
|
||||
}
|
||||
|
||||
declare module "react-timeago/lib/formatters/buildFormatter" {
|
||||
import { Formatter, LocaleDefinition } from "react-timeago";
|
||||
function buildFormatter(localeInput: LocaleDefinition): Formatter;
|
||||
export default buildFormatter;
|
||||
}
|
||||
|
||||
declare module "react-timeago/lib/language-strings/*" {
|
||||
import { LocaleDefinition } from "react-timeago";
|
||||
const localeStrings: LocaleDefinition;
|
||||
export default localeStrings;
|
||||
}
|
||||
Reference in New Issue
Block a user