diff --git a/config/jest/client.config.js b/config/jest/client.config.js index d5ba3c585..975397a29 100644 --- a/config/jest/client.config.js +++ b/config/jest/client.config.js @@ -10,6 +10,8 @@ module.exports = { "/src/core/build/polyfills.js", "/src/core/client/test/setup.ts", ], + setupTestFrameworkScriptFile: + "/src/core/client/test/setupTestFramework.ts", testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"], testEnvironment: "node", testURL: "http://localhost", diff --git a/package-lock.json b/package-lock.json index 87ed024ab..efc1a3a5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1086,16 +1086,6 @@ "@babel/helper-plugin-utils": "7.0.0-beta.49" } }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.49.tgz", - "integrity": "sha1-CMxbZM9qWUKoe92bSkgY1MuhLfM=", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.49", - "@babel/helper-regex": "7.0.0-beta.49" - } - }, "@babel/plugin-transform-template-literals": { "version": "7.0.0-beta.49", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.49.tgz", @@ -1211,6 +1201,16 @@ "@babel/helper-plugin-utils": "7.0.0-beta.49", "@babel/helper-simple-access": "7.0.0-beta.49" } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.0.0-beta.49", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.49.tgz", + "integrity": "sha1-CMxbZM9qWUKoe92bSkgY1MuhLfM=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0-beta.49", + "@babel/helper-regex": "7.0.0-beta.49" + } } } }, @@ -1342,9 +1342,9 @@ } }, "@coralproject/rte": { - "version": "0.10.9", - "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.9.tgz", - "integrity": "sha512-0K+bc3JaOhjgJJ91uupev4EmSoldd/IizfjRgAQ8LbZjRc3pWprzIBhoTMChApBzz7eI9DUfw2ULg5muziV8BA==", + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.11.tgz", + "integrity": "sha512-Tq8NznCOYx84QpHhZbUldxcYrztEQnnNjDC2aW5HhzltO9nBG2Hu78MzTwliML3MmaTFbLehdIb/BKGKj4eMSw==", "dev": true, "requires": { "bowser": "^1.0.0", @@ -2162,9 +2162,13 @@ } }, "@types/recompose": { - "version": "github:coralproject/patched#3ca0deec868322739fc0d750398fd97735d27aac", - "from": "github:coralproject/patched#types/recompose", - "dev": true + "version": "0.26.5", + "resolved": "https://registry.npmjs.org/@types/recompose/-/recompose-0.26.5.tgz", + "integrity": "sha512-Il5stz/Z3pVIMl48pyggl6nnhRLQ8N8YN8hi0Anm0M5UjVh2uMSY0ah2vzwZZKxnca4NzyJArloSjsJ9fL2vWw==", + "dev": true, + "requires": { + "@types/react": "*" + } }, "@types/relateurl": { "version": "0.2.28", @@ -14225,6 +14229,12 @@ "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", "dev": true }, + "jest-mock-console": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jest-mock-console/-/jest-mock-console-0.4.0.tgz", + "integrity": "sha512-WElCbNvfqQlD7cpfHfTn1ytZ+RjKg1Ftrvr5wEjdWP7a9esXmaiZuEAPeYUSK5fd0Cra+dR1oF8HAjjKKxDQdg==", + "dev": true + }, "jest-regex-util": { "version": "23.3.0", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", @@ -18041,6 +18051,11 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "permit": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/permit/-/permit-0.2.4.tgz", + "integrity": "sha512-Mp2XTEMD3mPsZIWq3bp0claE4IxXKa4C6nhSDPZgGri8Q4CLjEjAQrP/xGKq2548a2KFENmA1V7W0Lob8kTuzw==" + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -20822,9 +20837,9 @@ "dev": true }, "react-contenteditable": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-2.1.5.tgz", - "integrity": "sha512-mjaxwybOX7Om8Cx/0mVCUpgvAyheP48JXoABMTvzWENluFTpwAhlsLfKN4xNZk3Y7DsiidA7mUZibh0sTMly2w==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-2.1.6.tgz", + "integrity": "sha512-HqqAdAP69SEhZhlrzJSujVSVMmThpUzVnbNDB7dFk+LWhvuhNj5iT0DgedoURpPJqbTDX8msSkFuU7FsAMxf3Q==", "dev": true, "requires": { "@types/prop-types": "^15.5.4", diff --git a/package.json b/package.json index 5a29bc959..9a804811f 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "passport-oauth2": "^1.4.0", "passport-strategy": "^1.0.0", "performance-now": "^2.1.0", + "permit": "^0.2.4", "subscriptions-transport-ws": "^0.9.12", "tlds": "^1.203.1", "uuid": "^3.3.2" @@ -85,7 +86,7 @@ "@babel/polyfill": "7.0.0-beta.49", "@babel/preset-env": "7.0.0-beta.49", "@babel/preset-react": "7.0.0-beta.49", - "@coralproject/rte": "^0.10.9", + "@coralproject/rte": "^0.10.11", "@types/bcryptjs": "^2.4.1", "@types/bull": "^3.3.16", "@types/bunyan": "^1.8.4", @@ -133,7 +134,7 @@ "@types/react-relay": "^1.3.9", "@types/react-responsive": "^3.0.1", "@types/react-test-renderer": "^16.0.1", - "@types/recompose": "github:coralproject/patched#types/recompose", + "@types/recompose": "^0.26.5", "@types/relay-runtime": "^1.3.6", "@types/sane": "^2.0.0", "@types/sinon": "^5.0.1", @@ -185,6 +186,7 @@ "jest": "^23.4.1", "jest-junit": "^5.1.0", "jest-localstorage-mock": "^2.2.0", + "jest-mock-console": "^0.4.0", "jsdom": "^11.11.0", "loader-utils": "^1.1.0", "material-design-icons": "^3.0.1", diff --git a/src/core/build/createWebpackConfig.ts b/src/core/build/createWebpackConfig.ts index 6085d8899..a19c33c47 100644 --- a/src/core/build/createWebpackConfig.ts +++ b/src/core/build/createWebpackConfig.ts @@ -288,6 +288,25 @@ export default function createWebpackConfig({ }, ], }, + // Makes sure node_modules are transpiled the way we need them to be. + { + test: /\.js$/, + include: /node_modules\//, + use: [ + { + loader: require.resolve("babel-loader"), + options: { + presets: [ + [ + "@babel/env", + { targets: "last 2 versions, ie 11", modules: false }, + ], + ], + cacheDirectory: true, + }, + }, + ], + }, // "postcss" loader applies autoprefixer to our CSS. // "css" loader resolves paths in CSS and adds assets as dependencies. // "style" loader turns CSS into JS modules that inject + + + +

+ Default | Article With Button +

+

Talk 5.0 – Article

+

