diff --git a/package-lock.json b/package-lock.json
index f76415ba2..100de84ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1338,14 +1338,14 @@
"dev": true
},
"@emotion/babel-utils": {
- "version": "0.6.5",
- "resolved": "https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.5.tgz",
- "integrity": "sha1-NNeETrUy0Rdcj8cBdb63TQcb++s=",
+ "version": "0.6.7",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.7.tgz",
+ "integrity": "sha512-U6iTV9HurrZ0ye8VOTIGcV4tMdDtAIGbfCH58L/NdOhW05pV/KVlNmbXsHp01VMSM+pgmSkGqHSLLE8zdDYJvA==",
"dev": true,
"requires": {
- "@emotion/hash": "^0.6.3",
- "@emotion/memoize": "^0.6.3",
- "@emotion/serialize": "^0.8.3",
+ "@emotion/hash": "^0.6.5",
+ "@emotion/memoize": "^0.6.5",
+ "@emotion/serialize": "^0.8.5",
"convert-source-map": "^1.5.1",
"find-root": "^1.1.0",
"source-map": "^0.7.2"
@@ -1360,54 +1360,54 @@
}
},
"@emotion/hash": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.3.tgz",
- "integrity": "sha1-DnpWBGJvxsbUrEBhovWsgNUCYqQ=",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.5.tgz",
+ "integrity": "sha512-JlZbn5+adseTdDPTUkx/O1/UZbhaGR5fCLLWQDCIJ4eP9fJcVdP/qjlTveEX6mkNoJHWFbZ47wArWQQ0Qk6nMA==",
"dev": true
},
"@emotion/is-prop-valid": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.6.3.tgz",
- "integrity": "sha1-sElW4tZVAn/ojGI0Zp/XBk+wccw=",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.6.5.tgz",
+ "integrity": "sha512-pfjQHT3FCZkAPxqY+sI6MmMTPMCKGIV3DcC1qhf68XkE9pGtKqb5WNN2vfmLgTMGnNVXnaFyx/VjdDYmGFcRaw==",
"dev": true,
"requires": {
- "@emotion/memoize": "^0.6.3"
+ "@emotion/memoize": "^0.6.5"
}
},
"@emotion/memoize": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.3.tgz",
- "integrity": "sha1-ZDeaHWr2+NT+i9bv6dnoJOpLItg=",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.5.tgz",
+ "integrity": "sha512-n1USr7yICA4LFIv7z6kKsXM8rZJxd1btKCBmDewlit+3OJ2j4bDfgXTAxTHYbPkHS/eztHmFWfsbxW2Pu5mDqA==",
"dev": true
},
"@emotion/serialize": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.8.3.tgz",
- "integrity": "sha1-D61Vual/lSPmsf1vtvdLbLQcf4s=",
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.8.5.tgz",
+ "integrity": "sha512-NObUNfK7lqxFpSJWHRwJeeGSZaElEgMn5eR76ift06yx9FdwIfFY47ffapkf2lJUoIUx4ffP55fPSVPMgv0Qgw==",
"dev": true,
"requires": {
- "@emotion/hash": "^0.6.3",
- "@emotion/memoize": "^0.6.3",
- "@emotion/unitless": "^0.6.3",
- "@emotion/utils": "^0.7.1"
+ "@emotion/hash": "^0.6.5",
+ "@emotion/memoize": "^0.6.5",
+ "@emotion/unitless": "^0.6.5",
+ "@emotion/utils": "^0.7.3"
}
},
"@emotion/stylis": {
- "version": "0.6.10",
- "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.6.10.tgz",
- "integrity": "sha1-fTIeY568i6I6zlmQwg6U3Ou4890=",
+ "version": "0.6.12",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.6.12.tgz",
+ "integrity": "sha512-yS+t7l5FeYeiIyADyqjFBJvdotpphHb2S3mP4qak5BpV7ODvxuyAVF24IchEslW+A1MWHAhn5SiOW6GZIumiEQ==",
"dev": true
},
"@emotion/unitless": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.3.tgz",
- "integrity": "sha1-ZWguaKgnAccO77ONf5QaLAv6kN4=",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.5.tgz",
+ "integrity": "sha512-kAFHWyQTMLcOvse+RfEfJ6uwwOlVFZcUOMX0wuBvEMwnF3Zb/6VQ/xDgbx9fNlp3I6AAmijAtUtIyT2qVldpnA==",
"dev": true
},
"@emotion/utils": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.7.1.tgz",
- "integrity": "sha1-5E5ZbQPJ8WujsSetMzqKByvLWgo=",
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.7.3.tgz",
+ "integrity": "sha512-1bGqG9IYJETOOqQEjWinGx1deLlAW/Fcew4GyrzZx0dJIgAjivZTmwzjrhB+Vhj7TgjMr0aTyj5byHmZezisZQ==",
"dev": true
},
"@mdx-js/loader": {
@@ -1544,9 +1544,9 @@
"dev": true
},
"@types/bluebird": {
- "version": "3.5.21",
- "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.21.tgz",
- "integrity": "sha512-6UNEwyw+6SGMC/WMI0ld0PS4st7Qq51qgguFrFizOSpGvZiqe9iswztFSdZvwJBEhLOy2JaxNE6VC7yMAlbfyQ==",
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.22.tgz",
+ "integrity": "sha512-wQamz3CYZCz9QVxhPOF+Odu2jZifcXkGJDff7580sx/TCR6/9Zsgv+iGe2/o4Upcs0RDFLVJuEtYSEFeAqDTSA==",
"dev": true
},
"@types/body-parser": {
@@ -1794,9 +1794,9 @@
}
},
"@types/ioredis": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-3.2.12.tgz",
- "integrity": "sha512-n1jXNSx4CFcDSmuV1Zchb6YpmcovMgCZtsboqGExlDRR6DWKorS9Dy44ekaM5Txq0CYlZhqJSj86gG0/nW/EBA==",
+ "version": "3.2.13",
+ "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-3.2.13.tgz",
+ "integrity": "sha512-38qPXqV4oEzucMcdl0yhqN9JCGYOjspvfQyOL4T4HpMcD4VIj4RfuumuM2mwTtK12jiHF0oxjyU8EvnLfHD+TA==",
"dev": true,
"requires": {
"@types/bluebird": "*",
@@ -1804,9 +1804,9 @@
}
},
"@types/jest": {
- "version": "23.1.5",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.1.5.tgz",
- "integrity": "sha512-GlN74UAcT2i+G4BzVVI/aHip0HDXZaiY11VEjHzAz74+dB3hIeM5lJmnnZx4acxxinK9lT+uEH1Vsa5aWj6w4Q==",
+ "version": "23.1.6",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.1.6.tgz",
+ "integrity": "sha512-lBu2tjrfGuj0gARErNmHZJrnWBdRrYk2XqlBY3LRv8Dqxk3w3461uuFMKmwfDDiOa5kzXocUnunCBBacGwF3+A==",
"dev": true
},
"@types/joi": {
@@ -1843,9 +1843,9 @@
"dev": true
},
"@types/lodash": {
- "version": "4.14.111",
- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.111.tgz",
- "integrity": "sha512-t2FwnkdqdZNYPJHTEF+Zf//j5d2I7UbM2Ng+vqqmUCE2RuiVVINJi9RlVdpKvqPqVItsJa0X+ra/tvmwLzlcgg==",
+ "version": "4.14.112",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.112.tgz",
+ "integrity": "sha512-jDD7sendv3V7iwyRXSlECOR8HCtMN2faVA9ngLdHHihSVIwY7nbfsKl2kA6fimUDU1i5l/zgpG3aevwWnN3zCQ==",
"dev": true
},
"@types/luxon": {
@@ -1954,6 +1954,15 @@
"csstype": "^2.2.0"
}
},
+ "@types/react-copy-to-clipboard": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.2.5.tgz",
+ "integrity": "sha512-5LggaWWlMcgehfeDmr4FZmK8MXntzxLYERFdrXk72ye6QwpoZpyYFGgUM+xGVVtIP2WXlor8twZTXK1xckEDYw==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-dom": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.6.tgz",
@@ -2928,9 +2937,9 @@
"dev": true
},
"ast-types": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz",
- "integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==",
+ "version": "0.11.5",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz",
+ "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==",
"dev": true
},
"astral-regex": {
@@ -3248,9 +3257,9 @@
}
},
"babel-plugin-emotion": {
- "version": "9.2.5",
- "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.5.tgz",
- "integrity": "sha1-AEbgO+XBYnb4U4BHb4jJ/L98lTY=",
+ "version": "9.2.6",
+ "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.6.tgz",
+ "integrity": "sha512-aCRXUPm2pwaUqUtpQ2Gzbn5EeOD2RyUDTQDJl5Yqwg1RLQPs3OvnB6Xt6GUrMomMISxuwFrxuWfBMajHv74UjQ==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "7.0.0-beta.51",
@@ -3258,6 +3267,7 @@
"@emotion/hash": "^0.6.2",
"@emotion/memoize": "^0.6.1",
"@emotion/stylis": "^0.6.10",
+ "babel-core": "^6.26.3",
"babel-plugin-macros": "^2.0.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"convert-source-map": "^1.5.0",
@@ -3287,6 +3297,45 @@
"lodash": "^4.17.5",
"to-fast-properties": "^2.0.0"
}
+ },
+ "babel-core": {
+ "version": "6.26.3",
+ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
+ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "^6.26.0",
+ "babel-generator": "^6.26.0",
+ "babel-helpers": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-register": "^6.26.0",
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "convert-source-map": "^1.5.1",
+ "debug": "^2.6.9",
+ "json5": "^0.5.1",
+ "lodash": "^4.17.4",
+ "minimatch": "^3.0.4",
+ "path-is-absolute": "^1.0.1",
+ "private": "^0.1.8",
+ "slash": "^1.0.0",
+ "source-map": "^0.5.7"
+ }
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
+ "dev": true
+ },
+ "json5": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+ "dev": true
}
}
},
@@ -6169,6 +6218,15 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
+ "copy-to-clipboard": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz",
+ "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==",
+ "dev": true,
+ "requires": {
+ "toggle-selection": "^1.0.3"
+ }
+ },
"copy-webpack-plugin": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz",
@@ -6434,9 +6492,9 @@
}
},
"create-emotion": {
- "version": "9.2.5",
- "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-9.2.5.tgz",
- "integrity": "sha1-XbTwbZNgJeQ70xJFOj7pRuTV5ds=",
+ "version": "9.2.6",
+ "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-9.2.6.tgz",
+ "integrity": "sha512-4g46va26lw6DPfKF7HeWY3OI/qoaNSwpvO+li8dMydZfC6f6+ZffwlYHeIyAhGR8Z8C8c0H9J1pJbQRtb9LScw==",
"dev": true,
"requires": {
"@emotion/hash": "^0.6.2",
@@ -6449,9 +6507,9 @@
}
},
"create-emotion-styled": {
- "version": "9.2.5",
- "resolved": "https://registry.npmjs.org/create-emotion-styled/-/create-emotion-styled-9.2.5.tgz",
- "integrity": "sha1-P1VTZXECM/DyaDHR7PC473YNSyo=",
+ "version": "9.2.6",
+ "resolved": "https://registry.npmjs.org/create-emotion-styled/-/create-emotion-styled-9.2.6.tgz",
+ "integrity": "sha512-Kk6qWuEiHY9iptLsxzoJztHM237yedoY2Zhmc79HwkRkC+wZ7W8IFt5gP6AAGx6eN44hc3Zwc+WA5N7ku3QJfg==",
"dev": true,
"requires": {
"@emotion/is-prop-valid": "^0.6.1"
@@ -7938,6 +7996,16 @@
"tapable": "^1.0.0"
}
},
+ "eslint-scope": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz",
+ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
@@ -8029,9 +8097,9 @@
}
},
"webpack": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.0.tgz",
- "integrity": "sha512-oNx9djAd6uAcccyfqN3hyXLNMjZHiRySZmBQ4c8FNmf1SNJGhx7n9TSvHNyXxgToRdH65g/Q97s94Ip9N6F7xg==",
+ "version": "4.16.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.1.tgz",
+ "integrity": "sha512-6jpzObU18y7lXDJz7XCLvzgrqcJ0rZ2jhKvnTivza9gM2GvPW93xxtmEll2GgmdC0zVQAtbHrH/9BtyMjSDZfA==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.5.13",
@@ -8045,7 +8113,7 @@
"ajv-keywords": "^3.1.0",
"chrome-trace-event": "^1.0.0",
"enhanced-resolve": "^4.1.0",
- "eslint-scope": "^3.7.1",
+ "eslint-scope": "^4.0.0",
"json-parse-better-errors": "^1.0.2",
"loader-runner": "^2.3.0",
"loader-utils": "^1.1.0",
@@ -8061,15 +8129,6 @@
"webpack-sources": "^1.0.1"
}
},
- "ws": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
- "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
- "dev": true,
- "requires": {
- "async-limiter": "~1.0.0"
- }
- },
"yargs": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz",
@@ -8327,19 +8386,19 @@
"dev": true
},
"emotion": {
- "version": "9.2.5",
- "resolved": "https://registry.npmjs.org/emotion/-/emotion-9.2.5.tgz",
- "integrity": "sha1-Zm+xUaacJcdYL/PeBvYPjYSPdKo=",
+ "version": "9.2.6",
+ "resolved": "https://registry.npmjs.org/emotion/-/emotion-9.2.6.tgz",
+ "integrity": "sha512-95/EiWkADklxyy1y1qlJeX5Cepa7WfpJBJSBgbLkDCBzOnP4maluvz52xcV5UaObBTfVnEBq77Go6/bgF7+xaA==",
"dev": true,
"requires": {
- "babel-plugin-emotion": "^9.2.5",
- "create-emotion": "^9.2.5"
+ "babel-plugin-emotion": "^9.2.6",
+ "create-emotion": "^9.2.6"
}
},
"emotion-theming": {
- "version": "9.2.5",
- "resolved": "https://registry.npmjs.org/emotion-theming/-/emotion-theming-9.2.5.tgz",
- "integrity": "sha1-8z8hDxHIDhE2QbNK7J4L+eBMvYk=",
+ "version": "9.2.6",
+ "resolved": "https://registry.npmjs.org/emotion-theming/-/emotion-theming-9.2.6.tgz",
+ "integrity": "sha512-sbZStubPmaDuMhs3+saH4XegnoMgbVtEY2giD1MP+maDinCnJdzf/1Apcip1wo5HMAN7vrjvpmcY13pH34xR6g==",
"dev": true,
"requires": {
"hoist-non-react-statics": "^2.3.1"
@@ -8614,9 +8673,9 @@
}
},
"esm": {
- "version": "3.0.69",
- "resolved": "https://registry.npmjs.org/esm/-/esm-3.0.69.tgz",
- "integrity": "sha512-ECQbEBAzGE15NJXO+ediu/Zdk7rtxIkPAlFbgoJbLM96lAJEKC0XfmN2PmFHNvh6k6m+wAJUffL3E49KO7xCLg==",
+ "version": "3.0.71",
+ "resolved": "https://registry.npmjs.org/esm/-/esm-3.0.71.tgz",
+ "integrity": "sha512-RAgyznLvtK17ld4IQBp+wEsVp3JAHb9Nnbu5fGevnFpKeKVPuh/WexdqUuanhez5s9NJD2+iEtM8LfFu0jL8Rw==",
"dev": true
},
"esprima": {
@@ -9330,6 +9389,13 @@
"dev": true,
"requires": {
"intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b"
+ },
+ "dependencies": {
+ "intl-pluralrules": {
+ "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
+ "from": "github:projectfluent/IntlPluralRules#module",
+ "dev": true
+ }
}
},
"fluent-langneg": {
@@ -9527,24 +9593,28 @@
"dependencies": {
"abbrev": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"aproba": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.4",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"dev": true,
"optional": true,
"requires": {
@@ -9554,12 +9624,14 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
@@ -9568,34 +9640,40 @@
},
"chownr": {
"version": "1.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=",
"dev": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
},
"core-util-is": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true,
"optional": true
},
"debug": {
"version": "2.6.9",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"optional": true,
"requires": {
@@ -9604,25 +9682,29 @@
},
"deep-extend": {
"version": "0.5.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==",
"dev": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true,
"optional": true
},
"detect-libc": {
"version": "1.0.3",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"dev": true,
"optional": true
},
"fs-minipass": {
"version": "1.2.5",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
"dev": true,
"optional": true,
"requires": {
@@ -9631,13 +9713,15 @@
},
"fs.realpath": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true,
"optional": true
},
"gauge": {
"version": "2.7.4",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"optional": true,
"requires": {
@@ -9653,7 +9737,8 @@
},
"glob": {
"version": "7.1.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"optional": true,
"requires": {
@@ -9667,13 +9752,15 @@
},
"has-unicode": {
"version": "2.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true,
"optional": true
},
"iconv-lite": {
"version": "0.4.21",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==",
"dev": true,
"optional": true,
"requires": {
@@ -9682,7 +9769,8 @@
},
"ignore-walk": {
"version": "3.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
"dev": true,
"optional": true,
"requires": {
@@ -9691,7 +9779,8 @@
},
"inflight": {
"version": "1.0.6",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"optional": true,
"requires": {
@@ -9701,18 +9790,21 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"ini": {
"version": "1.3.5",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "^1.0.0"
@@ -9720,13 +9812,15 @@
},
"isarray": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true,
"optional": true
},
"minimatch": {
"version": "3.0.4",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@@ -9734,12 +9828,14 @@
},
"minimist": {
"version": "0.0.8",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"minipass": {
"version": "2.2.4",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
"dev": true,
"requires": {
"safe-buffer": "^5.1.1",
@@ -9748,7 +9844,8 @@
},
"minizlib": {
"version": "1.1.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==",
"dev": true,
"optional": true,
"requires": {
@@ -9757,7 +9854,8 @@
},
"mkdirp": {
"version": "0.5.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
@@ -9765,13 +9863,15 @@
},
"ms": {
"version": "2.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true,
"optional": true
},
"needle": {
"version": "2.2.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==",
"dev": true,
"optional": true,
"requires": {
@@ -9782,7 +9882,8 @@
},
"node-pre-gyp": {
"version": "0.10.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==",
"dev": true,
"optional": true,
"requires": {
@@ -9800,7 +9901,8 @@
},
"nopt": {
"version": "4.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"dev": true,
"optional": true,
"requires": {
@@ -9810,13 +9912,15 @@
},
"npm-bundled": {
"version": "1.0.3",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==",
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.1.10",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==",
"dev": true,
"optional": true,
"requires": {
@@ -9826,7 +9930,8 @@
},
"npmlog": {
"version": "4.1.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"optional": true,
"requires": {
@@ -9838,18 +9943,21 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"object-assign": {
"version": "4.1.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true,
"optional": true
},
"once": {
"version": "1.4.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
@@ -9857,19 +9965,22 @@
},
"os-homedir": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true,
"optional": true
},
"osenv": {
"version": "0.1.5",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"dev": true,
"optional": true,
"requires": {
@@ -9879,19 +9990,22 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"optional": true
},
"process-nextick-args": {
"version": "2.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"dev": true,
"optional": true
},
"rc": {
"version": "1.2.7",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==",
"dev": true,
"optional": true,
"requires": {
@@ -9903,7 +10017,8 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true,
"optional": true
}
@@ -9911,7 +10026,8 @@
},
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"optional": true,
"requires": {
@@ -9926,7 +10042,8 @@
},
"rimraf": {
"version": "2.6.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dev": true,
"optional": true,
"requires": {
@@ -9935,42 +10052,49 @@
},
"safe-buffer": {
"version": "5.1.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"optional": true
},
"sax": {
"version": "1.2.4",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true,
"optional": true
},
"semver": {
"version": "5.5.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true,
"optional": true
},
"string-width": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "^1.0.0",
@@ -9980,7 +10104,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"optional": true,
"requires": {
@@ -9989,7 +10114,8 @@
},
"strip-ansi": {
"version": "3.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
@@ -9997,13 +10123,15 @@
},
"strip-json-comments": {
"version": "2.0.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true,
"optional": true
},
"tar": {
"version": "4.4.1",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==",
"dev": true,
"optional": true,
"requires": {
@@ -10018,13 +10146,15 @@
},
"util-deprecate": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true,
"optional": true
},
"wide-align": {
"version": "1.1.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"dev": true,
"optional": true,
"requires": {
@@ -10033,12 +10163,14 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"yallist": {
"version": "3.0.2",
- "bundled": true,
+ "resolved": "",
+ "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
"dev": true
}
}
@@ -11393,11 +11525,6 @@
"integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=",
"dev": true
},
- "intl-pluralrules": {
- "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
- "from": "intl-pluralrules@github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
- "dev": true
- },
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -16368,6 +16495,12 @@
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
"dev": true
},
+ "popper.js": {
+ "version": "1.14.3",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz",
+ "integrity": "sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=",
+ "dev": true
+ },
"portfinder": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz",
@@ -16522,12 +16655,12 @@
}
},
"postcss-color-functional-notation": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-1.0.1.tgz",
- "integrity": "sha512-8ECwUd75SwcjLMzFdRVRBqjVoHwGeWpLQKCNIQo9T+QeCrUNKI1NJ6rgpni5vo7iZ7MZy21qKqUieMM3D0wHYQ==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-1.0.2.tgz",
+ "integrity": "sha512-FxkEr+s/KCrcrTxUhHcDMKGZmnLjUKK7pl2gDjnEoAJaVcbThdDWLhuASu02qdA3Ys7np/BwJgwc72JrURTvJQ==",
"dev": true,
"requires": {
- "postcss": "^6.0.22",
+ "postcss": "^6.0.23",
"postcss-values-parser": "^1.5.0"
}
},
@@ -17819,6 +17952,15 @@
}
}
},
+ "postcss-nesting": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-6.0.0.tgz",
+ "integrity": "sha512-Yoglsy6eZbDCbRIXoYSmnIt9ao4xyg07iFwVBd7WyIkDzMSeRxIqUk8xEAdkeJQ7eGfWo6RufrTU7FSUjZ22fg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^6.0.22"
+ }
+ },
"postcss-normalize-charset": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz",
@@ -18088,18 +18230,18 @@
}
},
"postcss-preset-env": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-5.2.1.tgz",
- "integrity": "sha512-zsGFBKtTyCY5YxOq6Q/WR4oa9/LOKawEWMxJ7UpMtfj1t50OWeGZadRQp/i7DSSWjKus+eJKLIHDxLaobwhidQ==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-5.2.2.tgz",
+ "integrity": "sha512-ehhyyssfRLv6N5zysA09lTgL1KgGOt9RMuXEOrB8CV+aH6Ky1fz1OMmEv9YK+JQ6okRIeQTktOZ+STfTZj2ckw==",
"dev": true,
"requires": {
- "autoprefixer": "^8.6.3",
+ "autoprefixer": "^8.6.5",
"browserslist": "^3.2.8",
- "caniuse-lite": "^1.0.30000859",
+ "caniuse-lite": "^1.0.30000865",
"cssdb": "^3.1.0",
"postcss": "^6.0.23",
"postcss-attribute-case-insensitive": "^3.0.1",
- "postcss-color-functional-notation": "^1.0.1",
+ "postcss-color-functional-notation": "^1.0.2",
"postcss-color-hex-alpha": "^3.0.0",
"postcss-color-mod-function": "^2.4.2",
"postcss-color-rebeccapurple": "^3.1.0",
@@ -18128,14 +18270,11 @@
"postcss-selector-not": "^3.0.1"
},
"dependencies": {
- "postcss-nesting": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-6.0.0.tgz",
- "integrity": "sha512-Yoglsy6eZbDCbRIXoYSmnIt9ao4xyg07iFwVBd7WyIkDzMSeRxIqUk8xEAdkeJQ7eGfWo6RufrTU7FSUjZ22fg==",
- "dev": true,
- "requires": {
- "postcss": "^6.0.22"
- }
+ "caniuse-lite": {
+ "version": "1.0.30000865",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz",
+ "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==",
+ "dev": true
}
}
},
@@ -19059,6 +19198,16 @@
"integrity": "sha512-9Fv10F6rrzFDIT04aoRsw3dbN6l6onNXbBvCpt4OjXZFGDz/P65laNIujHVTlVjcqErut9d4BL0aplVByyGcJw==",
"dev": true
},
+ "react-copy-to-clipboard": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz",
+ "integrity": "sha512-ELKq31/E3zjFs5rDWNCfFL4NvNFQvGRoJdAKReD/rUPA+xxiLPQmZBZBvy2vgH7V0GE9isIQpT9WXbwIVErYdA==",
+ "dev": true,
+ "requires": {
+ "copy-to-clipboard": "^3",
+ "prop-types": "^15.5.8"
+ }
+ },
"react-dev-utils": {
"version": "6.0.0-next.3e165448",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-6.0.0-next.3e165448.tgz",
@@ -19256,18 +19405,26 @@
"dev": true
},
"react-docgen": {
- "version": "3.0.0-beta9",
- "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-3.0.0-beta9.tgz",
- "integrity": "sha512-3UqwxygAP/eZdDtOKum6vClKWUlceZ7RBVQ3Fe122l1WBYOqHcBzoUZIwN8feaLVo+s2eB/q+NkBfanLgvmt+w==",
+ "version": "3.0.0-rc.0",
+ "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-3.0.0-rc.0.tgz",
+ "integrity": "sha512-quhNeAo5LeACZ31LDAifq2Knyz2XE+Zm9QLo4EHgw+NifxBzH8FCQ5PyAPORqaAhWMiPOwYoIZ0biZVGqEkYag==",
"dev": true,
"requires": {
+ "@babel/parser": "7.0.0-beta.53",
"async": "^2.1.4",
"babel-runtime": "^6.9.2",
- "babylon": "7.0.0-beta.31",
"commander": "^2.9.0",
"doctrine": "^2.0.0",
"node-dir": "^0.1.10",
- "recast": "^0.12.6"
+ "recast": "^0.15.0"
+ },
+ "dependencies": {
+ "@babel/parser": {
+ "version": "7.0.0-beta.53",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.53.tgz",
+ "integrity": "sha1-H0XrYXv5Rj1IKywE00nZ5O2/SJI=",
+ "dev": true
+ }
}
},
"react-docgen-typescript": {
@@ -19299,13 +19456,13 @@
}
},
"react-emotion": {
- "version": "9.2.5",
- "resolved": "https://registry.npmjs.org/react-emotion/-/react-emotion-9.2.5.tgz",
- "integrity": "sha1-DkDt8GLX7qJg1DJ6n+JyiOyDg14=",
+ "version": "9.2.6",
+ "resolved": "https://registry.npmjs.org/react-emotion/-/react-emotion-9.2.6.tgz",
+ "integrity": "sha512-3GwLQ0Vib/X0G7KWY7N1F/7NmfoBLkkg3g9Jd8t6By5QUH0PyCSUVdDmfFBbgVKsAcpkjA0vcsMbiWeZusaRrA==",
"dev": true,
"requires": {
- "babel-plugin-emotion": "^9.2.5",
- "create-emotion-styled": "^9.2.5"
+ "babel-plugin-emotion": "^9.2.6",
+ "create-emotion-styled": "^9.2.6"
}
},
"react-error-overlay": {
@@ -19358,6 +19515,31 @@
"integrity": "sha512-OQvgdiPfWbAjudT8Q+V2aWsySCJW0aQ3wNjfOw1ooamydEQkDxRfWqfnEhCYLneCz67d+r3kBYoZlpAx5S+VWA==",
"dev": true
},
+ "react-popper": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.0.0.tgz",
+ "integrity": "sha1-uZRSFE6P5KzHf6PZWajHngemUIQ=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.x.x",
+ "create-react-context": "^0.2.1",
+ "popper.js": "^1.14.1",
+ "prop-types": "^15.6.1",
+ "typed-styles": "^0.0.5",
+ "warning": "^3.0.0"
+ },
+ "dependencies": {
+ "warning": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+ "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
"react-powerplug": {
"version": "1.0.0-rc.1",
"resolved": "https://registry.npmjs.org/react-powerplug/-/react-powerplug-1.0.0-rc.1.tgz",
@@ -19498,6 +19680,14 @@
"ramda": "^0.25.0",
"source-map-loader": "^0.2.3",
"typescript": "^2.7.2"
+ },
+ "dependencies": {
+ "typescript": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
+ "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
+ "dev": true
+ }
}
},
"read-cache": {
@@ -19574,13 +19764,12 @@
}
},
"recast": {
- "version": "0.12.9",
- "resolved": "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz",
- "integrity": "sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==",
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.15.2.tgz",
+ "integrity": "sha512-L4f/GqxjlEJ5IZ+tdll/l+6dVi2ylysWbkgFJbMuldD6Jklgfv6zJnCpuAZDfjwHhfcd/De0dDKelsTEPQ29qA==",
"dev": true,
"requires": {
- "ast-types": "0.10.1",
- "core-js": "^2.4.1",
+ "ast-types": "0.11.5",
"esprima": "~4.0.0",
"private": "~0.1.5",
"source-map": "~0.6.1"
@@ -21923,6 +22112,12 @@
}
}
},
+ "toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=",
+ "dev": true
+ },
"topo": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz",
@@ -22489,6 +22684,12 @@
}
}
},
+ "typed-styles": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.5.tgz",
+ "integrity": "sha512-ht+rEe5UsdEBAa3gr64+QjUOqjOLJfWLvl5HZR5Ev9uo/OnD3p43wPeFSB1hNFc13GXQF/JU1Bn0YHLUqBRIlw==",
+ "dev": true
+ },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -22508,9 +22709,9 @@
"dev": true
},
"typescript": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
- "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz",
+ "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==",
"dev": true
},
"ua-parser-js": {
@@ -23904,9 +24105,9 @@
}
},
"ws": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.0.tgz",
- "integrity": "sha512-c18dMeW+PEQdDFzkhDsnBAlS4Z8KGStBQQUcQ5mf7Nf689jyGk0594L+i9RaQuf4gog6SvWLJorz2NfSaqxZ7w==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
+ "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"requires": {
"async-limiter": "~1.0.0"
}
diff --git a/package.json b/package.json
index 2529918e2..d02d7d0ef 100644
--- a/package.json
+++ b/package.json
@@ -103,6 +103,7 @@
"@types/passport-oauth2": "^1.4.5",
"@types/passport-strategy": "^0.2.33",
"@types/query-string": "^6.1.0",
+ "@types/react-copy-to-clipboard": "^4.2.5",
"@types/react-dom": "^16.0.6",
"@types/react-relay": "github:coralproject/patched#types/react-relay",
"@types/react-responsive": "^3.0.1",
@@ -168,9 +169,11 @@
"query-string": "^6.1.0",
"raw-loader": "^0.5.1",
"react": "^16.4.0",
+ "react-copy-to-clipboard": "^5.0.1",
"react-dev-utils": "6.0.0-next.3e165448",
"react-dom": "^16.4.0",
"react-final-form": "^3.6.4",
+ "react-popper": "^1.0.0-beta.6",
"react-relay": "github:coralproject/patched#react-relay",
"react-responsive": "^4.1.0",
"react-test-renderer": "^16.4.1",
@@ -199,7 +202,7 @@
"typed-css-modules": "^0.3.4",
"typeface-manuale": "0.0.54",
"typeface-source-sans-pro": "0.0.54",
- "typescript": "^2.9.2",
+ "typescript": "^3.0.0",
"uglifyjs-webpack-plugin": "^1.2.5",
"webpack": "4.12.0",
"webpack-cli": "^3.0.2",
diff --git a/src/core/client/embed/Stream.ts b/src/core/client/embed/Stream.ts
index 61db1863d..ae47e450e 100644
--- a/src/core/client/embed/Stream.ts
+++ b/src/core/client/embed/Stream.ts
@@ -5,9 +5,9 @@ import {
Decorator,
withAutoHeight,
withClickEvent,
- withCommentID,
withEventEmitter,
withIOSSafariWidthWorkaround,
+ withSetCommentID,
} from "./decorators";
import PymControl from "./PymControl";
import { ensureEndSlash } from "./utils";
@@ -15,6 +15,7 @@ import { ensureEndSlash } from "./utils";
interface CreatePymControlConfig {
assetID?: string;
assetURL?: string;
+ commentID?: string;
title?: string;
eventEmitter: EventEmitter2;
id: string;
@@ -26,13 +27,14 @@ export function createPymControl(config: CreatePymControlConfig) {
withIOSSafariWidthWorkaround,
withAutoHeight,
withClickEvent,
- withCommentID,
+ withSetCommentID,
withEventEmitter(config.eventEmitter),
];
const query = qs.stringify({
assetID: config.assetID,
assetURL: config.assetURL,
+ commentID: config.commentID,
});
const url = `${ensureEndSlash(config.rootURL)}stream.html?${query}`;
return new PymControl({
@@ -73,6 +75,7 @@ export type StreamInterface = ReturnType;
export interface CreateConfig {
assetID?: string;
assetURL?: string;
+ commentID?: string;
title?: string;
eventEmitter: EventEmitter2;
id: string;
diff --git a/src/core/client/embed/decorators/index.ts b/src/core/client/embed/decorators/index.ts
index 59dbac12c..6e2d45195 100644
--- a/src/core/client/embed/decorators/index.ts
+++ b/src/core/client/embed/decorators/index.ts
@@ -4,7 +4,7 @@ export type CleanupCallback = () => void;
export type Decorator = (pym: pym.Parent) => CleanupCallback | void;
export { default as withAutoHeight } from "./withAutoHeight";
export { default as withClickEvent } from "./withClickEvent";
-export { default as withCommentID } from "./withCommentID";
+export { default as withSetCommentID } from "./withSetCommentID";
export { default as withEventEmitter } from "./withEventEmitter";
export {
default as withIOSSafariWidthWorkaround,
diff --git a/src/core/client/embed/decorators/withCommentID.ts b/src/core/client/embed/decorators/withCommentID.ts
deleted file mode 100644
index 3fa968374..000000000
--- a/src/core/client/embed/decorators/withCommentID.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import qs from "query-string";
-
-import { buildURL } from "../utils";
-import { Decorator } from "./";
-
-const withCommentID: Decorator = pym => {
- // Remove the comment id from the query.
- pym.onMessage("view-all-comments", () => {
- const search = qs.stringify({
- ...qs.parse(location.search),
- commentId: undefined,
- });
-
- // Remove the commentId url param.
- const url = buildURL({ search });
-
- // Change the url.
- window.history.replaceState({}, document.title, url);
- });
-
- // Add the permalink comment id to the query.
- pym.onMessage("view-comment", (id: string) => {
- const search = qs.stringify({
- ...qs.parse(location.search),
- commentId: id,
- });
-
- // Remove the commentId url param.
- const url = buildURL({ search });
-
- // Change the url.
- window.history.replaceState({}, document.title, url);
- });
-};
-
-export default withCommentID;
diff --git a/src/core/client/embed/decorators/withCommentID.spec.ts b/src/core/client/embed/decorators/withSetCommentID.spec.ts
similarity index 73%
rename from src/core/client/embed/decorators/withCommentID.spec.ts
rename to src/core/client/embed/decorators/withSetCommentID.spec.ts
index 2c5802aa0..ed8f17e8b 100644
--- a/src/core/client/embed/decorators/withCommentID.spec.ts
+++ b/src/core/client/embed/decorators/withSetCommentID.spec.ts
@@ -1,17 +1,17 @@
-import withCommentID from "./withCommentID";
+import withSetCommentID from "./withSetCommentID";
it("should add commentID", () => {
const previousLocation = location.toString();
const previousState = window.history.state;
const fakePym = {
onMessage: (type: string, callback: (id: string) => void) => {
- if (type === "view-comment") {
+ if (type === "setCommentID") {
callback("comment-id");
}
},
};
- withCommentID(fakePym as any);
- expect(location.toString()).toBe("http://localhost/?commentId=comment-id");
+ withSetCommentID(fakePym as any);
+ expect(location.toString()).toBe("http://localhost/?commentID=comment-id");
window.history.replaceState(previousState, document.title, previousLocation);
});
@@ -21,16 +21,16 @@ it("should remove commentID", () => {
window.history.replaceState(
previousState,
document.title,
- "http://localhost/?commentId=comment-id"
+ "http://localhost/?commentID=comment-id"
);
const fakePym = {
onMessage: (type: string, callback: () => void) => {
- if (type === "view-all-comments") {
+ if (type === "setCommentID") {
callback();
}
},
};
- withCommentID(fakePym as any);
+ withSetCommentID(fakePym as any);
expect(location.toString()).toBe("http://localhost/");
window.history.replaceState(previousState, document.title, previousLocation);
});
diff --git a/src/core/client/embed/decorators/withSetCommentID.ts b/src/core/client/embed/decorators/withSetCommentID.ts
new file mode 100644
index 000000000..b2b6812a3
--- /dev/null
+++ b/src/core/client/embed/decorators/withSetCommentID.ts
@@ -0,0 +1,22 @@
+import qs from "query-string";
+
+import { buildURL } from "../utils";
+import { Decorator } from "./";
+
+const withSetCommentID: Decorator = pym => {
+ // Add the permalink comment id to the query.
+ pym.onMessage("setCommentID", (id: string) => {
+ const search = qs.stringify({
+ ...qs.parse(location.search),
+ commentID: id || undefined,
+ });
+
+ // Remove the commentId url param.
+ const url = buildURL({ search });
+
+ // Change the url.
+ window.history.replaceState({}, document.title, url);
+ });
+};
+
+export default withSetCommentID;
diff --git a/src/core/client/embed/index.html b/src/core/client/embed/index.html
index 2eedfea25..297768c75 100644
--- a/src/core/client/embed/index.html
+++ b/src/core/client/embed/index.html
@@ -10,7 +10,7 @@
Talk 5.0 – Embed Stream
-
+
diff --git a/src/core/client/embed/index.ts b/src/core/client/embed/index.ts
index 6c6ebc36d..c440f72a8 100644
--- a/src/core/client/embed/index.ts
+++ b/src/core/client/embed/index.ts
@@ -6,6 +6,7 @@ import createStreamInterface from "./Stream";
export interface Config {
assetID?: string;
assetURL?: string;
+ commentID?: string;
rootURL?: string;
id?: string;
events?: (eventEmitter: EventEmitter2) => void;
@@ -23,6 +24,7 @@ export function render(config: Config = {}) {
return createStreamInterface({
assetID: config.assetID || query.assetID,
assetURL: config.assetURL || query.assetURL,
+ commentID: config.commentID || query.commentID,
id: config.id || "talk-embed-stream",
rootURL: config.rootURL || location.origin,
eventEmitter,
diff --git a/src/core/client/framework/lib/relay/createMutationContainer.tsx b/src/core/client/framework/lib/relay/createMutationContainer.tsx
index 0446e7d17..b8c74be18 100644
--- a/src/core/client/framework/lib/relay/createMutationContainer.tsx
+++ b/src/core/client/framework/lib/relay/createMutationContainer.tsx
@@ -7,7 +7,7 @@ import {
} from "recompose";
import { Environment } from "relay-runtime";
-import { withContext } from "../bootstrap";
+import { TalkContext, withContext } from "../bootstrap";
/**
* createMutationContainer creates a HOC that
@@ -18,10 +18,14 @@ import { withContext } from "../bootstrap";
*/
function createMutationContainer(
propName: T,
- commit: (environment: Environment, input: I) => Promise
+ commit: (
+ environment: Environment,
+ input: I,
+ context: TalkContext
+ ) => Promise
): InferableComponentEnhancer<{ [P in T]: (input: I) => Promise }> {
return compose(
- withContext(({ relayEnvironment }) => ({ relayEnvironment })),
+ withContext(context => ({ context })),
hoistStatics((BaseComponent: React.ComponentType) => {
class CreateMutationContainer extends React.Component {
public static displayName = wrapDisplayName(
@@ -30,7 +34,11 @@ function createMutationContainer(
);
private commit = (input: I) => {
- return commit(this.props.relayEnvironment, input);
+ return commit(
+ this.props.context.relayEnvironment,
+ input,
+ this.props.context
+ );
};
public render() {
diff --git a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx
index 9eaeb0ce8..421eb7d71 100644
--- a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx
+++ b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx
@@ -1,8 +1,14 @@
import * as React from "react";
-import { compose, hoistStatics, InferableComponentEnhancer } from "recompose";
+import {
+ compose,
+ hoistStatics,
+ InferableComponentEnhancer,
+ wrapDisplayName,
+} from "recompose";
import {
CSelector,
CSnapshot,
+ Disposable,
Environment,
GraphQLTaggedNode,
} from "relay-runtime";
@@ -36,6 +42,12 @@ function withLocalStateContainer(
withContext(({ relayEnvironment }) => ({ relayEnvironment })),
hoistStatics((BaseComponent: React.ComponentType) => {
class LocalStateContainer extends React.Component {
+ public static displayName = wrapDisplayName(
+ BaseComponent,
+ "withLocalStateContainer"
+ );
+ private subscription: Disposable;
+
constructor(props: Props) {
super(props);
const fragment = (fragmentSpec as any).data().default;
@@ -53,7 +65,10 @@ function withLocalStateContainer(
variables: {},
};
const snapshot = props.relayEnvironment.lookup(selector);
- props.relayEnvironment.subscribe(snapshot, this.updateSnapshot);
+ this.subscription = props.relayEnvironment.subscribe(
+ snapshot,
+ this.updateSnapshot
+ );
this.state = {
data: snapshot.data,
};
@@ -63,6 +78,10 @@ function withLocalStateContainer(
this.setState({ data: snapshot.data });
};
+ public componentWillUnmount() {
+ this.subscription.dispose();
+ }
+
public render() {
const { relayEnvironment: _, ...rest } = this.props;
return ;
diff --git a/src/core/client/framework/testHelpers/createRelayEnvironment.ts b/src/core/client/framework/testHelpers/createRelayEnvironment.ts
new file mode 100644
index 000000000..717c037ad
--- /dev/null
+++ b/src/core/client/framework/testHelpers/createRelayEnvironment.ts
@@ -0,0 +1,78 @@
+import { IResolvers } from "graphql-tools";
+import { createFetch } from "relay-local-schema";
+import {
+ commitLocalUpdate,
+ Environment,
+ Network,
+ RecordProxy,
+ RecordSource,
+ RecordSourceProxy,
+ Store,
+} from "relay-runtime";
+
+import {
+ createAndRetain,
+ LOCAL_ID,
+ LOCAL_TYPE,
+ wrapFetchWithLogger,
+} from "talk-framework/lib/relay";
+
+import { loadSchema } from "talk-common/graphql";
+
+export interface CreateRelayEnvironmentNetworkParams {
+ /** project name of graphql-config */
+ projectName: string;
+ /** graphql resolvers */
+ resolvers: IResolvers;
+ /** If enabled, graphql responses will be logged to the console */
+ logNetwork?: boolean;
+}
+
+export interface CreateRelayEnvironmentParams {
+ /** If set, creates a network to a local graphql server with a local schema */
+ network?: CreateRelayEnvironmentNetworkParams;
+ /** Allows to set initial state for Local state */
+ initLocalState?: (
+ local: RecordProxy,
+ source: RecordSourceProxy,
+ environment: Environment
+ ) => void;
+ /** Use this source for creating the environment */
+ source?: RecordSource;
+}
+
+/**
+ * create Relay environment for tests environments.
+ */
+export default function createRelayEnvironment(
+ params: CreateRelayEnvironmentParams = {}
+) {
+ let network: Network = null as any;
+ if (params.network) {
+ const schema = loadSchema(
+ params.network.projectName,
+ params.network.resolvers
+ );
+ network = Network.create(
+ wrapFetchWithLogger(createFetch({ schema }), params.network.logNetwork)
+ );
+ }
+ const environment = new Environment({
+ network,
+ store: new Store(params.source || new RecordSource()),
+ });
+ commitLocalUpdate(environment, sourceProxy => {
+ const root = sourceProxy.getRoot();
+ const localRecord = createAndRetain(
+ environment,
+ sourceProxy,
+ LOCAL_ID,
+ LOCAL_TYPE
+ );
+ root.setLinkedRecord(localRecord, "local");
+ if (params.initLocalState) {
+ params.initLocalState!(localRecord, sourceProxy, environment);
+ }
+ });
+ return environment;
+}
diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts
new file mode 100644
index 000000000..9cbc97018
--- /dev/null
+++ b/src/core/client/framework/testHelpers/index.ts
@@ -0,0 +1,4 @@
+export {
+ default as createRelayEnvironment,
+ CreateRelayEnvironmentParams,
+} from "./createRelayEnvironment";
diff --git a/src/core/client/stream/components/App.css b/src/core/client/stream/components/App.css
index 68c4e29c4..3f61a7cae 100644
--- a/src/core/client/stream/components/App.css
+++ b/src/core/client/stream/components/App.css
@@ -11,4 +11,5 @@
}
.root {
+ width: 100%;
}
diff --git a/src/core/client/stream/components/App.spec.tsx b/src/core/client/stream/components/App.spec.tsx
index 9b91a8974..d7dfdc3a7 100644
--- a/src/core/client/stream/components/App.spec.tsx
+++ b/src/core/client/stream/components/App.spec.tsx
@@ -5,17 +5,17 @@ import { PropTypesOf } from "talk-framework/types";
import App from "./App";
-it("renders correctly", () => {
+it("renders stream", () => {
const props: PropTypesOf = {
- asset: {},
+ showPermalinkView: false,
};
const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
});
-it("renders correctly when asset is null", () => {
+it("renders permalink view", () => {
const props: PropTypesOf = {
- asset: null,
+ showPermalinkView: true,
};
const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
diff --git a/src/core/client/stream/components/App.tsx b/src/core/client/stream/components/App.tsx
index 14659585a..39272cb17 100644
--- a/src/core/client/stream/components/App.tsx
+++ b/src/core/client/stream/components/App.tsx
@@ -3,23 +3,26 @@ import { StatelessComponent } from "react";
import { Flex } from "talk-ui/components";
-import StreamContainer from "../containers/StreamContainer";
+import PermalinkViewQuery from "../queries/PermalinkViewQuery";
+import StreamQuery from "../queries/StreamQuery";
import * as styles from "./App.css";
export interface AppProps {
- asset: {} | null;
+ showPermalinkView: boolean;
}
const App: StatelessComponent = props => {
- if (props.asset) {
- return (
-
-
-
- );
- }
- return Asset not found
;
+ const view = props.showPermalinkView ? (
+
+ ) : (
+
+ );
+ return (
+
+ {view}
+
+ );
};
export default App;
diff --git a/src/core/client/stream/components/Comment/Comment.css b/src/core/client/stream/components/Comment/Comment.css
new file mode 100644
index 000000000..c4bf8c532
--- /dev/null
+++ b/src/core/client/stream/components/Comment/Comment.css
@@ -0,0 +1,3 @@
+.footer {
+ margin-top: var(--spacing-unit);
+}
diff --git a/src/core/client/stream/components/Comment/Comment.spec.tsx b/src/core/client/stream/components/Comment/Comment.spec.tsx
index cae068012..8b409adeb 100644
--- a/src/core/client/stream/components/Comment/Comment.spec.tsx
+++ b/src/core/client/stream/components/Comment/Comment.spec.tsx
@@ -7,6 +7,7 @@ import Comment from "./Comment";
it("renders username and body", () => {
const props: PropTypesOf = {
+ id: "comment-id",
author: {
username: "Marvin",
},
diff --git a/src/core/client/stream/components/Comment/Comment.tsx b/src/core/client/stream/components/Comment/Comment.tsx
index 03a947a5a..932cd7155 100644
--- a/src/core/client/stream/components/Comment/Comment.tsx
+++ b/src/core/client/stream/components/Comment/Comment.tsx
@@ -1,13 +1,16 @@
import React from "react";
import { StatelessComponent } from "react";
-
import { Typography } from "talk-ui/components";
+import * as styles from "./Comment.css";
+import PermalinkButtonContainer from "../../containers/PermalinkButtonContainer";
import Timestamp from "./Timestamp";
import TopBar from "./TopBar";
import Username from "./Username";
export interface CommentProps {
+ id: string;
+ className?: string;
author: {
username: string | null;
} | null;
@@ -24,6 +27,9 @@ const Comment: StatelessComponent = props => {
{props.createdAt}
{props.body}
+
);
};
diff --git a/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap b/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap
index 2d047c200..c86fc9284 100644
--- a/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap
+++ b/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap
@@ -15,5 +15,12 @@ exports[`renders username and body 1`] = `
Woof
+
+
+
`;
diff --git a/src/core/client/stream/components/Logo.tsx b/src/core/client/stream/components/Logo.tsx
deleted file mode 100644
index 2fe4dcac0..000000000
--- a/src/core/client/stream/components/Logo.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Localized } from "fluent-react/compat";
-import * as React from "react";
-import { StatelessComponent } from "react";
-
-import { Typography } from "talk-ui/components";
-
-export interface LogoProps {
- className?: string;
- gutterBottom?: boolean;
-}
-
-const Logo: StatelessComponent = props => {
- return (
-
-
- Talk NEO
-
-
- );
-};
-
-export default Logo;
diff --git a/src/core/client/stream/components/PermalinkButton/PermalinkButton.css b/src/core/client/stream/components/PermalinkButton/PermalinkButton.css
new file mode 100644
index 000000000..b303f61b6
--- /dev/null
+++ b/src/core/client/stream/components/PermalinkButton/PermalinkButton.css
@@ -0,0 +1,4 @@
+.popover {
+ width: 350px;
+ max-width: 80%;
+}
diff --git a/src/core/client/stream/components/PermalinkButton/PermalinkButton.tsx b/src/core/client/stream/components/PermalinkButton/PermalinkButton.tsx
new file mode 100644
index 000000000..493625e2b
--- /dev/null
+++ b/src/core/client/stream/components/PermalinkButton/PermalinkButton.tsx
@@ -0,0 +1,73 @@
+import { Localized } from "fluent-react/compat";
+import React from "react";
+import { oncePerFrame } from "talk-common/utils";
+import {
+ Button,
+ ButtonIcon,
+ ClickOutside,
+ MatchMedia,
+ Popover,
+} from "talk-ui/components";
+
+import * as styles from "./PermalinkButton.css";
+import PermalinkPopover from "./PermalinkPopover";
+
+interface PermalinkProps {
+ commentID: string;
+ assetURL: string | null;
+}
+
+class Permalink extends React.Component {
+ // Helper that prevents calling toggleVisibility more then once per frame.
+ // In essence this means we'll process an event only once.
+ // This might happen, when clicking on the button which will
+ // cause its onClick to happen as well as onClickOutside.
+ private toggleVisibilityOncePerFrame = oncePerFrame(
+ (toggleVisibility: () => void) => toggleVisibility()
+ );
+
+ public render() {
+ const { commentID, assetURL } = this.props;
+ const popoverID = "permalink-popover";
+ return (
+ (
+
+ this.toggleVisibilityOncePerFrame(toggleVisibility)
+ }
+ >
+
+
+ )}
+ >
+ {({ toggleVisibility, forwardRef, visible }) => (
+
+ )}
+
+ );
+ }
+}
+
+export default Permalink;
diff --git a/src/core/client/stream/components/PermalinkButton/PermalinkPopover.css b/src/core/client/stream/components/PermalinkButton/PermalinkPopover.css
new file mode 100644
index 000000000..c2afa31b2
--- /dev/null
+++ b/src/core/client/stream/components/PermalinkButton/PermalinkPopover.css
@@ -0,0 +1,7 @@
+.root {
+ width: 100%;
+}
+
+.textField {
+ flex-grow: 1;
+}
diff --git a/src/core/client/stream/components/PermalinkButton/PermalinkPopover.tsx b/src/core/client/stream/components/PermalinkButton/PermalinkPopover.tsx
new file mode 100644
index 000000000..218bd78f6
--- /dev/null
+++ b/src/core/client/stream/components/PermalinkButton/PermalinkPopover.tsx
@@ -0,0 +1,60 @@
+import { Localized } from "fluent-react/compat";
+import React from "react";
+import CopyToClipboard from "react-copy-to-clipboard";
+
+import { Button, Flex, TextField } from "talk-ui/components";
+
+import * as styles from "./PermalinkPopover.css";
+
+interface InnerProps {
+ permalinkURL: string;
+ toggleVisibility: () => void;
+}
+
+interface State {
+ copied: boolean;
+}
+
+class PermalinkPopover extends React.Component {
+ public state: State = {
+ copied: false,
+ };
+
+ private onCopy = async () => {
+ await this.toggleCopied();
+ setTimeout(() => {
+ this.toggleCopied();
+ }, 800);
+ };
+
+ private toggleCopied = () => {
+ this.setState((state: State) => ({
+ copied: !state.copied,
+ }));
+ };
+
+ public render() {
+ const { permalinkURL } = this.props;
+ const { copied } = this.state;
+ return (
+
+
+
+
+
+
+ );
+ }
+}
+
+export default PermalinkPopover;
diff --git a/src/core/client/stream/components/PermalinkButton/index.ts b/src/core/client/stream/components/PermalinkButton/index.ts
new file mode 100644
index 000000000..de6d61c1c
--- /dev/null
+++ b/src/core/client/stream/components/PermalinkButton/index.ts
@@ -0,0 +1 @@
+export { default } from "./PermalinkButton";
diff --git a/src/core/client/stream/components/PermalinkView.css b/src/core/client/stream/components/PermalinkView.css
new file mode 100644
index 000000000..a71a33af8
--- /dev/null
+++ b/src/core/client/stream/components/PermalinkView.css
@@ -0,0 +1,7 @@
+.root {
+ width: 100%;
+}
+
+.button {
+ margin-bottom: calc(2 * var(--spacing-unit));
+}
diff --git a/src/core/client/stream/components/PermalinkView.tsx b/src/core/client/stream/components/PermalinkView.tsx
new file mode 100644
index 000000000..b7ea70370
--- /dev/null
+++ b/src/core/client/stream/components/PermalinkView.tsx
@@ -0,0 +1,43 @@
+import React, { StatelessComponent } from "react";
+
+import { Button, Flex, Typography } from "talk-ui/components";
+
+import CommentContainer from "../containers/CommentContainer";
+import * as styles from "./PermalinkView.css";
+
+export interface PermalinkViewProps {
+ comment: {} | null;
+ assetURL: string | null;
+ onShowAllComments: () => void;
+}
+
+const PermalinkView: StatelessComponent = ({
+ assetURL,
+ comment,
+ onShowAllComments,
+}) => {
+ return (
+
+ {assetURL && (
+
+ )}
+ {!comment && Comment not found}
+ {comment && (
+
+
+
+ )}
+
+ );
+};
+
+export default PermalinkView;
diff --git a/src/core/client/stream/components/Stream.css b/src/core/client/stream/components/Stream.css
index 13c90d031..88de80d08 100644
--- a/src/core/client/stream/components/Stream.css
+++ b/src/core/client/stream/components/Stream.css
@@ -1,4 +1,3 @@
.root {
width: 100%;
- max-width: 400px;
}
diff --git a/src/core/client/stream/components/Stream.tsx b/src/core/client/stream/components/Stream.tsx
index 9dbb21c5b..ef1ed5bda 100644
--- a/src/core/client/stream/components/Stream.tsx
+++ b/src/core/client/stream/components/Stream.tsx
@@ -7,22 +7,20 @@ import { Button, Flex } from "talk-ui/components";
import CommentContainer from "../containers/CommentContainer";
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
import ReplyListContainer from "../containers/ReplyListContainer";
-import Logo from "./Logo";
import * as styles from "./Stream.css";
export interface StreamProps {
assetID: string;
- isClosed: boolean;
+ isClosed?: boolean;
comments: ReadonlyArray<{ id: string }>;
- onLoadMore: () => void;
- hasMore: boolean;
- disableLoadMore: boolean;
+ onLoadMore?: () => void;
+ hasMore?: boolean;
+ disableLoadMore?: boolean;
}
const Stream: StatelessComponent = props => {
return (
-
-
+
`;
-exports[`renders correctly when asset is null 1`] = `
-
- Asset not found
-
+exports[`renders stream 1`] = `
+
+
+
`;
diff --git a/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap b/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap
index 95b463311..5da869b02 100644
--- a/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap
+++ b/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap
@@ -4,9 +4,6 @@ exports[`renders correctly 1`] = `
-
@@ -65,9 +62,6 @@ exports[`when there is more disables load more button 1`] = `
-
@@ -140,9 +134,6 @@ exports[`when there is more renders a load more button 1`] = `
-
diff --git a/src/core/client/stream/containers/AppContainer.spec.tsx b/src/core/client/stream/containers/AppContainer.spec.tsx
deleted file mode 100644
index 17a0a676d..000000000
--- a/src/core/client/stream/containers/AppContainer.spec.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { shallow } from "enzyme";
-import React from "react";
-
-import { PropTypesOf } from "talk-framework/types";
-
-import { AppContainer } from "./AppContainer";
-
-it("renders correctly", () => {
- const props: PropTypesOf
= {
- data: {
- asset: {},
- },
- };
- const wrapper = shallow();
- expect(wrapper).toMatchSnapshot();
-});
diff --git a/src/core/client/stream/containers/AppContainer.tsx b/src/core/client/stream/containers/AppContainer.tsx
index 90eb1e1c6..7fbc7f6d8 100644
--- a/src/core/client/stream/containers/AppContainer.tsx
+++ b/src/core/client/stream/containers/AppContainer.tsx
@@ -1,30 +1,27 @@
-import React, { StatelessComponent } from "react";
-import { graphql } from "react-relay";
+import * as React from "react";
+import { StatelessComponent } from "react";
-import { withFragmentContainer } from "talk-framework/lib/relay";
-import { PropTypesOf } from "talk-framework/types";
-import { AppContainer as Data } from "talk-stream/__generated__/AppContainer.graphql";
+import { graphql, withLocalStateContainer } from "talk-framework/lib/relay";
+import { AppContainerLocal as Local } from "talk-stream/__generated__/AppContainerLocal.graphql";
import App from "../components/App";
interface InnerProps {
- data: Data;
+ local: Local;
}
-export const AppContainer: StatelessComponent = props => {
- return ;
+const AppContainer: StatelessComponent = ({
+ local: { commentID },
+}) => {
+ return ;
};
-const enhanced = withFragmentContainer<{ data: Data }>({
- data: graphql`
- fragment AppContainer on Query
- @argumentDefinitions(assetID: { type: "ID!" }) {
- asset(id: $assetID) {
- ...StreamContainer_asset
- }
+const enhanced = withLocalStateContainer(
+ graphql`
+ fragment AppContainerLocal on Local {
+ commentID
}
- `,
-})(AppContainer);
+ `
+)(AppContainer);
-export type AppContainerProps = PropTypesOf;
export default enhanced;
diff --git a/src/core/client/stream/containers/CommentContainer.spec.tsx b/src/core/client/stream/containers/CommentContainer.spec.tsx
index 4836991b8..44e4a0a19 100644
--- a/src/core/client/stream/containers/CommentContainer.spec.tsx
+++ b/src/core/client/stream/containers/CommentContainer.spec.tsx
@@ -8,6 +8,7 @@ import { CommentContainer } from "./CommentContainer";
it("renders username and body", () => {
const props: PropTypesOf = {
data: {
+ id: "comment-id",
author: {
username: "Marvin",
},
@@ -23,6 +24,7 @@ it("renders username and body", () => {
it("renders body only", () => {
const props: PropTypesOf = {
data: {
+ id: "comment-id",
author: {
username: null,
},
diff --git a/src/core/client/stream/containers/CommentContainer.tsx b/src/core/client/stream/containers/CommentContainer.tsx
index 0196f6b2e..0316112d1 100644
--- a/src/core/client/stream/containers/CommentContainer.tsx
+++ b/src/core/client/stream/containers/CommentContainer.tsx
@@ -2,16 +2,19 @@ import React, { StatelessComponent } from "react";
import { graphql } from "react-relay";
import withFragmentContainer from "talk-framework/lib/relay/withFragmentContainer";
-import { Omit, PropTypesOf } from "talk-framework/types";
+import { PropTypesOf } from "talk-framework/types";
import { CommentContainer as Data } from "talk-stream/__generated__/CommentContainer.graphql";
-import Comment, { CommentProps } from "../components/Comment";
+import Comment from "../components/Comment";
-type InnerProps = { data: Data } & Omit;
+interface InnerProps {
+ data: Data;
+}
// tslint:disable-next-line:no-unused-expression
graphql`
fragment CommentContainer_comment on Comment {
+ id
author {
username
}
diff --git a/src/core/client/stream/containers/PermalinkButtonContainer.tsx b/src/core/client/stream/containers/PermalinkButtonContainer.tsx
new file mode 100644
index 000000000..fd1509117
--- /dev/null
+++ b/src/core/client/stream/containers/PermalinkButtonContainer.tsx
@@ -0,0 +1,30 @@
+import React, { StatelessComponent } from "react";
+import { graphql } from "react-relay";
+import { withLocalStateContainer } from "talk-framework/lib/relay";
+import { PermalinkButtonContainerLocal as Local } from "talk-stream/__generated__/PermalinkButtonContainerLocal.graphql";
+
+import PermalinkButton from "../components/PermalinkButton";
+
+interface InnerProps {
+ local: Local;
+ commentID: string;
+}
+
+export const PermalinkContainer: StatelessComponent = ({
+ local,
+ commentID,
+}) => {
+ return local.assetURL ? (
+
+ ) : null;
+};
+
+const enhanced = withLocalStateContainer(
+ graphql`
+ fragment PermalinkButtonContainerLocal on Local {
+ assetURL
+ }
+ `
+)(PermalinkContainer);
+
+export default enhanced;
diff --git a/src/core/client/stream/containers/PermalinkViewContainer.tsx b/src/core/client/stream/containers/PermalinkViewContainer.tsx
new file mode 100644
index 000000000..65fede9d8
--- /dev/null
+++ b/src/core/client/stream/containers/PermalinkViewContainer.tsx
@@ -0,0 +1,54 @@
+import React from "react";
+import { graphql } from "react-relay";
+import { withFragmentContainer } from "talk-framework/lib/relay";
+import { PermalinkViewContainer_asset as AssetData } from "talk-stream/__generated__/PermalinkViewContainer_asset.graphql";
+import { PermalinkViewContainer_comment as CommentData } from "talk-stream/__generated__/PermalinkViewContainer_comment.graphql";
+import {
+ SetCommentIDMutation,
+ withSetCommentIDMutation,
+} from "talk-stream/mutations";
+import PermalinkView from "../components/PermalinkView";
+
+interface PermalinkViewContainerProps {
+ comment: CommentData | null;
+ asset: AssetData | null;
+ setCommentID: SetCommentIDMutation;
+}
+
+class PermalinkViewContainer extends React.Component<
+ PermalinkViewContainerProps
+> {
+ private showAllComments = () => {
+ this.props.setCommentID({ id: null });
+ };
+ public render() {
+ const { comment, asset } = this.props;
+ return (
+
+ );
+ }
+}
+
+const enhanced = withSetCommentIDMutation(
+ withFragmentContainer<{
+ comment: CommentData | null;
+ asset: AssetData | null;
+ }>({
+ comment: graphql`
+ fragment PermalinkViewContainer_comment on Comment {
+ ...CommentContainer
+ }
+ `,
+ asset: graphql`
+ fragment PermalinkViewContainer_asset on Asset {
+ url
+ }
+ `,
+ })(PermalinkViewContainer)
+);
+
+export default enhanced;
diff --git a/src/core/client/stream/containers/__snapshots__/AppContainer.spec.tsx.snap b/src/core/client/stream/containers/__snapshots__/AppContainer.spec.tsx.snap
deleted file mode 100644
index f9a5c5316..000000000
--- a/src/core/client/stream/containers/__snapshots__/AppContainer.spec.tsx.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-
-`;
diff --git a/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap b/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap
index f6ec9754b..fdf0b47f0 100644
--- a/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap
+++ b/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap
@@ -9,6 +9,7 @@ exports[`renders body only 1`] = `
}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
+ id="comment-id"
/>
`;
@@ -21,5 +22,6 @@ exports[`renders username and body 1`] = `
}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
+ id="comment-id"
/>
`;
diff --git a/src/core/client/stream/index.tsx b/src/core/client/stream/index.tsx
index c6f9059e0..943d920dd 100644
--- a/src/core/client/stream/index.tsx
+++ b/src/core/client/stream/index.tsx
@@ -9,9 +9,9 @@ import {
TalkContextProvider,
} from "talk-framework/lib/bootstrap";
+import AppContainer from "./containers/AppContainer";
import { initLocalState } from "./local";
import localesData from "./locales";
-import AppQuery from "./queries/AppQuery";
// This is called when the context is first initialized.
async function init({ relayEnvironment }: TalkContext) {
@@ -29,7 +29,7 @@ async function main() {
const Index: StatelessComponent = () => (
-
+
);
diff --git a/src/core/client/stream/local/initLocalState.ts b/src/core/client/stream/local/initLocalState.ts
index d65ea6331..434c60e32 100644
--- a/src/core/client/stream/local/initLocalState.ts
+++ b/src/core/client/stream/local/initLocalState.ts
@@ -21,10 +21,23 @@ export default async function initLocalState(environment: Environment) {
// Parse query params
const query = qs.parse(location.search);
+
if (query.assetID) {
localRecord.setValue(query.assetID, "assetID");
}
+ // Saving location host for permalink until we get the asset url - the url now points to the tenant
+ if (location && query.assetID) {
+ localRecord.setValue(
+ `${location.origin}/?assetID=${query.assetID}`,
+ "assetURL"
+ );
+ }
+
+ if (query.commentID) {
+ localRecord.setValue(query.commentID, "commentID");
+ }
+
// Create network Record
const networkRecord = createAndRetain(
environment,
diff --git a/src/core/client/stream/local/local.graphql b/src/core/client/stream/local/local.graphql
index 3359458d0..9a2991607 100644
--- a/src/core/client/stream/local/local.graphql
+++ b/src/core/client/stream/local/local.graphql
@@ -6,6 +6,8 @@ type Network {
type Local {
network: Network!
assetID: String
+ commentID: String
+ assetURL: String
}
extend type Query {
diff --git a/src/core/client/stream/mutations/SetCommentIDMutation.spec.ts b/src/core/client/stream/mutations/SetCommentIDMutation.spec.ts
new file mode 100644
index 000000000..8f296c79d
--- /dev/null
+++ b/src/core/client/stream/mutations/SetCommentIDMutation.spec.ts
@@ -0,0 +1,54 @@
+import { Environment, RecordSource } from "relay-runtime";
+import sinon from "sinon";
+
+import { timeout } from "talk-common/utils";
+import { LOCAL_ID } from "talk-framework/lib/relay";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+
+import { commit } from "./SetCommentIDMutation";
+
+let environment: Environment;
+const source: RecordSource = new RecordSource();
+
+beforeAll(() => {
+ environment = createRelayEnvironment({
+ source,
+ });
+});
+
+it("Sets comment id", () => {
+ const id = "comment1-id";
+ commit(environment, { id }, {} as any);
+ expect(source.get(LOCAL_ID)!.commentID).toEqual(id);
+});
+
+it("Should call setCommentID in pym", async () => {
+ const id = "comment2-id";
+ const context = {
+ pym: {
+ sendMessage: sinon
+ .mock()
+ .once()
+ .withArgs("setCommentID", id),
+ },
+ };
+ commit(environment, { id }, context as any);
+ await timeout();
+ expect(source.get(LOCAL_ID)!.commentID).toEqual(id);
+ context.pym.sendMessage.verify();
+});
+
+it("Should call setCommentID in pym with empty id", async () => {
+ const context = {
+ pym: {
+ sendMessage: sinon
+ .mock()
+ .once()
+ .withArgs("setCommentID", ""),
+ },
+ };
+ commit(environment, { id: null }, context as any);
+ await timeout();
+ expect(source.get(LOCAL_ID)!.commentID).toEqual(null);
+ context.pym.sendMessage.verify();
+});
diff --git a/src/core/client/stream/mutations/SetCommentIDMutation.ts b/src/core/client/stream/mutations/SetCommentIDMutation.ts
new file mode 100644
index 000000000..4bacb1181
--- /dev/null
+++ b/src/core/client/stream/mutations/SetCommentIDMutation.ts
@@ -0,0 +1,31 @@
+import { commitLocalUpdate, Environment } from "relay-runtime";
+
+import { TalkContext } from "talk-framework/lib/bootstrap";
+import { createMutationContainer } from "talk-framework/lib/relay";
+import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer";
+
+export interface SetCommentIDInput {
+ id: string | null;
+}
+
+export type SetCommentIDMutation = (input: SetCommentIDInput) => Promise;
+
+export async function commit(
+ environment: Environment,
+ input: SetCommentIDInput,
+ { pym }: TalkContext
+) {
+ return commitLocalUpdate(environment, store => {
+ const record = store.get(LOCAL_ID)!;
+ record.setValue(input.id, "commentID");
+ if (pym) {
+ // This sets the comment id on the parent url.
+ pym.sendMessage("setCommentID", input.id || "");
+ }
+ });
+}
+
+export const withSetCommentIDMutation = createMutationContainer(
+ "setCommentID",
+ commit
+);
diff --git a/src/core/client/stream/mutations/SetNetworkStatusMutation.spec.ts b/src/core/client/stream/mutations/SetNetworkStatusMutation.spec.ts
new file mode 100644
index 000000000..1a6196c45
--- /dev/null
+++ b/src/core/client/stream/mutations/SetNetworkStatusMutation.spec.ts
@@ -0,0 +1,26 @@
+import { Environment, RecordSource } from "relay-runtime";
+
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+
+import { NETWORK_ID, NETWORK_TYPE } from "../local";
+
+import { commit } from "./SetNetworkStatusMutation";
+
+let environment: Environment;
+const source: RecordSource = new RecordSource();
+
+beforeAll(() => {
+ environment = createRelayEnvironment({
+ source,
+ initLocalState: (localRecord, sourceProxy) => {
+ const networkRecord = sourceProxy.create(NETWORK_ID, NETWORK_TYPE);
+ networkRecord.setValue(false, "isOffline");
+ localRecord.setLinkedRecord(networkRecord, "network");
+ },
+ });
+});
+
+it("Sets comment id", () => {
+ commit(environment, { isOffline: true });
+ expect(source.get(NETWORK_ID)!.isOffline).toEqual(true);
+});
diff --git a/src/core/client/stream/mutations/SetNetworkStatusMutation.ts b/src/core/client/stream/mutations/SetNetworkStatusMutation.ts
index e5c5d0145..5f2fa6486 100644
--- a/src/core/client/stream/mutations/SetNetworkStatusMutation.ts
+++ b/src/core/client/stream/mutations/SetNetworkStatusMutation.ts
@@ -8,9 +8,14 @@ export interface SetNetworkStatusInput {
isOffline: boolean;
}
-export type SetNetworkStatusMutation = (input: SetNetworkStatusInput) => void;
+export type SetNetworkStatusMutation = (
+ input: SetNetworkStatusInput
+) => Promise;
-async function commit(environment: Environment, input: SetNetworkStatusInput) {
+export async function commit(
+ environment: Environment,
+ input: SetNetworkStatusInput
+) {
return commitLocalUpdate(environment, store => {
const record = store.get(NETWORK_ID)!;
record.setValue(input.isOffline, "isOffline");
diff --git a/src/core/client/stream/mutations/index.ts b/src/core/client/stream/mutations/index.ts
index 83ea079c0..1b71335e7 100644
--- a/src/core/client/stream/mutations/index.ts
+++ b/src/core/client/stream/mutations/index.ts
@@ -1,2 +1,15 @@
-export * from "./CreateCommentMutation";
-export * from "./SetNetworkStatusMutation";
+export {
+ withCreateCommentMutation,
+ CreateCommentMutation,
+ CreateCommentInput,
+} from "./CreateCommentMutation";
+export {
+ withSetNetworkStatusMutation,
+ SetNetworkStatusMutation,
+ SetNetworkStatusInput,
+} from "./SetNetworkStatusMutation";
+export {
+ withSetCommentIDMutation,
+ SetCommentIDMutation,
+ SetCommentIDInput,
+} from "./SetCommentIDMutation";
diff --git a/src/core/client/stream/queries/AppQuery.tsx b/src/core/client/stream/queries/AppQuery.tsx
deleted file mode 100644
index 3fe95b6d9..000000000
--- a/src/core/client/stream/queries/AppQuery.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import * as React from "react";
-import { StatelessComponent } from "react";
-import { ReadyState } from "react-relay";
-
-import {
- graphql,
- QueryRenderer,
- withLocalStateContainer,
-} from "talk-framework/lib/relay";
-import {
- AppQueryResponse,
- AppQueryVariables,
-} from "talk-stream/__generated__/AppQuery.graphql";
-import { AppQueryLocal as Local } from "talk-stream/__generated__/AppQueryLocal.graphql";
-
-import AppContainer from "../containers/AppContainer";
-
-export const render = ({ error, props }: ReadyState) => {
- if (error) {
- return {error.message}
;
- }
- if (props) {
- return ;
- }
- return Loading
;
-};
-
-interface InnerProps {
- local: Local;
-}
-
-const AppQuery: StatelessComponent = props => {
- return (
-
- query={graphql`
- query AppQuery($assetID: ID!) {
- ...AppContainer @arguments(assetID: $assetID)
- }
- `}
- variables={{
- assetID: props.local.assetID,
- }}
- render={render}
- />
- );
-};
-
-const enhanced = withLocalStateContainer(
- graphql`
- fragment AppQueryLocal on Local {
- assetID
- }
- `
-)(AppQuery);
-
-export default enhanced;
diff --git a/src/core/client/stream/queries/PermalinkViewQuery.spec.tsx b/src/core/client/stream/queries/PermalinkViewQuery.spec.tsx
new file mode 100644
index 000000000..01b91218a
--- /dev/null
+++ b/src/core/client/stream/queries/PermalinkViewQuery.spec.tsx
@@ -0,0 +1,34 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import { render } from "./PermalinkViewQuery";
+
+it("renders permalink view container", () => {
+ const data = {
+ props: {
+ asset: {},
+ comment: {},
+ } as any,
+ error: null,
+ };
+ const wrapper = shallow(React.createElement(() => render(data)));
+ expect(wrapper).toMatchSnapshot();
+});
+
+it("renders loading", () => {
+ const data = {
+ props: null,
+ error: null,
+ };
+ const wrapper = shallow(React.createElement(() => render(data)));
+ expect(wrapper).toMatchSnapshot();
+});
+
+it("renders error", () => {
+ const data = {
+ props: null,
+ error: new Error("error"),
+ };
+ const wrapper = shallow(React.createElement(() => render(data)));
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/stream/queries/PermalinkViewQuery.tsx b/src/core/client/stream/queries/PermalinkViewQuery.tsx
new file mode 100644
index 000000000..b37cdaaf5
--- /dev/null
+++ b/src/core/client/stream/queries/PermalinkViewQuery.tsx
@@ -0,0 +1,68 @@
+import * as React from "react";
+import { StatelessComponent } from "react";
+import { ReadyState } from "react-relay";
+
+import {
+ graphql,
+ QueryRenderer,
+ withLocalStateContainer,
+} from "talk-framework/lib/relay";
+import {
+ PermalinkViewQueryResponse,
+ PermalinkViewQueryVariables,
+} from "talk-stream/__generated__/PermalinkViewQuery.graphql";
+import { PermalinkViewQueryLocal as Local } from "talk-stream/__generated__/PermalinkViewQueryLocal.graphql";
+
+import PermalinkViewContainer from "../containers/PermalinkViewContainer";
+
+interface InnerProps {
+ local: Local;
+}
+
+export const render = ({
+ error,
+ props,
+}: ReadyState) => {
+ if (error) {
+ return {error.message}
;
+ }
+ if (props) {
+ return (
+
+ );
+ }
+ return Loading
;
+};
+
+const PermalinkViewQuery: StatelessComponent = ({
+ local: { commentID, assetID },
+}) => (
+
+ query={graphql`
+ query PermalinkViewQuery($assetID: ID!, $commentID: ID!) {
+ asset(id: $assetID) {
+ ...PermalinkViewContainer_asset
+ }
+ comment(id: $commentID) {
+ ...PermalinkViewContainer_comment
+ }
+ }
+ `}
+ variables={{
+ assetID,
+ commentID,
+ }}
+ render={render}
+ />
+);
+
+const enhanced = withLocalStateContainer(
+ graphql`
+ fragment PermalinkViewQueryLocal on Local {
+ assetID
+ commentID
+ }
+ `
+)(PermalinkViewQuery);
+
+export default enhanced;
diff --git a/src/core/client/stream/queries/AppQuery.spec.tsx b/src/core/client/stream/queries/StreamQuery.spec.tsx
similarity index 83%
rename from src/core/client/stream/queries/AppQuery.spec.tsx
rename to src/core/client/stream/queries/StreamQuery.spec.tsx
index a301a76b7..91a1a768c 100644
--- a/src/core/client/stream/queries/AppQuery.spec.tsx
+++ b/src/core/client/stream/queries/StreamQuery.spec.tsx
@@ -1,11 +1,13 @@
import { shallow } from "enzyme";
import React from "react";
-import { render } from "./AppQuery";
+import { render } from "./StreamQuery";
-it("renders app", () => {
+it("renders stream container", () => {
const data = {
- props: {} as any,
+ props: {
+ asset: {},
+ } as any,
error: null,
};
const wrapper = shallow(React.createElement(() => render(data)));
diff --git a/src/core/client/stream/queries/StreamQuery.tsx b/src/core/client/stream/queries/StreamQuery.tsx
new file mode 100644
index 000000000..40d57387e
--- /dev/null
+++ b/src/core/client/stream/queries/StreamQuery.tsx
@@ -0,0 +1,58 @@
+import * as React from "react";
+import { StatelessComponent } from "react";
+import { ReadyState } from "react-relay";
+
+import {
+ graphql,
+ QueryRenderer,
+ withLocalStateContainer,
+} from "talk-framework/lib/relay";
+import {
+ StreamQueryResponse,
+ StreamQueryVariables,
+} from "talk-stream/__generated__/StreamQuery.graphql";
+import { StreamQueryLocal as Local } from "talk-stream/__generated__/StreamQueryLocal.graphql";
+
+import StreamContainer from "../containers/StreamContainer";
+
+interface InnerProps {
+ local: Local;
+}
+
+export const render = ({ error, props }: ReadyState) => {
+ if (error) {
+ return {error.message}
;
+ }
+ if (props) {
+ return ;
+ }
+ return Loading
;
+};
+
+const StreamQuery: StatelessComponent = ({
+ local: { assetID },
+}) => (
+
+ query={graphql`
+ query StreamQuery($assetID: ID!) {
+ asset(id: $assetID) {
+ ...StreamContainer_asset
+ }
+ }
+ `}
+ variables={{
+ assetID,
+ }}
+ render={render}
+ />
+);
+
+const enhanced = withLocalStateContainer(
+ graphql`
+ fragment StreamQueryLocal on Local {
+ assetID
+ }
+ `
+)(StreamQuery);
+
+export default enhanced;
diff --git a/src/core/client/stream/queries/__snapshots__/PermalinkViewQuery.spec.tsx.snap b/src/core/client/stream/queries/__snapshots__/PermalinkViewQuery.spec.tsx.snap
new file mode 100644
index 000000000..85a702a9a
--- /dev/null
+++ b/src/core/client/stream/queries/__snapshots__/PermalinkViewQuery.spec.tsx.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders error 1`] = `
+
+ error
+
+`;
+
+exports[`renders loading 1`] = `
+
+ Loading
+
+`;
+
+exports[`renders permalink view container 1`] = `
+
+`;
diff --git a/src/core/client/stream/queries/__snapshots__/AppQuery.spec.tsx.snap b/src/core/client/stream/queries/__snapshots__/StreamQuery.spec.tsx.snap
similarity index 65%
rename from src/core/client/stream/queries/__snapshots__/AppQuery.spec.tsx.snap
rename to src/core/client/stream/queries/__snapshots__/StreamQuery.spec.tsx.snap
index dbd2ad45a..317eb3f4a 100644
--- a/src/core/client/stream/queries/__snapshots__/AppQuery.spec.tsx.snap
+++ b/src/core/client/stream/queries/__snapshots__/StreamQuery.spec.tsx.snap
@@ -1,11 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders app 1`] = `
-
-`;
-
exports[`renders error 1`] = `
error
@@ -17,3 +11,9 @@ exports[`renders loading 1`] = `
Loading
`;
+
+exports[`renders stream container 1`] = `
+
+`;
diff --git a/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap
index 688e88d38..619f3269d 100644
--- a/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap
+++ b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap
@@ -7,11 +7,6 @@ exports[`loads more comments 1`] = `
@@ -146,11 +150,6 @@ exports[`renders comment stream 1`] = `
+
diff --git a/src/core/client/stream/test/createEnvironment.ts b/src/core/client/stream/test/createEnvironment.ts
deleted file mode 100644
index 0276c0b4e..000000000
--- a/src/core/client/stream/test/createEnvironment.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { IResolvers } from "graphql-tools";
-import { createFetch } from "relay-local-schema";
-import {
- commitLocalUpdate,
- Environment,
- Network,
- RecordProxy,
- RecordSource,
- Store,
-} from "relay-runtime";
-
-import { loadSchema } from "talk-common/graphql";
-import {
- createAndRetain,
- LOCAL_ID,
- LOCAL_TYPE,
- wrapFetchWithLogger,
-} from "talk-framework/lib/relay";
-
-export interface CreateEnvironmentParams {
- /** graphql resolvers */
- resolvers: IResolvers;
- /** Allows to set initial state for Local state */
- initLocalState?: (local: RecordProxy) => void;
- /** If enabled, graphql responses will be logged to the console */
- logNetwork?: boolean;
-}
-
-/**
- * create Relay environment for integration tests.
- */
-export default function createEnvironment(params: CreateEnvironmentParams) {
- const schema = loadSchema("tenant", params.resolvers);
- const environment = new Environment({
- network: Network.create(
- wrapFetchWithLogger(createFetch({ schema }), params.logNetwork)
- ),
- store: new Store(new RecordSource()),
- });
- if (params.initLocalState) {
- commitLocalUpdate(environment, s => {
- const root = s.getRoot();
- const localRecord = createAndRetain(environment, s, LOCAL_ID, LOCAL_TYPE);
- root.setLinkedRecord(localRecord, "local");
- params.initLocalState!(localRecord);
- });
- }
- return environment;
-}
diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts
index aa14af2e2..8913b67c7 100644
--- a/src/core/client/stream/test/fixtures.ts
+++ b/src/core/client/stream/test/fixtures.ts
@@ -37,6 +37,7 @@ export const comments = [
export const assets = [
{
id: "asset-1",
+ url: "http://localhost/assets/asset-1",
isClosed: false,
comments: {
edges: [
@@ -68,6 +69,7 @@ export const commentWithReplies = {
export const assetWithReplies = {
id: "asset-with-replies",
+ url: "http://localhost/assets/asset-with-replies",
isClosed: false,
comments: {
edges: [
diff --git a/src/core/client/stream/test/loadMore.spec.tsx b/src/core/client/stream/test/loadMore.spec.tsx
index 6f63edcfc..0f2c1642d 100644
--- a/src/core/client/stream/test/loadMore.spec.tsx
+++ b/src/core/client/stream/test/loadMore.spec.tsx
@@ -5,9 +5,9 @@ import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
-import AppQuery from "talk-stream/queries/AppQuery";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+import AppContainer from "talk-stream/containers/AppContainer";
-import createEnvironment from "./createEnvironment";
import { assets, comments } from "./fixtures";
const connectionStub = sinon.stub().throws();
@@ -61,10 +61,13 @@ const resolvers = {
},
};
-const environment = createEnvironment({
- // Set this to true, to see graphql responses.
- logNetwork: false,
- resolvers,
+const environment = createRelayEnvironment({
+ network: {
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ projectName: "tenant",
+ },
initLocalState: (localRecord: RecordProxy) => {
localRecord.setValue(assetStub.id, "assetID");
},
@@ -77,7 +80,7 @@ const context: TalkContext = {
const testRenderer = TestRenderer.create(
-
+
);
diff --git a/src/core/client/stream/test/permalinkView.spec.tsx b/src/core/client/stream/test/permalinkView.spec.tsx
new file mode 100644
index 000000000..09b0c05fe
--- /dev/null
+++ b/src/core/client/stream/test/permalinkView.spec.tsx
@@ -0,0 +1,85 @@
+import React from "react";
+import TestRenderer from "react-test-renderer";
+import { RecordProxy } from "relay-runtime";
+import sinon from "sinon";
+
+import { timeout } from "talk-common/utils";
+import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+import AppContainer from "talk-stream/containers/AppContainer";
+
+import { assets, comments } from "./fixtures";
+
+const commentStub = {
+ ...comments[0],
+};
+
+const assetStub = {
+ ...assets[0],
+ comments: {
+ pageInfo: {
+ hasNextPage: false,
+ },
+ edges: [
+ {
+ node: commentStub,
+ cursor: commentStub.createdAt,
+ },
+ ],
+ },
+};
+
+const resolvers = {
+ Query: {
+ comment: sinon
+ .stub()
+ .throws()
+ .withArgs(undefined, { id: commentStub.id })
+ .returns(commentStub),
+ asset: sinon
+ .stub()
+ .throws()
+ .withArgs(undefined, { id: assetStub.id })
+ .returns(assetStub),
+ },
+};
+
+const environment = createRelayEnvironment({
+ network: {
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ projectName: "tenant",
+ },
+ initLocalState: (localRecord: RecordProxy) => {
+ localRecord.setValue(assetStub.id, "assetID");
+ localRecord.setValue(commentStub.id, "commentID");
+ },
+});
+
+const context: TalkContext = {
+ relayEnvironment: environment,
+ localeMessages: [],
+};
+
+const testRenderer = TestRenderer.create(
+
+
+
+);
+
+it("renders permalink view", async () => {
+ // Wait for loading.
+ await timeout();
+ expect(testRenderer.toJSON()).toMatchSnapshot();
+});
+
+it("show all comments", async () => {
+ testRenderer.root
+ .findByProps({
+ id: "talk-comments-permalinkView-showAllComments",
+ })
+ .props.onClick();
+ await timeout();
+ expect(testRenderer.toJSON()).toMatchSnapshot();
+});
diff --git a/src/core/client/stream/test/permalinkViewAssetNotFound.spec.tsx b/src/core/client/stream/test/permalinkViewAssetNotFound.spec.tsx
new file mode 100644
index 000000000..4ee8ebb5c
--- /dev/null
+++ b/src/core/client/stream/test/permalinkViewAssetNotFound.spec.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import TestRenderer from "react-test-renderer";
+import { RecordProxy } from "relay-runtime";
+
+import { timeout } from "talk-common/utils";
+import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+import AppContainer from "talk-stream/containers/AppContainer";
+
+const resolvers = {
+ Query: {
+ comment: () => null,
+ asset: () => null,
+ },
+};
+
+const environment = createRelayEnvironment({
+ network: {
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ projectName: "tenant",
+ },
+ initLocalState: (localRecord: RecordProxy) => {
+ localRecord.setValue("unknown-asset-id", "assetID");
+ localRecord.setValue("unknown-comment-id", "commentID");
+ },
+});
+
+const context: TalkContext = {
+ relayEnvironment: environment,
+ localeMessages: [],
+};
+
+const testRenderer = TestRenderer.create(
+
+
+
+);
+
+it("renders permalink view with unknown asset", async () => {
+ // Wait for loading.
+ await timeout();
+ expect(testRenderer.toJSON()).toMatchSnapshot();
+});
diff --git a/src/core/client/stream/test/permalinkViewCommentNotFound.spec.tsx b/src/core/client/stream/test/permalinkViewCommentNotFound.spec.tsx
new file mode 100644
index 000000000..473e6d510
--- /dev/null
+++ b/src/core/client/stream/test/permalinkViewCommentNotFound.spec.tsx
@@ -0,0 +1,81 @@
+import React from "react";
+import TestRenderer from "react-test-renderer";
+import { RecordProxy } from "relay-runtime";
+import sinon from "sinon";
+
+import { timeout } from "talk-common/utils";
+import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+import AppContainer from "talk-stream/containers/AppContainer";
+
+import { assets, comments } from "./fixtures";
+
+const commentStub = {
+ ...comments[0],
+};
+
+const assetStub = {
+ ...assets[0],
+ comments: {
+ pageInfo: {
+ hasNextPage: false,
+ },
+ edges: [
+ {
+ node: commentStub,
+ cursor: commentStub.createdAt,
+ },
+ ],
+ },
+};
+
+const resolvers = {
+ Query: {
+ comment: () => null,
+ asset: sinon
+ .stub()
+ .throws()
+ .withArgs(undefined, { id: assetStub.id })
+ .returns(assetStub),
+ },
+};
+
+const environment = createRelayEnvironment({
+ network: {
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ projectName: "tenant",
+ },
+ initLocalState: (localRecord: RecordProxy) => {
+ localRecord.setValue(assetStub.id, "assetID");
+ localRecord.setValue("unknown-comment-id", "commentID");
+ },
+});
+
+const context: TalkContext = {
+ relayEnvironment: environment,
+ localeMessages: [],
+};
+
+const testRenderer = TestRenderer.create(
+
+
+
+);
+
+it("renders permalink view with unknown comment", async () => {
+ // Wait for loading.
+ await timeout();
+ expect(testRenderer.toJSON()).toMatchSnapshot();
+});
+
+it("show all comments", async () => {
+ testRenderer.root
+ .findByProps({
+ id: "talk-comments-permalinkView-showAllComments",
+ })
+ .props.onClick();
+ await timeout();
+ expect(testRenderer.toJSON()).toMatchSnapshot();
+});
diff --git a/src/core/client/stream/test/renderReplies.spec.tsx b/src/core/client/stream/test/renderReplies.spec.tsx
index 9c535520f..221dc19c0 100644
--- a/src/core/client/stream/test/renderReplies.spec.tsx
+++ b/src/core/client/stream/test/renderReplies.spec.tsx
@@ -5,9 +5,9 @@ import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
-import AppQuery from "talk-stream/queries/AppQuery";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+import AppContainer from "talk-stream/containers/AppContainer";
-import createEnvironment from "./createEnvironment";
import { assetWithReplies } from "./fixtures";
const resolvers = {
@@ -20,10 +20,13 @@ const resolvers = {
},
};
-const environment = createEnvironment({
- // Set this to true, to see graphql responses.
- logNetwork: false,
- resolvers,
+const environment = createRelayEnvironment({
+ network: {
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ projectName: "tenant",
+ },
initLocalState: (localRecord: RecordProxy) => {
localRecord.setValue(assetWithReplies.id, "assetID");
},
@@ -36,7 +39,7 @@ const context: TalkContext = {
const testRenderer = TestRenderer.create(
-
+
);
diff --git a/src/core/client/stream/test/renderStream.spec.tsx b/src/core/client/stream/test/renderStream.spec.tsx
index 998058cce..3c31c1080 100644
--- a/src/core/client/stream/test/renderStream.spec.tsx
+++ b/src/core/client/stream/test/renderStream.spec.tsx
@@ -5,9 +5,9 @@ import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
-import AppQuery from "talk-stream/queries/AppQuery";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+import AppContainer from "talk-stream/containers/AppContainer";
-import createEnvironment from "./createEnvironment";
import { assets } from "./fixtures";
const resolvers = {
@@ -20,10 +20,13 @@ const resolvers = {
},
};
-const environment = createEnvironment({
- // Set this to true, to see graphql responses.
- logNetwork: false,
- resolvers,
+const environment = createRelayEnvironment({
+ network: {
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ projectName: "tenant",
+ },
initLocalState: (localRecord: RecordProxy) => {
localRecord.setValue(assets[0].id, "assetID");
},
@@ -36,7 +39,7 @@ const context: TalkContext = {
const testRenderer = TestRenderer.create(
-
+
);
diff --git a/src/core/client/stream/test/showAllReplies.spec.tsx b/src/core/client/stream/test/showAllReplies.spec.tsx
index 3e929b5b0..71f857331 100644
--- a/src/core/client/stream/test/showAllReplies.spec.tsx
+++ b/src/core/client/stream/test/showAllReplies.spec.tsx
@@ -5,9 +5,9 @@ import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
-import AppQuery from "talk-stream/queries/AppQuery";
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+import AppContainer from "talk-stream/containers/AppContainer";
-import createEnvironment from "./createEnvironment";
import { assets, comments } from "./fixtures";
const connectionStub = sinon.stub().throws();
@@ -77,10 +77,13 @@ const resolvers = {
},
};
-const environment = createEnvironment({
- // Set this to true, to see graphql responses.
- logNetwork: false,
- resolvers,
+const environment = createRelayEnvironment({
+ network: {
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ projectName: "tenant",
+ },
initLocalState: (localRecord: RecordProxy) => {
localRecord.setValue(assetStub.id, "assetID");
},
@@ -93,7 +96,7 @@ const context: TalkContext = {
const testRenderer = TestRenderer.create(
-
+
);
diff --git a/src/core/client/tslint.json b/src/core/client/tslint.json
index 9abc9b921..a2fafdc6b 100644
--- a/src/core/client/tslint.json
+++ b/src/core/client/tslint.json
@@ -1,20 +1,15 @@
{
- "extends": [
- "../../../tslint.json",
- "tslint-react"
- ],
+ "extends": ["../../../tslint.json", "tslint-react"],
"rules": {
+ "jsx-curly-spacing": false,
"jsx-no-multiline-js": false,
- "jsx-boolean-value": [
- true,
- "never"
- ]
+ "jsx-boolean-value": [true, "never"],
+ "jsx-no-lambda": false
},
"jsRules": {
+ "jsx-curly-spacing": false,
"jsx-no-multiline-js": false,
- "jsx-boolean-value": [
- true,
- "never"
- ]
+ "jsx-boolean-value": [true, "never"],
+ "jsx-no-lambda": false
}
-}
\ No newline at end of file
+}
diff --git a/src/core/client/ui/components/AriaInfo/AiraInfo.mdx b/src/core/client/ui/components/AriaInfo/AriaInfo.mdx
similarity index 100%
rename from src/core/client/ui/components/AriaInfo/AiraInfo.mdx
rename to src/core/client/ui/components/AriaInfo/AriaInfo.mdx
diff --git a/src/core/client/ui/components/ClickOutside/ClickOutside.mdx b/src/core/client/ui/components/ClickOutside/ClickOutside.mdx
index 0d8126b5d..04bd19da7 100644
--- a/src/core/client/ui/components/ClickOutside/ClickOutside.mdx
+++ b/src/core/client/ui/components/ClickOutside/ClickOutside.mdx
@@ -19,8 +19,11 @@ clicks outside the component.
Click the blue background. It should trigger an alert. Nothing should happen if you click the button.
-
-
alert('You clicked outside!')}>
+
+ {
+ if (e.srcElement.id === "outside") {
+ alert('You clicked outside!');
+ }}}>
Push Me
diff --git a/src/core/client/ui/components/ClickOutside/ClickOutside.tsx b/src/core/client/ui/components/ClickOutside/ClickOutside.tsx
index 2d79994ad..275b5fdb0 100644
--- a/src/core/client/ui/components/ClickOutside/ClickOutside.tsx
+++ b/src/core/client/ui/components/ClickOutside/ClickOutside.tsx
@@ -10,8 +10,8 @@ export type ClickFarAwayRegister = (
callback: ClickFarAwayCallback
) => ClickFarAwayUnlistenCallback;
-interface Props {
- onClickOutside: () => void;
+export interface ClickOutsideProps {
+ onClickOutside: (e?: MouseEvent) => void;
/**
* A way to listen for clicks that are e.g. outside of the
@@ -22,7 +22,7 @@ interface Props {
children: React.ReactNode;
}
-export class ClickOutside extends React.Component {
+export class ClickOutside extends React.Component {
public domNode: Element | null = null;
private unlisten?: ClickFarAwayUnlistenCallback;
@@ -30,7 +30,7 @@ export class ClickOutside extends React.Component {
const { onClickOutside } = this.props;
if (!e || !this.domNode!.contains(e.target as HTMLInputElement)) {
// tslint:disable-next-line:no-unused-expression
- onClickOutside && onClickOutside();
+ onClickOutside && onClickOutside(e);
}
};
@@ -65,7 +65,9 @@ export class ClickOutside extends React.Component {
}
}
-const ClickOutsideWithContext: StatelessComponent = props => (
+const ClickOutsideWithContext: StatelessComponent<
+ ClickOutsideProps
+> = props => (
{({ registerClickFarAway }) => (
diff --git a/src/core/client/ui/components/ClickOutside/index.ts b/src/core/client/ui/components/ClickOutside/index.ts
index b70132125..91803ea08 100644
--- a/src/core/client/ui/components/ClickOutside/index.ts
+++ b/src/core/client/ui/components/ClickOutside/index.ts
@@ -1 +1 @@
-export { default as ClickOutside, ClickFarAwayRegister } from "./ClickOutside";
+export { default, ClickFarAwayRegister } from "./ClickOutside";
diff --git a/src/core/client/ui/components/Icon/Icon.css b/src/core/client/ui/components/Icon/Icon.css
index 37fb390d5..a34a46bfa 100644
--- a/src/core/client/ui/components/Icon/Icon.css
+++ b/src/core/client/ui/components/Icon/Icon.css
@@ -37,18 +37,18 @@
}
.sm {
+ font-size: 14px;
+ width: 14px;
+}
+.md {
font-size: 18px;
width: 18px;
}
-.md {
+.lg {
font-size: 24px;
width: 24px;
}
-.lg {
+.xl {
font-size: 36px;
width: 36px;
}
-.xl {
- font-size: 48px;
- width: 48px;
-}
diff --git a/src/core/client/ui/components/Popover/Popover.css b/src/core/client/ui/components/Popover/Popover.css
new file mode 100644
index 000000000..82879b01b
--- /dev/null
+++ b/src/core/client/ui/components/Popover/Popover.css
@@ -0,0 +1,21 @@
+.root {
+ background: var(--palette-common-white);
+ border: 1px solid var(--palette-grey-lighter);
+ box-sizing: border-box;
+ box-shadow: var(--elevation-main);
+ border-radius: var(--round-corners);
+ padding: calc(0.5 * var(--spacing-unit));
+}
+
+.top {
+ margin: calc(0.5 * var(--spacing-unit)) 0;
+}
+.left {
+ margin: 0 calc(0.5 * var(--spacing-unit));
+}
+.right {
+ margin: 0 calc(0.5 * var(--spacing-unit));
+}
+.bottom {
+ margin: calc(0.5 * var(--spacing-unit)) 0;
+}
diff --git a/src/core/client/ui/components/Popover/Popover.mdx b/src/core/client/ui/components/Popover/Popover.mdx
new file mode 100644
index 000000000..a7a67a04b
--- /dev/null
+++ b/src/core/client/ui/components/Popover/Popover.mdx
@@ -0,0 +1,49 @@
+---
+name: Popover
+menu: UI Kit
+---
+
+import { Playground } from 'docz'
+import Popover from './Popover'
+import Button from '../Button'
+import Flex from '../Flex'
+import Typography from '../Typography'
+import ButtonIcon from '../Button/ButtonIcon'
+
+# Popover
+
+`Popover` renders a popover dialog attached to another `Element`.
+
+## Basic usage
+
+ This is the body}
+ >
+ {({ toggleVisibility, forwardRef }) => (
+
+ Click me!
+
+ )}
+
+
+
+#### Example with `placement=top`
+
+ (
+
+ This is the body
+
+ close
+
+
+ )}
+ >
+ {({ toggleVisibility, forwardRef }) => (
+
+ Click me!
+
+ )}
+
+
diff --git a/src/core/client/ui/components/Popover/Popover.tsx b/src/core/client/ui/components/Popover/Popover.tsx
new file mode 100644
index 000000000..7708b86aa
--- /dev/null
+++ b/src/core/client/ui/components/Popover/Popover.tsx
@@ -0,0 +1,147 @@
+import cn from "classnames";
+import React from "react";
+import {
+ Manager,
+ Popper,
+ PopperArrowProps,
+ Reference,
+ RefHandler,
+} from "react-popper";
+import AriaInfo from "../AriaInfo";
+import * as styles from "./Popover.css";
+
+type Placement =
+ | "top-start"
+ | "top"
+ | "top-end"
+ | "right-start"
+ | "right"
+ | "right-end"
+ | "bottom-end"
+ | "bottom"
+ | "bottom-start"
+ | "left-end"
+ | "left"
+ | "left-start";
+
+interface BodyRenderProps {
+ toggleVisibility: () => void;
+ visible: boolean;
+}
+
+interface ChildrenRenderProps {
+ toggleVisibility: () => void;
+ forwardRef?: RefHandler;
+ visible: boolean;
+}
+
+interface PopoverProps {
+ body: (props: BodyRenderProps) => React.ReactNode | React.ReactElement;
+ children: (props: ChildrenRenderProps) => React.ReactNode;
+ description: string;
+ id: string;
+ onClose?: () => void;
+ className?: string;
+ placement?: Placement;
+}
+
+interface State {
+ visible: false;
+}
+
+class Popover extends React.Component {
+ public static defaultProps = {
+ placement: "top",
+ };
+ public state: State = {
+ visible: false,
+ };
+
+ public toggleVisibility = () => {
+ this.setState((state: State) => ({
+ visible: !state.visible,
+ }));
+ };
+
+ public close = () => {
+ this.setState((state: State) => ({
+ visible: false,
+ }));
+ };
+
+ public handleEsc = (e: KeyboardEvent) => {
+ if (e.key === "Escape") {
+ e.preventDefault();
+ this.close();
+ }
+ };
+
+ public componentDidMount() {
+ document.addEventListener("keydown", this.handleEsc, true);
+ }
+
+ public componentWillUnmount() {
+ document.removeEventListener("keydown", this.handleEsc, true);
+ }
+
+ public render() {
+ const {
+ id,
+ body,
+ children,
+ description,
+ className,
+ placement,
+ } = this.props;
+
+ const { visible } = this.state;
+ const popoverClassName = cn(styles.root, className, {
+ [styles.top]: placement!.startsWith("top"),
+ [styles.left]: placement!.startsWith("left"),
+ [styles.right]: placement!.startsWith("right"),
+ [styles.bottom]: placement!.startsWith("bottom"),
+ });
+
+ return (
+
+
+ {(props: PopperArrowProps) =>
+ children({
+ forwardRef: props.ref,
+ toggleVisibility: this.toggleVisibility,
+ visible: this.state.visible,
+ })
+ }
+
+
+ {(props: PopperArrowProps) => (
+
+
{description}
+ {visible && (
+
+ {typeof body === "function"
+ ? body({
+ toggleVisibility: this.toggleVisibility,
+ visible: this.state.visible,
+ })
+ : body}
+
+ )}
+
+ )}
+
+
+ );
+ }
+}
+
+export default Popover;
diff --git a/src/core/client/ui/components/Popover/index.ts b/src/core/client/ui/components/Popover/index.ts
new file mode 100644
index 000000000..04072ed30
--- /dev/null
+++ b/src/core/client/ui/components/Popover/index.ts
@@ -0,0 +1 @@
+export { default } from "./Popover";
diff --git a/src/core/client/ui/components/TextField/TextField.css b/src/core/client/ui/components/TextField/TextField.css
new file mode 100644
index 000000000..7cbfed982
--- /dev/null
+++ b/src/core/client/ui/components/TextField/TextField.css
@@ -0,0 +1,8 @@
+.root {
+ composes: textField from "talk-ui/shared/typography.css";
+ background: var(--palette-common-white);
+ border: 1px solid var(--palette-grey-lighter);
+ box-sizing: border-box;
+ border-radius: var(--round-corners);
+ padding: calc(0.5 * var(--spacing-unit));
+}
diff --git a/src/core/client/ui/components/TextField/TextField.mdx b/src/core/client/ui/components/TextField/TextField.mdx
new file mode 100644
index 000000000..82f1c474d
--- /dev/null
+++ b/src/core/client/ui/components/TextField/TextField.mdx
@@ -0,0 +1,17 @@
+---
+name: TextField
+menu: UI Kit
+---
+
+import { Playground, PropsTable } from 'docz'
+import TextField from './TextField'
+
+# TextField
+
+`TextField`
+
+## Basic usage
+
+
+
+
diff --git a/src/core/client/ui/components/TextField/TextField.tsx b/src/core/client/ui/components/TextField/TextField.tsx
new file mode 100644
index 000000000..57f26e7d1
--- /dev/null
+++ b/src/core/client/ui/components/TextField/TextField.tsx
@@ -0,0 +1,17 @@
+import cn from "classnames";
+import React, { InputHTMLAttributes, StatelessComponent } from "react";
+
+import * as styles from "./TextField.css";
+
+interface TextFieldProps extends InputHTMLAttributes {
+ classes?: typeof styles;
+}
+
+const TextField: StatelessComponent = ({
+ className,
+ ...rest
+}) => {
+ return ;
+};
+
+export default TextField;
diff --git a/src/core/client/ui/components/TextField/index.ts b/src/core/client/ui/components/TextField/index.ts
new file mode 100644
index 000000000..e38acd4ea
--- /dev/null
+++ b/src/core/client/ui/components/TextField/index.ts
@@ -0,0 +1,2 @@
+export * from "./TextField";
+export { default } from "./TextField";
diff --git a/src/core/client/ui/components/index.ts b/src/core/client/ui/components/index.ts
index 3c1a3e21a..f7366ddc1 100644
--- a/src/core/client/ui/components/index.ts
+++ b/src/core/client/ui/components/index.ts
@@ -1,9 +1,13 @@
export { default as BaseButton } from "./BaseButton";
export { default as Button } from "./Button";
+export { default as ButtonIcon } from "./Button/ButtonIcon";
export { default as Typography } from "./Typography";
+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";
export { default as TrapFocus } from "./TrapFocus";
+export { default as ClickOutside } from "./ClickOutside";
export { default as Popup } from "./Popup";
diff --git a/src/core/client/ui/shared/typography.css b/src/core/client/ui/shared/typography.css
index 9e52cfc89..518d0561f 100644
--- a/src/core/client/ui/shared/typography.css
+++ b/src/core/client/ui/shared/typography.css
@@ -126,3 +126,12 @@
line-height: calc(18em / 16);
letter-spacing: calc(0.2em / 16);
}
+
+.textField {
+ color: var(--palette-common-black);
+ font-family: "Source Sans Pro";
+ font-weight: var(--font-weight-regular);
+ font-size: 16px;
+ line-height: calc(18em / 16);
+ letter-spacing: calc(0.57em / 16);
+}
diff --git a/src/core/client/ui/theme/variables.ts b/src/core/client/ui/theme/variables.ts
index 415a5c32f..a5ae14893 100644
--- a/src/core/client/ui/theme/variables.ts
+++ b/src/core/client/ui/theme/variables.ts
@@ -4,6 +4,9 @@
*/
const variables = {
+ elevation: {
+ main: "1px 0px 4px rgba(0, 0, 0, 0.25)",
+ },
palette: {
/* Primary colors */
primary: {
diff --git a/src/core/common/utils/index.ts b/src/core/common/utils/index.ts
index e91489017..b5b0a0be6 100644
--- a/src/core/common/utils/index.ts
+++ b/src/core/common/utils/index.ts
@@ -1,2 +1,3 @@
export { default as timeout } from "./timeout";
export { default as pascalCase } from "./pascalCase";
+export { default as oncePerFrame } from "./oncePerFrame";
diff --git a/src/core/common/utils/oncePerFrame.ts b/src/core/common/utils/oncePerFrame.ts
new file mode 100644
index 000000000..a5e59bd9b
--- /dev/null
+++ b/src/core/common/utils/oncePerFrame.ts
@@ -0,0 +1,18 @@
+/**
+ * Function decorator that prevents calling `fn` more then once per frame.
+ * If called more than once, the last return value gets returned.
+ */
+const oncePerFrame = any>(fn: T) => {
+ let toggledThisFrame = false;
+ let lastResult: any = null;
+ return ((...args: any[]) => {
+ if (toggledThisFrame) {
+ return lastResult;
+ }
+ toggledThisFrame = true;
+ lastResult = fn(...args);
+ setTimeout(() => (toggledThisFrame = false), 0);
+ }) as T;
+};
+
+export default oncePerFrame;
diff --git a/src/core/server/graph/tenant/resolvers/query.ts b/src/core/server/graph/tenant/resolvers/query.ts
index 5dbd4bccc..295b6c459 100644
--- a/src/core/server/graph/tenant/resolvers/query.ts
+++ b/src/core/server/graph/tenant/resolvers/query.ts
@@ -2,6 +2,8 @@ import { GQLQueryTypeResolver } from "talk-server/graph/tenant/schema/__generate
const Query: GQLQueryTypeResolver = {
asset: (source, args, ctx) => ctx.loaders.Assets.findOrCreate(args),
+ comment: (source, { id }, ctx) =>
+ id ? ctx.loaders.Comments.comment.load(id) : null,
settings: (source, args, ctx) => ctx.tenant,
me: (source, args, ctx) => ctx.user,
};
diff --git a/src/docs/introduction.mdx b/src/docs/introduction.mdx
index 73121ef24..65a72f2c0 100644
--- a/src/docs/introduction.mdx
+++ b/src/docs/introduction.mdx
@@ -5,5 +5,11 @@ route: '/'
# Introduction
-Hello, I'm a mdx file!
+## Running the Talk V5
+
+Run this command to start Talk V5 in watch mode:
+
+```sh
+npm run watch
+```
diff --git a/src/docs/workarounds.mdx b/src/docs/workarounds.mdx
index feab40cd4..f511e64c8 100644
--- a/src/docs/workarounds.mdx
+++ b/src/docs/workarounds.mdx
@@ -78,3 +78,26 @@ const enhanced = withLocalStateContainer(
export type ContainerProps = ReturnPropTypes;
export default enhanced;
```
+
+A working chaining example looks like this:
+
+```
+const enhanced = withFragmentContainer<{ data: Data }>({
+ data: graphql`
+ fragment PermalinkViewContainerQuery on Query
+ @argumentDefinitions(commentID: { type: "ID!" }) {
+ comment(id: $commentID) {
+ ...CommentContainer
+ }
+ }
+ `,
+})(
+ withLocalStateContainer(
+ graphql`
+ fragment PermalinkViewContainerLocal on Local {
+ assetURL
+ }
+ `
+ )(PermalinkViewContainer)
+);
+```
diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl
index 439e8955a..08a397cc1 100644
--- a/src/locales/en-US/stream.ftl
+++ b/src/locales/en-US/stream.ftl
@@ -5,3 +5,7 @@
comments-postCommentForm-post = Post
comments-stream-loadMore = Load more
comments-replyList-showAll = Show all
+
+comments-permalink-share = Share
+comments-permalink-copy = Copy
+comments-permalink-copied = Copied