Dismember a mouse and then regurgitate parts of it on the family room floor. Dont wait for the storm to pass, + dance in the rain stand in front of the computer screen, so stares at human while pushing stuff off a table chew + the plant meow hiss at vacuum cleaner. Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not + sorry chew the plant. Litter kitter kitty litty little kitten big roar roar feed me rub whiskers on bare skin act + innocent sleep on keyboard, so give me attention or face the wrath of my claws for demand to be let outside at + once, and expect owner to wait for me as i think about it spread kitty litter all over house so nya nya nyan. Catty + ipsum massacre a bird in the living room and then look like the cutest and most innocent animal on the planet you + have cat to be kitten me right meow. Hiss and stare at nothing then run suddenly away refuse to come home when + humans are going to bed; stay out all night then yowl like i am dying at 4am and lick plastic bags. Chase dog then + run away purrr purr littel cat, little cat purr purr and step on your keyboard while you're gaming and then turn in + a circle . Twitch tail in permanent irritation put butt in owner's face and the dog smells bad yet attempt to leap + between furniture but woefully miscalibrate and bellyflop onto the floor; what's your problem? i meant to do that + now i shall wash myself intently. Sniff all the things groom forever, stretch tongue and leave it slightly out, + blep, but bring your owner a dead bird decide to want nothing to do with my owner today for lay on arms while + you're using the keyboard meow meow, i tell my human or scratch. Sleep on my human's head then cats take over the + world bleghbleghvomit my furball really tie the room together sleep more napping, more napping all the napping is + exhausting. When in doubt, wash drink water out of the faucet, cats are fats i like to pets them they like to meow + back and cat dog hate mouse eat string barf pillow no baths hate everything yet swat at dog kitty kitty but you + call this cat food. Cough furball into food bowl then scratch owner for a new one flex claws on the human's belly + and purr like a lawnmower for has closed eyes but still sees you groom yourself 4 hours - checked, have your beauty + sleep 18 hours - checked, be fabulous for the rest of the day - checked. Freak human out make funny noise mow mow + mow mow mow mow success now attack human flex claws on the human's belly and purr like a lawnmower or meowwww. + Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not sorry paw at your fat belly so yowling + nonstop the whole night small kitty warm kitty little balls of fur or eat owner's food reward the chosen human with + a slow blink. Gate keepers of hell plan steps for world domination for more napping, more napping all the napping + is exhausting give me some of your food give me some of your food give me some of your food meh, i don't want it so + flop over. Make meme, make cute face ears back wide eyed so sit and stare. Dead stare with ears cocked furrier and + even more furrier hairball. Stand in front of the computer screen demand to have some of whatever the human is + cooking, then sniff the offering and walk away for catasstrophe, kitty scratches couch bad kitty. Wack the mini + furry mouse intrigued by the shower, and pooping rainbow while flying in a toasted bread costume in space. + Mesmerizing birds love me! shake treat bag, yet lies down where is my slave? I'm getting hungry so lick face hiss + at owner, pee a lot, and meow repeatedly scratch at fence purrrrrr eat muffins and poutine until owner comes back. + You have cat to be kitten me right meow sniff other cat's butt and hang jaw half open thereafter but run outside as + soon as door open so munch on tasty moths or munch on tasty moths, for paw at beetle and eat it before it gets + away. Sit on human. Gnaw the corn cob massacre a bird in the living room and then look like the cutest and most + innocent animal on the planet for sit on the laptop. Meow scratch leg; meow for can opener to feed me cat fur is + the new black but hide when guests come over, and Gate keepers of hell. Refuse to come home when humans are going + to bed; stay out all night then yowl like i am dying at 4am cat slap dog in face or eat a rug and furry furry hairs + everywhere oh no human coming lie on counter don't get off counter for i like fish sit on human they not getting up + ever but meow meow but cuddle no cuddle cuddle love scratch scratch.

+

I show my fluffy belly but it's a trap! if you pet it i will tear up your hand refuse to drink water except out of + someone's glass mice, so cough hairball, eat toilet paper or curl into a furry donut lick sellotape but wack the + mini furry mouse. When owners are asleep, cry for no apparent reason. Chase imaginary bugs. Stinky cat reward the + chosen human with a slow blink, or chase dog then run away. Chew on cable scratch the furniture for you are a + captive audience while sitting on the toilet, pet me for i like cats because they are fat and fluffy and spend all + night ensuring people don't sleep sleep all day. Scoot butt on the rug need to check on human, have not seen in an + hour might be dead oh look, human is alive, hiss at human, feed me, leave fur on owners clothes, so instantly break + out into full speed gallop across the house for no reason play riveting piece on synthesizer keyboard and scoot + butt on the rug yet meow meow. Attack dog, run away and pretend to be victim annoy the old grumpy cat, start a + fight and then retreat to wash when i lose or meow go back to sleep owner brings food and water tries to pet on + head, so scratch get sprayed by water because bad cat. Meowwww pelt around the house and up and down stairs chasing + phantoms drink water out of the faucet meow meow, i tell my human. Destroy couch.

+

Ask to go outside and ask to come inside and ask to go outside and ask to come inside the dog smells bad. Lick + butt and make a weird face. Toilet paper attack claws fluff everywhere meow miao french ciao litterbox. Shake treat + bag immediately regret falling into bathtub or white cat sleeps on a black shirt so what a cat-ass-trophy! eat + owner's food spit up on light gray carpet instead of adjacent linoleum. Warm up laptop with butt lick butt fart + rainbows until owner yells pee in litter box hiss at cats scratch the box so loved it, hated it, loved it, hated it + but need to check on human, have not seen in an hour might be dead oh look, human is alive, hiss at human, feed me. +

+
+ + + + diff --git a/src/core/client/embed/articleButton.html b/src/core/client/embed/articleButton.html new file mode 100644 index 000000000..bf0c0cf38 --- /dev/null +++ b/src/core/client/embed/articleButton.html @@ -0,0 +1,103 @@ + + + + + Talk 5.0 – Embed Stream + + + + + + + +

+ Default | Article +

+

Talk 5.0 – Article with Button

+

Dismember a mouse and then regurgitate parts of it on the family room floor. Dont wait for the storm to pass, + dance in the rain stand in front of the computer screen, so stares at human while pushing stuff off a table chew + the plant meow hiss at vacuum cleaner. Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not + sorry chew the plant. Litter kitter kitty litty little kitten big roar roar feed me rub whiskers on bare skin act + innocent sleep on keyboard, so give me attention or face the wrath of my claws for demand to be let outside at + once, and expect owner to wait for me as i think about it spread kitty litter all over house so nya nya nyan. Catty + ipsum massacre a bird in the living room and then look like the cutest and most innocent animal on the planet you + have cat to be kitten me right meow. Hiss and stare at nothing then run suddenly away refuse to come home when + humans are going to bed; stay out all night then yowl like i am dying at 4am and lick plastic bags. Chase dog then + run away purrr purr littel cat, little cat purr purr and step on your keyboard while you're gaming and then turn in + a circle . Twitch tail in permanent irritation put butt in owner's face and the dog smells bad yet attempt to leap + between furniture but woefully miscalibrate and bellyflop onto the floor; what's your problem? i meant to do that + now i shall wash myself intently. Sniff all the things groom forever, stretch tongue and leave it slightly out, + blep, but bring your owner a dead bird decide to want nothing to do with my owner today for lay on arms while + you're using the keyboard meow meow, i tell my human or scratch. Sleep on my human's head then cats take over the + world bleghbleghvomit my furball really tie the room together sleep more napping, more napping all the napping is + exhausting. When in doubt, wash drink water out of the faucet, cats are fats i like to pets them they like to meow + back and cat dog hate mouse eat string barf pillow no baths hate everything yet swat at dog kitty kitty but you + call this cat food. Cough furball into food bowl then scratch owner for a new one flex claws on the human's belly + and purr like a lawnmower for has closed eyes but still sees you groom yourself 4 hours - checked, have your beauty + sleep 18 hours - checked, be fabulous for the rest of the day - checked. Freak human out make funny noise mow mow + mow mow mow mow success now attack human flex claws on the human's belly and purr like a lawnmower or meowwww. + Terrorize the hundred-and-twenty-pound rottweiler and steal his bed, not sorry paw at your fat belly so yowling + nonstop the whole night small kitty warm kitty little balls of fur or eat owner's food reward the chosen human with + a slow blink. Gate keepers of hell plan steps for world domination for more napping, more napping all the napping + is exhausting give me some of your food give me some of your food give me some of your food meh, i don't want it so + flop over. Make meme, make cute face ears back wide eyed so sit and stare. Dead stare with ears cocked furrier and + even more furrier hairball. Stand in front of the computer screen demand to have some of whatever the human is + cooking, then sniff the offering and walk away for catasstrophe, kitty scratches couch bad kitty. Wack the mini + furry mouse intrigued by the shower, and pooping rainbow while flying in a toasted bread costume in space. + Mesmerizing birds love me! shake treat bag, yet lies down where is my slave? I'm getting hungry so lick face hiss + at owner, pee a lot, and meow repeatedly scratch at fence purrrrrr eat muffins and poutine until owner comes back. + You have cat to be kitten me right meow sniff other cat's butt and hang jaw half open thereafter but run outside as + soon as door open so munch on tasty moths or munch on tasty moths, for paw at beetle and eat it before it gets + away. Sit on human. Gnaw the corn cob massacre a bird in the living room and then look like the cutest and most + innocent animal on the planet for sit on the laptop. Meow scratch leg; meow for can opener to feed me cat fur is + the new black but hide when guests come over, and Gate keepers of hell. Refuse to come home when humans are going + to bed; stay out all night then yowl like i am dying at 4am cat slap dog in face or eat a rug and furry furry hairs + everywhere oh no human coming lie on counter don't get off counter for i like fish sit on human they not getting up + ever but meow meow but cuddle no cuddle cuddle love scratch scratch.

+

I show my fluffy belly but it's a trap! if you pet it i will tear up your hand refuse to drink water except out of + someone's glass mice, so cough hairball, eat toilet paper or curl into a furry donut lick sellotape but wack the + mini furry mouse. When owners are asleep, cry for no apparent reason. Chase imaginary bugs. Stinky cat reward the + chosen human with a slow blink, or chase dog then run away. Chew on cable scratch the furniture for you are a + captive audience while sitting on the toilet, pet me for i like cats because they are fat and fluffy and spend all + night ensuring people don't sleep sleep all day. Scoot butt on the rug need to check on human, have not seen in an + hour might be dead oh look, human is alive, hiss at human, feed me, leave fur on owners clothes, so instantly break + out into full speed gallop across the house for no reason play riveting piece on synthesizer keyboard and scoot + butt on the rug yet meow meow. Attack dog, run away and pretend to be victim annoy the old grumpy cat, start a + fight and then retreat to wash when i lose or meow go back to sleep owner brings food and water tries to pet on + head, so scratch get sprayed by water because bad cat. Meowwww pelt around the house and up and down stairs chasing + phantoms drink water out of the faucet meow meow, i tell my human. Destroy couch.

+

Ask to go outside and ask to come inside and ask to go outside and ask to come inside the dog smells bad. Lick + butt and make a weird face. Toilet paper attack claws fluff everywhere meow miao french ciao litterbox. Shake treat + bag immediately regret falling into bathtub or white cat sleeps on a black shirt so what a cat-ass-trophy! eat + owner's food spit up on light gray carpet instead of adjacent linoleum. Warm up laptop with butt lick butt fart + rainbows until owner yells pee in litter box hiss at cats scratch the box so loved it, hated it, loved it, hated it + but need to check on human, have not seen in an hour might be dead oh look, human is alive, hiss at human, feed me. +

+
+
+ +
+ + + + diff --git a/src/core/client/embed/index.html b/src/core/client/embed/index.html index b579dab8f..e0e8f74e6 100644 --- a/src/core/client/embed/index.html +++ b/src/core/client/embed/index.html @@ -9,16 +9,21 @@ -

Talk 5.0 – Embed Stream

+

+ Article | Article With Button +

+

Talk 5.0 – Embed Stream

diff --git a/src/core/client/embed/index.spec.ts b/src/core/client/embed/index.spec.ts index 66613f1c7..696e6adba 100644 --- a/src/core/client/embed/index.spec.ts +++ b/src/core/client/embed/index.spec.ts @@ -1,8 +1,10 @@ -import * as Talk from "./"; +import mockConsole from "jest-mock-console"; +import * as Coral from "./"; + +// tslint:disable:no-console describe("Basic integration test", () => { const container: HTMLElement = document.createElement("div"); - let streamInterface: ReturnType; beforeAll(() => { container.id = "basic-integration-test-id"; document.body.appendChild(container); @@ -11,13 +13,40 @@ describe("Basic integration test", () => { document.body.removeChild(container); }); it("should render iframe", () => { - streamInterface = Talk.render({ + mockConsole(); + const TalkEmbedStream = Coral.Talk.createStreamEmbed({ id: "basic-integration-test-id", }); + TalkEmbedStream.render(); expect(container.innerHTML).toMatchSnapshot(); + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.error).not.toHaveBeenCalled(); + }); + it("should use canonical link", () => { + mockConsole(); + const link = document.createElement("link"); + link.rel = "canonical"; + link.href = "http://localhost/canonical"; + document.head.appendChild(link); + const TalkEmbedStream = Coral.Talk.createStreamEmbed({ + id: "basic-integration-test-id", + }); + TalkEmbedStream.render(); + expect(container.innerHTML).toMatchSnapshot(); + document.head.removeChild(link); + expect(console.warn).not.toHaveBeenCalled(); + expect(console.error).not.toHaveBeenCalled(); }); it("should remove iframe", () => { - streamInterface.remove(); + mockConsole(); + const TalkEmbedStream = Coral.Talk.createStreamEmbed({ + id: "basic-integration-test-id", + }); + TalkEmbedStream.render(); + TalkEmbedStream.remove(); expect(container.innerHTML).toBe(""); + // tslint:disable-next-line:no-console + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.error).not.toHaveBeenCalled(); }); }); diff --git a/src/core/client/embed/index.ts b/src/core/client/embed/index.ts index c440f72a8..542bc1caa 100644 --- a/src/core/client/embed/index.ts +++ b/src/core/client/embed/index.ts @@ -1,32 +1,2 @@ -import { EventEmitter2 } from "eventemitter2"; -import qs from "query-string"; - -import createStreamInterface from "./Stream"; - -export interface Config { - assetID?: string; - assetURL?: string; - commentID?: string; - rootURL?: string; - id?: string; - events?: (eventEmitter: EventEmitter2) => void; -} - -export function render(config: Config = {}) { - // Parse query params - const query = qs.parse(location.search); - const eventEmitter = new EventEmitter2({ wildcard: true }); - - if (config.events) { - config.events(eventEmitter); - } - - 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, - }); -} +import * as TalkImport from "./Talk"; +export const Talk = TalkImport; diff --git a/src/core/client/embed/onIntersect.ts b/src/core/client/embed/onIntersect.ts new file mode 100644 index 000000000..761abf970 --- /dev/null +++ b/src/core/client/embed/onIntersect.ts @@ -0,0 +1,20 @@ +export default function onIntersect(el: HTMLElement, callback: () => void) { + if (!IntersectionObserver) { + // tslint:disable-next-line:no-console + console.warn("IntersectionObserver not available"); + callback(); + return; + } + const options = { + rootMargin: "100px", + threshold: 1.0, + }; + + const observer = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + observer.disconnect(); + callback(); + } + }, options); + observer.observe(el); +} diff --git a/src/core/client/embed/utils/index.ts b/src/core/client/embed/utils/index.ts index 9a373ce0b..84796bfff 100644 --- a/src/core/client/embed/utils/index.ts +++ b/src/core/client/embed/utils/index.ts @@ -2,3 +2,4 @@ export { default as buildURL } from "./buildURL"; export { default as ensureEndSlash } from "./ensureEndSlash"; export { default as startsWith } from "./startsWith"; export { default as prefixStorage } from "./prefixStorage"; +export { default as parseHashQuery } from "./parseHashQuery"; diff --git a/src/core/client/embed/utils/parseHashQuery.spec.ts b/src/core/client/embed/utils/parseHashQuery.spec.ts new file mode 100644 index 000000000..93a00de8e --- /dev/null +++ b/src/core/client/embed/utils/parseHashQuery.spec.ts @@ -0,0 +1,24 @@ +import parseHashQuery from "./parseHashQuery"; + +it("should parse hash", () => { + const testCases: Array<[string, ReturnType]> = [ + [ + "#commentID=comment-id", + { + commentID: "comment-id", + }, + ], + [ + "#commentID=comment-id&assetURL=asset-url", + { + commentID: "comment-id", + assetURL: "asset-url", + }, + ], + ["#", {}], + ["", {}], + ]; + testCases.forEach(tc => { + expect(parseHashQuery(tc[0])).toEqual(tc[1]); + }); +}); diff --git a/src/core/client/embed/utils/parseHashQuery.ts b/src/core/client/embed/utils/parseHashQuery.ts new file mode 100644 index 000000000..daf5f5a5a --- /dev/null +++ b/src/core/client/embed/utils/parseHashQuery.ts @@ -0,0 +1,5 @@ +import qs from "query-string"; + +export default function parseQueryHash(hash: string): Record { + return qs.parse(hash); +} diff --git a/src/core/client/framework/lib/relay/createMutationContainer.tsx b/src/core/client/framework/lib/relay/createMutationContainer.tsx index b8c74be18..3084635c9 100644 --- a/src/core/client/framework/lib/relay/createMutationContainer.tsx +++ b/src/core/client/framework/lib/relay/createMutationContainer.tsx @@ -42,7 +42,7 @@ function createMutationContainer( }; public render() { - const { relayEnvironment: _, ...rest } = this.props; + const { context: _, ...rest } = this.props; const inject = { [propName]: this.commit, }; diff --git a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx index 444b67fa2..484928fc7 100644 --- a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx +++ b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx @@ -39,6 +39,7 @@ export const LOCAL_ID = "client:root.local"; function withLocalStateContainer( fragmentSpec: GraphQLTaggedNode ): InferableComponentEnhancer<{ local: _RefType }> { + const fragment = (fragmentSpec as any).data().default; return compose( withContext(({ relayEnvironment }) => ({ relayEnvironment })), hoistStatics((BaseComponent: React.ComponentType) => { @@ -49,9 +50,7 @@ function withLocalStateContainer( ); private subscription: Disposable; - constructor(props: Props) { - super(props); - const fragment = (fragmentSpec as any).data().default; + private subscribe(environment: Environment) { if (fragment.kind !== "Fragment") { throw new Error("Expected fragment"); } @@ -65,24 +64,44 @@ function withLocalStateContainer( node: { selections: fragment.selections }, variables: {}, }; - const snapshot = props.relayEnvironment.lookup(selector); - this.subscription = props.relayEnvironment.subscribe( + const snapshot = environment.lookup(selector); + this.subscription = environment.subscribe( snapshot, this.updateSnapshot ); - this.state = { - data: snapshot.data, - }; + this.updateSnapshot(snapshot); + } + + constructor(props: Props) { + super(props); + this.subscribe(props.relayEnvironment); } private updateSnapshot = (snapshot: CSnapshot) => { - this.setState({ data: snapshot.data }); + const nextState = { data: snapshot.data }; + // State has not been initialized yet. + if (!this.state) { + this.state = nextState; + return; + } + this.setState(nextState); }; - public componentWillUnmount() { + private unsubscribe() { this.subscription.dispose(); } + public componentWillReceiveProps(next: Props) { + if (this.props.relayEnvironment !== next.relayEnvironment) { + this.unsubscribe(); + this.subscribe(next.relayEnvironment); + } + } + + public componentWillUnmount() { + this.unsubscribe(); + } + public render() { const { relayEnvironment: _, ...rest } = this.props; return ; diff --git a/src/core/client/framework/utils/index.ts b/src/core/client/framework/utils/index.ts index 4eca76650..0b16c4981 100644 --- a/src/core/client/framework/utils/index.ts +++ b/src/core/client/framework/utils/index.ts @@ -1,2 +1,3 @@ export { default as buildURL } from "./buildURL"; export { default as parseURL } from "./parseURL"; +export { default as modifyQuery } from "./modifyQuery"; diff --git a/src/core/client/framework/utils/modifyQuery.spec.ts b/src/core/client/framework/utils/modifyQuery.spec.ts new file mode 100644 index 000000000..638554e8f --- /dev/null +++ b/src/core/client/framework/utils/modifyQuery.spec.ts @@ -0,0 +1,30 @@ +import modifyQuery from "./modifyQuery"; + +it("should modify query", () => { + const testCases: Array<[string, Record, string]> = [ + [ + "http://localhost:8080/?a=b#hash", + { + c: "d", + }, + "http://localhost:8080/?a=b&c=d#hash", + ], + [ + "http://localhost:8080/#hash", + { + a: "b", + }, + "http://localhost:8080/?a=b#hash", + ], + [ + "http://localhost:8080/?a=b#hash", + { + a: undefined, + }, + "http://localhost:8080/#hash", + ], + ]; + testCases.forEach(([url, params, expected]) => { + expect(modifyQuery(url, params)).toEqual(expected); + }); +}); diff --git a/src/core/client/framework/utils/modifyQuery.ts b/src/core/client/framework/utils/modifyQuery.ts new file mode 100644 index 000000000..b3f0cc41a --- /dev/null +++ b/src/core/client/framework/utils/modifyQuery.ts @@ -0,0 +1,11 @@ +import qs from "query-string"; + +import buildURL from "./buildURL"; +import parseURL from "./parseURL"; + +export default function modifyQuery(url: string, params: {}) { + const parsed = parseURL(url); + const query = qs.parse(parsed.search); + parsed.search = qs.stringify({ ...query, ...params }); + return buildURL(parsed); +} diff --git a/src/core/client/framework/utils/parseHashQuery.spec.ts b/src/core/client/framework/utils/parseHashQuery.spec.ts new file mode 100644 index 000000000..2cbb6eb4d --- /dev/null +++ b/src/core/client/framework/utils/parseHashQuery.spec.ts @@ -0,0 +1,24 @@ +import parseHashQuery from "./parseHashQuery"; + +it("should parse hash", () => { + const testCases: Array<[string, ReturnType]> = [ + [ + "#commentID=comment-id", + { + commentID: "comment-id", + }, + ], + [ + "#commentID=comment-id&assetURL=asset-url", + { + commentID: "comment-id", + assetURL: "asset-url", + }, + ], + ["#", {}], + ["", {}], + ]; + testCases.forEach(([url, expected]) => { + expect(parseHashQuery(url)).toEqual(expected); + }); +}); diff --git a/src/core/client/framework/utils/parseHashQuery.ts b/src/core/client/framework/utils/parseHashQuery.ts new file mode 100644 index 000000000..daf5f5a5a --- /dev/null +++ b/src/core/client/framework/utils/parseHashQuery.ts @@ -0,0 +1,5 @@ +import qs from "query-string"; + +export default function parseQueryHash(hash: string): Record { + return qs.parse(hash); +} diff --git a/src/core/client/stream/local/initLocalState.ts b/src/core/client/stream/local/initLocalState.ts index b2f09b110..b722b630e 100644 --- a/src/core/client/stream/local/initLocalState.ts +++ b/src/core/client/stream/local/initLocalState.ts @@ -42,12 +42,8 @@ export default async function initLocalState( 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.assetURL) { + localRecord.setValue(query.assetURL, "assetURL"); } if (query.commentID) { diff --git a/src/core/client/stream/tabs/comments/components/Comment/Comment.spec.tsx b/src/core/client/stream/tabs/comments/components/Comment/Comment.spec.tsx index d9c0a9acf..a793f4d1c 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/Comment.spec.tsx +++ b/src/core/client/stream/tabs/comments/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/tabs/comments/components/Comment/Comment.tsx b/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx index aee9d5740..b30621913 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx @@ -10,6 +10,7 @@ import TopBarLeft from "./TopBarLeft"; import Username from "./Username"; export interface CommentProps { + id?: string; className?: string; author: { username: string | null; @@ -28,6 +29,7 @@ const Comment: StatelessComponent = props => { className={styles.topBar} direction="row" justifyContent="space-between" + id={props.id} > {props.author && diff --git a/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap index 64419b0fa..09cd14a62 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap @@ -8,6 +8,7 @@ exports[`renders username and body 1`] = ` diff --git a/src/core/client/stream/tabs/comments/components/Indent.css b/src/core/client/stream/tabs/comments/components/Indent.css index 6aa4c5e7a..b88e0b642 100644 --- a/src/core/client/stream/tabs/comments/components/Indent.css +++ b/src/core/client/stream/tabs/comments/components/Indent.css @@ -6,6 +6,30 @@ border-left: 3px solid var(--palette-grey-darkest); } +.level2 { + padding-left: var(--spacing-unit); + margin-left: var(--spacing-unit); + border-left: 3px solid var(--palette-grey-dark); +} + +.level3 { + padding-left: var(--spacing-unit); + margin-left: calc(2 * var(--spacing-unit)); + border-left: 3px solid var(--palette-grey-main); +} + +.level4 { + padding-left: var(--spacing-unit); + margin-left: calc(3 * var(--spacing-unit)); + border-left: 3px solid var(--palette-grey-light); +} + +.level5 { + padding-left: var(--spacing-unit); + margin-left: calc(4 * var(--spacing-unit)); + border-left: 3px solid var(--palette-grey-lighter); +} + .noBorder { border: 0; } diff --git a/src/core/client/stream/tabs/comments/components/Indent.tsx b/src/core/client/stream/tabs/comments/components/Indent.tsx index e40d10181..e91cdb974 100644 --- a/src/core/client/stream/tabs/comments/components/Indent.tsx +++ b/src/core/client/stream/tabs/comments/components/Indent.tsx @@ -12,13 +12,19 @@ export interface IndentProps { const Indent: StatelessComponent = props => { return ( -
- {props.children} +
+
+ {props.children} +
); }; diff --git a/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx b/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx index cf0fad56c..d93fb46e8 100644 --- a/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx +++ b/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx @@ -15,7 +15,7 @@ import PermalinkPopover from "./PermalinkPopover"; interface PermalinkProps { commentID: string; - assetURL: string | null; + url: string; } class Permalink extends React.Component { @@ -28,7 +28,7 @@ class Permalink extends React.Component { ); public render() { - const { commentID, assetURL } = this.props; + const { commentID, url } = this.props; const popoverID = `permalink-popover-${commentID}`; return ( { } > diff --git a/src/core/client/stream/tabs/comments/components/ReplyList.tsx b/src/core/client/stream/tabs/comments/components/ReplyList.tsx index 47e0dbc39..e942ae5a9 100644 --- a/src/core/client/stream/tabs/comments/components/ReplyList.tsx +++ b/src/core/client/stream/tabs/comments/components/ReplyList.tsx @@ -21,6 +21,17 @@ export interface ReplyListProps { hasMore: boolean; disableShowAll: boolean; indentLevel?: number; + ReplyListComponent?: React.ComponentType; +} + +function getReplyListElement( + { ReplyListComponent, me, asset }: ReplyListProps, + comment: PropTypesOf["comment"] +) { + if (!ReplyListComponent) { + return null; + } + return ; } const ReplyList: StatelessComponent = props => { @@ -30,13 +41,16 @@ const ReplyList: StatelessComponent = props => { role="log" > {props.comments.map(comment => ( - + + + {getReplyListElement(props, comment)} + ))} {props.hasMore && ( diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/Indent.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/Indent.spec.tsx.snap index d1b2bd78f..49a938937 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/Indent.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/Indent.spec.tsx.snap @@ -4,28 +4,40 @@ exports[`renders level0 1`] = `
-
- Hello World +
+
+ Hello World +
`; exports[`renders level1 1`] = `
-
- Hello World +
+
+ Hello World +
`; exports[`renders without border 1`] = `
-
- Hello World +
+
+ Hello World +
`; diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap index 3473f9fe0..cd78852c3 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap @@ -5,36 +5,44 @@ exports[`renders correctly 1`] = ` id="talk-comments-replyList-log--comment-id" role="log" > - - + + + + > + + `; @@ -43,36 +51,44 @@ exports[`when there is more disables load more button 1`] = ` id="talk-comments-replyList-log--comment-id" role="log" > - - + + + + > + + - - + + + + > + + - - - - - - - - { return ( <> = ({ commentID, }) => { return local.assetURL ? ( - + ) : null; }; diff --git a/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx b/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx index 664168e36..a21057c04 100644 --- a/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx @@ -40,6 +40,18 @@ class PermalinkViewContainer extends React.Component< // Remove the commentId url param. return buildURL({ ...urlParts, search }); } + + public componentDidMount() { + if (this.props.pym) { + const scrollTo = this.props.comment + ? document + .getElementById(`comment-${this.props.comment.id}`)! + .getBoundingClientRect().top + window.pageYOffset + : 50; + setTimeout(() => this.props.pym!.scrollParentToChildPos(scrollTo), 100); + } + } + public render() { const { comment, asset, me } = this.props; return ( @@ -66,6 +78,7 @@ const enhanced = withContext(ctx => ({ `, comment: graphql` fragment PermalinkViewContainer_comment on Comment { + id ...CommentContainer_comment } `, diff --git a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx index a23402ce1..08508b157 100644 --- a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx +++ b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx @@ -27,6 +27,8 @@ it("renders correctly", () => { isLoading: noop, } as any, me: null, + indentLevel: 1, + ReplyListComponent: () => null, }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -46,6 +48,8 @@ it("renders correctly when replies are null", () => { isLoading: noop, } as any, me: null, + indentLevel: 1, + ReplyListComponent: undefined, }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -69,6 +73,8 @@ describe("when has more replies", () => { loadMore: (_: any, callback: () => void) => (finishLoading = callback), } as any, me: null, + indentLevel: 1, + ReplyListComponent: undefined, }; let wrapper: ShallowWrapper; diff --git a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx index 65c6b9ec9..f9744f55a 100644 --- a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.tsx @@ -1,15 +1,15 @@ import React from "react"; -import { graphql, RelayPaginationProp } from "react-relay"; +import { graphql, GraphQLTaggedNode, RelayPaginationProp } from "react-relay"; +import { withProps } from "recompose"; import { withPaginationContainer } from "talk-framework/lib/relay"; -import { PropTypesOf } from "talk-framework/types"; -import { ReplyListContainer_asset as AssetData } from "talk-stream/__generated__/ReplyListContainer_asset.graphql"; -import { ReplyListContainer_comment as CommentData } from "talk-stream/__generated__/ReplyListContainer_comment.graphql"; -import { ReplyListContainer_me as MeData } from "talk-stream/__generated__/ReplyListContainer_me.graphql"; +import { ReplyListContainer1_asset as AssetData } from "talk-stream/__generated__/ReplyListContainer1_asset.graphql"; +import { ReplyListContainer1_comment as CommentData } from "talk-stream/__generated__/ReplyListContainer1_comment.graphql"; +import { ReplyListContainer1_me as MeData } from "talk-stream/__generated__/ReplyListContainer1_me.graphql"; import { COMMENT_SORT, - ReplyListContainerPaginationQueryVariables, -} from "talk-stream/__generated__/ReplyListContainerPaginationQuery.graphql"; + ReplyListContainer1PaginationQueryVariables, +} from "talk-stream/__generated__/ReplyListContainer1PaginationQuery.graphql"; import ReplyList from "../components/ReplyList"; @@ -18,8 +18,16 @@ export interface InnerProps { asset: AssetData; comment: CommentData; relay: RelayPaginationProp; + indentLevel: number; + ReplyListComponent: React.ComponentType | undefined; } +// TODO: (cvle) This should be autogenerated. +interface FragmentVariables { + count: number; + cursor?: string; + orderBy: COMMENT_SORT; +} export class ReplyListContainer extends React.Component { public state = { disableShowAll: false, @@ -42,7 +50,8 @@ export class ReplyListContainer extends React.Component { onShowAll={this.showAll} hasMore={this.props.relay.hasMore()} disableShowAll={this.state.disableShowAll} - indentLevel={1} + indentLevel={this.props.indentLevel} + ReplyListComponent={this.props.ReplyListComponent} /> ); } @@ -66,31 +75,61 @@ export class ReplyListContainer extends React.Component { }; } -// TODO: (cvle) This should be autogenerated. -interface FragmentVariables { - count: number; - cursor?: string; - orderBy: COMMENT_SORT; +function createReplyListContainer( + indentLevel: number, + fragments: { + me: GraphQLTaggedNode; + asset: GraphQLTaggedNode; + comment: GraphQLTaggedNode; + }, + query: GraphQLTaggedNode, + ReplyListComponent?: React.ComponentType +) { + return withProps({ indentLevel, ReplyListComponent })( + withPaginationContainer< + InnerProps, + ReplyListContainer1PaginationQueryVariables, + FragmentVariables + >(fragments, { + direction: "forward", + getConnectionFromProps(props) { + return props.comment && props.comment.replies; + }, + // This is also the default implementation of `getFragmentVariables` if it isn't provided. + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + count: totalCount, + }; + }, + getVariables(props, { count, cursor }, fragmentVariables) { + return { + count, + cursor, + orderBy: fragmentVariables.orderBy, + commentID: props.comment.id, + }; + }, + query, + })(ReplyListContainer) + ); } -const enhanced = withPaginationContainer< - InnerProps, - ReplyListContainerPaginationQueryVariables, - FragmentVariables ->( +const ReplyListContainer5 = createReplyListContainer( + 5, { me: graphql` - fragment ReplyListContainer_me on User { + fragment ReplyListContainer5_me on User { ...CommentContainer_me } `, asset: graphql` - fragment ReplyListContainer_asset on Asset { + fragment ReplyListContainer5_asset on Asset { ...CommentContainer_asset } `, comment: graphql` - fragment ReplyListContainer_comment on Comment + fragment ReplyListContainer5_comment on Comment @argumentDefinitions( count: { type: "Int!", defaultValue: 5 } cursor: { type: "Cursor" } @@ -109,43 +148,237 @@ const enhanced = withPaginationContainer< } `, }, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query ReplyListContainer5PaginationQuery( + $count: Int! + $cursor: Cursor + $orderBy: COMMENT_SORT! + $commentID: ID! + ) { + comment(id: $commentID) { + ...ReplyListContainer5_comment + @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + } + } + ` +); + +const ReplyListContainer4 = createReplyListContainer( + 4, { - direction: "forward", - getConnectionFromProps(props) { - return props.comment && props.comment.replies; - }, - // This is also the default implementation of `getFragmentVariables` if it isn't provided. - getFragmentVariables(prevVars, totalCount) { - return { - ...prevVars, - count: totalCount, - }; - }, - getVariables(props, { count, cursor }, fragmentVariables) { - return { - count, - cursor, - orderBy: fragmentVariables.orderBy, - commentID: props.comment.id, - }; - }, - query: graphql` - # Pagination query to be fetched upon calling 'loadMore'. - # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. - query ReplyListContainerPaginationQuery( - $count: Int! - $cursor: Cursor - $orderBy: COMMENT_SORT! - $commentID: ID! - ) { - comment(id: $commentID) { - ...ReplyListContainer_comment - @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + me: graphql` + fragment ReplyListContainer4_me on User { + ...ReplyListContainer5_me + ...CommentContainer_me + } + `, + asset: graphql` + fragment ReplyListContainer4_asset on Asset { + ...ReplyListContainer5_asset + ...CommentContainer_asset + } + `, + comment: graphql` + fragment ReplyListContainer4_comment on Comment + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC } + ) { + id + replies(first: $count, after: $cursor, orderBy: $orderBy) + @connection(key: "ReplyList_replies") { + edges { + node { + id + ...CommentContainer_comment + ...ReplyListContainer5_comment + } + } } } `, - } -)(ReplyListContainer); + }, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query ReplyListContainer4PaginationQuery( + $count: Int! + $cursor: Cursor + $orderBy: COMMENT_SORT! + $commentID: ID! + ) { + comment(id: $commentID) { + ...ReplyListContainer4_comment + @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + } + } + `, + ReplyListContainer5 +); -export type ReplyListContainerProps = PropTypesOf; -export default enhanced; +const ReplyListContainer3 = createReplyListContainer( + 3, + { + me: graphql` + fragment ReplyListContainer3_me on User { + ...ReplyListContainer4_me + ...CommentContainer_me + } + `, + asset: graphql` + fragment ReplyListContainer3_asset on Asset { + ...ReplyListContainer4_asset + ...CommentContainer_asset + } + `, + comment: graphql` + fragment ReplyListContainer3_comment on Comment + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC } + ) { + id + replies(first: $count, after: $cursor, orderBy: $orderBy) + @connection(key: "ReplyList_replies") { + edges { + node { + id + ...CommentContainer_comment + ...ReplyListContainer4_comment + } + } + } + } + `, + }, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query ReplyListContainer3PaginationQuery( + $count: Int! + $cursor: Cursor + $orderBy: COMMENT_SORT! + $commentID: ID! + ) { + comment(id: $commentID) { + ...ReplyListContainer3_comment + @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + } + } + `, + ReplyListContainer4 +); + +const ReplyListContainer2 = createReplyListContainer( + 2, + { + me: graphql` + fragment ReplyListContainer2_me on User { + ...ReplyListContainer3_me + ...CommentContainer_me + } + `, + asset: graphql` + fragment ReplyListContainer2_asset on Asset { + ...ReplyListContainer3_asset + ...CommentContainer_asset + } + `, + comment: graphql` + fragment ReplyListContainer2_comment on Comment + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC } + ) { + id + replies(first: $count, after: $cursor, orderBy: $orderBy) + @connection(key: "ReplyList_replies") { + edges { + node { + id + ...CommentContainer_comment + ...ReplyListContainer3_comment + } + } + } + } + `, + }, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query ReplyListContainer2PaginationQuery( + $count: Int! + $cursor: Cursor + $orderBy: COMMENT_SORT! + $commentID: ID! + ) { + comment(id: $commentID) { + ...ReplyListContainer2_comment + @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + } + } + `, + ReplyListContainer3 +); + +const ReplyListContainer1 = createReplyListContainer( + 1, + { + me: graphql` + fragment ReplyListContainer1_me on User { + ...ReplyListContainer2_me + ...CommentContainer_me + } + `, + asset: graphql` + fragment ReplyListContainer1_asset on Asset { + ...ReplyListContainer2_asset + ...CommentContainer_asset + } + `, + comment: graphql` + fragment ReplyListContainer1_comment on Comment + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC } + ) { + id + replies(first: $count, after: $cursor, orderBy: $orderBy) + @connection(key: "ReplyList_replies") { + edges { + node { + id + ...CommentContainer_comment + ...ReplyListContainer2_comment + } + } + } + } + `, + }, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query ReplyListContainer1PaginationQuery( + $count: Int! + $cursor: Cursor + $orderBy: COMMENT_SORT! + $commentID: ID! + ) { + comment(id: $commentID) { + ...ReplyListContainer1_comment + @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + } + } + `, + ReplyListContainer2 +); + +export default ReplyListContainer1; diff --git a/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx b/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx index 980d737b2..5ca2286bc 100644 --- a/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/StreamContainer.tsx @@ -23,7 +23,7 @@ graphql` fragment StreamContainer_comment on Comment { id ...CommentContainer_comment - ...ReplyListContainer_comment + ...ReplyListContainer1_comment } `; @@ -95,12 +95,12 @@ const enhanced = withPaginationContainer< } } ...CommentContainer_asset - ...ReplyListContainer_asset + ...ReplyListContainer1_asset } `, me: graphql` fragment StreamContainer_me on User { - ...ReplyListContainer_me + ...ReplyListContainer1_me ...CommentContainer_me ...UserBoxContainer_me } @@ -135,7 +135,7 @@ const enhanced = withPaginationContainer< $count: Int! $cursor: Cursor $orderBy: COMMENT_SORT! - $assetID: ID! + $assetID: ID ) { asset(id: $assetID) { ...StreamContainer_asset diff --git a/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap b/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap index f6e7f50ba..db1219282 100644 --- a/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap @@ -24,6 +24,7 @@ exports[`renders body only 1`] = ` /> } + id="comment-comment-id" indentLevel={1} showEditedMarker={false} /> @@ -54,6 +55,7 @@ exports[`renders username and body 1`] = ` /> } + id="comment-comment-id" indentLevel={1} showEditedMarker={false} /> diff --git a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap index e17a56740..ad14ab002 100644 --- a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap @@ -2,6 +2,7 @@ exports[`renders correctly 1`] = ` = ({ - local: { commentID, assetID }, + local: { commentID, assetID, assetURL }, }) => ( query={graphql` - query PermalinkViewQuery($commentID: ID!, $assetID: ID!) { + query PermalinkViewQuery( + $commentID: ID! + $assetID: ID + $assetURL: String + ) { me { ...PermalinkViewContainer_me } - asset(id: $assetID) { + asset(id: $assetID, url: $assetURL) { ...PermalinkViewContainer_asset } comment(id: $commentID) { @@ -62,8 +66,9 @@ const PermalinkViewQuery: StatelessComponent = ({ } `} variables={{ - assetID: assetID!, commentID: commentID!, + assetID, + assetURL, }} render={render} /> @@ -74,6 +79,7 @@ const enhanced = withLocalStateContainer( fragment PermalinkViewQueryLocal on Local { assetID commentID + assetURL } ` )(PermalinkViewQuery); diff --git a/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx b/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx index ec61ec723..da7879f8e 100644 --- a/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx +++ b/src/core/client/stream/tabs/comments/queries/StreamQuery.tsx @@ -38,21 +38,22 @@ export const render = ({ }; const StreamQuery: StatelessComponent = ({ - local: { assetID }, + local: { assetID, assetURL }, }) => ( query={graphql` - query StreamQuery($assetID: ID!) { + query StreamQuery($assetID: ID, $assetURL: String) { me { ...StreamContainer_me } - asset(id: $assetID) { + asset(id: $assetID, url: $assetURL) { ...StreamContainer_asset } } `} variables={{ - assetID: assetID!, + assetID, + assetURL, }} render={render} /> @@ -62,6 +63,7 @@ const enhanced = withLocalStateContainer( graphql` fragment StreamQueryLocal on Local { assetID + assetURL } ` )(StreamQuery); diff --git a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap index f24f5c40a..f6e5286bd 100644 --- a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap @@ -64,12 +64,103 @@ exports[`cancel edit: edit canceled 1`] = ` className="HorizontalGutter-root Stream-root HorizontalGutter-double" >
+
+
+
+
+
+ + Markus + +
+ +
+
+
+ +
+
+
+
+ +
+
+
+
+
+
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Sign Out +======= + className="" + > +
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -244,7 +396,11 @@ exports[`cancel edit: edit canceled 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Markus @@ -288,13 +449,33 @@ exports[`cancel edit: edit canceled 1`] = ` > Edit +======= + > + Lukas + +
+ +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 } } /> @@ -303,7 +484,11 @@ exports[`cancel edit: edit canceled 1`] = ` > +>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 } } /> @@ -1712,7 +1975,11 @@ exports[`edit a comment: render stream 1`] = ` >
+<<<<<<< HEAD
@@ -1839,6 +2107,8 @@ exports[`edit a comment: server response 1`] = ` Sign Out
+======= +>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -1966,6 +2237,67 @@ exports[`edit a comment: server response 1`] = ` > Submit +======= + className="" + > +
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -2070,7 +2402,11 @@ exports[`edit a comment: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
+<<<<<<< HEAD Lukas +======= + Markus +>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
+======= + dateTime="2018-07-06T18:24:00.000Z" + title="2018-07-06T18:24:00.000Z" + > + 2018-07-06T18:24:00.000Z + +
+ ( + + Edited + + ) +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
+
+ +
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 } } /> @@ -2113,7 +2494,11 @@ exports[`edit a comment: server response 1`] = ` > +======= + className="" + > +
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -2381,7 +2828,11 @@ exports[`shows expiry message: edit form closed 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Markus @@ -2423,6 +2879,36 @@ exports[`shows expiry message: edit form closed 1`] = ` onTouchEnd={[Function]} type="button" > +======= + > + Markus + +
+ +
+
+
+
@@ -2462,7 +2948,11 @@ exports[`shows expiry message: edit form closed 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Lukas
+======= + > + Lukas + +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Markus
+======= + > + Markus + +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -297,60 +319,65 @@ exports[`show all comments 1`] = ` className="Indent-root" >
- - Markus -
- + Markus + +
+ +
-
-
-
- + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap index 6441ad902..3c0648b22 100644 --- a/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap @@ -14,6 +14,7 @@ exports[`renders permalink view with unknown comment 1`] = ` onClick={[Function]} role="presentation" > +<<<<<<< HEAD
@@ -241,60 +248,65 @@ exports[`show all comments 1`] = ` className="Indent-root" >
- - Markus -
- + Markus + +
+ +
-
-
-
- + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap index 14b46db82..424e3199a 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap @@ -384,7 +384,11 @@ exports[`post a comment: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Lukas @@ -412,13 +421,49 @@ exports[`post a comment: optimistic response 1`] = ` 2018-07-06T18:20:00.000Z
+======= + > + Markus + +
+ +
+
+
+ +>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
Hello world!", +>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 } } /> @@ -427,7 +472,11 @@ exports[`post a comment: optimistic response 1`] = ` > +======= + className="" + > +
+
+
+ + Markus + +
+ +
+
+
+
+
+ +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -563,6 +674,7 @@ exports[`post a comment: server response 1`] = ` onSubmit={[Function]} >
@@ -681,6 +793,67 @@ exports[`post a comment: server response 1`] = ` > Submit +======= + className="" + > +
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -760,7 +933,11 @@ exports[`post a comment: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Hello world! (from server)", +>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 } } /> @@ -803,7 +988,11 @@ exports[`post a comment: server response 1`] = ` > +======= + className="" + > +
+
+
+ + Lukas + +
+ +
+
+
+
+
+ +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47
@@ -1136,7 +1418,11 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47 >
Markus
+======= + > + Markus + +
+>>>>>>> 65156c052820a6676b17c2fb7ff5df56b3183d47